tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

RequestContextService.cpp (16641B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ;*; */
      2 /* vim: set sw=2 ts=8 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 "nsIDocShell.h"
      8 #include "mozilla/dom/Document.h"
      9 #include "nsComponentManagerUtils.h"
     10 #include "nsIDocumentLoader.h"
     11 #include "nsIObserverService.h"
     12 #include "nsITimer.h"
     13 #include "nsIXULRuntime.h"
     14 #include "nsServiceManagerUtils.h"
     15 #include "nsThreadUtils.h"
     16 #include "RequestContextService.h"
     17 
     18 #include "mozilla/Atomics.h"
     19 #include "mozilla/ClearOnShutdown.h"
     20 #include "mozilla/Logging.h"
     21 #include "mozilla/Services.h"
     22 #include "mozilla/StaticPtr.h"
     23 #include "mozilla/TimeStamp.h"
     24 
     25 #include "mozilla/net/NeckoChild.h"
     26 #include "mozilla/net/NeckoCommon.h"
     27 
     28 #include "../protocol/http/nsHttpHandler.h"
     29 
     30 namespace mozilla {
     31 namespace net {
     32 
     33 LazyLogModule gRequestContextLog("RequestContext");
     34 #undef LOG
     35 #define LOG(args) MOZ_LOG(gRequestContextLog, LogLevel::Info, args)
     36 
     37 static StaticRefPtr<RequestContextService> gSingleton;
     38 
     39 // This is used to prevent adding tail pending requests after shutdown
     40 static bool sShutdown = false;
     41 
     42 // nsIRequestContext
     43 class RequestContext final : public nsIRequestContext,
     44                             public nsITimerCallback,
     45                             public nsINamed {
     46 public:
     47  NS_DECL_THREADSAFE_ISUPPORTS
     48  NS_DECL_NSIREQUESTCONTEXT
     49  NS_DECL_NSITIMERCALLBACK
     50  NS_DECL_NSINAMED
     51 
     52  explicit RequestContext(const uint64_t id);
     53 
     54 private:
     55  virtual ~RequestContext();
     56 
     57  void ProcessTailQueue(nsresult aResult);
     58  // Reschedules the timer if needed
     59  void ScheduleUnblock();
     60  // Hard-reschedules the timer
     61  void RescheduleUntailTimer(TimeStamp const& now);
     62 
     63  uint64_t mID;
     64  Atomic<uint32_t> mBlockingTransactionCount;
     65 
     66  using PendingTailRequest = nsCOMPtr<nsIRequestTailUnblockCallback>;
     67  // Number of known opened non-tailed requets
     68  uint32_t mNonTailRequests;
     69  // Queue of requests that have been tailed, when conditions are met
     70  // we call each of them to unblock and drop the reference
     71  nsTArray<PendingTailRequest> mTailQueue;
     72  // Loosly scheduled timer, never scheduled further to the future than
     73  // mUntailAt time
     74  nsCOMPtr<nsITimer> mUntailTimer;
     75  // Timestamp when the timer is expected to fire,
     76  // always less than or equal to mUntailAt
     77  TimeStamp mTimerScheduledAt;
     78  // Timestamp when we want to actually untail queued requets based on
     79  // the number of request count change in the past; iff this timestamp
     80  // is set, we tail requests
     81  TimeStamp mUntailAt;
     82 
     83  // Timestamp of the navigation start time, set to Now() in BeginLoad().
     84  // This is used to progressively lower the maximum delay time so that
     85  // we can't get to a situation when a number of repetitive requests
     86  // on the page causes forever tailing.
     87  TimeStamp mBeginLoadTime;
     88 
     89  // This member is true only between DOMContentLoaded notification and
     90  // next document load beginning for this request context.
     91  // Top level request contexts are recycled.
     92  bool mAfterDOMContentLoaded;
     93 };
     94 
     95 NS_IMPL_ISUPPORTS(RequestContext, nsIRequestContext, nsITimerCallback, nsINamed)
     96 
     97 RequestContext::RequestContext(const uint64_t aID)
     98    : mID(aID),
     99      mBlockingTransactionCount(0),
    100      mNonTailRequests(0),
    101      mAfterDOMContentLoaded(false) {
    102  LOG(("RequestContext::RequestContext this=%p id=%" PRIx64, this, mID));
    103 }
    104 
    105 RequestContext::~RequestContext() {
    106  MOZ_ASSERT(mTailQueue.Length() == 0);
    107 
    108  LOG(("RequestContext::~RequestContext this=%p blockers=%u", this,
    109       static_cast<uint32_t>(mBlockingTransactionCount)));
    110 }
    111 
    112 NS_IMETHODIMP
    113 RequestContext::BeginLoad() {
    114  MOZ_ASSERT(NS_IsMainThread());
    115 
    116  LOG(("RequestContext::BeginLoad %p", this));
    117 
    118  if (IsNeckoChild()) {
    119    // Tailing is not supported on the child process
    120    if (gNeckoChild) {
    121      gNeckoChild->SendRequestContextLoadBegin(mID);
    122    }
    123    return NS_OK;
    124  }
    125 
    126  mAfterDOMContentLoaded = false;
    127  mBeginLoadTime = TimeStamp::NowLoRes();
    128  return NS_OK;
    129 }
    130 
    131 NS_IMETHODIMP
    132 RequestContext::DOMContentLoaded() {
    133  MOZ_ASSERT(NS_IsMainThread());
    134 
    135  LOG(("RequestContext::DOMContentLoaded %p", this));
    136 
    137  if (IsNeckoChild()) {
    138    // Tailing is not supported on the child process
    139    if (gNeckoChild) {
    140      gNeckoChild->SendRequestContextAfterDOMContentLoaded(mID);
    141    }
    142    return NS_OK;
    143  }
    144 
    145  if (mAfterDOMContentLoaded) {
    146    // There is a possibility of a duplicate notification
    147    return NS_OK;
    148  }
    149 
    150  mAfterDOMContentLoaded = true;
    151 
    152  // Conditions for the delay calculation has changed.
    153  ScheduleUnblock();
    154  return NS_OK;
    155 }
    156 
    157 NS_IMETHODIMP
    158 RequestContext::GetBlockingTransactionCount(
    159    uint32_t* aBlockingTransactionCount) {
    160  NS_ENSURE_ARG_POINTER(aBlockingTransactionCount);
    161  *aBlockingTransactionCount = mBlockingTransactionCount;
    162  return NS_OK;
    163 }
    164 
    165 NS_IMETHODIMP
    166 RequestContext::AddBlockingTransaction() {
    167  mBlockingTransactionCount++;
    168  LOG(("RequestContext::AddBlockingTransaction this=%p blockers=%u", this,
    169       static_cast<uint32_t>(mBlockingTransactionCount)));
    170  return NS_OK;
    171 }
    172 
    173 NS_IMETHODIMP
    174 RequestContext::RemoveBlockingTransaction(uint32_t* outval) {
    175  NS_ENSURE_ARG_POINTER(outval);
    176  mBlockingTransactionCount--;
    177  LOG(("RequestContext::RemoveBlockingTransaction this=%p blockers=%u", this,
    178       static_cast<uint32_t>(mBlockingTransactionCount)));
    179  *outval = mBlockingTransactionCount;
    180  return NS_OK;
    181 }
    182 
    183 uint64_t RequestContext::GetID() { return mID; }
    184 
    185 NS_IMETHODIMP
    186 RequestContext::AddNonTailRequest() {
    187  MOZ_ASSERT(NS_IsMainThread());
    188 
    189  ++mNonTailRequests;
    190  LOG(("RequestContext::AddNonTailRequest this=%p, cnt=%u", this,
    191       mNonTailRequests));
    192 
    193  ScheduleUnblock();
    194  return NS_OK;
    195 }
    196 
    197 NS_IMETHODIMP
    198 RequestContext::RemoveNonTailRequest() {
    199  MOZ_ASSERT(NS_IsMainThread());
    200  MOZ_ASSERT(mNonTailRequests > 0);
    201 
    202  LOG(("RequestContext::RemoveNonTailRequest this=%p, cnt=%u", this,
    203       mNonTailRequests - 1));
    204 
    205  --mNonTailRequests;
    206 
    207  ScheduleUnblock();
    208  return NS_OK;
    209 }
    210 
    211 void RequestContext::ScheduleUnblock() {
    212  MOZ_ASSERT(!IsNeckoChild());
    213  MOZ_ASSERT(NS_IsMainThread());
    214 
    215  if (!gHttpHandler) {
    216    return;
    217  }
    218 
    219  uint32_t quantum =
    220      gHttpHandler->TailBlockingDelayQuantum(mAfterDOMContentLoaded);
    221  uint32_t delayMax = gHttpHandler->TailBlockingDelayMax();
    222  uint32_t totalMax = gHttpHandler->TailBlockingTotalMax();
    223 
    224  if (!mBeginLoadTime.IsNull()) {
    225    // We decrease the maximum delay progressively with the time since the page
    226    // load begin.  This seems like a reasonable and clear heuristic allowing us
    227    // to start loading tailed requests in a deterministic time after the load
    228    // has started.
    229 
    230    uint32_t sinceBeginLoad = static_cast<uint32_t>(
    231        (TimeStamp::NowLoRes() - mBeginLoadTime).ToMilliseconds());
    232    uint32_t tillTotal = totalMax - std::min(sinceBeginLoad, totalMax);
    233    uint32_t proportion = totalMax  // values clamped between 0 and 60'000
    234                              ? (delayMax * tillTotal) / totalMax
    235                              : 0;
    236    delayMax = std::min(delayMax, proportion);
    237  }
    238 
    239  CheckedInt<uint32_t> delay = quantum * mNonTailRequests;
    240 
    241  if (!mAfterDOMContentLoaded) {
    242    // Before DOMContentLoaded notification we want to make sure that tailed
    243    // requests don't start when there is a short delay during which we may
    244    // not have any active requests on the page happening.
    245    delay += quantum;
    246  }
    247 
    248  if (!delay.isValid() || delay.value() > delayMax) {
    249    delay = delayMax;
    250  }
    251 
    252  LOG(
    253      ("RequestContext::ScheduleUnblock this=%p non-tails=%u tail-queue=%zu "
    254       "delay=%u after-DCL=%d",
    255       this, mNonTailRequests, mTailQueue.Length(), delay.value(),
    256       mAfterDOMContentLoaded));
    257 
    258  TimeStamp now = TimeStamp::NowLoRes();
    259  mUntailAt = now + TimeDuration::FromMilliseconds(delay.value());
    260 
    261  if (mTimerScheduledAt.IsNull() || mUntailAt < mTimerScheduledAt) {
    262    LOG(("RequestContext %p timer would fire too late, rescheduling", this));
    263    RescheduleUntailTimer(now);
    264  }
    265 }
    266 
    267 void RequestContext::RescheduleUntailTimer(TimeStamp const& now) {
    268  MOZ_ASSERT(mUntailAt >= now);
    269 
    270  if (mUntailTimer) {
    271    mUntailTimer->Cancel();
    272  }
    273 
    274  if (!mTailQueue.Length()) {
    275    mUntailTimer = nullptr;
    276    mTimerScheduledAt = TimeStamp();
    277    return;
    278  }
    279 
    280  TimeDuration interval = mUntailAt - now;
    281  if (!mTimerScheduledAt.IsNull() && mUntailAt < mTimerScheduledAt) {
    282    // When the number of untailed requests goes down,
    283    // let's half the interval, since it's likely we would
    284    // reschedule for a shorter time again very soon.
    285    // This will likely save rescheduling this timer.
    286    interval = interval / int64_t(2);
    287    mTimerScheduledAt = mUntailAt - interval;
    288  } else {
    289    mTimerScheduledAt = mUntailAt;
    290  }
    291 
    292  uint32_t delay = interval.ToMilliseconds();
    293  nsresult rv =
    294      NS_NewTimerWithCallback(getter_AddRefs(mUntailTimer), this, delay,
    295                              nsITimer::TYPE_ONE_SHOT, nullptr);
    296  if (NS_FAILED(rv)) {
    297    NS_WARNING("Could not reschedule untail timer");
    298  }
    299 
    300  LOG(("RequestContext::RescheduleUntailTimer %p in %d", this, delay));
    301 }
    302 
    303 NS_IMETHODIMP
    304 RequestContext::Notify(nsITimer* timer) {
    305  MOZ_ASSERT(NS_IsMainThread());
    306  MOZ_ASSERT(timer == mUntailTimer);
    307  MOZ_ASSERT(!mTimerScheduledAt.IsNull());
    308  MOZ_ASSERT(mTailQueue.Length());
    309 
    310  mUntailTimer = nullptr;
    311 
    312  TimeStamp now = TimeStamp::NowLoRes();
    313  if (mUntailAt > mTimerScheduledAt && mUntailAt > now) {
    314    LOG(("RequestContext %p timer fired too soon, rescheduling", this));
    315    RescheduleUntailTimer(now);
    316    return NS_OK;
    317  }
    318 
    319  // Must drop to allow re-engage of the timer
    320  mTimerScheduledAt = TimeStamp();
    321 
    322  ProcessTailQueue(NS_OK);
    323 
    324  return NS_OK;
    325 }
    326 
    327 NS_IMETHODIMP
    328 RequestContext::GetName(nsACString& aName) {
    329  aName.AssignLiteral("RequestContext");
    330  return NS_OK;
    331 }
    332 
    333 NS_IMETHODIMP
    334 RequestContext::IsContextTailBlocked(nsIRequestTailUnblockCallback* aRequest,
    335                                     bool* aBlocked) {
    336  MOZ_ASSERT(NS_IsMainThread());
    337 
    338  LOG(("RequestContext::IsContextTailBlocked this=%p, request=%p, queued=%zu",
    339       this, aRequest, mTailQueue.Length()));
    340 
    341  *aBlocked = false;
    342 
    343  if (sShutdown) {
    344    return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
    345  }
    346 
    347  if (mUntailAt.IsNull()) {
    348    LOG(("  untail time passed"));
    349    return NS_OK;
    350  }
    351 
    352  if (mAfterDOMContentLoaded && !mNonTailRequests) {
    353    LOG(("  after DOMContentLoaded and no untailed requests"));
    354    return NS_OK;
    355  }
    356 
    357  if (!gHttpHandler) {
    358    // Xpcshell tests may not have http handler
    359    LOG(("  missing gHttpHandler?"));
    360    return NS_OK;
    361  }
    362 
    363  *aBlocked = true;
    364  mTailQueue.AppendElement(aRequest);
    365 
    366  LOG(("  request queued"));
    367 
    368  if (!mUntailTimer) {
    369    ScheduleUnblock();
    370  }
    371 
    372  return NS_OK;
    373 }
    374 
    375 NS_IMETHODIMP
    376 RequestContext::CancelTailedRequest(nsIRequestTailUnblockCallback* aRequest) {
    377  MOZ_ASSERT(NS_IsMainThread());
    378 
    379  bool removed = mTailQueue.RemoveElement(aRequest);
    380 
    381  LOG(("RequestContext::CancelTailedRequest %p req=%p removed=%d", this,
    382       aRequest, removed));
    383 
    384  // Stop untail timer if all tail requests are canceled.
    385  if (removed && mTailQueue.IsEmpty()) {
    386    if (mUntailTimer) {
    387      mUntailTimer->Cancel();
    388      mUntailTimer = nullptr;
    389    }
    390 
    391    // Must drop to allow re-engage of the timer
    392    mTimerScheduledAt = TimeStamp();
    393  }
    394 
    395  return NS_OK;
    396 }
    397 
    398 void RequestContext::ProcessTailQueue(nsresult aResult) {
    399  LOG(("RequestContext::ProcessTailQueue this=%p, queued=%zu, rv=%" PRIx32,
    400       this, mTailQueue.Length(), static_cast<uint32_t>(aResult)));
    401 
    402  if (mUntailTimer) {
    403    mUntailTimer->Cancel();
    404    mUntailTimer = nullptr;
    405  }
    406 
    407  // Must drop to stop tailing requests
    408  mUntailAt = TimeStamp();
    409 
    410  nsTArray<PendingTailRequest> queue = std::move(mTailQueue);
    411 
    412  for (const auto& request : queue) {
    413    LOG(("  untailing %p", request.get()));
    414    request->OnTailUnblock(aResult);
    415  }
    416 }
    417 
    418 NS_IMETHODIMP
    419 RequestContext::CancelTailPendingRequests(nsresult aResult) {
    420  MOZ_ASSERT(NS_IsMainThread());
    421  MOZ_ASSERT(NS_FAILED(aResult));
    422 
    423  ProcessTailQueue(aResult);
    424  return NS_OK;
    425 }
    426 
    427 // nsIRequestContextService
    428 RequestContextService* RequestContextService::sSelf = nullptr;
    429 
    430 NS_IMPL_ISUPPORTS(RequestContextService, nsIRequestContextService, nsIObserver)
    431 
    432 RequestContextService::RequestContextService() {
    433  MOZ_ASSERT(!sSelf, "multiple rcs instances!");
    434  MOZ_ASSERT(NS_IsMainThread());
    435  sSelf = this;
    436 
    437  nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
    438  runtime->GetProcessID(&mRCIDNamespace);
    439 }
    440 
    441 RequestContextService::~RequestContextService() {
    442  MOZ_ASSERT(NS_IsMainThread());
    443  Shutdown();
    444  sSelf = nullptr;
    445 }
    446 
    447 nsresult RequestContextService::Init() {
    448  nsresult rv;
    449 
    450  MOZ_ASSERT(NS_IsMainThread());
    451  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    452  if (!obs) {
    453    return NS_ERROR_NOT_AVAILABLE;
    454  }
    455 
    456  rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
    457  if (NS_FAILED(rv)) {
    458    return rv;
    459  }
    460  obs->AddObserver(this, "content-document-interactive", false);
    461  if (NS_FAILED(rv)) {
    462    return rv;
    463  }
    464 
    465  return NS_OK;
    466 }
    467 
    468 void RequestContextService::Shutdown() {
    469  MOZ_ASSERT(NS_IsMainThread());
    470  // We need to do this to prevent the requests from being scheduled after
    471  // shutdown.
    472  for (const auto& data : mTable.Values()) {
    473    data->CancelTailPendingRequests(NS_ERROR_ABORT);
    474  }
    475  mTable.Clear();
    476  sShutdown = true;
    477 }
    478 
    479 /* static */
    480 already_AddRefed<nsIRequestContextService>
    481 RequestContextService::GetOrCreate() {
    482  MOZ_ASSERT(NS_IsMainThread());
    483 
    484  if (sShutdown) {
    485    return nullptr;
    486  }
    487 
    488  RefPtr<RequestContextService> svc;
    489  if (gSingleton) {
    490    svc = gSingleton;
    491  } else {
    492    svc = new RequestContextService();
    493    nsresult rv = svc->Init();
    494    NS_ENSURE_SUCCESS(rv, nullptr);
    495    gSingleton = svc;
    496    ClearOnShutdown(&gSingleton);
    497  }
    498 
    499  return svc.forget();
    500 }
    501 
    502 NS_IMETHODIMP
    503 RequestContextService::GetRequestContext(const uint64_t rcID,
    504                                         nsIRequestContext** rc) {
    505  MOZ_ASSERT(NS_IsMainThread());
    506  NS_ENSURE_ARG_POINTER(rc);
    507  *rc = nullptr;
    508 
    509  if (sShutdown) {
    510    return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
    511  }
    512 
    513  if (!rcID) {
    514    return NS_ERROR_INVALID_ARG;
    515  }
    516 
    517  *rc = do_AddRef(mTable.LookupOrInsertWith(rcID, [&] {
    518          nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
    519          return newSC;
    520        })).take();
    521 
    522  return NS_OK;
    523 }
    524 
    525 NS_IMETHODIMP
    526 RequestContextService::GetRequestContextFromLoadGroup(nsILoadGroup* aLoadGroup,
    527                                                      nsIRequestContext** rc) {
    528  nsresult rv;
    529 
    530  uint64_t rcID;
    531  rv = aLoadGroup->GetRequestContextID(&rcID);
    532  if (NS_FAILED(rv)) {
    533    return rv;
    534  }
    535 
    536  return GetRequestContext(rcID, rc);
    537 }
    538 
    539 NS_IMETHODIMP
    540 RequestContextService::NewRequestContext(nsIRequestContext** rc) {
    541  MOZ_ASSERT(NS_IsMainThread());
    542  NS_ENSURE_ARG_POINTER(rc);
    543  *rc = nullptr;
    544 
    545  if (sShutdown) {
    546    return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
    547  }
    548 
    549  uint64_t rcID =
    550      ((static_cast<uint64_t>(mRCIDNamespace) << 32) & 0xFFFFFFFF00000000LL) |
    551      mNextRCID++;
    552 
    553  nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
    554  mTable.InsertOrUpdate(rcID, newSC);
    555  newSC.swap(*rc);
    556 
    557  return NS_OK;
    558 }
    559 
    560 NS_IMETHODIMP
    561 RequestContextService::RemoveRequestContext(const uint64_t rcID) {
    562  MOZ_ASSERT(NS_IsMainThread());
    563  mTable.Remove(rcID);
    564  return NS_OK;
    565 }
    566 
    567 NS_IMETHODIMP
    568 RequestContextService::Observe(nsISupports* subject, const char* topic,
    569                               const char16_t* data_unicode) {
    570  MOZ_ASSERT(NS_IsMainThread());
    571  if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
    572    Shutdown();
    573    return NS_OK;
    574  }
    575 
    576  if (!strcmp("content-document-interactive", topic)) {
    577    nsCOMPtr<dom::Document> document(do_QueryInterface(subject));
    578    MOZ_ASSERT(document);
    579    // We want this be triggered also for iframes, since those track their
    580    // own request context ids.
    581    if (!document) {
    582      return NS_OK;
    583    }
    584    nsIDocShell* ds = document->GetDocShell();
    585    // XML documents don't always have a docshell assigned
    586    if (!ds) {
    587      return NS_OK;
    588    }
    589    nsCOMPtr<nsIDocumentLoader> dl(do_QueryInterface(ds));
    590    if (!dl) {
    591      return NS_OK;
    592    }
    593    nsCOMPtr<nsILoadGroup> lg;
    594    dl->GetLoadGroup(getter_AddRefs(lg));
    595    if (!lg) {
    596      return NS_OK;
    597    }
    598    nsCOMPtr<nsIRequestContext> rc;
    599    GetRequestContextFromLoadGroup(lg, getter_AddRefs(rc));
    600    if (rc) {
    601      rc->DOMContentLoaded();
    602    }
    603 
    604    return NS_OK;
    605  }
    606 
    607  MOZ_ASSERT(false, "Unexpected observer topic");
    608  return NS_OK;
    609 }
    610 
    611 }  // namespace net
    612 }  // namespace mozilla
    613 
    614 #undef LOG