Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /*
7 * No magic constructor behaviour, as is de rigeur for XPCOM.
8 * If you must perform some initialization, and it could possibly fail (even
9 * due to an out-of-memory condition), you should use an Init method, which
10 * can convey failure appropriately (thrown exception in JS,
11 * NS_FAILED(nsresult) return in C++).
12 *
13 * In JS, you can actually cheat, because a thrown exception will cause the
14 * CreateInstance call to fail in turn, but not all languages are so lucky.
15 * (Though ANSI C++ provides exceptions, they are verboten in Mozilla code
16 * for portability reasons -- and even when you're building completely
17 * platform-specific code, you can't throw across an XPCOM method boundary.)
18 */
20 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
22 // FIXME: improve this list of filters.
23 const IMAGE_FILTERS = ['image/gif', 'image/jpeg', 'image/pjpeg',
24 'image/png', 'image/svg+xml', 'image/tiff',
25 'image/vnd.microsoft.icon'];
26 const VIDEO_FILTERS = ['video/mpeg', 'video/mp4', 'video/ogg',
27 'video/quicktime', 'video/webm', 'video/x-matroska',
28 'video/x-ms-wmv', 'video/x-flv'];
29 const AUDIO_FILTERS = ['audio/basic', 'audio/L24', 'audio/mp4',
30 'audio/mpeg', 'audio/ogg', 'audio/vorbis',
31 'audio/vnd.rn-realaudio', 'audio/vnd.wave',
32 'audio/webm'];
34 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
35 Cu.import("resource://gre/modules/osfile.jsm");
37 XPCOMUtils.defineLazyServiceGetter(this, 'cpmm',
38 '@mozilla.org/childprocessmessagemanager;1',
39 'nsIMessageSender');
41 function FilePicker() {
42 }
44 FilePicker.prototype = {
45 classID: Components.ID('{436ff8f9-0acc-4b11-8ec7-e293efba3141}'),
46 QueryInterface: XPCOMUtils.generateQI([Ci.nsIFilePicker]),
48 /* members */
50 mParent: undefined,
51 mExtraProps: undefined,
52 mFilterTypes: undefined,
53 mFileEnumerator: undefined,
54 mFilePickerShownCallback: undefined,
56 /* methods */
58 init: function(parent, title, mode) {
59 this.mParent = parent;
60 this.mExtraProps = {};
61 this.mFilterTypes = [];
62 this.mMode = mode;
64 if (mode != Ci.nsIFilePicker.modeOpen &&
65 mode != Ci.nsIFilePicker.modeOpenMultiple) {
66 throw Cr.NS_ERROR_NOT_IMPLEMENTED;
67 }
68 },
70 /* readonly attribute nsILocalFile file - not implemented; */
71 /* readonly attribute nsISimpleEnumerator files - not implemented; */
72 /* readonly attribute nsIURI fileURL - not implemented; */
74 get domfiles() {
75 return this.mFilesEnumerator;
76 },
78 get domfile() {
79 return this.mFilesEnumerator ? this.mFilesEnumerator.mFiles[0] : null;
80 },
82 get mode() {
83 return this.mMode;
84 },
86 appendFilters: function(filterMask) {
87 // Ci.nsIFilePicker.filterHTML is not supported
88 // Ci.nsIFilePicker.filterText is not supported
90 if (filterMask & Ci.nsIFilePicker.filterImages) {
91 this.mFilterTypes = this.mFilterTypes.concat(IMAGE_FILTERS);
92 // This property is needed for the gallery app pick activity.
93 this.mExtraProps['nocrop'] = true;
94 }
96 // Ci.nsIFilePicker.filterXML is not supported
97 // Ci.nsIFilePicker.filterXUL is not supported
98 // Ci.nsIFilePicker.filterApps is not supported
99 // Ci.nsIFilePicker.filterAllowURLs is not supported
101 if (filterMask & Ci.nsIFilePicker.filterVideo) {
102 this.mFilterTypes = this.mFilterTypes.concat(VIDEO_FILTERS);
103 }
105 if (filterMask & Ci.nsIFilePicker.filterAudio) {
106 this.mFilterTypes = this.mFilterTypes.concat(AUDIO_FILTERS);
107 }
109 if (filterMask & Ci.nsIFilePicker.filterAll) {
110 // This property is needed for the gallery app pick activity.
111 this.mExtraProps['nocrop'] = true;
112 }
113 },
115 appendFilter: function(title, extensions) {
116 // pick activity doesn't support extensions
117 },
119 open: function(aFilePickerShownCallback) {
120 this.mFilePickerShownCallback = aFilePickerShownCallback;
122 cpmm.addMessageListener('file-picked', this);
124 let detail = {};
125 if (this.mFilterTypes) {
126 detail.type = this.mFilterTypes;
127 }
129 for (let prop in this.mExtraProps) {
130 if (!(prop in detail)) {
131 detail[prop] = this.mExtraProps[prop];
132 }
133 }
135 cpmm.sendAsyncMessage('file-picker', detail);
136 },
138 fireSuccess: function(file) {
139 this.mFilesEnumerator = {
140 QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
142 mFiles: [file],
143 mIndex: 0,
145 hasMoreElements: function() {
146 return (this.mIndex < this.mFiles.length);
147 },
149 getNext: function() {
150 if (this.mIndex >= this.mFiles.length) {
151 throw Components.results.NS_ERROR_FAILURE;
152 }
153 return this.mFiles[this.mIndex++];
154 }
155 };
157 if (this.mFilePickerShownCallback) {
158 this.mFilePickerShownCallback.done(Ci.nsIFilePicker.returnOK);
159 this.mFilePickerShownCallback = null;
160 }
161 },
163 fireError: function() {
164 if (this.mFilePickerShownCallback) {
165 this.mFilePickerShownCallback.done(Ci.nsIFilePicker.returnCancel);
166 this.mFilePickerShownCallback = null;
167 }
168 },
170 receiveMessage: function(message) {
171 if (message.name !== 'file-picked') {
172 return;
173 }
175 cpmm.removeMessageListener('file-picked', this);
177 let data = message.data;
178 if (!data.success || !data.result.blob) {
179 this.fireError();
180 return;
181 }
183 // The name to be shown can be part of the message, or can be taken from
184 // the DOMFile (if the blob is a DOMFile).
185 let name = data.result.name;
186 if (!name &&
187 (data.result.blob instanceof this.mParent.File) &&
188 data.result.blob.name) {
189 name = data.result.blob.name;
190 }
192 // Let's try to remove the full path and take just the filename.
193 if (name) {
194 let names = OS.Path.split(name);
195 name = names.components[names.components.length - 1];
196 }
198 // the fallback is a filename composed by 'blob' + extension.
199 if (!name) {
200 name = 'blob';
201 if (data.result.blob.type) {
202 let mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
203 let mimeInfo = mimeSvc.getFromTypeAndExtension(data.result.blob.type, '');
204 if (mimeInfo) {
205 name += '.' + mimeInfo.primaryExtension;
206 }
207 }
208 }
210 let file = new this.mParent.File(data.result.blob,
211 { name: name,
212 type: data.result.blob.type });
214 if (file) {
215 this.fireSuccess(file);
216 } else {
217 this.fireError();
218 }
219 }
220 };
222 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FilePicker]);