| Index: tools/testing/dart/browser_controller.dart | 
| diff --git a/tools/testing/dart/browser_controller.dart b/tools/testing/dart/browser_controller.dart | 
| index 7094120661ad9d45ee880021dce68a5be28e1d92..27765697ea3b74db45fe86f46381cd51199c2df6 100644 | 
| --- a/tools/testing/dart/browser_controller.dart | 
| +++ b/tools/testing/dart/browser_controller.dart | 
| @@ -14,9 +14,6 @@ import 'http_server.dart'; | 
| import 'path.dart'; | 
| import 'utils.dart'; | 
|  | 
| -import 'reset_safari.dart' show | 
| -    killAndResetSafari; | 
| - | 
| class BrowserOutput { | 
| final StringBuffer stdout = new StringBuffer(); | 
| final StringBuffer stderr = new StringBuffer(); | 
| @@ -56,10 +53,10 @@ abstract class Browser { | 
| String id; | 
|  | 
| /** | 
| -   * Reset the browser to a known configuration on start-up. | 
| +   * Delete the browser specific caches on startup. | 
| * Browser specific implementations are free to ignore this. | 
| */ | 
| -  static bool resetBrowserConfiguration = false; | 
| +  static bool deleteCache = false; | 
|  | 
| /** Print everything (stdout, stderr, usageLog) whenever we add to it */ | 
| bool debugPrint = false; | 
| @@ -108,15 +105,6 @@ abstract class Browser { | 
| 'ie10' | 
| ]; | 
|  | 
| -  /// If [browserName] doesn't support Window.open, we use iframes instead. | 
| -  static bool requiresIframe(String browserName) { | 
| -    return !BROWSERS_WITH_WINDOW_SUPPORT.contains(browserName); | 
| -  } | 
| - | 
| -  static bool requiresFocus(String browserName) { | 
| -    return browserName == "safari"; | 
| -  } | 
| - | 
| // TODO(kustermann): add standard support for chrome on android | 
| static bool supportedBrowser(String name) { | 
| return SUPPORTED_BROWSERS.contains(name); | 
| @@ -281,13 +269,6 @@ abstract class Browser { | 
|  | 
| /** Starts the browser loading the given url */ | 
| Future<bool> start(String url); | 
| - | 
| -  /// Called when the driver page is requested, that is, when the browser first | 
| -  /// contacts the test server. At this time, it's safe to assume that the | 
| -  /// browser process has started and opened its first window. | 
| -  /// | 
| -  /// This is used by [Safari] to ensure the browser window has focus. | 
| -  Future<Null> onDriverPageRequested() => new Future<Null>.value(); | 
| } | 
|  | 
| class Safari extends Browser { | 
| @@ -297,48 +278,62 @@ class Safari extends Browser { | 
| static const String versionFile = | 
| "/Applications/Safari.app/Contents/version.plist"; | 
|  | 
| -  static const String safariBundleLocation = "/Applications/Safari.app/"; | 
| +  /** | 
| +   * Directories where safari stores state. We delete these if the deleteCache | 
| +   * is set | 
| +   */ | 
| +  static const List<String> CACHE_DIRECTORIES = const [ | 
| +    "Library/Caches/com.apple.Safari", | 
| +    "Library/Safari", | 
| +    "Library/Saved Application State/com.apple.Safari.savedState", | 
| +    "Library/Caches/Metadata/Safari" | 
| +  ]; | 
|  | 
| -  // Clears the cache if the static resetBrowserConfiguration flag is set. | 
| -  // Returns false if the command to actually clear the cache did not complete. | 
| -  Future<bool> resetConfiguration() async { | 
| -    if (!Browser.resetBrowserConfiguration) return true; | 
| +  Future<bool> allowPopUps() { | 
| +    var command = "defaults"; | 
| +    var args = [ | 
| +      "write", | 
| +      "com.apple.safari", | 
| +      "com.apple.Safari.ContentPageGroupIdentifier." | 
| +          "WebKit2JavaScriptCanOpenWindowsAutomatically", | 
| +      "1" | 
| +    ]; | 
| +    return Process.run(command, args).then((result) { | 
| +      if (result.exitCode != 0) { | 
| +        _logEvent("Could not disable pop-up blocking for safari"); | 
| +        return false; | 
| +      } | 
| +      return true; | 
| +    }); | 
| +  } | 
|  | 
| -    Completer completer = new Completer(); | 
| -    handleUncaughtError(error, StackTrace stackTrace) { | 
| -      if (!completer.isCompleted) { | 
| -        completer.completeError(error, stackTrace); | 
| +  Future<bool> deleteIfExists(Iterator<String> paths) { | 
| +    if (!paths.moveNext()) return new Future.value(true); | 
| +    Directory directory = new Directory(paths.current); | 
| +    return directory.exists().then((exists) { | 
| +      if (exists) { | 
| +        _logEvent("Deleting ${paths.current}"); | 
| +        return directory | 
| +            .delete(recursive: true) | 
| +            .then((_) => deleteIfExists(paths)) | 
| +            .catchError((error) { | 
| +          _logEvent("Failure trying to delete ${paths.current}: $error"); | 
| +          return false; | 
| +        }); | 
| } else { | 
| -        throw new AsyncError(error, stackTrace); | 
| +        _logEvent("${paths.current} is not present"); | 
| +        return deleteIfExists(paths); | 
| } | 
| -    } | 
| -    Zone parent = Zone.current; | 
| -    ZoneSpecification specification = new ZoneSpecification( | 
| -        print: (Zone self, ZoneDelegate delegate, Zone zone, String line) { | 
| -          delegate.run(parent, () { | 
| -            _logEvent(line); | 
| -          }); | 
| -        }); | 
| -    Future zoneWrapper() { | 
| -      Uri safariUri = Uri.base.resolve(safariBundleLocation); | 
| -      return new Future(() => killAndResetSafari(bundle: safariUri)) | 
| -          .then(completer.complete); | 
| -    } | 
| - | 
| -    // We run killAndResetSafari in a Zone as opposed to running an external | 
| -    // process. The Zone allows us to collect its output, and protect the rest | 
| -    // of the test infrastructure against errors in it. | 
| -    runZoned( | 
| -        zoneWrapper, zoneSpecification: specification, | 
| -        onError: handleUncaughtError); | 
| +    }); | 
| +  } | 
|  | 
| -    try { | 
| -      await completer.future; | 
| -      return true; | 
| -    } catch (error, st) { | 
| -      _logEvent("Unable to reset Safari: $error$st"); | 
| -      return false; | 
| -    } | 
| +  // Clears the cache if the static deleteCache flag is set. | 
| +  // Returns false if the command to actually clear the cache did not complete. | 
| +  Future<bool> clearCache() { | 
| +    if (!Browser.deleteCache) return new Future.value(true); | 
| +    var home = Platform.environment['HOME']; | 
| +    Iterator iterator = CACHE_DIRECTORIES.map((s) => "$home/$s").iterator; | 
| +    return deleteIfExists(iterator); | 
| } | 
|  | 
| Future<String> getVersion() { | 
| @@ -374,58 +369,42 @@ class Safari extends Browser { | 
| }); | 
| } | 
|  | 
| -  Future<Null> _createLaunchHTML(var path, var url) async { | 
| +  void _createLaunchHTML(var path, var url) { | 
| var file = new File("${path}/launch.html"); | 
| -    var randomFile = await file.open(mode: FileMode.WRITE); | 
| +    var randomFile = file.openSync(mode: FileMode.WRITE); | 
| var content = '<script language="JavaScript">location = "$url"</script>'; | 
| -    await randomFile.writeString(content); | 
| -    await randomFile.close(); | 
| +    randomFile.writeStringSync(content); | 
| +    randomFile.close(); | 
| } | 
|  | 
| -  Future<bool> start(String url) async { | 
| +  Future<bool> start(String url) { | 
| _logEvent("Starting Safari browser on: $url"); | 
| -    if (!await resetConfiguration()) { | 
| -      _logEvent("Could not clear cache"); | 
| -      return false; | 
| -    } | 
| -    String version; | 
| -    try { | 
| -      version = await getVersion(); | 
| -    } catch (error) { | 
| -      _logEvent("Running $_binary --version failed with $error"); | 
| -      return false; | 
| -    } | 
| -    _logEvent("Got version: $version"); | 
| -    Directory userDir; | 
| -    try { | 
| -      userDir = await Directory.systemTemp.createTemp(); | 
| -    } catch (error) { | 
| -      _logEvent("Error creating temporary directory: $error"); | 
| -      return false; | 
| -    } | 
| -    _cleanup = () { | 
| -      userDir.deleteSync(recursive: true); | 
| -    }; | 
| -    try { | 
| -      await _createLaunchHTML(userDir.path, url); | 
| -    } catch (error) { | 
| -      _logEvent("Error creating launch HTML: $error"); | 
| -      return false; | 
| -    } | 
| -    var args = [ | 
| -        "-d", "-i", "-m", "-s", "-u", _binary, | 
| -        "${userDir.path}/launch.html"]; | 
| -    try { | 
| -      return startBrowserProcess("/usr/bin/caffeinate", args); | 
| -    } catch (error) { | 
| -      _logEvent("Error starting browser process: $error"); | 
| -      return false; | 
| -    } | 
| -  } | 
| - | 
| -  Future<Null> onDriverPageRequested() async { | 
| -    await Process.run("/usr/bin/osascript", | 
| -        ['-e', 'tell application "Safari" to activate']); | 
| +    return allowPopUps().then((success) { | 
| +      if (!success) { | 
| +        return false; | 
| +      } | 
| +      return clearCache().then((cleared) { | 
| +        if (!cleared) { | 
| +          _logEvent("Could not clear cache"); | 
| +          return false; | 
| +        } | 
| +        // Get the version and log that. | 
| +        return getVersion().then((version) { | 
| +          _logEvent("Got version: $version"); | 
| +          return Directory.systemTemp.createTemp().then((userDir) { | 
| +            _cleanup = () { | 
| +              userDir.deleteSync(recursive: true); | 
| +            }; | 
| +            _createLaunchHTML(userDir.path, url); | 
| +            var args = ["${userDir.path}/launch.html"]; | 
| +            return startBrowserProcess(_binary, args); | 
| +          }); | 
| +        }).catchError((error) { | 
| +          _logEvent("Running $_binary --version failed with $error"); | 
| +          return false; | 
| +        }); | 
| +      }); | 
| +    }); | 
| } | 
|  | 
| String toString() => "Safari"; | 
| @@ -498,16 +477,16 @@ class Chrome extends Browser { | 
| class SafariMobileSimulator extends Safari { | 
| /** | 
| * Directories where safari simulator stores state. We delete these if the | 
| -   * resetBrowserConfiguration is set | 
| +   * deleteCache is set | 
| */ | 
| static const List<String> CACHE_DIRECTORIES = const [ | 
| "Library/Application Support/iPhone Simulator/7.1/Applications" | 
| ]; | 
|  | 
| -  // Clears the cache if the static resetBrowserConfiguration flag is set. | 
| +  // Clears the cache if the static deleteCache flag is set. | 
| // Returns false if the command to actually clear the cache did not complete. | 
| -  Future<bool> resetConfiguration() { | 
| -    if (!Browser.resetBrowserConfiguration) return new Future.value(true); | 
| +  Future<bool> clearCache() { | 
| +    if (!Browser.deleteCache) return new Future.value(true); | 
| var home = Platform.environment['HOME']; | 
| Iterator iterator = CACHE_DIRECTORIES.map((s) => "$home/$s").iterator; | 
| return deleteIfExists(iterator); | 
| @@ -515,7 +494,7 @@ class SafariMobileSimulator extends Safari { | 
|  | 
| Future<bool> start(String url) { | 
| _logEvent("Starting safari mobile simulator browser on: $url"); | 
| -    return resetConfiguration().then((success) { | 
| +    return clearCache().then((success) { | 
| if (!success) { | 
| _logEvent("Could not clear cache, exiting"); | 
| return false; | 
| @@ -582,10 +561,9 @@ class IE extends Browser { | 
| }); | 
| } | 
|  | 
| -  // Clears the recovery cache if the static resetBrowserConfiguration flag is | 
| -  // set. | 
| -  Future<bool> resetConfiguration() { | 
| -    if (!Browser.resetBrowserConfiguration) return new Future.value(true); | 
| +  // Clears the recovery cache if the static deleteCache flag is set. | 
| +  Future<bool> clearCache() { | 
| +    if (!Browser.deleteCache) return new Future.value(true); | 
| var localAppData = Platform.environment['LOCALAPPDATA']; | 
|  | 
| Directory dir = new Directory("$localAppData\\Microsoft\\" | 
| @@ -600,7 +578,7 @@ class IE extends Browser { | 
|  | 
| Future<bool> start(String url) { | 
| _logEvent("Starting ie browser on: $url"); | 
| -    return resetConfiguration().then((_) => getVersion()).then((version) { | 
| +    return clearCache().then((_) => getVersion()).then((version) { | 
| _logEvent("Got version: $version"); | 
| return startBrowserProcess(_binary, [url]); | 
| }); | 
| @@ -898,12 +876,12 @@ class BrowserTestRunner { | 
| static const Duration MIN_NONEMPTY_QUEUE_TIME = const Duration(seconds: 1); | 
|  | 
| final Map configuration; | 
| -  final BrowserTestingServer testingServer; | 
| +  BrowserTestingServer testingServer; | 
|  | 
| final String localIp; | 
| -  final String browserName; | 
| -  final int maxNumBrowsers; | 
| -  final bool checkedMode; | 
| +  String browserName; | 
| +  int maxNumBrowsers; | 
| +  bool checkedMode; | 
| int numBrowsers = 0; | 
| // Used to send back logs from the browser (start, stop etc) | 
| Function logger; | 
| @@ -950,23 +928,27 @@ class BrowserTestRunner { | 
| if (_currentStartingBrowserId == id) _currentStartingBrowserId = null; | 
| } | 
|  | 
| +  // If [browserName] doesn't support opening new windows, we use new iframes | 
| +  // instead. | 
| +  bool get useIframe => | 
| +      !Browser.BROWSERS_WITH_WINDOW_SUPPORT.contains(browserName); | 
| + | 
| +  /// The optional testingServer parameter allows callers to pass in | 
| +  /// a testing server with different behavior than the  default | 
| +  /// BrowserTestServer. The url handlers of the testingServer are | 
| +  /// overwritten, so an existing handler can't be shared between instances. | 
| BrowserTestRunner( | 
| -      Map configuration, | 
| -      String localIp, | 
| -      String browserName, | 
| -      this.maxNumBrowsers) | 
| -      : configuration = configuration, | 
| -        localIp = localIp, | 
| -        browserName = (browserName == 'ff') ? 'firefox' : browserName, | 
| -        checkedMode = configuration['checked'], | 
| -        testingServer = new BrowserTestingServer( | 
| -            configuration, localIp, | 
| -            Browser.requiresIframe(browserName), | 
| -            Browser.requiresFocus(browserName)) { | 
| -    testingServer.testRunner = this; | 
| +      this.configuration, this.localIp, this.browserName, this.maxNumBrowsers, | 
| +      {BrowserTestingServer this.testingServer}) { | 
| +    checkedMode = configuration['checked']; | 
| +    if (browserName == 'ff') browserName = 'firefox'; | 
| } | 
|  | 
| Future start() async { | 
| +    if (testingServer == null) { | 
| +      testingServer = | 
| +          new BrowserTestingServer(configuration, localIp, useIframe); | 
| +    } | 
| await testingServer.start(); | 
| testingServer | 
| ..testDoneCallBack = handleResults | 
| @@ -1303,9 +1285,6 @@ class BrowserTestingServer { | 
| ///                                   test | 
|  | 
| final String localIp; | 
| -  final bool useIframe; | 
| -  final bool requiresFocus; | 
| -  BrowserTestRunner testRunner; | 
|  | 
| static const String driverPath = "/driver"; | 
| static const String nextTestPath = "/next_test"; | 
| @@ -1318,14 +1297,14 @@ class BrowserTestingServer { | 
| var testCount = 0; | 
| var errorReportingServer; | 
| bool underTermination = false; | 
| +  bool useIframe = false; | 
|  | 
| Function testDoneCallBack; | 
| Function testStatusUpdateCallBack; | 
| Function testStartedCallBack; | 
| Function nextTestCallBack; | 
|  | 
| -  BrowserTestingServer( | 
| -      this.configuration, this.localIp, this.useIframe, this.requiresFocus); | 
| +  BrowserTestingServer(this.configuration, this.localIp, this.useIframe); | 
|  | 
| Future start() { | 
| var test_driver_error_port = configuration['test_driver_error_port']; | 
| @@ -1387,41 +1366,29 @@ class BrowserTestingServer { | 
| handleStarted(request, browserId(request, startedPath), testId(request)); | 
| }); | 
|  | 
| -    void sendPageHandler(HttpRequest request) { | 
| -      // Do NOT make this method async. We need to call catchError below | 
| -      // synchronously to avoid unhandled asynchronous errors. | 
| -      noCache(request); | 
| -      Future<String> textResponse; | 
| -      if (request.uri.path.startsWith(driverPath)) { | 
| -        textResponse = getDriverPage(browserId(request, driverPath)); | 
| -        request.response.headers.set('Content-Type', 'text/html'); | 
| -      } else if (request.uri.path.startsWith(nextTestPath)) { | 
| -        textResponse = new Future<String>.value( | 
| -            getNextTest(browserId(request, nextTestPath))); | 
| -        request.response.headers.set('Content-Type', 'text/plain'); | 
| -      } else { | 
| -        textResponse = new Future<String>.value(""); | 
| -      } | 
| -      request.response.done.catchError((error) { | 
| -        if (!underTermination) { | 
| -          return textResponse.then((String text) { | 
| -            print("URI ${request.uri}"); | 
| -            print("textResponse $textResponse"); | 
| -            throw "Error returning content to browser: $error"; | 
| +    makeSendPageHandler(String prefix) => (HttpRequest request) { | 
| +          noCache(request); | 
| +          var textResponse = ""; | 
| +          if (prefix == driverPath) { | 
| +            textResponse = getDriverPage(browserId(request, prefix)); | 
| +            request.response.headers.set('Content-Type', 'text/html'); | 
| +          } | 
| +          if (prefix == nextTestPath) { | 
| +            textResponse = getNextTest(browserId(request, prefix)); | 
| +            request.response.headers.set('Content-Type', 'text/plain'); | 
| +          } | 
| +          request.response.write(textResponse); | 
| +          request.listen((_) {}, onDone: request.response.close); | 
| +          request.response.done.catchError((error) { | 
| +            if (!underTermination) { | 
| +              print("URI ${request.uri}"); | 
| +              print("Textresponse $textResponse"); | 
| +              throw "Error returning content to browser: $error"; | 
| +            } | 
| }); | 
| -        } | 
| -      }); | 
| -      textResponse.then((String text) async { | 
| -        request.response.write(text); | 
| -        await request.listen(null).asFuture(); | 
| -        // Ignoring the returned closure as it returns the 'done' future | 
| -        // which alread has catchError installed above. | 
| -        request.response.close(); | 
| -      }); | 
| -    } | 
| - | 
| -    server.addHandler(driverPath, sendPageHandler); | 
| -    server.addHandler(nextTestPath, sendPageHandler); | 
| +        }; | 
| +    server.addHandler(driverPath, makeSendPageHandler(driverPath)); | 
| +    server.addHandler(nextTestPath, makeSendPageHandler(nextTestPath)); | 
| } | 
|  | 
| void handleReport(HttpRequest request, String browserId, var testId, | 
| @@ -1480,34 +1447,33 @@ class BrowserTestingServer { | 
| return "http://$localIp:$port/driver/$browserId"; | 
| } | 
|  | 
| -  Future<String> getDriverPage(String browserId) async { | 
| -    await testRunner.browserStatus[browserId].browser.onDriverPageRequested(); | 
| +  String getDriverPage(String browserId) { | 
| var errorReportingUrl = | 
| "http://$localIp:${errorReportingServer.port}/$browserId"; | 
| String driverContent = """ | 
| <!DOCTYPE html><html> | 
| <head> | 
| -  <title>Driving page</title> | 
| <style> | 
| -.big-notice { | 
| -  background-color: red; | 
| -  color: white; | 
| -  font-weight: bold; | 
| -  font-size: xx-large; | 
| -  text-align: center; | 
| -} | 
| -.controller.box { | 
| -  white-space: nowrap; | 
| -  overflow: scroll; | 
| -  height: 6em; | 
| -} | 
| -body { | 
| -  font-family: sans-serif; | 
| -} | 
| -body div { | 
| -  padding-top: 10px; | 
| -} | 
| +    body { | 
| +      margin: 0; | 
| +    } | 
| +    .box { | 
| +      overflow: hidden; | 
| +      overflow-y: auto; | 
| +      position: absolute; | 
| +      left: 0; | 
| +      right: 0; | 
| +    } | 
| +    .controller.box { | 
| +      height: 75px; | 
| +      top: 0; | 
| +    } | 
| +    .test.box { | 
| +      top: 75px; | 
| +      bottom: 0; | 
| +    } | 
| </style> | 
| +  <title>Driving page</title> | 
| <script type='text/javascript'> | 
| var STATUS_UPDATE_INTERVAL = 10000; | 
|  | 
| @@ -1637,8 +1603,7 @@ body div { | 
| embedded_iframe_div.removeChild(embedded_iframe); | 
| embedded_iframe = document.createElement('iframe'); | 
| embedded_iframe.id = "embedded_iframe"; | 
| -          embedded_iframe.width='800px'; | 
| -          embedded_iframe.height='600px'; | 
| +          embedded_iframe.style="width:100%;height:100%"; | 
| embedded_iframe_div.appendChild(embedded_iframe); | 
| embedded_iframe.src = url; | 
| } else { | 
| @@ -1810,26 +1775,13 @@ body div { | 
| </script> | 
| </head> | 
| <body onload="startTesting()"> | 
| - | 
| -    <div class='big-notice'> | 
| -      Please keep this window in focus at all times. | 
| -    </div> | 
| - | 
| -    <div> | 
| -      Some browsers, Safari, in particular, may pause JavaScript when not | 
| -      visible to conserve power consumption and CPU resources. In addition, | 
| -      some tests of focus events will not work correctly if this window doesn't | 
| -      have focus. It's also advisable to close any other programs that may open | 
| -      modal dialogs, for example, Chrome with Calendar open. | 
| -    </div> | 
| - | 
| <div class="controller box"> | 
| Dart test driver, number of tests: <span id="number"></span><br> | 
| Currently executing: <span id="currently_executing"></span><br> | 
| Unhandled error: <span id="unhandled_error"></span> | 
| </div> | 
| <div id="embedded_iframe_div" class="test box"> | 
| -      <iframe id="embedded_iframe"></iframe> | 
| +      <iframe style="width:100%;height:100%;" id="embedded_iframe"></iframe> | 
| </div> | 
| </body> | 
| </html> | 
|  |