Skip to content

Latest commit

 

History

History
467 lines (383 loc) · 15.1 KB

Loading_Appcache_Manifest.md

File metadata and controls

467 lines (383 loc) · 15.1 KB

The flow of loading appcache from html element's manifest attribute

A: nsXMLContentSink::SetDocElement(int32_t aNameSpaceID, nsIAtom* aTagName, nsIContent *aContent)

  if (aTagName == nsGkAtoms::html &&
      aNameSpaceID == kNameSpaceID_XHTML) {
    ProcessOfflineManifest(aContent); // Go to [B-1]
  }

B-1: nsContentSink::ProcessOfflineManifest(nsIContent *aElement)

  // Check for a manifest= attribute.
  nsAutoString manifestSpec;
  aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::manifest, manifestSpec); // Go to [C]
  ProcessOfflineManifest(manifestSpec);

C: nsIContent::GetAttr(int32_t aNameSpaceID, nsIAtom* aName, nsAString& aResult)

  if (IsElement()) {
    return AsElement()->GetAttr(aNameSpaceID, aName, aResult); // Go to [D]
  }

D: Element::GetAttr(int32_t aNameSpaceID, nsIAtom* aName, nsAString& aResult)

  DOMString str;
  bool haveAttr = GetAttr(aNameSpaceID, aName, str); // Go to [E]
  str.ToString(aResult);
  return haveAttr;

E: inline bool GetAttr(int32_t aNameSpaceID, nsIAtom* aName, DOMString& aResult) @ Element.h

  const nsAttrValue* val = mAttrsAndChildren.GetAttr(aName, aNameSpaceID); // Go to [F]
  if (val) {
    val->ToString(aResult);
    return true;
  }

F: nsAttrAndChildArray::GetAttr(nsIAtom* aLocalName, int32_t aNamespaceID)

  // Below would retrieve value of nsGkAtoms::manifest (manifest attribute),
  // then returns result all the way back to [B-1]
  if (aNamespaceID == kNameSpaceID_None) {
    // This should be the common case so lets make an optimized loop
    for (i = 0; i < slotCount && AttrSlotIsTaken(i); ++i) {
      if (ATTRS(mImpl)[i].mName.Equals(aLocalName)) {
        return &ATTRS(mImpl)[i].mValue;
      }
    }

    if (mImpl && mImpl->mMappedAttrs) {
      return mImpl->mMappedAttrs->GetAttr(aLocalName);
    }
  }

B-1: nsContentSink::ProcessOfflineManifest(nsIContent *aElement)

  // Check for a manifest= attribute.
  nsAutoString manifestSpec;
  aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::manifest, manifestSpec);
  ProcessOfflineManifest(manifestSpec); // Get the manifest, go to [B-2]

B-2: nsContentSink::ProcessOfflineManifest(const nsAString& aManifestSpec)

  // Before proceeding to here, would went through checks of
  //  - if the document was intercepted by Service Worker, then skip processing appcache manifest
  //  - if in private browsing mode, then skip processing appcache manifest
  //  - if empty appcache manifest and no appcache, then skip processing appcache manifest
  //  - if no permission of offline APIs and no auto-granted permission, then skip processing appcache manifest.
  // Then go to [B-3].
  rv = SelectDocAppCache(applicationCache, manifestURI, fetchedWithHTTPGetOrEquiv, &action);
  if (NS_FAILED(rv)) {
    return;
  }

B-3: nsContentSink::SelectDocAppCache

  // In this method, it would decide what action to take on the given appcache manifest.
  // For the 1st time met appcache (no applicationCache), the action would as below, then back to [B-2].
  *aAction = CACHE_SELECTION_UPDATE;

B-2: nsContentSink::ProcessOfflineManifest(const nsAString& aManifestSpec)

  case CACHE_SELECTION_UPDATE: {
    nsCOMPtr<nsIOfflineCacheUpdateService> updateService =
      do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID);

    if (updateService) {
      nsCOMPtr<nsIDOMDocument> domdoc = do_QueryInterface(mDocument);
      // Go to [I-1]
      updateService->ScheduleOnDocumentStop(manifestURI, mDocumentURI, mDocument->NodePrincipal(), domdoc);
    }
    break;
  }

