diff --git a/extensions/xforms/Makefile.in b/extensions/xforms/Makefile.in index a3707e8cac18..a94f94ca8b53 100644 --- a/extensions/xforms/Makefile.in +++ b/extensions/xforms/Makefile.in @@ -97,6 +97,7 @@ REQUIRES = \ intl \ pref \ htmlparser \ + exthandler \ $(NULL) # XForms IDLs @@ -123,6 +124,8 @@ XPIDLSRCS = \ nsIXFormsItemElement.idl \ nsIXFormsLabelElement.idl \ nsIXFormsItemSetUIElement.idl \ + nsIXFormsUploadElement.idl \ + nsIXFormsUploadUIElement.idl \ $(NULL) # XForms source files diff --git a/extensions/xforms/nsIXFormsUploadElement.idl b/extensions/xforms/nsIXFormsUploadElement.idl new file mode 100644 index 000000000000..6b97f4b7895b --- /dev/null +++ b/extensions/xforms/nsIXFormsUploadElement.idl @@ -0,0 +1,57 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla XForms support. + * + * The Initial Developer of the Original Code is + * IBM Corporation. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Javier Pedemonte + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsISupports.idl" + + +/** + * Interface implemented by the XTF part of the upload element to allow + * communication from XBL code. + */ +[scriptable, uuid(6dc00fd0-674c-41b3-8b0b-ba6c02aa769a)] +interface nsIXFormsUploadElement : nsISupports +{ + /** + * Display file picker dialog so user can select a file. + */ + void pickFile(); + + /** + * Unset file from \ and instance data. + */ + void clearFile(); +}; diff --git a/extensions/xforms/nsIXFormsUploadUIElement.idl b/extensions/xforms/nsIXFormsUploadUIElement.idl new file mode 100644 index 000000000000..bafa035dc7bc --- /dev/null +++ b/extensions/xforms/nsIXFormsUploadUIElement.idl @@ -0,0 +1,51 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla XForms support. + * + * The Initial Developer of the Original Code is + * IBM Corporation. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Javier Pedemonte + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsISupports.idl" + + +/** + * Interface implemented by the XBL part of the upload element. + */ +[scriptable, uuid(82c82c46-f111-416c-a3e6-ce9a0864d00f)] +interface nsIXFormsUploadUIElement : nsISupports +{ + /** + * Set text value of \'s text field. + */ + void setFieldText(in AString value); +}; diff --git a/extensions/xforms/nsXFormsControlStub.cpp b/extensions/xforms/nsXFormsControlStub.cpp index d83e40d48e66..36b34a7b1505 100644 --- a/extensions/xforms/nsXFormsControlStub.cpp +++ b/extensions/xforms/nsXFormsControlStub.cpp @@ -315,17 +315,6 @@ nsXFormsControlStubBase::ResetHelpAndHint(PRBool aInitialize) } } -PRBool -nsXFormsControlStubBase::GetReadOnlyState() -{ - PRBool res = PR_FALSE; - nsCOMPtr content(do_QueryInterface(mElement)); - if (content && (content->IntrinsicState() & NS_EVENT_STATE_MOZ_READONLY)) { - res = PR_TRUE; - } - return res; -} - PRBool nsXFormsControlStubBase::GetRelevantState() { diff --git a/extensions/xforms/nsXFormsControlStub.h b/extensions/xforms/nsXFormsControlStub.h index 943dd102faae..a224d7ef226d 100644 --- a/extensions/xforms/nsXFormsControlStub.h +++ b/extensions/xforms/nsXFormsControlStub.h @@ -178,9 +178,6 @@ protected: */ PRInt32 mBindAttrsCount; - /** Returns the read only state of the control (ie. mBoundNode) */ - PRBool GetReadOnlyState(); - /** Returns the relevant state of the control */ PRBool GetRelevantState(); diff --git a/extensions/xforms/nsXFormsSubmissionElement.cpp b/extensions/xforms/nsXFormsSubmissionElement.cpp index 62ae3030f45d..a32541995128 100644 --- a/extensions/xforms/nsXFormsSubmissionElement.cpp +++ b/extensions/xforms/nsXFormsSubmissionElement.cpp @@ -1289,8 +1289,8 @@ nsXFormsSubmissionElement::CopyChildren(nsIDOMNode *source, nsIDOMNode *dest, nsCOMPtr content = do_QueryInterface(currentNode); NS_ENSURE_STATE(content); - nsILocalFile *file = - NS_STATIC_CAST(nsILocalFile *, + nsIFile *file = + NS_STATIC_CAST(nsIFile *, content->GetProperty(nsXFormsAtoms::uploadFileProperty)); // NOTE: this value may be null if a file hasn't been selected. @@ -1655,8 +1655,8 @@ nsXFormsSubmissionElement::AppendMultipartFormData(nsIDOMNode *data, nsCOMPtr content = do_QueryInterface(data); NS_ENSURE_STATE(content); - nsILocalFile *file = - NS_STATIC_CAST(nsILocalFile *, + nsIFile *file = + NS_STATIC_CAST(nsIFile *, content->GetProperty(nsXFormsAtoms::uploadFileProperty)); nsAutoString leafName; diff --git a/extensions/xforms/nsXFormsUploadElement.cpp b/extensions/xforms/nsXFormsUploadElement.cpp index a2589b60b861..60b51e85e74e 100644 --- a/extensions/xforms/nsXFormsUploadElement.cpp +++ b/extensions/xforms/nsXFormsUploadElement.cpp @@ -36,26 +36,142 @@ * * ***** END LICENSE BLOCK ***** */ -#include "nsIDOMEventTarget.h" -#include "nsIDOM3Node.h" -#include "nsIDOMElement.h" -#include "nsMemory.h" -#include "nsCOMPtr.h" -#include "nsString.h" -#include "nsIXTFXMLVisualWrapper.h" -#include "nsIDOMDocument.h" -#include "nsXFormsControlStub.h" -#include "nsISchema.h" -#include "nsIDOMHTMLInputElement.h" -#include "nsXFormsAtoms.h" -#include "nsAutoPtr.h" -#include "nsIDOMXPathResult.h" -#include "nsIDOMFocusListener.h" +#include "nsIXFormsUploadElement.h" +#include "nsIXFormsUploadUIElement.h" #include "nsXFormsUtils.h" -#include "nsIModelElementPrivate.h" #include "nsIContent.h" -#include "nsIDOMXPathExpression.h" #include "nsNetUtil.h" +#include "nsXFormsDelegateStub.h" +#include "nsIMIMEService.h" +#include "nsCExternalHandlerService.h" +#include "plbase64.h" +#include "nsIFilePicker.h" +#include "nsIDOMDocument.h" +#include "nsIDOMDocumentView.h" +#include "nsIDOMAbstractView.h" +#include "nsIDOMWindowInternal.h" +#include "nsIStringBundle.h" +#include "nsIDOM3Node.h" +#include "nsAutoBuffer.h" +#include "nsIEventStateManager.h" +#include "prmem.h" + +#define NS_HTMLFORM_BUNDLE_URL \ + "chrome://global/locale/layout/HtmlForm.properties" + + +enum nsBoundType { + TYPE_DEFAULT, + TYPE_ANYURI, + TYPE_BASE64, + TYPE_HEX +}; + + +/** + * Implementation of the \ element. + */ +class nsXFormsUploadElement : public nsXFormsDelegateStub, + public nsIXFormsUploadElement +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIXFORMSUPLOADELEMENT + + NS_IMETHOD Refresh(); + +private: + /** + * Returns the type of the node to which this element is bound. + */ + nsBoundType GetBoundType(); + + /** + * Sets file path/contents into instance data. If aFile is nsnull, + * this clears the data. + */ + nsresult SetFile(nsILocalFile *aFile); + + /** + * Sets "filename" & "mediatype" in the instance data, using the given file. + * If aFile == nsnull, then this function clears the values in the + * instance data. + */ + nsresult HandleChildElements(nsILocalFile *aFile, PRBool *aChanged); + + /** + * Read the contents of the file and encode in Base64 or Hex. |aResult| must + * be freed by nsMemory::Free(). + */ + nsresult EncodeFileContents(nsIFile *aFile, nsBoundType aType, + PRUnichar **aResult); + + void BinaryToHex(const char *aBuffer, PRUint32 aCount, + PRUnichar **aHexString); +}; + +NS_IMPL_ISUPPORTS_INHERITED1(nsXFormsUploadElement, + nsXFormsDelegateStub, + nsIXFormsUploadElement) + +NS_IMETHODIMP +nsXFormsUploadElement::Refresh() +{ + // This is called when the 'bind', 'ref' or 'model' attribute has changed. + // So we need to make sure that the element is still bound to a node of + // type 'anyURI', 'base64Binary', or 'hexBinary'. + + nsresult rv = nsXFormsDelegateStub::Refresh(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mBoundNode) + return NS_OK; + + // If it is not bound to 'anyURI', 'base64Binary', or 'hexBinary', then + // mark as not relevant. + // XXX Bug 313313 - the 'relevant' state should be handled in XBL constructor. + nsCOMPtr xtfWrap(do_QueryInterface(mElement)); + NS_ENSURE_STATE(xtfWrap); + if (GetBoundType() == TYPE_DEFAULT) { + xtfWrap->SetIntrinsicState(NS_EVENT_STATE_DISABLED); + nsXFormsUtils::ReportError(NS_LITERAL_STRING("uploadBoundTypeError"), + mElement); + } else { + xtfWrap->SetIntrinsicState(NS_EVENT_STATE_ENABLED); + } + + return NS_OK; +} + +nsBoundType +nsXFormsUploadElement::GetBoundType() +{ + nsBoundType result = TYPE_DEFAULT; + + // get type bound to node + nsAutoString type, prefix, nsuri; + nsresult rv = nsXFormsUtils::ParseTypeFromNode(mBoundNode, type, prefix); + + if (NS_SUCCEEDED(rv)) { + // get the namespace url from the prefix + nsCOMPtr domNode3 = do_QueryInterface(mElement, &rv); + if (NS_SUCCEEDED(rv)) { + rv = domNode3->LookupNamespaceURI(prefix, nsuri); + + if (NS_SUCCEEDED(rv) && nsuri.EqualsLiteral(NS_NAMESPACE_XML_SCHEMA)) { + if (type.EqualsLiteral("anyURI")) { + result = TYPE_ANYURI; + } else if (type.EqualsLiteral("base64Binary")) { + result = TYPE_BASE64; + } else if (type.EqualsLiteral("hexBinary")) { + result = TYPE_HEX; + } + } + } + } + + return result; +} static void ReleaseObject(void *aObject, @@ -66,215 +182,153 @@ ReleaseObject(void *aObject, NS_STATIC_CAST(nsISupports *, aPropertyValue)->Release(); } - -/** - * Implementation of the \ element. - */ -class nsXFormsUploadElement : public nsIDOMFocusListener, - public nsXFormsControlStub -{ -public: - NS_DECL_ISUPPORTS_INHERITED - - // nsIXTFXMLVisual overrides - NS_IMETHOD OnCreated(nsIXTFXMLVisualWrapper *aWrapper); - - // nsIXTFVisual overrides - NS_IMETHOD GetVisualContent(nsIDOMElement **aElement); - NS_IMETHOD GetInsertionPoint(nsIDOMElement **aPoint); - - // nsIXTFElement overrides - NS_IMETHOD OnDestroyed(); - NS_IMETHOD AttributeSet(nsIAtom *aName, const nsAString &aValue); - NS_IMETHOD AttributeRemoved(nsIAtom *aName); - - // nsIXFormsControl - NS_IMETHOD Refresh(); - NS_IMETHOD TryFocus(PRBool* aOK); - - // nsIDOMEventListener - NS_IMETHOD HandleEvent(nsIDOMEvent *aEvent); - - // nsIDOMFocusListener - NS_IMETHOD Focus(nsIDOMEvent *aEvent); - NS_IMETHOD Blur(nsIDOMEvent *aEvent); - -private: - nsCOMPtr mLabel; - nsCOMPtr mInput; -}; - -NS_IMPL_ISUPPORTS_INHERITED2(nsXFormsUploadElement, - nsXFormsControlStub, - nsIDOMFocusListener, - nsIDOMEventListener) - -// nsIXTFXMLVisual - NS_IMETHODIMP -nsXFormsUploadElement::OnCreated(nsIXTFXMLVisualWrapper *aWrapper) +nsXFormsUploadElement::PickFile() { - nsresult rv = nsXFormsControlStub::OnCreated(aWrapper); - NS_ENSURE_SUCCESS(rv, rv); - - // Our anonymous content structure will look like this: - // - // - - nsCOMPtr domDoc; - mElement->GetOwnerDocument(getter_AddRefs(domDoc)); - - domDoc->CreateElementNS(NS_LITERAL_STRING(NS_NAMESPACE_XHTML), - NS_LITERAL_STRING("label"), - getter_AddRefs(mLabel)); - NS_ENSURE_STATE(mLabel); - - nsCOMPtr element; - nsCOMPtr childReturn; - - domDoc->CreateElementNS(NS_LITERAL_STRING(NS_NAMESPACE_XHTML), - NS_LITERAL_STRING("span"), - getter_AddRefs(element)); - NS_ENSURE_STATE(element); - - mLabel->AppendChild(element, getter_AddRefs(childReturn)); - - domDoc->CreateElementNS(NS_LITERAL_STRING(NS_NAMESPACE_XHTML), - NS_LITERAL_STRING("input"), - getter_AddRefs(element)); - mInput = do_QueryInterface(element); - NS_ENSURE_STATE(mInput); - - mInput->SetType(NS_LITERAL_STRING("file")); - - mLabel->AppendChild(mInput, getter_AddRefs(childReturn)); - - // We can't use xtf handleDefault here because editor stops blur events from - // bubbling, and we can't use a system event group handler because blur - // events don't go to the system event group (bug 263240). So, just install - // a bubbling listener in the normal event group. This works out because - // content can't get at the anonymous content to install an event handler, - // and the event doesn't bubble up. - - nsCOMPtr targ = do_QueryInterface(mInput); - NS_ASSERTION(targ, "input must be an event target!"); - - targ->AddEventListener(NS_LITERAL_STRING("blur"), this, PR_FALSE); - return NS_OK; -} - -NS_IMETHODIMP -nsXFormsUploadElement::AttributeSet(nsIAtom *aName, const nsAString &aValue) -{ - if (aName == nsXFormsAtoms::accesskey) { - // accesskey - mInput->SetAttribute(NS_LITERAL_STRING("accesskey"), aValue); - } - - return NS_OK; -} - -NS_IMETHODIMP -nsXFormsUploadElement::AttributeRemoved(nsIAtom *aName) -{ - if (aName == nsXFormsAtoms::accesskey) { - // accesskey - mInput->RemoveAttribute(NS_LITERAL_STRING("accesskey")); - } - - return NS_OK; -} - -// nsIXTFVisual - -NS_IMETHODIMP -nsXFormsUploadElement::GetVisualContent(nsIDOMElement **aElement) -{ - NS_ADDREF(*aElement = mLabel); - return NS_OK; -} - -NS_IMETHODIMP -nsXFormsUploadElement::GetInsertionPoint(nsIDOMElement **aPoint) -{ - nsCOMPtr childNode; - mLabel->GetFirstChild(getter_AddRefs(childNode)); - return CallQueryInterface(childNode, aPoint); -} - -// nsIXTFElement - -NS_IMETHODIMP -nsXFormsUploadElement::OnDestroyed() -{ - nsCOMPtr targ = do_QueryInterface(mInput); - if (NS_LIKELY(targ != nsnull)) { - targ->RemoveEventListener(NS_LITERAL_STRING("blur"), this, PR_FALSE); - } - - return nsXFormsControlStub::OnDestroyed(); -} - -// nsIDOMEventListener - -NS_IMETHODIMP -nsXFormsUploadElement::HandleEvent(nsIDOMEvent *aEvent) -{ - return NS_OK; -} - -NS_IMETHODIMP -nsXFormsUploadElement::Focus(nsIDOMEvent *aEvent) -{ - return NS_OK; -} - -NS_IMETHODIMP -nsXFormsUploadElement::Blur(nsIDOMEvent *aEvent) -{ - if (!mInput || !mBoundNode || !mModel || - !nsXFormsUtils::EventHandlingAllowed(aEvent, mElement)) + if (!mElement) return NS_OK; - nsAutoString value; - mInput->GetValue(value); + nsresult rv; - // store the file as a property on the selected content node. the submission - // code will read this value. + // get localized file picker title text + nsCOMPtr bundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr bundle; + rv = bundleService->CreateBundle(NS_HTMLFORM_BUNDLE_URL, + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + nsXPIDLString filepickerTitle; + rv = bundle->GetStringFromName(NS_LITERAL_STRING("FileUpload").get(), + getter_Copies(filepickerTitle)); + if (NS_FAILED(rv)) { + // fall back to English text + filepickerTitle.AssignASCII("File Upload"); + } + + // get nsIDOMWindowInternal + nsCOMPtr doc; + mElement->GetOwnerDocument(getter_AddRefs(doc)); + nsCOMPtr dview = do_QueryInterface(doc); + NS_ENSURE_STATE(dview); + nsCOMPtr aview; + dview->GetDefaultView(getter_AddRefs(aview)); + nsCOMPtr internal = do_QueryInterface(aview); + NS_ENSURE_STATE(internal); + + // init filepicker + nsCOMPtr filePicker = + do_CreateInstance("@mozilla.org/filepicker;1"); + if (!filePicker) + return NS_ERROR_FAILURE; + + rv = filePicker->Init(internal, filepickerTitle, nsIFilePicker::modeOpen); + NS_ENSURE_SUCCESS(rv, rv); + + // set filter "All Files" + // XXX implement "@mediatype" file filters + filePicker->AppendFilters(nsIFilePicker::filterAll); + + // open dialog + PRInt16 mode; + rv = filePicker->Show(&mode); + NS_ENSURE_SUCCESS(rv, rv); + if (mode == nsIFilePicker::returnCancel) + return NS_OK; + + // file was selected + nsCOMPtr localFile; + rv = filePicker->GetFile(getter_AddRefs(localFile)); + if (localFile) { + // set path value in \'s text field. + nsCOMPtr uiUpload = do_QueryInterface(mElement); + if (uiUpload) { + nsCAutoString spec; + NS_GetURLSpecFromFile(localFile, spec); + uiUpload->SetFieldText(NS_ConvertUTF8toUTF16(spec)); + } + + // set file into instance data + return SetFile(localFile); + } + + return rv; +} + +NS_IMETHODIMP +nsXFormsUploadElement::ClearFile() +{ + // clear path value in \'s text field. + nsCOMPtr uiUpload = do_QueryInterface(mElement); + if (uiUpload) { + uiUpload->SetFieldText(EmptyString()); + } + + // clear file from instance data + return SetFile(nsnull); +} + +nsresult +nsXFormsUploadElement::SetFile(nsILocalFile *aFile) +{ + if (!mBoundNode || !mModel) + return NS_OK; + + nsresult rv; nsCOMPtr content = do_QueryInterface(mBoundNode); NS_ENSURE_STATE(content); - nsILocalFile *file = nsnull; - nsCAutoString spec; - - // We need to special case an empty value. If the input field is blank, - // then I assume that the intention is that all bound nodes will become - // (or stay) empty and that there shouldn't be any file kept in the property. - if (!value.IsEmpty()) { - NS_NewLocalFile(value, PR_FALSE, &file); - NS_ENSURE_STATE(file); - NS_GetURLSpecFromFile(file, spec); - content->SetProperty(nsXFormsAtoms::uploadFileProperty, file, - ReleaseObject); - } else { + PRBool dataChanged = PR_FALSE; + if (!aFile) { + // clear instance data content->DeleteProperty(nsXFormsAtoms::uploadFileProperty); + rv = mModel->SetNodeValue(mBoundNode, EmptyString(), &dataChanged); + } else { + // set file into instance data + + nsBoundType type = GetBoundType(); + if (type == TYPE_ANYURI) { + // set fully qualified path as value in instance data node + nsCAutoString spec; + NS_GetURLSpecFromFile(aFile, spec); + rv = mModel->SetNodeValue(mBoundNode, NS_ConvertUTF8toUTF16(spec), + &dataChanged); + } else if (type == TYPE_BASE64 || type == TYPE_HEX) { + // encode file contents in base64/hex and set into instance data node + PRUnichar *fileData; + rv = EncodeFileContents(aFile, type, &fileData); + if (NS_SUCCEEDED(rv)) { + rv = mModel->SetNodeValue(mBoundNode, nsDependentString(fileData), + &dataChanged); + nsMemory::Free(fileData); + } + } else { + rv = NS_ERROR_FAILURE; + } + + // XXX need to handle derived types + + // Set nsIFile object as property on instance data node, so submission + // code can use it. + if (NS_SUCCEEDED(rv)) { + nsIFile *fileCopy = nsnull; + rv = aFile->Clone(&fileCopy); + NS_ENSURE_SUCCESS(rv, rv); + rv = content->SetProperty(nsXFormsAtoms::uploadFileProperty, fileCopy, + ReleaseObject); + } } - - - // update the instance data node. this value is never used by the submission - // code. instead, it exists so that the instance data will appear to be in- - // sync with what is actually submitted. - - PRBool changed; - nsresult rv = mModel->SetNodeValue(mBoundNode, NS_ConvertUTF8toUTF16(spec), - &changed); NS_ENSURE_SUCCESS(rv, rv); - if (changed) { + + // Handle and children + PRBool childrenChanged; + rv = HandleChildElements(aFile, &childrenChanged); + NS_ENSURE_SUCCESS(rv, rv); + + if (dataChanged || childrenChanged) { nsCOMPtr model = do_QueryInterface(mModel); + if (model) { rv = nsXFormsUtils::DispatchEvent(model, eEvent_Recalculate); NS_ENSURE_SUCCESS(rv, rv); @@ -284,42 +338,199 @@ nsXFormsUploadElement::Blur(nsIDOMEvent *aEvent) NS_ENSURE_SUCCESS(rv, rv); } } + return NS_OK; } -// nsIXFormsControl - -NS_IMETHODIMP -nsXFormsUploadElement::Refresh() +nsresult +nsXFormsUploadElement::HandleChildElements(nsILocalFile *aFile, + PRBool *aChanged) { - if (!mInput || !mBoundNode) + if (!aChanged) { + return NS_ERROR_NULL_POINTER; + } + *aChanged = PR_FALSE; + + // return immediately if we have no children + PRBool hasNodes; + mElement->HasChildNodes(&hasNodes); + if (!hasNodes) return NS_OK; - nsCOMPtr content = do_QueryInterface(mBoundNode); - NS_ENSURE_STATE(content); + nsresult rv = NS_OK; - nsILocalFile *file = - NS_STATIC_CAST(nsILocalFile *, - content->GetProperty(nsXFormsAtoms::uploadFileProperty)); + // look for the \ & \ elements in the + // \ children + nsCOMPtr filenameNode, mediatypeNode; + nsCOMPtr child, temp; + mElement->GetFirstChild(getter_AddRefs(child)); + while (child && (!filenameNode || !mediatypeNode)) { + if (!filenameNode && + nsXFormsUtils::IsXFormsElement(child, + NS_LITERAL_STRING("filename"))) + { + filenameNode = child; + } - // get the text value for the control - nsAutoString value; - if (file) - file->GetPath(value); + if (!mediatypeNode && + nsXFormsUtils::IsXFormsElement(child, + NS_LITERAL_STRING("mediatype"))) + { + mediatypeNode = child; + } - mInput->SetValue(value); - mInput->SetReadOnly(GetReadOnlyState()); + temp.swap(child); + temp->GetNextSibling(getter_AddRefs(child)); + } - return NS_OK; + // handle "filename" + PRBool filenameChanged = PR_FALSE; + if (filenameNode) { + nsCOMPtr filenameElem = do_QueryInterface(filenameNode); + if (aFile) { + nsAutoString filename; + rv = aFile->GetLeafName(filename); + if (!filename.IsEmpty()) { + rv = nsXFormsUtils::SetSingleNodeBindingValue(filenameElem, filename, + &filenameChanged); + } + } else { + rv = nsXFormsUtils::SetSingleNodeBindingValue(filenameElem, EmptyString(), + &filenameChanged); + } + NS_ENSURE_SUCCESS(rv, rv); + } + + // handle "mediatype" + PRBool mediatypechanged = PR_FALSE; + if (mediatypeNode) { + nsCOMPtr mediatypeElem = do_QueryInterface(mediatypeNode); + if (aFile) { + nsCOMPtr mimeService = + do_GetService(NS_MIMESERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + nsCAutoString contentType; + rv = mimeService->GetTypeFromFile(aFile, contentType); + if (NS_FAILED(rv)) { + contentType.AssignLiteral("application/octet-stream"); + } + rv = nsXFormsUtils::SetSingleNodeBindingValue(mediatypeElem, + NS_ConvertUTF8toUTF16(contentType), &mediatypechanged); + } + } else { + rv = nsXFormsUtils::SetSingleNodeBindingValue(mediatypeElem, + EmptyString(), &mediatypechanged); + } + } + + *aChanged = filenameChanged || mediatypechanged; + return rv; } -NS_IMETHODIMP -nsXFormsUploadElement::TryFocus(PRBool* aOK) +typedef nsAutoBuffer nsAutoCharBuffer; + +static void +ReportEncodingMemoryError(nsIDOMElement* aElement, nsIFile *aFile, + PRUint32 aFailedSize) { - *aOK = GetRelevantState() && nsXFormsUtils::FocusControl(mInput); - return NS_OK; + nsAutoString filename; + if (NS_FAILED(aFile->GetLeafName(filename))) { + return; + } + + nsAutoString size; + size.AppendInt((PRInt64)aFailedSize); + const PRUnichar *strings[] = { filename.get(), size.get() }; + nsXFormsUtils::ReportError(NS_LITERAL_STRING("encodingMemoryError"), + strings, 2, aElement, aElement); } +nsresult +nsXFormsUploadElement::EncodeFileContents(nsIFile *aFile, nsBoundType aType, + PRUnichar **aResult) +{ + nsresult rv; + + // get file contents + nsCOMPtr fileStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), aFile, + PR_RDONLY, -1 /* no mode bits */, + nsIFileInputStream::CLOSE_ON_EOF); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint32 size; + rv = fileStream->Available(&size); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCharBuffer fileData; + if (!fileData.EnsureElemCapacity(size + 1)) { + ReportEncodingMemoryError(mElement, aFile, size + 1); + return NS_ERROR_OUT_OF_MEMORY; + } + + PRUint32 bytesRead; + rv = fileStream->Read(fileData.get(), size, &bytesRead); + NS_ASSERTION(NS_SUCCEEDED(rv) && bytesRead == size, + "fileStream->Read failed"); + + if (NS_SUCCEEDED(rv)) { + if (aType == TYPE_BASE64) { + // encode file contents + *aResult = nsnull; + char *buffer = PL_Base64Encode(fileData.get(), bytesRead, nsnull); + if (buffer) { + *aResult = ToNewUnicode(nsDependentCString(buffer)); + PR_Free(buffer); + } + if (!*aResult) { + PRUint32 failedSize = buffer ? strlen(buffer) * sizeof(PRUnichar) + : ((bytesRead + 2) / 3) * 4 + 1; + ReportEncodingMemoryError(mElement, aFile, failedSize); + rv = NS_ERROR_OUT_OF_MEMORY; + } + } else if (aType == TYPE_HEX) { + // create buffer for hex encoded data + PRUint32 length = bytesRead * 2 + 1; + PRUnichar *fileDataHex = + NS_STATIC_CAST(PRUnichar*, nsMemory::Alloc(length * sizeof(PRUnichar))); + if (!fileDataHex) { + ReportEncodingMemoryError(mElement, aFile, length * sizeof(PRUnichar)); + rv = NS_ERROR_OUT_OF_MEMORY; + } else { + // encode file contents + BinaryToHex(fileData.get(), bytesRead, &fileDataHex); + fileDataHex[bytesRead * 2] = 0; + *aResult = fileDataHex; + } + } else { + NS_ERROR("Unknown encoding type for element"); + rv = NS_ERROR_INVALID_ARG; + } + } + + return rv; +} + +static inline PRUnichar +ToHexChar(PRInt16 aValue) +{ + if (aValue < 10) + return (PRUnichar) aValue + '0'; + else + return (PRUnichar) aValue - 10 + 'A'; +} + +void +nsXFormsUploadElement::BinaryToHex(const char *aBuffer, PRUint32 aCount, + PRUnichar **aHexString) +{ + for (PRUint32 index = 0; index < aCount; index++) { + (*aHexString)[index * 2] = ToHexChar((aBuffer[index] >> 4) & 0xf); + (*aHexString)[index * 2 + 1] = ToHexChar(aBuffer[index] & 0xf); + } +} + + NS_HIDDEN_(nsresult) NS_NewXFormsUploadElement(nsIXTFElement **aResult) { diff --git a/extensions/xforms/nsXFormsUtils.cpp b/extensions/xforms/nsXFormsUtils.cpp index 921ca9cfb6f2..953d3398c56f 100644 --- a/extensions/xforms/nsXFormsUtils.cpp +++ b/extensions/xforms/nsXFormsUtils.cpp @@ -754,11 +754,10 @@ nsXFormsUtils::SetNodeValue(nsIDOMNode* aDataNode, const nsString& aNodeValue) } } -/// -/// @todo Use this consistently, or delete? (XXX) /* static */ PRBool -nsXFormsUtils::GetSingleNodeBindingValue(nsIDOMElement* aElement, - nsString& aValue) +nsXFormsUtils::GetSingleNodeBinding(nsIDOMElement* aElement, + nsIDOMNode** aNode, + nsIModelElementPrivate** aModel) { if (!aElement) return PR_FALSE; @@ -781,10 +780,44 @@ nsXFormsUtils::GetSingleNodeBindingValue(nsIDOMElement* aElement, if (!singleNode) return PR_FALSE; - nsXFormsUtils::GetNodeValue(singleNode, aValue); + singleNode.swap(*aNode); + if (aModel) + model.swap(*aModel); // transfers ref return PR_TRUE; } +/// +/// @todo Use this consistently, or delete? (XXX) +/* static */ PRBool +nsXFormsUtils::GetSingleNodeBindingValue(nsIDOMElement* aElement, + nsString& aValue) +{ + nsCOMPtr node; + if (GetSingleNodeBinding(aElement, getter_AddRefs(node), nsnull)) { + nsXFormsUtils::GetNodeValue(node, aValue); + return PR_TRUE; + } + return PR_FALSE; +} + +/* static */ PRBool +nsXFormsUtils::SetSingleNodeBindingValue(nsIDOMElement *aElement, + const nsAString &aValue, + PRBool *aChanged) +{ + *aChanged = PR_FALSE; + nsCOMPtr node; + nsCOMPtr model; + if (GetSingleNodeBinding(aElement, getter_AddRefs(node), + getter_AddRefs(model))) + { + nsresult rv = model->SetNodeValue(node, aValue, aChanged); + if (NS_SUCCEEDED(rv)) + return PR_TRUE; + } + return PR_FALSE; +} + /* static */ nsresult nsXFormsUtils::DispatchEvent(nsIDOMNode* aTarget, nsXFormsEvent aEvent, PRBool *aDefaultActionEnabled) diff --git a/extensions/xforms/nsXFormsUtils.h b/extensions/xforms/nsXFormsUtils.h index 3548ab52b0e9..e19f60ab582a 100644 --- a/extensions/xforms/nsXFormsUtils.h +++ b/extensions/xforms/nsXFormsUtils.h @@ -252,6 +252,15 @@ public: static NS_HIDDEN_(void) SetNodeValue(nsIDOMNode *aDataNode, const nsString &aNodeValue); + /** + * Convenience method for doing XPath evaluations to get bound node + * for an element. Also returns the associated model if aModel != null. + * Returns PR_TRUE if the evaluation succeeds. + */ + static NS_HIDDEN_(PRBool) + GetSingleNodeBinding(nsIDOMElement* aElement, nsIDOMNode** aNode, + nsIModelElementPrivate** aModel); + /** * Convenience method for doing XPath evaluations to get string value * for an element. @@ -260,6 +269,14 @@ public: static NS_HIDDEN_(PRBool) GetSingleNodeBindingValue(nsIDOMElement* aElement, nsString& aValue); + /** + * Convenience method for doing XPath evaluations to set string value + * for an element. + * Returns PR_TRUE if the evaluation succeeds. + */ + static NS_HIDDEN_(PRBool) + SetSingleNodeBindingValue(nsIDOMElement *aElement, const nsAString &aValue, + PRBool *aChanged); /** * Dispatch an XForms event. aDefaultActionEnabled is returned indicating * if the default action of the dispatched event was enabled. diff --git a/extensions/xforms/resources/content/xforms.css b/extensions/xforms/resources/content/xforms.css index ebf2ccc744f3..e69c33186e52 100755 --- a/extensions/xforms/resources/content/xforms.css +++ b/extensions/xforms/resources/content/xforms.css @@ -255,4 +255,19 @@ select html|div.select-choice-content { padding-left: 10px; } +upload { + -moz-binding: url('chrome://xforms/content/xforms.xml#xformswidget-upload-disabled'); +} + +upload[mozType|type="http://www.w3.org/2001/XMLSchema#anyURI"] { + -moz-binding: url('chrome://xforms/content/xforms.xml#xformswidget-upload'); +} + +upload[mozType|type="http://www.w3.org/2001/XMLSchema#base64Binary"] { + -moz-binding: url('chrome://xforms/content/xforms.xml#xformswidget-upload'); +} + +upload[mozType|type="http://www.w3.org/2001/XMLSchema#hexBinary"] { + -moz-binding: url('chrome://xforms/content/xforms.xml#xformswidget-upload'); +} diff --git a/extensions/xforms/resources/content/xforms.xml b/extensions/xforms/resources/content/xforms.xml index 63536ca60648..c22c3d2ce28e 100644 --- a/extensions/xforms/resources/content/xforms.xml +++ b/extensions/xforms/resources/content/xforms.xml @@ -38,6 +38,11 @@ - - ***** END LICENSE BLOCK ***** --> + + %xformsDTD; +]> + + + + + + + + &xforms.upload.browsetext; + &xforms.upload.cleartext; + + + + + + this._uploadElem = null; + this._textControl = null; + this._browseButton = null; + this._clearButton = null; + + + null + + + + if (!this._uploadElem) { + this._uploadElem = + this.QueryInterface(Components.interfaces.nsIXFormsUploadElement); + } + return this._uploadElem; + + + + null + + + + if (!this._textControl) { + this._textControl = + document.getAnonymousElementByAttribute(this, "anonid", + "upload_text_control"); + } + return this._textControl; + + + + null + + + + if (!this._browseButton) { + this._browseButton = + document.getAnonymousElementByAttribute(this, "anonid", + "upload_browse_button"); + } + return this._browseButton; + + + + null + + + + if (!this._clearButton) { + this._clearButton = + document.getAnonymousElementByAttribute(this, "anonid", + "upload_clear_button"); + } + return this._clearButton; + + + + + + + this.textControl.value = aString; + + + + + + + + + + + &xforms.upload.browsetext; + &xforms.upload.cleartext; + + + + diff --git a/extensions/xforms/resources/locale/en-US/xforms.dtd b/extensions/xforms/resources/locale/en-US/xforms.dtd index 987522eecdeb..90be38e5e734 100644 --- a/extensions/xforms/resources/locale/en-US/xforms.dtd +++ b/extensions/xforms/resources/locale/en-US/xforms.dtd @@ -43,3 +43,6 @@ + + + diff --git a/extensions/xforms/resources/locale/en-US/xforms.properties b/extensions/xforms/resources/locale/en-US/xforms.properties index 02df06fd78e4..cf855369fb0e 100644 --- a/extensions/xforms/resources/locale/en-US/xforms.properties +++ b/extensions/xforms/resources/locale/en-US/xforms.properties @@ -58,6 +58,8 @@ labelLink2Error = XForms Error (19): Failed to load Label element from exte invalidSeparator = XForms Error (20): Submission separator may only be either "&" or ";", but found "%S". instanceBindError = XForms Error (21): Submission failed trying to replace instance document '%S'. Instance document doesn't exist in same model as submission element. instanceInstanceLoad = XForms Error (22): Instance document not allowed to load external instance: %S +encodingMemoryError = XForms Error (23): Not enough available memory to encode file %S, size = %S. +uploadBoundTypeError = XForms Error (24): Upload element not bound to valid datatype. Must be bound to datatype 'xsd:anyURI', 'xsd:base64Binary', or 'xsd:hexBinary'. # Warning Messages: warnSOAP = XForms Warning (1): You are using the SOAP post feature, which is an experimental feature! Beware that the functionality might change, and forms may stop working at any time. @@ -65,4 +67,3 @@ warnSOAP = XForms Warning (1): You are using the SOAP post feature, # XForms Permission Messages: xformsXDPermissionDialogTitle = Allowed Sites - XForms Cross Domain Access xformsXDPermissionDialogIntro = You can specify which web sites containing XForms may submit data to other domains. Type the exact address of the site you want to allow and then press Allow. -