Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(107)

Side by Side Diff: chrome/browser/resources/file_manager/js/util.js

Issue 39123003: [Files.app] Split the JavaScript files into subdirectories: common, background, and foreground (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fixed test failure. Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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 'use strict';
6
7 /**
8 * Namespace for utility functions.
9 */
10 var util = {};
11
12 /**
13 * Returns a function that console.log's its arguments, prefixed by |msg|.
14 *
15 * @param {string} msg The message prefix to use in the log.
16 * @param {function(...string)=} opt_callback A function to invoke after
17 * logging.
18 * @return {function(...string)} Function that logs.
19 */
20 util.flog = function(msg, opt_callback) {
21 return function() {
22 var ary = Array.apply(null, arguments);
23 console.log(msg + ': ' + ary.join(', '));
24 if (opt_callback)
25 opt_callback.apply(null, arguments);
26 };
27 };
28
29 /**
30 * Returns a function that throws an exception that includes its arguments
31 * prefixed by |msg|.
32 *
33 * @param {string} msg The message prefix to use in the exception.
34 * @return {function(...string)} Function that throws.
35 */
36 util.ferr = function(msg) {
37 return function() {
38 var ary = Array.apply(null, arguments);
39 throw new Error(msg + ': ' + ary.join(', '));
40 };
41 };
42
43 /**
44 * Install a sensible toString() on the FileError object.
45 *
46 * FileError.prototype.code is a numeric code describing the cause of the
47 * error. The FileError constructor has a named property for each possible
48 * error code, but provides no way to map the code to the named property.
49 * This toString() implementation fixes that.
50 */
51 util.installFileErrorToString = function() {
52 FileError.prototype.toString = function() {
53 return '[object FileError: ' + util.getFileErrorMnemonic(this.code) + ']';
54 };
55 };
56
57 /**
58 * @param {number} code The file error code.
59 * @return {string} The file error mnemonic.
60 */
61 util.getFileErrorMnemonic = function(code) {
62 for (var key in FileError) {
63 if (key.search(/_ERR$/) != -1 && FileError[key] == code)
64 return key;
65 }
66
67 return code;
68 };
69
70 /**
71 * @param {number} code File error code (from FileError object).
72 * @return {string} Translated file error string.
73 */
74 util.getFileErrorString = function(code) {
75 for (var key in FileError) {
76 var match = /(.*)_ERR$/.exec(key);
77 if (match && FileError[key] == code) {
78 // This would convert 1 to 'NOT_FOUND'.
79 code = match[1];
80 break;
81 }
82 }
83 console.warn('File error: ' + code);
84 return loadTimeData.getString('FILE_ERROR_' + code) ||
85 loadTimeData.getString('FILE_ERROR_GENERIC');
86 };
87
88 /**
89 * @param {string} str String to escape.
90 * @return {string} Escaped string.
91 */
92 util.htmlEscape = function(str) {
93 return str.replace(/[<>&]/g, function(entity) {
94 switch (entity) {
95 case '<': return '&lt;';
96 case '>': return '&gt;';
97 case '&': return '&amp;';
98 }
99 });
100 };
101
102 /**
103 * @param {string} str String to unescape.
104 * @return {string} Unescaped string.
105 */
106 util.htmlUnescape = function(str) {
107 return str.replace(/&(lt|gt|amp);/g, function(entity) {
108 switch (entity) {
109 case '&lt;': return '<';
110 case '&gt;': return '>';
111 case '&amp;': return '&';
112 }
113 });
114 };
115
116 /**
117 * Iterates the entries contained by dirEntry, and invokes callback once for
118 * each entry. On completion, successCallback will be invoked.
119 *
120 * @param {DirectoryEntry} dirEntry The entry of the directory.
121 * @param {function(Entry, function())} callback Invoked for each entry.
122 * @param {function()} successCallback Invoked on completion.
123 * @param {function(FileError)} errorCallback Invoked if an error is found on
124 * directory entry reading.
125 */
126 util.forEachDirEntry = function(
127 dirEntry, callback, successCallback, errorCallback) {
128 var reader = dirEntry.createReader();
129 var iterate = function() {
130 reader.readEntries(function(entries) {
131 if (entries.length == 0) {
132 successCallback();
133 return;
134 }
135
136 AsyncUtil.forEach(
137 entries,
138 function(forEachCallback, entry) {
139 // Do not pass index nor entries.
140 callback(entry, forEachCallback);
141 },
142 iterate);
143 }, errorCallback);
144 };
145 iterate();
146 };
147
148 /**
149 * Reads contents of directory.
150 * @param {DirectoryEntry} root Root entry.
151 * @param {string} path Directory path.
152 * @param {function(Array.<Entry>)} callback List of entries passed to callback.
153 */
154 util.readDirectory = function(root, path, callback) {
155 var onError = function(e) {
156 callback([], e);
157 };
158 root.getDirectory(path, {create: false}, function(entry) {
159 var reader = entry.createReader();
160 var r = [];
161 var readNext = function() {
162 reader.readEntries(function(results) {
163 if (results.length == 0) {
164 callback(r, null);
165 return;
166 }
167 r.push.apply(r, results);
168 readNext();
169 }, onError);
170 };
171 readNext();
172 }, onError);
173 };
174
175 /**
176 * Utility function to resolve multiple directories with a single call.
177 *
178 * The successCallback will be invoked once for each directory object
179 * found. The errorCallback will be invoked once for each
180 * path that could not be resolved.
181 *
182 * The successCallback is invoked with a null entry when all paths have
183 * been processed.
184 *
185 * @param {DirEntry} dirEntry The base directory.
186 * @param {Object} params The parameters to pass to the underlying
187 * getDirectory calls.
188 * @param {Array.<string>} paths The list of directories to resolve.
189 * @param {function(!DirEntry)} successCallback The function to invoke for
190 * each DirEntry found. Also invoked once with null at the end of the
191 * process.
192 * @param {function(FileError)} errorCallback The function to invoke
193 * for each path that cannot be resolved.
194 */
195 util.getDirectories = function(dirEntry, params, paths, successCallback,
196 errorCallback) {
197
198 // Copy the params array, since we're going to destroy it.
199 params = [].slice.call(params);
200
201 var onComplete = function() {
202 successCallback(null);
203 };
204
205 var getNextDirectory = function() {
206 var path = paths.shift();
207 if (!path)
208 return onComplete();
209
210 dirEntry.getDirectory(
211 path, params,
212 function(entry) {
213 successCallback(entry);
214 getNextDirectory();
215 },
216 function(err) {
217 errorCallback(err);
218 getNextDirectory();
219 });
220 };
221
222 getNextDirectory();
223 };
224
225 /**
226 * Utility function to resolve multiple files with a single call.
227 *
228 * The successCallback will be invoked once for each directory object
229 * found. The errorCallback will be invoked once for each
230 * path that could not be resolved.
231 *
232 * The successCallback is invoked with a null entry when all paths have
233 * been processed.
234 *
235 * @param {DirEntry} dirEntry The base directory.
236 * @param {Object} params The parameters to pass to the underlying
237 * getFile calls.
238 * @param {Array.<string>} paths The list of files to resolve.
239 * @param {function(!FileEntry)} successCallback The function to invoke for
240 * each FileEntry found. Also invoked once with null at the end of the
241 * process.
242 * @param {function(FileError)} errorCallback The function to invoke
243 * for each path that cannot be resolved.
244 */
245 util.getFiles = function(dirEntry, params, paths, successCallback,
246 errorCallback) {
247 // Copy the params array, since we're going to destroy it.
248 params = [].slice.call(params);
249
250 var onComplete = function() {
251 successCallback(null);
252 };
253
254 var getNextFile = function() {
255 var path = paths.shift();
256 if (!path)
257 return onComplete();
258
259 dirEntry.getFile(
260 path, params,
261 function(entry) {
262 successCallback(entry);
263 getNextFile();
264 },
265 function(err) {
266 errorCallback(err);
267 getNextFile();
268 });
269 };
270
271 getNextFile();
272 };
273
274 /**
275 * Resolve a path to either a DirectoryEntry or a FileEntry, regardless of
276 * whether the path is a directory or file.
277 *
278 * @param {DirectoryEntry} root The root of the filesystem to search.
279 * @param {string} path The path to be resolved.
280 * @param {function(Entry)} resultCallback Called back when a path is
281 * successfully resolved. Entry will be either a DirectoryEntry or
282 * a FileEntry.
283 * @param {function(FileError)} errorCallback Called back if an unexpected
284 * error occurs while resolving the path.
285 */
286 util.resolvePath = function(root, path, resultCallback, errorCallback) {
287 if (path == '' || path == '/') {
288 resultCallback(root);
289 return;
290 }
291
292 root.getFile(
293 path, {create: false},
294 resultCallback,
295 function(err) {
296 if (err.code == FileError.TYPE_MISMATCH_ERR) {
297 // Bah. It's a directory, ask again.
298 root.getDirectory(
299 path, {create: false},
300 resultCallback,
301 errorCallback);
302 } else {
303 errorCallback(err);
304 }
305 });
306 };
307
308 /**
309 * Locate the file referred to by path, creating directories or the file
310 * itself if necessary.
311 * @param {DirEntry} root The root entry.
312 * @param {string} path The file path.
313 * @param {function(FileEntry)} successCallback The callback.
314 * @param {function(FileError)} errorCallback The callback.
315 */
316 util.getOrCreateFile = function(root, path, successCallback, errorCallback) {
317 var dirname = null;
318 var basename = null;
319
320 var onDirFound = function(dirEntry) {
321 dirEntry.getFile(basename, { create: true },
322 successCallback, errorCallback);
323 };
324
325 var i = path.lastIndexOf('/');
326 if (i > -1) {
327 dirname = path.substr(0, i);
328 basename = path.substr(i + 1);
329 } else {
330 basename = path;
331 }
332
333 if (!dirname) {
334 onDirFound(root);
335 return;
336 }
337
338 util.getOrCreateDirectory(root, dirname, onDirFound, errorCallback);
339 };
340
341 /**
342 * Locate the directory referred to by path, creating directories along the
343 * way.
344 * @param {DirEntry} root The root entry.
345 * @param {string} path The directory path.
346 * @param {function(FileEntry)} successCallback The callback.
347 * @param {function(FileError)} errorCallback The callback.
348 */
349 util.getOrCreateDirectory = function(root, path, successCallback,
350 errorCallback) {
351 var names = path.split('/');
352
353 var getOrCreateNextName = function(dir) {
354 if (!names.length)
355 return successCallback(dir);
356
357 var name;
358 do {
359 name = names.shift();
360 } while (!name || name == '.');
361
362 dir.getDirectory(name, { create: true }, getOrCreateNextName,
363 errorCallback);
364 };
365
366 getOrCreateNextName(root);
367 };
368
369 /**
370 * Renames the entry to newName.
371 * @param {Entry} entry The entry to be renamed.
372 * @param {string} newName The new name.
373 * @param {function(Entry)} successCallback Callback invoked when the rename
374 * is successfully done.
375 * @param {function(FileError)} errorCallback Callback invoked when an error
376 * is found.
377 */
378 util.rename = function(entry, newName, successCallback, errorCallback) {
379 entry.getParent(function(parent) {
380 // Before moving, we need to check if there is an existing entry at
381 // parent/newName, since moveTo will overwrite it.
382 // Note that this way has some timing issue. After existing check,
383 // a new entry may be create on background. However, there is no way not to
384 // overwrite the existing file, unfortunately. The risk should be low,
385 // assuming the unsafe period is very short.
386 (entry.isFile ? parent.getFile : parent.getDirectory).call(
387 parent, newName, {create: false},
388 function(entry) {
389 // The entry with the name already exists.
390 errorCallback(util.createFileError(FileError.PATH_EXISTS_ERR));
391 },
392 function(error) {
393 if (error.code != FileError.NOT_FOUND_ERR) {
394 // Unexpected error is found.
395 errorCallback(error);
396 return;
397 }
398
399 // No existing entry is found.
400 entry.moveTo(parent, newName, successCallback, errorCallback);
401 });
402 }, errorCallback);
403 };
404
405 /**
406 * Remove a file or a directory.
407 * @param {Entry} entry The entry to remove.
408 * @param {function()} onSuccess The success callback.
409 * @param {function(FileError)} onError The error callback.
410 */
411 util.removeFileOrDirectory = function(entry, onSuccess, onError) {
412 if (entry.isDirectory)
413 entry.removeRecursively(onSuccess, onError);
414 else
415 entry.remove(onSuccess, onError);
416 };
417
418 /**
419 * Checks if an entry exists at |relativePath| in |dirEntry|.
420 * If exists, tries to deduplicate the path by inserting parenthesized number,
421 * such as " (1)", before the extension. If it still exists, tries the
422 * deduplication again by increasing the number up to 10 times.
423 * For example, suppose "file.txt" is given, "file.txt", "file (1).txt",
424 * "file (2).txt", ..., "file (9).txt" will be tried.
425 *
426 * @param {DirectoryEntry} dirEntry The target directory entry.
427 * @param {string} relativePath The path to be deduplicated.
428 * @param {function(string)} onSuccess Called with the deduplicated path on
429 * success.
430 * @param {function(FileError)} onError Called on error.
431 */
432 util.deduplicatePath = function(dirEntry, relativePath, onSuccess, onError) {
433 // The trial is up to 10.
434 var MAX_RETRY = 10;
435
436 // Crack the path into three part. The parenthesized number (if exists) will
437 // be replaced by incremented number for retry. For example, suppose
438 // |relativePath| is "file (10).txt", the second check path will be
439 // "file (11).txt".
440 var match = /^(.*?)(?: \((\d+)\))?(\.[^.]*?)?$/.exec(relativePath);
441 var prefix = match[1];
442 var copyNumber = match[2] ? parseInt(match[2], 10) : 0;
443 var ext = match[3] ? match[3] : '';
444
445 // The path currently checking the existence.
446 var trialPath = relativePath;
447
448 var onNotResolved = function(err) {
449 // We expect to be unable to resolve the target file, since we're going
450 // to create it during the copy. However, if the resolve fails with
451 // anything other than NOT_FOUND, that's trouble.
452 if (err.code != FileError.NOT_FOUND_ERR) {
453 onError(err);
454 return;
455 }
456
457 // Found a path that doesn't exist.
458 onSuccess(trialPath);
459 }
460
461 var numRetry = MAX_RETRY;
462 var onResolved = function(entry) {
463 if (--numRetry == 0) {
464 // Hit the limit of the number of retrial.
465 // Note that we cannot create FileError object directly, so here we use
466 // Object.create instead.
467 onError(util.createFileError(FileError.PATH_EXISTS_ERR));
468 return;
469 }
470
471 ++copyNumber;
472 trialPath = prefix + ' (' + copyNumber + ')' + ext;
473 util.resolvePath(dirEntry, trialPath, onResolved, onNotResolved);
474 };
475
476 // Check to see if the target exists.
477 util.resolvePath(dirEntry, trialPath, onResolved, onNotResolved);
478 };
479
480 /**
481 * Convert a number of bytes into a human friendly format, using the correct
482 * number separators.
483 *
484 * @param {number} bytes The number of bytes.
485 * @return {string} Localized string.
486 */
487 util.bytesToString = function(bytes) {
488 // Translation identifiers for size units.
489 var UNITS = ['SIZE_BYTES',
490 'SIZE_KB',
491 'SIZE_MB',
492 'SIZE_GB',
493 'SIZE_TB',
494 'SIZE_PB'];
495
496 // Minimum values for the units above.
497 var STEPS = [0,
498 Math.pow(2, 10),
499 Math.pow(2, 20),
500 Math.pow(2, 30),
501 Math.pow(2, 40),
502 Math.pow(2, 50)];
503
504 var str = function(n, u) {
505 // TODO(rginda): Switch to v8Locale's number formatter when it's
506 // available.
507 return strf(u, n.toLocaleString());
508 };
509
510 var fmt = function(s, u) {
511 var rounded = Math.round(bytes / s * 10) / 10;
512 return str(rounded, u);
513 };
514
515 // Less than 1KB is displayed like '80 bytes'.
516 if (bytes < STEPS[1]) {
517 return str(bytes, UNITS[0]);
518 }
519
520 // Up to 1MB is displayed as rounded up number of KBs.
521 if (bytes < STEPS[2]) {
522 var rounded = Math.ceil(bytes / STEPS[1]);
523 return str(rounded, UNITS[1]);
524 }
525
526 // This loop index is used outside the loop if it turns out |bytes|
527 // requires the largest unit.
528 var i;
529
530 for (i = 2 /* MB */; i < UNITS.length - 1; i++) {
531 if (bytes < STEPS[i + 1])
532 return fmt(STEPS[i], UNITS[i]);
533 }
534
535 return fmt(STEPS[i], UNITS[i]);
536 };
537
538 /**
539 * Utility function to read specified range of bytes from file
540 * @param {File} file The file to read.
541 * @param {number} begin Starting byte(included).
542 * @param {number} end Last byte(excluded).
543 * @param {function(File, Uint8Array)} callback Callback to invoke.
544 * @param {function(FileError)} onError Error handler.
545 */
546 util.readFileBytes = function(file, begin, end, callback, onError) {
547 var fileReader = new FileReader();
548 fileReader.onerror = onError;
549 fileReader.onloadend = function() {
550 callback(file, new ByteReader(fileReader.result));
551 };
552 fileReader.readAsArrayBuffer(file.slice(begin, end));
553 };
554
555 /**
556 * Write a blob to a file.
557 * Truncates the file first, so the previous content is fully overwritten.
558 * @param {FileEntry} entry File entry.
559 * @param {Blob} blob The blob to write.
560 * @param {function(Event)} onSuccess Completion callback. The first argument is
561 * a 'writeend' event.
562 * @param {function(FileError)} onError Error handler.
563 */
564 util.writeBlobToFile = function(entry, blob, onSuccess, onError) {
565 var truncate = function(writer) {
566 writer.onerror = onError;
567 writer.onwriteend = write.bind(null, writer);
568 writer.truncate(0);
569 };
570
571 var write = function(writer) {
572 writer.onwriteend = onSuccess;
573 writer.write(blob);
574 };
575
576 entry.createWriter(truncate, onError);
577 };
578
579 /**
580 * Returns a string '[Ctrl-][Alt-][Shift-][Meta-]' depending on the event
581 * modifiers. Convenient for writing out conditions in keyboard handlers.
582 *
583 * @param {Event} event The keyboard event.
584 * @return {string} Modifiers.
585 */
586 util.getKeyModifiers = function(event) {
587 return (event.ctrlKey ? 'Ctrl-' : '') +
588 (event.altKey ? 'Alt-' : '') +
589 (event.shiftKey ? 'Shift-' : '') +
590 (event.metaKey ? 'Meta-' : '');
591 };
592
593 /**
594 * @param {HTMLElement} element Element to transform.
595 * @param {Object} transform Transform object,
596 * contains scaleX, scaleY and rotate90 properties.
597 */
598 util.applyTransform = function(element, transform) {
599 element.style.webkitTransform =
600 transform ? 'scaleX(' + transform.scaleX + ') ' +
601 'scaleY(' + transform.scaleY + ') ' +
602 'rotate(' + transform.rotate90 * 90 + 'deg)' :
603 '';
604 };
605
606 /**
607 * Makes filesystem: URL from the path.
608 * @param {string} path File or directory path.
609 * @return {string} URL.
610 */
611 util.makeFilesystemUrl = function(path) {
612 path = path.split('/').map(encodeURIComponent).join('/');
613 var prefix = 'external';
614 return 'filesystem:' + document.location.origin + '/' + prefix + path;
615 };
616
617 /**
618 * Extracts path from filesystem: URL.
619 * @param {string} url Filesystem URL.
620 * @return {string} The path.
621 */
622 util.extractFilePath = function(url) {
623 var match =
624 /^filesystem:[\w-]*:\/\/[\w]*\/(external|persistent|temporary)(\/.*)$/.
625 exec(url);
626 var path = match && match[2];
627 if (!path) return null;
628 return decodeURIComponent(path);
629 };
630
631 /**
632 * Traverses a directory tree whose root is the given entry, and invokes
633 * callback for each entry. Upon completion, successCallback will be called.
634 * On error, errorCallback will be called.
635 *
636 * @param {Entry} entry The root entry.
637 * @param {function(Entry):boolean} callback Callback invoked for each entry.
638 * If this returns false, entries under it won't be traversed. Note that
639 * its siblings (and their children) will be still traversed.
640 * @param {function()} successCallback Called upon successful completion.
641 * @param {function(error)} errorCallback Called upon error.
642 */
643 util.traverseTree = function(entry, callback, successCallback, errorCallback) {
644 if (!callback(entry)) {
645 successCallback();
646 return;
647 }
648
649 util.forEachDirEntry(
650 entry,
651 function(child, iterationCallback) {
652 util.traverseTree(child, callback, iterationCallback, errorCallback);
653 },
654 successCallback,
655 errorCallback);
656 };
657
658 /**
659 * A shortcut function to create a child element with given tag and class.
660 *
661 * @param {HTMLElement} parent Parent element.
662 * @param {string=} opt_className Class name.
663 * @param {string=} opt_tag Element tag, DIV is omitted.
664 * @return {Element} Newly created element.
665 */
666 util.createChild = function(parent, opt_className, opt_tag) {
667 var child = parent.ownerDocument.createElement(opt_tag || 'div');
668 if (opt_className)
669 child.className = opt_className;
670 parent.appendChild(child);
671 return child;
672 };
673
674 /**
675 * Update the app state.
676 *
677 * @param {string} path Path to be put in the address bar after the hash.
678 * If null the hash is left unchanged.
679 * @param {string|Object=} opt_param Search parameter. Used directly if string,
680 * stringified if object. If omitted the search query is left unchanged.
681 */
682 util.updateAppState = function(path, opt_param) {
683 window.appState = window.appState || {};
684 if (typeof opt_param == 'string')
685 window.appState.params = {};
686 else if (typeof opt_param == 'object')
687 window.appState.params = opt_param;
688 if (path)
689 window.appState.defaultPath = path;
690 util.saveAppState();
691 return;
692 };
693
694 /**
695 * Return a translated string.
696 *
697 * Wrapper function to make dealing with translated strings more concise.
698 * Equivalent to loadTimeData.getString(id).
699 *
700 * @param {string} id The id of the string to return.
701 * @return {string} The translated string.
702 */
703 function str(id) {
704 return loadTimeData.getString(id);
705 }
706
707 /**
708 * Return a translated string with arguments replaced.
709 *
710 * Wrapper function to make dealing with translated strings more concise.
711 * Equivalent to loadTimeData.getStringF(id, ...).
712 *
713 * @param {string} id The id of the string to return.
714 * @param {...string} var_args The values to replace into the string.
715 * @return {string} The translated string with replaced values.
716 */
717 function strf(id, var_args) {
718 return loadTimeData.getStringF.apply(loadTimeData, arguments);
719 }
720
721 /**
722 * Adapter object that abstracts away the the difference between Chrome app APIs
723 * v1 and v2. Is only necessary while the migration to v2 APIs is in progress.
724 * TODO(mtomasz): Clean up this. crbug.com/240606.
725 */
726 util.platform = {
727 /**
728 * @return {boolean} True if Files.app is running as an open files or a select
729 * folder dialog. False otherwise.
730 */
731 runningInBrowser: function() {
732 return !window.appID;
733 },
734
735 /**
736 * @param {function(Object)} callback Function accepting a preference map.
737 */
738 getPreferences: function(callback) {
739 chrome.storage.local.get(callback);
740 },
741
742 /**
743 * @param {string} key Preference name.
744 * @param {function(string)} callback Function accepting the preference value.
745 */
746 getPreference: function(key, callback) {
747 chrome.storage.local.get(key, function(items) {
748 callback(items[key]);
749 });
750 },
751
752 /**
753 * @param {string} key Preference name.
754 * @param {string|Object} value Preference value.
755 * @param {function()=} opt_callback Completion callback.
756 */
757 setPreference: function(key, value, opt_callback) {
758 if (typeof value != 'string')
759 value = JSON.stringify(value);
760
761 var items = {};
762 items[key] = value;
763 chrome.storage.local.set(items, opt_callback);
764 }
765 };
766
767 /**
768 * Attach page load handler.
769 * @param {function()} handler Application-specific load handler.
770 */
771 util.addPageLoadHandler = function(handler) {
772 document.addEventListener('DOMContentLoaded', function() {
773 handler();
774 });
775 };
776
777 /**
778 * Save app launch data to the local storage.
779 */
780 util.saveAppState = function() {
781 if (window.appState)
782 util.platform.setPreference(window.appID, window.appState);
783 };
784
785 /**
786 * AppCache is a persistent timestamped key-value storage backed by
787 * HTML5 local storage.
788 *
789 * It is not designed for frequent access. In order to avoid costly
790 * localStorage iteration all data is kept in a single localStorage item.
791 * There is no in-memory caching, so concurrent access is _almost_ safe.
792 *
793 * TODO(kaznacheev) Reimplement this based on Indexed DB.
794 */
795 util.AppCache = function() {};
796
797 /**
798 * Local storage key.
799 */
800 util.AppCache.KEY = 'AppCache';
801
802 /**
803 * Max number of items.
804 */
805 util.AppCache.CAPACITY = 100;
806
807 /**
808 * Default lifetime.
809 */
810 util.AppCache.LIFETIME = 30 * 24 * 60 * 60 * 1000; // 30 days.
811
812 /**
813 * @param {string} key Key.
814 * @param {function(number)} callback Callback accepting a value.
815 */
816 util.AppCache.getValue = function(key, callback) {
817 util.AppCache.read_(function(map) {
818 var entry = map[key];
819 callback(entry && entry.value);
820 });
821 };
822
823 /**
824 * Update the cache.
825 *
826 * @param {string} key Key.
827 * @param {string} value Value. Remove the key if value is null.
828 * @param {number=} opt_lifetime Maximum time to keep an item (in milliseconds).
829 */
830 util.AppCache.update = function(key, value, opt_lifetime) {
831 util.AppCache.read_(function(map) {
832 if (value != null) {
833 map[key] = {
834 value: value,
835 expire: Date.now() + (opt_lifetime || util.AppCache.LIFETIME)
836 };
837 } else if (key in map) {
838 delete map[key];
839 } else {
840 return; // Nothing to do.
841 }
842 util.AppCache.cleanup_(map);
843 util.AppCache.write_(map);
844 });
845 };
846
847 /**
848 * @param {function(Object)} callback Callback accepting a map of timestamped
849 * key-value pairs.
850 * @private
851 */
852 util.AppCache.read_ = function(callback) {
853 util.platform.getPreference(util.AppCache.KEY, function(json) {
854 if (json) {
855 try {
856 callback(JSON.parse(json));
857 } catch (e) {
858 // The local storage item somehow got messed up, start fresh.
859 }
860 }
861 callback({});
862 });
863 };
864
865 /**
866 * @param {Object} map A map of timestamped key-value pairs.
867 * @private
868 */
869 util.AppCache.write_ = function(map) {
870 util.platform.setPreference(util.AppCache.KEY, JSON.stringify(map));
871 };
872
873 /**
874 * Remove over-capacity and obsolete items.
875 *
876 * @param {Object} map A map of timestamped key-value pairs.
877 * @private
878 */
879 util.AppCache.cleanup_ = function(map) {
880 // Sort keys by ascending timestamps.
881 var keys = [];
882 for (var key in map) {
883 if (map.hasOwnProperty(key))
884 keys.push(key);
885 }
886 keys.sort(function(a, b) { return map[a].expire > map[b].expire });
887
888 var cutoff = Date.now();
889
890 var obsolete = 0;
891 while (obsolete < keys.length &&
892 map[keys[obsolete]].expire < cutoff) {
893 obsolete++;
894 }
895
896 var overCapacity = Math.max(0, keys.length - util.AppCache.CAPACITY);
897
898 var itemsToDelete = Math.max(obsolete, overCapacity);
899 for (var i = 0; i != itemsToDelete; i++) {
900 delete map[keys[i]];
901 }
902 };
903
904 /**
905 * Load an image.
906 *
907 * @param {Image} image Image element.
908 * @param {string} url Source url.
909 * @param {Object=} opt_options Hash array of options, eg. width, height,
910 * maxWidth, maxHeight, scale, cache.
911 * @param {function()=} opt_isValid Function returning false iff the task
912 * is not valid and should be aborted.
913 * @return {?number} Task identifier or null if fetched immediately from
914 * cache.
915 */
916 util.loadImage = function(image, url, opt_options, opt_isValid) {
917 return ImageLoaderClient.loadToImage(url,
918 image,
919 opt_options || {},
920 function() {},
921 function() { image.onerror(); },
922 opt_isValid);
923 };
924
925 /**
926 * Cancels loading an image.
927 * @param {number} taskId Task identifier returned by util.loadImage().
928 */
929 util.cancelLoadImage = function(taskId) {
930 ImageLoaderClient.getInstance().cancel(taskId);
931 };
932
933 /**
934 * Finds proerty descriptor in the object prototype chain.
935 * @param {Object} object The object.
936 * @param {string} propertyName The property name.
937 * @return {Object} Property descriptor.
938 */
939 util.findPropertyDescriptor = function(object, propertyName) {
940 for (var p = object; p; p = Object.getPrototypeOf(p)) {
941 var d = Object.getOwnPropertyDescriptor(p, propertyName);
942 if (d)
943 return d;
944 }
945 return null;
946 };
947
948 /**
949 * Calls inherited property setter (useful when property is
950 * overriden).
951 * @param {Object} object The object.
952 * @param {string} propertyName The property name.
953 * @param {*} value Value to set.
954 */
955 util.callInheritedSetter = function(object, propertyName, value) {
956 var d = util.findPropertyDescriptor(Object.getPrototypeOf(object),
957 propertyName);
958 d.set.call(object, value);
959 };
960
961 /**
962 * Returns true if the board of the device matches the given prefix.
963 * @param {string} boardPrefix The board prefix to match against.
964 * (ex. "x86-mario". Prefix is used as the actual board name comes with
965 * suffix like "x86-mario-something".
966 * @return {boolean} True if the board of the device matches the given prefix.
967 */
968 util.boardIs = function(boardPrefix) {
969 // The board name should be lower-cased, but making it case-insensitive for
970 // backward compatibility just in case.
971 var board = str('CHROMEOS_RELEASE_BOARD');
972 var pattern = new RegExp('^' + boardPrefix, 'i');
973 return board.match(pattern) != null;
974 };
975
976 /**
977 * Adds an isFocused method to the current window object.
978 */
979 util.addIsFocusedMethod = function() {
980 var focused = true;
981
982 window.addEventListener('focus', function() {
983 focused = true;
984 });
985
986 window.addEventListener('blur', function() {
987 focused = false;
988 });
989
990 /**
991 * @return {boolean} True if focused.
992 */
993 window.isFocused = function() {
994 return focused;
995 };
996 };
997
998 /**
999 * Makes a redirect to the specified Files.app's window from another window.
1000 * @param {number} id Window id.
1001 * @param {string} url Target url.
1002 * @return {boolean} True if the window has been found. False otherwise.
1003 */
1004 util.redirectMainWindow = function(id, url) {
1005 // TODO(mtomasz): Implement this for Apps V2, once the photo importer is
1006 // restored.
1007 return false;
1008 };
1009
1010 /**
1011 * Checks, if the Files.app's window is in a full screen mode.
1012 *
1013 * @param {AppWindow} appWindow App window to be maximized.
1014 * @return {boolean} True if the full screen mode is enabled.
1015 */
1016 util.isFullScreen = function(appWindow) {
1017 if (appWindow) {
1018 return appWindow.isFullscreen();
1019 } else {
1020 console.error('App window not passed. Unable to check status of ' +
1021 'the full screen mode.');
1022 return false;
1023 }
1024 };
1025
1026 /**
1027 * Toggles the full screen mode.
1028 *
1029 * @param {AppWindow} appWindow App window to be maximized.
1030 * @param {boolean} enabled True for enabling, false for disabling.
1031 */
1032 util.toggleFullScreen = function(appWindow, enabled) {
1033 if (appWindow) {
1034 if (enabled)
1035 appWindow.fullscreen();
1036 else
1037 appWindow.restore();
1038 return;
1039 }
1040
1041 console.error(
1042 'App window not passed. Unable to toggle the full screen mode.');
1043 };
1044
1045 /**
1046 * The type of a file operation.
1047 * @enum {string}
1048 */
1049 util.FileOperationType = {
1050 COPY: 'COPY',
1051 MOVE: 'MOVE',
1052 ZIP: 'ZIP',
1053 };
1054
1055 /**
1056 * The type of a file operation error.
1057 * @enum {number}
1058 */
1059 util.FileOperationErrorType = {
1060 UNEXPECTED_SOURCE_FILE: 0,
1061 TARGET_EXISTS: 1,
1062 FILESYSTEM_ERROR: 2,
1063 };
1064
1065 /**
1066 * The kind of an entry changed event.
1067 * @enum {number}
1068 */
1069 util.EntryChangedKind = {
1070 CREATED: 0,
1071 DELETED: 1,
1072 };
1073
1074 /**
1075 * @param {DirectoryEntry|Object} entry DirectoryEntry to be checked.
1076 * @return {boolean} True if the given entry is fake.
1077 */
1078 util.isFakeDirectoryEntry = function(entry) {
1079 // Currently, fake entry doesn't support createReader.
1080 return !('createReader' in entry);
1081 };
1082
1083 /**
1084 * Creates a FileError instance with given code.
1085 * Note that we cannot create FileError instance by "new FileError(code)",
1086 * unfortunately, so here we use Object.create.
1087 * @param {number} code Error code for the FileError.
1088 * @return {FileError} FileError instance
1089 */
1090 util.createFileError = function(code) {
1091 return Object.create(FileError.prototype, {
1092 code: { get: function() { return code; } }
1093 });
1094 };
1095
1096 /**
1097 * @param {Entry|Object} entry1 The entry to be compared. Can be a fake.
1098 * @param {Entry|Object} entry2 The entry to be compared. Can be a fake.
1099 * @return {boolean} True if the both entry represents a same file or directory.
1100 */
1101 util.isSameEntry = function(entry1, entry2) {
1102 // Currently, we can assume there is only one root.
1103 // When we support multi-file system, we need to look at filesystem, too.
1104 return entry1 === null ? entry2 === null : entry1.fullPath == entry2.fullPath;
1105 };
1106
1107 /**
1108 * @param {Entry|Object} parent The parent entry. Can be a fake.
1109 * @param {Entry|Object} child The child entry. Can be a fake.
1110 * @return {boolean} True if parent entry is actualy the parent of the child
1111 * entry.
1112 */
1113 util.isParentEntry = function(parent, child) {
1114 // Currently, we can assume there is only one root.
1115 // When we support multi-file system, we need to look at filesystem, too.
1116 return PathUtil.isParentPath(parent.fullPath, child.fullPath);
1117 };
1118
1119 /**
1120 * Views files in the browser.
1121 *
1122 * @param {Array.<string>} urls URLs of files to view.
1123 * @param {function(bool)} callback Callback notifying success or not.
1124 */
1125 util.viewFilesInBrowser = function(urls, callback) {
1126 var taskId = chrome.runtime.id + '|file|view-in-browser';
1127 chrome.fileBrowserPrivate.executeTask(taskId, urls, callback);
1128 };
1129
1130 /**
1131 * Visit the URL.
1132 *
1133 * If the browser is opening, the url is opened in a new tag, otherwise the url
1134 * is opened in a new window.
1135 *
1136 * @param {string} url URL to visit.
1137 */
1138 util.visitURL = function(url) {
1139 var params = {url: url};
1140 chrome.tabs.create(params, function() {
1141 if (chrome.runtime.lastError)
1142 chrome.windows.create(params);
1143 });
1144 };
1145
1146 /**
1147 * Returns normalized current locale, or default locale - 'en'.
1148 * @return {string} Current locale
1149 */
1150 util.getCurrentLocaleOrDefault = function() {
1151 // chrome.i18n.getMessage('@@ui_locale') can't be used in packed app.
1152 // Instead, we pass it from C++-side with strings.
1153 return str('UI_LOCALE') || 'en';
1154 };
1155
1156 /**
1157 * Error type of VolumeManager.
1158 * @enum {string}
1159 */
1160 util.VolumeError = Object.freeze({
1161 /* Internal errors */
1162 NOT_MOUNTED: 'not_mounted',
1163 TIMEOUT: 'timeout',
1164
1165 /* System events */
1166 UNKNOWN: 'error_unknown',
1167 INTERNAL: 'error_internal',
1168 UNKNOWN_FILESYSTEM: 'error_unknown_filesystem',
1169 UNSUPPORTED_FILESYSTEM: 'error_unsupported_filesystem',
1170 INVALID_ARCHIVE: 'error_invalid_archive',
1171 AUTHENTICATION: 'error_authentication',
1172 PATH_UNMOUNTED: 'error_path_unmounted'
1173 });
1174
1175 /**
1176 * List of connection types of drive.
1177 *
1178 * Keep this in sync with the kDriveConnectionType* constants in
1179 * file_browser_private_api.cc.
1180 *
1181 * @enum {string}
1182 */
1183 util.DriveConnectionType = Object.freeze({
1184 OFFLINE: 'offline', // Connection is offline or drive is unavailable.
1185 METERED: 'metered', // Connection is metered. Should limit traffic.
1186 ONLINE: 'online' // Connection is online.
1187 });
1188
1189 /**
1190 * List of reasons of DriveConnectionType.
1191 *
1192 * Keep this in sync with the kDriveConnectionReason constants in
1193 * file_browser_private_api.cc.
1194 *
1195 * @enum {string}
1196 */
1197 util.DriveConnectionReason = Object.freeze({
1198 NOT_READY: 'not_ready', // Drive is not ready or authentication is failed.
1199 NO_NETWORK: 'no_network', // Network connection is unavailable.
1200 NO_SERVICE: 'no_service' // Drive service is unavailable.
1201 });
1202
1203 /**
1204 * The type of each volume.
1205 * @enum {string}
1206 */
1207 util.VolumeType = Object.freeze({
1208 DRIVE: 'drive',
1209 DOWNLOADS: 'downloads',
1210 REMOVABLE: 'removable',
1211 ARCHIVE: 'archive'
1212 });
OLDNEW
« no previous file with comments | « chrome/browser/resources/file_manager/js/url_constants.js ('k') | chrome/browser/resources/file_manager/js/volume_manager.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698