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.fileManagerPrivate.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 * fileManagerPrivate.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.fileManagerPrivate.getFileTasks = getFileTasks; 615 contentWindow.chrome.fileManagerPrivate.executeTask = executeTask; 616 contentWindow.chrome.fileManagerPrivate.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.fileManagerPrivate.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.fileManagerPrivate.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(); 720