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
;
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();
109 this._startCasting();
117 _startCasting: function()
119 if (WebInspector
.targetManager
.allTargetsSuspended())
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;
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
)
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())
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");
212 this._glassPaneElement
.classList
.add("hidden");
217 * @param {!Event} event
219 _handleMouseEvent: function(event
)
221 if (this._isGlassPaneActive()) {
226 if (!this._pageScaleFactor
)
229 if (!this._inspectModeConfig
|| event
.type
=== "mousewheel") {
230 this._simulateTouchForMouseEvent(event
);
231 event
.preventDefault();
232 if (event
.type
=== "mousedown")
233 this._canvasElement
.focus();
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
)
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()) {
265 var shortcutKey
= WebInspector
.KeyboardShortcut
.makeKeyFromEvent(/** @type {!KeyboardEvent} */ (event
));
266 var handler
= this._shortcuts
[shortcutKey
];
267 if (handler
&& handler(event
)) {
273 switch (event
.type
) {
274 case "keydown": type
= "keyDown"; break;
275 case "keyup": type
= "keyUp"; break;
276 case "keypress": type
= "char"; break;
280 var text
= event
.type
=== "keypress" ? String
.fromCharCode(event
.charCode
) : undefined;
281 this._target
.inputAgent().invoke_dispatchKeyEvent({
283 modifiers
: this._modifiersForEvent(event
),
284 timestamp
: event
.timeStamp
/ 1000,
286 unmodifiedText
: text
? text
.toLowerCase() : undefined,
287 keyIdentifier
: event
.keyIdentifier
,
290 windowsVirtualKeyCode
: event
.keyCode
,
291 nativeVirtualKeyCode
: event
.keyCode
,
294 isSystemKey
: false});
296 this._canvasElement
.focus();
300 * @param {!Event} event
302 _handleContextMenuEvent: function(event
)
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
))
316 if (event
.type
!== "mousewheel" && buttons
[event
.which
] === "none")
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
;
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
)
358 position
.x
= Math
.round(event
.offsetX
/ this._screenZoom
);
359 position
.y
= Math
.round(event
.offsetY
/ this._screenZoom
);
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
);
375 * @param {!Event} event
378 _modifiersForEvent: function(event
)
394 if (this._deferredCasting
) {
395 clearTimeout(this._deferredCasting
);
396 delete this._deferredCasting
;
400 this._deferredCasting
= setTimeout(this._startCasting
.bind(this), 100);
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
;
418 this._titleElement
.classList
.add("hidden");
424 node
.boxModel(callback
.bind(this));
427 * @param {?DOMAgent.BoxModel} model
428 * @this {WebInspector.ScreencastView}
430 function callback(model
)
432 if (!model
|| !this._pageScaleFactor
) {
436 this._model
= this._scaleModel(model
);
437 this._config
= config
;
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
);
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)";
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
522 _quadsAreEqual: function(quad1
, quad2
)
524 for (var i
= 0; i
< quad1
.length
; ++i
) {
525 if (quad1
[i
] !== quad2
[i
])
532 * @param {!DOMAgent.RGBA} color
535 _cssColor: function(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()
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;
623 if (anchorTop
> canvasHeight
) {
624 boxY
= canvasHeight
- titleHeight
- arrowHeight
;
625 renderArrowDown
= true;
626 } else if (anchorBottom
< 0) {
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;
638 this._context
.save();
639 this._context
.translate(0.5, 0.5);
640 this._context
.beginPath();
641 this._context
.moveTo(boxX
, boxY
);
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
};
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;
694 * @param {!PageAgent.FrameId} frameId
696 highlightFrame: function(frameId
)
701 * @param {!CanvasRenderingContext2D} context
703 _createCheckerboardPattern: function(context
)
705 var pattern
= /** @type {!HTMLCanvasElement} */(createElement("canvas"));
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
)
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')
765 var url
= this._navigationUrl
.value
;
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
)
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
);
794 InspectorFrontendHost
.inspectedURLChanged(url
);
795 this._navigationUrl
.value
= url
;
798 _focusNavigationBar: function()
800 this._navigationUrl
.focus();
801 this._navigationUrl
.select();
805 __proto__
: WebInspector
.VBox
.prototype
809 * @param {!Element} element
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.
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);
842 _navigationProgressVisible: function()
844 return !!this._requestIds
;
847 _onRequestStarted: function(event
)
849 if (!this._navigationProgressVisible())
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
)
855 this._requestIds
[request
.requestId
] = request
;
856 ++this._startedRequests
;
859 _onRequestFinished: function(event
)
861 if (!this._navigationProgressVisible())
863 var request
= /** @type {!WebInspector.NetworkRequest} */ (event
.data
);
864 if (!(request
.requestId
in this._requestIds
))
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())
876 if (this._maxDisplayedProgress
>= progress
)
878 this._maxDisplayedProgress
= progress
;
879 this._displayProgress(progress
);
882 _displayProgress: function(progress
)
884 this._element
.style
.width
= (100 * progress
) + "%";