NetworkPersistenceManager.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. export default class NetworkPersistenceManager extends Common.Object{constructor(workspace){super();this._bindingSymbol=Symbol('NetworkPersistenceBinding');this._originalResponseContentPromiseSymbol=Symbol('OriginalResponsePromise');this._savingSymbol=Symbol('SavingForOverrides');this._enabledSetting=Common.settings.moduleSetting('persistenceNetworkOverridesEnabled');this._enabledSetting.addChangeListener(this._enabledChanged,this);this._workspace=workspace;this._networkUISourceCodeForEncodedPath=new Map();this._interceptionHandlerBound=this._interceptionHandler.bind(this);this._updateInterceptionThrottler=new Common.Throttler(50);this._project=null;this._activeProject=null;this._active=false;this._enabled=false;this._workspace.addEventListener(Workspace.Workspace.Events.ProjectAdded,event=>this._onProjectAdded((event.data)));this._workspace.addEventListener(Workspace.Workspace.Events.ProjectRemoved,event=>this._onProjectRemoved((event.data)));Persistence.persistence.addNetworkInterceptor(this._canHandleNetworkUISourceCode.bind(this));this._eventDescriptors=[];this._enabledChanged();}
  2. active(){return this._active;}
  3. project(){return this._project;}
  4. originalContentForUISourceCode(uiSourceCode){if(!uiSourceCode[this._bindingSymbol]){return null;}
  5. const fileSystemUISourceCode=uiSourceCode[this._bindingSymbol].fileSystem;return fileSystemUISourceCode[this._originalResponseContentPromiseSymbol]||null;}
  6. _enabledChanged(){if(this._enabled===this._enabledSetting.get()){return;}
  7. this._enabled=this._enabledSetting.get();if(this._enabled){this._eventDescriptors=[Workspace.workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeRenamed,event=>{const uiSourceCode=(event.data.uiSourceCode);this._onUISourceCodeRemoved(uiSourceCode);this._onUISourceCodeAdded(uiSourceCode);}),Workspace.workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeAdded,event=>this._onUISourceCodeAdded((event.data))),Workspace.workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeRemoved,event=>this._onUISourceCodeRemoved((event.data))),Workspace.workspace.addEventListener(Workspace.Workspace.Events.WorkingCopyCommitted,event=>this._onUISourceCodeWorkingCopyCommitted((event.data.uiSourceCode)))];this._updateActiveProject();}else{Common.EventTarget.removeEventListeners(this._eventDescriptors);this._updateActiveProject();}}
  8. _updateActiveProject(){const wasActive=this._active;this._active=!!(this._enabledSetting.get()&&SDK.targetManager.mainTarget()&&this._project);if(this._active===wasActive){return;}
  9. if(this._active){this._project.uiSourceCodes().forEach(this._filesystemUISourceCodeAdded.bind(this));const networkProjects=this._workspace.projectsForType(Workspace.projectTypes.Network);for(const networkProject of networkProjects){networkProject.uiSourceCodes().forEach(this._networkUISourceCodeAdded.bind(this));}}else if(this._project){this._project.uiSourceCodes().forEach(this._filesystemUISourceCodeRemoved.bind(this));this._networkUISourceCodeForEncodedPath.clear();}
  10. Persistence.persistence.refreshAutomapping();}
  11. _encodedPathFromUrl(url){if(!this._active){return'';}
  12. let urlPath=Common.ParsedURL.urlWithoutHash(url.replace(/^https?:\/\//,''));if(urlPath.endsWith('/')&&urlPath.indexOf('?')===-1){urlPath=urlPath+'index.html';}
  13. let encodedPathParts=encodeUrlPathToLocalPathParts(urlPath);const projectPath=Persistence.FileSystemWorkspaceBinding.fileSystemPath(this._project.id());const encodedPath=encodedPathParts.join('/');if(projectPath.length+encodedPath.length>200){const domain=encodedPathParts[0];const encodedFileName=encodedPathParts[encodedPathParts.length-1];const shortFileName=encodedFileName?encodedFileName.substr(0,10)+'-':'';const extension=Common.ParsedURL.extractExtension(urlPath);const extensionPart=extension?'.'+extension.substr(0,10):'';encodedPathParts=[domain,'longurls',shortFileName+String.hashCode(encodedPath).toString(16)+extensionPart];}
  14. return encodedPathParts.join('/');function encodeUrlPathToLocalPathParts(urlPath){const encodedParts=[];for(const pathPart of fileNamePartsFromUrlPath(urlPath)){if(!pathPart){continue;}
  15. let encodedName=encodeURI(pathPart).replace(/[\/:\?\*]/g,match=>'%'+match[0].charCodeAt(0).toString(16));if(_reservedFileNames.has(encodedName.toLowerCase())){encodedName=encodedName.split('').map(char=>'%'+char.charCodeAt(0).toString(16)).join('');}
  16. const lastChar=encodedName.charAt(encodedName.length-1);if(lastChar==='.'){encodedName=encodedName.substr(0,encodedName.length-1)+'%2e';}
  17. encodedParts.push(encodedName);}
  18. return encodedParts;}
  19. function fileNamePartsFromUrlPath(urlPath){urlPath=Common.ParsedURL.urlWithoutHash(urlPath);const queryIndex=urlPath.indexOf('?');if(queryIndex===-1){return urlPath.split('/');}
  20. if(queryIndex===0){return[urlPath];}
  21. const endSection=urlPath.substr(queryIndex);const parts=urlPath.substr(0,urlPath.length-endSection.length).split('/');parts[parts.length-1]+=endSection;return parts;}}
  22. _decodeLocalPathToUrlPath(path){try{return unescape(path);}catch(e){console.error(e);}
  23. return path;}
  24. _unbind(uiSourceCode){const binding=uiSourceCode[this._bindingSymbol];if(!binding){return;}
  25. delete binding.network[this._bindingSymbol];delete binding.fileSystem[this._bindingSymbol];Persistence.persistence.removeBinding(binding);}
  26. async _bind(networkUISourceCode,fileSystemUISourceCode){if(networkUISourceCode[this._bindingSymbol]){this._unbind(networkUISourceCode);}
  27. if(fileSystemUISourceCode[this._bindingSymbol]){this._unbind(fileSystemUISourceCode);}
  28. const binding=new Persistence.PersistenceBinding(networkUISourceCode,fileSystemUISourceCode);networkUISourceCode[this._bindingSymbol]=binding;fileSystemUISourceCode[this._bindingSymbol]=binding;Persistence.persistence.addBinding(binding);const uiSourceCodeOfTruth=networkUISourceCode[this._savingSymbol]?networkUISourceCode:fileSystemUISourceCode;const[{content},encoded]=await Promise.all([uiSourceCodeOfTruth.requestContent(),uiSourceCodeOfTruth.contentEncoded()]);Persistence.persistence.syncContent(uiSourceCodeOfTruth,content,encoded);}
  29. _onUISourceCodeWorkingCopyCommitted(uiSourceCode){this.saveUISourceCodeForOverrides(uiSourceCode);}
  30. canSaveUISourceCodeForOverrides(uiSourceCode){return this._active&&uiSourceCode.project().type()===Workspace.projectTypes.Network&&!uiSourceCode[this._bindingSymbol]&&!uiSourceCode[this._savingSymbol];}
  31. async saveUISourceCodeForOverrides(uiSourceCode){if(!this.canSaveUISourceCodeForOverrides(uiSourceCode)){return;}
  32. uiSourceCode[this._savingSymbol]=true;let encodedPath=this._encodedPathFromUrl(uiSourceCode.url());const content=(await uiSourceCode.requestContent()).content||'';const encoded=await uiSourceCode.contentEncoded();const lastIndexOfSlash=encodedPath.lastIndexOf('/');const encodedFileName=encodedPath.substr(lastIndexOfSlash+1);encodedPath=encodedPath.substr(0,lastIndexOfSlash);await this._project.createFile(encodedPath,encodedFileName,content,encoded);this._fileCreatedForTest(encodedPath,encodedFileName);uiSourceCode[this._savingSymbol]=false;}
  33. _fileCreatedForTest(path,fileName){}
  34. _patternForFileSystemUISourceCode(uiSourceCode){const relativePathParts=Persistence.FileSystemWorkspaceBinding.relativePath(uiSourceCode);if(relativePathParts.length<2){return'';}
  35. if(relativePathParts[1]==='longurls'&&relativePathParts.length!==2){return'http?://'+relativePathParts[0]+'/*';}
  36. return'http?://'+this._decodeLocalPathToUrlPath(relativePathParts.join('/'));}
  37. _onUISourceCodeAdded(uiSourceCode){this._networkUISourceCodeAdded(uiSourceCode);this._filesystemUISourceCodeAdded(uiSourceCode);}
  38. _canHandleNetworkUISourceCode(uiSourceCode){return this._active&&!uiSourceCode.url().startsWith('snippet://');}
  39. _networkUISourceCodeAdded(uiSourceCode){if(uiSourceCode.project().type()!==Workspace.projectTypes.Network||!this._canHandleNetworkUISourceCode(uiSourceCode)){return;}
  40. const url=Common.ParsedURL.urlWithoutHash(uiSourceCode.url());this._networkUISourceCodeForEncodedPath.set(this._encodedPathFromUrl(url),uiSourceCode);const fileSystemUISourceCode=this._project.uiSourceCodeForURL((this._project).fileSystemPath()+'/'+
  41. this._encodedPathFromUrl(url));if(!fileSystemUISourceCode){return;}
  42. this._bind(uiSourceCode,fileSystemUISourceCode);}
  43. _filesystemUISourceCodeAdded(uiSourceCode){if(!this._active||uiSourceCode.project()!==this._project){return;}
  44. this._updateInterceptionPatterns();const relativePath=Persistence.FileSystemWorkspaceBinding.relativePath(uiSourceCode);const networkUISourceCode=this._networkUISourceCodeForEncodedPath.get(relativePath.join('/'));if(networkUISourceCode){this._bind(networkUISourceCode,uiSourceCode);}}
  45. _updateInterceptionPatterns(){this._updateInterceptionThrottler.schedule(innerUpdateInterceptionPatterns.bind(this));function innerUpdateInterceptionPatterns(){if(!this._active){return SDK.multitargetNetworkManager.setInterceptionHandlerForPatterns([],this._interceptionHandlerBound);}
  46. const patterns=new Set();const indexFileName='index.html';for(const uiSourceCode of this._project.uiSourceCodes()){const pattern=this._patternForFileSystemUISourceCode(uiSourceCode);patterns.add(pattern);if(pattern.endsWith('/'+indexFileName)){patterns.add(pattern.substr(0,pattern.length-indexFileName.length));}}
  47. return SDK.multitargetNetworkManager.setInterceptionHandlerForPatterns(Array.from(patterns).map(pattern=>({urlPattern:pattern,interceptionStage:Protocol.Network.InterceptionStage.HeadersReceived})),this._interceptionHandlerBound);}}
  48. _onUISourceCodeRemoved(uiSourceCode){this._networkUISourceCodeRemoved(uiSourceCode);this._filesystemUISourceCodeRemoved(uiSourceCode);}
  49. _networkUISourceCodeRemoved(uiSourceCode){if(uiSourceCode.project().type()!==Workspace.projectTypes.Network){return;}
  50. this._unbind(uiSourceCode);this._networkUISourceCodeForEncodedPath.delete(this._encodedPathFromUrl(uiSourceCode.url()));}
  51. _filesystemUISourceCodeRemoved(uiSourceCode){if(uiSourceCode.project()!==this._project){return;}
  52. this._updateInterceptionPatterns();delete uiSourceCode[this._originalResponseContentPromiseSymbol];this._unbind(uiSourceCode);}
  53. _setProject(project){if(project===this._project){return;}
  54. if(this._project){this._project.uiSourceCodes().forEach(this._filesystemUISourceCodeRemoved.bind(this));}
  55. this._project=project;if(this._project){this._project.uiSourceCodes().forEach(this._filesystemUISourceCodeAdded.bind(this));}
  56. this._updateActiveProject();this.dispatchEventToListeners(Events.ProjectChanged,this._project);}
  57. _onProjectAdded(project){if(project.type()!==Workspace.projectTypes.FileSystem||Persistence.FileSystemWorkspaceBinding.fileSystemType(project)!=='overrides'){return;}
  58. const fileSystemPath=Persistence.FileSystemWorkspaceBinding.fileSystemPath(project.id());if(!fileSystemPath){return;}
  59. if(this._project){this._project.remove();}
  60. this._setProject(project);}
  61. _onProjectRemoved(project){if(project!==this._project){return;}
  62. this._setProject(null);}
  63. async _interceptionHandler(interceptedRequest){const method=interceptedRequest.request.method;if(!this._active||(method!=='GET'&&method!=='POST')){return;}
  64. const path=(this._project).fileSystemPath()+'/'+this._encodedPathFromUrl(interceptedRequest.request.url);const fileSystemUISourceCode=this._project.uiSourceCodeForURL(path);if(!fileSystemUISourceCode){return;}
  65. let mimeType='';if(interceptedRequest.responseHeaders){const responseHeaders=SDK.NetworkManager.lowercaseHeaders(interceptedRequest.responseHeaders);mimeType=responseHeaders['content-type'];}
  66. if(!mimeType){const expectedResourceType=Common.resourceTypes[interceptedRequest.resourceType]||Common.resourceTypes.Other;mimeType=fileSystemUISourceCode.mimeType();if(Common.ResourceType.fromMimeType(mimeType)!==expectedResourceType){mimeType=expectedResourceType.canonicalMimeType();}}
  67. const project=(fileSystemUISourceCode.project());fileSystemUISourceCode[this._originalResponseContentPromiseSymbol]=interceptedRequest.responseBody().then(response=>{if(response.error||response.content===null){return null;}
  68. return response.encoded?atob(response.content):response.content;});const blob=await project.requestFileBlob(fileSystemUISourceCode);interceptedRequest.continueRequestWithContent(new Blob([blob],{type:mimeType}));}}
  69. const _reservedFileNames=new Set(['con','prn','aux','nul','com1','com2','com3','com4','com5','com6','com7','com8','com9','lpt1','lpt2','lpt3','lpt4','lpt5','lpt6','lpt7','lpt8','lpt9']);export const Events={ProjectChanged:Symbol('ProjectChanged')};self.Persistence=self.Persistence||{};Persistence=Persistence||{};Persistence.NetworkPersistenceManager=NetworkPersistenceManager;Persistence.NetworkPersistenceManager.Events=Events;Persistence.networkPersistenceManager;