author | Ben Turner <bent.mozilla@gmail.com> |
Sat, 09 Feb 2013 17:59:47 +0000 | |
changeset 132168 | ad49bd35d558686f74d72f86717878117636da59 |
parent 132167 | d509e44cae3effde6814bb08c7c8cf9808197917 |
child 132169 | 93ba23f414ffec7fdecdff3511e038fd3d877c43 |
push id | 317 |
push user | bbajaj@mozilla.com |
push date | Tue, 07 May 2013 01:20:33 +0000 |
treeherder | mozilla-release@159a10910249 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jduell |
bugs | 835698 |
milestone | 21.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/ipc/Makefile.in +++ b/dom/ipc/Makefile.in @@ -17,17 +17,20 @@ EXPORT_LIBRARY = 1 ifndef _MSC_VER FAIL_ON_WARNINGS := 1 endif # !_MSC_VER ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT)) TEST_DIRS += tests endif -EXPORTS = PCOMContentPermissionRequestChild.h +EXPORTS = \ + nsICachedFileDescriptorListener.h \ + PCOMContentPermissionRequestChild.h \ + $(NULL) EXPORTS_NAMESPACES = \ mozilla \ mozilla/dom \ mozilla/dom/ipc \ $(NULL) EXPORTS_mozilla = \
--- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -26,16 +26,17 @@ include "mozilla/layout/RenderFrameUtils using IPC::Principal; using gfxMatrix; using gfxRect; using gfxSize; using mozilla::layers::LayersBackend; using mozilla::layers::FrameMetrics; using mozilla::layout::ScrollingBehavior; +using mozilla::void_t; using mozilla::WindowsHandle; using nscolor; using nsCompositionEvent; using nsIMEUpdatePreference; using nsIntPoint; using nsIntRect; using nsIntSize; using nsKeyEvent; @@ -285,16 +286,18 @@ child: * |Show()| and |Move()| take IntSizes rather than Rects because * content processes always render to a virtual <0, 0> top-left * point. */ Show(nsIntSize size); LoadURL(nsCString uri); + CacheFileDescriptor(nsString path, FileDescriptor fd); + UpdateDimensions(nsRect rect, nsIntSize size, ScreenOrientation orientation) compress; UpdateFrame(FrameMetrics frame) compress; /** * Requests handling of a double tap. |point| is in CSS pixels, relative to * the scroll offset. This message is expected to round-trip back to * ZoomToRect() with a rect indicating where we should zoom to.
--- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -13,16 +13,17 @@ #include "ContentChild.h" #include "IndexedDBChild.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/IntentionalCrash.h" #include "mozilla/docshell/OfflineCacheUpdateChild.h" #include "mozilla/dom/PContentChild.h" #include "mozilla/dom/PContentDialogChild.h" #include "mozilla/ipc/DocumentRendererChild.h" +#include "mozilla/ipc/FileDescriptorUtils.h" #include "mozilla/layers/AsyncPanZoomController.h" #include "mozilla/layers/CompositorChild.h" #include "mozilla/layers/PLayersChild.h" #include "mozilla/layout/RenderFrameChild.h" #include "mozilla/StaticPtr.h" #include "mozilla/unused.h" #include "mozIApplication.h" #include "nsComponentManagerUtils.h" @@ -32,16 +33,17 @@ #include "nsEventListenerManager.h" #include <algorithm> #ifdef MOZ_CRASHREPORTER #include "nsExceptionHandler.h" #endif #include "mozilla/dom/Element.h" #include "nsIAppsService.h" #include "nsIBaseWindow.h" +#include "nsICachedFileDescriptorListener.h" #include "nsIComponentManager.h" #include "nsIDocumentInlines.h" #include "nsIDOMClassInfo.h" #include "nsIDOMElement.h" #include "nsIDOMEvent.h" #include "nsIDOMWindow.h" #include "nsIDOMWindowUtils.h" #include "nsIDocShell.h" @@ -116,16 +118,108 @@ ContentListener::HandleEvent(nsIDOMEvent class ContentDialogChild : public PContentDialogChild { public: virtual bool Recv__delete__(const InfallibleTArray<int>& aIntParams, const InfallibleTArray<nsString>& aStringParams); }; +class TabChild::CachedFileDescriptorInfo +{ + struct PathOnlyComparatorHelper + { + bool Equals(const nsAutoPtr<CachedFileDescriptorInfo>& a, + const CachedFileDescriptorInfo& b) const + { + return a->mPath == b.mPath; + } + }; + + struct PathAndCallbackComparatorHelper + { + bool Equals(const nsAutoPtr<CachedFileDescriptorInfo>& a, + const CachedFileDescriptorInfo& b) const + { + return a->mPath == b.mPath && + a->mCallback == b.mCallback; + } + }; + +public: + nsString mPath; + FileDescriptor mFileDescriptor; + nsCOMPtr<nsICachedFileDescriptorListener> mCallback; + bool mCanceled; + + CachedFileDescriptorInfo(const nsAString& aPath) + : mPath(aPath), mCanceled(false) + { } + + CachedFileDescriptorInfo(const nsAString& aPath, + const FileDescriptor& aFileDescriptor) + : mPath(aPath), mFileDescriptor(aFileDescriptor), mCanceled(false) + { } + + CachedFileDescriptorInfo(const nsAString& aPath, + nsICachedFileDescriptorListener* aCallback) + : mPath(aPath), mCallback(aCallback), mCanceled(false) + { } + + PathOnlyComparatorHelper PathOnlyComparator() const + { + return PathOnlyComparatorHelper(); + } + + PathAndCallbackComparatorHelper PathAndCallbackComparator() const + { + return PathAndCallbackComparatorHelper(); + } + + void FireCallback() const + { + mCallback->OnCachedFileDescriptor(mPath, mFileDescriptor); + } +}; + +class TabChild::CachedFileDescriptorCallbackRunnable : public nsRunnable +{ + typedef TabChild::CachedFileDescriptorInfo CachedFileDescriptorInfo; + + nsAutoPtr<CachedFileDescriptorInfo> mInfo; + +public: + CachedFileDescriptorCallbackRunnable(CachedFileDescriptorInfo* aInfo) + : mInfo(aInfo) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aInfo); + MOZ_ASSERT(!aInfo->mPath.IsEmpty()); + MOZ_ASSERT(aInfo->mCallback); + } + + void Dispatch() + { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv = NS_DispatchToCurrentThread(this); + NS_ENSURE_SUCCESS_VOID(rv); + } + +private: + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInfo); + + mInfo->FireCallback(); + return NS_OK; + } +}; + StaticRefPtr<TabChild> sPreallocatedTab; /*static*/ void TabChild::PreloadSlowThings() { MOZ_ASSERT(!sPreallocatedTab); nsRefPtr<TabChild> tab(new TabChild(TabContext(), /* chromeFlags */ 0)); @@ -1112,16 +1206,140 @@ TabChild::RecvLoadURL(const nsCString& u #ifdef MOZ_CRASHREPORTER CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("URL"), uri); #endif return true; } +bool +TabChild::RecvCacheFileDescriptor(const nsString& aPath, + const FileDescriptor& aFileDescriptor) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aPath.IsEmpty()); + + // aFileDescriptor may be invalid here, but the callback will choose how to + // handle it. + + // First see if we already have a request for this path. + const CachedFileDescriptorInfo search(aPath); + uint32_t index = + mCachedFileDescriptorInfos.IndexOf(search, 0, + search.PathOnlyComparator()); + if (index == mCachedFileDescriptorInfos.NoIndex) { + // We haven't had any requests for this path yet. Assume that we will + // in a little while and save the file descriptor here. + mCachedFileDescriptorInfos.AppendElement( + new CachedFileDescriptorInfo(aPath, aFileDescriptor)); + return true; + } + + nsAutoPtr<CachedFileDescriptorInfo>& info = + mCachedFileDescriptorInfos[index]; + + MOZ_ASSERT(info); + MOZ_ASSERT(info->mPath == aPath); + MOZ_ASSERT(!info->mFileDescriptor.IsValid()); + MOZ_ASSERT(info->mCallback); + + // If this callback has been canceled then we can simply close the file + // descriptor and forget about the callback. + if (info->mCanceled) { + // Only close if this is a valid file descriptor. + if (aFileDescriptor.IsValid()) { + nsRefPtr<CloseFileRunnable> runnable = + new CloseFileRunnable(aFileDescriptor); + runnable->Dispatch(); + } + } else { + // Not canceled so fire the callback. + info->mFileDescriptor = aFileDescriptor; + + // We don't need a runnable here because we should already be at the top + // of the event loop. Just fire immediately. + info->FireCallback(); + } + + mCachedFileDescriptorInfos.RemoveElementAt(index); + return true; +} + +bool +TabChild::GetCachedFileDescriptor(const nsAString& aPath, + nsICachedFileDescriptorListener* aCallback) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aPath.IsEmpty()); + MOZ_ASSERT(aCallback); + + // First see if we've already received a cached file descriptor for this + // path. + const CachedFileDescriptorInfo search(aPath); + uint32_t index = + mCachedFileDescriptorInfos.IndexOf(search, 0, + search.PathOnlyComparator()); + if (index == mCachedFileDescriptorInfos.NoIndex) { + // We haven't received a file descriptor for this path yet. Assume that + // we will in a little while and save the request here. + mCachedFileDescriptorInfos.AppendElement( + new CachedFileDescriptorInfo(aPath, aCallback)); + return false; + } + + nsAutoPtr<CachedFileDescriptorInfo>& info = + mCachedFileDescriptorInfos[index]; + + MOZ_ASSERT(info); + MOZ_ASSERT(info->mPath == aPath); + MOZ_ASSERT(!info->mCallback); + MOZ_ASSERT(!info->mCanceled); + + info->mCallback = aCallback; + + nsRefPtr<CachedFileDescriptorCallbackRunnable> runnable = + new CachedFileDescriptorCallbackRunnable(info.forget()); + runnable->Dispatch(); + + mCachedFileDescriptorInfos.RemoveElementAt(index); + return true; +} + +void +TabChild::CancelCachedFileDescriptorCallback( + const nsAString& aPath, + nsICachedFileDescriptorListener* aCallback) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aPath.IsEmpty()); + MOZ_ASSERT(aCallback); + + const CachedFileDescriptorInfo search(aPath, aCallback); + uint32_t index = + mCachedFileDescriptorInfos.IndexOf(search, 0, + search.PathAndCallbackComparator()); + if (index == mCachedFileDescriptorInfos.NoIndex) { + // Nothing to do here. + return; + } + + nsAutoPtr<CachedFileDescriptorInfo>& info = + mCachedFileDescriptorInfos[index]; + + MOZ_ASSERT(info); + MOZ_ASSERT(info->mPath == aPath); + MOZ_ASSERT(!info->mFileDescriptor.IsValid()); + MOZ_ASSERT(info->mCallback == aCallback); + MOZ_ASSERT(!info->mCanceled); + + // Set this flag so that we will close the file descriptor when it arrives. + info->mCanceled = true; +} + void TabChild::DoFakeShow() { RecvShow(nsIntSize(0, 0)); mDidFakeShow = true; } bool
--- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -49,16 +49,17 @@ #include "nsWeakReference.h" #include "nsITabChild.h" #include "mozilla/Attributes.h" #include "FrameMetrics.h" #include "ProcessUtils.h" #include "mozilla/dom/TabContext.h" struct gfxMatrix; +class nsICachedFileDescriptorListener; namespace mozilla { namespace layout { class RenderFrameChild; } namespace dom { @@ -191,16 +192,19 @@ public: */ virtual bool DoSendSyncMessage(const nsAString& aMessage, const mozilla::dom::StructuredCloneData& aData, InfallibleTArray<nsString>* aJSONRetVal); virtual bool DoSendAsyncMessage(const nsAString& aMessage, const mozilla::dom::StructuredCloneData& aData); virtual bool RecvLoadURL(const nsCString& uri); + virtual bool RecvCacheFileDescriptor(const nsString& aPath, + const FileDescriptor& aFileDescriptor) + MOZ_OVERRIDE; virtual bool RecvShow(const nsIntSize& size); virtual bool RecvUpdateDimensions(const nsRect& rect, const nsIntSize& size, const ScreenOrientation& orientation); virtual bool RecvUpdateFrame(const mozilla::layers::FrameMetrics& aFrameMetrics); virtual bool RecvHandleDoubleTap(const nsIntPoint& aPoint); virtual bool RecvHandleSingleTap(const nsIntPoint& aPoint); virtual bool RecvHandleLongTap(const nsIntPoint& aPoint); virtual bool RecvActivate(); virtual bool RecvDeactivate(); @@ -312,16 +316,25 @@ public: /** * Get this object's app type. * * A TabChild's app type corresponds to the value of its frame element's * "mozapptype" attribute. */ void GetAppType(nsAString& aAppType) const { aAppType = mAppType; } + // Returns true if the file descriptor was found in the cache, false + // otherwise. + bool GetCachedFileDescriptor(const nsAString& aPath, + nsICachedFileDescriptorListener* aCallback); + + void CancelCachedFileDescriptorCallback( + const nsAString& aPath, + nsICachedFileDescriptorListener* aCallback); + protected: virtual PRenderFrameChild* AllocPRenderFrame(ScrollingBehavior* aScrolling, LayersBackend* aBackend, int32_t* aMaxTextureSize, uint64_t* aLayersId) MOZ_OVERRIDE; virtual bool DeallocPRenderFrame(PRenderFrameChild* aFrame) MOZ_OVERRIDE; virtual bool RecvDestroy() MOZ_OVERRIDE; @@ -407,16 +420,19 @@ private: nsIDOMWindowUtils* GetDOMWindowUtils() { nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(mWebNav); nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window); return utils; } + class CachedFileDescriptorInfo; + class CachedFileDescriptorCallbackRunnable; + nsCOMPtr<nsIWebNavigation> mWebNav; nsCOMPtr<nsIWidget> mWidget; nsCOMPtr<nsIURI> mLastURI; FrameMetrics mLastMetrics; RenderFrameChild* mRemoteFrame; nsRefPtr<TabChildGlobal> mTabChildGlobal; uint32_t mChromeFlags; nsIntRect mOuterRect; @@ -425,16 +441,19 @@ private: // point of the touchstart. nsIntPoint mGestureDownPoint; // The touch identifier of the active gesture. int32_t mActivePointerId; // A timer task that fires if the tap-hold timeout is exceeded by // the touch we're tracking. That is, if touchend or a touchmove // that exceeds the gesture threshold doesn't happen. CancelableTask* mTapHoldTimer; + // At present only 1 of these is really expected. + nsAutoTArray<nsAutoPtr<CachedFileDescriptorInfo>, 1> + mCachedFileDescriptorInfos; float mOldViewportWidth; nscolor mLastBackgroundColor; ScrollingBehavior mScrolling; bool mDidFakeShow; bool mNotified; bool mContentDocumentIsDisplayed; bool mTriedBrowserInit; nsString mAppType;
--- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -45,32 +45,143 @@ #include "nsIWidget.h" #include "nsIWindowWatcher.h" #include "nsNetUtil.h" #include "nsPIDOMWindow.h" #include "nsPrintfCString.h" #include "nsSerializationHelper.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" +#include "private/pprio.h" #include "StructuredCloneUtils.h" #include "TabChild.h" #include <algorithm> using namespace mozilla::dom; using namespace mozilla::ipc; using namespace mozilla::layers; using namespace mozilla::layout; using namespace mozilla::services; using namespace mozilla::widget; using namespace mozilla::dom::indexedDB; // The flags passed by the webProgress notifications are 16 bits shifted // from the ones registered by webProgressListeners. #define NOTIFY_FLAG_SHIFT 16 +class OpenFileAndSendFDRunnable : public nsRunnable +{ + const nsString mPath; + nsRefPtr<TabParent> mTabParent; + nsCOMPtr<nsIEventTarget> mEventTarget; + PRFileDesc* mFD; + +public: + OpenFileAndSendFDRunnable(const nsAString& aPath, TabParent* aTabParent) + : mPath(aPath), mTabParent(aTabParent), mFD(nullptr) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aPath.IsEmpty()); + MOZ_ASSERT(aTabParent); + } + + void Dispatch() + { + MOZ_ASSERT(NS_IsMainThread()); + + mEventTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + NS_ENSURE_TRUE_VOID(mEventTarget); + + nsresult rv = mEventTarget->Dispatch(this, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS_VOID(rv); + } + +private: + ~OpenFileAndSendFDRunnable() + { + MOZ_ASSERT(!mFD); + } + + // This shouldn't be called directly except by the event loop. Use Dispatch + // to start the sequence. + NS_IMETHOD Run() + { + if (NS_IsMainThread()) { + SendResponse(); + } else if (mFD) { + CloseFile(); + } else { + OpenFile(); + } + + return NS_OK; + } + + void SendResponse() + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mTabParent); + MOZ_ASSERT(mEventTarget); + MOZ_ASSERT(mFD); + + nsRefPtr<TabParent> tabParent; + mTabParent.swap(tabParent); + + FileDescriptor::PlatformHandleType handle = + FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(mFD)); + + mozilla::unused << tabParent->SendCacheFileDescriptor(mPath, handle); + + nsCOMPtr<nsIEventTarget> eventTarget; + mEventTarget.swap(eventTarget); + + if (NS_FAILED(eventTarget->Dispatch(this, NS_DISPATCH_NORMAL))) { + NS_WARNING("Failed to dispatch to stream transport service!"); + + // It's probably safer to take the main thread IO hit here rather + // than leak a file descriptor. + CloseFile(); + } + } + + void OpenFile() + { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(!mFD); + + nsCOMPtr<nsIFile> file; + nsresult rv = NS_NewLocalFile(mPath, false, getter_AddRefs(file)); + NS_ENSURE_SUCCESS_VOID(rv); + + PRFileDesc* fd; + rv = file->OpenNSPRFileDesc(PR_RDONLY, 0, &fd); + NS_ENSURE_SUCCESS_VOID(rv); + + mFD = fd; + + if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { + NS_WARNING("Failed to dispatch to main thread!"); + + CloseFile(); + } + } + + void CloseFile() + { + // It's possible for this to happen on the main thread if the dispatch + // to the stream service fails after we've already opened the file so + // we can't assert the thread we're running on. + + MOZ_ASSERT(mFD); + + PR_Close(mFD); + mFD = nullptr; + } +}; + namespace mozilla { namespace dom { TabParent* sEventCapturer; TabParent *TabParent::mIMETabParent = nullptr; NS_IMPL_ISUPPORTS3(TabParent, nsITabParent, nsIAuthPromptProvider, nsISecureBrowserUI) @@ -88,16 +199,17 @@ TabParent::TabParent(const TabContext& a , mRect(0, 0, 0, 0) , mDimensions(0, 0) , mOrientation(0) , mDPI(0) , mShown(false) , mUpdatedDimensions(false) , mMarkedDestroying(false) , mIsDestroyed(false) + , mAppPackageFileDescriptorSent(false) { } TabParent::~TabParent() { } void @@ -225,33 +337,76 @@ TabParent::AnswerCreateWindow(PBrowserPa *retval = frameLoader->GetRemoteBrowser(); return true; } void TabParent::LoadURL(nsIURI* aURI) { + MOZ_ASSERT(aURI); + if (mIsDestroyed) { - return; - } - if (!mShown) { - nsAutoCString spec; - if (aURI) { - aURI->GetSpec(spec); - } - NS_WARNING(nsPrintfCString("TabParent::LoadURL(%s) called before " - "Show(). Ignoring LoadURL.\n", spec.get()).get()); - return; + return; } nsCString spec; aURI->GetSpec(spec); + if (!mShown) { + NS_WARNING(nsPrintfCString("TabParent::LoadURL(%s) called before " + "Show(). Ignoring LoadURL.\n", + spec.get()).get()); + return; + } + unused << SendLoadURL(spec); + + // If this app is a packaged app then we can speed startup by sending over + // the file descriptor for the "application.zip" file that it will + // invariably request. Only do this once. + if (!mAppPackageFileDescriptorSent) { + mAppPackageFileDescriptorSent = true; + + nsCOMPtr<mozIApplication> app = GetOwnOrContainingApp(); + if (app) { + nsString manifestURL; + nsresult rv = app->GetManifestURL(manifestURL); + NS_ENSURE_SUCCESS_VOID(rv); + + if (StringBeginsWith(manifestURL, NS_LITERAL_STRING("app:"))) { + nsString basePath; + rv = app->GetBasePath(basePath); + NS_ENSURE_SUCCESS_VOID(rv); + + nsString appId; + rv = app->GetId(appId); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIFile> packageFile; + rv = NS_NewLocalFile(basePath, false, + getter_AddRefs(packageFile)); + NS_ENSURE_SUCCESS_VOID(rv); + + rv = packageFile->Append(appId); + NS_ENSURE_SUCCESS_VOID(rv); + + rv = packageFile->Append(NS_LITERAL_STRING("application.zip")); + NS_ENSURE_SUCCESS_VOID(rv); + + nsString path; + rv = packageFile->GetPath(path); + NS_ENSURE_SUCCESS_VOID(rv); + + nsRefPtr<OpenFileAndSendFDRunnable> openFileRunnable = + new OpenFileAndSendFDRunnable(path, this); + openFileRunnable->Dispatch(); + } + } + } } void TabParent::Show(const nsIntSize& size) { // sigh mShown = true; mDimensions = size;
--- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -299,14 +299,16 @@ private: void MaybeForwardEventToRenderFrame(const nsInputEvent& aEvent, nsInputEvent* aOutEvent); // When true, we've initiated normal shutdown and notified our // managing PContent. bool mMarkedDestroying; // When true, the TabParent is invalid and we should not send IPC messages // anymore. bool mIsDestroyed; + // Whether we have already sent a FileDescriptor for the app package. + bool mAppPackageFileDescriptorSent; }; } // namespace dom } // namespace mozilla #endif
new file mode 100644 --- /dev/null +++ b/dom/ipc/nsICachedFileDescriptorListener.h @@ -0,0 +1,38 @@ +/* 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/. */ + +#ifndef mozilla_dom_ipc_nsICachedFileDescriptorListener_h +#define mozilla_dom_ipc_nsICachedFileDescriptorListener_h + +#include "nsISupports.h" + +#ifndef NS_NO_VTABLE +#define NS_NO_VTABLE +#endif + +#define NS_ICACHEDFILEDESCRIPTORLISTENER_IID \ + {0x2cedaee0, 0x6ef2, 0x4f60, {0x9a, 0x6c, 0xdf, 0x4e, 0x4d, 0x65, 0x6a, 0xf7}} + +class nsAString; + +namespace mozilla { +namespace ipc { +class FileDescriptor; +} +} + +class NS_NO_VTABLE nsICachedFileDescriptorListener : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICACHEDFILEDESCRIPTORLISTENER_IID) + + virtual void + OnCachedFileDescriptor(const nsAString& aPath, + const mozilla::ipc::FileDescriptor& aFD) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsICachedFileDescriptorListener, + NS_ICACHEDFILEDESCRIPTORLISTENER_IID) + +#endif // mozilla_dom_ipc_nsICachedFileDescriptorListener_h
--- a/ipc/glue/FileDescriptor.cpp +++ b/ipc/glue/FileDescriptor.cpp @@ -37,17 +37,18 @@ FileDescriptor::FileDescriptor(PlatformH mHandleCreatedByOtherProcessWasUsed(false) { DuplicateInCurrentProcess(aHandle); } void FileDescriptor::DuplicateInCurrentProcess(PlatformHandleType aHandle) { - MOZ_ASSERT(!mHandleCreatedByOtherProcess); + MOZ_ASSERT_IF(mHandleCreatedByOtherProcess, + mHandleCreatedByOtherProcessWasUsed); if (IsValid(aHandle)) { PlatformHandleType newHandle; #ifdef XP_WIN if (DuplicateHandle(GetCurrentProcess(), aHandle, GetCurrentProcess(), &newHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) { #else // XP_WIN if ((newHandle = dup(aHandle)) != INVALID_HANDLE) {
--- a/ipc/glue/FileDescriptor.h +++ b/ipc/glue/FileDescriptor.h @@ -48,17 +48,19 @@ public: // This should only ever be created by IPDL. struct IPDLPrivate {}; FileDescriptor(); FileDescriptor(const FileDescriptor& aOther) { - *this = aOther; + // Don't use operator= here because that will call + // CloseCurrentProcessHandle() on this (uninitialized) object. + Assign(aOther); } FileDescriptor(PlatformHandleType aHandle); FileDescriptor(const IPDLPrivate&, const PickleType& aPickle) #ifdef XP_WIN : mHandle(aPickle) #else @@ -72,28 +74,17 @@ public: { CloseCurrentProcessHandle(); } FileDescriptor& operator=(const FileDescriptor& aOther) { CloseCurrentProcessHandle(); - - if (aOther.mHandleCreatedByOtherProcess) { - mHandleCreatedByOtherProcess = true; - mHandleCreatedByOtherProcessWasUsed = - aOther.mHandleCreatedByOtherProcessWasUsed; - mHandle = aOther.PlatformHandle(); - } else { - DuplicateInCurrentProcess(aOther.PlatformHandle()); - mHandleCreatedByOtherProcess = false; - mHandleCreatedByOtherProcessWasUsed = false; - } - + Assign(aOther); return *this; } // Performs platform-specific actions to duplicate mHandle in the other // process (e.g. dup() on POSIX, DuplicateHandle() on Windows). Returns a // pickled value that can be passed to the other process via IPC. PickleType ShareTo(const IPDLPrivate&, ProcessHandle aOtherProcess) const; @@ -117,16 +108,31 @@ public: bool operator==(const FileDescriptor& aOther) const { return mHandle == aOther.mHandle; } private: + void + Assign(const FileDescriptor& aOther) + { + if (aOther.mHandleCreatedByOtherProcess) { + mHandleCreatedByOtherProcess = true; + mHandleCreatedByOtherProcessWasUsed = + aOther.mHandleCreatedByOtherProcessWasUsed; + mHandle = aOther.PlatformHandle(); + } else { + DuplicateInCurrentProcess(aOther.PlatformHandle()); + mHandleCreatedByOtherProcess = false; + mHandleCreatedByOtherProcessWasUsed = false; + } + } + static bool IsValid(PlatformHandleType aHandle); void DuplicateInCurrentProcess(PlatformHandleType aHandle); void CloseCurrentProcessHandle();
new file mode 100644 --- /dev/null +++ b/ipc/glue/FileDescriptorUtils.cpp @@ -0,0 +1,81 @@ +/* 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 "FileDescriptorUtils.h" + +#include "nsIEventTarget.h" + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "prio.h" +#include "private/pprio.h" + +using mozilla::ipc::CloseFileRunnable; + +#ifdef DEBUG + +CloseFileRunnable::CloseFileRunnable(const FileDescriptor& aFileDescriptor) +: mFileDescriptor(aFileDescriptor) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aFileDescriptor.IsValid()); +} + +#endif // DEBUG + +CloseFileRunnable::~CloseFileRunnable() +{ + if (mFileDescriptor.IsValid()) { + // It's probably safer to take the main thread IO hit here rather than leak + // the file descriptor. + CloseFile(); + } +} + +NS_IMPL_THREADSAFE_ISUPPORTS1(CloseFileRunnable, nsIRunnable) + +void +CloseFileRunnable::Dispatch() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIEventTarget> eventTarget = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + NS_ENSURE_TRUE_VOID(eventTarget); + + nsresult rv = eventTarget->Dispatch(this, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS_VOID(rv); +} + +void +CloseFileRunnable::CloseFile() +{ + // It's possible for this to happen on the main thread if the dispatch to the + // stream service fails so we can't assert the thread on which we're running. + + MOZ_ASSERT(mFileDescriptor.IsValid()); + + PRFileDesc* fd = + PR_ImportFile(PROsfd(mFileDescriptor.PlatformHandle())); + NS_WARN_IF_FALSE(fd, "Failed to import file handle!"); + + mFileDescriptor = FileDescriptor(); + + if (fd) { + PR_Close(fd); + fd = nullptr; + } +} + +NS_IMETHODIMP +CloseFileRunnable::Run() +{ + MOZ_ASSERT(!NS_IsMainThread()); + + CloseFile(); + return NS_OK; +}
new file mode 100644 --- /dev/null +++ b/ipc/glue/FileDescriptorUtils.h @@ -0,0 +1,47 @@ + +/* 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/. */ + +#ifndef mozilla_ipc_FileDescriptorUtils_h +#define mozilla_ipc_FileDescriptorUtils_h + +#include "mozilla/ipc/FileDescriptor.h" +#include "nsIRunnable.h" + +namespace mozilla { +namespace ipc { + +// When Dispatch() is called (from main thread) this class arranges to close the +// provided FileDescriptor on one of the socket transport service threads (to +// avoid main thread I/O). +class CloseFileRunnable : public nsIRunnable +{ + typedef mozilla::ipc::FileDescriptor FileDescriptor; + + FileDescriptor mFileDescriptor; + +public: + CloseFileRunnable(const FileDescriptor& aFileDescriptor) +#ifdef DEBUG + ; +#else + : mFileDescriptor(aFileDescriptor) + { } +#endif + + NS_DECL_ISUPPORTS + NS_DECL_NSIRUNNABLE + + void Dispatch(); + +private: + ~CloseFileRunnable(); + + void CloseFile(); +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_FileDescriptorUtils_h
--- a/ipc/glue/Makefile.in +++ b/ipc/glue/Makefile.in @@ -21,16 +21,17 @@ EXPORTS_NAMESPACES = ipc mozilla/ipc EXPORTS_ipc = IPCMessageUtils.h EXPORTS_mozilla/ipc = \ AsyncChannel.h \ BrowserProcessSubThread.h \ CrossProcessMutex.h \ FileDescriptor.h \ + FileDescriptorUtils.h \ GeckoChildProcessHost.h \ InputStreamUtils.h \ IOThreadChild.h \ ProcessChild.h \ ProtocolUtils.h \ RPCChannel.h \ SharedMemory.h \ SharedMemoryBasic.h \ @@ -66,16 +67,17 @@ EXPORTS_mozilla/ipc += SharedMemoryBasic else EXPORTS_mozilla/ipc += SharedMemoryBasic_chromium.h endif #} CPPSRCS += \ AsyncChannel.cpp \ BrowserProcessSubThread.cpp \ FileDescriptor.cpp \ + FileDescriptorUtils.cpp \ GeckoChildProcessHost.cpp \ InputStreamUtils.cpp \ MessagePump.cpp \ ProcessChild.cpp \ ProtocolUtils.cpp \ RPCChannel.cpp \ ScopedXREEmbed.cpp \ SharedMemory.cpp \
--- a/modules/libjar/nsJARChannel.cpp +++ b/modules/libjar/nsJARChannel.cpp @@ -13,17 +13,16 @@ #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsIViewSourceChannel.h" #include "nsChannelProperties.h" #include "nsIScriptSecurityManager.h" #include "nsIPrincipal.h" #include "nsIFileURL.h" -#include "nsXULAppAPI.h" #include "mozilla/Preferences.h" #include "mozilla/net/RemoteOpenFileChild.h" #include "nsITabChild.h" using namespace mozilla; using namespace mozilla::net; @@ -344,19 +343,19 @@ nsJARChannel::LookupFile() // try to get a nsIFile directly from the url, which will often succeed. { nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mJarBaseURI); if (fileURL) fileURL->GetFile(getter_AddRefs(mJarFile)); } // if we're in child process and have special "remoteopenfile:://" scheme, // create special nsIFile that gets file handle from parent when opened. - if (!mJarFile && XRE_GetProcessType() != GeckoProcessType_Default) { + if (!mJarFile && !gJarHandler->IsMainProcess()) { nsAutoCString scheme; - nsresult rv = mJarBaseURI->GetScheme(scheme); + rv = mJarBaseURI->GetScheme(scheme); if (NS_SUCCEEDED(rv) && scheme.EqualsLiteral("remoteopenfile")) { nsRefPtr<RemoteOpenFileChild> remoteFile = new RemoteOpenFileChild(); rv = remoteFile->Init(mJarBaseURI); NS_ENSURE_SUCCESS(rv, rv); mJarFile = remoteFile; nsIZipReaderCache *jarCache = gJarHandler->JarCache(); if (jarCache) { @@ -364,23 +363,30 @@ nsJARChannel::LookupFile() rv = jarCache->IsCached(mJarFile, &cached); if (NS_SUCCEEDED(rv) && cached) { // zipcache already has file mmapped: don't open on parent, // just return and proceed to cache hit in CreateJarInput() return NS_OK; } } + mOpeningRemote = true; + + if (gJarHandler->RemoteOpenFileInProgress(remoteFile, this)) { + // JarHandler will trigger OnRemoteFileOpen() after the first + // request for this file completes and we'll get a JAR cache + // hit. + return NS_OK; + } + // Open file on parent: OnRemoteFileOpenComplete called when done nsCOMPtr<nsITabChild> tabChild; NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, tabChild); rv = remoteFile->AsyncRemoteFileOpen(PR_RDONLY, this, tabChild.get()); NS_ENSURE_SUCCESS(rv, rv); - - mOpeningRemote = true; } } // try to handle a nested jar if (!mJarFile) { nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(mJarBaseURI); if (jarURI) { nsCOMPtr<nsIFileURL> fileURL; nsCOMPtr<nsIURI> innerJarURI; @@ -392,16 +398,48 @@ nsJARChannel::LookupFile() jarURI->GetJAREntry(mInnerJarEntry); } } } return rv; } +nsresult +nsJARChannel::OpenLocalFile() +{ + MOZ_ASSERT(mIsPending); + + // Local files are always considered safe. + mIsUnsafe = false; + + nsRefPtr<nsJARInputThunk> input; + nsresult rv = CreateJarInput(gJarHandler->JarCache(), + getter_AddRefs(input)); + if (NS_SUCCEEDED(rv)) { + // Create input stream pump and call AsyncRead as a block. + rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input); + if (NS_SUCCEEDED(rv)) + rv = mPump->AsyncRead(this, nullptr); + } + + return rv; +} + +void +nsJARChannel::NotifyError(nsresult aError) +{ + MOZ_ASSERT(NS_FAILED(aError)); + + mStatus = aError; + + OnStartRequest(nullptr, nullptr); + OnStopRequest(nullptr, nullptr, aError); +} + //----------------------------------------------------------------------------- // nsIRequest //----------------------------------------------------------------------------- NS_IMETHODIMP nsJARChannel::GetName(nsACString &result) { return mJarURI->GetSpec(result); @@ -744,27 +782,17 @@ nsJARChannel::AsyncOpen(nsIStreamListene rv = NS_NewDownloader(getter_AddRefs(mDownloader), this); if (NS_SUCCEEDED(rv)) rv = NS_OpenURI(mDownloader, nullptr, mJarBaseURI, nullptr, mLoadGroup, mCallbacks, mLoadFlags & ~(LOAD_DOCUMENT_URI | LOAD_CALL_CONTENT_SNIFFERS)); } else if (mOpeningRemote) { // nothing to do: already asked parent to open file. } else { - // local files are always considered safe - mIsUnsafe = false; - - nsRefPtr<nsJARInputThunk> input; - rv = CreateJarInput(gJarHandler->JarCache(), getter_AddRefs(input)); - if (NS_SUCCEEDED(rv)) { - // create input stream pump and call AsyncRead as a block - rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input); - if (NS_SUCCEEDED(rv)) - rv = mPump->AsyncRead(this, nullptr); - } + rv = OpenLocalFile(); } if (NS_FAILED(rv)) { mIsPending = false; mListenerContext = nullptr; mListener = nullptr; return rv; } @@ -897,56 +925,43 @@ nsJARChannel::OnDownloadComplete(nsIDown rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input); if (NS_SUCCEEDED(rv)) rv = mPump->AsyncRead(this, nullptr); } status = rv; } if (NS_FAILED(status)) { - mStatus = status; - OnStartRequest(nullptr, nullptr); - OnStopRequest(nullptr, nullptr, status); + NotifyError(status); } return NS_OK; } //----------------------------------------------------------------------------- // nsIRemoteOpenFileListener //----------------------------------------------------------------------------- nsresult nsJARChannel::OnRemoteFileOpenComplete(nsresult aOpenStatus) { nsresult rv = aOpenStatus; - if (NS_SUCCEEDED(rv)) { - // files on parent are always considered safe - mIsUnsafe = false; - - nsRefPtr<nsJARInputThunk> input; - rv = CreateJarInput(gJarHandler->JarCache(), getter_AddRefs(input)); - if (NS_SUCCEEDED(rv)) { - // create input stream pump and call AsyncRead as a block - rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input); - if (NS_SUCCEEDED(rv)) - rv = mPump->AsyncRead(this, nullptr); - } + // NS_ERROR_ALREADY_OPENED here means we'll hit JAR cache in + // OpenLocalFile(). + if (NS_SUCCEEDED(rv) || rv == NS_ERROR_ALREADY_OPENED) { + rv = OpenLocalFile(); } if (NS_FAILED(rv)) { - mStatus = rv; - OnStartRequest(nullptr, nullptr); - OnStopRequest(nullptr, nullptr, mStatus); + NotifyError(rv); } return NS_OK; } - //----------------------------------------------------------------------------- // nsIStreamListener //----------------------------------------------------------------------------- NS_IMETHODIMP nsJARChannel::OnStartRequest(nsIRequest *req, nsISupports *ctx) { LOG(("nsJARChannel::OnStartRequest [this=%x %s]\n", this, mSpec.get()));
--- a/modules/libjar/nsJARChannel.h +++ b/modules/libjar/nsJARChannel.h @@ -46,16 +46,18 @@ public: nsJARChannel(); virtual ~nsJARChannel(); nsresult Init(nsIURI *uri); private: nsresult CreateJarInput(nsIZipReaderCache *, nsJARInputThunk **); nsresult LookupFile(); + nsresult OpenLocalFile(); + void NotifyError(nsresult aError); #if defined(PR_LOGGING) nsCString mSpec; #endif bool mOpened; nsCOMPtr<nsIJARURI> mJarURI;
--- a/modules/libjar/nsJARProtocolHandler.cpp +++ b/modules/libjar/nsJARProtocolHandler.cpp @@ -12,27 +12,37 @@ #include "nsJARURI.h" #include "nsIURL.h" #include "nsJARChannel.h" #include "nsXPIDLString.h" #include "nsString.h" #include "nsNetCID.h" #include "nsIMIMEService.h" #include "nsMimeTypes.h" +#include "nsIRemoteOpenFileListener.h" +#include "nsIHashable.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" static NS_DEFINE_CID(kZipReaderCacheCID, NS_ZIPREADERCACHE_CID); #define NS_JAR_CACHE_SIZE 32 //----------------------------------------------------------------------------- nsJARProtocolHandler *gJarHandler = nullptr; nsJARProtocolHandler::nsJARProtocolHandler() +: mIsMainProcess(XRE_GetProcessType() == GeckoProcessType_Default) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mIsMainProcess) { + mRemoteFileListeners.Init(); + } } nsJARProtocolHandler::~nsJARProtocolHandler() { } nsresult nsJARProtocolHandler::Init() @@ -50,16 +60,77 @@ nsIMIMEService * nsJARProtocolHandler::MimeService() { if (!mMimeService) mMimeService = do_GetService("@mozilla.org/mime;1"); return mMimeService.get(); } +bool +nsJARProtocolHandler::RemoteOpenFileInProgress( + nsIHashable *aRemoteFile, + nsIRemoteOpenFileListener *aListener) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aRemoteFile); + MOZ_ASSERT(aListener); + + if (IsMainProcess()) { + MOZ_NOT_REACHED("Shouldn't be called in the main process!"); + return false; + } + + RemoteFileListenerArray *listeners; + if (mRemoteFileListeners.Get(aRemoteFile, &listeners)) { + listeners->AppendElement(aListener); + return true; + } + + // We deliberately don't put the listener in the new array since the first + // load is handled differently. + mRemoteFileListeners.Put(aRemoteFile, new RemoteFileListenerArray()); + return false; +} + +void +nsJARProtocolHandler::RemoteOpenFileComplete(nsIHashable *aRemoteFile, + nsresult aStatus) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aRemoteFile); + + if (IsMainProcess()) { + MOZ_NOT_REACHED("Shouldn't be called in the main process!"); + return; + } + + RemoteFileListenerArray *tempListeners; + if (!mRemoteFileListeners.Get(aRemoteFile, &tempListeners)) { + return; + } + + // Save the listeners in a stack array. The call to Remove() below will + // delete the tempListeners array. + RemoteFileListenerArray listeners; + tempListeners->SwapElements(listeners); + + mRemoteFileListeners.Remove(aRemoteFile); + + // Technically we must fail OnRemoteFileComplete() since OpenNSPRFileDesc() + // won't succeed here. We've trained nsJARChannel to recognize + // NS_ERROR_ALREADY_OPENED in this case as "proceed to JAR cache hit." + nsresult status = NS_SUCCEEDED(aStatus) ? NS_ERROR_ALREADY_OPENED : aStatus; + + uint32_t count = listeners.Length(); + for (uint32_t index = 0; index < count; index++) { + listeners[index]->OnRemoteFileOpenComplete(status); + } +} + NS_IMPL_THREADSAFE_ISUPPORTS3(nsJARProtocolHandler, nsIJARProtocolHandler, nsIProtocolHandler, nsISupportsWeakReference) nsJARProtocolHandler* nsJARProtocolHandler::GetSingleton() {
--- a/modules/libjar/nsJARProtocolHandler.h +++ b/modules/libjar/nsJARProtocolHandler.h @@ -8,20 +8,28 @@ #include "nsIJARProtocolHandler.h" #include "nsIProtocolHandler.h" #include "nsIJARURI.h" #include "nsIZipReader.h" #include "nsIMIMEService.h" #include "nsWeakReference.h" #include "nsCOMPtr.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" + +class nsIHashable; +class nsIRemoteOpenFileListener; class nsJARProtocolHandler : public nsIJARProtocolHandler , public nsSupportsWeakReference { + typedef nsAutoTArray<nsCOMPtr<nsIRemoteOpenFileListener>, 5> + RemoteFileListenerArray; + public: NS_DECL_ISUPPORTS NS_DECL_NSIPROTOCOLHANDLER NS_DECL_NSIJARPROTOCOLHANDLER // nsJARProtocolHandler methods: nsJARProtocolHandler(); virtual ~nsJARProtocolHandler(); @@ -29,19 +37,32 @@ public: static nsJARProtocolHandler *GetSingleton(); nsresult Init(); // returns non addref'ed pointer. nsIMIMEService *MimeService(); nsIZipReaderCache *JarCache() { return mJARCache; } + bool IsMainProcess() const { return mIsMainProcess; } + + bool RemoteOpenFileInProgress(nsIHashable *aRemoteFile, + nsIRemoteOpenFileListener *aListener); + void RemoteOpenFileComplete(nsIHashable *aRemoteFile, nsresult aStatus); + protected: nsCOMPtr<nsIZipReaderCache> mJARCache; nsCOMPtr<nsIMIMEService> mMimeService; + + // Holds lists of RemoteOpenFileChild (not including the 1st) that have + // requested the same file from parent. + nsClassHashtable<nsHashableHashKey, RemoteFileListenerArray> + mRemoteFileListeners; + + bool mIsMainProcess; }; extern nsJARProtocolHandler *gJarHandler; #define NS_JARPROTOCOLHANDLER_CLASSNAME \ "nsJarProtocolHandler" #define NS_JARPROTOCOLHANDLER_CID \ { /* 0xc7e410d4-0x85f2-11d3-9f63-006008a6efe9 */ \
--- a/netwerk/ipc/Makefile.in +++ b/netwerk/ipc/Makefile.in @@ -41,13 +41,14 @@ CPPSRCS = \ ChannelEventQueue.cpp \ RemoteOpenFileParent.cpp \ RemoteOpenFileChild.cpp \ $(NULL) LOCAL_INCLUDES += \ -I$(srcdir)/../protocol/http \ -I$(srcdir)/../base/src \ + -I$(topsrcdir)/modules/libjar \ $(NULL) include $(topsrcdir)/config/config.mk include $(topsrcdir)/ipc/chromium/chromium-config.mk include $(topsrcdir)/config/rules.mk
--- a/netwerk/ipc/RemoteOpenFileChild.cpp +++ b/netwerk/ipc/RemoteOpenFileChild.cpp @@ -1,44 +1,94 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et tw=80 : */ /* 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 "mozilla/net/RemoteOpenFileChild.h" + +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "mozilla/ipc/URIUtils.h" #include "mozilla/net/NeckoChild.h" -#include "mozilla/net/RemoteOpenFileChild.h" +#include "nsThreadUtils.h" +#include "nsJARProtocolHandler.h" #include "nsIRemoteOpenFileListener.h" -#include "mozilla/ipc/URIUtils.h" // needed to alloc/free NSPR file descriptors #include "private/pprio.h" using namespace mozilla::ipc; namespace mozilla { namespace net { -NS_IMPL_THREADSAFE_ISUPPORTS2(RemoteOpenFileChild, +//----------------------------------------------------------------------------- +// Helper class to dispatch events async on windows/OSX +//----------------------------------------------------------------------------- + +class CallsListenerInNewEvent : public nsRunnable +{ +public: + CallsListenerInNewEvent(nsIRemoteOpenFileListener *aListener, nsresult aRv) + : mListener(aListener), mRV(aRv) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aListener); + } + + void Dispatch() + { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv = NS_DispatchToCurrentThread(this); + NS_ENSURE_SUCCESS_VOID(rv); + } + +private: + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mListener); + + mListener->OnRemoteFileOpenComplete(mRV); + return NS_OK; + } + + nsCOMPtr<nsIRemoteOpenFileListener> mListener; + nsresult mRV; +}; + +//----------------------------------------------------------------------------- +// RemoteOpenFileChild +//----------------------------------------------------------------------------- + +NS_IMPL_THREADSAFE_ISUPPORTS3(RemoteOpenFileChild, nsIFile, - nsIHashable) - + nsIHashable, + nsICachedFileDescriptorListener) RemoteOpenFileChild::RemoteOpenFileChild(const RemoteOpenFileChild& other) - : mNSPRFileDesc(other.mNSPRFileDesc) + : mTabChild(other.mTabChild) + , mNSPRFileDesc(other.mNSPRFileDesc) , mAsyncOpenCalled(other.mAsyncOpenCalled) , mNSPROpenCalled(other.mNSPROpenCalled) { // Note: don't clone mListener or we'll have a refcount leak. other.mURI->Clone(getter_AddRefs(mURI)); other.mFile->Clone(getter_AddRefs(mFile)); } RemoteOpenFileChild::~RemoteOpenFileChild() { + if (mListener) { + NotifyListener(NS_ERROR_UNEXPECTED); + } + if (mNSPRFileDesc) { // If we handed out fd we shouldn't have pointer to it any more. MOZ_ASSERT(!mNSPROpenCalled); // PR_Close both closes the file and deallocates the PRFileDesc PR_Close(mNSPRFileDesc); } } @@ -97,118 +147,181 @@ RemoteOpenFileChild::AsyncRemoteFileOpen if (mAsyncOpenCalled) { return NS_ERROR_ALREADY_OPENED; } if (aFlags != PR_RDONLY) { return NS_ERROR_NOT_AVAILABLE; } - mozilla::dom::TabChild* tabChild = nullptr; - if (aTabChild) { - tabChild = static_cast<mozilla::dom::TabChild*>(aTabChild); - } - if (MissingRequiredTabChild(tabChild, "remoteopenfile")) { + mTabChild = static_cast<TabChild*>(aTabChild); + + if (MissingRequiredTabChild(mTabChild, "remoteopenfile")) { return NS_ERROR_ILLEGAL_VALUE; } #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA) // Windows/OSX desktop builds skip remoting, and just open file in child // process when asked for NSPR handle - aListener->OnRemoteFileOpenComplete(NS_OK); + nsRefPtr<CallsListenerInNewEvent> runnable = + new CallsListenerInNewEvent(aListener, NS_OK); + runnable->Dispatch(); + mAsyncOpenCalled = true; return NS_OK; #else + nsString path; + if (NS_FAILED(mFile->GetPath(path))) { + MOZ_NOT_REACHED("Couldn't get path from file!"); + } + + if (mTabChild) { + if (mTabChild->GetCachedFileDescriptor(path, this)) { + // The file descriptor was found in the cache and OnCachedFileDescriptor() + // will be called with it. + return NS_OK; + } + } + URIParams uri; SerializeURI(mURI, uri); - gNeckoChild->SendPRemoteOpenFileConstructor(this, uri, tabChild); + gNeckoChild->SendPRemoteOpenFileConstructor(this, uri, mTabChild); // Can't seem to reply from within IPDL Parent constructor, so send open as // separate message SendAsyncOpenFile(); // The chrome process now has a logical ref to us until we call Send__delete AddIPDLReference(); mListener = aListener; mAsyncOpenCalled = true; return NS_OK; #endif } +void +RemoteOpenFileChild::OnCachedFileDescriptor(const nsAString& aPath, + const FileDescriptor& aFD) +{ +#ifdef DEBUG + if (!aPath.IsEmpty()) { + MOZ_ASSERT(mFile); + + nsString path; + if (NS_FAILED(mFile->GetPath(path))) { + MOZ_NOT_REACHED("Couldn't get path from file!"); + } + + MOZ_ASSERT(path == aPath, "Paths don't match!"); + } +#endif + + HandleFileDescriptorAndNotifyListener(aFD, /* aFromRecvFileOpened */ false); +} + +void +RemoteOpenFileChild::HandleFileDescriptorAndNotifyListener( + const FileDescriptor& aFD, + bool aFromRecvFileOpened) +{ +#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA) + MOZ_NOT_REACHED("OS X and Windows shouldn't be doing IPDL here"); +#else + if (!mListener) { + // We already notified our listener (either in response to a cached file + // descriptor callback or through the normal messaging mechanism). Close the + // file descriptor if it is valid. + if (aFD.IsValid()) { + nsRefPtr<CloseFileRunnable> runnable = new CloseFileRunnable(aFD); + runnable->Dispatch(); + } + return; + } + + MOZ_ASSERT(!mNSPRFileDesc); + + nsRefPtr<TabChild> tabChild; + mTabChild.swap(tabChild); + + // If there is a pending callback and we're being called from IPDL then we + // need to cancel it. + if (tabChild && aFromRecvFileOpened) { + nsString path; + if (NS_FAILED(mFile->GetPath(path))) { + MOZ_NOT_REACHED("Couldn't get path from file!"); + } + + tabChild->CancelCachedFileDescriptorCallback(path, this); + } + + if (aFD.IsValid()) { + mNSPRFileDesc = PR_ImportFile(aFD.PlatformHandle()); + if (!mNSPRFileDesc) { + NS_WARNING("Failed to import file handle!"); + } + } + + NotifyListener(mNSPRFileDesc ? NS_OK : NS_ERROR_FILE_NOT_FOUND); +#endif +} + +void +RemoteOpenFileChild::NotifyListener(nsresult aResult) +{ + MOZ_ASSERT(mListener); + mListener->OnRemoteFileOpenComplete(aResult); + mListener = nullptr; // release ref to listener + + nsRefPtr<nsJARProtocolHandler> handler(gJarHandler); + NS_WARN_IF_FALSE(handler, "nsJARProtocolHandler is already gone!"); + + if (handler) { + handler->RemoteOpenFileComplete(this, aResult); + } +} //----------------------------------------------------------------------------- // RemoteOpenFileChild::PRemoteOpenFileChild //----------------------------------------------------------------------------- bool RemoteOpenFileChild::RecvFileOpened(const FileDescriptor& aFD) { #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA) - NS_NOTREACHED("osX and Windows shouldn't be doing IPDL here"); + NS_NOTREACHED("OS X and Windows shouldn't be doing IPDL here"); #else - if (!aFD.IsValid()) { - return RecvFileDidNotOpen(); - } - - MOZ_ASSERT(!mNSPRFileDesc); - mNSPRFileDesc = PR_AllocFileDesc(aFD.PlatformHandle(), PR_GetFileMethods()); - - MOZ_ASSERT(mListener); - mListener->OnRemoteFileOpenComplete(NS_OK); - mListener = nullptr; // release ref to listener + HandleFileDescriptorAndNotifyListener(aFD, /* aFromRecvFileOpened */ true); // This calls NeckoChild::DeallocPRemoteOpenFile(), which deletes |this| if // IPDL holds the last reference. Don't rely on |this| existing after here! Send__delete__(this); #endif return true; } bool RemoteOpenFileChild::RecvFileDidNotOpen() { #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA) - NS_NOTREACHED("osX and Windows shouldn't be doing IPDL here"); + NS_NOTREACHED("OS X and Windows shouldn't be doing IPDL here"); #else - MOZ_ASSERT(!mNSPRFileDesc); - printf_stderr("RemoteOpenFileChild: file was not opened!\n"); - - MOZ_ASSERT(mListener); - mListener->OnRemoteFileOpenComplete(NS_ERROR_FILE_NOT_FOUND); - mListener = nullptr; // release ref to listener + HandleFileDescriptorAndNotifyListener(FileDescriptor(), + /* aFromRecvFileOpened */ true); // This calls NeckoChild::DeallocPRemoteOpenFile(), which deletes |this| if // IPDL holds the last reference. Don't rely on |this| existing after here! Send__delete__(this); #endif return true; } -void -RemoteOpenFileChild::AddIPDLReference() -{ - AddRef(); -} - -void -RemoteOpenFileChild::ReleaseIPDLReference() -{ - // if we haven't gotten fd from parent yet, we're not going to. - if (mListener) { - mListener->OnRemoteFileOpenComplete(NS_ERROR_UNEXPECTED); - mListener = nullptr; - } - - Release(); -} - //----------------------------------------------------------------------------- // RemoteOpenFileChild::nsIFile functions that we override logic for //----------------------------------------------------------------------------- NS_IMETHODIMP RemoteOpenFileChild::Clone(nsIFile **file) { *file = new RemoteOpenFileChild(*this); @@ -657,9 +770,8 @@ RemoteOpenFileChild::GetHashCode(uint32_ if (hashable) { return hashable->GetHashCode(aResult); } return NS_ERROR_UNEXPECTED; } } // namespace net } // namespace mozilla -
--- a/netwerk/ipc/RemoteOpenFileChild.h +++ b/netwerk/ipc/RemoteOpenFileChild.h @@ -4,20 +4,26 @@ * 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/. */ #ifndef _RemoteOpenFileChild_h #define _RemoteOpenFileChild_h #include "mozilla/dom/TabChild.h" #include "mozilla/net/PRemoteOpenFileChild.h" +#include "nsICachedFileDescriptorListener.h" #include "nsILocalFile.h" #include "nsIRemoteOpenFileListener.h" namespace mozilla { + +namespace ipc { +class FileDescriptor; +} + namespace net { /** * RemoteOpenFileChild: a thin wrapper around regular nsIFile classes that does * IPC to open a file handle on parent instead of child. Used when we can't * open file handle on child (don't have permission), but we don't want the * overhead of shipping all I/O traffic across IPDL. Example: JAR files. * @@ -33,55 +39,75 @@ namespace net { * * This class should only be instantiated in a child process. * */ class RemoteOpenFileChild MOZ_FINAL : public PRemoteOpenFileChild , public nsIFile , public nsIHashable + , public nsICachedFileDescriptorListener { + typedef mozilla::dom::TabChild TabChild; + typedef mozilla::ipc::FileDescriptor FileDescriptor; + public: RemoteOpenFileChild() : mNSPRFileDesc(nullptr) , mAsyncOpenCalled(false) , mNSPROpenCalled(false) {} virtual ~RemoteOpenFileChild(); NS_DECL_ISUPPORTS NS_DECL_NSIFILE NS_DECL_NSIHASHABLE // URI must be scheme 'remoteopenfile://': otherwise looks like a file:// uri. nsresult Init(nsIURI* aRemoteOpenUri); - void AddIPDLReference(); - void ReleaseIPDLReference(); - // Send message to parent to tell it to open file handle for file. // TabChild is required, for IPC security. // Note: currently only PR_RDONLY is supported for 'flags' nsresult AsyncRemoteFileOpen(int32_t aFlags, nsIRemoteOpenFileListener* aListener, nsITabChild* aTabChild); + + void ReleaseIPDLReference() + { + Release(); + } + private: RemoteOpenFileChild(const RemoteOpenFileChild& other); protected: + void AddIPDLReference() + { + AddRef(); + } + virtual bool RecvFileOpened(const FileDescriptor&); virtual bool RecvFileDidNotOpen(); + virtual void OnCachedFileDescriptor(const nsAString& aPath, + const FileDescriptor& aFD) MOZ_OVERRIDE; + + void HandleFileDescriptorAndNotifyListener(const FileDescriptor&, + bool aFromRecvFileOpened); + + void NotifyListener(nsresult aResult); + // regular nsIFile object, that we forward most calls to. nsCOMPtr<nsIFile> mFile; nsCOMPtr<nsIURI> mURI; nsCOMPtr<nsIRemoteOpenFileListener> mListener; + nsRefPtr<TabChild> mTabChild; PRFileDesc* mNSPRFileDesc; bool mAsyncOpenCalled; bool mNSPROpenCalled; }; } // namespace net } // namespace mozilla #endif // _RemoteOpenFileChild_h -