gecko-dev/b2g/components/FilePicker.js

224 lines
6.9 KiB
JavaScript

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* No magic constructor behaviour, as is de rigeur for XPCOM.
* If you must perform some initialization, and it could possibly fail (even
* due to an out-of-memory condition), you should use an Init method, which
* can convey failure appropriately (thrown exception in JS,
* NS_FAILED(nsresult) return in C++).
*
* In JS, you can actually cheat, because a thrown exception will cause the
* CreateInstance call to fail in turn, but not all languages are so lucky.
* (Though ANSI C++ provides exceptions, they are verboten in Mozilla code
* for portability reasons -- and even when you're building completely
* platform-specific code, you can't throw across an XPCOM method boundary.)
*/
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
// FIXME: improve this list of filters.
const IMAGE_FILTERS = ['image/gif', 'image/jpeg', 'image/pjpeg',
'image/png', 'image/svg+xml', 'image/tiff',
'image/vnd.microsoft.icon'];
const VIDEO_FILTERS = ['video/mpeg', 'video/mp4', 'video/ogg',
'video/quicktime', 'video/webm', 'video/x-matroska',
'video/x-ms-wmv', 'video/x-flv'];
const AUDIO_FILTERS = ['audio/basic', 'audio/L24', 'audio/mp4',
'audio/mpeg', 'audio/ogg', 'audio/vorbis',
'audio/vnd.rn-realaudio', 'audio/vnd.wave',
'audio/webm'];
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import("resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyServiceGetter(this, 'cpmm',
'@mozilla.org/childprocessmessagemanager;1',
'nsIMessageSender');
function FilePicker() {
}
FilePicker.prototype = {
classID: Components.ID('{436ff8f9-0acc-4b11-8ec7-e293efba3141}'),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFilePicker]),
/* members */
mParent: undefined,
mExtraProps: undefined,
mFilterTypes: undefined,
mFileEnumerator: undefined,
mFilePickerShownCallback: undefined,
/* methods */
init: function(parent, title, mode) {
this.mParent = parent;
this.mExtraProps = {};
this.mFilterTypes = [];
this.mMode = mode;
if (mode != Ci.nsIFilePicker.modeOpen &&
mode != Ci.nsIFilePicker.modeOpenMultiple) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
}
},
/* readonly attribute nsILocalFile file - not implemented; */
/* readonly attribute nsISimpleEnumerator files - not implemented; */
/* readonly attribute nsIURI fileURL - not implemented; */
get domFileOrDirectoryEnumerator() {
return this.mFilesEnumerator;
},
// We don't support directory selection yet.
get domFileOrDirectory() {
return this.mFilesEnumerator ? this.mFilesEnumerator.mFiles[0] : null;
},
get mode() {
return this.mMode;
},
appendFilters: function(filterMask) {
// Ci.nsIFilePicker.filterHTML is not supported
// Ci.nsIFilePicker.filterText is not supported
if (filterMask & Ci.nsIFilePicker.filterImages) {
this.mFilterTypes = this.mFilterTypes.concat(IMAGE_FILTERS);
// This property is needed for the gallery app pick activity.
this.mExtraProps['nocrop'] = true;
}
// Ci.nsIFilePicker.filterXML is not supported
// Ci.nsIFilePicker.filterXUL is not supported
// Ci.nsIFilePicker.filterApps is not supported
// Ci.nsIFilePicker.filterAllowURLs is not supported
if (filterMask & Ci.nsIFilePicker.filterVideo) {
this.mFilterTypes = this.mFilterTypes.concat(VIDEO_FILTERS);
}
if (filterMask & Ci.nsIFilePicker.filterAudio) {
this.mFilterTypes = this.mFilterTypes.concat(AUDIO_FILTERS);
}
if (filterMask & Ci.nsIFilePicker.filterAll) {
// This property is needed for the gallery app pick activity.
this.mExtraProps['nocrop'] = true;
}
},
appendFilter: function(title, extensions) {
// pick activity doesn't support extensions
},
open: function(aFilePickerShownCallback) {
this.mFilePickerShownCallback = aFilePickerShownCallback;
cpmm.addMessageListener('file-picked', this);
let detail = {};
if (this.mFilterTypes) {
detail.type = this.mFilterTypes;
}
for (let prop in this.mExtraProps) {
if (!(prop in detail)) {
detail[prop] = this.mExtraProps[prop];
}
}
cpmm.sendAsyncMessage('file-picker', detail);
},
fireSuccess: function(file) {
this.mFilesEnumerator = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
mFiles: [file],
mIndex: 0,
hasMoreElements: function() {
return (this.mIndex < this.mFiles.length);
},
getNext: function() {
if (this.mIndex >= this.mFiles.length) {
throw Components.results.NS_ERROR_FAILURE;
}
return this.mFiles[this.mIndex++];
}
};
if (this.mFilePickerShownCallback) {
this.mFilePickerShownCallback.done(Ci.nsIFilePicker.returnOK);
this.mFilePickerShownCallback = null;
}
},
fireError: function() {
if (this.mFilePickerShownCallback) {
this.mFilePickerShownCallback.done(Ci.nsIFilePicker.returnCancel);
this.mFilePickerShownCallback = null;
}
},
receiveMessage: function(message) {
if (message.name !== 'file-picked') {
return;
}
cpmm.removeMessageListener('file-picked', this);
let data = message.data;
if (!data.success || !data.result.blob) {
this.fireError();
return;
}
// The name to be shown can be part of the message, or can be taken from
// the File (if the blob is a File).
let name = data.result.name;
if (!name &&
(data.result.blob instanceof this.mParent.File) &&
data.result.blob.name) {
name = data.result.blob.name;
}
// Let's try to remove the full path and take just the filename.
if (name) {
let names = OS.Path.split(name);
name = names.components[names.components.length - 1];
}
// the fallback is a filename composed by 'blob' + extension.
if (!name) {
name = 'blob';
if (data.result.blob.type) {
let mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
let mimeInfo = mimeSvc.getFromTypeAndExtension(data.result.blob.type, '');
if (mimeInfo) {
name += '.' + mimeInfo.primaryExtension;
}
}
}
let file = new this.mParent.File([data.result.blob],
name,
{ type: data.result.blob.type });
if (file) {
this.fireSuccess(file);
} else {
this.fireError();
}
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FilePicker]);