diff options
author | kinuko@chromium.org <kinuko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-13 11:09:57 +0000 |
---|---|---|
committer | kinuko@chromium.org <kinuko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-13 11:09:57 +0000 |
commit | 46f998248881f3271d6b679ebc4b0dd1d2a302f3 (patch) | |
tree | 18cf22890cc797502f14058c285cb0fedccf5630 | |
parent | e91aa9ea67f61df6a0ccd9ddac36330dcd8a2770 (diff) | |
download | chromium_src-46f998248881f3271d6b679ebc4b0dd1d2a302f3.zip chromium_src-46f998248881f3271d6b679ebc4b0dd1d2a302f3.tar.gz chromium_src-46f998248881f3271d6b679ebc4b0dd1d2a302f3.tar.bz2 |
SyncFS: Add {set,get}ConflictResolutionPolicy API
BUG=177159
TEST=browser_tests:SyncFileSystemApiTest.ConflictResolutionPolicyTest
TBR=benwells
Review URL: https://codereview.chromium.org/12422006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@187826 0039d316-1c4b-4281-b951-d872f2087c98
15 files changed, 227 insertions, 13 deletions
diff --git a/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.cc b/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.cc index 01e1f19..fbc6ccf 100644 --- a/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.cc +++ b/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.cc @@ -12,6 +12,7 @@ #include "chrome/browser/extensions/api/sync_file_system/extension_sync_event_observer.h" #include "chrome/browser/extensions/api/sync_file_system/extension_sync_event_observer_factory.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sync_file_system/conflict_resolution_policy.h" #include "chrome/browser/sync_file_system/drive_file_sync_service.h" #include "chrome/browser/sync_file_system/sync_file_system_service.h" #include "chrome/browser/sync_file_system/sync_file_system_service_factory.h" @@ -29,6 +30,7 @@ using content::BrowserContext; using content::BrowserThread; +using sync_file_system::ConflictResolutionPolicy; using sync_file_system::SyncFileStatus; using sync_file_system::SyncFileSystemServiceFactory; using sync_file_system::SyncStatusCode; @@ -44,6 +46,8 @@ const char* const kDriveCloudService = // Error messages. const char kFileError[] = "File error %d."; const char kQuotaError[] = "Quota error %d."; +const char kUnsupportedConflictResolutionPolicy[] = + "Policy %s is not supported."; api::sync_file_system::FileStatus FileSyncStatusEnumToExtensionEnum( const SyncFileStatus state) { @@ -61,6 +65,37 @@ api::sync_file_system::FileStatus FileSyncStatusEnumToExtensionEnum( return api::sync_file_system::FILE_STATUS_NONE; } +ConflictResolutionPolicy ExtensionEnumToConflictResolutionPolicy( + const std::string& policy_string) { + api::sync_file_system::ConflictResolutionPolicy policy = + api::sync_file_system::ParseConflictResolutionPolicy(policy_string); + switch (policy) { + case api::sync_file_system::CONFLICT_RESOLUTION_POLICY_NONE: + return sync_file_system::CONFLICT_RESOLUTION_UNKNOWN; + case api::sync_file_system::CONFLICT_RESOLUTION_POLICY_LAST_WRITE_WIN: + return sync_file_system::CONFLICT_RESOLUTION_LAST_WRITE_WIN; + case api::sync_file_system::CONFLICT_RESOLUTION_POLICY_MANUAL: + return sync_file_system::CONFLICT_RESOLUTION_MANUAL; + } + NOTREACHED(); + return sync_file_system::CONFLICT_RESOLUTION_UNKNOWN; +} + +api::sync_file_system::ConflictResolutionPolicy +ConflictResolutionPolicyToExtensionEnum( + ConflictResolutionPolicy policy) { + switch (policy) { + case sync_file_system::CONFLICT_RESOLUTION_UNKNOWN: + return api::sync_file_system::CONFLICT_RESOLUTION_POLICY_NONE; + case sync_file_system::CONFLICT_RESOLUTION_LAST_WRITE_WIN: + return api::sync_file_system::CONFLICT_RESOLUTION_POLICY_LAST_WRITE_WIN; + case sync_file_system::CONFLICT_RESOLUTION_MANUAL: + return api::sync_file_system::CONFLICT_RESOLUTION_POLICY_MANUAL; + } + NOTREACHED(); + return api::sync_file_system::CONFLICT_RESOLUTION_POLICY_NONE; +} + sync_file_system::SyncFileSystemService* GetSyncFileSystemService( Profile* profile) { sync_file_system::SyncFileSystemService* service = @@ -298,4 +333,37 @@ void SyncFileSystemGetUsageAndQuotaFunction::DidGetUsageAndQuota( SendResponse(true); } +bool SyncFileSystemSetConflictResolutionPolicyFunction::RunImpl() { + std::string policy_string; + EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &policy_string)); + ConflictResolutionPolicy policy = + ExtensionEnumToConflictResolutionPolicy(policy_string); + if (policy == sync_file_system::CONFLICT_RESOLUTION_UNKNOWN) { + SetError(base::StringPrintf(kUnsupportedConflictResolutionPolicy, + policy_string.c_str())); + return false; + } + sync_file_system::SyncFileSystemService* service = GetSyncFileSystemService( + profile()); + DCHECK(service); + SyncStatusCode status = service->SetConflictResolutionPolicy(policy); + if (status != sync_file_system::SYNC_STATUS_OK) { + SetError(sync_file_system::SyncStatusCodeToString(status)); + return false; + } + return true; +} + +bool SyncFileSystemGetConflictResolutionPolicyFunction::RunImpl() { + sync_file_system::SyncFileSystemService* service = GetSyncFileSystemService( + profile()); + DCHECK(service); + api::sync_file_system::ConflictResolutionPolicy policy = + ConflictResolutionPolicyToExtensionEnum( + service->GetConflictResolutionPolicy()); + SetResult(Value::CreateStringValue( + api::sync_file_system::ToString(policy))); + return true; +} + } // namespace extensions diff --git a/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.h b/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.h index 7d2c5be..d51b75d 100644 --- a/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.h +++ b/chrome/browser/extensions/api/sync_file_system/sync_file_system_api.h @@ -7,6 +7,7 @@ #include "base/platform_file.h" #include "chrome/browser/extensions/extension_function.h" +#include "chrome/browser/sync_file_system/conflict_resolution_policy.h" #include "webkit/fileapi/syncable/sync_file_status.h" #include "webkit/fileapi/syncable/sync_status_code.h" #include "webkit/quota/quota_types.h" @@ -87,6 +88,28 @@ class SyncFileSystemRequestFileSystemFunction const GURL& root_url); }; +class SyncFileSystemSetConflictResolutionPolicyFunction + : public SyncExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("syncFileSystem.setConflictResolutionPolicy", + SYNCFILESYSTEM_SETCONFLICTRESOLUTIONPOLICY) + + protected: + virtual ~SyncFileSystemSetConflictResolutionPolicyFunction() {} + virtual bool RunImpl() OVERRIDE; +}; + +class SyncFileSystemGetConflictResolutionPolicyFunction + : public SyncExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("syncFileSystem.getConflictResolutionPolicy", + SYNCFILESYSTEM_GETCONFLICTRESOLUTIONPOLICY) + + protected: + virtual ~SyncFileSystemGetConflictResolutionPolicyFunction() {} + virtual bool RunImpl() OVERRIDE; +}; + } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_SYNC_FILE_SYSTEM_SYNC_FILE_SYSTEM_API_H_ diff --git a/chrome/browser/extensions/api/sync_file_system/sync_file_system_apitest.cc b/chrome/browser/extensions/api/sync_file_system/sync_file_system_apitest.cc index 6f7e01f..e645d86 100644 --- a/chrome/browser/extensions/api/sync_file_system/sync_file_system_apitest.cc +++ b/chrome/browser/extensions/api/sync_file_system/sync_file_system_apitest.cc @@ -173,6 +173,11 @@ IN_PROC_BROWSER_TEST_F(SyncFileSystemApiTest, WriteFileThenGetUsage) { << message_; } +IN_PROC_BROWSER_TEST_F(SyncFileSystemApiTest, ConflictResolutionPolicy) { + ASSERT_TRUE(RunPlatformAppTest("sync_file_system/conflict_resolution_policy")) + << message_; +} + #endif // !defined(OS_CHROMEOS) } // namespace chrome diff --git a/chrome/browser/extensions/extension_function_histogram_value.h b/chrome/browser/extensions/extension_function_histogram_value.h index 91afbb0..ce4f1fc 100644 --- a/chrome/browser/extensions/extension_function_histogram_value.h +++ b/chrome/browser/extensions/extension_function_histogram_value.h @@ -488,6 +488,8 @@ enum HistogramValue { FILEBROWSERPRIVATE_OPENNEWWINDOW, CLOUDPRINTPRIVATE_GETCLIENTID, ECHOPRIVATE_GETUSERCONSENT, + SYNCFILESYSTEM_SETCONFLICTRESOLUTIONPOLICY, + SYNCFILESYSTEM_GETCONFLICTRESOLUTIONPOLICY, ENUM_BOUNDARY // Last entry: Add new entries above. }; diff --git a/chrome/browser/sync_file_system/conflict_resolution_policy.h b/chrome/browser/sync_file_system/conflict_resolution_policy.h index 168549e..2fee089 100644 --- a/chrome/browser/sync_file_system/conflict_resolution_policy.h +++ b/chrome/browser/sync_file_system/conflict_resolution_policy.h @@ -8,11 +8,14 @@ namespace sync_file_system { enum ConflictResolutionPolicy { + // Resolution policy unknown or not initialized. Usually indicates an error. + CONFLICT_RESOLUTION_UNKNOWN, + // The service automatically resolves a conflict by choosing the one // that is updated more recently. CONFLICT_RESOLUTION_LAST_WRITE_WIN, - // The service does nothing but just leaves conflicting files as + // The service does nothing and just leaves conflicting files in // 'conflicted' state. CONFLICT_RESOLUTION_MANUAL, }; diff --git a/chrome/browser/sync_file_system/drive_file_sync_service.cc b/chrome/browser/sync_file_system/drive_file_sync_service.cc index b1785ed..8eb1ee8 100644 --- a/chrome/browser/sync_file_system/drive_file_sync_service.cc +++ b/chrome/browser/sync_file_system/drive_file_sync_service.cc @@ -552,9 +552,10 @@ void DriveFileSyncService::SetSyncEnabled(bool enabled) { } } -void DriveFileSyncService::SetConflictResolutionPolicy( +SyncStatusCode DriveFileSyncService::SetConflictResolutionPolicy( ConflictResolutionPolicy resolution) { conflict_resolution_ = resolution; + return SYNC_STATUS_OK; } ConflictResolutionPolicy diff --git a/chrome/browser/sync_file_system/drive_file_sync_service.h b/chrome/browser/sync_file_system/drive_file_sync_service.h index 2334306..9f08c49 100644 --- a/chrome/browser/sync_file_system/drive_file_sync_service.h +++ b/chrome/browser/sync_file_system/drive_file_sync_service.h @@ -94,7 +94,7 @@ class DriveFileSyncService virtual RemoteServiceState GetCurrentState() const OVERRIDE; virtual const char* GetServiceName() const OVERRIDE; virtual void SetSyncEnabled(bool enabled) OVERRIDE; - virtual void SetConflictResolutionPolicy( + virtual SyncStatusCode SetConflictResolutionPolicy( ConflictResolutionPolicy resolution) OVERRIDE; virtual ConflictResolutionPolicy GetConflictResolutionPolicy() const OVERRIDE; diff --git a/chrome/browser/sync_file_system/mock_remote_file_sync_service.cc b/chrome/browser/sync_file_system/mock_remote_file_sync_service.cc index 46d650c..b907927 100644 --- a/chrome/browser/sync_file_system/mock_remote_file_sync_service.cc +++ b/chrome/browser/sync_file_system/mock_remote_file_sync_service.cc @@ -20,7 +20,8 @@ namespace sync_file_system { const char MockRemoteFileSyncService::kServiceName[] = "mock_sync_service"; -MockRemoteFileSyncService::MockRemoteFileSyncService() { +MockRemoteFileSyncService::MockRemoteFileSyncService() + : conflict_resolution_policy_(CONFLICT_RESOLUTION_MANUAL) { typedef MockRemoteFileSyncService self; ON_CALL(*this, AddServiceObserver(_)) .WillByDefault(Invoke(this, &self::AddServiceObserverStub)); @@ -46,6 +47,10 @@ MockRemoteFileSyncService::MockRemoteFileSyncService() { .WillByDefault(Return(REMOTE_SERVICE_OK)); ON_CALL(*this, GetServiceName()) .WillByDefault(Return(kServiceName)); + ON_CALL(*this, SetConflictResolutionPolicy(_)) + .WillByDefault(Invoke(this, &self::SetConflictResolutionPolicyStub)); + ON_CALL(*this, GetConflictResolutionPolicy()) + .WillByDefault(Invoke(this, &self::GetConflictResolutionPolicyStub)); } MockRemoteFileSyncService::~MockRemoteFileSyncService() { @@ -130,4 +135,15 @@ void MockRemoteFileSyncService::GetRemoteFileMetadataStub( FROM_HERE, base::Bind(callback, SYNC_STATUS_OK, iter->second)); } +SyncStatusCode MockRemoteFileSyncService::SetConflictResolutionPolicyStub( + ConflictResolutionPolicy policy) { + conflict_resolution_policy_ = policy; + return SYNC_STATUS_OK; +} + +ConflictResolutionPolicy +MockRemoteFileSyncService::GetConflictResolutionPolicyStub() const { + return conflict_resolution_policy_; +} + } // namespace sync_file_system diff --git a/chrome/browser/sync_file_system/mock_remote_file_sync_service.h b/chrome/browser/sync_file_system/mock_remote_file_sync_service.h index d5e7dd6..0337b03 100644 --- a/chrome/browser/sync_file_system/mock_remote_file_sync_service.h +++ b/chrome/browser/sync_file_system/mock_remote_file_sync_service.h @@ -53,7 +53,7 @@ class MockRemoteFileSyncService : public RemoteFileSyncService { MOCK_CONST_METHOD0(GetServiceName, const char*()); MOCK_METHOD1(SetSyncEnabled, void(bool)); MOCK_METHOD1(SetConflictResolutionPolicy, - void(ConflictResolutionPolicy)); + SyncStatusCode(ConflictResolutionPolicy)); MOCK_CONST_METHOD0(GetConflictResolutionPolicy, ConflictResolutionPolicy()); @@ -99,6 +99,9 @@ class MockRemoteFileSyncService : public RemoteFileSyncService { void GetRemoteFileMetadataStub( const fileapi::FileSystemURL& url, const SyncFileMetadataCallback& callback); + SyncStatusCode SetConflictResolutionPolicyStub( + ConflictResolutionPolicy policy); + ConflictResolutionPolicy GetConflictResolutionPolicyStub() const; OriginToURLSetMap conflict_file_urls_; FileMetadataMap conflict_file_metadata_; @@ -109,6 +112,8 @@ class MockRemoteFileSyncService : public RemoteFileSyncService { ObserverList<Observer> service_observers_; ObserverList<FileStatusObserver> file_status_observers_; + ConflictResolutionPolicy conflict_resolution_policy_; + DISALLOW_COPY_AND_ASSIGN(MockRemoteFileSyncService); }; diff --git a/chrome/browser/sync_file_system/remote_file_sync_service.h b/chrome/browser/sync_file_system/remote_file_sync_service.h index e0ef3b3..615cba3 100644 --- a/chrome/browser/sync_file_system/remote_file_sync_service.h +++ b/chrome/browser/sync_file_system/remote_file_sync_service.h @@ -143,8 +143,10 @@ class RemoteFileSyncService { // REMOTE_SERVICE_TEMPORARY_UNAVAILABLE). virtual void SetSyncEnabled(bool enabled) = 0; - // Sets the conflict resolution policy. - virtual void SetConflictResolutionPolicy( + // Sets the conflict resolution policy. Returns SYNC_STATUS_OK on success, + // or returns an error code if the given policy is not supported or had + // an error. + virtual SyncStatusCode SetConflictResolutionPolicy( ConflictResolutionPolicy policy) = 0; // Gets the conflict resolution policy. diff --git a/chrome/browser/sync_file_system/sync_file_system_service.cc b/chrome/browser/sync_file_system/sync_file_system_service.cc index 38b1f40..4774edf 100644 --- a/chrome/browser/sync_file_system/sync_file_system_service.cc +++ b/chrome/browser/sync_file_system/sync_file_system_service.cc @@ -146,6 +146,16 @@ void SyncFileSystemService::RemoveSyncEventObserver( observers_.RemoveObserver(observer); } +ConflictResolutionPolicy +SyncFileSystemService::GetConflictResolutionPolicy() const { + return remote_file_service_->GetConflictResolutionPolicy(); +} + +SyncStatusCode SyncFileSystemService::SetConflictResolutionPolicy( + ConflictResolutionPolicy policy) { + return remote_file_service_->SetConflictResolutionPolicy(policy); +} + SyncFileSystemService::SyncFileSystemService(Profile* profile) : profile_(profile), pending_local_changes_(0), diff --git a/chrome/browser/sync_file_system/sync_file_system_service.h b/chrome/browser/sync_file_system/sync_file_system_service.h index ec46e5a..6ff9280 100644 --- a/chrome/browser/sync_file_system/sync_file_system_service.h +++ b/chrome/browser/sync_file_system/sync_file_system_service.h @@ -15,6 +15,7 @@ #include "base/observer_list.h" #include "chrome/browser/api/sync/profile_sync_service_observer.h" #include "chrome/browser/profiles/profile_keyed_service.h" +#include "chrome/browser/sync_file_system/conflict_resolution_policy.h" #include "chrome/browser/sync_file_system/file_status_observer.h" #include "chrome/browser/sync_file_system/local_file_sync_service.h" #include "chrome/browser/sync_file_system/remote_file_sync_service.h" @@ -59,6 +60,9 @@ class SyncFileSystemService void AddSyncEventObserver(SyncEventObserver* observer); void RemoveSyncEventObserver(SyncEventObserver* observer); + ConflictResolutionPolicy GetConflictResolutionPolicy() const; + SyncStatusCode SetConflictResolutionPolicy(ConflictResolutionPolicy policy); + private: friend class SyncFileSystemServiceFactory; friend class SyncFileSystemServiceTest; diff --git a/chrome/common/extensions/api/sync_file_system.idl b/chrome/common/extensions/api/sync_file_system.idl index dcc3011..9d4fdd2 100644 --- a/chrome/common/extensions/api/sync_file_system.idl +++ b/chrome/common/extensions/api/sync_file_system.idl @@ -48,6 +48,10 @@ namespace syncFileSystem { local_to_remote, remote_to_local }; + enum ConflictResolutionPolicy { + last_write_win, manual + }; + dictionary FileInfo { // <code>fileEntry</code> for the target file whose status has changed. // Contains name and path information of synchronized file. @@ -55,18 +59,18 @@ namespace syncFileSystem { // <code>fileEntry</code> information will still be available // but file will no longer exist. [instanceOf=FileEntry] object fileEntry; - + // Resulting file status after $ref:onFileStatusChanged event. - // The status value can be <code>'synced'</code>, + // The status value can be <code>'synced'</code>, // <code>'pending'</code> or <code>'conflicting'</code>. FileStatus status; - + // Sync action taken to fire $ref:onFileStatusChanged event. // The action value can be // <code>'added'</code>, <code>'updated'</code> or <code>'deleted'</code>. // Only applies if status is <code>'synced'</code>. SyncAction? action; - + // Sync direction for the $ref:onFileStatusChanged event. // Sync direction value can be // <code>'local_to_remote'</code> or <code>'remote_to_local'</code>. @@ -97,15 +101,38 @@ namespace syncFileSystem { // A callback type for getFileStatus. callback GetFileStatusCallback = void (FileStatus status); + // A callback type for getConflictResolutionPolicy. + callback GetConflictResolutionPolicyCallback = + void (ConflictResolutionPolicy policy); + + // A generic result callback to indicate success or failure. + callback ResultCallback = void (); + interface Functions { - // Returns a syncable filesystem backed by Google Drive. + // Returns a syncable filesystem backed by Google Drive. // The returned <code>DOMFileSystem</code> instance can be operated on // in the same way as the Temporary and Persistant file systems (see // <a href="http://www.w3.org/TR/file-system-api/">http://www.w3.org/TR/file-system-api/</a>). // Calling this multiple times from - // the same app will return the same handle to the same file system. + // the same app will return the same handle to the same file system. static void requestFileSystem(GetFileSystemCallback callback); + // Sets the default conflict resolution policy + // for the <code>'syncable'</code> file storage for the app. + // By default it is set to <code>'manual'</code>. + // When conflict resolution policy is set to <code>'last_write_win'</code> + // conflicts for existing files are automatically resolved next time + // the file is updated. + // |callback| can be optionally given to know if the request has + // succeeded or not. + static void setConflictResolutionPolicy( + ConflictResolutionPolicy policy, + optional ResultCallback callback); + + // Gets the current conflict resolution policy. + static void getConflictResolutionPolicy( + GetConflictResolutionPolicyCallback callback); + // Returns the current usage and quota in bytes // for the <code>'syncable'</code> file storage for the app. static void getUsageAndQuota([instanceOf=DOMFileSystem] object fileSystem, @@ -118,6 +145,8 @@ namespace syncFileSystem { // Returns the $ref:FileStatus for the given <code>fileEntry</code>. // The status value can be <code>'synced'</code>, // <code>'pending'</code> or <code>'conflicting'</code>. + // Note that <code>'conflicting'</code> state only happens when + // the service's conflict resolution policy is set to <code>'manual'</code>. static void getFileStatus([instanceOf=FileEntry] object fileEntry, GetFileStatusCallback callback); }; diff --git a/chrome/test/data/extensions/api_test/sync_file_system/conflict_resolution_policy/manifest.json b/chrome/test/data/extensions/api_test/sync_file_system/conflict_resolution_policy/manifest.json new file mode 100644 index 0000000..0d9b438 --- /dev/null +++ b/chrome/test/data/extensions/api_test/sync_file_system/conflict_resolution_policy/manifest.json @@ -0,0 +1,12 @@ +{ + "name": "chrome.syncFileSystem.setConflictResolutionPolicy", + "version": "0.1", + "manifest_version": 2, + "description": "end-to-end browser test for set and get conflict resolution policy via chrome.syncFileSystem.{set,get}conflictResolutionPolicy API", + "permissions": ["syncFileSystem"], + "app": { + "background": { + "scripts": ["test.js"] + } + } +} diff --git a/chrome/test/data/extensions/api_test/sync_file_system/conflict_resolution_policy/test.js b/chrome/test/data/extensions/api_test/sync_file_system/conflict_resolution_policy/test.js new file mode 100644 index 0000000..6226198 --- /dev/null +++ b/chrome/test/data/extensions/api_test/sync_file_system/conflict_resolution_policy/test.js @@ -0,0 +1,34 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var testStep = [ + function testManualPolicy() { + testConflictResolutionPolicy('manual', testStep.shift()); + }, + function testLastWriteWinPolicy() { + testConflictResolutionPolicy('last_write_win', chrome.test.callbackPass()); + }, +]; + +function testConflictResolutionPolicy(policy, callback) { + var steps = [ + function setManualPolicy() { + chrome.syncFileSystem.setConflictResolutionPolicy( + policy, chrome.test.callbackPass(steps.shift())); + }, + function getManualPolicy() { + chrome.syncFileSystem.getConflictResolutionPolicy( + chrome.test.callbackPass(steps.shift())); + }, + function checkManualPolicy(policy_returned) { + chrome.test.assertEq(policy, policy_returned); + callback(); + } + ]; + steps.shift()(); +} + +chrome.test.runTests([ + testStep.shift() +]); |