Bug 1397627 - nsIAsyncFileMetadata interface, r=asuth
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 08 Sep 2017 16:06:26 +0200
changeset 661562 85b18b5a5e439fbf31eeb40f94b22a278836ef82
parent 661561 f77a81b06e4305db0abe0b12c29945cf067fcb4e
child 661563 c08a59cb75a34422a3fdeb287cbfa954df117be8
push id78830
push userasasaki@mozilla.com
push dateFri, 08 Sep 2017 19:44:43 +0000
reviewersasuth
bugs1397627
milestone57.0a1
Bug 1397627 - nsIAsyncFileMetadata interface, r=asuth
dom/file/ipc/IPCBlobInputStream.cpp
dom/file/ipc/IPCBlobInputStream.h
dom/indexedDB/ActorsChild.cpp
netwerk/base/nsIFileStreams.idl
--- a/dom/file/ipc/IPCBlobInputStream.cpp
+++ b/dom/file/ipc/IPCBlobInputStream.cpp
@@ -15,26 +15,26 @@
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
 static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
 
-class CallbackRunnable final : public CancelableRunnable
+class InputStreamCallbackRunnable final : public CancelableRunnable
 {
 public:
   static void
   Execute(nsIInputStreamCallback* aCallback,
           nsIEventTarget* aEventTarget,
           IPCBlobInputStream* aStream)
   {
-    RefPtr<CallbackRunnable> runnable =
-      new CallbackRunnable(aCallback, aStream);
+    RefPtr<InputStreamCallbackRunnable> runnable =
+      new InputStreamCallbackRunnable(aCallback, aStream);
 
     nsCOMPtr<nsIEventTarget> target = aEventTarget;
     if (!target) {
       target = NS_GetCurrentThread();
     }
 
     target->Dispatch(runnable, NS_DISPATCH_NORMAL);
   }
@@ -44,42 +44,86 @@ public:
   {
     mCallback->OnInputStreamReady(mStream);
     mCallback = nullptr;
     mStream = nullptr;
     return NS_OK;
   }
 
 private:
-  CallbackRunnable(nsIInputStreamCallback* aCallback,
-                   IPCBlobInputStream* aStream)
-    : CancelableRunnable("dom::CallbackRunnable")
+  InputStreamCallbackRunnable(nsIInputStreamCallback* aCallback,
+                              IPCBlobInputStream* aStream)
+    : CancelableRunnable("dom::InputStreamCallbackRunnable")
     , mCallback(aCallback)
     , mStream(aStream)
   {
     MOZ_ASSERT(mCallback);
     MOZ_ASSERT(mStream);
   }
 
   nsCOMPtr<nsIInputStreamCallback> mCallback;
   RefPtr<IPCBlobInputStream> mStream;
 };
 
