GIOChannelChild.cpp (14400B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=4 sw=2 sts=2 et tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/net/NeckoChild.h" 8 #include "GIOChannelChild.h" 9 #include "nsGIOProtocolHandler.h" 10 #include "mozilla/dom/ContentChild.h" 11 #include "mozilla/dom/BrowserChild.h" 12 #include "nsContentUtils.h" 13 #include "nsIBrowserChild.h" 14 #include "nsStringStream.h" 15 #include "nsNetUtil.h" 16 #include "mozilla/ipc/IPCStreamUtils.h" 17 #include "mozilla/ipc/URIUtils.h" 18 #include "SerializedLoadContext.h" 19 #include "mozilla/ipc/BackgroundUtils.h" 20 #include "nsIURIMutator.h" 21 #include "nsContentSecurityManager.h" 22 #include "SerializedLoadContext.h" 23 #include "mozilla/Logging.h" 24 25 using mozilla::dom::ContentChild; 26 27 namespace mozilla { 28 #undef LOG 29 #define LOG(args) MOZ_LOG(gGIOLog, mozilla::LogLevel::Debug, args) 30 namespace net { 31 32 GIOChannelChild::GIOChannelChild(nsIURI* aUri) 33 : mEventQ(new ChannelEventQueue(static_cast<nsIChildChannel*>(this))) { 34 SetURI(aUri); 35 // We could support thread retargeting, but as long as we're being driven by 36 // IPDL on the main thread it doesn't buy us anything. 37 DisallowThreadRetargeting(); 38 } 39 40 void GIOChannelChild::AddIPDLReference() { 41 MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference"); 42 mIPCOpen = true; 43 AddRef(); 44 } 45 46 void GIOChannelChild::ReleaseIPDLReference() { 47 MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference"); 48 mIPCOpen = false; 49 Release(); 50 } 51 52 //----------------------------------------------------------------------------- 53 // GIOChannelChild::nsISupports 54 //----------------------------------------------------------------------------- 55 56 NS_IMPL_ISUPPORTS_INHERITED(GIOChannelChild, nsBaseChannel, nsIChildChannel) 57 58 //----------------------------------------------------------------------------- 59 60 NS_IMETHODIMP 61 GIOChannelChild::AsyncOpen(nsIStreamListener* aListener) { 62 nsCOMPtr<nsIStreamListener> listener = aListener; 63 nsresult rv = 64 nsContentSecurityManager::doContentSecurityCheck(this, listener); 65 NS_ENSURE_SUCCESS(rv, rv); 66 67 LOG(("GIOChannelChild::AsyncOpen [this=%p]\n", this)); 68 69 NS_ENSURE_TRUE((gNeckoChild), NS_ERROR_FAILURE); 70 NS_ENSURE_TRUE( 71 !static_cast<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown(), 72 NS_ERROR_FAILURE); 73 NS_ENSURE_ARG_POINTER(listener); 74 NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); 75 NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); 76 77 // Port checked in parent, but duplicate here so we can return with error 78 // immediately, as we've done since before e10s. 79 rv = NS_CheckPortSafety(nsBaseChannel::URI()); // Need to disambiguate, 80 // because in the child ipdl, 81 // a typedef URI is defined... 82 if (NS_FAILED(rv)) { 83 return rv; 84 } 85 86 mozilla::dom::BrowserChild* browserChild = nullptr; 87 nsCOMPtr<nsIBrowserChild> iBrowserChild; 88 NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, 89 NS_GET_IID(nsIBrowserChild), 90 getter_AddRefs(iBrowserChild)); 91 GetCallback(iBrowserChild); 92 if (iBrowserChild) { 93 browserChild = 94 static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get()); 95 } 96 97 mListener = listener; 98 99 // add ourselves to the load group. 100 if (mLoadGroup) { 101 mLoadGroup->AddRequest(this, nullptr); 102 } 103 104 Maybe<mozilla::ipc::IPCStream> ipcStream; 105 mozilla::ipc::SerializeIPCStream(do_AddRef(mUploadStream), ipcStream, 106 /* aAllowLazy */ false); 107 108 uint32_t loadFlags = 0; 109 GetLoadFlags(&loadFlags); 110 111 GIOChannelOpenArgs openArgs; 112 SerializeURI(nsBaseChannel::URI(), openArgs.uri()); 113 openArgs.startPos() = mStartPos; 114 openArgs.entityID() = mEntityID; 115 openArgs.uploadStream() = ipcStream; 116 openArgs.loadFlags() = loadFlags; 117 118 nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo(); 119 rv = mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &openArgs.loadInfo()); 120 NS_ENSURE_SUCCESS(rv, rv); 121 122 // This must happen before the constructor message is sent. 123 SetupNeckoTarget(); 124 125 // The socket transport layer in the chrome process now has a logical ref to 126 // us until OnStopRequest is called. 127 AddIPDLReference(); 128 129 if (!gNeckoChild->SendPGIOChannelConstructor( 130 this, browserChild, IPC::SerializedLoadContext(this), openArgs)) { 131 return NS_ERROR_FAILURE; 132 } 133 134 mIsPending = true; 135 mWasOpened = true; 136 137 return rv; 138 } 139 140 NS_IMETHODIMP 141 GIOChannelChild::IsPending(bool* aResult) { 142 *aResult = mIsPending; 143 return NS_OK; 144 } 145 146 nsresult GIOChannelChild::OpenContentStream(bool aAsync, 147 nsIInputStream** aStream, 148 nsIChannel** aChannel) { 149 MOZ_CRASH("GIOChannel*Child* should never have OpenContentStream called!"); 150 return NS_OK; 151 } 152 153 mozilla::ipc::IPCResult GIOChannelChild::RecvOnStartRequest( 154 const nsresult& aChannelStatus, const int64_t& aContentLength, 155 const nsACString& aContentType, const nsACString& aEntityID, 156 const URIParams& aURI) { 157 LOG(("GIOChannelChild::RecvOnStartRequest [this=%p]\n", this)); 158 mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( 159 this, [self = UnsafePtr<GIOChannelChild>(this), aChannelStatus, 160 aContentLength, aContentType = nsCString(aContentType), 161 aEntityID = nsCString(aEntityID), aURI]() { 162 self->DoOnStartRequest(aChannelStatus, aContentLength, aContentType, 163 aEntityID, aURI); 164 })); 165 return IPC_OK(); 166 } 167 168 void GIOChannelChild::DoOnStartRequest(const nsresult& aChannelStatus, 169 const int64_t& aContentLength, 170 const nsACString& aContentType, 171 const nsACString& aEntityID, 172 const URIParams& aURI) { 173 LOG(("GIOChannelChild::DoOnStartRequest [this=%p]\n", this)); 174 if (!mCanceled && NS_SUCCEEDED(mStatus)) { 175 mStatus = aChannelStatus; 176 } 177 178 mContentLength = aContentLength; 179 SetContentType(aContentType); 180 mEntityID = aEntityID; 181 182 nsCString spec; 183 nsCOMPtr<nsIURI> uri = DeserializeURI(aURI); 184 nsresult rv = uri->GetSpec(spec); 185 if (NS_SUCCEEDED(rv)) { 186 // Changes nsBaseChannel::URI() 187 rv = NS_MutateURI(mURI).SetSpec(spec).Finalize(mURI); 188 } 189 190 if (NS_FAILED(rv)) { 191 Cancel(rv); 192 } 193 194 AutoEventEnqueuer ensureSerialDispatch(mEventQ); 195 rv = mListener->OnStartRequest(this); 196 if (NS_FAILED(rv)) { 197 Cancel(rv); 198 } 199 } 200 201 mozilla::ipc::IPCResult GIOChannelChild::RecvOnDataAvailable( 202 const nsresult& aChannelStatus, const nsACString& aData, 203 const uint64_t& aOffset, const uint32_t& aCount) { 204 LOG(("GIOChannelChild::RecvOnDataAvailable [this=%p]\n", this)); 205 mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( 206 this, [self = UnsafePtr<GIOChannelChild>(this), aChannelStatus, 207 aData = nsCString(aData), aOffset, aCount]() { 208 self->DoOnDataAvailable(aChannelStatus, aData, aOffset, aCount); 209 })); 210 211 return IPC_OK(); 212 } 213 214 void GIOChannelChild::DoOnDataAvailable(const nsresult& aChannelStatus, 215 const nsACString& aData, 216 const uint64_t& aOffset, 217 const uint32_t& aCount) { 218 LOG(("GIOChannelChild::DoOnDataAvailable [this=%p]\n", this)); 219 220 if (!mCanceled && NS_SUCCEEDED(mStatus)) { 221 mStatus = aChannelStatus; 222 } 223 224 if (mCanceled) { 225 return; 226 } 227 228 // NOTE: the OnDataAvailable contract requires the client to read all the data 229 // in the inputstream. This code relies on that ('data' will go away after 230 // this function). Apparently the previous, non-e10s behavior was to actually 231 // support only reading part of the data, allowing later calls to read the 232 // rest. 233 nsCOMPtr<nsIInputStream> stringStream; 234 nsresult rv = 235 NS_NewByteInputStream(getter_AddRefs(stringStream), 236 Span(aData).To(aCount), NS_ASSIGNMENT_DEPEND); 237 if (NS_FAILED(rv)) { 238 Cancel(rv); 239 return; 240 } 241 242 AutoEventEnqueuer ensureSerialDispatch(mEventQ); 243 rv = mListener->OnDataAvailable(this, stringStream, aOffset, aCount); 244 if (NS_FAILED(rv)) { 245 Cancel(rv); 246 } 247 stringStream->Close(); 248 } 249 250 mozilla::ipc::IPCResult GIOChannelChild::RecvOnStopRequest( 251 const nsresult& aChannelStatus) { 252 LOG(("GIOChannelChild::RecvOnStopRequest [this=%p status=%" PRIx32 "]\n", 253 this, static_cast<uint32_t>(aChannelStatus))); 254 255 mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( 256 this, [self = UnsafePtr<GIOChannelChild>(this), aChannelStatus]() { 257 self->DoOnStopRequest(aChannelStatus); 258 })); 259 return IPC_OK(); 260 } 261 262 void GIOChannelChild::DoOnStopRequest(const nsresult& aChannelStatus) { 263 LOG(("GIOChannelChild::DoOnStopRequest [this=%p status=%" PRIx32 "]\n", this, 264 static_cast<uint32_t>(aChannelStatus))); 265 266 if (!mCanceled) { 267 mStatus = aChannelStatus; 268 } 269 270 { // Ensure that all queued ipdl events are dispatched before 271 // we initiate protocol deletion below. 272 mIsPending = false; 273 AutoEventEnqueuer ensureSerialDispatch(mEventQ); 274 (void)mListener->OnStopRequest(this, aChannelStatus); 275 276 mListener = nullptr; 277 278 if (mLoadGroup) { 279 mLoadGroup->RemoveRequest(this, nullptr, aChannelStatus); 280 } 281 } 282 283 // This calls NeckoChild::DeallocPGIOChannelChild(), which deletes |this| if 284 // IPDL holds the last reference. Don't rely on |this| existing after here! 285 Send__delete__(this); 286 } 287 288 mozilla::ipc::IPCResult GIOChannelChild::RecvFailedAsyncOpen( 289 const nsresult& aStatusCode) { 290 LOG(("GIOChannelChild::RecvFailedAsyncOpen [this=%p status=%" PRIx32 "]\n", 291 this, static_cast<uint32_t>(aStatusCode))); 292 mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( 293 this, [self = UnsafePtr<GIOChannelChild>(this), aStatusCode]() { 294 self->DoFailedAsyncOpen(aStatusCode); 295 })); 296 return IPC_OK(); 297 } 298 299 void GIOChannelChild::DoFailedAsyncOpen(const nsresult& aStatusCode) { 300 LOG(("GIOChannelChild::DoFailedAsyncOpen [this=%p status=%" PRIx32 "]\n", 301 this, static_cast<uint32_t>(aStatusCode))); 302 mStatus = aStatusCode; 303 304 if (mLoadGroup) { 305 mLoadGroup->RemoveRequest(this, nullptr, aStatusCode); 306 } 307 308 if (mListener) { 309 mListener->OnStartRequest(this); 310 mIsPending = false; 311 mListener->OnStopRequest(this, aStatusCode); 312 } else { 313 mIsPending = false; 314 } 315 316 mListener = nullptr; 317 318 if (mIPCOpen) { 319 Send__delete__(this); 320 } 321 } 322 323 mozilla::ipc::IPCResult GIOChannelChild::RecvDeleteSelf() { 324 mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( 325 this, 326 [self = UnsafePtr<GIOChannelChild>(this)]() { self->DoDeleteSelf(); })); 327 return IPC_OK(); 328 } 329 330 void GIOChannelChild::DoDeleteSelf() { 331 if (mIPCOpen) { 332 Send__delete__(this); 333 } 334 } 335 336 //----------------------------------------------------------------------------- 337 // GIOChannelChild::nsIResumableChannel 338 //----------------------------------------------------------------------------- 339 340 NS_IMETHODIMP 341 GIOChannelChild::Cancel(nsresult aStatus) { 342 LOG(("GIOChannelChild::Cancel [this=%p]\n", this)); 343 344 if (mCanceled) { 345 return NS_OK; 346 } 347 348 mCanceled = true; 349 mStatus = aStatus; 350 if (mIPCOpen) { 351 SendCancel(aStatus); 352 } 353 return NS_OK; 354 } 355 356 NS_IMETHODIMP 357 GIOChannelChild::Suspend() { 358 NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE); 359 360 LOG(("GIOChannelChild::Suspend [this=%p]\n", this)); 361 362 // SendSuspend only once, when suspend goes from 0 to 1. 363 if (!mSuspendCount++) { 364 SendSuspend(); 365 mSuspendSent = true; 366 } 367 mEventQ->Suspend(); 368 369 return NS_OK; 370 } 371 372 NS_IMETHODIMP 373 GIOChannelChild::Resume() { 374 NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE); 375 376 LOG(("GIOChannelChild::Resume [this=%p]\n", this)); 377 378 // SendResume only once, when suspend count drops to 0. 379 if (!--mSuspendCount && mSuspendSent) { 380 SendResume(); 381 } 382 mEventQ->Resume(); 383 384 return NS_OK; 385 } 386 387 //----------------------------------------------------------------------------- 388 // GIOChannelChild::nsIChildChannel 389 //----------------------------------------------------------------------------- 390 391 NS_IMETHODIMP 392 GIOChannelChild::ConnectParent(uint32_t aId) { 393 NS_ENSURE_TRUE((gNeckoChild), NS_ERROR_FAILURE); 394 NS_ENSURE_TRUE( 395 !static_cast<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown(), 396 NS_ERROR_FAILURE); 397 398 LOG(("GIOChannelChild::ConnectParent [this=%p]\n", this)); 399 400 mozilla::dom::BrowserChild* browserChild = nullptr; 401 nsCOMPtr<nsIBrowserChild> iBrowserChild; 402 NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, 403 NS_GET_IID(nsIBrowserChild), 404 getter_AddRefs(iBrowserChild)); 405 GetCallback(iBrowserChild); 406 if (iBrowserChild) { 407 browserChild = 408 static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get()); 409 } 410 411 // This must happen before the constructor message is sent. 412 SetupNeckoTarget(); 413 414 // The socket transport in the chrome process now holds a logical ref to us 415 // until OnStopRequest, or we do a redirect, or we hit an IPDL error. 416 AddIPDLReference(); 417 418 GIOChannelConnectArgs connectArgs(aId); 419 420 if (!gNeckoChild->SendPGIOChannelConstructor( 421 this, browserChild, IPC::SerializedLoadContext(this), connectArgs)) { 422 return NS_ERROR_FAILURE; 423 } 424 425 return NS_OK; 426 } 427 428 NS_IMETHODIMP 429 GIOChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) { 430 LOG(("GIOChannelChild::CompleteRedirectSetup [this=%p]\n", this)); 431 NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); 432 NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); 433 434 mIsPending = true; 435 mWasOpened = true; 436 mListener = aListener; 437 438 // add ourselves to the load group. 439 if (mLoadGroup) { 440 mLoadGroup->AddRequest(this, nullptr); 441 } 442 443 // We already have an open IPDL connection to the parent. If on-modify-request 444 // listeners or load group observers canceled us, let the parent handle it 445 // and send it back to us naturally. 446 return NS_OK; 447 } 448 449 void GIOChannelChild::SetupNeckoTarget() { 450 if (mNeckoTarget) { 451 return; 452 } 453 mNeckoTarget = GetMainThreadSerialEventTarget(); 454 } 455 456 } // namespace net 457 } // namespace mozilla