Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / ui / file_manager / file_manager / background / js / test_util.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 /**
6  * Namespace for test related things.
7  */
8 var test = test || {};
9
10 /**
11  * Namespace for test utility functions.
12  *
13  * Public functions in the test.util.sync and the test.util.async namespaces are
14  * published to test cases and can be called by using callRemoteTestUtil. The
15  * arguments are serialized as JSON internally. If application ID is passed to
16  * callRemoteTestUtil, the content window of the application is added as the
17  * first argument. The functions in the test.util.async namespace are passed the
18  * callback function as the last argument.
19  */
20 test.util = {};
21
22 /**
23  * Namespace for synchronous utility functions.
24  */
25 test.util.sync = {};
26
27 /**
28  * Namespace for asynchronous utility functions.
29  */
30 test.util.async = {};
31
32 /**
33  * Extension ID of the testing extension.
34  * @type {string}
35  * @const
36  */
37 test.util.TESTING_EXTENSION_ID = 'oobinhbdbiehknkpbpejbbpdbkdjmoco';
38
39 /**
40  * Opens the main Files.app's window and waits until it is ready.
41  *
42  * @param {Object} appState App state.
43  * @param {function(string)} callback Completion callback with the new window's
44  *     App ID.
45  */
46 test.util.async.openMainWindow = function(appState, callback) {
47   launchFileManager(appState,
48                     undefined,  // opt_type
49                     undefined,  // opt_id
50                     callback);
51 };
52
53 /**
54  * Obtains window information.
55  *
56  * @return {Object.<string, {innerWidth:number, innerHeight:number}>} Map window
57  *     ID and window information.
58  */
59 test.util.sync.getWindows = function() {
60   var windows = {};
61   for (var id in background.appWindows) {
62     var windowWrapper = background.appWindows[id];
63     windows[id] = {
64       outerWidth: windowWrapper.contentWindow.outerWidth,
65       outerHeight: windowWrapper.contentWindow.outerHeight
66     };
67   }
68   for (var id in background.dialogs) {
69     windows[id] = {
70       outerWidth: background.dialogs[id].outerWidth,
71       outerHeight: background.dialogs[id].outerHeight
72     };
73   }
74   return windows;
75 };
76
77 /**
78  * Closes the specified window.
79  *
80  * @param {string} appId AppId of window to be closed.
81  * @return {boolean} Result: True if success, false otherwise.
82  */
83 test.util.sync.closeWindow = function(appId) {
84   if (appId in background.appWindows &&
85       background.appWindows[appId].contentWindow) {
86     background.appWindows[appId].close();
87     return true;
88   }
89   return false;
90 };
91
92 /**
93  * Gets a document in the Files.app's window, including iframes.
94  *
95  * @param {Window} contentWindow Window to be used.
96  * @param {string=} opt_iframeQuery Query for the iframe.
97  * @return {Document=} Returns the found document or undefined if not found.
98  * @private
99  */
100 test.util.sync.getDocument_ = function(contentWindow, opt_iframeQuery) {
101   if (opt_iframeQuery) {
102     var iframe = contentWindow.document.querySelector(opt_iframeQuery);
103     return iframe && iframe.contentWindow && iframe.contentWindow.document;
104   }
105
106   return contentWindow.document;
107 };
108
109 /**
110  * Gets total Javascript error count from background page and each app window.
111  * @return {number} Error count.
112  */
113 test.util.sync.getErrorCount = function() {
114   var totalCount = JSErrorCount;
115   for (var appId in background.appWindows) {
116     var contentWindow = background.appWindows[appId].contentWindow;
117     if (contentWindow.JSErrorCount)
118       totalCount += contentWindow.JSErrorCount;
119   }
120   return totalCount;
121 };
122
123 /**
124  * Resizes the window to the specified dimensions.
125  *
126  * @param {Window} contentWindow Window to be tested.
127  * @param {number} width Window width.
128  * @param {number} height Window height.
129  * @return {boolean} True for success.
130  */
131 test.util.sync.resizeWindow = function(contentWindow, width, height) {
132   background.appWindows[contentWindow.appID].resizeTo(width, height);
133   return true;
134 };
135
136 /**
137  * Returns an array with the files currently selected in the file manager.
138  * TODO(hirono): Integrate the method into getFileList method.
139  *
140  * @param {Window} contentWindow Window to be tested.
141  * @return {Array.<string>} Array of selected files.
142  */
143 test.util.sync.getSelectedFiles = function(contentWindow) {
144   var table = contentWindow.document.querySelector('#detail-table');
145   var rows = table.querySelectorAll('li');
146   var selected = [];
147   for (var i = 0; i < rows.length; ++i) {
148     if (rows[i].hasAttribute('selected')) {
149       selected.push(
150           rows[i].querySelector('.filename-label').textContent);
151     }
152   }
153   return selected;
154 };
155
156 /**
157  * Returns an array with the files on the file manager's file list.
158  *
159  * @param {Window} contentWindow Window to be tested.
160  * @return {Array.<Array.<string>>} Array of rows.
161  */
162 test.util.sync.getFileList = function(contentWindow) {
163   var table = contentWindow.document.querySelector('#detail-table');
164   var rows = table.querySelectorAll('li');
165   var fileList = [];
166   for (var j = 0; j < rows.length; ++j) {
167     var row = rows[j];
168     fileList.push([
169       row.querySelector('.filename-label').textContent,
170       row.querySelector('.size').textContent,
171       row.querySelector('.type').textContent,
172       row.querySelector('.date').textContent
173     ]);
174   }
175   return fileList;
176 };
177
178 /**
179  * Queries all elements.
180  *
181  * @param {Window} contentWindow Window to be tested.
182  * @param {string} targetQuery Query to specify the element.
183  * @param {?string} iframeQuery Iframe selector or null if no iframe.
184  * @param {Array.<string>=} opt_styleNames List of CSS property name to be
185  *     obtained.
186  * @return {Array.<{attributes:Object.<string, string>, text:string,
187  *                  styles:Object.<string, string>, hidden:boolean}>} Element
188  *     information that contains contentText, attribute names and
189  *     values, hidden attribute, and style names and values.
190  */
191 test.util.sync.queryAllElements = function(
192     contentWindow, targetQuery, iframeQuery, opt_styleNames) {
193   var doc = test.util.sync.getDocument_(contentWindow, iframeQuery);
194   if (!doc)
195     return [];
196   // The return value of querySelectorAll is not an array.
197   return Array.prototype.map.call(
198       doc.querySelectorAll(targetQuery),
199       function(element) {
200         var attributes = {};
201         for (var i = 0; i < element.attributes.length; i++) {
202           attributes[element.attributes[i].nodeName] =
203               element.attributes[i].nodeValue;
204         }
205         var styles = {};
206         var styleNames = opt_styleNames || [];
207         var computedStyles = contentWindow.getComputedStyle(element);
208         for (var i = 0; i < styleNames.length; i++) {
209           styles[styleNames[i]] = computedStyles[styleNames[i]];
210         }
211         var text = element.textContent;
212         return {
213           attributes: attributes,
214           text: text,
215           styles: styles,
216           // The hidden attribute is not in the element.attributes even if
217           // element.hasAttribute('hidden') is true.
218           hidden: !!element.hidden
219         };
220       });
221 };
222
223 /**
224  * Assigns the text to the input element.
225  * @param {Window} contentWindow Window to be tested.
226  * @param {string} query Query for the input element.
227  * @param {string} text Text to be assigned.
228  */
229 test.util.sync.inputText = function(contentWindow, query, text) {
230   var input = contentWindow.document.querySelector(query);
231   input.value = text;
232 };
233
234 /**
235  * Fakes pressing the down arrow until the given |filename| is selected.
236  *
237  * @param {Window} contentWindow Window to be tested.
238  * @param {string} filename Name of the file to be selected.
239  * @return {boolean} True if file got selected, false otherwise.
240  */
241 test.util.sync.selectFile = function(contentWindow, filename) {
242   var rows = contentWindow.document.querySelectorAll('#detail-table li');
243   test.util.sync.fakeKeyDown(contentWindow, '#file-list', 'Home', false);
244   for (var index = 0; index < rows.length; ++index) {
245     var selection = test.util.sync.getSelectedFiles(contentWindow);
246     if (selection.length === 1 && selection[0] === filename)
247       return true;
248     test.util.sync.fakeKeyDown(contentWindow, '#file-list', 'Down', false);
249   }
250   console.error('Failed to select file "' + filename + '"');
251   return false;
252 };
253
254 /**
255  * Open the file by selectFile and fakeMouseDoubleClick.
256  *
257  * @param {Window} contentWindow Window to be tested.
258  * @param {string} filename Name of the file to be opened.
259  * @return {boolean} True if file got selected and a double click message is
260  *     sent, false otherwise.
261  */
262 test.util.sync.openFile = function(contentWindow, filename) {
263   var query = '#file-list li.table-row[selected] .filename-label span';
264   return test.util.sync.selectFile(contentWindow, filename) &&
265          test.util.sync.fakeMouseDoubleClick(contentWindow, query);
266 };
267
268 /**
269  * Selects a volume specified by its icon name
270  *
271  * @param {Window} contentWindow Window to be tested.
272  * @param {string} iconName Name of the volume icon.
273  * @param {function(boolean)} callback Callback function to notify the caller
274  *     whether the target is found and mousedown and click events are sent.
275  */
276 test.util.async.selectVolume = function(contentWindow, iconName, callback) {
277   var query = '[volume-type-icon=' + iconName + ']';
278   var driveQuery = '[volume-type-icon=drive]';
279   var isDriveSubVolume = iconName == 'drive_recent' ||
280                          iconName == 'drive_shared_with_me' ||
281                          iconName == 'drive_offline';
282   var preSelection = false;
283   var steps = {
284     checkQuery: function() {
285       if (contentWindow.document.querySelector(query)) {
286         steps.sendEvents();
287         return;
288       }
289       // If the target volume is sub-volume of drive, we must click 'drive'
290       // before clicking the sub-item.
291       if (!preSelection) {
292         if (!isDriveSubVolume) {
293           callback(false);
294           return;
295         }
296         if (!(test.util.sync.fakeMouseDown(contentWindow, driveQuery) &&
297               test.util.sync.fakeMouseClick(contentWindow, driveQuery))) {
298           callback(false);
299           return;
300         }
301         preSelection = true;
302       }
303       setTimeout(steps.checkQuery, 50);
304     },
305     sendEvents: function() {
306       // To change the selected volume, we have to send both events 'mousedown'
307       // and 'click' to the navigation list.
308       callback(test.util.sync.fakeMouseDown(contentWindow, query) &&
309                test.util.sync.fakeMouseClick(contentWindow, query));
310     }
311   };
312   steps.checkQuery();
313 };
314
315 /**
316  * Executes Javascript code on a webview and returns the result.
317  *
318  * @param {Window} contentWindow Window to be tested.
319  * @param {string} webViewQuery Selector for the web view.
320  * @param {string} code Javascript code to be executed within the web view.
321  * @param {function(*)} callback Callback function with results returned by the
322  *     script.
323  */
324 test.util.async.executeScriptInWebView = function(
325     contentWindow, webViewQuery, code, callback) {
326   var webView = contentWindow.document.querySelector(webViewQuery);
327   webView.executeScript({code: code}, callback);
328 };
329
330 /**
331  * Sends an event to the element specified by |targetQuery|.
332  *
333  * @param {Window} contentWindow Window to be tested.
334  * @param {string} targetQuery Query to specify the element.
335  * @param {Event} event Event to be sent.
336  * @param {string=} opt_iframeQuery Optional iframe selector.
337  * @return {boolean} True if the event is sent to the target, false otherwise.
338  */
339 test.util.sync.sendEvent = function(
340     contentWindow, targetQuery, event, opt_iframeQuery) {
341   var doc = test.util.sync.getDocument_(contentWindow, opt_iframeQuery);
342   if (doc) {
343     var target = doc.querySelector(targetQuery);
344     if (target) {
345       target.dispatchEvent(event);
346       return true;
347     }
348   }
349   console.error('Target element for ' + targetQuery + ' not found.');
350   return false;
351 };
352
353 /**
354  * Sends an fake event having the specified type to the target query.
355  *
356  * @param {Window} contentWindow Window to be tested.
357  * @param {string} targetQuery Query to specify the element.
358  * @param {string} eventType Type of event.
359  * @param {Object=} opt_additionalProperties Object contaning additional
360  *     properties.
361  * @return {boolean} True if the event is sent to the target, false otherwise.
362  */
363 test.util.sync.fakeEvent = function(contentWindow,
364                                     targetQuery,
365                                     eventType,
366                                     opt_additionalProperties) {
367   var event = new Event(eventType, opt_additionalProperties || {});
368   if (opt_additionalProperties) {
369     for (var name in opt_additionalProperties) {
370       event[name] = opt_additionalProperties[name];
371     }
372   }
373   return test.util.sync.sendEvent(contentWindow, targetQuery, event);
374 };
375
376 /**
377  * Sends a fake key event to the element specified by |targetQuery| with the
378  * given |keyIdentifier| and optional |ctrl| modifier to the file manager.
379  *
380  * @param {Window} contentWindow Window to be tested.
381  * @param {string} targetQuery Query to specify the element.
382  * @param {string} keyIdentifier Identifier of the emulated key.
383  * @param {boolean} ctrl Whether CTRL should be pressed, or not.
384  * @param {string=} opt_iframeQuery Optional iframe selector.
385  * @return {boolean} True if the event is sent to the target, false otherwise.
386  */
387 test.util.sync.fakeKeyDown = function(
388     contentWindow, targetQuery, keyIdentifier, ctrl, opt_iframeQuery) {
389   var event = new KeyboardEvent(
390       'keydown',
391       { bubbles: true, keyIdentifier: keyIdentifier, ctrlKey: ctrl });
392   return test.util.sync.sendEvent(
393       contentWindow, targetQuery, event, opt_iframeQuery);
394 };
395
396 /**
397  * Simulates a fake mouse click (left button, single click) on the element
398  * specified by |targetQuery|. If the element has the click method, just calls
399  * it. Otherwise, this sends 'mouseover', 'mousedown', 'mouseup' and 'click'
400  * events in turns.
401  *
402  * @param {Window} contentWindow Window to be tested.
403  * @param {string} targetQuery Query to specify the element.
404  * @param {string=} opt_iframeQuery Optional iframe selector.
405  * @return {boolean} True if the all events are sent to the target, false
406  *     otherwise.
407  */
408 test.util.sync.fakeMouseClick = function(
409     contentWindow, targetQuery, opt_iframeQuery) {
410   var mouseOverEvent = new MouseEvent('mouseover', {bubbles: true, detail: 1});
411   var resultMouseOver = test.util.sync.sendEvent(
412       contentWindow, targetQuery, mouseOverEvent, opt_iframeQuery);
413   var mouseDownEvent = new MouseEvent('mousedown', {bubbles: true, detail: 1});
414   var resultMouseDown = test.util.sync.sendEvent(
415       contentWindow, targetQuery, mouseDownEvent, opt_iframeQuery);
416   var mouseUpEvent = new MouseEvent('mouseup', {bubbles: true, detail: 1});
417   var resultMouseUp = test.util.sync.sendEvent(
418       contentWindow, targetQuery, mouseUpEvent, opt_iframeQuery);
419   var clickEvent = new MouseEvent('click', {bubbles: true, detail: 1});
420   var resultClick = test.util.sync.sendEvent(
421       contentWindow, targetQuery, clickEvent, opt_iframeQuery);
422   return resultMouseOver && resultMouseDown && resultMouseUp && resultClick;
423 };
424
425 /**
426  * Simulates a fake mouse click (right button, single click) on the element
427  * specified by |targetQuery|.
428  *
429  * @param {Window} contentWindow Window to be tested.
430  * @param {string} targetQuery Query to specify the element.
431  * @param {string=} opt_iframeQuery Optional iframe selector.
432  * @return {boolean} True if the event is sent to the target, false
433  *     otherwise.
434  */
435 test.util.sync.fakeMouseRightClick = function(
436     contentWindow, targetQuery, opt_iframeQuery) {
437   var contextMenuEvent = new MouseEvent('contextmenu', {bubbles: true});
438   var result = test.util.sync.sendEvent(
439       contentWindow, targetQuery, contextMenuEvent, opt_iframeQuery);
440   return result;
441 };
442
443 /**
444  * Simulates a fake double click event (left button) to the element specified by
445  * |targetQuery|.
446  *
447  * @param {Window} contentWindow Window to be tested.
448  * @param {string} targetQuery Query to specify the element.
449  * @param {string=} opt_iframeQuery Optional iframe selector.
450  * @return {boolean} True if the event is sent to the target, false otherwise.
451  */
452 test.util.sync.fakeMouseDoubleClick = function(
453     contentWindow, targetQuery, opt_iframeQuery) {
454   // Double click is always preceded with a single click.
455   if (!test.util.sync.fakeMouseClick(
456       contentWindow, targetQuery, opt_iframeQuery)) {
457     return false;
458   }
459
460   // Send the second click event, but with detail equal to 2 (number of clicks)
461   // in a row.
462   var event = new MouseEvent('click', { bubbles: true, detail: 2 });
463   if (!test.util.sync.sendEvent(
464       contentWindow, targetQuery, event, opt_iframeQuery)) {
465     return false;
466   }
467
468   // Send the double click event.
469   var event = new MouseEvent('dblclick', { bubbles: true });
470   if (!test.util.sync.sendEvent(
471       contentWindow, targetQuery, event, opt_iframeQuery)) {
472     return false;
473   }
474
475   return true;
476 };
477
478 /**
479  * Sends a fake mouse down event to the element specified by |targetQuery|.
480  *
481  * @param {Window} contentWindow Window to be tested.
482  * @param {string} targetQuery Query to specify the element.
483  * @param {string=} opt_iframeQuery Optional iframe selector.
484  * @return {boolean} True if the event is sent to the target, false otherwise.
485  */
486 test.util.sync.fakeMouseDown = function(
487     contentWindow, targetQuery, opt_iframeQuery) {
488   var event = new MouseEvent('mousedown', { bubbles: true });
489   return test.util.sync.sendEvent(
490       contentWindow, targetQuery, event, opt_iframeQuery);
491 };
492
493 /**
494  * Sends a fake mouse up event to the element specified by |targetQuery|.
495  *
496  * @param {Window} contentWindow Window to be tested.
497  * @param {string} targetQuery Query to specify the element.
498  * @param {string=} opt_iframeQuery Optional iframe selector.
499  * @return {boolean} True if the event is sent to the target, false otherwise.
500  */
501 test.util.sync.fakeMouseUp = function(
502     contentWindow, targetQuery, opt_iframeQuery) {
503   var event = new MouseEvent('mouseup', { bubbles: true });
504   return test.util.sync.sendEvent(
505       contentWindow, targetQuery, event, opt_iframeQuery);
506 };
507
508 /**
509  * Selects |filename| and fakes pressing Ctrl+C, Ctrl+V (copy, paste).
510  *
511  * @param {Window} contentWindow Window to be tested.
512  * @param {string} filename Name of the file to be copied.
513  * @return {boolean} True if copying got simulated successfully. It does not
514  *     say if the file got copied, or not.
515  */
516 test.util.sync.copyFile = function(contentWindow, filename) {
517   if (!test.util.sync.selectFile(contentWindow, filename))
518     return false;
519   // Ctrl+C and Ctrl+V
520   test.util.sync.fakeKeyDown(contentWindow, '#file-list', 'U+0043', true);
521   test.util.sync.fakeKeyDown(contentWindow, '#file-list', 'U+0056', true);
522   return true;
523 };
524
525 /**
526  * Selects |filename| and fakes pressing the Delete key.
527  *
528  * @param {Window} contentWindow Window to be tested.
529  * @param {string} filename Name of the file to be deleted.
530  * @return {boolean} True if deleting got simulated successfully. It does not
531  *     say if the file got deleted, or not.
532  */
533 test.util.sync.deleteFile = function(contentWindow, filename) {
534   if (!test.util.sync.selectFile(contentWindow, filename))
535     return false;
536   // Delete
537   test.util.sync.fakeKeyDown(contentWindow, '#file-list', 'U+007F', false);
538   return true;
539 };
540
541 /**
542  * Execute a command on the document in the specified window.
543  *
544  * @param {Window} contentWindow Window to be tested.
545  * @param {string} command Command name.
546  * @return {boolean} True if the command is executed successfully.
547  */
548 test.util.sync.execCommand = function(contentWindow, command) {
549   return contentWindow.document.execCommand(command);
550 };
551
552 /**
553  * Override the installWebstoreItem method in private api for test.
554  *
555  * @param {Window} contentWindow Window to be tested.
556  * @param {string} expectedItemId Item ID to be called this method with.
557  * @param {?string} intendedError Error message to be returned when the item id
558  *     matches. 'null' represents no error.
559  * @return {boolean} Always return true.
560  */
561 test.util.sync.overrideInstallWebstoreItemApi =
562     function(contentWindow, expectedItemId, intendedError) {
563   var setLastError = function(message) {
564     contentWindow.chrome.runtime.lastError =
565         message ? {message: message} : null;
566   };
567
568   var installWebstoreItem = function(itemId, silentInstallation, callback) {
569     setTimeout(function() {
570       if (itemId !== expectedItemId) {
571         setLastError('Invalid Chrome Web Store item ID');
572         callback();
573         return;
574       }
575
576       setLastError(intendedError);
577       callback();
578     });
579   };
580
581   test.util.executedTasks_ = [];
582   contentWindow.chrome.fileBrowserPrivate.installWebstoreItem =
583       installWebstoreItem;
584   return true;
585 };
586
587 /**
588  * Override the task-related methods in private api for test.
589  *
590  * @param {Window} contentWindow Window to be tested.
591  * @param {Array.<Object>} taskList List of tasks to be returned in
592  *     fileBrowserPrivate.getFileTasks().
593  * @return {boolean} Always return true.
594  */
595 test.util.sync.overrideTasks = function(contentWindow, taskList) {
596   var getFileTasks = function(urls, onTasks) {
597     // Call onTask asynchronously (same with original getFileTasks).
598     setTimeout(function() {
599       onTasks(taskList);
600     });
601   };
602
603   var executeTask = function(taskId, url) {
604     test.util.executedTasks_.push(taskId);
605   };
606
607   var setDefaultTask = function(taskId) {
608     for (var i = 0; i < taskList.length; i++) {
609       taskList[i].isDefault = taskList[i].taskId === taskId;
610     }
611   };
612
613   test.util.executedTasks_ = [];
614   contentWindow.chrome.fileBrowserPrivate.getFileTasks = getFileTasks;
615   contentWindow.chrome.fileBrowserPrivate.executeTask = executeTask;
616   contentWindow.chrome.fileBrowserPrivate.setDefaultTask = setDefaultTask;
617   return true;
618 };
619
620 /**
621  * Obtains the list of executed tasks.
622  * @param {Window} contentWindow Window to be tested.
623  * @return {Array.<string>} List of executed task ID.
624  */
625 test.util.sync.getExecutedTasks = function(contentWindow) {
626   if (!test.util.executedTasks_) {
627     console.error('Please call overrideTasks() first.');
628     return null;
629   }
630   return test.util.executedTasks_;
631 };
632
633 /**
634  * Invoke chrome.fileBrowserPrivate.visitDesktop(profileId) to cause window
635  * teleportation.
636  *
637  * @param {Window} contentWindow Window to be tested.
638  * @param {string} profileId Destination profile's ID.
639  * @return {boolean} Always return true.
640  */
641 test.util.sync.visitDesktop = function(contentWindow, profileId) {
642   contentWindow.chrome.fileBrowserPrivate.visitDesktop(profileId);
643   return true;
644 };
645
646 /**
647  * Runs the 'Move to profileId' menu.
648  *
649  * @param {Window} contentWindow Window to be tested.
650  * @param {string} profileId Destination profile's ID.
651  * @return {boolean} True if the menu is found and run.
652  */
653 test.util.sync.runVisitDesktopMenu = function(contentWindow, profileId) {
654   var list = contentWindow.document.querySelectorAll('.visit-desktop');
655   for (var i = 0; i < list.length; ++i) {
656     if (list[i].label.indexOf(profileId) != -1) {
657       var activateEvent = contentWindow.document.createEvent('Event');
658       activateEvent.initEvent('activate');
659       list[i].dispatchEvent(activateEvent);
660       return true;
661     }
662   }
663   return false;
664 };
665
666 /**
667  * Registers message listener, which runs test utility functions.
668  */
669 test.util.registerRemoteTestUtils = function() {
670   // Register the message listener.
671   var onMessage = chrome.runtime ? chrome.runtime.onMessageExternal :
672       chrome.extension.onMessageExternal;
673   // Return true for asynchronous functions and false for synchronous.
674   onMessage.addListener(function(request, sender, sendResponse) {
675     // Check the sender.
676     if (sender.id != test.util.TESTING_EXTENSION_ID) {
677       console.error('The testing extension must be white-listed.');
678       return false;
679     }
680     // Set a global flag that we are in tests, so other components are aware
681     // of it.
682     window.IN_TEST = true;
683     // Check the function name.
684     if (!request.func || request.func[request.func.length - 1] == '_') {
685       request.func = '';
686     }
687     // Prepare arguments.
688     var args = request.args.slice();  // shallow copy
689     if (request.appId) {
690       if (background.appWindows[request.appId]) {
691         args.unshift(background.appWindows[request.appId].contentWindow);
692       } else if (background.dialogs[request.appId]) {
693         args.unshift(background.dialogs[request.appId]);
694       } else {
695         console.error('Specified window not found: ' + request.appId);
696         return false;
697       }
698     }
699     // Call the test utility function and respond the result.
700     if (test.util.async[request.func]) {
701       args[test.util.async[request.func].length - 1] = function() {
702         console.debug('Received the result of ' + request.func);
703         sendResponse.apply(null, arguments);
704       };
705       console.debug('Waiting for the result of ' + request.func);
706       test.util.async[request.func].apply(null, args);
707       return true;
708     } else if (test.util.sync[request.func]) {
709       sendResponse(test.util.sync[request.func].apply(null, args));
710       return false;
711     } else {
712       console.error('Invalid function name.');
713       return false;
714     }
715   });
716 };
717
718 // Register the test utils.
719 test.util.registerRemoteTestUtils();