+class FileMetadataCallbackRunnable final : public CancelableRunnable
+{
+public:
+  static void
+  Execute(nsIFileMetadataCallback* aCallback,
+          nsIEventTarget* aEventTarget,
+          IPCBlobInputStream* aStream)
+  {
+    RefPtr<FileMetadataCallbackRunnable> runnable =
+      new FileMetadataCallbackRunnable(aCallback, aStream);
+
+    nsCOMPtr<nsIEventTarget> target = aEventTarget;
+    if (!target) {
+      target = NS_GetCurrentThread();
+    }
+
+    target->Dispatch(runnable, NS_DISPATCH_NORMAL);
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    mCallback->OnFileMetadataReady(mStream);
+    mCallback = nullptr;
+    mStream = nullptr;
+    return NS_OK;
+  }
+
+private:
+  FileMetadataCallbackRunnable(nsIFileMetadataCallback* aCallback,
+                               IPCBlobInputStream* aStream)
+    : CancelableRunnable("dom::FileMetadataCallbackRunnable")
+    , mCallback(aCallback)
+    , mStream(aStream)
+  {
+    MOZ_ASSERT(mCallback);
+    MOZ_ASSERT(mStream);
+  }
+
+  nsCOMPtr<nsIFileMetadataCallback> mCallback;
+  RefPtr<IPCBlobInputStream> mStream;
+};
+
 } // anonymous
 
 NS_IMPL_ADDREF(IPCBlobInputStream);
 NS_IMPL_RELEASE(IPCBlobInputStream);
 
 NS_INTERFACE_MAP_BEGIN(IPCBlobInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
   NS_INTERFACE_MAP_ENTRY(nsICloneableInputStream)
   NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
-  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileMetadata, IsFileMetadata())
+  NS_INTERFACE_MAP_ENTRY(nsIFileMetadata)
+  NS_INTERFACE_MAP_ENTRY(nsIAsyncFileMetadata)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
 NS_INTERFACE_MAP_END
 
 IPCBlobInputStream::IPCBlobInputStream(IPCBlobInputStreamChild* aActor)
   : mActor(aActor)
   , mState(eInit)
 {
   MOZ_ASSERT(aActor);
@@ -196,17 +240,21 @@ IPCBlobInputStream::Close()
     mAsyncRemoteStream = nullptr;
   }
 
   if (mRemoteStream) {
     mRemoteStream->Close();
     mRemoteStream = nullptr;
   }
 
-  mCallback = nullptr;
+  mInputStreamCallback = nullptr;
+  mInputStreamCallbackEventTarget = nullptr;
+
+  mFileMetadataCallback = nullptr;
+  mFileMetadataCallbackEventTarget = nullptr;
 
   mState = eClosed;
   return NS_OK;
 }
 
 // nsICloneableInputStream interface
 
 NS_IMETHODIMP
