Commit 6316a74b authored by Nathan Bruer's avatar Nathan Bruer Committed by Commit Bot

[Devtools] Add request interception experiment

This patch adds the ability to intercept requests and rewrite them with
content that as changed in the devtools session.

see: https://user-images.githubusercontent.com/1831202/31698204-2668249e-b371-11e7-814f-4253aa61ed7d.png
see: https://user-images.githubusercontent.com/1831202/31698205-26818cea-b371-11e7-804a-68a172537644.png

R=lushnikov,pfeldman,einbinder
BUG=760316

Change-Id: I9ab2fc3aeca22cf49bc20f2be569c543f9fa4ab8
Reviewed-on: https://chromium-review.googlesource.com/720160
Commit-Queue: Blaise Bruer <allada@chromium.org>
Reviewed-by: default avatarAndrey Lushnikov <lushnikov@chromium.org>
Cr-Commit-Position: refs/heads/master@{#509684}
parent 4497ce78
...@@ -448,6 +448,7 @@ all_devtools_files = [ ...@@ -448,6 +448,7 @@ all_devtools_files = [
"front_end/persistence/FileSystemWorkspaceBinding.js", "front_end/persistence/FileSystemWorkspaceBinding.js",
"front_end/persistence/IsolatedFileSystem.js", "front_end/persistence/IsolatedFileSystem.js",
"front_end/persistence/IsolatedFileSystemManager.js", "front_end/persistence/IsolatedFileSystemManager.js",
"front_end/persistence/NetworkPersistenceManager.js",
"front_end/persistence/Persistence.js", "front_end/persistence/Persistence.js",
"front_end/persistence/PersistenceUtils.js", "front_end/persistence/PersistenceUtils.js",
"front_end/platform/module.json", "front_end/platform/module.json",
......
...@@ -316,7 +316,7 @@ Common.ResourceType._mimeTypeByExtension = new Map([ ...@@ -316,7 +316,7 @@ Common.ResourceType._mimeTypeByExtension = new Map([
['jsx', 'text/jsx'], ['jsx', 'text/jsx'],
// Image // Image
['jpeg', 'image/jpeg'], ['jpg', 'image/jpeg'], ['svg', 'image/svg'], ['gif', 'image/gif'], ['webp', 'image/webp'], ['jpeg', 'image/jpeg'], ['jpg', 'image/jpeg'], ['svg', 'image/svg+xml'], ['gif', 'image/gif'], ['webp', 'image/webp'],
['png', 'image/png'], ['ico', 'image/ico'], ['tiff', 'image/tiff'], ['tif', 'image/tif'], ['bmp', 'image/bmp'], ['png', 'image/png'], ['ico', 'image/ico'], ['tiff', 'image/tiff'], ['tif', 'image/tif'], ['bmp', 'image/bmp'],
// Font // Font
......
...@@ -121,6 +121,7 @@ Main.Main = class { ...@@ -121,6 +121,7 @@ Main.Main = class {
Runtime.experiments.register('logManagement', 'Log management', true); Runtime.experiments.register('logManagement', 'Log management', true);
Runtime.experiments.register('liveSASS', 'Live SASS'); Runtime.experiments.register('liveSASS', 'Live SASS');
Runtime.experiments.register('networkGroupingRequests', 'Network request groups support', true); Runtime.experiments.register('networkGroupingRequests', 'Network request groups support', true);
Runtime.experiments.register('networkPersistence', 'Override requests with workspace project');
Runtime.experiments.register('objectPreviews', 'Object previews', true); Runtime.experiments.register('objectPreviews', 'Object previews', true);
Runtime.experiments.register('performanceMonitor', 'Performance Monitor', true); Runtime.experiments.register('performanceMonitor', 'Performance Monitor', true);
Runtime.experiments.register('persistence2', 'Persistence 2.0'); Runtime.experiments.register('persistence2', 'Persistence 2.0');
...@@ -218,6 +219,7 @@ Main.Main = class { ...@@ -218,6 +219,7 @@ Main.Main = class {
new Persistence.FileSystemWorkspaceBinding(Persistence.isolatedFileSystemManager, Workspace.workspace); new Persistence.FileSystemWorkspaceBinding(Persistence.isolatedFileSystemManager, Workspace.workspace);
Persistence.persistence = Persistence.persistence =
new Persistence.Persistence(Workspace.workspace, Bindings.breakpointManager, Persistence.fileSystemMapping); new Persistence.Persistence(Workspace.workspace, Bindings.breakpointManager, Persistence.fileSystemMapping);
Persistence.networkPersistenceManager = new Persistence.NetworkPersistenceManager(Workspace.workspace);
new Main.ExecutionContextSelector(SDK.targetManager, UI.context); new Main.ExecutionContextSelector(SDK.targetManager, UI.context);
Bindings.blackboxManager = new Bindings.BlackboxManager(Bindings.debuggerWorkspaceBinding); Bindings.blackboxManager = new Bindings.BlackboxManager(Bindings.debuggerWorkspaceBinding);
......
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
/**
* @implements {UI.ListWidget.Delegate}
*/
Network.NetworkConfigView = class extends UI.VBox { Network.NetworkConfigView = class extends UI.VBox {
constructor() { constructor() {
super(true); super(true);
...@@ -13,6 +16,13 @@ Network.NetworkConfigView = class extends UI.VBox { ...@@ -13,6 +16,13 @@ Network.NetworkConfigView = class extends UI.VBox {
this._createNetworkThrottlingSection(); this._createNetworkThrottlingSection();
this.contentElement.createChild('div').classList.add('panel-section-separator'); this.contentElement.createChild('div').classList.add('panel-section-separator');
this._createUserAgentSection(); this._createUserAgentSection();
if (Runtime.experiments.isEnabled('networkPersistence')) {
this.contentElement.createChild('div').classList.add('panel-section-separator');
this._mappingsList = new UI.ListWidget(this);
this._mappingEditor = this._createMappingEditor();
this._createNetworkPersistenceSection();
}
} }
/** /**
...@@ -140,8 +150,134 @@ Network.NetworkConfigView = class extends UI.VBox { ...@@ -140,8 +150,134 @@ Network.NetworkConfigView = class extends UI.VBox {
SDK.multitargetNetworkManager.setCustomUserAgentOverride(customUA); SDK.multitargetNetworkManager.setCustomUserAgentOverride(customUA);
} }
} }
};
_createNetworkPersistenceSection() {
var section = this._createSection(Common.UIString('Request override'), 'network-config-override');
this._mappingsList.element.classList.add('network-config-mappings-list');
this._mappingsList.registerRequiredCSS('network/networkConfigView.css');
var enableInterceptionCheckbox = new UI.ToolbarCheckbox(
Common.UIString('Enable request override from file system'), undefined,
() => Persistence.networkPersistenceManager.setEnableInterception(enableInterceptionCheckbox.checked()));
section.appendChild(enableInterceptionCheckbox.element);
var header = section.createChild('div', 'network-config-mapping-list-header');
header.createChild('div', 'network-config-mapping-list-header-domain-path').textContent = Common.UIString('URL');
header.createChild('div', 'network-config-mapping-list-header-local-path').textContent = Common.UIString('File');
var mappingsPlaceholder = createElementWithClass('div', 'network-config-mappings-list-empty');
mappingsPlaceholder.textContent = Common.UIString('None');
this._mappingsList.setEmptyPlaceholder(mappingsPlaceholder);
this._refreshMappingsList();
this._mappingsList.show(section);
Persistence.networkPersistenceManager.addEventListener(
Persistence.NetworkPersistenceManager.Events.ProjectsChanged, this._refreshMappingsList, this);
}
/**
* @return {!UI.ListWidget.Editor}
*/
_createMappingEditor() {
var editor = new UI.ListWidget.Editor();
var content = editor.contentElement();
var titles = content.createChild('div', 'network-config-file-system-mapping-edit-row');
titles.createChild('div', 'network-config-file-system-mapping-system-value').textContent =
Common.UIString('URL prefix');
titles.createChild('div', 'network-config-file-system-mapping-value').textContent = Common.UIString('URL');
var fields = content.createChild('div', 'network-config-file-system-mapping-edit-row');
fields.createChild('div', 'network-config-file-system-mapping-value')
.appendChild(editor.createInput('domainPath', 'text', 'localhost/path/', (item, index, input) => {
var project = /** @type {!Workspace.Project} */ (item);
var domainPath = Persistence.networkPersistenceManager.domainPathForProject(project);
var newDomainPath = input.value;
if (!newDomainPath.endsWith('/'))
newDomainPath += '/';
if (domainPath === newDomainPath)
return true;
var parsedURL = new Common.ParsedURL('http://' + newDomainPath);
if (!newDomainPath || !parsedURL.isValid || /[\?#:@]/.test(newDomainPath))
return false;
for (var interceptionProject of Persistence.networkPersistenceManager.projects()) {
if (newDomainPath === Persistence.networkPersistenceManager.domainPathForProject(interceptionProject))
return false;
}
return true;
}));
fields.createChild('div', 'network-config-file-system-mapping-value').createTextChild('');
return editor;
}
_refreshMappingsList() {
this._mappingsList.clear();
for (var project of Persistence.networkPersistenceManager.projects())
this._mappingsList.appendItem(project, true);
}
/**
* @override
* @param {*} item
* @param {boolean} editable
* @return {!Element}
*/
renderItem(item, editable) {
var element = createElementWithClass('div', 'network-config-file-system-mapping-list-item');
var project = /** @type {!Workspace.Project} */ (item);
var domainPath = Persistence.networkPersistenceManager.domainPathForProject(project);
var fileSystemPath = Persistence.FileSystemWorkspaceBinding.fileSystemPath(project.id());
var urlElement = element.createChild('div', 'network-config-file-system-mapping-domain-path');
urlElement.textContent = domainPath;
urlElement.title = domainPath;
var fileElement = element.createChild('div', 'network-config-file-system-mapping-local-path');
var localPath = fileSystemPath.replace(/^file:\/\//, '');
fileElement.textContent = localPath;
fileElement.title = localPath;
return element;
}
/**
* @override
* @param {*} item
* @param {number} index
*/
removeItemRequested(item, index) {
Persistence.networkPersistenceManager.removeFileSystemProject(/** @type {!Workspace.Project} */ (item));
}
/**
* @override
* @param {*} item
* @param {!UI.ListWidget.Editor} editor
* @param {boolean} isNew
*/
commitEdit(item, editor, isNew) {
var project = /** @type {!Workspace.Project} */ (item);
var domainPath = editor.control('domainPath').value;
if (!domainPath.endsWith('/'))
domainPath += '/';
Persistence.networkPersistenceManager.removeFileSystemProject(project);
Persistence.networkPersistenceManager.addFileSystemProject(domainPath, project);
}
/**
* @override
* @param {*} item
* @return {!UI.ListWidget.Editor}
*/
beginEdit(item) {
var project = /** @type {!Workspace.Project} */ (item);
var domainPath = Persistence.networkPersistenceManager.domainPathForProject(project);
this._mappingEditor.control('domainPath').value = domainPath;
return this._mappingEditor;
}
};
/** @type {!Array.<{title: string, values: !Array.<{title: string, value: string}>}>} */ /** @type {!Array.<{title: string, values: !Array.<{title: string, value: string}>}>} */
Network.NetworkConfigView._userAgentGroups = [ Network.NetworkConfigView._userAgentGroups = [
......
...@@ -123,7 +123,8 @@ ...@@ -123,7 +123,8 @@
"product_registry", "product_registry",
"mobile_throttling", "mobile_throttling",
"har_importer", "har_importer",
"network_priorities" "network_priorities",
"persistence"
], ],
"scripts": [ "scripts": [
"BlockedURLsPane.js", "BlockedURLsPane.js",
......
...@@ -91,3 +91,55 @@ ...@@ -91,3 +91,55 @@
.network-config-ua-auto.checked, .network-config-ua-custom.checked { .network-config-ua-auto.checked, .network-config-ua-custom.checked {
opacity: 1; opacity: 1;
} }
.network-config-mapping-list-header-domain-path, .network-config-mapping-list-header-local-path {
width: 50%;
display: inline-block;
margin-top: 8px;
padding: 3px 5px;
}
.network-config-file-system-mapping-list-item, .network-config-mappings-list-empty {
width: 100%;
}
.network-config-mappings-list-empty {
width: 100%;
user-select: text;
line-height: 33px;
text-align: center;
}
.network-config-file-system-mapping-domain-path {
border-right: solid 1px rgb(231, 231, 231);
}
.network-config-file-system-mapping-domain-path, .network-config-file-system-mapping-local-path {
width: 50%;
padding: 0 5px;
display: inline-block;
user-select: text;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
line-height: 33px;
vertical-align: middle;
}
.network-config-file-system-mapping-edit-row {
flex: none;
display: flex;
flex-direction: row;
margin: 6px 5px;
align-items: center;
}
.network-config-file-system-mapping-edit-row input {
width: 100%;
text-align: inherit;
}
.network-config .add-button {
width: 100px;
margin-left: auto;
}
...@@ -103,6 +103,7 @@ Persistence.Automapping = class { ...@@ -103,6 +103,7 @@ Persistence.Automapping = class {
* @param {!Workspace.Project} project * @param {!Workspace.Project} project
*/ */
_onProjectRemoved(project) { _onProjectRemoved(project) {
this._ignoredProjects.delete(project);
for (var uiSourceCode of project.uiSourceCodes()) for (var uiSourceCode of project.uiSourceCodes())
this._onUISourceCodeRemoved(uiSourceCode); this._onUISourceCodeRemoved(uiSourceCode);
if (project.type() !== Workspace.projectTypes.FileSystem) if (project.type() !== Workspace.projectTypes.FileSystem)
...@@ -118,12 +119,13 @@ Persistence.Automapping = class { ...@@ -118,12 +119,13 @@ Persistence.Automapping = class {
* @param {!Workspace.Project} project * @param {!Workspace.Project} project
*/ */
_onProjectAdded(project) { _onProjectAdded(project) {
if (project.type() !== Workspace.projectTypes.FileSystem) if (project.type() !== Workspace.projectTypes.FileSystem || this._ignoredProjects.has(project))
return; return;
var fileSystem = /** @type {!Persistence.FileSystemWorkspaceBinding.FileSystem} */ (project); var fileSystem = /** @type {!Persistence.FileSystemWorkspaceBinding.FileSystem} */ (project);
for (var gitFolder of fileSystem.initialGitFolders()) for (var gitFolder of fileSystem.initialGitFolders())
this._projectFoldersIndex.addFolder(gitFolder); this._projectFoldersIndex.addFolder(gitFolder);
this._projectFoldersIndex.addFolder(fileSystem.fileSystemPath()); this._projectFoldersIndex.addFolder(fileSystem.fileSystemPath());
project.uiSourceCodes().forEach(this._onUISourceCodeAdded.bind(this));
this._scheduleRemap(); this._scheduleRemap();
} }
...@@ -131,6 +133,8 @@ Persistence.Automapping = class { ...@@ -131,6 +133,8 @@ Persistence.Automapping = class {
* @param {!Workspace.UISourceCode} uiSourceCode * @param {!Workspace.UISourceCode} uiSourceCode
*/ */
_onUISourceCodeAdded(uiSourceCode) { _onUISourceCodeAdded(uiSourceCode) {
if (this._ignoredProjects.has(uiSourceCode.project()))
return;
if (uiSourceCode.project().type() === Workspace.projectTypes.FileSystem) { if (uiSourceCode.project().type() === Workspace.projectTypes.FileSystem) {
this._filesIndex.addPath(uiSourceCode.url()); this._filesIndex.addPath(uiSourceCode.url());
this._fileSystemUISourceCodes.set(uiSourceCode.url(), uiSourceCode); this._fileSystemUISourceCodes.set(uiSourceCode.url(), uiSourceCode);
......
// Copyright (c) 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
Persistence.NetworkPersistenceManager = class extends Common.Object {
/**
* @param {!Workspace.Workspace} workspace
*/
constructor(workspace) {
super();
this._bindingSymbol = Symbol('NetworkPersistenceBinding');
this._domainPathForProjectSymbol = Symbol('NetworkPersistenceDomainPath');
this._enabled = false;
this._workspace = workspace;
this._domainPathForFileSystemPathSetting = Common.settings.createSetting('domainPathForFileSystemPath', []);
/** @type {!Map<string, string>} */
this._domainPathForFileSystemMap = new Map(this._domainPathForFileSystemPathSetting.get());
/** @type {!Set<!Workspace.Project>} */
this._projects = new Set();
/** @type {!Map<string, !Workspace.UISourceCode>} */
this._fileSystemUISourceCodeForUrlMap = new Map();
this._interceptionHandlerBound = this._interceptionHandler.bind(this);
/** @type {!Array<!Common.EventTarget.EventDescriptor>} */
this._eventDescriptors = [];
if (Runtime.experiments.isEnabled('networkPersistence')) {
this._workspace.addEventListener(
Workspace.Workspace.Events.ProjectAdded,
event => this._onProjectAdded(/** @type {!Workspace.Project} */ (event.data)));
this._workspace.addEventListener(
Workspace.Workspace.Events.ProjectRemoved,
event => this._onProjectRemoved(/** @type {!Workspace.Project} */ (event.data)));
}
}
/**
* @param {!Workspace.Project} project
*/
removeFileSystemProject(project) {
var fileSystemPath = Persistence.FileSystemWorkspaceBinding.fileSystemPath(project.id());
this._domainPathForFileSystemMap.delete(fileSystemPath);
this._domainPathForFileSystemPathSetting.set(Array.from(this._domainPathForFileSystemMap.entries()));
this._onProjectRemoved(project);
}
/**
* @param {string} domainPath
* @param {!Workspace.Project} project
*/
addFileSystemProject(domainPath, project) {
if (this._projects.has(project))
return;
var fileSystemPath = Persistence.FileSystemWorkspaceBinding.fileSystemPath(project.id());
this._domainPathForFileSystemMap.set(fileSystemPath, domainPath);
this._domainPathForFileSystemPathSetting.set(Array.from(this._domainPathForFileSystemMap.entries()));
this._onProjectAdded(project);
for (var uiSourceCode of project.uiSourceCodes())
this._onUISourceCodeAdded(uiSourceCode);
}
/**
* @return {boolean}
*/
enabled() {
return this._enabled;
}
/**
* @return {!Set<!Workspace.Project>}
*/
projects() {
return this._projects;
}
/**
* @param {!Workspace.Project} project
* @return {?string}
*/
domainPathForProject(project) {
return project[this._domainPathForProjectSymbol] || null;
}
/**
* @param {boolean} enabled
*/
setEnableInterception(enabled) {
if (this._enabled === enabled)
return;
this._enabled = enabled;
for (var project of this._projects) {
for (var uiSourceCode of project.uiSourceCodes()) {
if (enabled)
this._onUISourceCodeAdded(uiSourceCode);
else
this._onUISourceCodeRemoved(uiSourceCode);
}
}
if (enabled) {
this._eventDescriptors = [
Workspace.workspace.addEventListener(
Workspace.Workspace.Events.UISourceCodeAdded,
event => this._onUISourceCodeAdded(/** @type {!Workspace.UISourceCode} */ (event.data))),
Workspace.workspace.addEventListener(
Workspace.Workspace.Events.UISourceCodeRemoved,
event => this._onUISourceCodeRemoved(/** @type {!Workspace.UISourceCode} */ (event.data))),
Workspace.workspace.addEventListener(
Workspace.Workspace.Events.WorkingCopyCommitted,
event => this._onUISourceCodeWorkingCopyCommitted(
/** @type {!Workspace.UISourceCode} */ (event.data.uiSourceCode),
/** @type {string} */ (event.data.content)),
this)
];
} else {
Common.EventTarget.removeEventListeners(this._eventDescriptors);
}
this.dispatchEventToListeners(Persistence.NetworkPersistenceManager.Events.EnabledChanged);
}
/**
* @param {!Workspace.UISourceCode} fileSystemUISourceCode
* @return {?Workspace.UISourceCode}
*/
_networkUISourceCode(fileSystemUISourceCode) {
var networkProjects = this._workspace.projectsForType(Workspace.projectTypes.Network);
var urls = this._urlsForFileSystemUISourceCode(fileSystemUISourceCode);
for (var networkProject of networkProjects) {
for (var url of urls) {
var networkUISourceCode = networkProject.uiSourceCodeForURL(url);
if (!networkUISourceCode)
continue;
return networkUISourceCode;
}
}
return null;
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
_unbind(uiSourceCode) {
var binding = uiSourceCode[this._bindingSymbol];
if (!binding)
return;
delete binding.network[this._bindingSymbol];
delete binding.fileSystem[this._bindingSymbol];
Persistence.persistence.removeBinding(binding);
}
/**
* @param {!Workspace.UISourceCode} networkUISourceCode
* @param {!Workspace.UISourceCode} fileSystemUISourceCode
*/
_bind(networkUISourceCode, fileSystemUISourceCode) {
if (networkUISourceCode[this._bindingSymbol] || fileSystemUISourceCode[this._bindingSymbol])
return;
var binding = new Persistence.PersistenceBinding(networkUISourceCode, fileSystemUISourceCode, true);
networkUISourceCode[this._bindingSymbol] = binding;
fileSystemUISourceCode[this._bindingSymbol] = binding;
Persistence.persistence.addBinding(binding);
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {string} content
*/
_onUISourceCodeWorkingCopyCommitted(uiSourceCode, content) {
if (!this._enabled || uiSourceCode.project().type() !== Workspace.projectTypes.Network ||
uiSourceCode[this._bindingSymbol])
return;
for (var project of this._projects) {
var projectDomainPath = project[this._domainPathForProjectSymbol];
var urlDomainPath = uiSourceCode.url().replace(/^https?:\/\//, '');
if (!urlDomainPath.startsWith(projectDomainPath))
return;
var relativeFilePath = urlDomainPath.substr(projectDomainPath.length);
var fileName = relativeFilePath.substr(relativeFilePath.lastIndexOf('/') + 1);
var relativeFolderPath = relativeFilePath.substr(0, relativeFilePath.length - fileName.length);
if (!fileName)
fileName = 'index.html';
project.createFile(relativeFolderPath, fileName, content);
}
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @return {!Array<string>}
*/
_urlsForFileSystemUISourceCode(uiSourceCode) {
var domainPath = this.domainPathForProject(uiSourceCode.project());
var directoryPath = Persistence.FileSystemWorkspaceBinding.fileSystemPath(uiSourceCode.project().id());
var relativePath = uiSourceCode.url().substr(directoryPath.length + 1);
var completePath = domainPath + relativePath;
var entries = ['http://' + completePath, 'https://' + completePath];
var indexFileName = 'index.html';
if (relativePath.endsWith(indexFileName)) {
completePath = completePath.substr(0, completePath.length - indexFileName.length);
entries.push('http://' + completePath, 'https://' + completePath);
}
return entries;
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
_onUISourceCodeAdded(uiSourceCode) {
if (!this._enabled)
return;
if (uiSourceCode.project().type() === Workspace.projectTypes.Network) {
if (uiSourceCode[this._bindingSymbol])
return;
var fileSystemUISourceCode = this._fileSystemUISourceCodeForUrlMap.get(uiSourceCode.url());
if (!fileSystemUISourceCode)
return;
this._bind(uiSourceCode, fileSystemUISourceCode);
return;
}
if (!this._projects.has(uiSourceCode.project()))
return;
var urls = this._urlsForFileSystemUISourceCode(uiSourceCode);
for (var url of urls)
this._fileSystemUISourceCodeForUrlMap.set(url, uiSourceCode);
SDK.multitargetNetworkManager.setInterceptionHandlerForPatterns(
Array.from(this._fileSystemUISourceCodeForUrlMap.keys()), this._interceptionHandlerBound);
var networkUISourceCode = this._networkUISourceCode(uiSourceCode);
if (networkUISourceCode)
this._bind(networkUISourceCode, uiSourceCode);
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
_onUISourceCodeRemoved(uiSourceCode) {
if (uiSourceCode.project().type() === Workspace.projectTypes.Network) {
this._unbind(uiSourceCode);
return;
}
if (!this._projects.has(uiSourceCode.project()))
return;
for (var url of this._urlsForFileSystemUISourceCode(uiSourceCode))
this._fileSystemUISourceCodeForUrlMap.delete(url);
SDK.multitargetNetworkManager.setInterceptionHandlerForPatterns(
Array.from(this._fileSystemUISourceCodeForUrlMap.keys()), this._interceptionHandlerBound);
this._unbind(uiSourceCode);
}
/**
* @param {!Workspace.Project} project
*/
_onProjectAdded(project) {
if (project.type() !== Workspace.projectTypes.FileSystem)
return;
var fileSystemPath = Persistence.FileSystemWorkspaceBinding.fileSystemPath(project.id());
var domainPath = this._domainPathForFileSystemMap.get(fileSystemPath);
if (!domainPath)
return;
project[this._domainPathForProjectSymbol] = domainPath;
this._projects.add(project);
Persistence.persistence.ignoreProject(project);
this.dispatchEventToListeners(Persistence.NetworkPersistenceManager.Events.ProjectsChanged);
}
/**
* @param {!Workspace.Project} project
*/
_onProjectRemoved(project) {
if (!this._projects.has(project))
return;
this._projects.delete(project);
for (var uiSourceCode of project.uiSourceCodes())
this._onUISourceCodeRemoved(uiSourceCode);
Persistence.persistence.removeIgnoredProject(project);
this.dispatchEventToListeners(Persistence.NetworkPersistenceManager.Events.ProjectsChanged);
}
/**
* @param {!SDK.MultitargetNetworkManager.InterceptedRequest} interceptedRequest
* @return {!Promise}
*/
async _interceptionHandler(interceptedRequest) {
var fileSystemUISourceCode = this._fileSystemUISourceCodeForUrlMap.get(interceptedRequest.request.url);
if (!fileSystemUISourceCode)
return;
var content = await fileSystemUISourceCode.requestContent();
var isImage = Persistence.IsolatedFileSystem.ImageExtensions.has(
Common.ParsedURL.extractExtension(fileSystemUISourceCode.url()));
var mimeType = fileSystemUISourceCode.mimeType();
var blob = isImage ? (await fetch('data:' + mimeType + ';base64,' + content).then(res => res.blob())) :
new Blob([content], {type: mimeType});
interceptedRequest.continueRequestWithContent(blob);
}
};
Persistence.NetworkPersistenceManager.Events = {
ProjectsChanged: Symbol('ProjectsChanged'),
EnabledChanged: Symbol('EnabledChanged')
};
/** @type {!Persistence.NetworkPersistenceManager} */
Persistence.networkPersistenceManager;
...@@ -29,6 +29,9 @@ Persistence.PersistenceUtils = class { ...@@ -29,6 +29,9 @@ Persistence.PersistenceUtils = class {
if (binding) { if (binding) {
var icon = UI.Icon.create('mediumicon-file-sync'); var icon = UI.Icon.create('mediumicon-file-sync');
icon.title = Persistence.PersistenceUtils.tooltipForUISourceCode(binding.fileSystem); icon.title = Persistence.PersistenceUtils.tooltipForUISourceCode(binding.fileSystem);
// TODO(allada) This will not work properly with dark theme.
if (Persistence.networkPersistenceManager.projects().has(binding.fileSystem.project()))
icon.style.filter = 'hue-rotate(160deg)';
return icon; return icon;
} }
if (uiSourceCode.project().type() !== Workspace.projectTypes.FileSystem) if (uiSourceCode.project().type() !== Workspace.projectTypes.FileSystem)
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
"dependencies": [ "dependencies": [
"bindings", "bindings",
"workspace", "workspace",
"components" "components",
"sdk"
], ],
"extensions": [ "extensions": [
{ {
...@@ -20,6 +21,7 @@ ...@@ -20,6 +21,7 @@
"FileSystemWorkspaceBinding.js", "FileSystemWorkspaceBinding.js",
"DefaultMapping.js", "DefaultMapping.js",
"Automapping.js", "Automapping.js",
"NetworkPersistenceManager.js",
"Persistence.js", "Persistence.js",
"PersistenceUtils.js", "PersistenceUtils.js",
"FileSystemMapping.js", "FileSystemMapping.js",
......
...@@ -72,6 +72,8 @@ SourceFrame.UISourceCodeFrame = class extends SourceFrame.SourceFrame { ...@@ -72,6 +72,8 @@ SourceFrame.UISourceCodeFrame = class extends SourceFrame.SourceFrame {
this.textEditor.addEventListener( this.textEditor.addEventListener(
SourceFrame.SourcesTextEditor.Events.EditorFocused, SourceFrame.SourcesTextEditor.Events.EditorFocused,
() => UI.context.setFlavor(SourceFrame.UISourceCodeFrame, this)); () => UI.context.setFlavor(SourceFrame.UISourceCodeFrame, this));
Persistence.networkPersistenceManager.addEventListener(
Persistence.NetworkPersistenceManager.Events.EnabledChanged, this._onNetworkPersistenceChanged, this);
this._updateStyle(); this._updateStyle();
this._updateDiffUISourceCode(); this._updateDiffUISourceCode();
...@@ -133,6 +135,7 @@ SourceFrame.UISourceCodeFrame = class extends SourceFrame.SourceFrame { ...@@ -133,6 +135,7 @@ SourceFrame.UISourceCodeFrame = class extends SourceFrame.SourceFrame {
super.wasShown(); super.wasShown();
// We need CodeMirrorTextEditor to be initialized prior to this call as it calls |cursorPositionToCoordinates| internally. @see crbug.com/506566 // We need CodeMirrorTextEditor to be initialized prior to this call as it calls |cursorPositionToCoordinates| internally. @see crbug.com/506566
setImmediate(this._updateBucketDecorations.bind(this)); setImmediate(this._updateBucketDecorations.bind(this));
this.setEditable(this._canEditSource());
} }
/** /**
...@@ -154,9 +157,22 @@ SourceFrame.UISourceCodeFrame = class extends SourceFrame.SourceFrame { ...@@ -154,9 +157,22 @@ SourceFrame.UISourceCodeFrame = class extends SourceFrame.SourceFrame {
return true; return true;
if (this._uiSourceCode.project().isServiceProject()) if (this._uiSourceCode.project().isServiceProject())
return false; return false;
if (Persistence.networkPersistenceManager.enabled()) {
var networkPersistenceProjects = Persistence.networkPersistenceManager.projects();
for (var project of networkPersistenceProjects) {
var projectDomainPath = Persistence.networkPersistenceManager.domainPathForProject(project);
var urlDomainPath = this._uiSourceCode.url().replace(/^https?:\/\//, '');
if (projectDomainPath && urlDomainPath.startsWith(projectDomainPath))
return true;
}
}
return this._uiSourceCode.contentType() !== Common.resourceTypes.Document; return this._uiSourceCode.contentType() !== Common.resourceTypes.Document;
} }
_onNetworkPersistenceChanged() {
this.setEditable(this._canEditSource());
}
commitEditing() { commitEditing() {
if (!this._uiSourceCode.isDirty()) if (!this._uiSourceCode.isDirty())
return; return;
...@@ -340,6 +356,8 @@ SourceFrame.UISourceCodeFrame = class extends SourceFrame.SourceFrame { ...@@ -340,6 +356,8 @@ SourceFrame.UISourceCodeFrame = class extends SourceFrame.SourceFrame {
this.textEditor.dispose(); this.textEditor.dispose();
Common.moduleSetting('textEditorAutocompletion').removeChangeListener(this._updateAutocomplete, this); Common.moduleSetting('textEditorAutocompletion').removeChangeListener(this._updateAutocomplete, this);
this.detach(); this.detach();
Persistence.networkPersistenceManager.removeEventListener(
Persistence.NetworkPersistenceManager.Events.EnabledChanged, this._onNetworkPersistenceChanged, this);
} }
/** /**
......
...@@ -726,8 +726,32 @@ Sources.NavigatorView = class extends UI.VBox { ...@@ -726,8 +726,32 @@ Sources.NavigatorView = class extends UI.VBox {
contextMenu.appendSeparator(); contextMenu.appendSeparator();
Sources.NavigatorView.appendAddFolderItem(contextMenu); Sources.NavigatorView.appendAddFolderItem(contextMenu);
if (node instanceof Sources.NavigatorGroupTreeNode) if (node instanceof Sources.NavigatorGroupTreeNode) {
if (Runtime.experiments.isEnabled('networkPersistence')) {
var hasMapping = Persistence.networkPersistenceManager.projects().has(project);
if (hasMapping) {
var domainPath = Persistence.networkPersistenceManager.domainPathForProject(project);
contextMenu.appendItem(
Common.UIString('Stop serving folder for \'' + domainPath + '\''),
() => Persistence.networkPersistenceManager.removeFileSystemProject(project));
} else {
var parsedURL = new Common.ParsedURL(SDK.targetManager.mainTarget().inspectedURL());
var canServeDomain = parsedURL.isValid && (parsedURL.scheme === 'http' || parsedURL.scheme === 'https');
for (var activeProject of Persistence.networkPersistenceManager.projects()) {
if (Persistence.networkPersistenceManager.domainPathForProject(activeProject) !== parsedURL.domain() + '/')
continue;
canServeDomain = false;
break;
}
if (canServeDomain) {
contextMenu.appendItem(
Common.UIString('Start serving folder for \'' + parsedURL.domain() + '\''),
() => Persistence.networkPersistenceManager.addFileSystemProject(parsedURL.domain() + '/', project));
}
}
}
contextMenu.appendItem(Common.UIString('Remove folder from workspace'), removeFolder); contextMenu.appendItem(Common.UIString('Remove folder from workspace'), removeFolder);
}
contextMenu.show(); contextMenu.show();
} }
...@@ -966,6 +990,9 @@ Sources.NavigatorSourceTreeElement = class extends UI.TreeElement { ...@@ -966,6 +990,9 @@ Sources.NavigatorSourceTreeElement = class extends UI.TreeElement {
var container = createElementWithClass('span', 'icon-stack'); var container = createElementWithClass('span', 'icon-stack');
var icon = UI.Icon.create('largeicon-navigator-file-sync', 'icon'); var icon = UI.Icon.create('largeicon-navigator-file-sync', 'icon');
var badge = UI.Icon.create('badge-navigator-file-sync', 'icon-badge'); var badge = UI.Icon.create('badge-navigator-file-sync', 'icon-badge');
// TODO(allada) This does not play well with dark theme. Add an actual icon and use it.
if (Persistence.networkPersistenceManager.projects().has(binding.fileSystem.project()))
badge.style.filter = 'hue-rotate(160deg)';
container.appendChild(icon); container.appendChild(icon);
container.appendChild(badge); container.appendChild(badge);
container.title = Persistence.PersistenceUtils.tooltipForUISourceCode(this._uiSourceCode); container.title = Persistence.PersistenceUtils.tooltipForUISourceCode(this._uiSourceCode);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment