/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */
nsIXPConnect* xpc = nsContentUtils::XPConnect();
MOZ_ASSERT(xpc, "This should never be null!");
JS::Rooted<JSObject*> sandbox(aCx);
aRv = xpc->CreateSandbox(aCx, aPrincipal, sandbox.address()); if (NS_WARN_IF(aRv.Failed())) { return nullptr;
}
// This is called when the JSContext is not in a realm, so CreateSandbox // returned an unwrapped global.
MOZ_ASSERT(JS_IsGlobalObject(sandbox));
// We assume private browsing is not enabled here. The ScriptLoader // explicitly fails for private browsing so there should never be // a service worker running in private browsing mode. Therefore if // we are purging scripts or running a comparison algorithm we cannot // be in private browsing. // // Also, bypass the CacheStorage trusted origin checks. The ServiceWorker // has validated the origin prior to this point. All the information // to revalidate is not available now. return CacheStorage::CreateOnMainThread(cache::CHROME_ONLY_NAMESPACE,
sandboxGlobalObject, aPrincipal, true/* force trusted origin */, aRv);
}
class CompareManager; class CompareCache;
// This class downloads a URL from the network, compare the downloaded script // with an existing cache if provided, and report to CompareManager via calling // ComparisonFinished(). class CompareNetwork final : public nsIStreamLoaderObserver, public nsIRequestObserver { public:
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLOADEROBSERVER
NS_DECL_NSIREQUESTOBSERVER
// This class gets a cached Response from the CacheStorage and then it calls // CacheFinish() in the CompareNetwork. class CompareCache final : public PromiseNativeHandler, public nsIStreamLoaderObserver { public:
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLOADEROBSERVER
Optional<RequestOrUTF8String> request;
CacheQueryOptions options;
ErrorResult error;
RefPtr<Promise> promise = mOldCache->Keys(aCx, request, options, error); if (NS_WARN_IF(error.Failed())) { // No exception here because there are no ReadableStreams involved here.
MOZ_ASSERT(!error.IsJSException());
rv = error.StealNSResult(); return;
}
// Extract the list of URLs in the old cache. for (uint32_t i = 0; i < len; ++i) {
JS::Rooted<JS::Value> val(aCx); if (NS_WARN_IF(!JS_GetElement(aCx, obj, i, &val)) ||
NS_WARN_IF(!val.isObject())) { return;
}
// If the main script is missing, then something has gone wrong. We // will try to continue with the update process to trigger a new // installation. If that fails, however, then uninstall the registration // because it is broken in a way that cannot be fixed. if (!hasMainScript) {
mOnFailure = OnFailure::Uninstall;
}
// Always make sure to fetch the main script. If the old cache has // no entries or the main script entry is missing, then the loop below // may not trigger it. This should not really happen, but we handle it // gracefully if it does occur. Its possible the bad cache state is due // to a crash or shutdown during an update, etc.
rv = FetchScript(mURL, true/* aIsMainScript */, mOldCache); if (NS_WARN_IF(NS_FAILED(rv))) { return;
}
for (constauto& url : urlList) { // We explicitly start the fetch for the main script above. if (mURL == url) { continue;
}
// Just to be safe.
RefPtr<Cache> kungfuDeathGrip = cache;
MOZ_ASSERT(mPendingCount == 0); for (uint32_t i = 0; i < mCNList.Length(); ++i) { // We bail out immediately when something goes wrong.
rv = WriteToCache(aCx, cache, mCNList[i]); if (NS_WARN_IF(NS_FAILED(rv))) { return;
}
}
// For now we have to wait until the Put Promise is fulfilled before we can // continue since Cache does not yet support starting a read that is being // written to.
ErrorResult result;
RefPtr<Promise> cachePromise = aCache->Put(aCx, request, *response, result);
result.WouldReportJSException(); if (NS_WARN_IF(result.Failed())) { // No exception here because there are no ReadableStreams involved here.
MOZ_ASSERT(!result.IsJSException());
MOZ_ASSERT(!result.IsErrorWithMessage()); return result.StealNSResult();
}
if (mRegistration->IsLastUpdateCheckTimeOverOneDay()) {
mLoadFlags |= nsIRequest::LOAD_BYPASS_CACHE;
}
// Different settings are needed for fetching imported scripts, since they // might be cross-origin scripts.
uint32_t secFlags =
mIsMainScript ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
: nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
// Create a new cookieJarSettings.
nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
mozilla::net::CookieJarSettings::Create(aPrincipal);
// Populate the partitionKey by using the given prinicpal. The ServiceWorkers // are using the foreign partitioned principal, so we can get the partitionKey // from it and the partitionKey will only exist if it's in the third-party // context. In first-party context, we can still use the uri to set the // partitionKey. if (!aPrincipal->OriginAttributesRef().mPartitionKey.IsEmpty()) {
net::CookieJarSettings::Cast(cookieJarSettings)
->SetPartitionKey(aPrincipal->OriginAttributesRef().mPartitionKey);
} else {
net::CookieJarSettings::Cast(cookieJarSettings)
->SetPartitionKey(uri, false);
}
// Note that because there is no "serviceworker" RequestContext type, we can // use the TYPE_INTERNAL_SCRIPT content policy types when loading a service // worker.
rv = NS_NewChannel(getter_AddRefs(mChannel), uri, aPrincipal, secFlags,
contentPolicyType, cookieJarSettings,
nullptr /* aPerformanceStorage */, loadGroup,
nullptr /* aCallbacks */, mLoadFlags); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
// Set the IsInThirdPartyContext for the channel's loadInfo according to the // partitionKey of the principal. The worker is foreign if it's using // partitioned principal, i.e. the partitionKey is not empty. In this case, // we need to set the bit to the channel's loadInfo. if (!aPrincipal->OriginAttributesRef().mPartitionKey.IsEmpty()) {
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
rv = loadInfo->SetIsInThirdPartyContext(true);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel); if (httpChannel) { // Spec says no redirects allowed for top-level SW scripts. if (mIsMainScript) {
rv = httpChannel->SetRedirectionLimit(0);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
rv = mChannel->AsyncOpen(loader); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
// If we do have an existing cache to compare with. if (aCache) {
mCC = new CompareCache(this);
rv = mCC->Initialize(aCache, aURL); if (NS_WARN_IF(NS_FAILED(rv))) {
Abort(); return rv;
}
void CompareNetwork::Finish() { if (mState == Finished) { return;
}
bool same = true;
nsresult rv = NS_OK;
// mNetworkResult is prior to mCacheResult, since it's needed for reporting // various errors to web content. if (NS_FAILED(mNetworkResult)) { // An imported script could become offline, since it might no longer be // needed by the new importing script. In that case, the importing script // must be different, and thus, it's okay to report same script found here.
rv = mIsMainScript ? mNetworkResult : NS_OK;
same = true;
} elseif (mCC && NS_FAILED(mCacheResult)) {
rv = mCacheResult;
} else { // Both passed.
same = mCC && mCC->InCache() && mCC->Buffer().Equals(mBuffer);
}
if (isExtension &&
!StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup()) { // Return earlier with error is the worker script is a moz-extension url // but the feature isn't enabled by prefs. return NS_ERROR_FAILURE;
}
if (isExtension) { // NOTE: trying to register any moz-extension use that doesn't ends // with .js/.jsm/.mjs seems to be already completing with an error // in aStatus and they never reach this point.
// TODO: look into avoid duplicated parts that could be shared with the HTTP // channel scenario.
nsCOMPtr<nsIURI> channelURL;
rv = channel->GetURI(getter_AddRefs(channelURL)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
// Append the final URL (which for an extension worker script is going to // be a file or jar url).
MOZ_DIAGNOSTIC_ASSERT(!mURLList.IsEmpty()); if (channelURLSpec != mURLList[0]) {
mURLList.AppendElement(channelURLSpec);
}
UniquePtr<char16_t[], JS::FreePolicy> buffer;
size_t len = 0;
// Main scripts cannot be redirected successfully, however extensions // may successfuly redirect imported scripts to a moz-extension url // (if listed in the web_accessible_resources manifest property). // // When the service worker is initially registered the imported scripts // will be loaded from the child process (see dom/workers/ScriptLoader.cpp) // and in that case this method will only be called for the main script. // // When a registered worker is loaded again (e.g. when the webpage calls // the ServiceWorkerRegistration's update method): // // - both the main and imported scripts are loaded by the // CompareManager::FetchScript // - the update requests for the imported scripts will also be calling this // method and we should expect scripts redirected to an extension script // to have a null httpChannel. // // The request that triggers this method is: // // - the one that is coming from the network (which may be intercepted by // WebRequest listeners in extensions and redirected to a web_accessible // moz-extension url) // - it will then be compared with a previous response that we may have // in the cache // // When the next service worker update occurs, if the request (for an imported // script) is not redirected by an extension the cache entry is invalidated // and a network request is triggered for the import. if (!httpChannel) { // Redirecting a service worker main script should fail before reaching this // method. // If a main script is somehow redirected, the diagnostic assert will crash // in non-release builds. Release builds will return an explicit error.
MOZ_DIAGNOSTIC_ASSERT(!mIsMainScript, "Unexpected ServiceWorker main script redirected"); if (mIsMainScript) { return NS_ERROR_UNEXPECTED;
}
nsCOMPtr<nsIPrincipal> channelPrincipal;
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); if (!ssm) { return NS_ERROR_UNEXPECTED;
}
// An extension did redirect a non-MainScript request to a moz-extension url // (in that case the originalURL is the resolved jar URI and so we have to // look to the channel principal instead). if (channelPrincipal->SchemeIs("moz-extension")) {
UniquePtr<char16_t[], JS::FreePolicy> buffer;
size_t len = 0;
// Make non-release and debug builds to crash if this happens and fail // explicitly on release builds.
MOZ_DIAGNOSTIC_CRASH( "ServiceWorker imported script redirected to an url " "with an unexpected scheme"); return NS_ERROR_UNEXPECTED;
}
if (NS_WARN_IF(!requestSucceeded)) { // Get the stringified numeric status code, not statusText which could be // something misleading like OK for a 404.
uint32_t status = 0;
Unused << httpChannel->GetResponseStatus(
&status); // don't care if this fails, use 0.
nsAutoString statusAsText;
statusAsText.AppendInt(status);
// Note: we explicitly don't check for the return value here, because the // absence of the header is not an error condition.
Unused << httpChannel->GetResponseHeader("Service-Worker-Allowed"_ns,
mMaxScope);
// [9.2 Update]4.13, If response's cache state is not "local", // set registration's last update check time to the current time if (!mIsFromCache) {
mRegistration->RefreshLastUpdateCheckTime();
}
nsAutoCString mimeType;
rv = httpChannel->GetContentType(mimeType); if (NS_WARN_IF(NS_FAILED(rv))) { // We should only end up here if !mResponseHead in the channel. If headers // were received but no content type was specified, we'll be given // UNKNOWN_CONTENT_TYPE "application/x-unknown-content-type" and so fall // into the next case with its better error message.
rv = NS_ERROR_DOM_SECURITY_ERR; return rv;
}
// Append the final URL if its different from the original // request URL. This lets us note that a redirect occurred // even though we don't track every redirect URL here.
MOZ_DIAGNOSTIC_ASSERT(!mURLList.IsEmpty()); if (channelURLSpec != mURLList[0]) {
mURLList.AppendElement(channelURLSpec);
}
UniquePtr<char16_t[], JS::FreePolicy> buffer;
size_t len = 0;
// This JSContext will not end up executing JS code because here there are // no ReadableStreams involved.
AutoJSAPI jsapi;
jsapi.Init();
RequestOrUTF8String request;
request.SetAsUTF8String().ShareOrDependUpon(aURL);
ErrorResult error;
CacheQueryOptions params;
RefPtr<Promise> promise = aCache->Match(jsapi.cx(), request, params, error); if (NS_WARN_IF(error.Failed())) { // No exception here because there are no ReadableStreams involved here.
MOZ_ASSERT(!error.IsJSException());
mState = Finished; return error.StealNSResult();
}
// Retrieve the script from aCache.
mState = WaitingForScript;
promise->AppendNativeHandler(this); return NS_OK;
}
// RAII Cleanup when fails. auto guard = MakeScopeExit([&] { Cleanup(); });
mURL = aURL;
mPrincipal = aPrincipal;
// Always create a CacheStorage since we want to write the network entry to // the cache even if there isn't an existing one.
AutoJSAPI jsapi;
jsapi.Init();
ErrorResult result;
mCacheStorage = CreateCacheStorage(jsapi.cx(), aPrincipal, result); if (NS_WARN_IF(result.Failed())) {
MOZ_ASSERT(!result.IsErrorWithMessage()); return result.StealNSResult();
}
// If there is no existing cache, proceed to fetch the script directly. if (aCacheName.IsEmpty()) {
mState = WaitingForScriptOrComparisonResult;
nsresult rv = FetchScript(aURL, true/* aIsMainScript */); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
guard.release(); return NS_OK;
}
// Open the cache saving the old source scripts.
RefPtr<Promise> promise = mCacheStorage->Open(aCacheName, result); if (NS_WARN_IF(result.Failed())) {
MOZ_ASSERT(!result.IsErrorWithMessage()); return result.StealNSResult();
}
// This class manages 4 promises if needed: // 1. Retrieve the Cache object by a given CacheName of OldCache. // 2. Retrieve the URLs saved in OldCache. // 3. Retrieve the Cache object of the NewCache for the newly created SW. // 4. Put the value in the cache. // For this reason we have mState to know what callback we are handling. void CompareManager::ResolvedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mCallback);
switch (mState) { case Finished: return; case WaitingForExistingOpen:
ManageOldCache(aCx, aValue); return; case WaitingForExistingKeys:
ManageOldKeys(aCx, aValue); return; case WaitingForOpen:
ManageNewCache(aCx, aValue); return; case WaitingForPut:
MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0); if (--mPendingCount == 0) {
mCallback->ComparisonResult(NS_OK, false/* aIsEqual */, mOnFailure,
mNewCacheName, mMaxScope, mLoadFlags);
Cleanup();
} return; default:
MOZ_DIAGNOSTIC_CRASH("Missing case in CompareManager::ResolvedCallback");
}
}
void CompareManager::RejectedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread()); switch (mState) { case Finished: return; case WaitingForExistingOpen:
NS_WARNING("Could not open the existing cache."); break; case WaitingForExistingKeys:
NS_WARNING("Could not get the existing URLs."); break; case WaitingForOpen:
NS_WARNING("Could not open cache."); break; case WaitingForPut:
NS_WARNING("Could not write to cache."); break; default:
MOZ_DIAGNOSTIC_CRASH("Missing case in CompareManager::RejectedCallback");
}
// We use the ServiceWorker scope as key for the cacheStorage.
RefPtr<Promise> promise = cacheStorage->Delete(aCacheName, rv); if (NS_WARN_IF(rv.Failed())) { return rv.StealNSResult();
}
// Set [[PromiseIsHandled]] to ensure that if this promise gets rejected, // we don't end up reporting a rejected promise to the console.
MOZ_ALWAYS_TRUE(promise->SetAnyPromiseIsHandled());
// We don't actually care about the result of the delete operation. return NS_OK;
}
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.