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

Side by Side 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, 1 month 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6 * @constructor
7 * @param {!WebInspector.Workspace} workspace
8 * @param {function(!WebInspector.PersistenceBinding)} onBindingCreated
9 * @param {function(!WebInspector.PersistenceBinding)} onBindingRemoved
10 */
11 WebInspector.Automapping = function(workspace, onBindingCreated, onBindingRemove d)
12 {
13 this._workspace = workspace;
14
15 this._onBindingCreated = onBindingCreated;
16 this._onBindingRemoved = onBindingRemoved;
17 /** @type {!Set<!WebInspector.PersistenceBinding>} */
18 this._bindings = new Set();
19
20 /** @type {!Map<string, !WebInspector.UISourceCode>} */
21 this._fileSystemUISourceCodes = new Map();
22 this._sweepThrottler = new WebInspector.Throttler(100);
23
24 var pathEncoder = new WebInspector.Automapping.PathEncoder();
25 this._filesIndex = new WebInspector.Automapping.FilePathIndex(pathEncoder);
26 this._projectFoldersIndex = new WebInspector.Automapping.FolderIndex(pathEnc oder);
27 this._activeFoldersIndex = new WebInspector.Automapping.FolderIndex(pathEnco der);
28
29 this._eventListeners = [
30 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceC odeAdded, event => this._onUISourceCodeAdded(/** @type {!WebInspector.UISourceCo de} */(event.data))),
31 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceC odeRemoved, event => this._onUISourceCodeRemoved(/** @type {!WebInspector.UISour ceCode} */(event.data))),
32 this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectAd ded, event => this._onProjectAdded(/** @type {!WebInspector.Project} */(event.da ta)), this),
33 this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectRe moved, event => this._onProjectRemoved(/** @type {!WebInspector.Project} */(even t.data)), this),
34 ];
35
36 for (var fileSystem of workspace.projects())
37 this._onProjectAdded(fileSystem);
38 for (var uiSourceCode of workspace.uiSourceCodes())
39 this._onUISourceCodeAdded(uiSourceCode);
40 }
41
42 WebInspector.Automapping._binding = Symbol("Automapping.Binding");
43 WebInspector.Automapping._processingPromise = Symbol("Automapping.ProcessingProm ise");
44 WebInspector.Automapping._metadata = Symbol("Automapping.Metadata");
45
46 WebInspector.Automapping.prototype = {
47 _scheduleRemap: function()
48 {
49 for (var binding of this._bindings.valuesArray())
50 this._unbindNetwork(binding.network);
51 this._scheduleSweep();
52 },
53
54 _scheduleSweep: function()
55 {
56 this._sweepThrottler.schedule(sweepUnmapped.bind(this));
57
58 /**
59 * @this {WebInspector.Automapping}
60 * @return {!Promise}
61 */
62 function sweepUnmapped()
63 {
64 var networkProjects = this._workspace.projectsForType(WebInspector.p rojectTypes.Network);
65 for (var networkProject of networkProjects) {
66 for (var uiSourceCode of networkProject.uiSourceCodes())
67 this._bindNetwork(uiSourceCode);
68 }
69 this._onSweepHappenedForTest();
70 return Promise.resolve();
71 }
72 },
73
74 _onSweepHappenedForTest: function() { },
75
76 /**
77 * @param {!WebInspector.Project} project
78 */
79 _onProjectRemoved: function(project)
80 {
81 for (var uiSourceCode of project.uiSourceCodes())
82 this._onUISourceCodeRemoved(uiSourceCode);
83 if (project.type() !== WebInspector.projectTypes.FileSystem)
84 return;
85 var fileSystem = /** @type {!WebInspector.FileSystemWorkspaceBinding.Fil eSystem} */(project);
86 for (var gitFolder of fileSystem.gitFolders())
87 this._projectFoldersIndex.removeFolder(gitFolder);
88 this._projectFoldersIndex.removeFolder(fileSystem.fileSystemPath());
89 this._scheduleRemap();
90 },
91
92 /**
93 * @param {!WebInspector.Project} project
94 */
95 _onProjectAdded: function(project)
96 {
97 if (project.type() !== WebInspector.projectTypes.FileSystem)
98 return;
99 var fileSystem = /** @type {!WebInspector.FileSystemWorkspaceBinding.Fil eSystem} */(project);
100 for (var gitFolder of fileSystem.gitFolders())
101 this._projectFoldersIndex.addFolder(gitFolder);
102 this._projectFoldersIndex.addFolder(fileSystem.fileSystemPath());
103 this._scheduleRemap();
104 },
105
106 /**
107 * @param {!WebInspector.UISourceCode} uiSourceCode
108 */
109 _onUISourceCodeAdded: function(uiSourceCode)
110 {
111 if (uiSourceCode.project().type() === WebInspector.projectTypes.FileSyst em) {
112 this._filesIndex.addPath(uiSourceCode.url());
113 this._fileSystemUISourceCodes.set(uiSourceCode.url(), uiSourceCode);
114 this._scheduleSweep();
115 } else if (uiSourceCode.project().type() === WebInspector.projectTypes.N etwork) {
116 this._bindNetwork(uiSourceCode);
117 }
118 },
119
120 /**
121 * @param {!WebInspector.UISourceCode} uiSourceCode
122 */
123 _onUISourceCodeRemoved: function(uiSourceCode)
124 {
125 if (uiSourceCode.project().type() === WebInspector.projectTypes.FileSyst em) {
126 this._filesIndex.removePath(uiSourceCode.url());
127 this._fileSystemUISourceCodes.delete(uiSourceCode.url());
128 var binding = uiSourceCode[WebInspector.Automapping._binding];
129 if (binding)
130 this._unbindNetwork(binding.network);
131 } else if (uiSourceCode.project().type() === WebInspector.projectTypes.N etwork) {
132 this._unbindNetwork(uiSourceCode);
133 }
134 },
135
136 /**
137 * @param {!WebInspector.UISourceCode} networkSourceCode
138 */
139 _bindNetwork: function(networkSourceCode)
140 {
141 if (networkSourceCode[WebInspector.Automapping._processingPromise] || ne tworkSourceCode[WebInspector.Automapping._binding])
142 return;
143 var createBindingPromise = this._createBinding(networkSourceCode).then(o nBinding.bind(this));
144 networkSourceCode[WebInspector.Automapping._processingPromise] = createB indingPromise;
145
146 /**
147 * @param {?WebInspector.PersistenceBinding} binding
148 * @this {WebInspector.Automapping}
149 */
150 function onBinding(binding)
151 {
152 if (networkSourceCode[WebInspector.Automapping._processingPromise] ! == createBindingPromise)
153 return;
154 networkSourceCode[WebInspector.Automapping._processingPromise] = nul l;
155 if (!binding) {
156 this._onBindingFailedForTest();
157 return;
158 }
159
160 this._bindings.add(binding);
161 binding.network[WebInspector.Automapping._binding] = binding;
162 binding.fileSystem[WebInspector.Automapping._binding] = binding;
163 if (binding.exactMatch) {
164 var projectFolder = this._projectFoldersIndex.closestParentFolde r(binding.fileSystem.url());
165 var newFolderAdded = projectFolder ? this._activeFoldersIndex.ad dFolder(projectFolder) : false;
166 if (newFolderAdded)
167 this._scheduleSweep();
168 }
169 this._onBindingCreated.call(null, binding);
170 }
171 },
172
173 _onBindingFailedForTest: function() { },
174
175 /**
176 * @param {!WebInspector.UISourceCode} networkSourceCode
177 */
178 _unbindNetwork: function(networkSourceCode)
179 {
180 if (networkSourceCode[WebInspector.Automapping._processingPromise]) {
181 networkSourceCode[WebInspector.Automapping._processingPromise] = nul l;
182 return;
183 }
184 var binding = networkSourceCode[WebInspector.Automapping._binding];
185 if (!binding)
186 return;
187
188 this._bindings.delete(binding);
189 binding.network[WebInspector.Automapping._binding] = null;
190 binding.fileSystem[WebInspector.Automapping._binding] = null;
191 if (binding.exactMatch) {
192 var projectFolder = this._projectFoldersIndex.closestParentFolder(bi nding.fileSystem.url());
193 if (projectFolder)
194 this._activeFoldersIndex.removeFolder(projectFolder);
195 }
196 this._onBindingRemoved.call(null, binding);
197 },
198
199 /**
200 * @param {!WebInspector.UISourceCode} networkSourceCode
201 * @return {!Promise<?WebInspector.PersistenceBinding>}
202 */
203 _createBinding: function(networkSourceCode)
204 {
205 var networkPath = WebInspector.ParsedURL.extractPath(networkSourceCode.u rl());
206 if (networkPath === null)
207 return Promise.resolve(/** @type {?WebInspector.PersistenceBinding} */(null));
208
209 if (networkPath.endsWith("/"))
210 networkPath += "index.html";
211 var similarFiles = this._filesIndex.similarFiles(networkPath).map(path = > this._fileSystemUISourceCodes.get(path));
212 if (!similarFiles.length)
213 return Promise.resolve(/** @type {?WebInspector.PersistenceBinding} */(null));
214
215 return this._pullMetadatas(similarFiles.concat(networkSourceCode)).then( onMetadatas.bind(this));
216
217 /**
218 * @this {WebInspector.Automapping}
219 */
220 function onMetadatas()
221 {
222 var activeFiles = similarFiles.filter(file => !!this._activeFoldersI ndex.closestParentFolder(file.url()));
223 var networkMetadata = networkSourceCode[WebInspector.Automapping._me tadata];
224 if (!networkMetadata || (!networkMetadata.modificationTime && typeof networkMetadata.contentSize !== "number")) {
225 // If networkSourceCode does not have metadata, try to match aga inst active folders.
226 if (activeFiles.length !== 1)
227 return null;
228 return new WebInspector.PersistenceBinding(networkSourceCode, ac tiveFiles[0], false);
229 }
230
231 // Try to find exact matches, prioritizing active folders.
232 var exactMatches = this._filterWithMetadata(activeFiles, networkMeta data);
233 if (!exactMatches.length)
234 exactMatches = this._filterWithMetadata(similarFiles, networkMet adata);
235 if (exactMatches.length !== 1)
236 return null;
237 return new WebInspector.PersistenceBinding(networkSourceCode, exactM atches[0], true);
238 }
239 },
240
241 /**
242 * @param {!Array<!WebInspector.UISourceCode>} uiSourceCodes
243 * @return {!Promise}
244 */
245 _pullMetadatas: function(uiSourceCodes)
246 {
247 var promises = uiSourceCodes.map(file => fetchMetadata(file));
248 return Promise.all(promises);
249
250 /**
251 * @param {!WebInspector.UISourceCode} file
252 * @return {!Promise}
253 */
254 function fetchMetadata(file)
255 {
256 return file.requestMetadata().then(metadata => file[WebInspector.Aut omapping._metadata] = metadata);
257 }
258 },
259
260 /**
261 * @param {!Array<!WebInspector.UISourceCode>} files
262 * @param {!WebInspector.UISourceCodeMetadata} networkMetadata
263 * @return {!Array<!WebInspector.UISourceCode>}
264 */
265 _filterWithMetadata: function(files, networkMetadata)
266 {
267 return files.filter(file => {
268 var fileMetadata = file[WebInspector.Automapping._metadata];
269 if (!fileMetadata)
270 return false;
271 // Allow a second of difference due to network timestamps lack of pr ecision.
272 var timeMatches = !networkMetadata.modificationTime || Math.abs(netw orkMetadata.modificationTime - fileMetadata.modificationTime) < 1000;
273 var contentMatches = !networkMetadata.contentSize || fileMetadata.co ntentSize === networkMetadata.contentSize;
274 return timeMatches && contentMatches;
275 });
276 }
277 }
278
279 /**
280 * @constructor
281 */
282 WebInspector.Automapping.PathEncoder = function()
283 {
284 /** @type {!WebInspector.CharacterIdMap<string>} */
285 this._encoder = new WebInspector.CharacterIdMap();
286 }
287
288 WebInspector.Automapping.PathEncoder.prototype = {
289 /**
290 * @param {string} path
291 * @return {string}
292 */
293 encode: function(path)
294 {
295 return path.split("/").map(token => this._encoder.toChar(token)).join("" );
296 },
297
298 /**
299 * @param {string} path
300 * @return {string}
301 */
302 decode: function(path)
303 {
304 return path.split("").map(token => this._encoder.fromChar(token)).join(" /");
305 }
306 }
307
308 /**
309 * @constructor
310 * @param {!WebInspector.Automapping.PathEncoder} encoder
311 */
312 WebInspector.Automapping.FilePathIndex = function(encoder)
313 {
314 this._encoder = encoder;
315 this._reversedIndex = new WebInspector.Trie();
316 }
317
318 WebInspector.Automapping.FilePathIndex.prototype = {
319 /**
320 * @param {string} path
321 */
322 addPath: function(path)
323 {
324 var encodedPath = this._encoder.encode(path);
325 this._reversedIndex.add(encodedPath.reverse());
326 },
327
328 /**
329 * @param {string} path
330 */
331 removePath: function(path)
332 {
333 var encodedPath = this._encoder.encode(path);
334 this._reversedIndex.remove(encodedPath.reverse());
335 },
336
337 /**
338 * @param {string} networkPath
339 * @return {!Array<string>}
340 */
341 similarFiles: function(networkPath)
342 {
343 var encodedPath = this._encoder.encode(networkPath);
344 var longestCommonPrefix = this._reversedIndex.longestPrefix(encodedPath. reverse(), false);
345 if (!longestCommonPrefix)
346 return [];
347 return this._reversedIndex.words(longestCommonPrefix).map(encodedPath => this._encoder.decode(encodedPath.reverse()));
348 },
349 }
350
351 /**
352 * @constructor
353 * @param {!WebInspector.Automapping.PathEncoder} encoder
354 */
355 WebInspector.Automapping.FolderIndex = function(encoder)
356 {
357 this._encoder = encoder;
358 this._index = new WebInspector.Trie();
359 /** @type {!Map<string, number>} */
360 this._folderCount = new Map();
361 }
362
363 WebInspector.Automapping.FolderIndex.prototype = {
364 /**
365 * @param {string} path
366 * @return {boolean}
367 */
368 addFolder: function(path)
369 {
370 if (path.endsWith("/"))
371 path = path.substring(0, path.length - 1);
372 var encodedPath = this._encoder.encode(path);
373 this._index.add(encodedPath);
374 var count = this._folderCount.get(encodedPath) || 0;
375 this._folderCount.set(encodedPath, count + 1);
376 return count === 0;
377 },
378
379 /**
380 * @param {string} path
381 * @return {boolean}
382 */
383 removeFolder: function(path)
384 {
385 if (path.endsWith("/"))
386 path = path.substring(0, path.length - 1);
387 var encodedPath = this._encoder.encode(path);
388 var count = this._folderCount.get(encodedPath) || 0;
389 if (!count)
390 return false;
391 if (count > 1) {
392 this._folderCount.set(encodedPath, count - 1);
393 return false;
394 }
395 this._index.remove(encodedPath);
396 this._folderCount.delete(encodedPath);
397 return true;
398 },
399
400 /**
401 * @param {string} path
402 * @return {string}
403 */
404 closestParentFolder: function(path)
405 {
406 var encodedPath = this._encoder.encode(path);
407 var commonPrefix = this._index.longestPrefix(encodedPath, true);
408 return this._encoder.decode(commonPrefix);
409 }
410 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698