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 = [
"front_end/persistence/FileSystemWorkspaceBinding.js",
"front_end/persistence/IsolatedFileSystem.js",
"front_end/persistence/IsolatedFileSystemManager.js",
"front_end/persistence/NetworkPersistenceManager.js",
"front_end/persistence/Persistence.js",
"front_end/persistence/PersistenceUtils.js",
"front_end/platform/module.json",
......
......@@ -316,7 +316,7 @@ Common.ResourceType._mimeTypeByExtension = new Map([
['jsx', 'text/jsx'],
// 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'],
// Font
......
......@@ -121,6 +121,7 @@ Main.Main = class {
Runtime.experiments.register('logManagement', 'Log management', true);
Runtime.experiments.register('liveSASS', 'Live SASS');
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('performanceMonitor', 'Performance Monitor', true);
Runtime.experiments.register('persistence2', 'Persistence 2.0');
......@@ -218,6 +219,7 @@ Main.Main = class {
new Persistence.FileSystemWorkspaceBinding(Persistence.isolatedFileSystemManager, Workspace.workspace);
Persistence.persistence =
new Persistence.Persistence(Workspace.workspace, Bindings.breakpointManager, Persistence.fileSystemMapping);
Persistence.networkPersistenceManager = new Persistence.NetworkPersistenceManager(Workspace.workspace);
new Main.ExecutionContextSelector(SDK.targetManager, UI.context);
Bindings.blackboxManager = new Bindings.BlackboxManager(Bindings.debuggerWorkspaceBinding);
......
......@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @implements {UI.ListWidget.Delegate}
*/
Network.NetworkConfigView = class extends UI.VBox {
constructor() {
super(true);
......@@ -13,6 +16,13 @@ Network.NetworkConfigView = class extends UI.VBox {
this._createNetworkThrottlingSection();
this.contentElement.createChild('div').classList.add('panel-section-separator');
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 {
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}>}>} */
Network.NetworkConfigView._userAgentGroups = [
......
......@@ -123,7 +123,8 @@
"product_registry",
"mobile_throttling",
"har_importer",
"network_priorities"
"network_priorities",
"persistence"
],
"scripts": [
"BlockedURLsPane.js",
......
......@@ -91,3 +91,55 @@
.network-config-ua-auto.checked, .network-config-ua-custom.checked {
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 {
* @param {!Workspace.Project} project
*/
_onProjectRemoved(project) {
this._ignoredProjects.delete(project);
for (var uiSourceCode of project.uiSourceCodes())
this._onUISourceCodeRemoved(uiSourceCode);
if (project.type() !== Workspace.projectTypes.FileSystem)
......@@ -118,12 +119,13 @@ Persistence.Automapping = class {
* @param {!Workspace.Project} project
*/
_onProjectAdded(project) {
if (project.type() !== Workspace.projectTypes.FileSystem)
if (project.type() !== Workspace.projectTypes.FileSystem || this._ignoredProjects.has(project))
return;
var fileSystem = /** @type {!Persistence.FileSystemWorkspaceBinding.FileSystem} */ (project);
for (var gitFolder of fileSystem.initialGitFolders())
this._projectFoldersIndex.addFolder(gitFolder);
this._projectFoldersIndex.addFolder(fileSystem.fileSystemPath());
project.uiSourceCodes().forEach(this._onUISourceCodeAdded.bind(this));
this._scheduleRemap();
}
......@@ -131,6 +133,8 @@ Persistence.Automapping = class {
* @param {!Workspace.UISourceCode} uiSourceCode
*/
_onUISourceCodeAdded(uiSourceCode) {
if (this._ignoredProjects.has(uiSourceCode.project()))
return;
if (uiSourceCode.project().type() === Workspace.projectTypes.FileSystem) {
this._filesIndex.addPath(uiSourceCode.url());
this._fileSystemUISourceCodes.set(uiSourceCode.url(), uiSourceCode);
......
......@@ -29,6 +29,9 @@ Persistence.PersistenceUtils = class {
if (binding) {
var icon = UI.Icon.create('mediumicon-file-sync');
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;
}
if (uiSourceCode.project().type() !== Workspace.projectTypes.FileSystem)
......
......@@ -2,7 +2,8 @@
"dependencies": [
"bindings",
"workspace",
"components"
"components",
"sdk"
],
"extensions": [
{
......@@ -20,6 +21,7 @@
"FileSystemWorkspaceBinding.js",
"DefaultMapping.js",
"Automapping.js",
"NetworkPersistenceManager.js",
"Persistence.js",
"PersistenceUtils.js",
"FileSystemMapping.js",
......
......@@ -72,6 +72,8 @@ SourceFrame.UISourceCodeFrame = class extends SourceFrame.SourceFrame {
this.textEditor.addEventListener(
SourceFrame.SourcesTextEditor.Events.EditorFocused,
() => UI.context.setFlavor(SourceFrame.UISourceCodeFrame, this));
Persistence.networkPersistenceManager.addEventListener(
Persistence.NetworkPersistenceManager.Events.EnabledChanged, this._onNetworkPersistenceChanged, this);
this._updateStyle();
this._updateDiffUISourceCode();
......@@ -133,6 +135,7 @@ SourceFrame.UISourceCodeFrame = class extends SourceFrame.SourceFrame {
super.wasShown();
// We need CodeMirrorTextEditor to be initialized prior to this call as it calls |cursorPositionToCoordinates| internally. @see crbug.com/506566
setImmediate(this._updateBucketDecorations.bind(this));
this.setEditable(this._canEditSource());
}
/**
......@@ -154,9 +157,22 @@ SourceFrame.UISourceCodeFrame = class extends SourceFrame.SourceFrame {
return true;
if (this._uiSourceCode.project().isServiceProject())
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;
}
_onNetworkPersistenceChanged() {
this.setEditable(this._canEditSource());
}
commitEditing() {
if (!this._uiSourceCode.isDirty())
return;
......@@ -340,6 +356,8 @@ SourceFrame.UISourceCodeFrame = class extends SourceFrame.SourceFrame {
this.textEditor.dispose();
Common.moduleSetting('textEditorAutocompletion').removeChangeListener(this._updateAutocomplete, this);
this.detach();
Persistence.networkPersistenceManager.removeEventListener(
Persistence.NetworkPersistenceManager.Events.EnabledChanged, this._onNetworkPersistenceChanged, this);
}
/**
......
......@@ -726,8 +726,32 @@ Sources.NavigatorView = class extends UI.VBox {
contextMenu.appendSeparator();
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.show();
}
......@@ -966,6 +990,9 @@ Sources.NavigatorSourceTreeElement = class extends UI.TreeElement {
var container = createElementWithClass('span', 'icon-stack');
var icon = UI.Icon.create('largeicon-navigator-file-sync', 'icon');
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(badge);
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