InspectorBackend.js 13 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. export const ProtocolError=Symbol('Protocol.Error');export const DevToolsStubErrorCode=-32015;const _GenericError=-32000;const _ConnectionClosedErrorCode=-32001;export default class InspectorBackend{constructor(){this._agentPrototypes=new Map();this._dispatcherPrototypes=new Map();this._initialized=false;}
  2. static reportProtocolError(error,messageObject){console.error(error+': '+JSON.stringify(messageObject));}
  3. isInitialized(){return this._initialized;}
  4. _addAgentGetterMethodToProtocolTargetPrototype(domain){let upperCaseLength=0;while(upperCaseLength<domain.length&&domain[upperCaseLength].toLowerCase()!==domain[upperCaseLength]){++upperCaseLength;}
  5. const methodName=domain.substr(0,upperCaseLength).toLowerCase()+domain.slice(upperCaseLength)+'Agent';function agentGetter(){return this._agents[domain];}
  6. TargetBase.prototype[methodName]=agentGetter;function registerDispatcher(dispatcher){this.registerDispatcher(domain,dispatcher);}
  7. TargetBase.prototype['register'+domain+'Dispatcher']=registerDispatcher;}
  8. _agentPrototype(domain){if(!this._agentPrototypes.has(domain)){this._agentPrototypes.set(domain,new _AgentPrototype(domain));this._addAgentGetterMethodToProtocolTargetPrototype(domain);}
  9. return this._agentPrototypes.get(domain);}
  10. _dispatcherPrototype(domain){if(!this._dispatcherPrototypes.has(domain)){this._dispatcherPrototypes.set(domain,new _DispatcherPrototype());}
  11. return this._dispatcherPrototypes.get(domain);}
  12. registerCommand(method,signature,replyArgs,hasErrorData){const domainAndMethod=method.split('.');this._agentPrototype(domainAndMethod[0]).registerCommand(domainAndMethod[1],signature,replyArgs,hasErrorData);this._initialized=true;}
  13. registerEnum(type,values){const domainAndName=type.split('.');const domain=domainAndName[0];if(!Protocol[domain]){Protocol[domain]={};}
  14. Protocol[domain][domainAndName[1]]=values;this._initialized=true;}
  15. registerEvent(eventName,params){const domain=eventName.split('.')[0];this._dispatcherPrototype(domain).registerEvent(eventName,params);this._initialized=true;}
  16. wrapClientCallback(clientCallback,errorPrefix,constructor,defaultValue){function callbackWrapper(error,value){if(error){console.error(errorPrefix+error);clientCallback(defaultValue);return;}
  17. if(constructor){clientCallback(new constructor(value));}else{clientCallback(value);}}
  18. return callbackWrapper;}}
  19. let _factory;export class Connection{constructor(){this._onMessage;}
  20. setOnMessage(onMessage){}
  21. setOnDisconnect(onDisconnect){}
  22. sendRawMessage(message){}
  23. disconnect(){}
  24. static setFactory(factory){_factory=factory;}
  25. static getFactory(){return _factory;}}
  26. const test={dumpProtocol:null,deprecatedRunAfterPendingDispatches:null,sendRawMessage:null,suppressRequestErrors:false,onMessageSent:null,onMessageReceived:null,};class SessionRouter{constructor(connection){this._connection=connection;this._lastMessageId=1;this._pendingResponsesCount=0;this._domainToLogger=new Map();this._sessions=new Map();this._pendingScripts=[];test.deprecatedRunAfterPendingDispatches=this._deprecatedRunAfterPendingDispatches.bind(this);test.sendRawMessage=this._sendRawMessageForTesting.bind(this);this._connection.setOnMessage(this._onMessage.bind(this));this._connection.setOnDisconnect(reason=>{const session=this._sessions.get('');if(session){session.target.dispose(reason);}});}
  27. registerSession(target,sessionId,proxyConnection){if(proxyConnection){for(const session of this._sessions.values()){if(session.proxyConnection){console.error('Multiple simultaneous proxy connections are currently unsupported');break;}}}
  28. this._sessions.set(sessionId,{target,callbacks:new Map(),proxyConnection});}
  29. unregisterSession(sessionId){const session=this._sessions.get(sessionId);for(const callback of session.callbacks.values()){SessionRouter.dispatchConnectionError(callback);}
  30. this._sessions.delete(sessionId);}
  31. _getTargetBySessionId(sessionId){const session=this._sessions.get(sessionId?sessionId:'');if(!session){return null;}
  32. return session.target;}
  33. _nextMessageId(){return this._lastMessageId++;}
  34. connection(){return this._connection;}
  35. sendMessage(sessionId,domain,method,params,callback){const messageObject={};const messageId=this._nextMessageId();messageObject.id=messageId;messageObject.method=method;if(params){messageObject.params=params;}
  36. if(sessionId){messageObject.sessionId=sessionId;}
  37. if(test.dumpProtocol){test.dumpProtocol('frontend: '+JSON.stringify(messageObject));}
  38. if(test.onMessageSent){const paramsObject=JSON.parse(JSON.stringify(params||{}));test.onMessageSent({domain,method,params:(paramsObject),id:messageId},this._getTargetBySessionId(sessionId));}
  39. ++this._pendingResponsesCount;this._sessions.get(sessionId).callbacks.set(messageId,callback);this._connection.sendRawMessage(JSON.stringify(messageObject));}
  40. _sendRawMessageForTesting(method,params,callback){const domain=method.split('.')[0];this.sendMessage('',domain,method,params,callback||(()=>{}));}
  41. _onMessage(message){if(test.dumpProtocol){test.dumpProtocol('backend: '+((typeof message==='string')?message:JSON.stringify(message)));}
  42. if(test.onMessageReceived){const messageObjectCopy=JSON.parse((typeof message==='string')?message:JSON.stringify(message));test.onMessageReceived((messageObjectCopy),this._getTargetBySessionId(messageObjectCopy.sessionId));}
  43. const messageObject=((typeof message==='string')?JSON.parse(message):message);let suppressUnknownMessageErrors=false;for(const session of this._sessions.values()){if(!session.proxyConnection){continue;}
  44. if(!session.proxyConnection._onMessage){Protocol.InspectorBackend.reportProtocolError('Protocol Error: the session has a proxyConnection with no _onMessage',messageObject);continue;}
  45. session.proxyConnection._onMessage(messageObject);suppressUnknownMessageErrors=true;}
  46. const sessionId=messageObject.sessionId||'';const session=this._sessions.get(sessionId);if(!session){if(!suppressUnknownMessageErrors){Protocol.InspectorBackend.reportProtocolError('Protocol Error: the message with wrong session id',messageObject);}
  47. return;}
  48. if(session.proxyConnection){return;}
  49. if(session.target._needsNodeJSPatching){Protocol.NodeURL.patch(messageObject);}
  50. if('id'in messageObject){const callback=session.callbacks.get(messageObject.id);session.callbacks.delete(messageObject.id);if(!callback){if(!suppressUnknownMessageErrors){Protocol.InspectorBackend.reportProtocolError('Protocol Error: the message with wrong id',messageObject);}
  51. return;}
  52. callback(messageObject.error,messageObject.result);--this._pendingResponsesCount;if(this._pendingScripts.length&&!this._pendingResponsesCount){this._deprecatedRunAfterPendingDispatches();}}else{if(!('method'in messageObject)){Protocol.InspectorBackend.reportProtocolError('Protocol Error: the message without method',messageObject);return;}
  53. const method=messageObject.method.split('.');const domainName=method[0];if(!(domainName in session.target._dispatchers)){Protocol.InspectorBackend.reportProtocolError(`Protocol Error: the message ${messageObject.method} is for non-existing domain '${domainName}'`,messageObject);return;}
  54. session.target._dispatchers[domainName].dispatch(method[1],messageObject);}}
  55. _deprecatedRunAfterPendingDispatches(script){if(script){this._pendingScripts.push(script);}
  56. setTimeout(()=>{if(!this._pendingResponsesCount){this._executeAfterPendingDispatches();}else{this._deprecatedRunAfterPendingDispatches();}},0);}
  57. _executeAfterPendingDispatches(){if(!this._pendingResponsesCount){const scripts=this._pendingScripts;this._pendingScripts=[];for(let id=0;id<scripts.length;++id){scripts[id]();}}}
  58. static dispatchConnectionError(callback){const error={message:'Connection is closed, can\'t dispatch pending call',code:_ConnectionClosedErrorCode,data:null};setTimeout(()=>callback(error,null),0);}}
  59. export class TargetBase{constructor(needsNodeJSPatching,parentTarget,sessionId,connection){this._needsNodeJSPatching=needsNodeJSPatching;this._sessionId=sessionId;if((!parentTarget&&connection)||(!parentTarget&&sessionId)||(connection&&sessionId)){throw new Error('Either connection or sessionId (but not both) must be supplied for a child target');}
  60. if(sessionId){this._router=parentTarget._router;}else if(connection){this._router=new SessionRouter(connection);}else{this._router=new SessionRouter(_factory());}
  61. this._router.registerSession(this,this._sessionId);this._agents={};for(const[domain,agentPrototype]of Protocol.inspectorBackend._agentPrototypes){this._agents[domain]=Object.create((agentPrototype));this._agents[domain]._target=this;}
  62. this._dispatchers={};for(const[domain,dispatcherPrototype]of Protocol.inspectorBackend._dispatcherPrototypes){this._dispatchers[domain]=Object.create((dispatcherPrototype));this._dispatchers[domain]._dispatchers=[];}}
  63. registerDispatcher(domain,dispatcher){if(!this._dispatchers[domain]){return;}
  64. this._dispatchers[domain].addDomainDispatcher(dispatcher);}
  65. dispose(reason){this._router.unregisterSession(this._sessionId);this._router=null;}
  66. isDisposed(){return!this._router;}
  67. markAsNodeJSForTest(){this._needsNodeJSPatching=true;}
  68. router(){return this._router;}}
  69. class _AgentPrototype{constructor(domain){this._replyArgs={};this._hasErrorData={};this._domain=domain;}
  70. registerCommand(methodName,signature,replyArgs,hasErrorData){const domainAndMethod=this._domain+'.'+methodName;function sendMessagePromise(vararg){const params=Array.prototype.slice.call(arguments);return _AgentPrototype.prototype._sendMessageToBackendPromise.call(this,domainAndMethod,signature,params);}
  71. this[methodName]=sendMessagePromise;function invoke(request){return this._invoke(domainAndMethod,request);}
  72. this['invoke_'+methodName]=invoke;this._replyArgs[domainAndMethod]=replyArgs;if(hasErrorData){this._hasErrorData[domainAndMethod]=true;}}
  73. _prepareParameters(method,signature,args,errorCallback){const params={};let hasParams=false;for(const param of signature){const paramName=param['name'];const typeName=param['type'];const optionalFlag=param['optional'];if(!args.length&&!optionalFlag){errorCallback(`Protocol Error: Invalid number of arguments for method '${method}' call. `+`It must have the following arguments ${JSON.stringify(signature)}'.`);return null;}
  74. const value=args.shift();if(optionalFlag&&typeof value==='undefined'){continue;}
  75. if(typeof value!==typeName){errorCallback(`Protocol Error: Invalid type of argument '${paramName}' for method '${method}' call. `+`It must be '${typeName}' but it is '${typeof value}'.`);return null;}
  76. params[paramName]=value;hasParams=true;}
  77. if(args.length){errorCallback(`Protocol Error: Extra ${args.length} arguments in a call to method '${method}'.`);return null;}
  78. return hasParams?params:null;}
  79. _sendMessageToBackendPromise(method,signature,args){let errorMessage;function onError(message){console.error(message);errorMessage=message;}
  80. const params=this._prepareParameters(method,signature,args,onError);if(errorMessage){return Promise.resolve(null);}
  81. return new Promise((resolve,reject)=>{const callback=(error,result)=>{if(error){if(!test.suppressRequestErrors&&error.code!==Protocol.DevToolsStubErrorCode&&error.code!==_GenericError&&error.code!==_ConnectionClosedErrorCode){console.error('Request '+method+' failed. '+JSON.stringify(error));reject(error);}else{resolve(null);}
  82. return;}
  83. const args=this._replyArgs[method];resolve(result&&args.length?result[args[0]]:undefined);};if(!this._target._router){SessionRouter.dispatchConnectionError(callback);}else{this._target._router.sendMessage(this._target._sessionId,this._domain,method,params,callback);}});}
  84. _invoke(method,request){return new Promise(fulfill=>{const callback=(error,result)=>{if(error&&!test.suppressRequestErrors&&error.code!==Protocol.DevToolsStubErrorCode&&error.code!==_GenericError&&error.code!==_ConnectionClosedErrorCode){console.error('Request '+method+' failed. '+JSON.stringify(error));}
  85. if(!result){result={};}
  86. if(error){result[Protocol.Error]=error.message;}
  87. fulfill(result);};if(!this._target._router){SessionRouter.dispatchConnectionError(callback);}else{this._target._router.sendMessage(this._target._sessionId,this._domain,method,request,callback);}});}}
  88. class _DispatcherPrototype{constructor(){this._eventArgs={};}
  89. registerEvent(eventName,params){this._eventArgs[eventName]=params;}
  90. addDomainDispatcher(dispatcher){this._dispatchers.push(dispatcher);}
  91. dispatch(functionName,messageObject){if(!this._dispatchers.length){return;}
  92. if(!this._eventArgs[messageObject.method]){Protocol.InspectorBackend.reportProtocolError(`Protocol Error: Attempted to dispatch an unspecified method '${messageObject.method}'`,messageObject);return;}
  93. const params=[];if(messageObject.params){const paramNames=this._eventArgs[messageObject.method];for(let i=0;i<paramNames.length;++i){params.push(messageObject.params[paramNames[i]]);}}
  94. for(let index=0;index<this._dispatchers.length;++index){const dispatcher=this._dispatchers[index];if(functionName in dispatcher){dispatcher[functionName].apply(dispatcher,params);}}}}
  95. self.Protocol=self.Protocol||{};Protocol=Protocol||{};Protocol.DevToolsStubErrorCode=DevToolsStubErrorCode;Protocol.SessionRouter=SessionRouter;Protocol.InspectorBackend=InspectorBackend;Protocol.Connection=Connection;Protocol.inspectorBackend=new InspectorBackend();Protocol.test=test;Protocol.TargetBase=TargetBase;Protocol._Callback;Protocol.Error=ProtocolError;