ScreencastView.js 19 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. export default class ScreencastView extends UI.VBox{constructor(screenCaptureModel){super();this._screenCaptureModel=screenCaptureModel;this._domModel=screenCaptureModel.target().model(SDK.DOMModel);this._overlayModel=screenCaptureModel.target().model(SDK.OverlayModel);this._resourceTreeModel=screenCaptureModel.target().model(SDK.ResourceTreeModel);this._networkManager=screenCaptureModel.target().model(SDK.NetworkManager);this._inputModel=screenCaptureModel.target().model(Screencast.InputModel);this.setMinimumSize(150,150);this.registerRequiredCSS('screencast/screencastView.css');}
  2. initialize(){this.element.classList.add('screencast');this._createNavigationBar();this._viewportElement=this.element.createChild('div','screencast-viewport hidden');this._canvasContainerElement=this._viewportElement.createChild('div','screencast-canvas-container');this._glassPaneElement=this._canvasContainerElement.createChild('div','screencast-glasspane fill hidden');this._canvasElement=this._canvasContainerElement.createChild('canvas');this._canvasElement.tabIndex=0;this._canvasElement.addEventListener('mousedown',this._handleMouseEvent.bind(this),false);this._canvasElement.addEventListener('mouseup',this._handleMouseEvent.bind(this),false);this._canvasElement.addEventListener('mousemove',this._handleMouseEvent.bind(this),false);this._canvasElement.addEventListener('mousewheel',this._handleMouseEvent.bind(this),false);this._canvasElement.addEventListener('click',this._handleMouseEvent.bind(this),false);this._canvasElement.addEventListener('contextmenu',this._handleContextMenuEvent.bind(this),false);this._canvasElement.addEventListener('keydown',this._handleKeyEvent.bind(this),false);this._canvasElement.addEventListener('keyup',this._handleKeyEvent.bind(this),false);this._canvasElement.addEventListener('keypress',this._handleKeyEvent.bind(this),false);this._canvasElement.addEventListener('blur',this._handleBlurEvent.bind(this),false);this._titleElement=this._canvasContainerElement.createChild('div','screencast-element-title monospace hidden');this._tagNameElement=this._titleElement.createChild('span','screencast-tag-name');this._nodeIdElement=this._titleElement.createChild('span','screencast-node-id');this._classNameElement=this._titleElement.createChild('span','screencast-class-name');this._titleElement.createTextChild(' ');this._nodeWidthElement=this._titleElement.createChild('span');this._titleElement.createChild('span','screencast-px').textContent='px';this._titleElement.createTextChild(' \u00D7 ');this._nodeHeightElement=this._titleElement.createChild('span');this._titleElement.createChild('span','screencast-px').textContent='px';this._titleElement.style.top='0';this._titleElement.style.left='0';this._imageElement=new Image();this._isCasting=false;this._context=this._canvasElement.getContext('2d');this._checkerboardPattern=this._createCheckerboardPattern(this._context);this._shortcuts=({});this._shortcuts[UI.KeyboardShortcut.makeKey('l',UI.KeyboardShortcut.Modifiers.Ctrl)]=this._focusNavigationBar.bind(this);SDK.targetManager.addEventListener(SDK.TargetManager.Events.SuspendStateChanged,this._onSuspendStateChange,this);this._updateGlasspane();}
  3. wasShown(){this._startCasting();}
  4. willHide(){this._stopCasting();}
  5. _startCasting(){if(SDK.targetManager.allTargetsSuspended()){return;}
  6. if(this._isCasting){return;}
  7. this._isCasting=true;const maxImageDimension=2048;const dimensions=this._viewportDimensions();if(dimensions.width<0||dimensions.height<0){this._isCasting=false;return;}
  8. dimensions.width*=window.devicePixelRatio;dimensions.height*=window.devicePixelRatio;this._screenCaptureModel.startScreencast('jpeg',80,Math.floor(Math.min(maxImageDimension,dimensions.width)),Math.floor(Math.min(maxImageDimension,dimensions.height)),undefined,this._screencastFrame.bind(this),this._screencastVisibilityChanged.bind(this));for(const emulationModel of SDK.targetManager.models(SDK.EmulationModel)){emulationModel.overrideEmulateTouch(true);}
  9. if(this._overlayModel){this._overlayModel.setHighlighter(this);}}
  10. _stopCasting(){if(!this._isCasting){return;}
  11. this._isCasting=false;this._screenCaptureModel.stopScreencast();for(const emulationModel of SDK.targetManager.models(SDK.EmulationModel)){emulationModel.overrideEmulateTouch(false);}
  12. if(this._overlayModel){this._overlayModel.setHighlighter(null);}}
  13. _screencastFrame(base64Data,metadata){this._imageElement.onload=()=>{this._pageScaleFactor=metadata.pageScaleFactor;this._screenOffsetTop=metadata.offsetTop;this._scrollOffsetX=metadata.scrollOffsetX;this._scrollOffsetY=metadata.scrollOffsetY;const deviceSizeRatio=metadata.deviceHeight/metadata.deviceWidth;const dimensionsCSS=this._viewportDimensions();this._imageZoom=Math.min(dimensionsCSS.width/this._imageElement.naturalWidth,dimensionsCSS.height/(this._imageElement.naturalWidth*deviceSizeRatio));this._viewportElement.classList.remove('hidden');const bordersSize=_bordersSize;if(this._imageZoom<1.01/window.devicePixelRatio){this._imageZoom=1/window.devicePixelRatio;}
  14. this._screenZoom=this._imageElement.naturalWidth*this._imageZoom/metadata.deviceWidth;this._viewportElement.style.width=metadata.deviceWidth*this._screenZoom+bordersSize+'px';this._viewportElement.style.height=metadata.deviceHeight*this._screenZoom+bordersSize+'px';this.highlightInOverlay({node:this._highlightNode},this._highlightConfig);};this._imageElement.src='data:image/jpg;base64,'+base64Data;}
  15. _isGlassPaneActive(){return!this._glassPaneElement.classList.contains('hidden');}
  16. _screencastVisibilityChanged(visible){this._targetInactive=!visible;this._updateGlasspane();}
  17. _onSuspendStateChange(event){if(SDK.targetManager.allTargetsSuspended()){this._stopCasting();}else{this._startCasting();}
  18. this._updateGlasspane();}
  19. _updateGlasspane(){if(this._targetInactive){this._glassPaneElement.textContent=Common.UIString('The tab is inactive');this._glassPaneElement.classList.remove('hidden');}else if(SDK.targetManager.allTargetsSuspended()){this._glassPaneElement.textContent=Common.UIString('Profiling in progress');this._glassPaneElement.classList.remove('hidden');}else{this._glassPaneElement.classList.add('hidden');}}
  20. async _handleMouseEvent(event){if(this._isGlassPaneActive()){event.consume();return;}
  21. if(!this._pageScaleFactor||!this._domModel){return;}
  22. if(!this._inspectModeConfig||event.type==='mousewheel'){if(this._inputModel){this._inputModel.emitTouchFromMouseEvent(event,this._screenOffsetTop,this._screenZoom);}
  23. event.preventDefault();if(event.type==='mousedown'){this._canvasElement.focus();}
  24. return;}
  25. const position=this._convertIntoScreenSpace(event);const node=await this._domModel.nodeForLocation(Math.floor(position.x/this._pageScaleFactor+this._scrollOffsetX),Math.floor(position.y/this._pageScaleFactor+this._scrollOffsetY),Common.moduleSetting('showUAShadowDOM').get());if(!node){return;}
  26. if(event.type==='mousemove'){this.highlightInOverlay({node},this._inspectModeConfig);this._domModel.overlayModel().nodeHighlightRequested(node.id);}else if(event.type==='click'){this._domModel.overlayModel().inspectNodeRequested(node.backendNodeId());}}
  27. _handleKeyEvent(event){if(this._isGlassPaneActive()){event.consume();return;}
  28. const shortcutKey=UI.KeyboardShortcut.makeKeyFromEvent((event));const handler=this._shortcuts[shortcutKey];if(handler&&handler(event)){event.consume();return;}
  29. if(this._inputModel){this._inputModel.emitKeyEvent(event);}
  30. event.consume();this._canvasElement.focus();}
  31. _handleContextMenuEvent(event){event.consume(true);}
  32. _handleBlurEvent(event){if(this._inputModel){this._inputModel.cancelTouch();}}
  33. _convertIntoScreenSpace(event){const position={};position.x=Math.round(event.offsetX/this._screenZoom);position.y=Math.round(event.offsetY/this._screenZoom-this._screenOffsetTop);return position;}
  34. onResize(){if(this._deferredCasting){clearTimeout(this._deferredCasting);delete this._deferredCasting;}
  35. this._stopCasting();this._deferredCasting=setTimeout(this._startCasting.bind(this),100);}
  36. highlightInOverlay(data,config){this._highlightInOverlay(data,config);}
  37. async _highlightInOverlay(data,config){const{node:n,deferredNode,object}=data;let node=n;if(!node&&deferredNode){node=await deferredNode.resolvePromise();}
  38. if(!node&&object){const domModel=object.runtimeModel().target().model(SDK.DOMModel);if(domModel){node=await domModel.pushObjectAsNodeToFrontend(object);}}
  39. this._highlightNode=node;this._highlightConfig=config;if(!node){this._model=null;this._config=null;this._node=null;this._titleElement.classList.add('hidden');this._repaint();return;}
  40. this._node=node;node.boxModel().then(model=>{if(!model||!this._pageScaleFactor){this._repaint();return;}
  41. this._model=this._scaleModel(model);this._config=config;this._repaint();});}
  42. _scaleModel(model){function scaleQuad(quad){for(let i=0;i<quad.length;i+=2){quad[i]=quad[i]*this._pageScaleFactor*this._screenZoom;quad[i+1]=(quad[i+1]*this._pageScaleFactor+this._screenOffsetTop)*this._screenZoom;}}
  43. scaleQuad.call(this,model.content);scaleQuad.call(this,model.padding);scaleQuad.call(this,model.border);scaleQuad.call(this,model.margin);return model;}
  44. _repaint(){const model=this._model;const config=this._config;const canvasWidth=this._canvasElement.getBoundingClientRect().width;const canvasHeight=this._canvasElement.getBoundingClientRect().height;this._canvasElement.width=window.devicePixelRatio*canvasWidth;this._canvasElement.height=window.devicePixelRatio*canvasHeight;this._context.save();this._context.scale(window.devicePixelRatio,window.devicePixelRatio);this._context.save();this._context.fillStyle=this._checkerboardPattern;this._context.fillRect(0,0,canvasWidth,this._screenOffsetTop*this._screenZoom);this._context.fillRect(0,this._screenOffsetTop*this._screenZoom+this._imageElement.naturalHeight*this._imageZoom,canvasWidth,canvasHeight);this._context.restore();if(model&&config){this._context.save();const transparentColor='rgba(0, 0, 0, 0)';const quads=[];if(model.content&&config.contentColor!==transparentColor){quads.push({quad:model.content,color:config.contentColor});}
  45. if(model.padding&&config.paddingColor!==transparentColor){quads.push({quad:model.padding,color:config.paddingColor});}
  46. if(model.border&&config.borderColor!==transparentColor){quads.push({quad:model.border,color:config.borderColor});}
  47. if(model.margin&&config.marginColor!==transparentColor){quads.push({quad:model.margin,color:config.marginColor});}
  48. for(let i=quads.length-1;i>0;--i){this._drawOutlinedQuadWithClip(quads[i].quad,quads[i-1].quad,quads[i].color);}
  49. if(quads.length>0){this._drawOutlinedQuad(quads[0].quad,quads[0].color);}
  50. this._context.restore();this._drawElementTitle();this._context.globalCompositeOperation='destination-over';}
  51. this._context.drawImage(this._imageElement,0,this._screenOffsetTop*this._screenZoom,this._imageElement.naturalWidth*this._imageZoom,this._imageElement.naturalHeight*this._imageZoom);this._context.restore();}
  52. _cssColor(color){if(!color){return'transparent';}
  53. return Common.Color.fromRGBA([color.r,color.g,color.b,color.a]).asString(Common.Color.Format.RGBA)||'';}
  54. _quadToPath(quad){this._context.beginPath();this._context.moveTo(quad[0],quad[1]);this._context.lineTo(quad[2],quad[3]);this._context.lineTo(quad[4],quad[5]);this._context.lineTo(quad[6],quad[7]);this._context.closePath();return this._context;}
  55. _drawOutlinedQuad(quad,fillColor){this._context.save();this._context.lineWidth=2;this._quadToPath(quad).clip();this._context.fillStyle=this._cssColor(fillColor);this._context.fill();this._context.restore();}
  56. _drawOutlinedQuadWithClip(quad,clipQuad,fillColor){this._context.fillStyle=this._cssColor(fillColor);this._context.save();this._context.lineWidth=0;this._quadToPath(quad).fill();this._context.globalCompositeOperation='destination-out';this._context.fillStyle='red';this._quadToPath(clipQuad).fill();this._context.restore();}
  57. _drawElementTitle(){if(!this._node){return;}
  58. const canvasWidth=this._canvasElement.getBoundingClientRect().width;const canvasHeight=this._canvasElement.getBoundingClientRect().height;const lowerCaseName=this._node.localName()||this._node.nodeName().toLowerCase();this._tagNameElement.textContent=lowerCaseName;this._nodeIdElement.textContent=this._node.getAttribute('id')?'#'+this._node.getAttribute('id'):'';this._nodeIdElement.textContent=this._node.getAttribute('id')?'#'+this._node.getAttribute('id'):'';let className=this._node.getAttribute('class');if(className&&className.length>50){className=className.substring(0,50)+'\u2026';}
  59. this._classNameElement.textContent=className||'';this._nodeWidthElement.textContent=this._model.width;this._nodeHeightElement.textContent=this._model.height;this._titleElement.classList.remove('hidden');const titleWidth=this._titleElement.offsetWidth+6;const titleHeight=this._titleElement.offsetHeight+4;const anchorTop=this._model.margin[1];const anchorBottom=this._model.margin[7];const arrowHeight=7;let renderArrowUp=false;let renderArrowDown=false;let boxX=Math.max(2,this._model.margin[0]);if(boxX+titleWidth>canvasWidth){boxX=canvasWidth-titleWidth-2;}
  60. let boxY;if(anchorTop>canvasHeight){boxY=canvasHeight-titleHeight-arrowHeight;renderArrowDown=true;}else if(anchorBottom<0){boxY=arrowHeight;renderArrowUp=true;}else if(anchorBottom+titleHeight+arrowHeight<canvasHeight){boxY=anchorBottom+arrowHeight-4;renderArrowUp=true;}else if(anchorTop-titleHeight-arrowHeight>0){boxY=anchorTop-titleHeight-arrowHeight+3;renderArrowDown=true;}else{boxY=arrowHeight;}
  61. this._context.save();this._context.translate(0.5,0.5);this._context.beginPath();this._context.moveTo(boxX,boxY);if(renderArrowUp){this._context.lineTo(boxX+2*arrowHeight,boxY);this._context.lineTo(boxX+3*arrowHeight,boxY-arrowHeight);this._context.lineTo(boxX+4*arrowHeight,boxY);}
  62. this._context.lineTo(boxX+titleWidth,boxY);this._context.lineTo(boxX+titleWidth,boxY+titleHeight);if(renderArrowDown){this._context.lineTo(boxX+4*arrowHeight,boxY+titleHeight);this._context.lineTo(boxX+3*arrowHeight,boxY+titleHeight+arrowHeight);this._context.lineTo(boxX+2*arrowHeight,boxY+titleHeight);}
  63. this._context.lineTo(boxX,boxY+titleHeight);this._context.closePath();this._context.fillStyle='rgb(255, 255, 194)';this._context.fill();this._context.strokeStyle='rgb(128, 128, 128)';this._context.stroke();this._context.restore();this._titleElement.style.top=(boxY+3)+'px';this._titleElement.style.left=(boxX+3)+'px';}
  64. _viewportDimensions(){const gutterSize=30;const bordersSize=_bordersSize;const width=this.element.offsetWidth-bordersSize-gutterSize;const height=this.element.offsetHeight-bordersSize-gutterSize-_navBarHeight;return{width:width,height:height};}
  65. setInspectMode(mode,config){this._inspectModeConfig=mode!==Protocol.Overlay.InspectMode.None?config:null;return Promise.resolve();}
  66. highlightFrame(frameId){}
  67. _createCheckerboardPattern(context){const pattern=(createElement('canvas'));const size=32;pattern.width=size*2;pattern.height=size*2;const pctx=pattern.getContext('2d');pctx.fillStyle='rgb(195, 195, 195)';pctx.fillRect(0,0,size*2,size*2);pctx.fillStyle='rgb(225, 225, 225)';pctx.fillRect(0,0,size,size);pctx.fillRect(size,size,size,size);return context.createPattern(pattern,'repeat');}
  68. _createNavigationBar(){this._navigationBar=this.element.createChild('div','screencast-navigation');this._navigationBack=this._navigationBar.createChild('button','back');this._navigationBack.disabled=true;this._navigationForward=this._navigationBar.createChild('button','forward');this._navigationForward.disabled=true;this._navigationReload=this._navigationBar.createChild('button','reload');this._navigationUrl=UI.createInput();this._navigationBar.appendChild(this._navigationUrl);this._navigationUrl.type='text';this._navigationProgressBar=new Screencast.ScreencastView.ProgressTracker(this._resourceTreeModel,this._networkManager,this._navigationBar.createChild('div','progress'));if(this._resourceTreeModel){this._navigationBack.addEventListener('click',this._navigateToHistoryEntry.bind(this,-1),false);this._navigationForward.addEventListener('click',this._navigateToHistoryEntry.bind(this,1),false);this._navigationReload.addEventListener('click',this._navigateReload.bind(this),false);this._navigationUrl.addEventListener('keyup',this._navigationUrlKeyUp.bind(this),true);this._requestNavigationHistory();this._resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.MainFrameNavigated,this._requestNavigationHistory,this);this._resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.CachedResourcesLoaded,this._requestNavigationHistory,this);}}
  69. _navigateToHistoryEntry(offset){const newIndex=this._historyIndex+offset;if(newIndex<0||newIndex>=this._historyEntries.length){return;}
  70. this._resourceTreeModel.navigateToHistoryEntry(this._historyEntries[newIndex]);this._requestNavigationHistory();}
  71. _navigateReload(){this._resourceTreeModel.reloadPage();}
  72. _navigationUrlKeyUp(event){if(event.key!=='Enter'){return;}
  73. let url=this._navigationUrl.value;if(!url){return;}
  74. if(!url.match(_SchemeRegex)){url='http://'+url;}
  75. this._resourceTreeModel.navigate(url);this._canvasElement.focus();}
  76. async _requestNavigationHistory(){const history=await this._resourceTreeModel.navigationHistory();if(!history){return;}
  77. this._historyIndex=history.currentIndex;this._historyEntries=history.entries;this._navigationBack.disabled=this._historyIndex===0;this._navigationForward.disabled=this._historyIndex===(this._historyEntries.length-1);let url=this._historyEntries[this._historyIndex].url;const match=url.match(_HttpRegex);if(match){url=match[1];}
  78. Host.InspectorFrontendHost.inspectedURLChanged(url);this._navigationUrl.value=url;}
  79. _focusNavigationBar(){this._navigationUrl.focus();this._navigationUrl.select();return true;}}
  80. export const _bordersSize=44;export const _navBarHeight=29;export const _HttpRegex=/^http:\/\/(.+)/;export const _SchemeRegex=/^(https?|about|chrome):/;export class ProgressTracker{constructor(resourceTreeModel,networkManager,element){this._element=element;if(resourceTreeModel){resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.MainFrameNavigated,this._onMainFrameNavigated,this);resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.Load,this._onLoad,this);}
  81. if(networkManager){networkManager.addEventListener(SDK.NetworkManager.Events.RequestStarted,this._onRequestStarted,this);networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished,this._onRequestFinished,this);}}
  82. _onMainFrameNavigated(){this._requestIds={};this._startedRequests=0;this._finishedRequests=0;this._maxDisplayedProgress=0;this._updateProgress(0.1);}
  83. _onLoad(){delete this._requestIds;this._updateProgress(1);setTimeout(function(){if(!this._navigationProgressVisible()){this._displayProgress(0);}}.bind(this),500);}
  84. _navigationProgressVisible(){return!!this._requestIds;}
  85. _onRequestStarted(event){if(!this._navigationProgressVisible()){return;}
  86. const request=(event.data);if(request.type===Common.resourceTypes.WebSocket){return;}
  87. this._requestIds[request.requestId()]=request;++this._startedRequests;}
  88. _onRequestFinished(event){if(!this._navigationProgressVisible()){return;}
  89. const request=(event.data);if(!(request.requestId()in this._requestIds)){return;}
  90. ++this._finishedRequests;setTimeout(function(){this._updateProgress(this._finishedRequests/this._startedRequests*0.9);}.bind(this),500);}
  91. _updateProgress(progress){if(!this._navigationProgressVisible()){return;}
  92. if(this._maxDisplayedProgress>=progress){return;}
  93. this._maxDisplayedProgress=progress;this._displayProgress(progress);}
  94. _displayProgress(progress){this._element.style.width=(100*progress)+'%';}}
  95. self.Screencast=self.Screencast||{};Screencast=Screencast||{};Screencast.ScreencastView=ScreencastView;Screencast.ScreencastView._bordersSize=_bordersSize;Screencast.ScreencastView._navBarHeight=_navBarHeight;Screencast.ScreencastView._HttpRegex=_HttpRegex;Screencast.ScreencastView._SchemeRegex=_SchemeRegex;Screencast.ScreencastView.ProgressTracker=ProgressTracker;