I-1: nsOfflineCacheUpdateService::ScheduleOnDocumentStop

  // Proceed with cache update
  RefPtr<nsOfflineCachePendingUpdate> update = new nsOfflineCachePendingUpdate(this, aManifestURI, aDocumentURI, aLoadingPrincipal, aDocument);
  NS_ENSURE_TRUE(update, NS_ERROR_OUT_OF_MEMORY);

  // This would listent to document load state changes, which would invoke [J-1].
  nsresult rv = progress->AddProgressListener(update, nsIWebProgress::NOTIFY_STATE_DOCUMENT);

J-1: nsOfflineCachePendingUpdate::OnStateChange

  nsCOMPtr<nsIDOMDocument> updateDoc = do_QueryReferent(mDocument);
  if (!updateDoc) {
      // The document that scheduled this update has gone away,
      // we don't need to listen anymore.
      aWebProgress->RemoveProgressListener(this);
      MOZ_ASSERT(!mDidReleaseThis);
      mDidReleaseThis = true;
      NS_RELEASE_THIS();
      return NS_OK;
  }
  // Proceed after document load stops
  if (!(progressStateFlags & STATE_STOP)) {
      return NS_OK;
  }

  // ......

  // Only schedule the update if the document loaded successfully
  if (NS_SUCCEEDED(aStatus)) {
      nsCOMPtr<nsIOfflineCacheUpdate> update;
      // Go to [I-2]
      mService->Schedule(mManifestURI, mDocumentURI, mLoadingPrincipal, updateDoc, innerWindow, nullptr, getter_AddRefs(update));
      if (mDidReleaseThis) {
          return NS_OK;
      }
  }

I-2: nsOfflineCacheUpdateService::Schedule

  nsCOMPtr<nsIOfflineCacheUpdate> update;
  if (GeckoProcessType_Default != XRE_GetProcessType()) { // The content proccess
      // I-2-1: OfflineCacheUpdateChild::Schedule()
      update = new OfflineCacheUpdateChild(aWindow);
  }
  else {
      // I-2-2: OfflineCacheUpdateGlue::Schedule()
      update = new OfflineCacheUpdateGlue();
  }

  // .......

  // Invoke [I-2-1] or [I-2-2], then go to [L-1]
  rv = update->Init(aManifestURI, aDocumentURI, aLoadingPrincipal, aDocument, aCustomProfileDir);

  rv = update->Schedule();

L-1: nsOfflineCacheUpdate::Init(nsIURI *aManifestURI, nsIURI aDocumentURI, nsIPrincipal aLoadingPrincipal, nsIDOMDocument *aDocument, nsIFile *aCustomProfileDir)

  rv = InitInternal(aManifestURI, aLoadingPrincipal); // Go to L-2
  NS_ENSURE_SUCCESS(rv, rv);

L-2: nsOfflineCacheUpdate::InitInternal(nsIURI aManifestURI, nsIPrincipal aLoadingPrincipal)

  // Only http and https applications are supported.
  bool match;
  rv = aManifestURI->SchemeIs("http", &match);
  NS_ENSURE_SUCCESS(rv, rv);

  if (!match) {
      rv = aManifestURI->SchemeIs("https", &match);
      NS_ENSURE_SUCCESS(rv, rv);
      if (!match)
          return NS_ERROR_ABORT;
  }

  // ......

  rv = mManifestURI->GetAsciiHost(mUpdateDomain); // Back to [L-1]

L-1: nsOfflineCacheUpdate::Init(nsIURI *aManifestURI, nsIURI aDocumentURI, nsIPrincipal aLoadingPrincipal, nsIDOMDocument *aDocument, nsIFile *aCustomProfileDir)

  // ......

  nsAutoCString originSuffix;
  rv = aLoadingPrincipal->GetOriginSuffix(originSuffix);

  // ......

  if (aCustomProfileDir) {
    // ......
  } else {
    // Below cacheService is nsIApplicationCacheService

    // This would lead to [M-1]
    rv = cacheService->BuildGroupIDForSuffix(aManifestURI, originSuffix, mGroupID);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = cacheService->GetActiveCache(mGroupID, getter_AddRefs(mPreviousApplicationCache));
    NS_ENSURE_SUCCESS(rv, rv);

    // This would lead to [M-2]
    rv = cacheService->CreateApplicationCache(mGroupID, getter_AddRefs(mApplicationCache));
    NS_ENSURE_SUCCESS(rv, rv);
  }

