1 // Copyright 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.
5 // Custom binding for the fileSystemProvider API.
7 var binding = require('binding').Binding.create('fileSystemProvider');
8 var fileSystemProviderInternal =
9 require('binding').Binding.create('fileSystemProviderInternal').generate();
10 var eventBindings = require('event_bindings');
11 var fileSystemNatives = requireNative('file_system_natives');
12 var GetDOMError = fileSystemNatives.GetDOMError;
15 * Maximum size of the thumbnail in bytes.
19 var METADATA_THUMBNAIL_SIZE_LIMIT = 32 * 1024 * 1024;
22 * Regular expression to validate if the thumbnail URI is a valid data URI,
23 * taking into account allowed formats.
27 var METADATA_THUMBNAIL_FORMAT = new RegExp(
28 '^data:image/(png|jpeg|webp);', 'i');
31 * Annotates a date with its serialized value.
32 * @param {Date} date Input date.
33 * @return {Date} Date with an extra <code>value</code> attribute.
35 function annotateDate(date) {
36 // Copy in case the input date is frozen.
37 var result = new Date(date.getTime());
38 result.value = result.toString();
43 * Verifies if the passed image URI is valid.
44 * @param {*} uri Image URI.
45 * @return {boolean} True if valid, valse otherwise.
47 function verifyImageURI(uri) {
48 // The URI is specified by a user, so the type may be incorrect.
49 if (typeof uri != 'string' && !(uri instanceof String))
52 return METADATA_THUMBNAIL_FORMAT.test(uri);
56 * Annotates an entry metadata by serializing its modifiedTime value.
57 * @param {EntryMetadata} metadata Input metadata.
58 * @return {EntryMetadata} metadata Annotated metadata, which can be passed
59 * back to the C++ layer.
61 function annotateMetadata(metadata) {
63 isDirectory: metadata.isDirectory,
66 modificationTime: annotateDate(metadata.modificationTime)
68 if ('mimeType' in metadata)
69 result.mimeType = metadata.mimeType;
70 if ('thumbnail' in metadata)
71 result.thumbnail = metadata.thumbnail;
76 * Massages arguments of an event raised by the File System Provider API.
77 * @param {Array.<*>} args Input arguments.
78 * @param {function(Array.<*>)} dispatch Closure to be called with massaged
81 function massageArgumentsDefault(args, dispatch) {
82 var executionStart = Date.now();
83 var options = args[0];
84 var onSuccessCallback = function(hasNext) {
85 fileSystemProviderInternal.operationRequestedSuccess(
86 options.fileSystemId, options.requestId, Date.now() - executionStart);
88 var onErrorCallback = function(error) {
89 fileSystemProviderInternal.operationRequestedError(
90 options.fileSystemId, options.requestId, error,
91 Date.now() - executionStart);
93 dispatch([options, onSuccessCallback, onErrorCallback]);
97 binding.registerCustomHook(function(bindingsAPI) {
98 var apiFunctions = bindingsAPI.apiFunctions;
100 apiFunctions.setUpdateArgumentsPostValidate(
102 function(options, successCallback, errorCallback) {
103 // Piggyback the error callback onto the success callback,
104 // so we can use the error callback later.
105 successCallback.errorCallback_ = errorCallback;
106 return [options, successCallback];
109 apiFunctions.setCustomCallback(
111 function(name, request, response) {
113 if (request.callback && response) {
114 // DOMError is present only if mount() failed.
116 // Convert a Dictionary to a DOMError.
117 domError = GetDOMError(response[0].name, response[0].message);
121 var successCallback = request.callback;
122 var errorCallback = request.callback.errorCallback_;
123 delete request.callback;
126 errorCallback(domError);
132 apiFunctions.setUpdateArgumentsPostValidate(
134 function(options, successCallback, errorCallback) {
135 // Piggyback the error callback onto the success callback,
136 // so we can use the error callback later.
137 successCallback.errorCallback_ = errorCallback;
138 return [options, successCallback];
141 apiFunctions.setCustomCallback(
143 function(name, request, response) {
145 if (request.callback) {
146 // DOMError is present only if mount() failed.
147 if (response && response[0]) {
148 // Convert a Dictionary to a DOMError.
149 domError = GetDOMError(response[0].name, response[0].message);
153 var successCallback = request.callback;
154 var errorCallback = request.callback.errorCallback_;
155 delete request.callback;
158 errorCallback(domError);
165 eventBindings.registerArgumentMassager(
166 'fileSystemProvider.onUnmountRequested',
167 massageArgumentsDefault);
169 eventBindings.registerArgumentMassager(
170 'fileSystemProvider.onGetMetadataRequested',
171 function(args, dispatch) {
172 var executionStart = Date.now();
173 var options = args[0];
174 var onSuccessCallback = function(metadata) {
176 // It is invalid to return a thumbnail when it's not requested. The
177 // restriction is added in order to avoid fetching the thumbnail while
179 if (!options.thumbnail && metadata.thumbnail)
180 error = 'Thumbnail data provided, but not requested.';
182 // Check the format and size. Note, that in the C++ layer, there is
183 // another sanity check to avoid passing any evil URL.
184 if ('thumbnail' in metadata && !verifyImageURI(metadata.thumbnail))
185 error = 'Thumbnail format invalid.';
187 if ('thumbnail' in metadata &&
188 metadata.thumbnail.length > METADATA_THUMBNAIL_SIZE_LIMIT) {
189 error = 'Thumbnail data too large.';
193 console.error(error);
194 fileSystemProviderInternal.operationRequestedError(
195 options.fileSystemId, options.requestId, 'FAILED',
196 Date.now() - executionStart);
200 fileSystemProviderInternal.getMetadataRequestedSuccess(
201 options.fileSystemId,
203 annotateMetadata(metadata),
204 Date.now() - executionStart);
207 var onErrorCallback = function(error) {
208 fileSystemProviderInternal.operationRequestedError(
209 options.fileSystemId, options.requestId, error,
210 Date.now() - executionStart);
213 dispatch([options, onSuccessCallback, onErrorCallback]);
216 eventBindings.registerArgumentMassager(
217 'fileSystemProvider.onReadDirectoryRequested',
218 function(args, dispatch) {
219 var executionStart = Date.now();
220 var options = args[0];
221 var onSuccessCallback = function(entries, hasNext) {
222 var annotatedEntries = entries.map(annotateMetadata);
223 // It is invalid to return a thumbnail when it's not requested.
225 annotatedEntries.forEach(function(metadata) {
226 if (metadata.thumbnail) {
228 'Thumbnails must not be provided when reading a directory.';
234 console.error(error);
235 fileSystemProviderInternal.operationRequestedError(
236 options.fileSystemId, options.requestId, 'FAILED',
237 Date.now() - executionStart);
241 fileSystemProviderInternal.readDirectoryRequestedSuccess(
242 options.fileSystemId, options.requestId, annotatedEntries, hasNext,
243 Date.now() - executionStart);
246 var onErrorCallback = function(error) {
247 fileSystemProviderInternal.operationRequestedError(
248 options.fileSystemId, options.requestId, error,
249 Date.now() - executionStart);
251 dispatch([options, onSuccessCallback, onErrorCallback]);
254 eventBindings.registerArgumentMassager(
255 'fileSystemProvider.onOpenFileRequested',
256 massageArgumentsDefault);
258 eventBindings.registerArgumentMassager(
259 'fileSystemProvider.onCloseFileRequested',
260 massageArgumentsDefault);
262 eventBindings.registerArgumentMassager(
263 'fileSystemProvider.onReadFileRequested',
264 function(args, dispatch) {
265 var executionStart = Date.now();
266 var options = args[0];
267 var onSuccessCallback = function(data, hasNext) {
268 fileSystemProviderInternal.readFileRequestedSuccess(
269 options.fileSystemId, options.requestId, data, hasNext,
270 Date.now() - executionStart);
272 var onErrorCallback = function(error) {
273 fileSystemProviderInternal.operationRequestedError(
274 options.fileSystemId, options.requestId, error,
275 Date.now() - executionStart);
277 dispatch([options, onSuccessCallback, onErrorCallback]);
280 eventBindings.registerArgumentMassager(
281 'fileSystemProvider.onCreateDirectoryRequested',
282 massageArgumentsDefault);
284 eventBindings.registerArgumentMassager(
285 'fileSystemProvider.onDeleteEntryRequested',
286 massageArgumentsDefault);
288 eventBindings.registerArgumentMassager(
289 'fileSystemProvider.onCreateFileRequested',
290 massageArgumentsDefault);
292 eventBindings.registerArgumentMassager(
293 'fileSystemProvider.onCopyEntryRequested',
294 massageArgumentsDefault);
296 eventBindings.registerArgumentMassager(
297 'fileSystemProvider.onMoveEntryRequested',
298 massageArgumentsDefault);
300 eventBindings.registerArgumentMassager(
301 'fileSystemProvider.onTruncateRequested',
302 massageArgumentsDefault);
304 eventBindings.registerArgumentMassager(
305 'fileSystemProvider.onWriteFileRequested',
306 massageArgumentsDefault);
308 eventBindings.registerArgumentMassager(
309 'fileSystemProvider.onAbortRequested',
310 massageArgumentsDefault);
312 exports.binding = binding.generate();