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

Side by Side Diff: chrome/third_party/chromevox/extensions/searchvox/search.js

Issue 274963004: Update latest ChromeVox from upstream (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 7 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 2013 Google Inc. All Rights Reserved.
2
3 /**
4 * @fileoverview Uses ChromeVox API to enhance the search experience.
5 * @author peterxiao@google.com (Peter Xiao)
6 */
7
8 goog.provide('cvox.Search');
9
10 goog.require('cvox.ChromeVox');
11 goog.require('cvox.SearchConstants');
12 goog.require('cvox.SearchResults');
13 goog.require('cvox.SearchUtil');
14 goog.require('cvox.UnknownResult');
15
16 /**
17 * @constructor
18 */
19 cvox.Search = function() {
20 };
21
22 /**
23 * Selectors to match results.
24 * @type {Object.<string, string>}
25 */
26 cvox.Search.selectors = {};
27
28 /**
29 * Selectors for web results.
30 */
31 cvox.Search.webSelectors = {
32 /* Topstuff typically contains important messages to be added first. */
33 TOPSTUFF_SELECT: '#topstuff',
34 SPELL_SUGG_SELECT: '.ssp',
35 SPELL_CORRECTION_SELECT: '.sp_cnt',
36 KNOW_PANEL_SELECT: '.knop',
37 RESULT_SELECT: 'li.g',
38 RELATED_SELECT: '#brs'
39 };
40
41 /**
42 * Selectors for image results.
43 */
44 cvox.Search.imageSelectors = {
45 IMAGE_CATEGORIES_SELECT: '#ifbc .rg_fbl',
46 IMAGE_RESULT_SELECT: '#rg_s .rg_di'
47 };
48
49 /**
50 * Index of the currently synced result.
51 * @type {number}
52 */
53 cvox.Search.index;
54
55 /**
56 * Array of the search results.
57 * @type {Array.<Element>}
58 */
59 cvox.Search.results = [];
60
61 /**
62 * Array of the navigation panes.
63 * @type {Array.<Element>}
64 */
65 cvox.Search.panes = [];
66
67 /**
68 * Index of the currently synced pane.
69 * @type {number}
70 */
71 cvox.Search.paneIndex;
72
73 /**
74 * If currently synced item is a pane.
75 */
76 cvox.Search.isPane = false;
77
78 /**
79 * Class of a selected pane.
80 */
81 cvox.Search.SELECTED_PANE_CLASS = 'hdtb_mitem hdtb_msel';
82
83
84 /**
85 * Speak and sync.
86 * @private
87 */
88 cvox.Search.speakSync_ = function() {
89 var result = cvox.Search.results[cvox.Search.index];
90 var resultType = cvox.Search.getResultType(result);
91 var isSpoken = resultType.speak(result);
92 cvox.ChromeVox.syncToNode(resultType.getSyncNode(result), !isSpoken);
93 cvox.Search.isPane = false;
94 };
95
96 /**
97 * Sync the search result index to ChromeVox.
98 */
99 cvox.Search.syncToIndex = function() {
100 cvox.ChromeVox.tts.stop();
101 var prop = { endCallback: cvox.Search.speakSync_ };
102 if (cvox.Search.index === 0) {
103 cvox.ChromeVox.tts.speak('First result', 1, prop);
104 } else if (cvox.Search.index === cvox.Search.results.length - 1) {
105 cvox.ChromeVox.tts.speak('Last result', 1, prop);
106 } else {
107 cvox.Search.speakSync_();
108 }
109 };
110
111 /**
112 * Sync the current pane index to ChromeVox.
113 */
114 cvox.Search.syncPaneToIndex = function() {
115 var pane = cvox.Search.panes[cvox.Search.paneIndex];
116 var anchor = pane.querySelector('a');
117 if (anchor) {
118 cvox.ChromeVox.syncToNode(anchor, true);
119 } else {
120 cvox.ChromeVox.syncToNode(pane, true);
121 }
122 cvox.Search.isPane = true;
123 };
124
125 /**
126 * Get the type of the result such as Knowledge Panel, Weather, etc.
127 * @param {Element} result Result to be classified.
128 * @return {cvox.AbstractResult} Type of the result.
129 */
130 cvox.Search.getResultType = function(result) {
131 for (var i = 0; i < cvox.SearchResults.RESULT_TYPES.length; i++) {
132 var resultType = new cvox.SearchResults.RESULT_TYPES[i]();
133 if (resultType.isType(result)) {
134 return resultType;
135 }
136 }
137 return new cvox.UnknownResult();
138 };
139
140 /**
141 * Get the page number associated with the url.
142 * @param {string} url Url of search page.
143 * @return {number} Page number.
144 */
145 cvox.Search.getPageNumber = function(url) {
146 var PAGE_ANCHOR_SELECTOR = '#nav .fl';
147 var pageAnchors = document.querySelectorAll(PAGE_ANCHOR_SELECTOR);
148 for (var i = 0; i < pageAnchors.length; i++) {
149 var pageAnchor = pageAnchors.item(i);
150 if (pageAnchor.href === url) {
151 return parseInt(pageAnchor.innerText, 10);
152 }
153 }
154 return NaN;
155 };
156
157 /**
158 * Navigate to the next / previous page.
159 * @param {boolean} next True for the next page, false for the previous.
160 */
161 cvox.Search.navigatePage = function(next) {
162 /* NavEnd contains previous / next page links. */
163 var NAV_END_CLASS = 'navend';
164 var navEnds = document.getElementsByClassName(NAV_END_CLASS);
165 var navEnd = next ? navEnds[1] : navEnds[0];
166 var url = cvox.SearchUtil.extractURL(navEnd);
167 var navToUrl = function() {
168 window.location = url;
169 };
170 var prop = { endCallback: navToUrl };
171 if (url) {
172 var pageNumber = cvox.Search.getPageNumber(url);
173 if (!isNaN(pageNumber)) {
174 cvox.ChromeVox.tts.speak('Page ' + pageNumber, 0, prop);
175 } else {
176 cvox.ChromeVox.tts.speak('Unknown page.', 0, prop);
177 }
178 }
179 };
180
181 /**
182 * Navigates to the currently synced pane.
183 */
184 cvox.Search.goToPane = function() {
185 var pane = cvox.Search.panes[cvox.Search.paneIndex];
186 if (pane.className === cvox.Search.SELECTED_PANE_CLASS) {
187 cvox.ChromeVox.tts.speak('You are already on that page.');
188 return;
189 }
190 var anchor = pane.querySelector('a');
191 cvox.ChromeVox.tts.speak(anchor.textContent);
192 var url = cvox.SearchUtil.extractURL(pane);
193 if (url) {
194 window.location = url;
195 }
196 };
197
198 /**
199 * Follow the link to the current result.
200 */
201 cvox.Search.goToResult = function() {
202 var result = cvox.Search.results[cvox.Search.index];
203 var resultType = cvox.Search.getResultType(result);
204 var url = resultType.getURL(result);
205 if (url) {
206 window.location = url;
207 }
208 };
209
210 /**
211 * Handle the keyboard.
212 * @param {Event} evt Keydown event.
213 * @return {boolean} True if key was handled, false otherwise.
214 */
215 cvox.Search.keyhandler = function(evt) {
216 var SEARCH_INPUT_ID = 'gbqfq';
217 var searchInput = document.getElementById(SEARCH_INPUT_ID);
218 var result = cvox.Search.results[cvox.Search.index];
219 var ret = false;
220
221 /* TODO(peterxiao): Add cvox api call to determine cvox key. */
222 if (evt.shiftKey || evt.altKey || evt.ctrlKey) {
223 return false;
224 }
225
226 /* Do not handle if search input has focus, or if the search widget
227 * has focus.
228 */
229 if (document.activeElement !== searchInput &&
230 !cvox.SearchUtil.isSearchWidgetActive()) {
231 switch (evt.keyCode) {
232 case cvox.SearchConstants.KeyCode.UP:
233 /* Add results.length because JS Modulo is silly. */
234 cvox.Search.index = cvox.SearchUtil.subOneWrap(cvox.Search.index,
235 cvox.Search.results.length);
236 if (cvox.Search.index === cvox.Search.results.length - 1) {
237 cvox.ChromeVox.earcons.playEarconByName('WRAP');
238 }
239 cvox.Search.syncToIndex();
240 break;
241
242 case cvox.SearchConstants.KeyCode.DOWN:
243 cvox.Search.index = cvox.SearchUtil.addOneWrap(cvox.Search.index,
244 cvox.Search.results.length);
245 if (cvox.Search.index === 0) {
246 cvox.ChromeVox.earcons.playEarconByName('WRAP');
247 }
248 cvox.Search.syncToIndex();
249 break;
250
251 case cvox.SearchConstants.KeyCode.PAGE_UP:
252 cvox.Search.navigatePage(false);
253 break;
254
255 case cvox.SearchConstants.KeyCode.PAGE_DOWN:
256 cvox.Search.navigatePage(true);
257 break;
258
259 case cvox.SearchConstants.KeyCode.LEFT:
260 cvox.Search.paneIndex = cvox.SearchUtil.subOneWrap(cvox.Search.paneIndex,
261 cvox.Search.panes.length);
262 cvox.Search.syncPaneToIndex();
263 break;
264
265 case cvox.SearchConstants.KeyCode.RIGHT:
266 cvox.Search.paneIndex = cvox.SearchUtil.addOneWrap(cvox.Search.paneIndex,
267 cvox.Search.panes.length);
268 cvox.Search.syncPaneToIndex();
269 break;
270
271 case cvox.SearchConstants.KeyCode.ENTER:
272 if (cvox.Search.isPane) {
273 cvox.Search.goToPane();
274 } else {
275 cvox.Search.goToResult();
276 }
277 break;
278
279 default:
280 return false;
281 }
282 evt.preventDefault();
283 evt.stopPropagation();
284 return true;
285 }
286 return false;
287 };
288
289 /**
290 * Adds the elements that match the selector to results.
291 * @param {string} selector Selector of element to add.
292 */
293 cvox.Search.addToResultsBySelector = function(selector) {
294 var nodes = document.querySelectorAll(selector);
295 for (var i = 0; i < nodes.length; i++) {
296 var node = nodes.item(i);
297 /* Do not add if empty. */
298 if (node.innerHTML !== '') {
299 cvox.Search.results.push(nodes.item(i));
300 }
301 }
302 };
303
304 /**
305 * Populates the panes array.
306 */
307 cvox.Search.populatePanes = function() {
308 cvox.Search.panes = [];
309 var PANE_SELECT = '.hdtb_mitem';
310 var paneElems = document.querySelectorAll(PANE_SELECT);
311 for (var i = 0; i < paneElems.length; i++) {
312 cvox.Search.panes.push(paneElems.item(i));
313 }
314 };
315
316 /**
317 * Populates the results with results.
318 */
319 cvox.Search.populateResults = function() {
320 for (var prop in cvox.Search.selectors) {
321 cvox.Search.addToResultsBySelector(cvox.Search.selectors[prop]);
322 }
323 };
324
325 /**
326 * Populates the results with ad results.
327 */
328 cvox.Search.populateAdResults = function() {
329 cvox.Search.results = [];
330 var ADS_SELECT = '.ads-ad';
331 cvox.Search.addToResultsBySelector(ADS_SELECT);
332 };
333
334 /**
335 * Observes mutations and updates results accordingly.
336 */
337 cvox.Search.observeMutation = function() {
338 var SEARCH_AREA_SELECT = '#rg_s';
339 var target = document.querySelector(SEARCH_AREA_SELECT);
340
341 var observer = new MutationObserver(function(mutations) {
342 cvox.Search.results = [];
343 cvox.Search.populateResults();
344 });
345
346 var config =
347 /** @type MutationObserverInit */
348 ({ attributes: true, childList: true, characterData: true });
349 observer.observe(target, config);
350 };
351
352 /**
353 * Get the current selected pane's index.
354 * @return {number} Index of selected pane.
355 */
356 cvox.Search.getSelectedPaneIndex = function() {
357 var panes = cvox.Search.panes;
358 for (var i = 0; i < panes.length; i++) {
359 if (panes[i].className === cvox.Search.SELECTED_PANE_CLASS) {
360 return i;
361 }
362 }
363 return 0;
364 };
365
366 /**
367 * Get the ancestor of node that is a result.
368 * @param {Node} node Node.
369 * @return {Node} Result ancestor.
370 */
371 cvox.Search.getAncestorResult = function(node) {
372 var curr = node;
373 while (curr) {
374 for (var prop in cvox.Search.selectors) {
375 var selector = cvox.Search.selectors[prop];
376 if (curr.webkitMatchesSelector && curr.webkitMatchesSelector(selector)) {
377 return curr;
378 }
379 }
380 curr = curr.parentNode;
381 }
382 return null;
383 };
384
385 /**
386 * Sync to the correct initial node.
387 */
388 cvox.Search.initialSync = function() {
389 var currNode = cvox.ChromeVox.navigationManager.getCurrentNode();
390 var result = cvox.Search.getAncestorResult(currNode);
391 cvox.Search.index = cvox.Search.results.indexOf(result);
392 if (cvox.Search.index === -1) {
393 cvox.Search.index = 0;
394 }
395
396 if (cvox.Search.results.length > 0) {
397 cvox.Search.syncToIndex();
398 }
399 };
400
401 /**
402 * Initialize Search.
403 */
404 cvox.Search.init = function() {
405 cvox.Search.index = 0;
406
407 /* Flush out anything that may have been speaking. */
408 cvox.ChromeVox.tts.stop();
409
410 /* Determine the type of search. */
411 var SELECTED_CLASS = 'hdtb_msel';
412 var selected = document.getElementsByClassName(SELECTED_CLASS)[0];
413 if (!selected) {
414 return;
415 }
416
417 var selectedHTML = selected.innerHTML;
418 switch (selectedHTML) {
419 case 'Web':
420 case 'News':
421 cvox.Search.selectors = cvox.Search.webSelectors;
422 break;
423 case 'Images':
424 cvox.Search.selectors = cvox.Search.imageSelectors;
425 cvox.Search.observeMutation();
426 break;
427 default:
428 return;
429 }
430
431 cvox.Search.populateResults();
432 cvox.Search.populatePanes();
433 cvox.Search.paneIndex = cvox.Search.getSelectedPaneIndex();
434
435 cvox.Search.initialSync();
436
437 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698