M-1: nsOfflineCacheDevice::BuildApplicationCacheGroupID(nsIURI *aManifestURL, nsACString const &aOriginSuffix, nsACString &_result)

  nsCOMPtr<nsIURI> newURI;
  nsresult rv = aManifestURL->CloneIgnoringRef(getter_AddRefs(newURI));
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString manifestSpec;
  rv = newURI->GetAsciiSpec(manifestSpec);
  NS_ENSURE_SUCCESS(rv, rv);

  // Here is the place building up appcache group Id, which is the uri to appcache.
  // Then, back to [L-1]
  _result.Assign(manifestSpec);
  _result.Append('#');
  _result.Append(aOriginSuffix);

M-2: nsOfflineCacheDevice::CreateApplicationCache(const nsACString &group, nsIApplicationCache **out)

  // Include the timestamp to guarantee uniqueness across runs, and
  // the gNextTemporaryClientID for uniqueness within a second.
  clientID.Append(nsPrintfCString("|%016" PRId64 "|%d", now / PR_USEC_PER_SEC, gNextTemporaryClientID++));

  nsCOMPtr<nsIApplicationCache> cache = new nsApplicationCache(this, group, clientID);

  // ......

  MutexAutoLock lock(mLock);
  mCaches.Put(clientID, weak);

  cache.swap(*out); // Back to [L-1]

L-1: nsOfflineCacheUpdate::Init

  rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aDocumentURI, nullptr, &mPinned);
  NS_ENSURE_SUCCESS(rv, rv);

  mState = STATE_INITIALIZED;
  return NS_OK; // Back to [I-2]

I-2: nsOfflineCacheUpdateService::Schedule

  rv = update->Schedule(); // Eventually lead to [L-2]

L-2: nsOfflineCacheUpdate::Schedule()

  return service->ScheduleUpdate(this); // Go to [I-3].

I-3: nsOfflineCacheUpdateService::ScheduleUpdate(nsOfflineCacheUpdate *aUpdate)

  aUpdate->SetOwner(this);

  mUpdates.AppendElement(aUpdate);
  ProcessNextUpdate(); // Go to [I-4]

I-4: nsOfflineCacheUpdateService::ProcessNextUpdate()

  if (mUpdates.Length() > 0) {
      mUpdateRunning = true;
      // Canceling the update before Begin() call will make the update
      // asynchronously finish with an error.
      if (mLowFreeSpace) {
          mUpdates[0]->Cancel();
      }
      return mUpdates[0]->Begin(); // Go to [L-3]
  }

L-3: nsOfflineCacheUpdate::Begin()

  // Start checking the manifest.
  mManifestItem = new nsOfflineManifestItem(mManifestURI,
                                            mDocumentURI,
                                            mLoadingPrincipal,
                                            mApplicationCache,
                                            mPreviousApplicationCache);
  if (!mManifestItem) {
      return NS_ERROR_OUT_OF_MEMORY;
  }

  // ......

  nsresult rv = mManifestItem->OpenChannel(this); // Go to [N-1]
  if (NS_FAILED(rv)) {
      LoadCompleted(mManifestItem); // Go to [L-4]
  }
  return NS_OK;

N-1: nsOfflineCacheUpdateItem::OpenChannel(nsOfflineCacheUpdate *aUpdate)

  // Here would open a http channel to start http request for manifest file and appcache resources.

  nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
      do_QueryInterface(mChannel, &rv);

  // Support for nsIApplicationCacheChannel is required.
  NS_ENSURE_SUCCESS(rv, rv);

  // Use the existing application cache as the cache to check.
  rv = appCacheChannel->SetApplicationCache(mPreviousApplicationCache);
  NS_ENSURE_SUCCESS(rv, rv);

  // Set the new application cache as the target for write.
  rv = appCacheChannel->SetApplicationCacheForWrite(mApplicationCache);
  NS_ENSURE_SUCCESS(rv, rv);
  // configure HTTP specific stuff
  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
  if (httpChannel) {
      rv = httpChannel->SetReferrer(mReferrerURI);
      MOZ_ASSERT(NS_SUCCEEDED(rv));
      rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
                                         NS_LITERAL_CSTRING("offline-resource"),
                                         false);
      MOZ_ASSERT(NS_SUCCEEDED(rv));
  }

  // This would async trigger [N-2]
  rv = mChannel->AsyncOpen2(this);
  NS_ENSURE_SUCCESS(rv, rv);

  mUpdate = aUpdate;
  mState = LoadStatus::REQUESTED;
  return NS_OK; // Back to [L-3]

