/* vim:set ts=4 sw=2 sts=2 et cin: */ /* 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/. */
// HttpLog.h should generally be included first #include"HttpLog.h"
DnsAndConnectSocket::~DnsAndConnectSocket() {
LOG(("Destroying DnsAndConnectSocket [this=%p]\n", this));
MOZ_ASSERT(mState == DnsAndSocketState::DONE);
CheckIsDone(); // Check in case something goes wrong that we decrease // the nsHttpConnectionMgr active connection number.
mPrimaryTransport.MaybeSetConnectingDone();
mBackupTransport.MaybeSetConnectingDone();
}
if (mProxyTransparentResolvesHost) { // Name resolution is done on the server side. Just pretend // client resolution is complete, this will get picked up later. // since we don't need to do DNS now, we bypass the resolving // step by initializing mNetAddr to an empty address, but we // must keep the port. The SOCKS IO layer will use the hostname // we send it when it's created, rather than the empty address // we send with the connect call.
mPrimaryTransport.mSkipDnsResolution = true;
mBackupTransport.mSkipDnsResolution = true;
mSkipDnsResolution = true;
}
nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS; bool disableIpv6ForBackup = false; if (mCaps & NS_HTTP_REFRESH_DNS) {
dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
} if (mCaps & NS_HTTP_DISABLE_IPV4) {
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
} elseif (mCaps & NS_HTTP_DISABLE_IPV6) {
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
} elseif (ent->PreferenceKnown()) { if (ent->mPreferIPv6) {
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
} elseif (ent->mPreferIPv4) {
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
}
mPrimaryTransport.mRetryWithDifferentIPFamily = true;
mBackupTransport.mRetryWithDifferentIPFamily = true;
} elseif (gHttpHandler->FastFallbackToIPv4()) { // For backup connections, we disable IPv6. That's because some users have // broken IPv6 connectivity (leading to very long timeouts), and disabling // IPv6 on the backup connection gives them a much better user experience // with dual-stack hosts, though they still pay the 250ms delay for each new // connection. This strategy is also known as "happy eyeballs".
disableIpv6ForBackup = true;
}
if (ent->mConnInfo->HasIPHintAddress()) {
nsresult rv;
nsCOMPtr<nsIDNSService> dns;
dns = mozilla::components::DNS::Service(&rv); if (NS_FAILED(rv)) { return rv;
}
// The spec says: "If A and AAAA records for TargetName are locally // available, the client SHOULD ignore these hints.", so we check if the DNS // record is in cache before setting USE_IP_HINT_ADDRESS.
nsCOMPtr<nsIDNSRecord> record;
rv = dns->ResolveNative(
mPrimaryTransport.mHost, nsIDNSService::RESOLVE_OFFLINE,
mConnInfo->GetOriginAttributes(), getter_AddRefs(record)); if (NS_FAILED(rv) || !record) {
LOG(("Setting Socket to use IP hint address"));
dnsFlags |= nsIDNSService::RESOLVE_IP_HINT;
}
}
// When we get here, we are not resolving using any configured proxy likely // because of individual proxy setting on the request or because the host is // excluded from proxying. Hence, force resolution despite global proxy-DNS // configuration.
dnsFlags |= nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS;
NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4), "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
// When using Fast Open the correct transport will be setup for sure (it is // guaranteed), but it can be that it will happened a bit later. if (timeout && (!mSpeculative || mConnInfo->GetFallbackConnection()) &&
!mIsHttp3) { // Setup the timer that will establish a backup socket // if we do not get a writable event on the main one. // We do this because a lost SYN takes a very long time // to repair at the TCP level. // // Failure to setup the timer is something we can live with, // so don't return an error in that case.
NS_NewTimerWithCallback(getter_AddRefs(mSynTimer), this, timeout,
nsITimer::TYPE_ONE_SHOT);
LOG(("DnsAndConnectSocket::SetupBackupTimer() [this=%p]", this));
} elseif (timeout) {
LOG(("DnsAndConnectSocket::SetupBackupTimer() [this=%p], did not arm\n", this));
}
}
void DnsAndConnectSocket::CancelBackupTimer() { // If the syntimer is still armed, we can cancel it because no backup // socket should be formed at this point if (!mSynTimer) { return;
}
if (!request || (!IsPrimary(request) && !IsBackup(request))) { return NS_OK;
}
if (IsPrimary(request) && NS_SUCCEEDED(status)) {
mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_RESOLVED_HOST, 0);
}
// When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP // proxy host is not found, so we fixup the error code. // For SOCKS proxies (mProxyTransparent == true), the socket // transport resolves the real host here, so there's no fixup // (see bug 226943). if (mProxyNotTransparent && (status == NS_ERROR_UNKNOWN_HOST)) {
status = NS_ERROR_UNKNOWN_PROXY_HOST;
}
nsresult rv; // remember if it was primary because TransportSetups will delete the ref to // the DNS request and check cannot be performed later. bool isPrimary = IsPrimary(request); if (isPrimary) {
rv = mPrimaryTransport.OnLookupComplete(this, rec, status); if ((!mIsHttp3 && mPrimaryTransport.ConnectingOrRetry()) ||
(mIsHttp3 && mPrimaryTransport.Resolved())) {
SetupEvent(SetupEvents::RESOLVED_PRIMARY_EVENT);
}
} else {
rv = mBackupTransport.OnLookupComplete(this, rec, status);
}
if (NS_FAILED(rv) || mIsHttp3) { // If we are retrying DNS, we should not setup the connection. if (mIsHttp3 && mPrimaryTransport.mState ==
TransportSetup::TransportSetupState::RETRY_RESOLVING) {
LOG(("Retry DNS for Http3")); return NS_OK;
}
// Before calling SetupConn we need to hold reference to this, i.e. a // delete protector, because the corresponding ConnectionEntry may be // abandoned and that will abandon this DnsAndConnectSocket.
SetupConn(isPrimary, rv); // During a connection dispatch that will happen in SetupConn, // a ConnectionEntry may be abandon and that will abandon this // DnsAndConnectSocket. In that case mState will already be // DnsAndSocketState::DONE and we do not need to set it again. if (mState != DnsAndSocketState::DONE) { if (isPrimary) {
SetupEvent(SetupEvents::PRIMARY_DONE_EVENT);
} else {
SetupEvent(SetupEvents::BACKUP_DONE_EVENT);
}
}
} return NS_OK;
}
nsresult socketStatus = out->StreamStatus(); if (StaticPrefs::network_http_retry_with_another_half_open() &&
NS_FAILED(socketStatus) && socketStatus != NS_BASE_STREAM_WOULD_BLOCK) { if (isPrimary) { if (mBackupTransport.mState ==
TransportSetup::TransportSetupState::CONNECTING) {
mPrimaryTransport.Abandon(); return NS_OK;
}
} elseif (IsBackup(out)) { if (mPrimaryTransport.mState ==
TransportSetup::TransportSetupState::CONNECTING) {
mBackupTransport.Abandon(); return NS_OK;
}
}
}
// Before calling SetupConn we need to hold a reference to this, i.e. a // delete protector, because the corresponding ConnectionEntry may be // abandoned and that will abandon this DnsAndConnectSocket.
rv = SetupConn(isPrimary, rv); if (mState != DnsAndSocketState::DONE) { // During a connection dispatch that will happen in SetupConn, // a ConnectionEntry may be abandon and that will abandon this // DnsAndConnectSocket. In that case mState will already be // DnsAndSocketState::DONE and we do not need to set it again. if (isPrimary) {
SetupEvent(SetupEvents::PRIMARY_DONE_EVENT);
} else {
SetupEvent(SetupEvents::BACKUP_DONE_EVENT);
}
} return rv;
}
nsresult DnsAndConnectSocket::SetupConn(bool isPrimary, nsresult status) { // assign the new socket to the http connection
RefPtr<ConnectionEntry> ent =
gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
MOZ_DIAGNOSTIC_ASSERT(ent); if (!ent) {
Abandon(); return NS_OK;
}
if (nsHttpTransaction* trans = mTransaction->QueryHttpTransaction()) { if (mIsHttp3 && !mConnInfo->GetWebTransport()) {
trans->DisableHttp3(true);
gHttpHandler->ExcludeHttp3(mConnInfo);
} // The transaction's connection info is changed after DisableHttp3(), so // this is the only point we can remove this transaction from its conn // entry.
ent->RemoveTransFromPendingQ(trans);
}
mTransaction->Close(rv);
return rv;
}
// This half-open socket has created a connection. This flag excludes it // from counter of actual connections used for checking limits.
mHasConnected = true;
// if this is still in the pending list, remove it and dispatch it
RefPtr<PendingTransactionInfo> pendingTransInfo =
gHttpHandler->ConnMgr()->FindTransactionHelper(true, ent, mTransaction); if (pendingTransInfo) {
MOZ_ASSERT(!mSpeculative, "Speculative Half Open found mTransaction");
ent->InsertIntoActiveConns(conn); if (mIsHttp3) { // Each connection must have a ConnectionHandle wrapper. // In case of Http < 2 the a ConnectionHandle is created for each // transaction in DispatchAbstractTransaction. // In case of Http2/ and Http3, ConnectionHandle is created only once. // Http2 connection always starts as http1 connection and the first // transaction use DispatchAbstractTransaction to be dispatched and // a ConnectionHandle is created. All consecutive transactions for // Http2 use a short-cut in DispatchTransaction and call // HttpConnectionBase::Activate (DispatchAbstractTransaction is never // called). // In case of Http3 the short-cut HttpConnectionBase::Activate is always // used also for the first transaction, therefore we need to create // ConnectionHandle here.
RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
pendingTransInfo->Transaction()->SetConnection(handle);
}
rv = gHttpHandler->ConnMgr()->DispatchTransaction(
ent, pendingTransInfo->Transaction(), conn);
} else { // this transaction was dispatched off the pending q before all the // sockets established themselves.
// After about 1 second allow for the possibility of restarting a // transaction due to server close. Keep at sub 1 second as that is the // minimum granularity we can expect a server to be timing out with.
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn); if (connTCP) {
connTCP->SetIsReusedAfter(950);
}
// if we are using ssl and no other transactions are waiting right now, // then form a null transaction to drive the SSL handshake to // completion. Afterwards the connection will be 100% ready for the next // transaction to use it. Make an exception for SSL tunneled HTTP proxy as // the NullHttpTransaction does not know how to drive Connect // Http3 cannot be dispatched using OnMsgReclaimConnection (see below), // therefore we need to use a Nulltransaction. if (!connTCP ||
(ent->mConnInfo->FirstHopSSL() && !ent->UrgentStartQueueLength() &&
!ent->PendingQueueLength() && !ent->mConnInfo->UsingConnect())) {
LOG(
("DnsAndConnectSocket::SetupConn null transaction will " "be used to finish SSL handshake on conn %p\n",
conn.get()));
RefPtr<nsAHttpTransaction> trans; if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) { // null transactions cannot be put in the entry queue, so that // explains why it is not present.
mDispatchedMTransaction = true;
trans = mTransaction;
} else {
trans = new NullHttpTransaction(mConnInfo, callbacks, mCaps);
}
ent->InsertIntoActiveConns(conn);
rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(ent, trans,
mCaps, conn, 0);
} else { // otherwise just put this in the persistent connection pool
LOG(
("DnsAndConnectSocket::SetupConn no transaction match " "returning conn %p to pool\n",
conn.get()));
gHttpHandler->ConnMgr()->OnMsgReclaimConnection(conn);
// We expect that there is at least one tranasction in the pending // queue that can take this connection, but it can happened that // all transactions are blocked or they have took other idle // connections. In that case the connection has been added to the // idle queue. // If the connection is in the idle queue but it is using ssl, make // a nulltransaction for it to finish ssl handshake! if (ent->mConnInfo->FirstHopSSL() && !ent->mConnInfo->UsingConnect()) {
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn); // If RemoveIdleConnection succeeds that means that conn is in the // idle queue. if (connTCP && NS_SUCCEEDED(ent->RemoveIdleConnection(connTCP))) {
RefPtr<nsAHttpTransaction> trans; if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
mDispatchedMTransaction = true;
trans = mTransaction;
} else {
trans = new NullHttpTransaction(ent->mConnInfo, callbacks, mCaps);
}
ent->InsertIntoActiveConns(conn);
rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(
ent, trans, mCaps, conn, 0);
}
}
}
}
// If this halfOpenConn was speculative, but at the end the conn got a // non-null transaction than this halfOpen is not speculative anymore! if (conn->Transaction() && !conn->Transaction()->IsNullTransaction()) {
Claim();
}
return rv;
}
// method for nsITransportEventSink
NS_IMETHODIMP
DnsAndConnectSocket::OnTransportStatus(nsITransport* trans, nsresult status,
int64_t progress, int64_t progressMax) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(IsPrimary(trans) || IsBackup(trans)); if (mTransaction) { if (IsPrimary(trans) ||
(IsBackup(trans) && (status == NS_NET_STATUS_CONNECTED_TO) &&
mPrimaryTransport.mSocketTransport)) { // Send this status event only if the transaction is still pending, // i.e. it has not found a free already connected socket. // Sockets in halfOpen state can only get following events: // NS_NET_STATUS_CONNECTING_TO and NS_NET_STATUS_CONNECTED_TO. // mBackupTransport is only started after // NS_NET_STATUS_CONNECTING_TO of mSocketTransport, so ignore // NS_NET_STATUS_CONNECTING_TO event for mBackupTransport and // send NS_NET_STATUS_CONNECTED_TO. // mBackupTransport must be connected before mSocketTransport(e.g. // mPrimaryTransport.mSocketTransport != nullpttr).
mTransaction->OnTransportStatus(trans, status, progress);
}
}
if (status == NS_NET_STATUS_CONNECTED_TO) { if (IsPrimary(trans)) {
mPrimaryTransport.mConnectedOK = true;
} else {
mBackupTransport.mConnectedOK = true;
}
}
// The rest of this method only applies to the primary transport if (!IsPrimary(trans)) { return NS_OK;
}
// if we are doing spdy coalescing and haven't recorded the ip address // for this entry before then make the hash key if our dns lookup // just completed. We can't do coalescing if using a proxy because the // ip addresses are not available to the client.
nsCOMPtr<nsIDNSAddrRecord> dnsRecord(
do_GetInterface(mPrimaryTransport.mSocketTransport)); if (status == NS_NET_STATUS_CONNECTING_TO &&
StaticPrefs::network_http_http2_enabled() &&
StaticPrefs::network_http_http2_coalesce_hostnames()) {
RefPtr<ConnectionEntry> ent =
gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
MOZ_DIAGNOSTIC_ASSERT(ent); if (ent) { if (ent->MaybeProcessCoalescingKeys(dnsRecord)) {
gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(ent);
}
}
}
return NS_OK;
}
bool DnsAndConnectSocket::IsPrimary(nsITransport* trans) { return trans == mPrimaryTransport.mSocketTransport;
}
bool DnsAndConnectSocket::IsPrimary(nsIAsyncOutputStream* out) { return out == mPrimaryTransport.mStreamOut;
}
// Http3 has its own syn-retransmission, therefore it does not need a // backup connection. if (mPrimaryTransport.ConnectingOrRetry() &&
!mBackupTransport.mSocketTransport && !mSynTimer && !mIsHttp3) {
SetupBackupTimer();
}
}
if (mFreeToUse) {
mFreeToUse = false;
if (mPrimaryTransport.mSocketTransport) {
nsCOMPtr<nsITLSSocketControl> tlsSocketControl; if (NS_SUCCEEDED(mPrimaryTransport.mSocketTransport->GetTlsSocketControl(
getter_AddRefs(tlsSocketControl))) &&
tlsSocketControl) {
Unused << tlsSocketControl->Claim();
}
}
returntrue;
}
returnfalse;
}
void DnsAndConnectSocket::Unclaim() {
MOZ_ASSERT(!mSpeculative && !mFreeToUse); // We will keep the backup-timer running. Most probably this halfOpen will // be used by a transaction from which this transaction took the halfOpen. // (this is happening because of the transaction priority.)
mFreeToUse = true;
}
void DnsAndConnectSocket::CloseTransports(nsresult error) { if (mPrimaryTransport.mSocketTransport) {
mPrimaryTransport.mSocketTransport->Close(error);
} if (mBackupTransport.mSocketTransport) {
mBackupTransport.mSocketTransport->Close(error);
}
}
// Toggle the RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4 flags in mDnsFlags // This ensures we switch the IP family for the DNS resolution
mDnsFlags ^= (nsIDNSService::RESOLVE_DISABLE_IPV6 |
nsIDNSService::RESOLVE_DISABLE_IPV4);
if ((mDnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) &&
(mDnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4)) { // Clear both flags to prevent an invalid state
mDnsFlags &= ~(nsIDNSService::RESOLVE_DISABLE_IPV6 |
nsIDNSService::RESOLVE_DISABLE_IPV4);
LOG(
("DnsAndConnectSocket::TransportSetup::" "ToggleIpFamilyFlagsIfRetryEnabled " "[this=%p] both v6 and v4 are disabled", this));
MOZ_DIAGNOSTIC_CRASH("both v6 and v4 addresses are disabled");
}
// Indicate that the IP family preference should be reset
mResetFamilyPreference = true; returntrue;
}
if (mSkipDnsResolution) { return NS_OK;
} bool retryDns = false;
mSocketTransport->GetRetryDnsIfPossible(&retryDns); if (!retryDns) { return NS_OK;
}
bool retry = false; if (ToggleIpFamilyFlagsIfRetryEnabled()) {
retry = true;
} elseif (!(mDnsFlags & nsIDNSService::RESOLVE_DISABLE_TRR)) { bool trrEnabled;
mDNSRecord->IsTRR(&trrEnabled); if (trrEnabled) {
nsIRequest::TRRMode trrMode = nsIRequest::TRR_DEFAULT_MODE;
mDNSRecord->GetEffectiveTRRMode(&trrMode); // If current trr mode is trr only, we should not retry. if (trrMode != nsIRequest::TRR_ONLY_MODE) { // Drop state to closed. This will trigger a new round of // DNS resolving. Bypass the cache this time since the // cached data came from TRR and failed already!
LOG((" failed to connect with TRR enabled, try w/o\n"));
mDnsFlags |= nsIDNSService::RESOLVE_DISABLE_TRR |
nsIDNSService::RESOLVE_BYPASS_CACHE |
nsIDNSService::RESOLVE_REFRESH_CACHE;
retry = true;
}
}
}
NullHttpTransaction* nullTrans = transaction->QueryNullTransaction(); if (nullTrans) {
conn->BootstrapTimings(nullTrans->Timings());
}
// Some capabilities are needed before a transaction actually gets // scheduled (e.g. how to negotiate false start)
conn->SetTransactionCaps(transaction->Caps());
sts = components::SocketTransport::Service(); if (!sts) { return NS_ERROR_NOT_AVAILABLE;
}
LOG(
("DnsAndConnectSocket::SetupStreams [this=%p ent=%s] " "setup routed transport to origin %s:%d via %s:%d\n", this, ci->HashKey().get(), ci->Origin(), ci->OriginPort(),
ci->RoutedHost(), ci->RoutedPort()));
nsCOMPtr<nsIRoutedSocketTransportService> routedSTS(do_QueryInterface(sts)); if (routedSTS) {
rv = routedSTS->CreateRoutedTransport(
socketTypes, ci->GetOrigin(), ci->OriginPort(), ci->GetRoutedHost(),
ci->RoutedPort(), ci->ProxyInfo(), mDNSRecord,
getter_AddRefs(socketTransport));
} else { if (!ci->GetRoutedHost().IsEmpty()) { // There is a route requested, but the legacy nsISocketTransportService // can't handle it. // Origin should be reachable on origin host name, so this should // not be a problem - but log it.
LOG(
("DnsAndConnectSocket this=%p using legacy nsISocketTransportService " "means explicit route %s:%d will be ignored.\n", this, ci->RoutedHost(), ci->RoutedPort()));
}
if (dnsAndSock->mCaps & NS_HTTP_LOAD_ANONYMOUS) {
tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
}
// When we are making a speculative connection we do not propagate all flags // in mCaps, so we need to query nsHttpConnectionInfo directly as well. if ((dnsAndSock->mCaps & NS_HTTP_LOAD_ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT) ||
ci->GetAnonymousAllowClientCert()) {
tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
}
if (ci->GetPrivate()) {
tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
}
if (dnsAndSock->mCaps & NS_HTTP_DISALLOW_ECH) {
tmpFlags |= nsISocketTransport::DONT_TRY_ECH;
}
if (dnsAndSock->mCaps & NS_HTTP_IS_RETRY) {
tmpFlags |= nsISocketTransport::IS_RETRY;
}
if (((dnsAndSock->mCaps & NS_HTTP_BE_CONSERVATIVE) ||
ci->GetBeConservative()) &&
gHttpHandler->ConnMgr()->BeConservativeIfProxied(ci->ProxyInfo())) {
LOG(("Setting Socket to BE_CONSERVATIVE"));
tmpFlags |= nsISocketTransport::BE_CONSERVATIVE;
}
if (ci->HasIPHintAddress()) {
nsCOMPtr<nsIDNSService> dns;
dns = mozilla::components::DNS::Service(&rv);
NS_ENSURE_SUCCESS(rv, rv);
// The spec says: "If A and AAAA records for TargetName are locally // available, the client SHOULD ignore these hints.", so we check if the DNS // record is in cache before setting USE_IP_HINT_ADDRESS.
nsCOMPtr<nsIDNSRecord> record;
rv = dns->ResolveNative(mHost, nsIDNSService::RESOLVE_OFFLINE,
dnsAndSock->mConnInfo->GetOriginAttributes(),
getter_AddRefs(record)); if (NS_FAILED(rv) || !record) {
LOG(("Setting Socket to use IP hint address"));
tmpFlags |= nsISocketTransport::USE_IP_HINT_ADDRESS;
}
}
if (mRetryWithDifferentIPFamily) { // From the same reason, let the backup socket fail faster to try the other // family.
uint16_t fallbackTimeout =
mIsBackup ? gHttpHandler->GetFallbackSynTimeout() : 0; if (fallbackTimeout) {
socketTransport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT,
fallbackTimeout);
}
}
if (!dnsAndSock->Allow1918()) {
tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
}
if (dnsAndSock->mSpeculative) {
tmpFlags |= nsISocketTransport::IS_SPECULATIVE_CONNECTION;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.