Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(820)

Unified Diff: third_party/WebKit/Source/devtools/front_end/persistence/Automapping.js

Issue 2418813005: DevTools: [Persistence] implement automapping (Closed)
Patch Set: address comments Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/Source/devtools/front_end/persistence/Automapping.js
diff --git a/third_party/WebKit/Source/devtools/front_end/persistence/Automapping.js b/third_party/WebKit/Source/devtools/front_end/persistence/Automapping.js
new file mode 100644
index 0000000000000000000000000000000000000000..fc9faf9afc1b6727594f2c2450fd8f2bf27cd8bb
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/persistence/Automapping.js
@@ -0,0 +1,410 @@
+// Copyright 2016 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.
+
+/**
+ * @constructor
+ * @param {!WebInspector.Workspace} workspace
+ * @param {function(!WebInspector.PersistenceBinding)} onBindingCreated
+ * @param {function(!WebInspector.PersistenceBinding)} onBindingRemoved
+ */
+WebInspector.Automapping = function(workspace, onBindingCreated, onBindingRemoved)
+{
+ this._workspace = workspace;
+
+ this._onBindingCreated = onBindingCreated;
+ this._onBindingRemoved = onBindingRemoved;
+ /** @type {!Set<!WebInspector.PersistenceBinding>} */
+ this._bindings = new Set();
+
+ /** @type {!Map<string, !WebInspector.UISourceCode>} */
+ this._fileSystemUISourceCodes = new Map();
+ this._sweepThrottler = new WebInspector.Throttler(100);
+
+ var pathEncoder = new WebInspector.Automapping.PathEncoder();
+ this._filesIndex = new WebInspector.Automapping.FilePathIndex(pathEncoder);
+ this._projectFoldersIndex = new WebInspector.Automapping.FolderIndex(pathEncoder);
+ this._activeFoldersIndex = new WebInspector.Automapping.FolderIndex(pathEncoder);
+
+ this._eventListeners = [
+ this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, event => this._onUISourceCodeAdded(/** @type {!WebInspector.UISourceCode} */(event.data))),
+ this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, event => this._onUISourceCodeRemoved(/** @type {!WebInspector.UISourceCode} */(event.data))),
+ this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectAdded, event => this._onProjectAdded(/** @type {!WebInspector.Project} */(event.data)), this),
+ this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, event => this._onProjectRemoved(/** @type {!WebInspector.Project} */(event.data)), this),
+ ];
+
+ for (var fileSystem of workspace.projects())
+ this._onProjectAdded(fileSystem);
+ for (var uiSourceCode of workspace.uiSourceCodes())
+ this._onUISourceCodeAdded(uiSourceCode);
+}
+
+WebInspector.Automapping._binding = Symbol("Automapping.Binding");
+WebInspector.Automapping._processingPromise = Symbol("Automapping.ProcessingPromise");
+WebInspector.Automapping._metadata = Symbol("Automapping.Metadata");
+
+WebInspector.Automapping.prototype = {
+ _scheduleRemap: function()
+ {
+ for (var binding of this._bindings.valuesArray())
+ this._unbindNetwork(binding.network);
+ this._scheduleSweep();
+ },
+
+ _scheduleSweep: function()
+ {
+ this._sweepThrottler.schedule(sweepUnmapped.bind(this));
+
+ /**
+ * @this {WebInspector.Automapping}
+ * @return {!Promise}
+ */
+ function sweepUnmapped()
+ {
+ var networkProjects = this._workspace.projectsForType(WebInspector.projectTypes.Network);
+ for (var networkProject of networkProjects) {
+ for (var uiSourceCode of networkProject.uiSourceCodes())
+ this._bindNetwork(uiSourceCode);
+ }
+ this._onSweepHappenedForTest();
+ return Promise.resolve();
+ }
+ },
+
+ _onSweepHappenedForTest: function() { },
+
+ /**
+ * @param {!WebInspector.Project} project
+ */
+ _onProjectRemoved: function(project)
+ {
+ for (var uiSourceCode of project.uiSourceCodes())
+ this._onUISourceCodeRemoved(uiSourceCode);
+ if (project.type() !== WebInspector.projectTypes.FileSystem)
+ return;
+ var fileSystem = /** @type {!WebInspector.FileSystemWorkspaceBinding.FileSystem} */(project);
+ for (var gitFolder of fileSystem.gitFolders())
+ this._projectFoldersIndex.removeFolder(gitFolder);
+ this._projectFoldersIndex.removeFolder(fileSystem.fileSystemPath());
+ this._scheduleRemap();
+ },
+
+ /**
+ * @param {!WebInspector.Project} project
+ */
+ _onProjectAdded: function(project)
+ {
+ if (project.type() !== WebInspector.projectTypes.FileSystem)
+ return;
+ var fileSystem = /** @type {!WebInspector.FileSystemWorkspaceBinding.FileSystem} */(project);
+ for (var gitFolder of fileSystem.gitFolders())
+ this._projectFoldersIndex.addFolder(gitFolder);
+ this._projectFoldersIndex.addFolder(fileSystem.fileSystemPath());
+ this._scheduleRemap();
+ },
+
+ /**
+ * @param {!WebInspector.UISourceCode} uiSourceCode
+ */
+ _onUISourceCodeAdded: function(uiSourceCode)
+ {
+ if (uiSourceCode.project().type() === WebInspector.projectTypes.FileSystem) {
+ this._filesIndex.addPath(uiSourceCode.url());
+ this._fileSystemUISourceCodes.set(uiSourceCode.url(), uiSourceCode);
+ this._scheduleSweep();
+ } else if (uiSourceCode.project().type() === WebInspector.projectTypes.Network) {
+ this._bindNetwork(uiSourceCode);
+ }
+ },
+
+ /**
+ * @param {!WebInspector.UISourceCode} uiSourceCode
+ */
+ _onUISourceCodeRemoved: function(uiSourceCode)
+ {
+ if (uiSourceCode.project().type() === WebInspector.projectTypes.FileSystem) {
+ this._filesIndex.removePath(uiSourceCode.url());
+ this._fileSystemUISourceCodes.delete(uiSourceCode.url());
+ var binding = uiSourceCode[WebInspector.Automapping._binding];
+ if (binding)
+ this._unbindNetwork(binding.network);
+ } else if (uiSourceCode.project().type() === WebInspector.projectTypes.Network) {
+ this._unbindNetwork(uiSourceCode);
+ }
+ },
+
+ /**
+ * @param {!WebInspector.UISourceCode} networkSourceCode
+ */
+ _bindNetwork: function(networkSourceCode)
+ {
+ if (networkSourceCode[WebInspector.Automapping._processingPromise] || networkSourceCode[WebInspector.Automapping._binding])
+ return;
+ var createBindingPromise = this._createBinding(networkSourceCode).then(onBinding.bind(this));
+ networkSourceCode[WebInspector.Automapping._processingPromise] = createBindingPromise;
+
+ /**
+ * @param {?WebInspector.PersistenceBinding} binding
+ * @this {WebInspector.Automapping}
+ */
+ function onBinding(binding)
+ {
+ if (networkSourceCode[WebInspector.Automapping._processingPromise] !== createBindingPromise)
+ return;
+ networkSourceCode[WebInspector.Automapping._processingPromise] = null;
+ if (!binding) {
+ this._onBindingFailedForTest();
+ return;
+ }
+
+ this._bindings.add(binding);
+ binding.network[WebInspector.Automapping._binding] = binding;
+ binding.fileSystem[WebInspector.Automapping._binding] = binding;
+ if (binding.exactMatch) {
+ var projectFolder = this._projectFoldersIndex.closestParentFolder(binding.fileSystem.url());
+ var newFolderAdded = projectFolder ? this._activeFoldersIndex.addFolder(projectFolder) : false;
+ if (newFolderAdded)
+ this._scheduleSweep();
+ }
+ this._onBindingCreated.call(null, binding);
+ }
+ },
+
+ _onBindingFailedForTest: function() { },
+
+ /**
+ * @param {!WebInspector.UISourceCode} networkSourceCode
+ */
+ _unbindNetwork: function(networkSourceCode)
+ {
+ if (networkSourceCode[WebInspector.Automapping._processingPromise]) {
+ networkSourceCode[WebInspector.Automapping._processingPromise] = null;
+ return;
+ }
+ var binding = networkSourceCode[WebInspector.Automapping._binding];
+ if (!binding)
+ return;
+
+ this._bindings.delete(binding);
+ binding.network[WebInspector.Automapping._binding] = null;
+ binding.fileSystem[WebInspector.Automapping._binding] = null;
+ if (binding.exactMatch) {
+ var projectFolder = this._projectFoldersIndex.closestParentFolder(binding.fileSystem.url());
+ if (projectFolder)
+ this._activeFoldersIndex.removeFolder(projectFolder);
+ }
+ this._onBindingRemoved.call(null, binding);
+ },
+
+ /**
+ * @param {!WebInspector.UISourceCode} networkSourceCode
+ * @return {!Promise<?WebInspector.PersistenceBinding>}
+ */
+ _createBinding: function(networkSourceCode)
+ {
+ var networkPath = WebInspector.ParsedURL.extractPath(networkSourceCode.url());
+ if (networkPath === null)
+ return Promise.resolve(/** @type {?WebInspector.PersistenceBinding} */(null));
+
+ if (networkPath.endsWith("/"))
+ networkPath += "index.html";
+ var similarFiles = this._filesIndex.similarFiles(networkPath).map(path => this._fileSystemUISourceCodes.get(path));
+ if (!similarFiles.length)
+ return Promise.resolve(/** @type {?WebInspector.PersistenceBinding} */(null));
+
+ return this._pullMetadatas(similarFiles.concat(networkSourceCode)).then(onMetadatas.bind(this));
+
+ /**
+ * @this {WebInspector.Automapping}
+ */
+ function onMetadatas()
+ {
+ var activeFiles = similarFiles.filter(file => !!this._activeFoldersIndex.closestParentFolder(file.url()));
+ var networkMetadata = networkSourceCode[WebInspector.Automapping._metadata];
+ if (!networkMetadata || (!networkMetadata.modificationTime && typeof networkMetadata.contentSize !== "number")) {
+ // If networkSourceCode does not have metadata, try to match against active folders.
+ if (activeFiles.length !== 1)
+ return null;
+ return new WebInspector.PersistenceBinding(networkSourceCode, activeFiles[0], false);
+ }
+
+ // Try to find exact matches, prioritizing active folders.
+ var exactMatches = this._filterWithMetadata(activeFiles, networkMetadata);
+ if (!exactMatches.length)
+ exactMatches = this._filterWithMetadata(similarFiles, networkMetadata);
+ if (exactMatches.length !== 1)
+ return null;
+ return new WebInspector.PersistenceBinding(networkSourceCode, exactMatches[0], true);
+ }
+ },
+
+ /**
+ * @param {!Array<!WebInspector.UISourceCode>} uiSourceCodes
+ * @return {!Promise}
+ */
+ _pullMetadatas: function(uiSourceCodes)
+ {
+ var promises = uiSourceCodes.map(file => fetchMetadata(file));
+ return Promise.all(promises);
+
+ /**
+ * @param {!WebInspector.UISourceCode} file
+ * @return {!Promise}
+ */
+ function fetchMetadata(file)
+ {
+ return file.requestMetadata().then(metadata => file[WebInspector.Automapping._metadata] = metadata);
+ }
+ },
+
+ /**
+ * @param {!Array<!WebInspector.UISourceCode>} files
+ * @param {!WebInspector.UISourceCodeMetadata} networkMetadata
+ * @return {!Array<!WebInspector.UISourceCode>}
+ */
+ _filterWithMetadata: function(files, networkMetadata)
+ {
+ return files.filter(file => {
+ var fileMetadata = file[WebInspector.Automapping._metadata];
+ if (!fileMetadata)
+ return false;
+ // Allow a second of difference due to network timestamps lack of precision.
+ var timeMatches = !networkMetadata.modificationTime || Math.abs(networkMetadata.modificationTime - fileMetadata.modificationTime) < 1000;
+ var contentMatches = !networkMetadata.contentSize || fileMetadata.contentSize === networkMetadata.contentSize;
+ return timeMatches && contentMatches;
+ });
+ }
+}
+
+/**
+ * @constructor
+ */
+WebInspector.Automapping.PathEncoder = function()
+{
+ /** @type {!WebInspector.CharacterIdMap<string>} */
+ this._encoder = new WebInspector.CharacterIdMap();
+}
+
+WebInspector.Automapping.PathEncoder.prototype = {
+ /**
+ * @param {string} path
+ * @return {string}
+ */
+ encode: function(path)
+ {
+ return path.split("/").map(token => this._encoder.toChar(token)).join("");
+ },
+
+ /**
+ * @param {string} path
+ * @return {string}
+ */
+ decode: function(path)
+ {
+ return path.split("").map(token => this._encoder.fromChar(token)).join("/");
+ }
+}
+
+/**
+ * @constructor
+ * @param {!WebInspector.Automapping.PathEncoder} encoder
+ */
+WebInspector.Automapping.FilePathIndex = function(encoder)
+{
+ this._encoder = encoder;
+ this._reversedIndex = new WebInspector.Trie();
+}
+
+WebInspector.Automapping.FilePathIndex.prototype = {
+ /**
+ * @param {string} path
+ */
+ addPath: function(path)
+ {
+ var encodedPath = this._encoder.encode(path);
+ this._reversedIndex.add(encodedPath.reverse());
+ },
+
+ /**
+ * @param {string} path
+ */
+ removePath: function(path)
+ {
+ var encodedPath = this._encoder.encode(path);
+ this._reversedIndex.remove(encodedPath.reverse());
+ },
+
+ /**
+ * @param {string} networkPath
+ * @return {!Array<string>}
+ */
+ similarFiles: function(networkPath)
+ {
+ var encodedPath = this._encoder.encode(networkPath);
+ var longestCommonPrefix = this._reversedIndex.longestPrefix(encodedPath.reverse(), false);
+ if (!longestCommonPrefix)
+ return [];
+ return this._reversedIndex.words(longestCommonPrefix).map(encodedPath => this._encoder.decode(encodedPath.reverse()));
+ },
+}
+
+/**
+ * @constructor
+ * @param {!WebInspector.Automapping.PathEncoder} encoder
+ */
+WebInspector.Automapping.FolderIndex = function(encoder)
+{
+ this._encoder = encoder;
+ this._index = new WebInspector.Trie();
+ /** @type {!Map<string, number>} */
+ this._folderCount = new Map();
+}
+
+WebInspector.Automapping.FolderIndex.prototype = {
+ /**
+ * @param {string} path
+ * @return {boolean}
+ */
+ addFolder: function(path)
+ {
+ if (path.endsWith("/"))
+ path = path.substring(0, path.length - 1);
+ var encodedPath = this._encoder.encode(path);
+ this._index.add(encodedPath);
+ var count = this._folderCount.get(encodedPath) || 0;
+ this._folderCount.set(encodedPath, count + 1);
+ return count === 0;
+ },
+
+ /**
+ * @param {string} path
+ * @return {boolean}
+ */
+ removeFolder: function(path)
+ {
+ if (path.endsWith("/"))
+ path = path.substring(0, path.length - 1);
+ var encodedPath = this._encoder.encode(path);
+ var count = this._folderCount.get(encodedPath) || 0;
+ if (!count)
+ return false;
+ if (count > 1) {
+ this._folderCount.set(encodedPath, count - 1);
+ return false;
+ }
+ this._index.remove(encodedPath);
+ this._folderCount.delete(encodedPath);
+ return true;
+ },
+
+ /**
+ * @param {string} path
+ * @return {string}
+ */
+ closestParentFolder: function(path)
+ {
+ var encodedPath = this._encoder.encode(path);
+ var commonPrefix = this._index.longestPrefix(encodedPath, true);
+ return this._encoder.decode(commonPrefix);
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698