Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: chrome/content/ui/filters-search.js

Issue 29356477: Issue 4510 - Filter preferences: replace the built-in findbar widget by our own look-alike (Closed) Base URL: https://hg.adblockplus.org/adblockplus
Patch Set: Addressed comments Created Oct. 12, 2016, 3:21 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/content/ui/filters.xul ('k') | chrome/locale/en-US/filters.dtd » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>, 2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2016 Eyeo GmbH 3 * Copyright (C) 2006-2016 Eyeo GmbH
4 * 4 *
5 * Adblock Plus is free software: you can redistribute it and/or modify 5 * Adblock Plus is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 3 as 6 * it under the terms of the GNU General Public License version 3 as
7 * published by the Free Software Foundation. 7 * published by the Free Software Foundation.
8 * 8 *
9 * Adblock Plus is distributed in the hope that it will be useful, 9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. 12 * GNU General Public License for more details.
13 * 13 *
14 * You should have received a copy of the GNU General Public License 14 * You should have received a copy of the GNU General Public License
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */ 16 */
17 17
18 /** 18 /**
19 * Implementation of the filter search functionality. 19 * Implementation of the filter search functionality.
20 * @class 20 * @class
21 */ 21 */
22 var FilterSearch = 22 var FilterSearch =
23 { 23 {
24 lastSearchString: null,
25
24 /** 26 /**
25 * Initializes findbar widget. 27 * Handles keypress events on the findbar widget.
26 */ 28 */
27 init: function() 29 keyPress: function(/**Event*/ event)
28 { 30 {
29 let filters = E("filtersTree"); 31 if (event.keyCode == KeyEvent.DOM_VK_RETURN)
30 for (let prop in FilterSearch.fakeBrowser) 32 event.preventDefault();
31 filters[prop] = FilterSearch.fakeBrowser[prop]; 33 else if (event.keyCode == KeyEvent.DOM_VK_ESCAPE)
32 Object.defineProperty(filters, "_lastSearchString", {
33 get: function()
34 {
35 return this.finder.searchString;
36 },
37 enumerable: true,
38 configurable: true
39 });
40
41 let findbar = E("findbar");
42 findbar.browser = filters;
43
44 findbar.addEventListener("keypress", function(event)
45 { 34 {
46 // Work-around for bug 490047 35 event.preventDefault();
47 if (event.keyCode == KeyEvent.DOM_VK_RETURN) 36 this.close();
48 event.preventDefault(); 37 }
49 }, false); 38 else if (event.keyCode == KeyEvent.DOM_VK_UP)
50 39 {
51 // Hack to prevent "highlight all" from getting enabled 40 event.preventDefault();
52 findbar.toggleHighlight = function() {}; 41 this.search(-1);
42 }
43 else if (event.keyCode == KeyEvent.DOM_VK_DOWN)
44 {
45 event.preventDefault();
46 this.search(1);
47 }
48 else if (event.keyCode == KeyEvent.DOM_VK_PAGE_UP)
49 {
50 event.preventDefault();
51 E("filtersTree").treeBoxObject.scrollByPages(-1);
52 }
53 else if (event.keyCode == KeyEvent.DOM_VK_PAGE_DOWN)
54 {
55 event.preventDefault();
56 E("filtersTree").treeBoxObject.scrollByPages(1);
57 }
53 }, 58 },
54 59
55 /** 60 /**
56 * Performs a text search. 61 * Makes the find bar visible and focuses it.
57 * @param {String} text text to be searched
58 * @param {Integer} direction search direction: -1 (backwards), 0 (forwards
59 * starting with current), 1 (forwards starting with next)
60 * @param {Boolean} caseSensitive if true, a case-sensitive search is perform ed
61 * @result {Integer} one of the nsITypeAheadFind constants
62 */ 62 */
63 search: function(text, direction, caseSensitive) 63 open: function()
64 { 64 {
65 function normalizeString(string) caseSensitive ? string : string.toLowerCase (); 65 E("findbar").hidden = false;
66 E("findbar-textbox").focus();
67 },
66 68
67 function findText(text, direction, startIndex) 69 /**
70 * Closes the find bar.
71 */
72 close: function()
73 {
74 E("findbar").hidden = true;
75 },
76
77 /**
78 * Performs a filter search.
79 * @param {Integer} [direction]
80 * See @link{FilterSearch#search}
81 * @return {String}
82 * result status, one of "" (success), "notFound", "wrappedEnd",
83 * "wrappedStart"
84 */
85 _search: function(direction)
86 {
87 let text = E("findbar-textbox").value.trim();
88 if (!text)
89 return "";
90
91 let caseSensitive = E("findbar-case-sensitive").checked;
92
93 if (typeof direction == "undefined")
94 direction = (text == this.lastSearchString ? 1 : 0);
95 this.lastSearchString = text;
96
97 function normalizeString(string)
98 {
99 return caseSensitive ? string : string.toLowerCase();
100 }
101
102 function findText(startIndex)
68 { 103 {
69 let list = E("filtersTree"); 104 let list = E("filtersTree");
70 let col = list.columns.getNamedColumn("col-filter"); 105 let col = list.columns.getNamedColumn("col-filter");
71 let count = list.view.rowCount; 106 let count = list.view.rowCount;
72 for (let i = startIndex + direction; i >= 0 && i < count; i += (direction || 1)) 107 for (let i = startIndex + direction; i >= 0 && i < count; i += (direction || 1))
73 { 108 {
74 let filter = normalizeString(list.view.getCellText(i, col)); 109 let filter = normalizeString(list.view.getCellText(i, col));
75 if (filter.indexOf(text) >= 0) 110 if (filter.indexOf(text) >= 0)
76 { 111 {
77 FilterView.selectRow(i); 112 FilterView.selectRow(i);
78 return true; 113 return true;
79 } 114 }
80 } 115 }
81 return false; 116 return false;
82 } 117 }
83 118
84 text = normalizeString(text); 119 text = normalizeString(text);
85 120
86 // First try to find the entry in the current list 121 // First try to find the entry in the current list
87 if (findText(text, direction, E("filtersTree").currentIndex)) 122 if (findText(E("filtersTree").currentIndex))
88 return Ci.nsITypeAheadFind.FIND_FOUND; 123 return "";
89 124
90 // Now go through the other subscriptions 125 // Now go through the other subscriptions
91 let result = Ci.nsITypeAheadFind.FIND_FOUND; 126 let result = "";
92 let subscriptions = FilterStorage.subscriptions.slice(); 127 let subscriptions = FilterStorage.subscriptions.slice();
93 subscriptions.sort((s1, s2) => (s1 instanceof SpecialSubscription) - (s2 ins tanceof SpecialSubscription)); 128 subscriptions.sort((s1, s2) => (s1 instanceof SpecialSubscription) - (s2 ins tanceof SpecialSubscription));
94 let current = subscriptions.indexOf(FilterView.subscription); 129 let current = subscriptions.indexOf(FilterView.subscription);
95 direction = direction || 1; 130 direction = direction || 1;
96 for (let i = current + direction; ; i+= direction) 131 for (let i = current + direction; ; i+= direction)
97 { 132 {
98 if (i < 0) 133 if (i < 0)
99 { 134 {
100 i = subscriptions.length - 1; 135 i = subscriptions.length - 1;
101 result = Ci.nsITypeAheadFind.FIND_WRAPPED; 136 result = "wrappedStart";
102 } 137 }
103 else if (i >= subscriptions.length) 138 else if (i >= subscriptions.length)
104 { 139 {
105 i = 0; 140 i = 0;
106 result = Ci.nsITypeAheadFind.FIND_WRAPPED; 141 result = "wrappedEnd";
107 } 142 }
108 if (i == current) 143 if (i == current)
109 break; 144 break;
110 145
111 let subscription = subscriptions[i]; 146 let subscription = subscriptions[i];
112 for (let j = 0; j < subscription.filters.length; j++) 147 for (let j = 0; j < subscription.filters.length; j++)
113 { 148 {
114 let filter = normalizeString(subscription.filters[j].text); 149 let filter = normalizeString(subscription.filters[j].text);
115 if (filter.indexOf(text) >= 0) 150 if (filter.indexOf(text) >= 0)
116 { 151 {
117 let list = E(subscription instanceof SpecialSubscription ? "groups" : "subscriptions"); 152 let list = E(subscription instanceof SpecialSubscription ? "groups" : "subscriptions");
118 let node = Templater.getNodeForData(list, "subscription", subscription ); 153 let node = Templater.getNodeForData(list, "subscription", subscription );
119 if (!node) 154 if (!node)
120 break; 155 break;
121 156
122 // Select subscription in its list and restore focus after that 157 // Select subscription in its list and restore focus after that
123 let oldFocus = document.commandDispatcher.focusedElement; 158 let oldFocus = document.commandDispatcher.focusedElement;
124 E("tabs").selectedIndex = (subscription instanceof SpecialSubscription ? 1 : 0); 159 E("tabs").selectedIndex = (subscription instanceof SpecialSubscription ? 1 : 0);
125 list.ensureElementIsVisible(node); 160 list.ensureElementIsVisible(node);
126 list.selectItem(node); 161 list.selectItem(node);
127 if (oldFocus) 162 if (oldFocus)
128 { 163 {
129 oldFocus.focus(); 164 oldFocus.focus();
130 Utils.runAsync(() => oldFocus.focus()); 165 Utils.runAsync(() => oldFocus.focus());
131 } 166 }
132 167
133 Utils.runAsync(() => findText(text, direction, direction == 1 ? -1 : subscription.filters.length)); 168 Utils.runAsync(() => findText(direction == 1 ? -1 : subscription.filt ers.length));
134 return result; 169 return result;
135 } 170 }
136 } 171 }
137 } 172 }
138 173
139 return Ci.nsITypeAheadFind.FIND_NOTFOUND; 174 return "notFound";
175 },
176
177 /**
178 * Performs a filter search and displays the resulting search status.
179 * @param {Integer} [direction]
180 * search direction: -1 (backwards), 0 (forwards starting with current),
181 * 1 (forwards starting with next)
182 */
183 search: function(direction)
184 {
185 E("findbar").setAttribute("data-status", this._search(direction));
140 } 186 }
141 }; 187 };
142 188
143 /** 189 window.addEventListener("load", event =>
144 * Fake browser implementation to make findbar widget happy - searches in
145 * the filter list.
146 */
147 FilterSearch.fakeBrowser =
148 { 190 {
149 finder: 191 E("findbar").setAttribute("data-os", Services.appinfo.OS.toLowerCase());
150 { 192 });
151 _resultListeners: [],
152 searchString: null,
153 caseSensitive: false,
154 lastResult: null,
155
156 _notifyResultListeners: function(result, findBackwards)
157 {
158 this.lastResult = result;
159 for (let listener of this._resultListeners)
160 {
161 // See https://bugzilla.mozilla.org/show_bug.cgi?id=958101, starting
162 // with Gecko 29 only one parameter is expected.
163 try
164 {
165 if (listener.onFindResult.length == 1)
166 {
167 listener.onFindResult({searchString: this.searchString,
168 result: result, findBackwards: findBackwards});
169 }
170 else
171 listener.onFindResult(result, findBackwards);
172 }
173 catch (e)
174 {
175 Cu.reportError(e);
176 }
177 }
178 },
179
180 fastFind: function(searchString, linksOnly, drawOutline)
181 {
182 this.searchString = searchString;
183 let result = FilterSearch.search(this.searchString, 0,
184 this.caseSensitive);
185 this._notifyResultListeners(result, false);
186 },
187
188 findAgain: function(findBackwards, linksOnly, drawOutline)
189 {
190 let result = FilterSearch.search(this.searchString,
191 findBackwards ? -1 : 1,
192 this.caseSensitive);
193 this._notifyResultListeners(result, findBackwards);
194 },
195
196 addResultListener: function(listener)
197 {
198 if (this._resultListeners.indexOf(listener) === -1)
199 this._resultListeners.push(listener);
200 },
201
202 removeResultListener: function(listener)
203 {
204 let index = this._resultListeners.indexOf(listener);
205 if (index !== -1)
206 this._resultListeners.splice(index, 1);
207 },
208
209 getInitialSelection: function()
210 {
211 for (let listener of this._resultListeners)
212 listener.onCurrentSelection(null, true);
213 },
214
215 // Irrelevant for us
216 requestMatchesCount: function(searchString, matchLimit, linksOnly) {},
217 highlight: function(highlight, word) {},
218 enableSelection: function() {},
219 removeSelection: function() {},
220 focusContent: function() {},
221 keyPress: function() {}
222 },
223
224 currentURI: Utils.makeURI("http://example.com/"),
225 contentWindow:
226 {
227 focus: function()
228 {
229 E("filtersTree").focus();
230 },
231 scrollByLines: function(num)
232 {
233 E("filtersTree").boxObject.scrollByLines(num);
234 },
235 scrollByPages: function(num)
236 {
237 E("filtersTree").boxObject.scrollByPages(num);
238 },
239 },
240
241 messageManager:
242 {
243 _messageMap: {
244 "Findbar:Mouseup": "mouseup",
245 "Findbar:Keypress": "keypress"
246 },
247
248 _messageFromEvent: function(event)
249 {
250 for (let message in this._messageMap)
251 if (this._messageMap[message] == event.type)
252 return {target: event.currentTarget, name: message, data: event};
253 return null;
254 },
255
256 addMessageListener: function(message, listener)
257 {
258 if (!this._messageMap.hasOwnProperty(message))
259 return;
260
261 if (!("_ABPHandler" in listener))
262 listener._ABPHandler = (event) => listener.receiveMessage(this._messageF romEvent(event));
263
264 E("filtersTree").addEventListener(this._messageMap[message], listener._ABP Handler, false);
265 },
266
267 removeMessageListener: function(message, listener)
268 {
269 if (this._messageMap.hasOwnProperty(message) && listener._ABPHandler)
270 E("filtersTree").removeEventListener(this._messageMap[message], listener ._ABPHandler, false);
271 },
272
273 sendAsyncMessage: function() {}
274 }
275 };
276
277 window.addEventListener("load", function()
278 {
279 FilterSearch.init();
280 }, false);
OLDNEW
« no previous file with comments | « chrome/content/ui/filters.xul ('k') | chrome/locale/en-US/filters.dtd » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld