Bug 835698 - 'Pre-open() and send the fd for app process's application.zip'. r=jduell.

This commit is contained in:
Ben Turner 2013-02-09 17:59:47 +00:00
parent 5c728f13b8
commit b66d3a5418
19 changed files with 937 additions and 114 deletions

View File

@ -22,7 +22,10 @@ ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT))
TEST_DIRS += tests
endif
EXPORTS = PCOMContentPermissionRequestChild.h
EXPORTS = \
nsICachedFileDescriptorListener.h \
PCOMContentPermissionRequestChild.h \
$(NULL)
EXPORTS_NAMESPACES = \
mozilla \

View File

@ -31,6 +31,7 @@ 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;
@ -290,6 +291,8 @@ child:
LoadURL(nsCString uri);
CacheFileDescriptor(nsString path, FileDescriptor fd);
UpdateDimensions(nsRect rect, nsIntSize size, ScreenOrientation orientation) compress;
UpdateFrame(FrameMetrics frame) compress;

View File

@ -18,6 +18,7 @@
#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"
@ -37,6 +38,7 @@
#include "mozilla/dom/Element.h"
#include "nsIAppsService.h"
#include "nsIBaseWindow.h"
#include "nsICachedFileDescriptorListener.h"
#include "nsIComponentManager.h"
#include "nsIDocumentInlines.h"
#include "nsIDOMClassInfo.h"
@ -121,6 +123,98 @@ public:
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
@ -1117,6 +1211,130 @@ TabChild::RecvLoadURL(const nsCString& uri)
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()
{

View File

@ -54,6 +54,7 @@
#include "mozilla/dom/TabContext.h"
struct gfxMatrix;
class nsICachedFileDescriptorListener;
namespace mozilla {
namespace layout {
@ -196,6 +197,9 @@ public:
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);
@ -317,6 +321,15 @@ public:
*/
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,
@ -412,6 +425,9 @@ private:
return utils;
}
class CachedFileDescriptorInfo;
class CachedFileDescriptorCallbackRunnable;
nsCOMPtr<nsIWebNavigation> mWebNav;
nsCOMPtr<nsIWidget> mWidget;
nsCOMPtr<nsIURI> mLastURI;
@ -430,6 +446,9 @@ private:
// 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;

View File

@ -50,6 +50,7 @@
#include "nsSerializationHelper.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "private/pprio.h"
#include "StructuredCloneUtils.h"
#include "TabChild.h"
#include <algorithm>
@ -66,6 +67,116 @@ using namespace mozilla::dom::indexedDB;
// 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 {
@ -93,6 +204,7 @@ TabParent::TabParent(const TabContext& aContext)
, mUpdatedDimensions(false)
, mMarkedDestroying(false)
, mIsDestroyed(false)
, mAppPackageFileDescriptorSent(false)
{
}
@ -230,23 +342,66 @@ TabParent::AnswerCreateWindow(PBrowserParent** retval)
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

View File

@ -304,6 +304,8 @@ private:
// 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

View File

@ -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

View File

@ -42,7 +42,8 @@ FileDescriptor::FileDescriptor(PlatformHandleType aHandle)
void
FileDescriptor::DuplicateInCurrentProcess(PlatformHandleType aHandle)
{
MOZ_ASSERT(!mHandleCreatedByOtherProcess);
MOZ_ASSERT_IF(mHandleCreatedByOtherProcess,
mHandleCreatedByOtherProcessWasUsed);
if (IsValid(aHandle)) {
PlatformHandleType newHandle;

View File

@ -53,7 +53,9 @@ public:
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);
@ -77,18 +79,7 @@ public:
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;
}
@ -122,6 +113,21 @@ public:
}
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);

View File

@ -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;
}

View File

@ -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

View File

@ -26,6 +26,7 @@ EXPORTS_mozilla/ipc = \
BrowserProcessSubThread.h \
CrossProcessMutex.h \
FileDescriptor.h \
FileDescriptorUtils.h \
GeckoChildProcessHost.h \
InputStreamUtils.h \
IOThreadChild.h \
@ -71,6 +72,7 @@ CPPSRCS += \
AsyncChannel.cpp \
BrowserProcessSubThread.cpp \
FileDescriptor.cpp \
FileDescriptorUtils.cpp \
GeckoChildProcessHost.cpp \
InputStreamUtils.cpp \
MessagePump.cpp \

View File

