ExtensionAPI.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. function defineCommonExtensionSymbols(apiPrivate){if(!apiPrivate.panels){apiPrivate.panels={};}
  2. apiPrivate.panels.SearchAction={CancelSearch:'cancelSearch',PerformSearch:'performSearch',NextSearchResult:'nextSearchResult',PreviousSearchResult:'previousSearchResult'};apiPrivate.Events={ButtonClicked:'button-clicked-',PanelObjectSelected:'panel-objectSelected-',NetworkRequestFinished:'network-request-finished',OpenResource:'open-resource',PanelSearch:'panel-search-',RecordingStarted:'trace-recording-started-',RecordingStopped:'trace-recording-stopped-',ResourceAdded:'resource-added',ResourceContentCommitted:'resource-content-committed',ViewShown:'view-shown-',ViewHidden:'view-hidden-'};apiPrivate.Commands={AddRequestHeaders:'addRequestHeaders',AddTraceProvider:'addTraceProvider',ApplyStyleSheet:'applyStyleSheet',CompleteTraceSession:'completeTraceSession',CreatePanel:'createPanel',CreateSidebarPane:'createSidebarPane',CreateToolbarButton:'createToolbarButton',EvaluateOnInspectedPage:'evaluateOnInspectedPage',ForwardKeyboardEvent:'_forwardKeyboardEvent',GetHAR:'getHAR',GetPageResources:'getPageResources',GetRequestContent:'getRequestContent',GetResourceContent:'getResourceContent',InspectedURLChanged:'inspectedURLChanged',OpenResource:'openResource',Reload:'Reload',Subscribe:'subscribe',SetOpenResourceHandler:'setOpenResourceHandler',SetResourceContent:'setResourceContent',SetSidebarContent:'setSidebarContent',SetSidebarHeight:'setSidebarHeight',SetSidebarPage:'setSidebarPage',ShowPanel:'showPanel',Unsubscribe:'unsubscribe',UpdateButton:'updateButton'};}
  3. self.injectedExtensionAPI=function(extensionInfo,inspectedTabId,themeName,keysToForward,testHook,injectedScriptId){const keysToForwardSet=new Set(keysToForward);const chrome=window.chrome||{};const devtools_descriptor=Object.getOwnPropertyDescriptor(chrome,'devtools');if(devtools_descriptor){return;}
  4. const apiPrivate={};defineCommonExtensionSymbols(apiPrivate);const commands=apiPrivate.Commands;const events=apiPrivate.Events;let userAction=false;function EventSinkImpl(type,customDispatch){this._type=type;this._listeners=[];this._customDispatch=customDispatch;}
  5. EventSinkImpl.prototype={addListener:function(callback){if(typeof callback!=='function'){throw'addListener: callback is not a function';}
  6. if(this._listeners.length===0){extensionServer.sendRequest({command:commands.Subscribe,type:this._type});}
  7. this._listeners.push(callback);extensionServer.registerHandler('notify-'+this._type,this._dispatch.bind(this));},removeListener:function(callback){const listeners=this._listeners;for(let i=0;i<listeners.length;++i){if(listeners[i]===callback){listeners.splice(i,1);break;}}
  8. if(this._listeners.length===0){extensionServer.sendRequest({command:commands.Unsubscribe,type:this._type});}},_fire:function(vararg){const listeners=this._listeners.slice();for(let i=0;i<listeners.length;++i){listeners[i].apply(null,arguments);}},_dispatch:function(request){if(this._customDispatch){this._customDispatch.call(this,request);}else{this._fire.apply(this,request.arguments);}}};function InspectorExtensionAPI(){this.inspectedWindow=new InspectedWindow();this.panels=new Panels();this.network=new Network();this.timeline=new Timeline();defineDeprecatedProperty(this,'webInspector','resources','network');}
  9. function Network(){function dispatchRequestEvent(message){const request=message.arguments[1];request.__proto__=new Request(message.arguments[0]);this._fire(request);}
  10. this.onRequestFinished=new EventSink(events.NetworkRequestFinished,dispatchRequestEvent);defineDeprecatedProperty(this,'network','onFinished','onRequestFinished');this.onNavigated=new EventSink(events.InspectedURLChanged);}
  11. Network.prototype={getHAR:function(callback){function callbackWrapper(result){const entries=(result&&result.entries)||[];for(let i=0;i<entries.length;++i){entries[i].__proto__=new Request(entries[i]._requestId);delete entries[i]._requestId;}
  12. callback(result);}
  13. extensionServer.sendRequest({command:commands.GetHAR},callback&&callbackWrapper);},addRequestHeaders:function(headers){extensionServer.sendRequest({command:commands.AddRequestHeaders,headers:headers,extensionId:window.location.hostname});}};function RequestImpl(id){this._id=id;}
  14. RequestImpl.prototype={getContent:function(callback){function callbackWrapper(response){callback(response.content,response.encoding);}
  15. extensionServer.sendRequest({command:commands.GetRequestContent,id:this._id},callback&&callbackWrapper);}};function Panels(){const panels={elements:new ElementsPanel(),sources:new SourcesPanel(),};function panelGetter(name){return panels[name];}
  16. for(const panel in panels){Object.defineProperty(this,panel,{get:panelGetter.bind(null,panel),enumerable:true});}
  17. this.applyStyleSheet=function(styleSheet){extensionServer.sendRequest({command:commands.ApplyStyleSheet,styleSheet:styleSheet});};}
  18. Panels.prototype={create:function(title,icon,page,callback){const id='extension-panel-'+extensionServer.nextObjectId();const request={command:commands.CreatePanel,id:id,title:title,icon:icon,page:page};extensionServer.sendRequest(request,callback&&callback.bind(this,new ExtensionPanel(id)));},setOpenResourceHandler:function(callback){const hadHandler=extensionServer.hasHandler(events.OpenResource);function callbackWrapper(message){userAction=true;try{callback.call(null,new Resource(message.resource),message.lineNumber);}finally{userAction=false;}}
  19. if(!callback){extensionServer.unregisterHandler(events.OpenResource);}else{extensionServer.registerHandler(events.OpenResource,callbackWrapper);}
  20. if(hadHandler===!callback){extensionServer.sendRequest({command:commands.SetOpenResourceHandler,'handlerPresent':!!callback});}},openResource:function(url,lineNumber,callback){extensionServer.sendRequest({command:commands.OpenResource,'url':url,'lineNumber':lineNumber},callback);},get SearchAction(){return apiPrivate.panels.SearchAction;}};function ExtensionViewImpl(id){this._id=id;function dispatchShowEvent(message){const frameIndex=message.arguments[0];if(typeof frameIndex==='number'){this._fire(window.parent.frames[frameIndex]);}else{this._fire();}}
  21. if(id){this.onShown=new EventSink(events.ViewShown+id,dispatchShowEvent);this.onHidden=new EventSink(events.ViewHidden+id);}}
  22. function PanelWithSidebarImpl(hostPanelName){ExtensionViewImpl.call(this,null);this._hostPanelName=hostPanelName;this.onSelectionChanged=new EventSink(events.PanelObjectSelected+hostPanelName);}
  23. PanelWithSidebarImpl.prototype={createSidebarPane:function(title,callback){const id='extension-sidebar-'+extensionServer.nextObjectId();const request={command:commands.CreateSidebarPane,panel:this._hostPanelName,id:id,title:title};function callbackWrapper(){callback(new ExtensionSidebarPane(id));}
  24. extensionServer.sendRequest(request,callback&&callbackWrapper);},__proto__:ExtensionViewImpl.prototype};function declareInterfaceClass(implConstructor){return function(){const impl={__proto__:implConstructor.prototype};implConstructor.apply(impl,arguments);populateInterfaceClass(this,impl);};}
  25. function defineDeprecatedProperty(object,className,oldName,newName){let warningGiven=false;function getter(){if(!warningGiven){console.warn(className+'.'+oldName+' is deprecated. Use '+className+'.'+newName+' instead');warningGiven=true;}
  26. return object[newName];}
  27. object.__defineGetter__(oldName,getter);}
  28. function extractCallbackArgument(args){const lastArgument=args[args.length-1];return typeof lastArgument==='function'?lastArgument:undefined;}
  29. const Button=declareInterfaceClass(ButtonImpl);const EventSink=declareInterfaceClass(EventSinkImpl);const ExtensionPanel=declareInterfaceClass(ExtensionPanelImpl);const ExtensionSidebarPane=declareInterfaceClass(ExtensionSidebarPaneImpl);const PanelWithSidebarClass=declareInterfaceClass(PanelWithSidebarImpl);const Request=declareInterfaceClass(RequestImpl);const Resource=declareInterfaceClass(ResourceImpl);const TraceSession=declareInterfaceClass(TraceSessionImpl);class ElementsPanel extends PanelWithSidebarClass{constructor(){super('elements');}}
  30. class SourcesPanel extends PanelWithSidebarClass{constructor(){super('sources');}}
  31. function ExtensionPanelImpl(id){ExtensionViewImpl.call(this,id);this.onSearch=new EventSink(events.PanelSearch+id);}
  32. ExtensionPanelImpl.prototype={createStatusBarButton:function(iconPath,tooltipText,disabled){const id='button-'+extensionServer.nextObjectId();const request={command:commands.CreateToolbarButton,panel:this._id,id:id,icon:iconPath,tooltip:tooltipText,disabled:!!disabled};extensionServer.sendRequest(request);return new Button(id);},show:function(){if(!userAction){return;}
  33. const request={command:commands.ShowPanel,id:this._id};extensionServer.sendRequest(request);},__proto__:ExtensionViewImpl.prototype};function ExtensionSidebarPaneImpl(id){ExtensionViewImpl.call(this,id);}
  34. ExtensionSidebarPaneImpl.prototype={setHeight:function(height){extensionServer.sendRequest({command:commands.SetSidebarHeight,id:this._id,height:height});},setExpression:function(expression,rootTitle,evaluateOptions){const request={command:commands.SetSidebarContent,id:this._id,expression:expression,rootTitle:rootTitle,evaluateOnPage:true,};if(typeof evaluateOptions==='object'){request.evaluateOptions=evaluateOptions;}
  35. extensionServer.sendRequest(request,extractCallbackArgument(arguments));},setObject:function(jsonObject,rootTitle,callback){extensionServer.sendRequest({command:commands.SetSidebarContent,id:this._id,expression:jsonObject,rootTitle:rootTitle},callback);},setPage:function(page){extensionServer.sendRequest({command:commands.SetSidebarPage,id:this._id,page:page});},__proto__:ExtensionViewImpl.prototype};function ButtonImpl(id){this._id=id;this.onClicked=new EventSink(events.ButtonClicked+id);}
  36. ButtonImpl.prototype={update:function(iconPath,tooltipText,disabled){const request={command:commands.UpdateButton,id:this._id,icon:iconPath,tooltip:tooltipText,disabled:!!disabled};extensionServer.sendRequest(request);}};function Timeline(){}
  37. Timeline.prototype={addTraceProvider:function(categoryName,categoryTooltip){const id='extension-trace-provider-'+extensionServer.nextObjectId();extensionServer.sendRequest({command:commands.AddTraceProvider,id:id,categoryName:categoryName,categoryTooltip:categoryTooltip});return new TraceProvider(id);}};function TraceSessionImpl(id){this._id=id;}
  38. TraceSessionImpl.prototype={complete:function(url,timeOffset){const request={command:commands.CompleteTraceSession,id:this._id,url:url||'',timeOffset:timeOffset||0};extensionServer.sendRequest(request);}};function TraceProvider(id){function dispatchRecordingStarted(message){const sessionId=message.arguments[0];this._fire(new TraceSession(sessionId));}
  39. this.onRecordingStarted=new EventSink(events.RecordingStarted+id,dispatchRecordingStarted);this.onRecordingStopped=new EventSink(events.RecordingStopped+id);}
  40. function InspectedWindow(){function dispatchResourceEvent(message){this._fire(new Resource(message.arguments[0]));}
  41. function dispatchResourceContentEvent(message){this._fire(new Resource(message.arguments[0]),message.arguments[1]);}
  42. this.onResourceAdded=new EventSink(events.ResourceAdded,dispatchResourceEvent);this.onResourceContentCommitted=new EventSink(events.ResourceContentCommitted,dispatchResourceContentEvent);}
  43. InspectedWindow.prototype={reload:function(optionsOrUserAgent){let options=null;if(typeof optionsOrUserAgent==='object'){options=optionsOrUserAgent;}else if(typeof optionsOrUserAgent==='string'){options={userAgent:optionsOrUserAgent};console.warn('Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. '+'Use inspectedWindow.reload({ userAgent: value}) instead.');}
  44. extensionServer.sendRequest({command:commands.Reload,options:options});},eval:function(expression,evaluateOptions){const callback=extractCallbackArgument(arguments);function callbackWrapper(result){if(result.isError||result.isException){callback(undefined,result);}else{callback(result.value);}}
  45. const request={command:commands.EvaluateOnInspectedPage,expression:expression};if(typeof evaluateOptions==='object'){request.evaluateOptions=evaluateOptions;}
  46. extensionServer.sendRequest(request,callback&&callbackWrapper);return null;},getResources:function(callback){function wrapResource(resourceData){return new Resource(resourceData);}
  47. function callbackWrapper(resources){callback(resources.map(wrapResource));}
  48. extensionServer.sendRequest({command:commands.GetPageResources},callback&&callbackWrapper);}};function ResourceImpl(resourceData){this._url=resourceData.url;this._type=resourceData.type;}
  49. ResourceImpl.prototype={get url(){return this._url;},get type(){return this._type;},getContent:function(callback){function callbackWrapper(response){callback(response.content,response.encoding);}
  50. extensionServer.sendRequest({command:commands.GetResourceContent,url:this._url},callback&&callbackWrapper);},setContent:function(content,commit,callback){extensionServer.sendRequest({command:commands.SetResourceContent,url:this._url,content:content,commit:commit},callback);}};function getTabId(){return inspectedTabId;}
  51. let keyboardEventRequestQueue=[];let forwardTimer=null;function forwardKeyboardEvent(event){const focused=document.activeElement;if(focused){const isInput=focused.nodeName==='INPUT'||focused.nodeName==='TEXTAREA';if(isInput&&!(event.ctrlKey||event.altKey||event.metaKey)){return;}}
  52. let modifiers=0;if(event.shiftKey){modifiers|=1;}
  53. if(event.ctrlKey){modifiers|=2;}
  54. if(event.altKey){modifiers|=4;}
  55. if(event.metaKey){modifiers|=8;}
  56. const num=(event.keyCode&255)|(modifiers<<8);if(!keysToForwardSet.has(num)){return;}
  57. event.preventDefault();const requestPayload={eventType:event.type,ctrlKey:event.ctrlKey,altKey:event.altKey,metaKey:event.metaKey,shiftKey:event.shiftKey,keyIdentifier:event.keyIdentifier,key:event.key,code:event.code,location:event.location,keyCode:event.keyCode};keyboardEventRequestQueue.push(requestPayload);if(!forwardTimer){forwardTimer=setTimeout(forwardEventQueue,0);}}
  58. function forwardEventQueue(){forwardTimer=null;const request={command:commands.ForwardKeyboardEvent,entries:keyboardEventRequestQueue};extensionServer.sendRequest(request);keyboardEventRequestQueue=[];}
  59. document.addEventListener('keydown',forwardKeyboardEvent,false);function ExtensionServerClient(){this._callbacks={};this._handlers={};this._lastRequestId=0;this._lastObjectId=0;this.registerHandler('callback',this._onCallback.bind(this));const channel=new MessageChannel();this._port=channel.port1;this._port.addEventListener('message',this._onMessage.bind(this),false);this._port.start();window.parent.postMessage('registerExtension','*',[channel.port2]);}
  60. ExtensionServerClient.prototype={sendRequest:function(message,callback){if(typeof callback==='function'){message.requestId=this._registerCallback(callback);}
  61. this._port.postMessage(message);},hasHandler:function(command){return!!this._handlers[command];},registerHandler:function(command,handler){this._handlers[command]=handler;},unregisterHandler:function(command){delete this._handlers[command];},nextObjectId:function(){return injectedScriptId.toString()+'_'+ ++this._lastObjectId;},_registerCallback:function(callback){const id=++this._lastRequestId;this._callbacks[id]=callback;return id;},_onCallback:function(request){if(request.requestId in this._callbacks){const callback=this._callbacks[request.requestId];delete this._callbacks[request.requestId];callback(request.result);}},_onMessage:function(event){const request=event.data;const handler=this._handlers[request.command];if(handler){handler.call(this,request);}}};function populateInterfaceClass(interfaze,implementation){for(const member in implementation){if(member.charAt(0)==='_'){continue;}
  62. let descriptor=null;for(let owner=implementation;owner&&!descriptor;owner=owner.__proto__){descriptor=Object.getOwnPropertyDescriptor(owner,member);}
  63. if(!descriptor){continue;}
  64. if(typeof descriptor.value==='function'){interfaze[member]=descriptor.value.bind(implementation);}else if(typeof descriptor.get==='function'){interfaze.__defineGetter__(member,descriptor.get.bind(implementation));}else{Object.defineProperty(interfaze,member,descriptor);}}}
  65. const extensionServer=new ExtensionServerClient();const coreAPI=new InspectorExtensionAPI();Object.defineProperty(chrome,'devtools',{value:{},enumerable:true});chrome.devtools.inspectedWindow={};Object.defineProperty(chrome.devtools.inspectedWindow,'tabId',{get:getTabId});chrome.devtools.inspectedWindow.__proto__=coreAPI.inspectedWindow;chrome.devtools.network=coreAPI.network;chrome.devtools.panels=coreAPI.panels;chrome.devtools.panels.themeName=themeName;if(extensionInfo.exposeExperimentalAPIs!==false){chrome.experimental=chrome.experimental||{};chrome.experimental.devtools=chrome.experimental.devtools||{};const properties=Object.getOwnPropertyNames(coreAPI);for(let i=0;i<properties.length;++i){const descriptor=Object.getOwnPropertyDescriptor(coreAPI,properties[i]);if(descriptor){Object.defineProperty(chrome.experimental.devtools,properties[i],descriptor);}}
  66. chrome.experimental.devtools.inspectedWindow=chrome.devtools.inspectedWindow;}
  67. if(extensionInfo.exposeWebInspectorNamespace){window.webInspector=coreAPI;}
  68. testHook(extensionServer,coreAPI);};self.buildExtensionAPIInjectedScript=function(extensionInfo,inspectedTabId,themeName,keysToForward,testHook){const argumentsJSON=[extensionInfo,inspectedTabId||null,themeName,keysToForward].map(_=>JSON.stringify(_)).join(',');if(!testHook){testHook=()=>{};}
  69. return'(function(injectedScriptId){ '+defineCommonExtensionSymbols.toString()+';'+'('+self.injectedExtensionAPI.toString()+')('+argumentsJSON+','+testHook+', injectedScriptId);'+'})';};self.Extensions=self.Extensions||{};Extensions=Extensions||{};Extensions.extensionAPI={};defineCommonExtensionSymbols(Extensions.extensionAPI);