N-2: nsOfflineCacheUpdateItem::OnStopRequest

  // We need to notify the update that the load is complete, but we
  // want to give the channel a chance to close the cache entries.
  NS_DispatchToCurrentThread(this); // Go to [N-3]

N-3: nsOfflineCacheUpdateItem::Run()

  update->LoadCompleted(this); // Go to [L-4]

L-4: nsOfflineCacheUpdate::LoadCompleted(nsOfflineCacheUpdateItem *aItem)

  // ......

  if (mState == STATE_CHECKING) { // Inside state checking manifest file
    // ......

    // A 404 or 410 is interpreted as an intentional removal of
    // the manifest file, rather than a transient server error.
    // Obsolete this cache group if one of these is returned.
    uint16_t status;
    rv = mManifestItem->GetStatus(&status);
    if (status == 404 || status == 410) {
        // ......
        Finish();
        return;
    }

    bool doUpdate;
    // Go to [L-5]
    if (NS_FAILED(HandleManifest(&doUpdate))) {
        mSucceeded = false;
        NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
        Finish();
        return;
    }


    if (!doUpdate) {
      // ......
      // No need to update, bye!
      return;
    }}

    // Save appcache manifest resource. This is going to create an appcache entry inside DB,
    // which then leads to [M-3]
    rv = mApplicationCache->MarkEntry(mManifestItem->mCacheKey, mManifestItem->mItemType);

    // Switch to the state downloading appcache resources given by manifest
    mState = STATE_DOWNLOADING;
    NotifyState(nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING);

    // Start fetching appcache resources given by manifest.
    ProcessNextURI();

    return;
  }

  // Below are codes processing appcache resources given by manifest......

L-5: nsOfflineCacheUpdate::HandleManifest(bool *aDoUpdate)

  *aDoUpdate = false;

  bool succeeded;
  nsresult rv = mManifestItem->GetRequestSucceeded(&succeeded);
  NS_ENSURE_SUCCESS(rv, rv);

  // Don't update if getting or parsing manifest failed
  if (!succeeded || !mManifestItem->ParseSucceeded()) {
      return NS_ERROR_FAILURE;
  }

  // Don't update if no needs.
  // So for the 1st time of retrieving, always results in needing update
  if (!mManifestItem->NeedsUpdate()) {
      return NS_OK;
  }

  // Add appcache resources specified inside manifest into pending queue to procecss next
  // Add items requested by the manifest.
  const nsCOMArray<nsIURI> &manifestURIs = mManifestItem->GetExplicitURIs();
  for (int32_t i = 0; i < manifestURIs.Count(); i++) {
      rv = AddURI(manifestURIs[i], nsIApplicationCache::ITEM_EXPLICIT);
      NS_ENSURE_SUCCESS(rv, rv);
  }

  // Keep adding resources into pending queue to procecss next......

  // Tell outside, we need to do update, back to [L-4]
  *aDoUpdate = true;
  return NS_OK;

M-3: nsOfflineCacheDevice::MarkEntry(const nsCString &clientID, const nsACString &key, uint32_t typeBits)

  // Assemble SQL statement
  AutoResetStatement statement(mStatement_MarkEntry);
  nsresult rv = statement->BindInt32ByIndex(0, typeBits);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = statement->BindUTF8StringByIndex(1, clientID);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = statement->BindUTF8StringByIndex(2, key);
  NS_ENSURE_SUCCESS(rv, rv);

  // Execute SQL, leading to [P-1]
  rv = statement->Execute();

P-1: Statement::ExecuteStep(bool *_moreResults)

  // DB operation finally is reached!
  int srv = mDBConnection->stepStatement(mNativeConnection, mDBStatement);