diff --git a/third_party/firefox_sendtophone/chrome/content/ff-overlay.js b/third_party/firefox_sendtophone/chrome/content/ff-overlay.js
index eab4333..bf288fe 100644
--- a/third_party/firefox_sendtophone/chrome/content/ff-overlay.js
+++ b/third_party/firefox_sendtophone/chrome/content/ff-overlay.js
@@ -108,7 +108,7 @@ sendtophone.checkDrag = function(event)
sendtophone.doDrop = function(event)
{
- var dt = event.dataTransfer
+ var dt = event.dataTransfer;
var types = dt.types;
var supportedTypes = ["application/x-moz-file", "text/x-moz-url", "text/uri-list", "text/plain"];
types = supportedTypes.filter(function (value) types.contains(value));
@@ -137,10 +137,8 @@ sendtophone.doDrop = function(event)
for (var i = 0; i < dt.mozItemCount; i++)
{
var file = dt.mozGetDataAt("application/x-moz-file", i);
- if (file instanceof Components.interfaces.nsIFile )
- {
+ if (file instanceof Ci.nsIFile )
sendtophoneCore.sendFile(file);
- }
else
this.alert(this.strings.getString("InvalidFile"));
}
@@ -172,3 +170,4 @@ sendtophone.pickFile = function(folder)
}
}
+
diff --git a/third_party/firefox_sendtophone/chrome/content/upload.css b/third_party/firefox_sendtophone/chrome/content/upload.css
new file mode 100644
index 0000000..86fe300
--- /dev/null
+++ b/third_party/firefox_sendtophone/chrome/content/upload.css
@@ -0,0 +1,16 @@
+
+richlistitem[type="upload"][state="0"] {
+ -moz-binding: url('chrome://sendtophone/content/upload.xml#uploading');
+ -moz-box-orient: vertical;
+}
+
+richlistitem[type="upload"][state="1"] {
+ -moz-binding: url('chrome://sendtophone/content/upload.xml#compressing');
+ -moz-box-orient: vertical;
+}
+
+/* Only focus buttons in the selected item*/
+richlistitem[type="upload"]:not([selected="true"]) button {
+ -moz-user-focus: none;
+}
+
diff --git a/third_party/firefox_sendtophone/chrome/content/upload.xml b/third_party/firefox_sendtophone/chrome/content/upload.xml
new file mode 100644
index 0000000..c5340e9
--- /dev/null
+++ b/third_party/firefox_sendtophone/chrome/content/upload.xml
@@ -0,0 +1,100 @@
+
+
+
+
+ %downloadDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/third_party/firefox_sendtophone/chrome/content/uploads.js b/third_party/firefox_sendtophone/chrome/content/uploads.js
new file mode 100644
index 0000000..1e0a7b7
--- /dev/null
+++ b/third_party/firefox_sendtophone/chrome/content/uploads.js
@@ -0,0 +1,160 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+let Cu = Components.utils;
+Cu.import("resource://gre/modules/DownloadUtils.jsm");
+Cu.import("resource://gre/modules/PluralForm.jsm");
+
+Cu.import("resource://sendtophone/uploadsManager.js");
+
+let gUploadManager = sendtophoneUploadsManager;
+let gUploadsView = null;
+
+function addFile(upload)
+{
+ let dl = document.createElement("richlistitem");
+
+ dl.setAttribute("file", upload.file.path);
+ dl.setAttribute("target", upload.file.leafName);
+ dl.setAttribute("image", "moz-icon://" + upload.file.path + "?size=32");
+
+ dl.setAttribute("state", upload.state);
+ dl.setAttribute("startTime", upload.startTime);
+ dl.setAttribute("currBytes", upload.currBytes);
+ dl.setAttribute("maxBytes", upload.maxBytes);
+ dl.setAttribute("lastSeconds", Infinity);
+
+ // Initialize other attributes
+ dl.setAttribute("type", "upload");
+ dl.setAttribute("id", "upl" + upload.id);
+ dl.setAttribute("uploadId", upload.id);
+
+ gUploadsView.appendChild( dl );
+}
+
+function checkPendingUploads()
+{
+ if (gUploadsView.children.length==0)
+ window.close();
+}
+
+function cancelUpload(item)
+{
+ gUploadManager.cancelUpload( parseInt(item.getAttribute("uploadId"), 10) );
+}
+
+let gUploadListener = {
+ fileAdded: function(data)
+ {
+ addFile(data);
+ },
+ progressUpdate: function(data)
+ {
+ let item = document.getElementById( "upl" + data.id);
+ item.setAttribute("currBytes", data.currBytes);
+ item.setAttribute("maxBytes", data.maxBytes);
+
+ let percentComplete = Math.round(100 * data.currBytes / data.maxBytes);
+ item.setAttribute("progress", percentComplete);
+
+ // Status text
+ updateStatus(item);
+ },
+ fileFinished: function(data)
+ {
+ let item = document.getElementById("upl" + data.id);
+ gUploadsView.removeChild(item);
+
+ // If no more pending uploads, close the tab.
+ // Use a 100ms timeout to avoid flicker while compress -> upload a folder
+ window.setTimeout( checkPendingUploads, 100);
+ }
+};
+
+
+function Startup()
+{
+ gUploadsView = document.getElementById("UploadsBox");
+
+ gUploadManager.addListener(gUploadListener);
+
+ for (let id in gUploadManager.uploads)
+ addFile( gUploadManager.uploads[id] );
+
+}
+
+function Shutdown()
+{
+ gUploadManager.removeListener(gUploadListener);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Command Updating and Command Handlers
+
+var gUploadViewController = {
+ isCommandEnabled: function(aCommand, aItem)
+ {
+ let dl = aItem;
+
+ switch (aCommand) {
+ case "cmd_cancel":
+ return dl.inProgress;
+ }
+ return false;
+ },
+
+ doCommand: function(aCommand, aItem)
+ {
+ if (this.isCommandEnabled(aCommand, aItem))
+ this.commands[aCommand](aItem);
+ },
+
+ commands: {
+ cmd_cancel: function(aSelectedItem) {
+ cancelUpload(aSelectedItem);
+ }
+ }
+};
+
+/**
+ * Helper function to do commands.
+ *
+ * @param aCmd
+ * The command to be performed.
+ * @param aItem
+ * The richlistitem that represents the download that will have the
+ * command performed on it. If this is null, the command is performed on
+ * all downloads. If the item passed in is not a richlistitem that
+ * represents a download, it will walk up the parent nodes until it finds
+ * a DOM node that is.
+ */
+function performCommand(aCmd, aItem)
+{
+ let elm = aItem;
+
+ while (elm.nodeName != "richlistitem" ||
+ elm.getAttribute("type") != "upload")
+ elm = elm.parentNode;
+
+ gUploadViewController.doCommand(aCmd, elm);
+}
+
+function updateStatus(aItem)
+{
+ let currBytes = Number(aItem.getAttribute("currBytes"));
+ let maxBytes = Number(aItem.getAttribute("maxBytes"));
+
+ let elapsedTime = (Date.now() - Number(aItem.getAttribute("startTime"))) / 1000;
+ // If we don't have an active upload, assume 0 bytes/sec
+ let speed = (currBytes>0) ? currBytes/elapsedTime : 0;
+ let lastSec = Number(aItem.getAttribute("lastSeconds"));
+
+ let status, newLast;
+ [status, newLast] =
+ DownloadUtils.getDownloadStatus(currBytes, maxBytes, speed, lastSec);
+
+ // Update lastSeconds to be the new value
+ aItem.setAttribute("lastSeconds", newLast);
+
+ aItem.setAttribute("status", status);
+}
diff --git a/third_party/firefox_sendtophone/chrome/content/uploads.xul b/third_party/firefox_sendtophone/chrome/content/uploads.xul
new file mode 100644
index 0000000..bbd27ad
--- /dev/null
+++ b/third_party/firefox_sendtophone/chrome/content/uploads.xul
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/third_party/firefox_sendtophone/chrome/locale/en/uploads.dtd b/third_party/firefox_sendtophone/chrome/locale/en/uploads.dtd
new file mode 100644
index 0000000..4c92f20
--- /dev/null
+++ b/third_party/firefox_sendtophone/chrome/locale/en/uploads.dtd
@@ -0,0 +1,3 @@
+
+
+
diff --git a/third_party/firefox_sendtophone/chrome/locale/es/overlay.properties b/third_party/firefox_sendtophone/chrome/locale/es/overlay.properties
index 1de4657..8d835d0 100644
--- a/third_party/firefox_sendtophone/chrome/locale/es/overlay.properties
+++ b/third_party/firefox_sendtophone/chrome/locale/es/overlay.properties
@@ -13,7 +13,7 @@ smstoLink=Número SMS
mmsLink=Número MMS
mmstoLink=Número MMS
telLink=Número de teléfono
-InvalidFile=Solo se puede enviar un fichero cada vez.
+InvalidFile=No es un fichero válido.
SendFileToPhone=Envía ficheros al teléfono.
SendFolderToPhone=Envía una carpeta al teléfono.
-qrTitle=QR Image Link
+qrTitle=Imagen QR
diff --git a/third_party/firefox_sendtophone/chrome/locale/es/uploads.dtd b/third_party/firefox_sendtophone/chrome/locale/es/uploads.dtd
new file mode 100644
index 0000000..0a6f0a9
--- /dev/null
+++ b/third_party/firefox_sendtophone/chrome/locale/es/uploads.dtd
@@ -0,0 +1,3 @@
+
+
+
diff --git a/third_party/firefox_sendtophone/chrome/skin/uploads.css b/third_party/firefox_sendtophone/chrome/skin/uploads.css
new file mode 100644
index 0000000..ab94d8b
--- /dev/null
+++ b/third_party/firefox_sendtophone/chrome/skin/uploads.css
@@ -0,0 +1,63 @@
+#UploadsBox {
+ -moz-appearance: none;
+ margin: 0;
+ padding: 0;
+ border-width: 0;
+}
+
+/* Upload View Items */
+richlistitem[type="upload"] {
+ padding: 5px;
+ min-height: 44px !important;
+ border: 1px solid transparent;
+}
+
+richlistitem[type="upload"]:not([selected="true"]):nth-child(odd) {
+ background-color: -moz-oddtreerow;
+}
+
+richlistitem[type="upload"] .status {
+ font-size: smaller;
+ color: #555;
+}
+
+richlistitem[selected="true"][type="upload"] {
+ outline: none;
+}
+
+richlistbox:focus > richlistitem[selected="true"][type="upload"] .status {
+ color: highlighttext;
+}
+
+richlistitem[type="upload"] button {
+ -moz-appearance: none;
+ min-height: 16px;
+ min-width: 16px;
+ max-height: 16px;
+ max-width: 16px;
+ padding: 0;
+ margin: 0 1px 0 1px;
+}
+
+/**
+ * Images for buttons in the interface
+ */
+richlistitem[type="upload"] button {
+ list-style-image: url(chrome://mozapps/skin/downloads/buttons.png);
+}
+.cancel {
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+.cancel:hover {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+.cancel:hover:active {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+
+/* prevent flickering when changing states */
+.uploadTypeIcon {
+ min-height: 32px;
+ min-width: 32px;
+ -moz-padding-end: 2px;
+}
diff --git a/third_party/firefox_sendtophone/modules/sendtophone.js b/third_party/firefox_sendtophone/modules/sendtophone.js
index 087aaf9..836d804 100644
--- a/third_party/firefox_sendtophone/modules/sendtophone.js
+++ b/third_party/firefox_sendtophone/modules/sendtophone.js
@@ -85,6 +85,15 @@ var sendtophoneCore = {
}
},
+ // For use while debugging
+ toConsole: function(text)
+ {
+ var aConsoleService = Cc["@mozilla.org/consoleservice;1"]
+ .getService(Ci.nsIConsoleService);
+
+ aConsoleService.logStringMessage( text );
+ },
+
processXHR: function(url, method, data, callback)
{
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
@@ -143,7 +152,8 @@ var sendtophoneCore = {
},
// Main function
- // This is the only method that has to be called from outside this module
+ // This is method that has to be called from outside this module
+ // The other available method is sendFile
send: function(title, url, selection)
{
if (!this.sendUrl)
@@ -235,12 +245,12 @@ var sendtophoneCore = {
openTab: function(url, successUrl, callback)
{
- var gBrowser = Cc["@mozilla.org/embedcomp/window-watcher;1"]
- .getService(Components.interfaces.nsIWindowWatcher)
- .activeWindow
- .gBrowser;
+ var gBrowser = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator)
+ .getMostRecentWindow("navigator:browser")
+ .gBrowser;
- var lastTab = gBrowser.tabContainer.selectedIndex;
+ var lastTabIndex = gBrowser.tabContainer.selectedIndex;
var tab = gBrowser.addTab(url);
gBrowser.selectedTab = tab;
@@ -253,9 +263,9 @@ var sendtophoneCore = {
callback();
// Close tab
- gBrowser.removeCurrentTab();
+ gBrowser.removeTab(c2pTab);
// ReFocus on tab being sent
- gBrowser.selectedTab = gBrowser.tabContainer.childNodes[lastTab];
+ gBrowser.selectedTab = gBrowser.tabContainer.childNodes[lastTabIndex];
}
}, true);
}
@@ -288,13 +298,20 @@ var sendtophoneCore = {
if (!this.prefs)
this.init();
+ if (typeof sendtophoneUploadsManager == "undefined")
+ Components.utils.import("resource://sendtophone/uploadsManager.js");
+
if (nsFile.isDirectory())
{
+ // There's no progress notification while compressing, only on end.
+ var progressId = sendtophoneUploadsManager.addZip(nsFile);
// Compress the contents to a zip file
zipFolder(nsFile, function(nsZip)
{
+ sendtophoneUploadsManager.finishedUpload( progressId );
+
// Send the zip and delete it afterwards
- sendtophoneCore.sendFile(nsZip, function() {/* nsZip.remove(false)*/ });
+ sendtophoneCore.sendFile(nsZip, function() { nsZip.remove(false) });
}
)
return;
@@ -353,25 +370,46 @@ var sendtophoneCore = {
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
-
- req.open('POST', uri, false);
+
+ // Show the progress of uploads
+ sendtophoneUploadsManager.addUpload(nsFile, req);
+
+ req.open('POST', uri, true);
+
req.setRequestHeader("Content-length",multiStream.available());
req.setRequestHeader("Content-type","multipart/form-data; charset: utf-8; boundary="+boundary);
- req.onload = function(event)
+ req.addEventListener("load", function(event)
{
+ // If there's a callback (to delete temporary files) we call it now
+ if (callback)
+ callback();
+
var body = event.target.responseXML;
var uploads;
if (body && (uploads = body.documentElement.getElementsByTagName("upload")))
{
- sendtophoneCore.send(uploadName, uploads[0].firstChild.data, "");
- // If there's a callback (to delete temporary files) we call it now
- if (callback)
- callback();
+ var data = uploads[0].firstChild.data;
+// sendtophoneCore.toConsole(data);
+ sendtophoneCore.send(uploadName, data, "");
return;
}
// error.
sendtophoneCore.alert(uri + "\r\n" + event.target.responseText);
- }
+ }, false);
+ // Handle errors or aborted uploads
+ req.addEventListener("error", function(evt)
+ {
+ // If there's a callback (to delete temporary files) we call it now
+ if (callback)
+ callback();
+ }, false);
+ req.addEventListener("abort", function(evt)
+ {
+ // If there's a callback (to delete temporary files) we call it now
+ if (callback)
+ callback();
+ }, false);
+
/*
if required for cookies... don't think so.
try {
@@ -403,7 +441,7 @@ const PR_EXCL = 0x80;
function zipFolder(folder, callback)
{
// get TMP directory
- var nsFile = Components.classes["@mozilla.org/file/directory_service;1"].
+ var nsFile = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties).
get("TmpD", Ci.nsIFile);
diff --git a/third_party/firefox_sendtophone/modules/uploadsManager.js b/third_party/firefox_sendtophone/modules/uploadsManager.js
new file mode 100644
index 0000000..fda28a6
--- /dev/null
+++ b/third_party/firefox_sendtophone/modules/uploadsManager.js
@@ -0,0 +1,172 @@
+var EXPORTED_SYMBOLS = ["sendtophoneUploadsManager"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+var sendtophoneUploadsManager = {
+ uploads: {},
+ _counter : 0,
+
+ _listeners: [],
+
+ // Add a listener that will be called when there's any change on the uploads
+ addListener: function( obj )
+ {
+ this._listeners.push( obj );
+ },
+
+ // Remove an existing listener object
+ removeListener: function( obj )
+ {
+ for(let i=0, listener; listener = this._listeners[i]; i++)
+ {
+ if (obj == listener)
+ {
+ this._listeners.splice(i, 1);
+ return;
+ }
+ }
+ },
+
+ /**
+ * Adds a new upload
+ * nsFile: The file that it's being send
+ * req: the XmlHttpRequest that will send that file
+ */
+ addUpload: function(nsFile, req)
+ {
+ let id = this._addToUploads( {file:nsFile, req:req, state:0, percent:0,
+ startTime: Date.now(), currBytes: 0, maxBytes: nsFile.fileSize} );
+
+ req.upload.addEventListener("progress", function(evt)
+ {
+ if (evt.lengthComputable) {
+ sendtophoneUploadsManager.updateProgress(id, evt.loaded, evt.total);
+ }
+ }, false);
+ req.upload.addEventListener("load", function(evt)
+ {
+ sendtophoneUploadsManager.updateProgress(id, evt.loaded, evt.total);
+ }, false);
+
+ // Clear row when it has finished
+ req.addEventListener("load", function(evt)
+ {
+ sendtophoneUploadsManager.finishedUpload(id);
+ }, false);
+ // If there's an error or it's aborted, finish its tracking.
+ req.addEventListener("error", function(evt)
+ {
+ sendtophoneUploadsManager.finishedUpload(id);
+ }, false);
+ req.addEventListener("abort", function(evt)
+ {
+ sendtophoneUploadsManager.finishedUpload(id);
+ }, false);
+
+ },
+
+ /**
+ * Adds a zip (it's not an upload, but this way we can show that something is going on)
+ * nsFolder: a nsFile object pointing to the folder being compressed
+ * When the compression has finished, the external code has to call .finishedUpload(id)
+ * with the id returned in this method.
+ */
+ addZip: function(nsFolder)
+ {
+ return this._addToUploads( {file:nsFolder, state:1} );
+ },
+
+ _addToUploads: function( obj )
+ {
+ this.init();
+
+ // Creates a counter to automatically assign new ids to each upload
+ let id = this._counter++;
+ obj.id = id;
+ this.uploads[id] = obj;
+
+ for(let i=0, listener; listener = this._listeners[i]; i++)
+ listener.fileAdded( obj );
+
+ return id;
+ },
+
+ init: function()
+ {
+ // Open the tab
+ openAndReuseOneTabPerURL("chrome://sendtophone/content/uploads.xul");
+ },
+
+ updateProgress: function(id, loaded, total)
+ {
+ let upload = this.uploads[id];
+ upload.currBytes = loaded;
+ upload.maxBytes = total;
+
+ this._listeners.forEach( function( listener ) {
+ listener.progressUpdate( upload );
+ });
+ },
+
+ finishedUpload: function(id)
+ {
+ let upload = this.uploads[id];
+ delete this.uploads[id];
+
+ this._listeners.forEach( function( listener ) {
+ listener.fileFinished( upload );
+ });
+ },
+
+ cancelUpload: function(id)
+ {
+ let upload = this.uploads[id];
+ upload.req.abort();
+
+ }
+}
+
+// https://developer.mozilla.org/en/Code_snippets/Tabbed_browser#Reusing_tabs
+function openAndReuseOneTabPerURL(url) {
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var browserEnumerator = wm.getEnumerator("navigator:browser");
+
+ // Check each browser instance for our URL
+ var found = false;
+ while (!found && browserEnumerator.hasMoreElements()) {
+ var browserWin = browserEnumerator.getNext();
+ var tabbrowser = browserWin.gBrowser;
+
+ // Check each tab of this browser instance
+ var numTabs = tabbrowser.browsers.length;
+ for (var index = 0; index < numTabs; index++) {
+ var currentBrowser = tabbrowser.getBrowserAtIndex(index);
+ if (url == currentBrowser.currentURI.spec) {
+
+ // The URL is already opened. Select this tab.
+ tabbrowser.selectedTab = tabbrowser.tabContainer.childNodes[index];
+
+ // Focus *this* browser-window
+ browserWin.focus();
+
+ found = true;
+ break;
+ }
+ }
+ }
+
+ // Our URL isn't open. Open it now.
+ if (!found) {
+ var recentWindow = wm.getMostRecentWindow("navigator:browser");
+ if (recentWindow) {
+ // Use an existing browser window
+ recentWindow.delayedOpenTab(url, null, null, null, null);
+ }
+ else {
+ // No browser windows are open, so open a new one.
+ window.open(url);
+ }
+ }
+}