@@ -249,36 +297,36 @@ IPCBlobInputStream::AsyncWait(nsIInputSt
 {
   // See IPCBlobInputStream.h for more information about this state machine.
 
   switch (mState) {
   // First call, we need to retrieve the stream from the parent actor.
   case eInit:
     MOZ_ASSERT(mActor);
 
-    mCallback = aCallback;
-    mCallbackEventTarget = aEventTarget;
+    mInputStreamCallback = aCallback;
+    mInputStreamCallbackEventTarget = aEventTarget;
     mState = ePending;
 
     mActor->StreamNeeded(this, aEventTarget);
     return NS_OK;
 
   // We are still waiting for the remote inputStream
   case ePending:
-    if (mCallback && aCallback) {
+    if (mInputStreamCallback && aCallback) {
       return NS_ERROR_FAILURE;
     }
 
-    mCallback = aCallback;
-    mCallbackEventTarget = aEventTarget;
+    mInputStreamCallback = aCallback;
+    mInputStreamCallbackEventTarget = aEventTarget;
     return NS_OK;
 
   // We have the remote inputStream, let's check if we can execute the callback.
   case eRunning:
-    return MaybeExecuteCallback(aCallback, aEventTarget);
+    return MaybeExecuteInputStreamCallback(aCallback, aEventTarget);
 
   // Stream is closed.
   default:
     MOZ_ASSERT(mState == eClosed);
     return NS_BASE_STREAM_CLOSED;
   }
 }
 
@@ -292,48 +340,65 @@ IPCBlobInputStream::StreamReady(nsIInput
     }
     return;
   }
 
   // If aInputStream is null, it means that the serialization went wrong or the
   // stream is not available anymore. We keep the state as pending just to block
   // any additional operation.
 
-  nsCOMPtr<nsIInputStreamCallback> callback;
-  callback.swap(mCallback);
+  if (!aInputStream) {
+    return;
+  }
+
+  mRemoteStream = aInputStream;
 
-  nsCOMPtr<nsIEventTarget> callbackEventTarget;
-  callbackEventTarget.swap(mCallbackEventTarget);
+  MOZ_ASSERT(mState == ePending);
+  mState = eRunning;
+
+  nsCOMPtr<nsIFileMetadataCallback> fileMetadataCallback;
+  fileMetadataCallback.swap(mFileMetadataCallback);
+
+  nsCOMPtr<nsIEventTarget> fileMetadataCallbackEventTarget;
+  fileMetadataCallbackEventTarget.swap(mFileMetadataCallbackEventTarget);
 
-  if (aInputStream && callback) {
-    MOZ_ASSERT(mState == ePending);
+  if (fileMetadataCallback) {
+    FileMetadataCallbackRunnable::Execute(fileMetadataCallback,
+                                          fileMetadataCallbackEventTarget,
+                                          this);
+  }
 
-    mRemoteStream = aInputStream;
-    mState = eRunning;
+  nsCOMPtr<nsIInputStreamCallback> inputStreamCallback;
+  inputStreamCallback.swap(mInputStreamCallback);
 
-    MaybeExecuteCallback(callback, callbackEventTarget);
+  nsCOMPtr<nsIEventTarget> inputStreamCallbackEventTarget;
+  inputStreamCallbackEventTarget.swap(mInputStreamCallbackEventTarget);
+
+  if (inputStreamCallback) {
+    MaybeExecuteInputStreamCallback(inputStreamCallback,
+                                    inputStreamCallbackEventTarget);
   }
 }
 
 nsresult
-IPCBlobInputStream::MaybeExecuteCallback(nsIInputStreamCallback* aCallback,
-                                         nsIEventTarget* aCallbackEventTarget)
+IPCBlobInputStream::MaybeExecuteInputStreamCallback(nsIInputStreamCallback* aCallback,
+                                                    nsIEventTarget* aCallbackEventTarget)
 {
   MOZ_ASSERT(mState == eRunning);
   MOZ_ASSERT(mRemoteStream);
 
   // If the callback has been already set, we return an error.
-  if (mCallback && aCallback) {
+  if (mInputStreamCallback && aCallback) {
     return NS_ERROR_FAILURE;
   }
 
-  mCallback = aCallback;
-  mCallbackEventTarget = aCallbackEventTarget;
+  mInputStreamCallback = aCallback;
+  mInputStreamCallbackEventTarget = aCallbackEventTarget;
 
-  if (!mCallback) {
+  if (!mInputStreamCallback) {
     return NS_OK;
   }
 
   nsresult rv = EnsureAsyncRemoteStream();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -351,27 +416,27 @@ IPCBlobInputStream::OnInputStreamReady(n
   if (mState == eClosed) {
     return NS_OK;
   }
 
   MOZ_ASSERT(mState == eRunning);
   MOZ_ASSERT(mAsyncRemoteStream == aStream);
 
   // The callback has been canceled in the meantime.
-  if (!mCallback) {
+  if (!mInputStreamCallback) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIInputStreamCallback> callback;
-  callback.swap(mCallback);
+  callback.swap(mInputStreamCallback);
 
   nsCOMPtr<nsIEventTarget> callbackEventTarget;
-  callbackEventTarget.swap(mCallbackEventTarget);
+  callbackEventTarget.swap(mInputStreamCallbackEventTarget);
  
-  CallbackRunnable::Execute(callback, callbackEventTarget, this);
+  InputStreamCallbackRunnable::Execute(callback, callbackEventTarget, this);
   return NS_OK;
 }
 
 // nsIIPCSerializableInputStream
 
 void
 IPCBlobInputStream::Serialize(mozilla::ipc::InputStreamParams& aParams,
                               FileDescriptorArray& aFileDescriptors)
@@ -391,27 +456,60 @@ IPCBlobInputStream::Deserialize(const mo
 }
 
 mozilla::Maybe<uint64_t>
 IPCBlobInputStream::ExpectedSerializedLength()
 {
   return mozilla::Nothing();
 }
 
-// nsIFileMetadata
+// nsIAsyncFileMetadata
 
-bool
-IPCBlobInputStream::IsFileMetadata() const
+NS_IMETHODIMP
+IPCBlobInputStream::AsyncWait(nsIFileMetadataCallback* aCallback,
+                              nsIEventTarget* aEventTarget)
 {
-  // We are nsIFileMetadata only if we have the remote stream and that is a
-  // nsIFileMetadata.
-  nsCOMPtr<nsIFileMetadata> fileMetadata = do_QueryInterface(mRemoteStream);
-  return !!fileMetadata;
+  // See IPCBlobInputStream.h for more information about this state machine.
+
+  switch (mState) {
+  // First call, we need to retrieve the stream from the parent actor.
+  case eInit:
+    MOZ_ASSERT(mActor);
+
+    mFileMetadataCallback = aCallback;
+    mFileMetadataCallbackEventTarget = aEventTarget;
+    mState = ePending;
+
+    mActor->StreamNeeded(this, aEventTarget);
+    return NS_OK;
+
+  // We are still waiting for the remote inputStream
+  case ePending:
+    if (mFileMetadataCallback && aCallback) {
+      return NS_ERROR_FAILURE;
+    }
+
+    mFileMetadataCallback = aCallback;
+    mFileMetadataCallbackEventTarget = aEventTarget;
+    return NS_OK;
+
+  // We have the remote inputStream, let's check if we can execute the callback.
+  case eRunning:
+    FileMetadataCallbackRunnable::Execute(aCallback, aEventTarget, this);
+    return NS_OK;
+
+  // Stream is closed.
+  default:
+    MOZ_ASSERT(mState == eClosed);
+    return NS_BASE_STREAM_CLOSED;
+  }
 }
 
+// nsIFileMetadata
+
 NS_IMETHODIMP
 IPCBlobInputStream::GetSize(int64_t* aRetval)
 {
   nsCOMPtr<nsIFileMetadata> fileMetadata = do_QueryInterface(mRemoteStream);
   if (!fileMetadata) {
     return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE;
   }
 
--- a/dom/file/ipc/IPCBlobInputStream.h
+++ b/dom/file/ipc/IPCBlobInputStream.h
@@ -17,45 +17,43 @@ namespace mozilla {
 namespace dom {
 
 class IPCBlobInputStreamChild;
 
 class IPCBlobInputStream final : public nsIAsyncInputStream
                                , public nsIInputStreamCallback
                                , public nsICloneableInputStream
                                , public nsIIPCSerializableInputStream
-                               , public nsIFileMetadata
+                               , public nsIAsyncFileMetadata
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
   NS_DECL_NSIASYNCINPUTSTREAM
   NS_DECL_NSIINPUTSTREAMCALLBACK
   NS_DECL_NSICLONEABLEINPUTSTREAM
   NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
   NS_DECL_NSIFILEMETADATA
+  NS_DECL_NSIASYNCFILEMETADATA
 
   explicit IPCBlobInputStream(IPCBlobInputStreamChild* aActor);
 
   void
   StreamReady(nsIInputStream* aInputStream);
 
 private:
   ~IPCBlobInputStream();
 
   nsresult
-  MaybeExecuteCallback(nsIInputStreamCallback* aCallback,
-                       nsIEventTarget* aEventTarget);
+  MaybeExecuteInputStreamCallback(nsIInputStreamCallback* aCallback,
+                                  nsIEventTarget* aEventTarget);
 
   nsresult
   EnsureAsyncRemoteStream();
 
-  bool
-  IsFileMetadata() const;
-
   RefPtr<IPCBlobInputStreamChild> mActor;
 
   // This is the list of possible states.
   enum {
     // The initial state. Only ::Available() can be used without receiving an
     // error. The available size is known by the actor.
     eInit,
 
@@ -73,16 +71,20 @@ private:
     // NS_BASE_STREAM_CLOSED.
     eClosed,
   } mState;
 
   nsCOMPtr<nsIInputStream> mRemoteStream;
   nsCOMPtr<nsIAsyncInputStream> mAsyncRemoteStream;
 
   // These 2 values are set only if mState is ePending.
-  nsCOMPtr<nsIInputStreamCallback> mCallback;
-  nsCOMPtr<nsIEventTarget> mCallbackEventTarget;
+  nsCOMPtr<nsIInputStreamCallback> mInputStreamCallback;
+  nsCOMPtr<nsIEventTarget> mInputStreamCallbackEventTarget;
+
+  // These 2 values are set only if mState is ePending.
+  nsCOMPtr<nsIFileMetadataCallback> mFileMetadataCallback;
+  nsCOMPtr<nsIEventTarget> mFileMetadataCallbackEventTarget;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ipc_IPCBlobInputStream_h
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -1472,16 +1472,17 @@ DispatchFileHandleSuccessEvent(FileHandl
 /*******************************************************************************
  * Actor class declarations
  ******************************************************************************/
 
 // CancelableRunnable is used to make workers happy.
 class BackgroundRequestChild::PreprocessHelper final
   : public CancelableRunnable
   , public nsIInputStreamCallback
+  , public nsIFileMetadataCallback
 {
   typedef std::pair<nsCOMPtr<nsIInputStream>,
                     nsCOMPtr<nsIInputStream>> StreamPair;
 
   nsCOMPtr<nsIEventTarget> mOwningEventTarget;
   nsTArray<StreamPair> mStreamPairs;
   nsTArray<RefPtr<JS::WasmModule>> mModuleSet;
   BackgroundRequestChild* mActor;
@@ -1555,19 +1556,23 @@ private:
   ProcessCurrentStreamPair();
 
   nsresult
   WaitForStreamReady(nsIInputStream* aInputStream);
 
   void
   ContinueWithStatus(nsresult aStatus);
 
+  nsresult
+  DataIsReady(nsIInputStream* aInputStream);
+
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIRUNNABLE
   NS_DECL_NSIINPUTSTREAMCALLBACK
+  NS_DECL_NSIFILEMETADATACALLBACK
 
   virtual nsresult
   Cancel() override;
 };
 
 /*******************************************************************************
  * Local class implementations
  ******************************************************************************/
@@ -3513,16 +3518,27 @@ PreprocessHelper::ProcessCurrentStreamPa
 
 nsresult
 BackgroundRequestChild::
 PreprocessHelper::WaitForStreamReady(nsIInputStream* aInputStream)
 {
   MOZ_ASSERT(!IsOnOwningThread());
   MOZ_ASSERT(aInputStream);
 
+  nsCOMPtr<nsIAsyncFileMetadata> asyncFileMetadata =
+    do_QueryInterface(aInputStream);
+  if (asyncFileMetadata) {
+    nsresult rv = asyncFileMetadata->AsyncWait(this, mTaskQueueEventTarget);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    return NS_OK;
+  }
+
   nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aInputStream);
   if (!asyncStream) {
     return NS_ERROR_NO_INTERFACE;
   }
 
   nsresult rv = asyncStream->AsyncWait(this, 0, 0, mTaskQueueEventTarget);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -3559,17 +3575,18 @@ PreprocessHelper::ContinueWithStatus(nsr
     eventTarget = mTaskQueueEventTarget;
   }
 
   nsresult rv = eventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
   Unused <<  NS_WARN_IF(NS_FAILED(rv));
 }
 
 NS_IMPL_ISUPPORTS_INHERITED(BackgroundRequestChild::PreprocessHelper,
-                            CancelableRunnable, nsIInputStreamCallback)
+                            CancelableRunnable, nsIInputStreamCallback,
+                            nsIFileMetadataCallback)
 
 NS_IMETHODIMP
 BackgroundRequestChild::
 PreprocessHelper::Run()
 {
   if (IsOnOwningThread()) {
     RunOnOwningThread();
   } else {
@@ -3578,16 +3595,33 @@ PreprocessHelper::Run()
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BackgroundRequestChild::
 PreprocessHelper::OnInputStreamReady(nsIAsyncInputStream* aStream)
 {
+  return DataIsReady(aStream);
+}
+
+NS_IMETHODIMP
+BackgroundRequestChild::
+PreprocessHelper::OnFileMetadataReady(nsIAsyncFileMetadata* aObject)
+{
+  nsCOMPtr<nsIInputStream> stream = do_QueryInterface(aObject);
+  MOZ_ASSERT(stream, "It was a stream before!");
+
+  return DataIsReady(stream);
+}
+
+nsresult
+BackgroundRequestChild::
+PreprocessHelper::DataIsReady(nsIInputStream* aStream)
+{
   MOZ_ASSERT(!IsOnOwningThread());
   MOZ_ASSERT(aStream);
   MOZ_ASSERT(!mStreamPairs.IsEmpty());
 
   // We still don't have the current bytecode FileDesc.
   if (!mCurrentBytecodeFileDesc) {
     mCurrentBytecodeFileDesc = GetFileDescriptorFromStream(aStream);
     if (!mCurrentBytecodeFileDesc) {
--- a/netwerk/base/nsIFileStreams.idl
+++ b/netwerk/base/nsIFileStreams.idl
@@ -1,17 +1,19 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
 #include "nsIInputStream.idl"
 #include "nsIOutputStream.idl"
 
+interface nsIEventTarget;
 interface nsIFile;
+interface nsIFileMetadataCallback;
 
 %{C++
 struct PRFileDesc;
 %}
 
 [ptr] native PRFileDescPtr(PRFileDesc);
 
 /**
@@ -177,30 +179,62 @@ interface nsIFileStream : nsISupports
      *       file is to be created, then it will not appear on the disk until
      *       the first write.
      */
     const long DEFER_OPEN = 1<<0;
 };
 
 /**
  * An interface that allows you to get some metadata like file size and
- * file last modified time.
+ * file last modified time. These methods and attributes can throw
+ * NS_BASE_STREAM_WOULD_BLOCK in case the informations are not available yet.
+ * If this happens, consider the use of nsIAsyncFileMetadata.
+ *
+ * If using nsIAsyncFileMetadata, you should retrieve any data via this
+ * interface before taking any action that might consume the underlying stream.
+ * For example, once Available(), Read(), or nsIAsyncInputStream::AsyncWait()
+ * are invoked, these methods may return NS_BASE_STREAM_CLOSED.  This will
+ * happen when using IPCBlobInputStream with an underlying file stream, for
+ * example.
  */
 [scriptable, uuid(07f679e4-9601-4bd1-b510-cd3852edb881)]
 interface nsIFileMetadata : nsISupports
 {
     /**
-     * File size in bytes;
+     * File size in bytes.
      */
     readonly attribute long long size;
 
     /**
      * File last modified time in milliseconds from midnight (00:00:00),
      * January 1, 1970 Greenwich Mean Time (GMT).
      */
     readonly attribute long long lastModified;
 
     /**
      * The internal file descriptor. It can be used for memory mapping of the
-     * underlying file. Please use carefully!
+     * underlying file. Please use carefully! If this returns
+     * NS_BASE_STREAM_WOULD_BLOCK, consider the use of nsIAsyncFileMetadata.
      */
     [noscript] PRFileDescPtr getFileDescriptor();
 };
+
+[scriptable, uuid(de15b80b-29ba-4b7f-9220-a3d75b17ae8c)]
+interface nsIAsyncFileMetadata : nsIFileMetadata
+{
+    /**
+     * Asynchronously wait for the object to be ready.
+     */
+    void asyncWait(in nsIFileMetadataCallback aCallback,
+                   in nsIEventTarget aEventTarget);
+};
+
+/**
+ * This is a companion interface for nsIAsyncFileMetadata::asyncWait.
+ */
+[function, scriptable, uuid(d01c7ead-7ba3-4726-b399-618ec8ec7057)]
+interface nsIFileMetadataCallback : nsISupports
+{
+    /**
+     * Called to indicate that the nsIFileMetadata object is ready.
+     */
+    void onFileMetadataReady(in nsIAsyncFileMetadata aObject);
+};