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);
......
...@@ -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