Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / net_internals / source_filter_parser.js
1 // Copyright (c) 2013 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 var SourceFilterParser = (function() {
6   'use strict';
7
8   /**
9    * Parses |filterText|, extracting a sort method, a list of filters, and a
10    * copy of |filterText| with all sort parameters removed.
11    */
12   function SourceFilterParser(filterText) {
13     // Final output will be stored here.
14     this.filter = null;
15     this.sort = {};
16     this.filterTextWithoutSort = '';
17     var filterList = parseFilter_(filterText);
18
19     // Text filters are stored here as strings and then added as a function at
20     // the end, for performance reasons.
21     var textFilters = [];
22
23     // Filter functions are first created individually, and then merged.
24     var filterFunctions = [];
25
26     for (var i = 0; i < filterList.length; ++i) {
27       var filterElement = filterList[i].parsed;
28       var negated = filterList[i].negated;
29
30       var sort = parseSortDirective_(filterElement, negated);
31       if (sort) {
32         this.sort = sort;
33         continue;
34       }
35
36       this.filterTextWithoutSort += filterList[i].original;
37
38       var filter = parseRestrictDirective_(filterElement, negated);
39       if (!filter)
40         filter = parseStringDirective_(filterElement, negated);
41       if (filter) {
42         if (negated) {
43           filter = (function(func, sourceEntry) {
44             return !func(sourceEntry);
45           }).bind(null, filter);
46         }
47         filterFunctions.push(filter);
48         continue;
49       }
50       textFilters.push({ text: filterElement, negated: negated });
51     }
52
53     // Create a single filter for all text filters, so they can share a
54     // TabePrinter.
55     filterFunctions.push(textFilter_.bind(null, textFilters));
56
57     // Create function to go through all the filters.
58     this.filter = function(sourceEntry) {
59       for (var i = 0; i < filterFunctions.length; ++i) {
60         if (!filterFunctions[i](sourceEntry))
61           return false;
62       }
63       return true;
64     };
65   }
66
67   /**
68    * Parses a single "sort:" directive, and returns a dictionary containing
69    * the sort function and direction.  Returns null on failure, including
70    * the case when no such sort function exists.
71    */
72   function parseSortDirective_(filterElement, backwards) {
73     var match = /^sort:(.*)$/.exec(filterElement);
74     if (!match)
75       return null;
76     return { method: match[1], backwards: backwards };
77   }
78
79   /**
80    * Tries to parses |filterElement| as a single "is:" directive, and returns a
81    * new filter function.  Returns null on failure.
82    */
83   function parseRestrictDirective_(filterElement) {
84     var match = /^is:(.*)$/.exec(filterElement);
85     if (!match)
86       return null;
87     if (match[1] == 'active') {
88       return function(sourceEntry) { return !sourceEntry.isInactive(); };
89     }
90     if (match[1] == 'error') {
91       return function(sourceEntry) { return sourceEntry.isError(); };
92     }
93     return null;
94   }
95
96   /**
97    * Tries to parse |filterElement| as a single filter of a type that takes
98    * arbitrary strings as input, and returns a new filter function on success.
99    * Returns null on failure.
100    */
101   function parseStringDirective_(filterElement) {
102     var match = RegExp('^([^:]*):(.*)$').exec(filterElement);
103     if (!match)
104       return null;
105
106     // Split parameters around commas and remove empty elements.
107     var parameters = match[2].split(',');
108     parameters = parameters.filter(function(string) {
109       return string.length > 0;
110     });
111
112     if (match[1] == 'type') {
113       return function(sourceEntry) {
114         var i;
115         var sourceType = sourceEntry.getSourceTypeString().toLowerCase();
116         for (i = 0; i < parameters.length; ++i) {
117           if (sourceType.search(parameters[i]) != -1)
118             return true;
119         }
120         return false;
121       };
122     }
123
124     if (match[1] == 'id') {
125       return function(sourceEntry) {
126         return parameters.indexOf(sourceEntry.getSourceId() + '') != -1;
127       };
128     }
129
130     return null;
131   }
132
133   /**
134    * Takes in the text of a filter and returns a list of
135    * {parsed, original, negated} values that correspond to substrings of the
136    * filter before and after filtering, and whether or not it started with a
137    * '-'.  Extra whitespace other than a single character after each element is
138    * ignored.  Parsed strings are all lowercase.
139    */
140   function parseFilter_(filterText) {
141     // Assemble a list of quoted and unquoted strings in the filter.
142     var filterList = [];
143     var position = 0;
144     while (position < filterText.length) {
145       var inQuote = false;
146       var filterElement = '';
147       var negated = false;
148       var startPosition = position;
149       while (position < filterText.length) {
150         var nextCharacter = filterText[position];
151         ++position;
152         if (nextCharacter == '\\' &&
153             position < filterText.length) {
154           // If there's a backslash, skip the backslash and add the next
155           // character to the element.
156           filterElement += filterText[position];
157           ++position;
158           continue;
159         } else if (nextCharacter == '"') {
160           // If there's an unescaped quote character, toggle |inQuote| without
161           // modifying the element.
162           inQuote = !inQuote;
163         } else if (!inQuote && /\s/.test(nextCharacter)) {
164           // If not in a quote and have a whitespace character, that's the
165           // end of the element.
166           break;
167         } else if (nextCharacter == '-' && startPosition == position - 1) {
168           // If this is the first character, and it's a '-', this entry is
169           // negated.
170           negated = true;
171         } else {
172           // Otherwise, add the next character to the element.
173           filterElement += nextCharacter;
174         }
175       }
176
177       if (filterElement.length > 0) {
178         var filter = {
179           parsed: filterElement.toLowerCase(),
180           original: filterText.substring(startPosition, position),
181           negated: negated,
182         };
183         filterList.push(filter);
184       }
185     }
186     return filterList;
187   }
188
189   /**
190    * Takes in a list of text filters and a SourceEntry.  Each filter has
191    * "text" and "negated" fields.  Returns true if the SourceEntry matches all
192    * filters in the (possibly empty) list.
193    */
194   function textFilter_(textFilters, sourceEntry) {
195     var tablePrinter = null;
196     for (var i = 0; i < textFilters.length; ++i) {
197       var text = textFilters[i].text;
198       var negated = textFilters[i].negated;
199       var match = false;
200       // The description is often not contained in one of the log entries.
201       // The source type almost never is, so check for them directly.
202       var description = sourceEntry.getDescription().toLowerCase();
203       var type = sourceEntry.getSourceTypeString().toLowerCase();
204       if (description.indexOf(text) != -1 || type.indexOf(text) != -1) {
205         match = true;
206       } else {
207         if (!tablePrinter)
208           tablePrinter = sourceEntry.createTablePrinter();
209         match = tablePrinter.search(text);
210       }
211       if (negated)
212         match = !match;
213       if (!match)
214         return false;
215     }
216     return true;
217   }
218
219   return SourceFilterParser;
220 })();