1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/b2g/components/FilePicker.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,222 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +/* 1.10 + * No magic constructor behaviour, as is de rigeur for XPCOM. 1.11 + * If you must perform some initialization, and it could possibly fail (even 1.12 + * due to an out-of-memory condition), you should use an Init method, which 1.13 + * can convey failure appropriately (thrown exception in JS, 1.14 + * NS_FAILED(nsresult) return in C++). 1.15 + * 1.16 + * In JS, you can actually cheat, because a thrown exception will cause the 1.17 + * CreateInstance call to fail in turn, but not all languages are so lucky. 1.18 + * (Though ANSI C++ provides exceptions, they are verboten in Mozilla code 1.19 + * for portability reasons -- and even when you're building completely 1.20 + * platform-specific code, you can't throw across an XPCOM method boundary.) 1.21 + */ 1.22 + 1.23 +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; 1.24 + 1.25 +// FIXME: improve this list of filters. 1.26 +const IMAGE_FILTERS = ['image/gif', 'image/jpeg', 'image/pjpeg', 1.27 + 'image/png', 'image/svg+xml', 'image/tiff', 1.28 + 'image/vnd.microsoft.icon']; 1.29 +const VIDEO_FILTERS = ['video/mpeg', 'video/mp4', 'video/ogg', 1.30 + 'video/quicktime', 'video/webm', 'video/x-matroska', 1.31 + 'video/x-ms-wmv', 'video/x-flv']; 1.32 +const AUDIO_FILTERS = ['audio/basic', 'audio/L24', 'audio/mp4', 1.33 + 'audio/mpeg', 'audio/ogg', 'audio/vorbis', 1.34 + 'audio/vnd.rn-realaudio', 'audio/vnd.wave', 1.35 + 'audio/webm']; 1.36 + 1.37 +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); 1.38 +Cu.import("resource://gre/modules/osfile.jsm"); 1.39 + 1.40 +XPCOMUtils.defineLazyServiceGetter(this, 'cpmm', 1.41 + '@mozilla.org/childprocessmessagemanager;1', 1.42 + 'nsIMessageSender'); 1.43 + 1.44 +function FilePicker() { 1.45 +} 1.46 + 1.47 +FilePicker.prototype = { 1.48 + classID: Components.ID('{436ff8f9-0acc-4b11-8ec7-e293efba3141}'), 1.49 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIFilePicker]), 1.50 + 1.51 + /* members */ 1.52 + 1.53 + mParent: undefined, 1.54 + mExtraProps: undefined, 1.55 + mFilterTypes: undefined, 1.56 + mFileEnumerator: undefined, 1.57 + mFilePickerShownCallback: undefined, 1.58 + 1.59 + /* methods */ 1.60 + 1.61 + init: function(parent, title, mode) { 1.62 + this.mParent = parent; 1.63 + this.mExtraProps = {}; 1.64 + this.mFilterTypes = []; 1.65 + this.mMode = mode; 1.66 + 1.67 + if (mode != Ci.nsIFilePicker.modeOpen && 1.68 + mode != Ci.nsIFilePicker.modeOpenMultiple) { 1.69 + throw Cr.NS_ERROR_NOT_IMPLEMENTED; 1.70 + } 1.71 + }, 1.72 + 1.73 + /* readonly attribute nsILocalFile file - not implemented; */ 1.74 + /* readonly attribute nsISimpleEnumerator files - not implemented; */ 1.75 + /* readonly attribute nsIURI fileURL - not implemented; */ 1.76 + 1.77 + get domfiles() { 1.78 + return this.mFilesEnumerator; 1.79 + }, 1.80 + 1.81 + get domfile() { 1.82 + return this.mFilesEnumerator ? this.mFilesEnumerator.mFiles[0] : null; 1.83 + }, 1.84 + 1.85 + get mode() { 1.86 + return this.mMode; 1.87 + }, 1.88 + 1.89 + appendFilters: function(filterMask) { 1.90 + // Ci.nsIFilePicker.filterHTML is not supported 1.91 + // Ci.nsIFilePicker.filterText is not supported 1.92 + 1.93 + if (filterMask & Ci.nsIFilePicker.filterImages) { 1.94 + this.mFilterTypes = this.mFilterTypes.concat(IMAGE_FILTERS); 1.95 + // This property is needed for the gallery app pick activity. 1.96 + this.mExtraProps['nocrop'] = true; 1.97 + } 1.98 + 1.99 + // Ci.nsIFilePicker.filterXML is not supported 1.100 + // Ci.nsIFilePicker.filterXUL is not supported 1.101 + // Ci.nsIFilePicker.filterApps is not supported 1.102 + // Ci.nsIFilePicker.filterAllowURLs is not supported 1.103 + 1.104 + if (filterMask & Ci.nsIFilePicker.filterVideo) { 1.105 + this.mFilterTypes = this.mFilterTypes.concat(VIDEO_FILTERS); 1.106 + } 1.107 + 1.108 + if (filterMask & Ci.nsIFilePicker.filterAudio) { 1.109 + this.mFilterTypes = this.mFilterTypes.concat(AUDIO_FILTERS); 1.110 + } 1.111 + 1.112 + if (filterMask & Ci.nsIFilePicker.filterAll) { 1.113 + // This property is needed for the gallery app pick activity. 1.114 + this.mExtraProps['nocrop'] = true; 1.115 + } 1.116 + }, 1.117 + 1.118 + appendFilter: function(title, extensions) { 1.119 + // pick activity doesn't support extensions 1.120 + }, 1.121 + 1.122 + open: function(aFilePickerShownCallback) { 1.123 + this.mFilePickerShownCallback = aFilePickerShownCallback; 1.124 + 1.125 + cpmm.addMessageListener('file-picked', this); 1.126 + 1.127 + let detail = {}; 1.128 + if (this.mFilterTypes) { 1.129 + detail.type = this.mFilterTypes; 1.130 + } 1.131 + 1.132 + for (let prop in this.mExtraProps) { 1.133 + if (!(prop in detail)) { 1.134 + detail[prop] = this.mExtraProps[prop]; 1.135 + } 1.136 + } 1.137 + 1.138 + cpmm.sendAsyncMessage('file-picker', detail); 1.139 + }, 1.140 + 1.141 + fireSuccess: function(file) { 1.142 + this.mFilesEnumerator = { 1.143 + QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]), 1.144 + 1.145 + mFiles: [file], 1.146 + mIndex: 0, 1.147 + 1.148 + hasMoreElements: function() { 1.149 + return (this.mIndex < this.mFiles.length); 1.150 + }, 1.151 + 1.152 + getNext: function() { 1.153 + if (this.mIndex >= this.mFiles.length) { 1.154 + throw Components.results.NS_ERROR_FAILURE; 1.155 + } 1.156 + return this.mFiles[this.mIndex++]; 1.157 + } 1.158 + }; 1.159 + 1.160 + if (this.mFilePickerShownCallback) { 1.161 + this.mFilePickerShownCallback.done(Ci.nsIFilePicker.returnOK); 1.162 + this.mFilePickerShownCallback = null; 1.163 + } 1.164 + }, 1.165 + 1.166 + fireError: function() { 1.167 + if (this.mFilePickerShownCallback) { 1.168 + this.mFilePickerShownCallback.done(Ci.nsIFilePicker.returnCancel); 1.169 + this.mFilePickerShownCallback = null; 1.170 + } 1.171 + }, 1.172 + 1.173 + receiveMessage: function(message) { 1.174 + if (message.name !== 'file-picked') { 1.175 + return; 1.176 + } 1.177 + 1.178 + cpmm.removeMessageListener('file-picked', this); 1.179 + 1.180 + let data = message.data; 1.181 + if (!data.success || !data.result.blob) { 1.182 + this.fireError(); 1.183 + return; 1.184 + } 1.185 + 1.186 + // The name to be shown can be part of the message, or can be taken from 1.187 + // the DOMFile (if the blob is a DOMFile). 1.188 + let name = data.result.name; 1.189 + if (!name && 1.190 + (data.result.blob instanceof this.mParent.File) && 1.191 + data.result.blob.name) { 1.192 + name = data.result.blob.name; 1.193 + } 1.194 + 1.195 + // Let's try to remove the full path and take just the filename. 1.196 + if (name) { 1.197 + let names = OS.Path.split(name); 1.198 + name = names.components[names.components.length - 1]; 1.199 + } 1.200 + 1.201 + // the fallback is a filename composed by 'blob' + extension. 1.202 + if (!name) { 1.203 + name = 'blob'; 1.204 + if (data.result.blob.type) { 1.205 + let mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); 1.206 + let mimeInfo = mimeSvc.getFromTypeAndExtension(data.result.blob.type, ''); 1.207 + if (mimeInfo) { 1.208 + name += '.' + mimeInfo.primaryExtension; 1.209 + } 1.210 + } 1.211 + } 1.212 + 1.213 + let file = new this.mParent.File(data.result.blob, 1.214 + { name: name, 1.215 + type: data.result.blob.type }); 1.216 + 1.217 + if (file) { 1.218 + this.fireSuccess(file); 1.219 + } else { 1.220 + this.fireError(); 1.221 + } 1.222 + } 1.223 +}; 1.224 + 1.225 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FilePicker]);