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

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/components/JavaScriptAutocomplete.js

Issue 2712513002: DevTools: extract ObjectUI module from Components (Closed)
Patch Set: fix build.gn Created 3 years, 10 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 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 Components.JavaScriptAutocomplete = {};
6
7 /** @typedef {{title:(string|undefined), items:Array<string>}} */
8 Components.JavaScriptAutocomplete.CompletionGroup;
9
10 /**
11 * @param {string} text
12 * @param {string} query
13 * @param {boolean=} force
14 * @return {!Promise<!UI.SuggestBox.Suggestions>}
15 */
16 Components.JavaScriptAutocomplete.completionsForTextInCurrentContext = function( text, query, force) {
17 var clippedExpression = Components.JavaScriptAutocomplete._clipExpression(text , true);
18 var mapCompletionsPromise = Components.JavaScriptAutocomplete._mapCompletions( text, query);
19 return Components.JavaScriptAutocomplete.completionsForExpression(clippedExpre ssion, query, force)
20 .then(completions => mapCompletionsPromise.then(mapCompletions => mapCompl etions.concat(completions)));
21 };
22
23 /**
24 * @param {string} text
25 * @param {boolean=} allowEndingBracket
26 * @return {string}
27 */
28 Components.JavaScriptAutocomplete._clipExpression = function(text, allowEndingBr acket) {
29 var index;
30 var stopChars = new Set('=:({;,!+-*/&|^<>`'.split(''));
31 var whiteSpaceChars = new Set(' \r\n\t'.split(''));
32 var continueChars = new Set('[. \r\n\t'.split(''));
33
34 for (index = text.length - 1; index >= 0; index--) {
35 if (stopChars.has(text.charAt(index)))
36 break;
37 if (whiteSpaceChars.has(text.charAt(index)) && !continueChars.has(text.charA t(index - 1)))
38 break;
39 }
40 var clippedExpression = text.substring(index + 1).trim();
41 var bracketCount = 0;
42
43 index = clippedExpression.length - 1;
44 while (index >= 0) {
45 var character = clippedExpression.charAt(index);
46 if (character === ']')
47 bracketCount++;
48 // Allow an open bracket at the end for property completion.
49 if (character === '[' && (index < clippedExpression.length - 1 || !allowEndi ngBracket)) {
50 bracketCount--;
51 if (bracketCount < 0)
52 break;
53 }
54 index--;
55 }
56 return clippedExpression.substring(index + 1).trim();
57 };
58
59 /**
60 * @param {string} text
61 * @param {string} query
62 * @return {!Promise<!UI.SuggestBox.Suggestions>}
63 */
64 Components.JavaScriptAutocomplete._mapCompletions = function(text, query) {
65 var mapMatch = text.match(/\.\s*(get|set|delete)\s*\(\s*$/);
66 var executionContext = UI.context.flavor(SDK.ExecutionContext);
67 if (!executionContext || !mapMatch)
68 return Promise.resolve([]);
69
70 var clippedExpression = Components.JavaScriptAutocomplete._clipExpression(text .substring(0, mapMatch.index));
71 var fulfill;
72 var promise = new Promise(x => fulfill = x);
73 executionContext.evaluate(clippedExpression, 'completion', true, true, false, false, false, evaluated);
74 return promise;
75
76 /**
77 * @param {?SDK.RemoteObject} result
78 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails
79 */
80 function evaluated(result, exceptionDetails) {
81 if (!result || !!exceptionDetails || result.subtype !== 'map') {
82 fulfill([]);
83 return;
84 }
85 result.getOwnPropertiesPromise(false).then(extractEntriesProperty);
86 }
87
88 /**
89 * @param {!{properties: ?Array<!SDK.RemoteObjectProperty>, internalProperties : ?Array<!SDK.RemoteObjectProperty>}} properties
90 */
91 function extractEntriesProperty(properties) {
92 var internalProperties = properties.internalProperties || [];
93 var entriesProperty = internalProperties.find(property => property.name === '[[Entries]]');
94 if (!entriesProperty) {
95 fulfill([]);
96 return;
97 }
98 entriesProperty.value.callFunctionJSONPromise(getEntries).then(keysObj => go tKeys(Object.keys(keysObj)));
99 }
100
101 /**
102 * @suppressReceiverCheck
103 * @this {!Array<{key:?, value:?}>}
104 * @return {!Object}
105 */
106 function getEntries() {
107 var result = {__proto__: null};
108 for (var i = 0; i < this.length; i++) {
109 if (typeof this[i].key === 'string')
110 result[this[i].key] = true;
111 }
112 return result;
113 }
114
115 /**
116 * @param {!Array<string>} rawKeys
117 */
118 function gotKeys(rawKeys) {
119 var caseSensitivePrefix = [];
120 var caseInsensitivePrefix = [];
121 var caseSensitiveAnywhere = [];
122 var caseInsensitiveAnywhere = [];
123 var quoteChar = '"';
124 if (query.startsWith('\''))
125 quoteChar = '\'';
126 var endChar = ')';
127 if (mapMatch[0].indexOf('set') !== -1)
128 endChar = ', ';
129
130 var sorter = rawKeys.length < 1000 ? String.naturalOrderComparator : undefin ed;
131 var keys = rawKeys.sort(sorter).map(key => quoteChar + key + quoteChar);
132
133 for (var key of keys) {
134 if (key.length < query.length)
135 continue;
136 if (query.length && key.toLowerCase().indexOf(query.toLowerCase()) === -1)
137 continue;
138 // Substitute actual newlines with newline characters. @see crbug.com/4984 21
139 var title = key.split('\n').join('\\n');
140 var text = title + endChar;
141
142 if (key.startsWith(query))
143 caseSensitivePrefix.push({text: text, title: title, priority: 4});
144 else if (key.toLowerCase().startsWith(query.toLowerCase()))
145 caseInsensitivePrefix.push({text: text, title: title, priority: 3});
146 else if (key.indexOf(query) !== -1)
147 caseSensitiveAnywhere.push({text: text, title: title, priority: 2});
148 else
149 caseInsensitiveAnywhere.push({text: text, title: title, priority: 1});
150 }
151 var suggestions = caseSensitivePrefix.concat(caseInsensitivePrefix, caseSens itiveAnywhere, caseInsensitiveAnywhere);
152 if (suggestions.length)
153 suggestions[0].subtitle = Common.UIString('Keys');
154 fulfill(suggestions);
155 }
156 };
157
158 /**
159 * @param {string} expressionString
160 * @param {string} query
161 * @param {boolean=} force
162 * @return {!Promise<!UI.SuggestBox.Suggestions>}
163 */
164 Components.JavaScriptAutocomplete.completionsForExpression = function(expression String, query, force) {
165 var executionContext = UI.context.flavor(SDK.ExecutionContext);
166 if (!executionContext)
167 return Promise.resolve([]);
168
169 var lastIndex = expressionString.length - 1;
170
171 var dotNotation = (expressionString[lastIndex] === '.');
172 var bracketNotation = (expressionString.length > 1 && expressionString[lastInd ex] === '[');
173
174 if (dotNotation || bracketNotation)
175 expressionString = expressionString.substr(0, lastIndex);
176 else
177 expressionString = '';
178
179 // User is entering float value, do not suggest anything.
180 if ((expressionString && !isNaN(expressionString)) || (!expressionString && qu ery && !isNaN(query)))
181 return Promise.resolve([]);
182
183
184 if (!query && !expressionString && !force)
185 return Promise.resolve([]);
186
187 var fulfill;
188 var promise = new Promise(x => fulfill = x);
189 var selectedFrame = executionContext.debuggerModel.selectedCallFrame();
190 if (!expressionString && selectedFrame)
191 variableNamesInScopes(selectedFrame, receivedPropertyNames);
192 else
193 executionContext.evaluate(expressionString, 'completion', true, true, false, false, false, evaluated);
194
195 return promise;
196 /**
197 * @param {?SDK.RemoteObject} result
198 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails
199 */
200 function evaluated(result, exceptionDetails) {
201 if (!result || !!exceptionDetails) {
202 fulfill([]);
203 return;
204 }
205
206 /**
207 * @param {?SDK.RemoteObject} object
208 * @return {!Promise<?SDK.RemoteObject>}
209 */
210 function extractTarget(object) {
211 if (!object)
212 return Promise.resolve(/** @type {?SDK.RemoteObject} */ (null));
213 if (object.type !== 'object' || object.subtype !== 'proxy')
214 return Promise.resolve(/** @type {?SDK.RemoteObject} */ (object));
215 return object.getOwnPropertiesPromise(false /* generatePreview */)
216 .then(extractTargetFromProperties)
217 .then(extractTarget);
218 }
219
220 /**
221 * @param {!{properties: ?Array<!SDK.RemoteObjectProperty>, internalProperti es: ?Array<!SDK.RemoteObjectProperty>}} properties
222 * @return {?SDK.RemoteObject}
223 */
224 function extractTargetFromProperties(properties) {
225 var internalProperties = properties.internalProperties || [];
226 var target = internalProperties.find(property => property.name === '[[Targ et]]');
227 return target ? target.value : null;
228 }
229
230 /**
231 * @param {string=} type
232 * @return {!Object}
233 * @suppressReceiverCheck
234 * @this {Object}
235 */
236 function getCompletions(type) {
237 var object;
238 if (type === 'string')
239 object = new String('');
240 else if (type === 'number')
241 object = new Number(0);
242 else if (type === 'boolean')
243 object = new Boolean(false);
244 else
245 object = this;
246
247 var result = [];
248 try {
249 for (var o = object; o; o = Object.getPrototypeOf(o)) {
250 if ((type === 'array' || type === 'typedarray') && o === object && Arr ayBuffer.isView(o) && o.length > 9999)
251 continue;
252
253 var group = {items: [], __proto__: null};
254 try {
255 if (typeof o === 'object' && o.constructor && o.constructor.name)
256 group.title = o.constructor.name;
257 } catch (ee) {
258 // we could break upon cross origin check.
259 }
260 result[result.length] = group;
261 var names = Object.getOwnPropertyNames(o);
262 var isArray = Array.isArray(o);
263 for (var i = 0; i < names.length; ++i) {
264 // Skip array elements indexes.
265 if (isArray && /^[0-9]/.test(names[i]))
266 continue;
267 group.items[group.items.length] = names[i];
268 }
269 }
270 } catch (e) {
271 }
272 return result;
273 }
274
275 /**
276 * @param {?SDK.RemoteObject} object
277 */
278 function completionsForObject(object) {
279 if (!object) {
280 receivedPropertyNames(null);
281 } else if (object.type === 'object' || object.type === 'function') {
282 object.callFunctionJSON(
283 getCompletions, [SDK.RemoteObject.toCallArgument(object.subtype)], r eceivedPropertyNames);
284 } else if (object.type === 'string' || object.type === 'number' || object. type === 'boolean') {
285 executionContext.evaluate(
286 '(' + getCompletions + ')("' + result.type + '")', 'completion', fal se, true, true, false, false,
287 receivedPropertyNamesFromEval);
288 }
289 }
290
291 extractTarget(result).then(completionsForObject);
292 }
293
294 /**
295 * @param {!SDK.DebuggerModel.CallFrame} callFrame
296 * @param {function(!Array<!Components.JavaScriptAutocomplete.CompletionGroup> )} callback
297 */
298 function variableNamesInScopes(callFrame, callback) {
299 var result = [{items: ['this']}];
300
301 /**
302 * @param {string} name
303 * @param {?Array<!SDK.RemoteObjectProperty>} properties
304 */
305 function propertiesCollected(name, properties) {
306 var group = {title: name, items: []};
307 result.push(group);
308 for (var i = 0; properties && i < properties.length; ++i)
309 group.items.push(properties[i].name);
310 if (--pendingRequests === 0)
311 callback(result);
312 }
313
314 var scopeChain = callFrame.scopeChain();
315 var pendingRequests = scopeChain.length;
316 for (var i = 0; i < scopeChain.length; ++i) {
317 var scope = scopeChain[i];
318 var object = scope.object();
319 object.getAllProperties(
320 false /* accessorPropertiesOnly */, false /* generatePreview */,
321 propertiesCollected.bind(null, scope.typeName()));
322 }
323 }
324
325 /**
326 * @param {?SDK.RemoteObject} result
327 * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails
328 */
329 function receivedPropertyNamesFromEval(result, exceptionDetails) {
330 executionContext.target().runtimeAgent().releaseObjectGroup('completion');
331 if (result && !exceptionDetails)
332 receivedPropertyNames(/** @type {!Object} */ (result.value));
333 else
334 fulfill([]);
335 }
336
337 /**
338 * @param {?Object} object
339 */
340 function receivedPropertyNames(object) {
341 executionContext.target().runtimeAgent().releaseObjectGroup('completion');
342 if (!object) {
343 fulfill([]);
344 return;
345 }
346 var propertyGroups = /** @type {!Array<!Components.JavaScriptAutocomplete.Co mpletionGroup>} */ (object);
347 var includeCommandLineAPI = (!dotNotation && !bracketNotation);
348 if (includeCommandLineAPI) {
349 const commandLineAPI = [
350 'dir',
351 'dirxml',
352 'keys',
353 'values',
354 'profile',
355 'profileEnd',
356 'monitorEvents',
357 'unmonitorEvents',
358 'inspect',
359 'copy',
360 'clear',
361 'getEventListeners',
362 'debug',
363 'undebug',
364 'monitor',
365 'unmonitor',
366 'table',
367 '$',
368 '$$',
369 '$x'
370 ];
371 propertyGroups.push({items: commandLineAPI});
372 }
373 fulfill(Components.JavaScriptAutocomplete._completionsForQuery(
374 dotNotation, bracketNotation, expressionString, query, propertyGroups));
375 }
376 };
377
378 /**
379 * @param {boolean} dotNotation
380 * @param {boolean} bracketNotation
381 * @param {string} expressionString
382 * @param {string} query
383 * @param {!Array<!Components.JavaScriptAutocomplete.CompletionGroup>} propert yGroups
384 * @return {!UI.SuggestBox.Suggestions}
385 */
386 Components.JavaScriptAutocomplete._completionsForQuery = function(
387 dotNotation, bracketNotation, expressionString, query, propertyGroups) {
388 if (bracketNotation) {
389 if (query.length && query[0] === '\'')
390 var quoteUsed = '\'';
391 else
392 var quoteUsed = '"';
393 }
394
395 if (!expressionString) {
396 const keywords = [
397 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do', 'else', 'finally',
398 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return ', 'switch', 'this',
399 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with'
400 ];
401 propertyGroups.push({title: Common.UIString('keywords'), items: keywords});
402 }
403
404 var result = [];
405 var lastGroupTitle;
406 for (var group of propertyGroups) {
407 group.items.sort(itemComparator.bind(null, group.items.length > 1000));
408 var caseSensitivePrefix = [];
409 var caseInsensitivePrefix = [];
410 var caseSensitiveAnywhere = [];
411 var caseInsensitiveAnywhere = [];
412
413 for (var property of group.items) {
414 // Assume that all non-ASCII characters are letters and thus can be used a s part of identifier.
415 if (!bracketNotation && !/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFF F]*$/.test(property))
416 continue;
417
418 if (bracketNotation) {
419 if (!/^[0-9]+$/.test(property))
420 property = quoteUsed + property.escapeCharacters(quoteUsed + '\\') + q uoteUsed;
421 property += ']';
422 }
423
424 if (property.length < query.length)
425 continue;
426 if (query.length && property.toLowerCase().indexOf(query.toLowerCase()) == = -1)
427 continue;
428 // Substitute actual newlines with newline characters. @see crbug.com/4984 21
429 var prop = property.split('\n').join('\\n');
430
431 if (property.startsWith(query))
432 caseSensitivePrefix.push({text: prop, priority: 4});
433 else if (property.toLowerCase().startsWith(query.toLowerCase()))
434 caseInsensitivePrefix.push({text: prop, priority: 3});
435 else if (property.indexOf(query) !== -1)
436 caseSensitiveAnywhere.push({text: prop, priority: 2});
437 else
438 caseInsensitiveAnywhere.push({text: prop, priority: 1});
439 }
440 var structuredGroup =
441 caseSensitivePrefix.concat(caseInsensitivePrefix, caseSensitiveAnywhere, caseInsensitiveAnywhere);
442 if (structuredGroup.length && group.title !== lastGroupTitle) {
443 structuredGroup[0].subtitle = group.title;
444 lastGroupTitle = group.title;
445 }
446 result = result.concat(structuredGroup);
447 result.forEach(item => {
448 if (item.text.endsWith(']'))
449 item.title = item.text.substring(0, item.text.length - 1);
450 });
451 }
452 return result;
453
454 /**
455 * @param {boolean} naturalOrder
456 * @param {string} a
457 * @param {string} b
458 * @return {number}
459 */
460 function itemComparator(naturalOrder, a, b) {
461 var aStartsWithUnderscore = a.startsWith('_');
462 var bStartsWithUnderscore = b.startsWith('_');
463 if (aStartsWithUnderscore && !bStartsWithUnderscore)
464 return 1;
465 if (bStartsWithUnderscore && !aStartsWithUnderscore)
466 return -1;
467 return naturalOrder ? String.naturalOrderComparator(a, b) : a.localeCompare( b);
468 }
469 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698