@ -18,7 +18,6 @@
#include "nsIScriptSecurityManager.h"
#include "nsIPrincipal.h"
#include "nsIFileURL.h"
#include "nsXULAppAPI.h"
#include "mozilla/Preferences.h"
#include "mozilla/net/RemoteOpenFileChild.h"
@ -349,9 +348,9 @@ nsJARChannel::LookupFile()
}
// 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);
@ -369,13 +368,20 @@ nsJARChannel::LookupFile()
}
}
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
@ -397,6 +403,38 @@ nsJARChannel::LookupFile()
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
//-----------------------------------------------------------------------------
@ -749,17 +787,7 @@ nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
} 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)) {
@ -902,9 +930,7 @@ nsJARChannel::OnDownloadComplete(nsIDownloader *downloader,
}
if (NS_FAILED(status)) {
mStatus = status;
OnStartRequest(nullptr, nullptr);
OnStopRequest(nullptr, nullptr, status);
NotifyError(status);
}
return NS_OK;
@ -918,30 +944,19 @@ 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
//-----------------------------------------------------------------------------

View File

@ -51,6 +51,8 @@ public:
private:
nsresult CreateJarInput(nsIZipReaderCache *, nsJARInputThunk **);
nsresult LookupFile();
nsresult OpenLocalFile();
void NotifyError(nsresult aError);
#if defined(PR_LOGGING)
nsCString mSpec;

View File

@ -17,6 +17,10 @@
#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);
@ -27,7 +31,13 @@ static NS_DEFINE_CID(kZipReaderCacheCID, NS_ZIPREADERCACHE_CID);
nsJARProtocolHandler *gJarHandler = nullptr;
nsJARProtocolHandler::nsJARProtocolHandler()
: mIsMainProcess(XRE_GetProcessType() == GeckoProcessType_Default)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mIsMainProcess) {
mRemoteFileListeners.Init();
}
}
nsJARProtocolHandler::~nsJARProtocolHandler()
@ -55,6 +65,67 @@ nsJARProtocolHandler::MimeService()
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,

View File

@ -13,10 +13,18 @@
#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
@ -34,9 +42,22 @@ public:
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;

View File

@ -46,6 +46,7 @@ CPPSRCS = \
LOCAL_INCLUDES += \
-I$(srcdir)/../protocol/http \
-I$(srcdir)/../base/src \
-I$(topsrcdir)/modules/libjar \
$(NULL)
include $(topsrcdir)/config/config.mk

View File

@ -4,10 +4,15 @@
* 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/NeckoChild.h"
#include "mozilla/net/RemoteOpenFileChild.h"
#include "nsIRemoteOpenFileListener.h"
#include "mozilla/ipc/FileDescriptor.h"
#include "mozilla/ipc/FileDescriptorUtils.h"
#include "mozilla/ipc/URIUtils.h"
#include "mozilla/net/NeckoChild.h"
#include "nsThreadUtils.h"
#include "nsJARProtocolHandler.h"
#include "nsIRemoteOpenFileListener.h"
// needed to alloc/free NSPR file descriptors
#include "private/pprio.h"
@ -17,13 +22,54 @@ using namespace mozilla::ipc;
namespace mozilla {
namespace net {
NS_IMPL_THREADSAFE_ISUPPORTS2(RemoteOpenFileChild,
nsIFile,
nsIHashable)
//-----------------------------------------------------------------------------
// 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,
nsICachedFileDescriptorListener)
RemoteOpenFileChild::RemoteOpenFileChild(const RemoteOpenFileChild& other)
: mNSPRFileDesc(other.mNSPRFileDesc)
: mTabChild(other.mTabChild)
, mNSPRFileDesc(other.mNSPRFileDesc)
, mAsyncOpenCalled(other.mAsyncOpenCalled)
, mNSPROpenCalled(other.mNSPROpenCalled)
{
@ -34,6 +80,10 @@ RemoteOpenFileChild::RemoteOpenFileChild(const RemoteOpenFileChild& other)
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);
@ -102,25 +152,39 @@ RemoteOpenFileChild::AsyncRemoteFileOpen(int32_t aFlags,
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
@ -135,6 +199,86 @@ RemoteOpenFileChild::AsyncRemoteFileOpen(int32_t aFlags,
#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
@ -144,18 +288,9 @@ 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!
@ -169,14 +304,10 @@ 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!
@ -186,24 +317,6 @@ RemoteOpenFileChild::RecvFileDidNotOpen()
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
//-----------------------------------------------------------------------------
@ -662,4 +775,3 @@ RemoteOpenFileChild::GetHashCode(uint32_t *aResult)
} // namespace net
} // namespace mozilla

View File

@ -9,10 +9,16 @@
#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 {
/**
@ -38,7 +44,11 @@ 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)
@ -55,26 +65,43 @@ public:
// 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;
@ -84,4 +111,3 @@ protected:
} // namespace mozilla
#endif // _RemoteOpenFileChild_h