1// Copyright (c) 2012 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// Custom binding for the fileSystem API. 6 7var binding = require('binding').Binding.create('fileSystem'); 8 9var fileSystemNatives = requireNative('file_system_natives'); 10var GetIsolatedFileSystem = fileSystemNatives.GetIsolatedFileSystem; 11var lastError = require('lastError'); 12var sendRequest = require('sendRequest'); 13var GetModuleSystem = requireNative('v8_context').GetModuleSystem; 14// TODO(sammc): Don't require extension. See http://crbug.com/235689. 15var GetExtensionViews = requireNative('runtime').GetExtensionViews; 16 17// Fallback to using the current window if no background page is running. 18var backgroundPage = GetExtensionViews(-1, 'BACKGROUND')[0] || window; 19var backgroundPageModuleSystem = GetModuleSystem(backgroundPage); 20 21// All windows use the bindFileEntryCallback from the background page so their 22// FileEntry objects have the background page's context as their own. This 23// allows them to be used from other windows (including the background page) 24// after the original window is closed. 25if (window == backgroundPage) { 26 var bindFileEntryCallback = function(functionName, apiFunctions) { 27 apiFunctions.setCustomCallback(functionName, 28 function(name, request, response) { 29 if (request.callback && response) { 30 var callback = request.callback; 31 request.callback = null; 32 33 var entries = []; 34 var hasError = false; 35 36 var getEntryError = function(fileError) { 37 if (!hasError) { 38 hasError = true; 39 lastError.run( 40 'fileSystem.' + functionName, 41 'Error getting fileEntry, code: ' + fileError.code, 42 request.stack, 43 callback); 44 } 45 } 46 47 // Loop through the response entries and asynchronously get the 48 // FileEntry for each. We use hasError to ensure that only the first 49 // error is reported. Note that an error can occur either during the 50 // loop or in the asynchronous error callback to getFile. 51 $Array.forEach(response.entries, function(entry) { 52 if (hasError) 53 return; 54 var fileSystemId = entry.fileSystemId; 55 var baseName = entry.baseName; 56 var id = entry.id; 57 var fs = GetIsolatedFileSystem(fileSystemId); 58 59 try { 60 var getEntryCallback = function(fileEntry) { 61 if (hasError) 62 return; 63 entryIdManager.registerEntry(id, fileEntry); 64 entries.push(fileEntry); 65 // Once all entries are ready, pass them to the callback. In the 66 // event of an error, this condition will never be satisfied so 67 // the callback will not be called with any entries. 68 if (entries.length == response.entries.length) { 69 if (response.multiple) { 70 sendRequest.safeCallbackApply( 71 'fileSystem.' + functionName, request, callback, 72 [entries]); 73 } else { 74 sendRequest.safeCallbackApply( 75 'fileSystem.' + functionName, request, callback, 76 [entries[0]]); 77 } 78 } 79 } 80 // TODO(koz): fs.root.getFile() makes a trip to the browser process, 81 // but it might be possible avoid that by calling 82 // WebDOMFileSystem::createV8Entry(). 83 if (entry.isDirectory) { 84 fs.root.getDirectory(baseName, {}, getEntryCallback, 85 getEntryError); 86 } else { 87 fs.root.getFile(baseName, {}, getEntryCallback, getEntryError); 88 } 89 } catch (e) { 90 if (!hasError) { 91 hasError = true; 92 lastError.run('fileSystem.' + functionName, 93 'Error getting fileEntry: ' + e.stack, 94 request.stack, 95 callback); 96 } 97 } 98 }); 99 } 100 }); 101 }; 102 var entryIdManager = require('entryIdManager'); 103} else { 104 // Force the fileSystem API to be loaded in the background page. Using 105 // backgroundPageModuleSystem.require('fileSystem') is insufficient as 106 // requireNative is only allowed while lazily loading an API. 107 backgroundPage.chrome.fileSystem; 108 var bindFileEntryCallback = backgroundPageModuleSystem.require( 109 'fileSystem').bindFileEntryCallback; 110 var entryIdManager = backgroundPageModuleSystem.require('entryIdManager'); 111} 112 113binding.registerCustomHook(function(bindingsAPI) { 114 var apiFunctions = bindingsAPI.apiFunctions; 115 var fileSystem = bindingsAPI.compiledApi; 116 117 function bindFileEntryFunction(functionName) { 118 apiFunctions.setUpdateArgumentsPostValidate( 119 functionName, function(fileEntry, callback) { 120 var fileSystemName = fileEntry.filesystem.name; 121 var relativePath = $String.slice(fileEntry.fullPath, 1); 122 return [fileSystemName, relativePath, callback]; 123 }); 124 } 125 $Array.forEach(['getDisplayPath', 'getWritableEntry', 'isWritableEntry'], 126 bindFileEntryFunction); 127 128 $Array.forEach(['getWritableEntry', 'chooseEntry', 'restoreEntry'], 129 function(functionName) { 130 bindFileEntryCallback(functionName, apiFunctions); 131 }); 132 133 apiFunctions.setHandleRequest('retainEntry', function(fileEntry) { 134 var id = entryIdManager.getEntryId(fileEntry); 135 if (!id) 136 return ''; 137 var fileSystemName = fileEntry.filesystem.name; 138 var relativePath = $String.slice(fileEntry.fullPath, 1); 139 140 sendRequest.sendRequest(this.name, [id, fileSystemName, relativePath], 141 this.definition.parameters, {}); 142 return id; 143 }); 144 145 apiFunctions.setHandleRequest('isRestorable', 146 function(id, callback) { 147 var savedEntry = entryIdManager.getEntryById(id); 148 if (savedEntry) { 149 sendRequest.safeCallbackApply( 150 'fileSystem.isRestorable', 151 {'stack': sendRequest.getExtensionStackTrace()}, 152 callback, 153 [true]); 154 } else { 155 sendRequest.sendRequest( 156 this.name, [id, callback], this.definition.parameters, {}); 157 } 158 }); 159 160 apiFunctions.setUpdateArgumentsPostValidate('restoreEntry', 161 function(id, callback) { 162 var savedEntry = entryIdManager.getEntryById(id); 163 if (savedEntry) { 164 // We already have a file entry for this id so pass it to the callback and 165 // send a request to the browser to move it to the back of the LRU. 166 sendRequest.safeCallbackApply( 167 'fileSystem.restoreEntry', 168 {'stack': sendRequest.getExtensionStackTrace()}, 169 callback, 170 [savedEntry]); 171 return [id, false, null]; 172 } else { 173 // Ask the browser process for a new file entry for this id, to be passed 174 // to |callback|. 175 return [id, true, callback]; 176 } 177 }); 178 179 // TODO(benwells): Remove these deprecated versions of the functions. 180 fileSystem.getWritableFileEntry = function() { 181 console.log("chrome.fileSystem.getWritableFileEntry is deprecated"); 182 console.log("Please use chrome.fileSystem.getWritableEntry instead"); 183 $Function.apply(fileSystem.getWritableEntry, this, arguments); 184 }; 185 186 fileSystem.isWritableFileEntry = function() { 187 console.log("chrome.fileSystem.isWritableFileEntry is deprecated"); 188 console.log("Please use chrome.fileSystem.isWritableEntry instead"); 189 $Function.apply(fileSystem.isWritableEntry, this, arguments); 190 }; 191 192 fileSystem.chooseFile = function() { 193 console.log("chrome.fileSystem.chooseFile is deprecated"); 194 console.log("Please use chrome.fileSystem.chooseEntry instead"); 195 $Function.apply(fileSystem.chooseEntry, this, arguments); 196 }; 197}); 198 199exports.bindFileEntryCallback = bindFileEntryCallback; 200exports.binding = binding.generate(); 201