| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| |
| /// Helper program for killing and resetting all Safari settings to a known |
| /// state that works well for testing dart2js output in Safari. |
| /// |
| /// Warning: this will delete all your Safari settings and bookmarks. |
| library testing.reset_safari; |
| |
| import 'dart:async' show |
| Future, |
| Timer; |
| |
| import 'dart:io' show |
| Directory, |
| File, |
| Platform, |
| Process, |
| ProcessResult; |
| |
| const String defaultSafariBundleLocation = "/Applications/Safari.app/"; |
| |
| const String relativeSafariLocation = "Contents/MacOS/Safari"; |
| |
| const String lsofLocation = "/usr/sbin/lsof"; |
| |
| const String killLocation = "/bin/kill"; |
| |
| const String pkillLocation = "/usr/bin/pkill"; |
| |
| const String safari = "com.apple.Safari"; |
| |
| const String defaultsLocation = "/usr/bin/defaults"; |
| |
| final List<String> safariSettings = <String>[ |
| "Library/Caches/$safari", |
| "Library/Safari", |
| "Library/Saved Application State/$safari.savedState", |
| "Library/Caches/Metadata/Safari", |
| "Library/Preferences/$safari.plist", |
| ]; |
| |
| const Duration defaultPollDelay = const Duration(milliseconds: 1); |
| |
| final String cpgi = "$safari.ContentPageGroupIdentifier"; |
| |
| final String knownSafariPreference = ''' |
| { |
| DefaultBrowserPromptingState2 = 2; |
| StartPageViewControllerMode = 0; |
| TestDriveOriginBrowser = 1; |
| TestDriveUserDecision = 2; |
| TestDriveState = 3; |
| AlwaysRestoreSessionAtLaunch = 0; |
| NewTabBehavior = 1; |
| NewWindowBehavior = 1; |
| LastSafariVersionWithWelcomePage = "9.0"; |
| OpenNewTabsInFront = 0; |
| TabCreationPolicy = 0; |
| |
| IncludeDevelopMenu = 1; |
| WebKitDeveloperExtrasEnabledPreferenceKey = 1; |
| "$cpgi.WebKit2DeveloperExtrasEnabled" = 1; |
| |
| AutoFillCreditCardData = 0; |
| AutoFillMiscellaneousForms = 0; |
| AutoFillPasswords = 0; |
| |
| SuppressSearchSuggestions = 1; |
| |
| PreloadTopHit = 0; |
| ShowFavoritesUnderSmartSearchField = 0; |
| WebsiteSpecificSearchEnabled = 0; |
| |
| WarnAboutFraudulentWebsites = 0; |
| |
| |
| WebKitJavaScriptEnabled = 1; |
| "$cpgi.WebKit2JavaScriptEnabled" = 1; |
| |
| WebKitJavaScriptCanOpenWindowsAutomatically = 1; |
| "$cpgi.WebKit2JavaScriptCanOpenWindowsAutomatically" = 1; |
| |
| "$cpgi.WebKit2WebGLEnabled" = 1; |
| WebGLDefaultLoadPolicy = WebGLPolicyAllowNoSecurityRestrictions; |
| |
| "$cpgi.WebKit2PluginsEnabled" = 0; |
| |
| BlockStoragePolicy = 1; |
| WebKitStorageBlockingPolicy = 0; |
| "$cpgi.WebKit2StorageBlockingPolicy" = 0; |
| |
| |
| SafariGeolocationPermissionPolicy = 0; |
| |
| CanPromptForPushNotifications = 0; |
| |
| InstallExtensionUpdatesAutomatically = 0; |
| |
| ShowFullURLInSmartSearchField = 1; |
| |
| "$cpgi.WebKit2PlugInSnapshottingEnabled" = 0; |
| } |
| '''; |
| |
| Future<Null> get pollDelay => new Future.delayed(defaultPollDelay); |
| |
| String signalArgument( |
| String defaultSignal, |
| {bool force: false, |
| bool testOnly: false}) { |
| if (force && testOnly) { |
| throw new ArgumentError("[force] and [testOnly] can't both be true."); |
| } |
| if (force) return "-KILL"; |
| if (testOnly) return "-0"; |
| return defaultSignal; |
| } |
| |
| Future<int> kill( |
| List<String> pids, |
| {bool force: false, |
| bool testOnly: false}) async { |
| List<String> arguments = |
| <String>[signalArgument("-TERM", force: force, testOnly: testOnly)] |
| ..addAll(pids); |
| ProcessResult result = await Process.run(killLocation, arguments); |
| return result.exitCode; |
| } |
| |
| Future<int> pkill( |
| String pattern, |
| {bool force: false, |
| bool testOnly: false}) async { |
| List<String> arguments = <String>[ |
| signalArgument("-HUP", force: force, testOnly: testOnly), |
| pattern]; |
| ProcessResult result = await Process.run(pkillLocation, arguments); |
| return result.exitCode; |
| } |
| |
| Uri validatedBundleName(Uri bundle) { |
| if (bundle == null) return Uri.base.resolve(defaultSafariBundleLocation); |
| if (!bundle.path.endsWith("/")) { |
| throw new ArgumentError( |
| "Bundle ('$bundle') must end with a slash ('/')."); |
| } |
| return bundle; |
| } |
| |
| Future<Null> killSafari({Uri bundle}) async { |
| bundle = validatedBundleName(bundle); |
| Uri safariBinary = bundle.resolve(relativeSafariLocation); |
| ProcessResult result = await Process.run( |
| lsofLocation, ["-t", safariBinary.toFilePath()]); |
| if (result.exitCode == 0) { |
| String stdout = result.stdout; |
| List<String> pids = new List<String>.from( |
| stdout.split("\n").where((String line) => !line.isEmpty)); |
| Timer timer = new Timer(const Duration(seconds: 10), () { |
| print("Kill -9 Safari $pids"); |
| kill(pids, force: true); |
| }); |
| int exitCode = await kill(pids); |
| while (exitCode == 0) { |
| await pollDelay; |
| print("Polling Safari $pids"); |
| exitCode = await kill(pids, testOnly: true); |
| } |
| timer.cancel(); |
| } |
| Timer timer = new Timer(const Duration(seconds: 10), () { |
| print("Kill -9 $safari"); |
| pkill(safari, force: true); |
| }); |
| int exitCode = await pkill(safari); |
| while (exitCode == 0) { |
| await pollDelay; |
| print("Polling $safari"); |
| exitCode = await pkill(safari, testOnly: true); |
| } |
| timer.cancel(); |
| } |
| |
| Future<Null> deleteIfExists(Uri uri) async { |
| Directory directory = new Directory.fromUri(uri); |
| if (await directory.exists()) { |
| print("Deleting directory '$uri'."); |
| await directory.delete(recursive: true); |
| } else { |
| File file = new File.fromUri(uri); |
| if (await file.exists()) { |
| print("Deleting file '$uri'."); |
| await file.delete(); |
| } else { |
| print("File '$uri' not found."); |
| } |
| } |
| } |
| |
| Future<Null> resetSafariSettings() async { |
| String home = Platform.environment["HOME"]; |
| if (!home.endsWith("/")) { |
| home = "$home/"; |
| } |
| Uri homeDirectory = Uri.base.resolve(home); |
| for (String setting in safariSettings) { |
| await deleteIfExists(homeDirectory.resolve(setting)); |
| } |
| ProcessResult result = await Process.run( |
| defaultsLocation, <String>["write", safari, knownSafariPreference]); |
| if (result.exitCode != 0) { |
| throw "Unable to reset Safari settings: ${result.stdout}${result.stderr}"; |
| } |
| } |
| |
| Future<Null> killAndResetSafari({Uri bundle}) async { |
| bundle = validatedBundleName(bundle); |
| await killSafari(bundle: bundle); |
| await resetSafariSettings(); |
| } |
| |
| Future<Null> main() async { |
| await killAndResetSafari(); |
| } |