From 2d4b405bccddef50fe27dea4e28d8b525b4035d8 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Wed, 23 Mar 2022 13:19:07 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1175413 - Cleanup usages of MOZ_ICU_CFLAGS. r=mshal (6d9b5f5be9) - Bug 1201693 - Remove Files pattern in js/src/moz.build corresponding to a deleted file. r=bbouvier (da12fd6ff8) - Bug 1185106 - Part 1: Exclude StoreBuffer.cpp from unified build to prevent build bustage. r=efaust (a40e13855c) - Bug 1145744 - Update CacheStorage to use common StorageAllowedForWindow logic, r=bkelly (0f3f86339c) - Bug 1147821 - Update IndexedDB to use common StorageAllowedForWindow logic, r=khuey (775734bee9) - Bug 1177226 - Support User Timing API events in the Developer HUD. r=ehsan, r=jryans (6fb8a84a47) - Bug 1184973 - Part 1: Add nsContentUtils::StorageAllowedForWindow as a unified mechanism for determining storage avaliability, r=ehsan, r=smaug (f4e6954cdb) - Bug 1184973 - Part 2: Tests for new storage permissions model, r=ehsan, r=smaug (92b414cd78) - Bug 1184607 P7.1 Move Cache schema SQL into separate constants. r=ehsan (ba543e589a) - Bug 1184607 P7.2 Validate Cache schema in debug builds. r=ehsan (5edc1a2ee6) - Bug 1184607 P7.4 Infrastructure for running Cache schema migrations. r=ehsan (f736d93955) - Bug 1184607 P7.3 Rename Cache "max wipe version" constant to "first shipped version". r=ehsan (5af8cd0966) - Bug 1184607 P7.5 Add RequestRedirect to Cache API schema with migration. r=ehsan (5737cb9897) - Bug 1184607 P2 Update Request and Response DOM objects for new redirect model. r=nsm (97e5b8c912) - Bug 1184607 P3 Add a RedirectMode flag to nsIHttpChannelInternal. r=nsm (ebf9145d09) - Bug 1184607 P4 Handle the RequestRedirect mode during service worker interception. r=nsm (2edc748b4c) - Bug 1184607 P5 Set RequestRedirect to "manual" for navigations. r=nsm (5484e78f2a) - Bug 1196592: Make retargeting Fetch to another thread actually work. r=nsm (3dcf206438) - Bug 1196524 - Add assertions to enforce that we don't attempt to perform a CORS preflight for fetches of no-cors requests; r=nsm (626b305328) - Bug 1184607 P6 Set RequestRedirect and fix various redirect bugs in FetchDriver. r=nsm (420dbd1579) - Bug 1193911 - Ensure synthetic Responses gets a valid channel info. r=ehsan (fa5158bc8f) - Bug 1184607 P7.7 Allow new Response() to be used in xpcshell tests. r=ehsan (8d66046592) - Bug 1184607 P7.8 Test Cache API schema verison migrations. r=ehsan (7d2a23e199) - Bug 1184607 P8 Fix mochitests to store opaqueredirect responses in Cache for navigation URLs. r=nsm (b280d3e551) - Bug 1184607 P9 Add wpt tests to verify service worker redirect logic. r=nsm (9ad92d5921) - Bug 1184607 P10 Expose channel security info during e10s redirect. Support security info in redirecting interceptions. r=jduell (fae135001e) - Bug 1171127 - Listeners are not released in OnStopRequest in e10s mode. r=jduell (0b63670825) - Bug 1203680 P7 Fix e10s handling on interceptions resulting in redirect status codes. r=jdm (f2ff5c5953) - Bug 1203680 P5 Make ServiceWorkerManager ensure channel upload stream is cloneable. r=nsm (c5459283d5) - Bug 572151 - Remove calls to SetCookies from HttpChannelChild. r=jduell (67efa04159) - Bug 1184607 P11 Fix fetch CORS tests to not expect same-origin requests that redirect to preflight. r=nsm (10df6003f7) - Bug 1184607 P7.6 Expose CacheStorage .caches property on xpcshell global. r=smaug (5bf8d69439) - Bug 1157468 - Avoid leaking the intercepting channel stream listener for redirected IPC channels. r=mayhemer (1b33a488cc) - Bug 1199049 - Part 1: Move nsCORSListenerProxy.* to necko; r=jduell (8e2cc6361e) - Bug 1199049 - Part 2: Add a channel API for requesting CORS preflights; r=jduell (bf417aa318) --- b2g/app/b2g.js | 3 + b2g/chrome/content/devtools/hud.js | 74 ++ configure.in | 1 + docshell/base/nsDocShell.cpp | 3 + dom/base/EventSource.cpp | 1 - dom/base/ImportManager.cpp | 1 - dom/base/nsContentUtils.cpp | 151 +++ dom/base/nsContentUtils.h | 55 + dom/base/nsGlobalWindow.cpp | 9 +- dom/base/nsPerformance.cpp | 40 +- dom/base/nsPerformance.h | 1 + dom/cache/CacheStorage.cpp | 47 +- dom/cache/CacheStorage.h | 5 +- dom/cache/CacheTypes.ipdlh | 2 + dom/cache/DBAction.cpp | 2 +- dom/cache/DBSchema.cpp | 531 +++++++--- dom/cache/DBSchema.h | 8 +- dom/cache/IPCUtils.h | 5 + dom/cache/Manager.cpp | 13 +- dom/cache/TypeUtils.cpp | 21 +- dom/cache/moz.build | 4 + dom/cache/test/xpcshell/head.js | 77 ++ dom/cache/test/xpcshell/make_profile.js | 142 +++ dom/cache/test/xpcshell/schema_15_profile.zip | Bin 0 -> 2577 bytes dom/cache/test/xpcshell/test_migration.js | 38 + dom/cache/test/xpcshell/xpcshell.ini | 16 + dom/fetch/ChannelInfo.cpp | 36 + dom/fetch/ChannelInfo.h | 4 + dom/fetch/FetchDriver.cpp | 231 +++- dom/fetch/FetchDriver.h | 28 +- dom/fetch/InternalRequest.cpp | 2 + dom/fetch/InternalRequest.h | 15 + dom/fetch/InternalResponse.cpp | 11 +- dom/fetch/InternalResponse.h | 31 +- dom/fetch/Request.cpp | 4 + dom/fetch/Request.h | 6 + dom/fetch/Response.cpp | 19 + dom/html/HTMLMediaElement.cpp | 1 - dom/indexedDB/IDBFactory.cpp | 50 +- dom/indexedDB/test/test_third_party.html | 36 +- dom/locales/en-US/chrome/dom/dom.properties | 4 +- dom/security/moz.build | 2 - dom/security/nsCSPContext.cpp | 2 + dom/tests/mochitest/fetch/test_fetch_cors.js | 32 +- .../general/frameStorageAllowed.html | 22 + .../mochitest/general/frameStorageChrome.html | 19 + .../general/frameStorageNullprincipal.sjs | 33 + .../general/frameStoragePrevented.html | 35 + dom/tests/mochitest/general/mochitest.ini | 15 + .../general/storagePermissionsUtils.js | 232 ++++ .../test_storagePermissionsAccept.html | 42 + .../test_storagePermissionsLimitForeign.html | 42 + .../test_storagePermissionsReject.html | 41 + .../test_storagePermissionsRejectForeign.html | 42 + .../mochitest/general/workerStorageAllowed.js | 61 ++ .../general/workerStoragePrevented.js | 61 ++ dom/webidl/PerformanceEntryEvent.webidl | 27 + dom/webidl/Request.webidl | 3 + dom/webidl/Response.webidl | 2 +- dom/webidl/moz.build | 1 + dom/workers/ScriptLoader.cpp | 4 +- dom/workers/ServiceWorkerEvents.cpp | 29 +- dom/workers/ServiceWorkerManager.cpp | 146 ++- dom/workers/WorkerPrivate.cpp | 15 +- dom/workers/WorkerPrivate.h | 6 +- dom/workers/WorkerScope.cpp | 3 +- dom/workers/Workers.h | 2 +- .../serviceworkers/fetch/https/https_test.js | 11 +- .../serviceworkers/fetch/https/register.html | 8 +- .../fetch/origin/https/origin_test.js | 3 +- .../origin/https/realindex.html^headers^ | 1 - .../fetch/origin/origin_test.js | 6 +- .../fetch/origin/realindex.html^headers^ | 1 - dom/workers/test/serviceworkers/mochitest.ini | 2 - .../test/serviceworkers/test_https_fetch.html | 4 + intl/unicharutil/util/internal/Makefile.in | 8 - intl/unicharutil/util/internal/moz.build | 3 + js/src/Makefile.in | 20 - js/src/moz.build | 10 +- js/xpconnect/src/Sandbox.cpp | 6 + js/xpconnect/src/xpcprivate.h | 1 + modules/libpref/init/all.js | 3 + netwerk/protocol/http/HttpBaseChannel.cpp | 50 +- netwerk/protocol/http/HttpBaseChannel.h | 11 + netwerk/protocol/http/HttpChannelChild.cpp | 41 +- netwerk/protocol/http/HttpChannelChild.h | 6 +- netwerk/protocol/http/HttpChannelParent.cpp | 30 +- netwerk/protocol/http/HttpChannelParent.h | 2 + netwerk/protocol/http/PHttpChannel.ipdl | 3 +- netwerk/protocol/http/moz.build | 2 + .../protocol/http}/nsCORSListenerProxy.cpp | 23 +- .../protocol/http}/nsCORSListenerProxy.h | 5 +- netwerk/protocol/http/nsHttpChannel.cpp | 8 +- netwerk/protocol/http/nsHttpChannel.h | 1 + .../protocol/http/nsIHttpChannelInternal.idl | 24 +- .../web-platform/mozilla/meta/MANIFEST.json | 6 + .../fetch-event-redirect.https.html | 997 ++++++++++++++++++ .../fetch-event-redirect-iframe.html | 13 + .../resources/fetch-rewrite-worker.js | 32 +- .../resources/get-host-info.sub.js | 2 + .../service-worker/resources/success.py | 8 + .../server/actors/performance-entries.js | 83 ++ toolkit/devtools/server/main.js | 5 + toolkit/devtools/server/moz.build | 1 + xpcom/base/ErrorList.h | 2 + 105 files changed, 3705 insertions(+), 384 deletions(-) create mode 100644 dom/cache/test/xpcshell/head.js create mode 100644 dom/cache/test/xpcshell/make_profile.js create mode 100644 dom/cache/test/xpcshell/schema_15_profile.zip create mode 100644 dom/cache/test/xpcshell/test_migration.js create mode 100644 dom/cache/test/xpcshell/xpcshell.ini create mode 100644 dom/tests/mochitest/general/frameStorageAllowed.html create mode 100644 dom/tests/mochitest/general/frameStorageChrome.html create mode 100644 dom/tests/mochitest/general/frameStorageNullprincipal.sjs create mode 100644 dom/tests/mochitest/general/frameStoragePrevented.html create mode 100644 dom/tests/mochitest/general/storagePermissionsUtils.js create mode 100644 dom/tests/mochitest/general/test_storagePermissionsAccept.html create mode 100644 dom/tests/mochitest/general/test_storagePermissionsLimitForeign.html create mode 100644 dom/tests/mochitest/general/test_storagePermissionsReject.html create mode 100644 dom/tests/mochitest/general/test_storagePermissionsRejectForeign.html create mode 100644 dom/tests/mochitest/general/workerStorageAllowed.js create mode 100644 dom/tests/mochitest/general/workerStoragePrevented.js create mode 100644 dom/webidl/PerformanceEntryEvent.webidl delete mode 100644 dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^ delete mode 100644 dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^ delete mode 100644 intl/unicharutil/util/internal/Makefile.in rename {dom/security => netwerk/protocol/http}/nsCORSListenerProxy.cpp (98%) rename {dom/security => netwerk/protocol/http}/nsCORSListenerProxy.h (95%) create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event-redirect.https.html create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-redirect-iframe.html create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/resources/success.py create mode 100644 toolkit/devtools/server/actors/performance-entries.js diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index f099e65e66..77df17dc59 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -1090,3 +1090,6 @@ pref("layers.compositor-lru-size", 10); // The app origin of bluetooth app, which is responsible for listening pairing // requests. pref("dom.bluetooth.app-origin", "app://bluetooth.gaiamobile.org"); + +// Enable notification of performance timing +pref("dom.performance.enable_notify_performance_timing", true); diff --git a/b2g/chrome/content/devtools/hud.js b/b2g/chrome/content/devtools/hud.js index 10434e44b5..47867c28c6 100644 --- a/b2g/chrome/content/devtools/hud.js +++ b/b2g/chrome/content/devtools/hud.js @@ -25,6 +25,10 @@ XPCOMUtils.defineLazyGetter(this, 'EventLoopLagFront', function() { return devtools.require('devtools/server/actors/eventlooplag').EventLoopLagFront; }); +XPCOMUtils.defineLazyGetter(this, 'PerformanceEntriesFront', function() { + return devtools.require('devtools/server/actors/performance-entries').PerformanceEntriesFront; +}); + XPCOMUtils.defineLazyGetter(this, 'MemoryFront', function() { return devtools.require('devtools/server/actors/memory').MemoryFront; }); @@ -477,6 +481,76 @@ let eventLoopLagWatcher = { }; developerHUD.registerWatcher(eventLoopLagWatcher); +/* + * The performanceEntriesWatcher determines the delta between the epoch + * of an app's launch time and the app's performance entry marks. + * When it receives an "appLaunch" performance entry mark it records the + * name of the app being launched and the epoch of when the launch ocurred. + * When it receives subsequent performance entry events for the app being + * launched, it records the delta of the performance entry opoch compared + * to the app-launch epoch and emits an "app-start-time-" + * event containing the delta. + */ +let performanceEntriesWatcher = { + _client: null, + _fronts: new Map(), + _appLaunchName: null, + _appLaunchStartTime: null, + + init(client) { + this._client = client; + }, + + trackTarget(target) { + // The performanceEntries watcher doesn't register a metric because + // currently the metrics generated are not displayed in + // in the front-end. + + let front = new PerformanceEntriesFront(this._client, target.actor); + this._fronts.set(target, front); + + // User timings are always gathered; there is no setting to enable/ + // disable. + front.start(); + + front.on('entry', detail => { + if (detail.type === 'mark') { + let name = detail.name; + let epoch = detail.epoch; + let CHARS_UNTIL_APP_NAME = 7; // '@app://' + + // FIXME There is a potential race condition that can result + // in some performance entries being disregarded. See bug 1189942. + if (name.indexOf('appLaunch') != -1) { + let appStartPos = name.indexOf('@app') + CHARS_UNTIL_APP_NAME; + let length = (name.indexOf('.') - appStartPos); + this._appLaunchName = name.substr(appStartPos, length); + this._appLaunchStartTime = epoch; + } else { + let origin = detail.origin; + origin = origin.substr(0, origin.indexOf('.')); + if (this._appLaunchName === origin) { + let time = epoch - this._appLaunchStartTime; + let eventName = 'app-startup-time-' + name; + + // Events based on performance marks are for telemetry only, they are + // not displayed in the HUD front end. + target._sendTelemetryEvent({name: eventName, value: time}); + } + } + } + }); + }, + + untrackTarget(target) { + let fronts = this._fronts; + if (fronts.has(target)) { + fronts.get(target).destroy(); + fronts.delete(target); + } + } +}; +developerHUD.registerWatcher(performanceEntriesWatcher); /** * The Memory Watcher uses devtools actors to track memory usage. diff --git a/configure.in b/configure.in index cbd90e3980..fbe4bdb9ff 100644 --- a/configure.in +++ b/configure.in @@ -299,6 +299,7 @@ if test -n "$gonkdir" ; then MOZ_B2G_BT_DAEMON=1 fi MOZ_NFC=1 + MOZ_RTSP=1 ;; *) AC_MSG_ERROR([Unsupported platform version: $ANDROID_VERSION]) diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index a2e24483a1..57541a8367 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -5085,6 +5085,7 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, case NS_ERROR_INTERCEPTED_ERROR_RESPONSE: case NS_ERROR_INTERCEPTED_USED_RESPONSE: case NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION: + case NS_ERROR_BAD_OPAQUE_REDIRECT_INTERCEPTION: // ServiceWorker intercepted request, but something went wrong. nsContentUtils::MaybeReportInterceptionErrorToConsole(GetDocument(), aError); @@ -10773,6 +10774,8 @@ nsDocShell::DoURILoad(nsIURI* aURI, } else { httpChannelInternal->SetDocumentURI(aReferrerURI); } + httpChannelInternal->SetRedirectMode( + nsIHttpChannelInternal::REDIRECT_MODE_MANUAL); } nsCOMPtr props(do_QueryInterface(channel)); diff --git a/dom/base/EventSource.cpp b/dom/base/EventSource.cpp index 1f1e1341e6..b4f71dafce 100644 --- a/dom/base/EventSource.cpp +++ b/dom/base/EventSource.cpp @@ -35,7 +35,6 @@ #include "nsContentUtils.h" #include "mozilla/Preferences.h" #include "xpcpublic.h" -#include "nsCORSListenerProxy.h" #include "nsWrapperCacheInlines.h" #include "mozilla/Attributes.h" #include "nsError.h" diff --git a/dom/base/ImportManager.cpp b/dom/base/ImportManager.cpp index e1ac7c7a93..c56fbd039b 100644 --- a/dom/base/ImportManager.cpp +++ b/dom/base/ImportManager.cpp @@ -10,7 +10,6 @@ #include "HTMLLinkElement.h" #include "nsContentPolicyUtils.h" #include "nsContentUtils.h" -#include "nsCORSListenerProxy.h" #include "nsIChannel.h" #include "nsIContentPolicy.h" #include "nsIContentSecurityPolicy.h" diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 7ddfde0aac..6e3eb93d0b 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -193,6 +193,10 @@ #include "xpcprivate.h" // nsXPConnect #include "HTMLSplitOnSpacesTokenizer.h" #include "nsContentTypeParser.h" +#include "nsICookiePermission.h" +#include "mozIThirdPartyUtil.h" +#include "nsICookieService.h" +#include "mozilla/EnumSet.h" #include "nsIBidiKeyboard.h" @@ -263,9 +267,13 @@ bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false; bool nsContentUtils::sEncodeDecodeURLHash = false; bool nsContentUtils::sGettersDecodeURLHash = false; bool nsContentUtils::sPrivacyResistFingerprinting = false; +bool nsContentUtils::sSendPerformanceTimingNotifications = false; uint32_t nsContentUtils::sHandlingInputTimeout = 1000; +uint32_t nsContentUtils::sCookiesLifetimePolicy = nsICookieService::ACCEPT_NORMALLY; +uint32_t nsContentUtils::sCookiesBehavior = nsICookieService::BEHAVIOR_ACCEPT; + nsHtml5StringParser* nsContentUtils::sHTMLFragmentParser = nullptr; nsIParser* nsContentUtils::sXMLFragmentParser = nullptr; nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nullptr; @@ -560,6 +568,17 @@ nsContentUtils::Init() "dom.event.handling-user-input-time-limit", 1000); + Preferences::AddBoolVarCache(&sSendPerformanceTimingNotifications, + "dom.performance.enable_notify_performance_timing", false); + + Preferences::AddUintVarCache(&sCookiesLifetimePolicy, + "network.cookie.lifetimePolicy", + nsICookieService::ACCEPT_NORMALLY); + + Preferences::AddUintVarCache(&sCookiesBehavior, + "network.cookie.cookieBehavior", + nsICookieService::BEHAVIOR_ACCEPT); + #if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP)) Preferences::AddBoolVarCache(&sDOMWindowDumpEnabled, "browser.dom.window.dump.enabled"); @@ -3453,6 +3472,8 @@ nsContentUtils::MaybeReportInterceptionErrorToConsole(nsIDocument* aDocument, messageName = "InterceptedUsedResponse"; } else if (aError == NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION) { messageName = "ClientRequestOpaqueInterception"; + } else if (aError == NS_ERROR_BAD_OPAQUE_REDIRECT_INTERCEPTION) { + messageName = "BadOpaqueRedirectInterception"; } if (messageName) { @@ -8075,3 +8096,133 @@ nsContentUtils::PushEnabled(JSContext* aCx, JSObject* aObj) return workerPrivate->PushEnabled(); } + +// static, public +nsContentUtils::StorageAccess +nsContentUtils::StorageAllowedForWindow(nsPIDOMWindow* aWindow) +{ + MOZ_ASSERT(aWindow->IsInnerWindow()); + + nsIDocument* document = aWindow->GetExtantDoc(); + if (document) { + nsCOMPtr principal = document->NodePrincipal(); + return InternalStorageAllowedForPrincipal(principal, aWindow); + } + + return StorageAccess::eDeny; +} + +// static, public +nsContentUtils::StorageAccess +nsContentUtils::StorageAllowedForPrincipal(nsIPrincipal* aPrincipal) +{ + return InternalStorageAllowedForPrincipal(aPrincipal, nullptr); +} + +// static, private +nsContentUtils::StorageAccess +nsContentUtils::InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal, + nsPIDOMWindow* aWindow) +{ + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(!aWindow || aWindow->IsInnerWindow()); + + StorageAccess access = StorageAccess::eAllow; + + // We don't allow storage on the null principal, in general. Even if the + // calling context is chrome. + bool isNullPrincipal; + if (NS_WARN_IF(NS_FAILED(aPrincipal->GetIsNullPrincipal(&isNullPrincipal))) || + isNullPrincipal) { + return StorageAccess::eDeny; + } + + if (aWindow) { + // If the document is sandboxed, then it is not permitted to use storage + nsIDocument* document = aWindow->GetExtantDoc(); + if (document->GetSandboxFlags() & SANDBOXED_ORIGIN) { + return StorageAccess::eDeny; + } + + // Check if we are in private browsing, and record that fact + if (IsInPrivateBrowsing(document)) { + access = StorageAccess::ePrivateBrowsing; + } + } + + // Check if we should only allow storage for the session, and record that fact + if (sCookiesLifetimePolicy == nsICookieService::ACCEPT_SESSION) { + // Storage could be StorageAccess::ePrivateBrowsing or StorageAccess::eAllow + // so perform a std::min comparison to make sure we preserve ePrivateBrowsing + // if it has been set. + access = std::min(StorageAccess::eSessionScoped, access); + } + + // If the caller is chrome privileged, then it is allowed to access any + // storage it likes, no matter whether the storage for that window/principal + // would normally be permitted. + if (IsSystemPrincipal(SubjectPrincipal())) { + return access; + } + + if (!SubjectPrincipal()->Subsumes(aPrincipal)) { + NS_WARNING("A principal is attempting to access storage for a principal " + "which it doesn't subsume!"); + return StorageAccess::eDeny; + } + + // About URIs are allowed to access storage, even if they don't have chrome + // privileges. If this is not desired, than the consumer will have to + // implement their own restriction functionality. + nsCOMPtr uri; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aPrincipal->GetURI(getter_AddRefs(uri)))); + bool isAbout = false; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(uri->SchemeIs("about", &isAbout))); + if (isAbout) { + return access; + } + + nsCOMPtr permissionManager = + services::GetPermissionManager(); + if (!permissionManager) { + return StorageAccess::eDeny; + } + + // check the permission manager for any allow or deny permissions + // for cookies for the window. + uint32_t perm; + permissionManager->TestPermissionFromPrincipal(aPrincipal, "cookie", &perm); + if (perm == nsIPermissionManager::DENY_ACTION) { + return StorageAccess::eDeny; + } else if (perm == nsICookiePermission::ACCESS_SESSION) { + return std::min(access, StorageAccess::eSessionScoped); + } else if (perm == nsIPermissionManager::ALLOW_ACTION) { + return access; + } + + // We don't want to prompt for every attempt to access permissions. + if (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT) { + return StorageAccess::eDeny; + } + + // In the absense of a window, we assume that we are first-party. + if (aWindow && (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN || + sCookiesBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN)) { + nsCOMPtr thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID); + MOZ_ASSERT(thirdPartyUtil); + + bool thirdPartyWindow = false; + if (NS_SUCCEEDED(thirdPartyUtil->IsThirdPartyWindow( + aWindow, nullptr, &thirdPartyWindow)) && thirdPartyWindow) { + // XXX For non-cookie forms of storage, we handle BEHAVIOR_LIMIT_FOREIGN by + // simply rejecting the request to use the storage. In the future, if we + // change the meaning of BEHAVIOR_LIMIT_FOREIGN to be one which makes sense + // for non-cookie storage types, this may change. + + return StorageAccess::eDeny; + } + } + + return access; +} diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index dce7dd05b2..419697580c 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -1997,6 +1997,14 @@ public: return sIsResourceTimingEnabled; } + /* + * Returns true if notification should be sent for peformance timing events. + */ + static bool SendPerformanceTimingNotifications() + { + return sSendPerformanceTimingNotifications; + } + /* * Returns true if URL setters should percent encode the Hash/Ref segment * and getters should return the percent decoded value of the segment @@ -2487,6 +2495,38 @@ public: static bool PushEnabled(JSContext* aCx, JSObject* aObj); + // The order of these entries matters, as we use std::min for total ordering + // of permissions. Private Browsing is considered to be more limiting + // then session scoping + enum class StorageAccess { + // Don't allow access to the storage + eDeny = 0, + // Allow access to the storage, but only if it is secure to do so in a + // private browsing context. + ePrivateBrowsing = 1, + // Allow access to the storage, but only persist it for the current session + eSessionScoped = 2, + // Allow access to the storage + eAllow = 3, + }; + + /* + * Checks if storage for the given window is permitted by a combination of + * the user's preferences, and whether the window is a third-party iframe. + * + * This logic is intended to be shared between the different forms of + * persistent storage which are available to web pages. Cookies don't use + * this logic, and security logic related to them must be updated separately. + */ + static StorageAccess StorageAllowedForWindow(nsPIDOMWindow* aWindow); + + /* + * Checks if storage for the given principal is permitted by the user's + * preferences. The caller is assumed to not be a third-party iframe. + * (if that is possible, the caller should use StorageAllowedForWindow) + */ + static StorageAccess StorageAllowedForPrincipal(nsIPrincipal* aPrincipal); + private: static bool InitializeEventTable(); @@ -2527,6 +2567,18 @@ private: CallOnRemoteChildFunction aCallback, void* aArg); + /* + * Checks if storage for a given principal is permitted by the user's + * preferences. If aWindow is non-null, its principal must be passed as + * aPrincipal, and the third-party iframe and sandboxing status of the window + * are also checked. + * + * Used in the implementation of StorageAllowedForWindow and + * StorageAllowedForPrincipal. + */ + static StorageAccess InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal, + nsPIDOMWindow* aWindow); + static nsIXPConnect *sXPConnect; static nsIScriptSecurityManager *sSecurityManager; @@ -2591,6 +2643,9 @@ private: static bool sEncodeDecodeURLHash; static bool sGettersDecodeURLHash; static bool sPrivacyResistFingerprinting; + static bool sSendPerformanceTimingNotifications; + static uint32_t sCookiesLifetimePolicy; + static uint32_t sCookiesBehavior; static nsHtml5StringParser* sHTMLFragmentParser; static nsIParser* sXMLFragmentParser; diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index a63400293b..2c14d4929b 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -10826,9 +10826,16 @@ nsGlobalWindow::GetCaches(ErrorResult& aRv) bool forceTrustedOrigin = GetOuterWindowInternal()->GetServiceWorkersTestingEnabled(); + nsContentUtils::StorageAccess access = + nsContentUtils::StorageAllowedForWindow(this); + + // We don't block the cache API when being told to only allow storage for the + // current session. + bool storageBlocked = access <= nsContentUtils::StorageAccess::ePrivateBrowsing; + mCacheStorage = CacheStorage::CreateOnMainThread(cache::DEFAULT_NAMESPACE, this, GetPrincipal(), - IsPrivateBrowsing(), + storageBlocked, forceTrustedOrigin, aRv); } diff --git a/dom/base/nsPerformance.cpp b/dom/base/nsPerformance.cpp index a5fc9a6403..06e39cc08f 100644 --- a/dom/base/nsPerformance.cpp +++ b/dom/base/nsPerformance.cpp @@ -21,6 +21,7 @@ #include "PerformanceResourceTiming.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/PerformanceBinding.h" +#include "mozilla/dom/PerformanceEntryEvent.h" #include "mozilla/dom/PerformanceTimingBinding.h" #include "mozilla/dom/PerformanceNavigationBinding.h" #include "mozilla/Preferences.h" @@ -737,14 +738,24 @@ nsPerformance::InsertUserEntry(PerformanceEntry* aEntry) { MOZ_ASSERT(NS_IsMainThread()); - if (nsContentUtils::IsUserTimingLoggingEnabled()) { - nsAutoCString uri; + nsAutoCString uri; + uint64_t markCreationEpoch = 0; + if (nsContentUtils::IsUserTimingLoggingEnabled() || + nsContentUtils::SendPerformanceTimingNotifications()) { nsresult rv = GetOwner()->GetDocumentURI()->GetHost(uri); if(NS_FAILED(rv)) { // If we have no URI, just put in "none". uri.AssignLiteral("none"); } - PerformanceBase::LogEntry(aEntry, uri); + markCreationEpoch = static_cast(PR_Now() / PR_USEC_PER_MSEC); + + if (nsContentUtils::IsUserTimingLoggingEnabled()) { + PerformanceBase::LogEntry(aEntry, uri); + } + } + + if (nsContentUtils::SendPerformanceTimingNotifications()) { + TimingNotification(aEntry, uri, markCreationEpoch); } PerformanceBase::InsertUserEntry(aEntry); @@ -985,6 +996,29 @@ PerformanceBase::LogEntry(PerformanceEntry* aEntry, const nsACString& aOwner) co static_cast(PR_Now() / PR_USEC_PER_MSEC)); } +void +PerformanceBase::TimingNotification(PerformanceEntry* aEntry, const nsACString& aOwner, uint64_t aEpoch) +{ + PerformanceEntryEventInit init; + init.mBubbles = false; + init.mCancelable = false; + init.mName = aEntry->GetName(); + init.mEntryType = aEntry->GetEntryType(); + init.mStartTime = aEntry->StartTime(); + init.mDuration = aEntry->Duration(); + init.mEpoch = aEpoch; + init.mOrigin = NS_ConvertUTF8toUTF16(aOwner.BeginReading()); + + nsRefPtr perfEntryEvent = + PerformanceEntryEvent::Constructor(this, NS_LITERAL_STRING("performanceentry"), init); + + nsCOMPtr et = do_QueryInterface(GetOwner()); + if (et) { + bool dummy = false; + et->DispatchEvent(perfEntryEvent, &dummy); + } +} + void PerformanceBase::InsertUserEntry(PerformanceEntry* aEntry) { diff --git a/dom/base/nsPerformance.h b/dom/base/nsPerformance.h index d4e7fb6e6b..0e3ee2fbcf 100644 --- a/dom/base/nsPerformance.h +++ b/dom/base/nsPerformance.h @@ -351,6 +351,7 @@ protected: } void LogEntry(PerformanceEntry* aEntry, const nsACString& aOwner) const; + void TimingNotification(PerformanceEntry* aEntry, const nsACString& aOwner, uint64_t epoch); private: nsTArray> mUserEntries; diff --git a/dom/cache/CacheStorage.cpp b/dom/cache/CacheStorage.cpp index c484d4d32f..607160e4b2 100644 --- a/dom/cache/CacheStorage.cpp +++ b/dom/cache/CacheStorage.cpp @@ -145,15 +145,15 @@ IsTrusted(const PrincipalInfo& aPrincipalInfo, bool aTestingPrefEnabled) // static already_AddRefed CacheStorage::CreateOnMainThread(Namespace aNamespace, nsIGlobalObject* aGlobal, - nsIPrincipal* aPrincipal, bool aPrivateBrowsing, + nsIPrincipal* aPrincipal, bool aStorageDisabled, bool aForceTrustedOrigin, ErrorResult& aRv) { MOZ_ASSERT(aGlobal); MOZ_ASSERT(aPrincipal); MOZ_ASSERT(NS_IsMainThread()); - if (aPrivateBrowsing) { - NS_WARNING("CacheStorage not supported during private browsing."); + if (aStorageDisabled) { + NS_WARNING("CacheStorage has been disabled."); nsRefPtr ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR); return ref.forget(); } @@ -189,6 +189,12 @@ CacheStorage::CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal, MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); + if (!aWorkerPrivate->IsStorageAllowed()) { + NS_WARNING("CacheStorage is not allowed."); + nsRefPtr ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR); + return ref.forget(); + } + if (aWorkerPrivate->IsInPrivateBrowsing()) { NS_WARNING("CacheStorage not supported during private browsing."); nsRefPtr ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR); @@ -219,6 +225,41 @@ CacheStorage::CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal, return ref.forget(); } +// static +bool +CacheStorage::DefineCaches(JSContext* aCx, JS::Handle aGlobal) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL, + "Passed object is not a global object!"); + + if (NS_WARN_IF(!CacheStorageBinding::GetConstructorObject(aCx, aGlobal) || + !CacheBinding::GetConstructorObject(aCx, aGlobal))) { + return false; + } + + nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aGlobal); + MOZ_ASSERT(principal); + + ErrorResult rv; + nsRefPtr storage = + CreateOnMainThread(DEFAULT_NAMESPACE, xpc::NativeGlobal(aGlobal), principal, + false, /* private browsing */ + true, /* force trusted */ + rv); + if (NS_WARN_IF(rv.Failed())) { + return ThrowMethodFailed(aCx, rv); + } + + JS::Rooted caches(aCx); + js::AssertSameCompartment(aCx, aGlobal); + if (NS_WARN_IF(!ToJSValue(aCx, storage, &caches))) { + return false; + } + + return JS_DefineProperty(aCx, aGlobal, "caches", caches, JSPROP_ENUMERATE); +} + CacheStorage::CacheStorage(Namespace aNamespace, nsIGlobalObject* aGlobal, const PrincipalInfo& aPrincipalInfo, Feature* aFeature) : mNamespace(aNamespace) diff --git a/dom/cache/CacheStorage.h b/dom/cache/CacheStorage.h index c5191ce674..19061ab341 100644 --- a/dom/cache/CacheStorage.h +++ b/dom/cache/CacheStorage.h @@ -49,13 +49,16 @@ class CacheStorage final : public nsIIPCBackgroundChildCreateCallback public: static already_AddRefed CreateOnMainThread(Namespace aNamespace, nsIGlobalObject* aGlobal, - nsIPrincipal* aPrincipal, bool aPrivateBrowsing, + nsIPrincipal* aPrincipal, bool aStorageDisabled, bool aForceTrustedOrigin, ErrorResult& aRv); static already_AddRefed CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal, workers::WorkerPrivate* aWorkerPrivate, ErrorResult& aRv); + static bool + DefineCaches(JSContext* aCx, JS::Handle aGlobal); + // webidl interface methods already_AddRefed Match(const RequestOrUSVString& aRequest, const CacheQueryOptions& aOptions, diff --git a/dom/cache/CacheTypes.ipdlh b/dom/cache/CacheTypes.ipdlh index bbff425d08..1f2f576f1a 100644 --- a/dom/cache/CacheTypes.ipdlh +++ b/dom/cache/CacheTypes.ipdlh @@ -13,6 +13,7 @@ using HeadersGuardEnum from "mozilla/dom/cache/IPCUtils.h"; using RequestCredentials from "mozilla/dom/cache/IPCUtils.h"; using RequestMode from "mozilla/dom/cache/IPCUtils.h"; using RequestCache from "mozilla/dom/cache/IPCUtils.h"; +using RequestRedirect from "mozilla/dom/cache/IPCUtils.h"; using ResponseType from "mozilla/dom/cache/IPCUtils.h"; using mozilla::void_t from "ipc/IPCMessageUtils.h"; using struct nsID from "nsID.h"; @@ -64,6 +65,7 @@ struct CacheRequest CacheReadStreamOrVoid body; uint32_t contentPolicyType; RequestCache requestCache; + RequestRedirect requestRedirect; }; union CacheRequestOrVoid diff --git a/dom/cache/DBAction.cpp b/dom/cache/DBAction.cpp index 3120aea503..71653b969d 100644 --- a/dom/cache/DBAction.cpp +++ b/dom/cache/DBAction.cpp @@ -169,7 +169,7 @@ DBAction::OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, int32_t schemaVersion = 0; rv = conn->GetSchemaVersion(&schemaVersion); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - if (schemaVersion > 0 && schemaVersion < db::kMaxWipeSchemaVersion) { + if (schemaVersion > 0 && schemaVersion < db::kFirstShippedSchemaVersion) { conn = nullptr; rv = WipeDatabase(dbFile, aDBDir); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } diff --git a/dom/cache/DBSchema.cpp b/dom/cache/DBSchema.cpp index b2138f5cef..52a9333342 100644 --- a/dom/cache/DBSchema.cpp +++ b/dom/cache/DBSchema.cpp @@ -14,6 +14,7 @@ #include "mozilla/dom/cache/TypeUtils.h" #include "mozIStorageConnection.h" #include "mozIStorageStatement.h" +#include "mozStorageHelper.h" #include "nsCOMPtr.h" #include "nsTArray.h" #include "nsCRT.h" @@ -31,11 +32,135 @@ namespace dom { namespace cache { namespace db { -const int32_t kMaxWipeSchemaVersion = 15; +const int32_t kFirstShippedSchemaVersion = 15; namespace { -const int32_t kLatestSchemaVersion = 15; +// Update this whenever the DB schema is changed. +const int32_t kLatestSchemaVersion = 16; + +// --------- +// The following constants define the SQL schema. These are defined in the +// same order the SQL should be executed in CreateOrMigrateSchema(). They are +// broken out as constants for convenient use in validation and migration. +// --------- + +// The caches table is the single source of truth about what Cache +// objects exist for the origin. The contents of the Cache are stored +// in the entries table that references back to caches. +// +// The caches table is also referenced from storage. Rows in storage +// represent named Cache objects. There are cases, however, where +// a Cache can still exist, but not be in a named Storage. For example, +// when content is still using the Cache after CacheStorage::Delete() +// has been run. +// +// For now, the caches table mainly exists for data integrity with +// foreign keys, but could be expanded to contain additional cache object +// information. +// +// AUTOINCREMENT is necessary to prevent CacheId values from being reused. +const char* const kTableCaches = + "CREATE TABLE caches (" + "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT " + ")"; + +// Security blobs are quite large and duplicated for every Response from +// the same https origin. This table is used to de-duplicate this data. +const char* const kTableSecurityInfo = + "CREATE TABLE security_info (" + "id INTEGER NOT NULL PRIMARY KEY, " + "hash BLOB NOT NULL, " // first 8-bytes of the sha1 hash of data column + "data BLOB NOT NULL, " // full security info data, usually a few KB + "refcount INTEGER NOT NULL" + ")"; + +// Index the smaller hash value instead of the large security data blob. +const char* const kIndexSecurityInfoHash = + "CREATE INDEX security_info_hash_index ON security_info (hash)"; + +const char* const kTableEntries = + "CREATE TABLE entries (" + "id INTEGER NOT NULL PRIMARY KEY, " + "request_method TEXT NOT NULL, " + "request_url_no_query TEXT NOT NULL, " + "request_url_no_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash + "request_url_query TEXT NOT NULL, " + "request_url_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash + "request_referrer TEXT NOT NULL, " + "request_headers_guard INTEGER NOT NULL, " + "request_mode INTEGER NOT NULL, " + "request_credentials INTEGER NOT NULL, " + "request_contentpolicytype INTEGER NOT NULL, " + "request_cache INTEGER NOT NULL, " + "request_body_id TEXT NULL, " + "response_type INTEGER NOT NULL, " + "response_url TEXT NOT NULL, " + "response_status INTEGER NOT NULL, " + "response_status_text TEXT NOT NULL, " + "response_headers_guard INTEGER NOT NULL, " + "response_body_id TEXT NULL, " + "response_security_info_id INTEGER NULL REFERENCES security_info(id), " + "response_principal_info TEXT NOT NULL, " + "response_redirected INTEGER NOT NULL, " + // Note that response_redirected_url is either going to be empty, or + // it's going to be a URL different than response_url. + "response_redirected_url TEXT NOT NULL, " + "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, " + + // New columns must be added at the end of table to migrate and + // validate properly. + "request_redirect INTEGER NOT NULL" + ")"; + +// Create an index to support the QueryCache() matching algorithm. This +// needs to quickly find entries in a given Cache that match the request +// URL. The url query is separated in order to support the ignoreSearch +// option. Finally, we index hashes of the URL values instead of the +// actual strings to avoid excessive disk bloat. The index will duplicate +// the contents of the columsn in the index. The hash index will prune +// the vast majority of values from the query result so that normal +// scanning only has to be done on a few values to find an exact URL match. +const char* const kIndexEntriesRequest = + "CREATE INDEX entries_request_match_index " + "ON entries (cache_id, request_url_no_query_hash, " + "request_url_query_hash)"; + +const char* const kTableRequestHeaders = + "CREATE TABLE request_headers (" + "name TEXT NOT NULL, " + "value TEXT NOT NULL, " + "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE" + ")"; + +const char* const kTableResponseHeaders = + "CREATE TABLE response_headers (" + "name TEXT NOT NULL, " + "value TEXT NOT NULL, " + "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE" + ")"; + +// We need an index on response_headers, but not on request_headers, +// because we quickly need to determine if a VARY header is present. +const char* const kIndexResponseHeadersName = + "CREATE INDEX response_headers_name_index " + "ON response_headers (name)"; + +// NOTE: key allows NULL below since that is how "" is represented +// in a BLOB column. We use BLOB to avoid encoding issues +// with storing DOMStrings. +const char* const kTableStorage = + "CREATE TABLE storage (" + "namespace INTEGER NOT NULL, " + "key BLOB NULL, " + "cache_id INTEGER NOT NULL REFERENCES caches(id), " + "PRIMARY KEY(namespace, key) " + ")"; + +// --------- +// End schema definition +// --------- + const int32_t kMaxEntriesPerStatement = 255; const uint32_t kPageSize = 4 * 1024; @@ -87,12 +212,18 @@ static_assert(int(RequestCache::Default) == 0 && int(RequestCache::Only_if_cached) == 5 && int(RequestCache::EndGuard_) == 6, "RequestCache values are as expected"); +static_assert(int(RequestRedirect::Follow) == 0 && + int(RequestRedirect::Error) == 1 && + int(RequestRedirect::Manual) == 2 && + int(RequestRedirect::EndGuard_) == 3, + "RequestRedirect values are as expected"); static_assert(int(ResponseType::Basic) == 0 && int(ResponseType::Cors) == 1 && int(ResponseType::Default) == 2 && int(ResponseType::Error) == 3 && int(ResponseType::Opaque) == 4 && - int(ResponseType::EndGuard_) == 5, + int(ResponseType::Opaqueredirect) == 5 && + int(ResponseType::EndGuard_) == 6, "ResponseType values are as expected"); // If the static_asserts below fails, it means that you have changed the @@ -207,10 +338,12 @@ static nsresult CreateAndBindKeyStatement(mozIStorageConnection* aConn, mozIStorageStatement** aStateOut); static nsresult HashCString(nsICryptoHash* aCrypto, const nsACString& aIn, nsACString& aOut); -} // anonymous namespace +nsresult Validate(mozIStorageConnection* aConn); +nsresult Migrate(mozIStorageConnection* aConn); +} // namespace nsresult -CreateSchema(mozIStorageConnection* aConn) +CreateOrMigrateSchema(mozIStorageConnection* aConn) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -220,135 +353,56 @@ CreateSchema(mozIStorageConnection* aConn) if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (schemaVersion == kLatestSchemaVersion) { - // We already have the correct schema, so just get started. + // We already have the correct schema version. Validate it matches + // our expected schema and then proceed. + rv = Validate(aConn); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + return rv; } - if (!schemaVersion) { - // The caches table is the single source of truth about what Cache - // objects exist for the origin. The contents of the Cache are stored - // in the entries table that references back to caches. - // - // The caches table is also referenced from storage. Rows in storage - // represent named Cache objects. There are cases, however, where - // a Cache can still exist, but not be in a named Storage. For example, - // when content is still using the Cache after CacheStorage::Delete() - // has been run. - // - // For now, the caches table mainly exists for data integrity with - // foreign keys, but could be expanded to contain additional cache object - // information. - // - // AUTOINCREMENT is necessary to prevent CacheId values from being reused. - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE caches (" - "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT " - ");" - )); + mozStorageTransaction trans(aConn, false, + mozIStorageConnection::TRANSACTION_IMMEDIATE); + bool needVacuum = false; + + if (schemaVersion) { + // A schema exists, but its not the current version. Attempt to + // migrate it to our new schema. + rv = Migrate(aConn); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - // Security blobs are quite large and duplicated for every Response from - // the same https origin. This table is used to de-duplicate this data. - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE security_info (" - "id INTEGER NOT NULL PRIMARY KEY, " - "hash BLOB NOT NULL, " // first 8-bytes of the sha1 hash of data column - "data BLOB NOT NULL, " // full security info data, usually a few KB - "refcount INTEGER NOT NULL" - ");" - )); + // Migrations happen infrequently and reflect a chance in DB structure. + // This is a good time to rebuild the database. It also helps catch + // if a new migration is incorrect by fast failing on the corruption. + needVacuum = true; + + } else { + // There is no schema installed. Create the database from scratch. + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableCaches)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - // Index the smaller hash value instead of the large security data blob. - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE INDEX security_info_hash_index ON security_info (hash);" - )); + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableSecurityInfo)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE entries (" - "id INTEGER NOT NULL PRIMARY KEY, " - "request_method TEXT NOT NULL, " - "request_url_no_query TEXT NOT NULL, " - "request_url_no_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash - "request_url_query TEXT NOT NULL, " - "request_url_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash - "request_referrer TEXT NOT NULL, " - "request_headers_guard INTEGER NOT NULL, " - "request_mode INTEGER NOT NULL, " - "request_credentials INTEGER NOT NULL, " - "request_contentpolicytype INTEGER NOT NULL, " - "request_cache INTEGER NOT NULL, " - "request_body_id TEXT NULL, " - "response_type INTEGER NOT NULL, " - "response_url TEXT NOT NULL, " - "response_status INTEGER NOT NULL, " - "response_status_text TEXT NOT NULL, " - "response_headers_guard INTEGER NOT NULL, " - "response_body_id TEXT NULL, " - "response_security_info_id INTEGER NULL REFERENCES security_info(id), " - "response_principal_info TEXT NOT NULL, " - "response_redirected INTEGER NOT NULL, " - // Note that response_redirected_url is either going to be empty, or - // it's going to be a URL different than response_url. - "response_redirected_url TEXT NOT NULL, " - "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE" - ");" - )); + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexSecurityInfoHash)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - // Create an index to support the QueryCache() matching algorithm. This - // needs to quickly find entries in a given Cache that match the request - // URL. The url query is separated in order to support the ignoreSearch - // option. Finally, we index hashes of the URL values instead of the - // actual strings to avoid excessive disk bloat. The index will duplicate - // the contents of the columsn in the index. The hash index will prune - // the vast majority of values from the query result so that normal - // scanning only has to be done on a few values to find an exact URL match. - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE INDEX entries_request_match_index " - "ON entries (cache_id, request_url_no_query_hash, " - "request_url_query_hash);" - )); + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableEntries)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE request_headers (" - "name TEXT NOT NULL, " - "value TEXT NOT NULL, " - "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE" - ");" - )); + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexEntriesRequest)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE response_headers (" - "name TEXT NOT NULL, " - "value TEXT NOT NULL, " - "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE" - ");" - )); + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableRequestHeaders)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - // We need an index on response_headers, but not on request_headers, - // because we quickly need to determine if a VARY header is present. - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE INDEX response_headers_name_index " - "ON response_headers (name);" - )); + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableResponseHeaders)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - // NOTE: key allows NULL below since that is how "" is represented - // in a BLOB column. We use BLOB to avoid encoding issues - // with storing DOMStrings. - rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE storage (" - "namespace INTEGER NOT NULL, " - "key BLOB NULL, " - "cache_id INTEGER NOT NULL REFERENCES caches(id), " - "PRIMARY KEY(namespace, key) " - ");" - )); + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexResponseHeadersName)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableStorage)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConn->SetSchemaVersion(kLatestSchemaVersion); @@ -358,8 +412,16 @@ CreateSchema(mozIStorageConnection* aConn) if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } - if (schemaVersion != kLatestSchemaVersion) { - return NS_ERROR_FAILURE; + rv = Validate(aConn); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = trans.Commit(); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + if (needVacuum) { + // Unfortunately, this must be performed outside of the transaction. + aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM")); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return rv; @@ -1531,6 +1593,7 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, "request_credentials, " "request_contentpolicytype, " "request_cache, " + "request_redirect, " "request_body_id, " "response_type, " "response_url, " @@ -1555,6 +1618,7 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, ":request_credentials, " ":request_contentpolicytype, " ":request_cache, " + ":request_redirect, " ":request_body_id, " ":response_type, " ":response_url, " @@ -1623,6 +1687,9 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, static_cast(aRequest.requestCache())); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + rv = state->BindInt32ByName(NS_LITERAL_CSTRING("request_redirect"), + static_cast(aRequest.requestRedirect())); + rv = BindId(state, NS_LITERAL_CSTRING("request_body_id"), aRequestBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1902,6 +1969,7 @@ ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId, "request_credentials, " "request_contentpolicytype, " "request_cache, " + "request_redirect, " "request_body_id " "FROM entries " "WHERE id=:id;" @@ -1956,13 +2024,19 @@ ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId, aSavedRequestOut->mValue.requestCache() = static_cast(requestCache); + int32_t requestRedirect; + rv = state->GetInt32(9, &requestRedirect); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + aSavedRequestOut->mValue.requestRedirect() = + static_cast(requestRedirect); + bool nullBody = false; - rv = state->GetIsNull(9, &nullBody); + rv = state->GetIsNull(10, &nullBody); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } aSavedRequestOut->mHasBodyId = !nullBody; if (aSavedRequestOut->mHasBodyId) { - rv = ExtractId(state, 9, &aSavedRequestOut->mBodyId); + rv = ExtractId(state, 10, &aSavedRequestOut->mBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } @@ -2186,6 +2260,225 @@ IncrementalVacuum(mozIStorageConnection* aConn) return NS_OK; } +namespace { + +#ifdef DEBUG +struct Expect +{ + // Expect exact SQL + Expect(const char* aName, const char* aType, const char* aSql) + : mName(aName) + , mType(aType) + , mSql(aSql) + , mIgnoreSql(false) + { } + + // Ignore SQL + Expect(const char* aName, const char* aType) + : mName(aName) + , mType(aType) + , mIgnoreSql(true) + { } + + const nsCString mName; + const nsCString mType; + const nsCString mSql; + const bool mIgnoreSql; +}; +#endif + +nsresult +Validate(mozIStorageConnection* aConn) +{ + int32_t schemaVersion; + nsresult rv = aConn->GetSchemaVersion(&schemaVersion); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + if (NS_WARN_IF(schemaVersion != kLatestSchemaVersion)) { + return NS_ERROR_FAILURE; + } + +#ifdef DEBUG + // This is the schema we expect the database at the latest version to + // contain. Update this list if you add a new table or index. + Expect expect[] = { + Expect("caches", "table", kTableCaches), + Expect("sqlite_sequence", "table"), // auto-gen by sqlite + Expect("security_info", "table", kTableSecurityInfo), + Expect("security_info_hash_index", "index", kIndexSecurityInfoHash), + Expect("entries", "table", kTableEntries), + Expect("entries_request_match_index", "index", kIndexEntriesRequest), + Expect("request_headers", "table", kTableRequestHeaders), + Expect("response_headers", "table", kTableResponseHeaders), + Expect("response_headers_name_index", "index", kIndexResponseHeadersName), + Expect("storage", "table", kTableStorage), + Expect("sqlite_autoindex_storage_1", "index"), // auto-gen by sqlite + }; + const uint32_t expectLength = sizeof(expect) / sizeof(Expect); + + // Read the schema from the sqlite_master table and compare. + nsCOMPtr state; + rv = aConn->CreateStatement(NS_LITERAL_CSTRING( + "SELECT name, type, sql FROM sqlite_master;" + ), getter_AddRefs(state)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + bool hasMoreData = false; + while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) { + nsAutoCString name; + rv = state->GetUTF8String(0, name); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsAutoCString type; + rv = state->GetUTF8String(1, type); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsAutoCString sql; + rv = state->GetUTF8String(2, sql); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + bool foundMatch = false; + for (uint32_t i = 0; i < expectLength; ++i) { + if (name == expect[i].mName) { + if (type != expect[i].mType) { + NS_WARNING(nsPrintfCString("Unexpected type for Cache schema entry %s", + name.get()).get()); + return NS_ERROR_FAILURE; + } + + if (!expect[i].mIgnoreSql && sql != expect[i].mSql) { + NS_WARNING(nsPrintfCString("Unexpected SQL for Cache schema entry %s", + name.get()).get()); + return NS_ERROR_FAILURE; + } + + foundMatch = true; + break; + } + } + + if (NS_WARN_IF(!foundMatch)) { + NS_WARNING(nsPrintfCString("Unexpected schema entry %s in Cache database", + name.get()).get()); + return NS_ERROR_FAILURE; + } + } +#endif + + return rv; +} + +// ----- +// Schema migration code +// ----- + +typedef nsresult (*MigrationFunc)(mozIStorageConnection*); +struct Migration +{ + Migration(int32_t aFromVersion, MigrationFunc aFunc) + : mFromVersion(aFromVersion) + , mFunc(aFunc) + { } + int32_t mFromVersion; + MigrationFunc mFunc; +}; + +// Declare migration functions here. Each function should upgrade +// the version by a single increment. Don't skip versions. +nsresult MigrateFrom15To16(mozIStorageConnection* aConn); + +// Configure migration functions to run for the given starting version. +Migration sMigrationList[] = { + Migration(15, MigrateFrom15To16), +}; + +uint32_t sMigrationListLength = sizeof(sMigrationList) / sizeof(Migration); + +nsresult +Migrate(mozIStorageConnection* aConn) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aConn); + + int32_t currentVersion = 0; + nsresult rv = aConn->GetSchemaVersion(¤tVersion); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + while (currentVersion < kLatestSchemaVersion) { + // Wiping old databases is handled in DBAction because it requires + // making a whole new mozIStorageConnection. Make sure we don't + // accidentally get here for one of those old databases. + MOZ_ASSERT(currentVersion >= kFirstShippedSchemaVersion); + + for (uint32_t i = 0; i < sMigrationListLength; ++i) { + if (sMigrationList[i].mFromVersion == currentVersion) { + rv = sMigrationList[i].mFunc(aConn); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + break; + } + } + + DebugOnly lastVersion = currentVersion; + rv = aConn->GetSchemaVersion(¤tVersion); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + MOZ_ASSERT(currentVersion > lastVersion); + } + + MOZ_ASSERT(currentVersion == kLatestSchemaVersion); + + return rv; +} + +nsresult MigrateFrom15To16(mozIStorageConnection* aConn) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aConn); + + // Add the request_redirect column with a default value of "follow". Note, + // we only use a default value here because its required by ALTER TABLE and + // we need to apply the default "follow" to existing records in the table. + // We don't actually want to keep the default in the schema for future + // INSERTs. + nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "ALTER TABLE entries " + "ADD COLUMN request_redirect INTEGER NOT NULL DEFAULT 0" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + // Now overwrite the master SQL for the entries table to remove the column + // default value. This is also necessary for our Validate() method to + // pass on this database. + rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "PRAGMA writable_schema = ON" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsCOMPtr state; + rv = aConn->CreateStatement(NS_LITERAL_CSTRING( + "UPDATE sqlite_master SET sql=:sql WHERE name='entries'" + ), getter_AddRefs(state)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("sql"), + nsDependentCString(kTableEntries)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = state->Execute(); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = aConn->SetSchemaVersion(16); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "PRAGMA writable_schema = OFF" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return rv; +} + +} // anonymous namespace + } // namespace db } // namespace cache } // namespace dom diff --git a/dom/cache/DBSchema.h b/dom/cache/DBSchema.h index 86fa097bf2..cc23f77434 100644 --- a/dom/cache/DBSchema.h +++ b/dom/cache/DBSchema.h @@ -29,8 +29,9 @@ struct SavedResponse; namespace db { +// Note, this cannot be executed within a transaction. nsresult -CreateSchema(mozIStorageConnection* aConn); +CreateOrMigrateSchema(mozIStorageConnection* aConn); // Note, this cannot be executed within a transaction. nsresult @@ -116,8 +117,9 @@ StorageGetKeys(mozIStorageConnection* aConn, Namespace aNamespace, nsresult IncrementalVacuum(mozIStorageConnection* aConn); -// We will wipe out databases with a schema versions less than this. -extern const int32_t kMaxWipeSchemaVersion; +// We will wipe out databases with a schema versions less than this. Newer +// versions will be migrated on open to the latest schema version. +extern const int32_t kFirstShippedSchemaVersion; } // namespace db } // namespace cache diff --git a/dom/cache/IPCUtils.h b/dom/cache/IPCUtils.h index cb09ebe6fc..6fe6e0b17d 100644 --- a/dom/cache/IPCUtils.h +++ b/dom/cache/IPCUtils.h @@ -39,6 +39,11 @@ namespace IPC { mozilla::dom::RequestCache::Default, mozilla::dom::RequestCache::EndGuard_> {}; template<> + struct ParamTraits : + public ContiguousEnumSerializer {}; + template<> struct ParamTraits : public ContiguousEnumSerializerGetCredentialsMode(); aOut.contentPolicyType() = aIn->ContentPolicyType(); aOut.requestCache() = aIn->GetCacheMode(); + aOut.requestRedirect() = aIn->GetRedirectMode(); if (aBodyAction == IgnoreBody) { aOut.body() = void_t(); @@ -212,8 +213,8 @@ TypeUtils::ToCacheResponseWithoutBody(CacheResponse& aOut, } } - aOut.status() = aIn.GetStatus(); - aOut.statusText() = aIn.GetStatusText(); + aOut.status() = aIn.GetUnfilteredStatus(); + aOut.statusText() = aIn.GetUnfilteredStatusText(); nsRefPtr headers = aIn.UnfilteredHeaders(); MOZ_ASSERT(headers); if (HasVaryStar(headers)) { @@ -245,7 +246,7 @@ TypeUtils::ToCacheResponse(CacheResponse& aOut, Response& aIn, ErrorResult& aRv) } nsCOMPtr stream; - ir->GetInternalBody(getter_AddRefs(stream)); + ir->GetUnfilteredBody(getter_AddRefs(stream)); if (stream) { aIn.SetBodyUsed(); } @@ -304,17 +305,20 @@ TypeUtils::ToResponse(const CacheResponse& aIn) switch (aIn.type()) { - case ResponseType::Default: - break; - case ResponseType::Opaque: - ir = ir->OpaqueResponse(); - break; case ResponseType::Basic: ir = ir->BasicResponse(); break; case ResponseType::Cors: ir = ir->CORSResponse(); break; + case ResponseType::Default: + break; + case ResponseType::Opaque: + ir = ir->OpaqueResponse(); + break; + case ResponseType::Opaqueredirect: + ir = ir->OpaqueRedirectResponse(); + break; default: MOZ_CRASH("Unexpected ResponseType!"); } @@ -340,6 +344,7 @@ TypeUtils::ToInternalRequest(const CacheRequest& aIn) internalRequest->SetCredentialsMode(aIn.credentials()); internalRequest->SetContentPolicyType(aIn.contentPolicyType()); internalRequest->SetCacheMode(aIn.requestCache()); + internalRequest->SetRedirectMode(aIn.requestRedirect()); nsRefPtr internalHeaders = ToInternalHeaders(aIn.headers(), aIn.headersGuard()); diff --git a/dom/cache/moz.build b/dom/cache/moz.build index 4655b6cad9..b8c055ba6d 100644 --- a/dom/cache/moz.build +++ b/dom/cache/moz.build @@ -100,3 +100,7 @@ MOCHITEST_CHROME_MANIFESTS += [ BROWSER_CHROME_MANIFESTS += [ 'test/mochitest/browser.ini', ] + +XPCSHELL_TESTS_MANIFESTS += [ + 'test/xpcshell/xpcshell.ini', +] diff --git a/dom/cache/test/xpcshell/head.js b/dom/cache/test/xpcshell/head.js new file mode 100644 index 0000000000..3d51929b35 --- /dev/null +++ b/dom/cache/test/xpcshell/head.js @@ -0,0 +1,77 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/ + * and are CC licensed by https://www.flickr.com/photos/legofenris/. + */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; + +// services required be initialized in order to run CacheStorage +var ss = Cc['@mozilla.org/storage/service;1'] + .createInstance(Ci.mozIStorageService); +var sts = Cc['@mozilla.org/network/stream-transport-service;1'] + .getService(Ci.nsIStreamTransportService); +var hash = Cc['@mozilla.org/security/hash;1'] + .createInstance(Ci.nsICryptoHash); + +// Expose Cache and Fetch symbols on the global +Cu.importGlobalProperties(['caches', 'fetch']); + +// Extract a zip file into the profile +function create_test_profile(zipFileName) { + do_get_profile(); + + var directoryService = Cc['@mozilla.org/file/directory_service;1'] + .getService(Ci.nsIProperties); + var profileDir = directoryService.get('ProfD', Ci.nsIFile); + var currentDir = directoryService.get('CurWorkD', Ci.nsIFile); + + var packageFile = currentDir.clone(); + packageFile.append(zipFileName); + + var zipReader = Cc['@mozilla.org/libjar/zip-reader;1'] + .createInstance(Ci.nsIZipReader); + zipReader.open(packageFile); + + var entryNames = []; + var entries = zipReader.findEntries(null); + while (entries.hasMore()) { + var entry = entries.getNext(); + entryNames.push(entry); + } + entryNames.sort(); + + for (var entryName of entryNames) { + var zipentry = zipReader.getEntry(entryName); + + var file = profileDir.clone(); + entryName.split('/').forEach(function(part) { + file.append(part); + }); + + if (zipentry.isDirectory) { + file.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0755', 8)); + } else { + var istream = zipReader.getInputStream(entryName); + + var ostream = Cc['@mozilla.org/network/file-output-stream;1'] + .createInstance(Ci.nsIFileOutputStream); + ostream.init(file, -1, parseInt('0644', 8), 0); + + var bostream = Cc['@mozilla.org/network/buffered-output-stream;1'] + .createInstance(Ci.nsIBufferedOutputStream); + bostream.init(ostream, 32 * 1024); + + bostream.writeFrom(istream, istream.available()); + + istream.close(); + bostream.close(); + } + } + + zipReader.close(); +} diff --git a/dom/cache/test/xpcshell/make_profile.js b/dom/cache/test/xpcshell/make_profile.js new file mode 100644 index 0000000000..b50c7aeb77 --- /dev/null +++ b/dom/cache/test/xpcshell/make_profile.js @@ -0,0 +1,142 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/ + * and are CC licensed by https://www.flickr.com/photos/legofenris/. + */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; + +// Enumerate the directory tree and store results in entryList as +// +// { path: 'a/b/c', file: } +// +// The algorithm starts with the first entry already in entryList. +function enumerate_tree(entryList) { + for (var index = 0; index < entryList.length; ++index) { + var path = entryList[index].path; + var file = entryList[index].file; + + if (file.isDirectory()) { + var dirList = file.directoryEntries; + while (dirList.hasMoreElements()) { + var dirFile = dirList.getNext().QueryInterface(Ci.nsIFile); + entryList.push({ path: path + '/' + dirFile.leafName, file: dirFile }); + } + } + } +} + +function zip_profile(zipFile, profileDir) { + var zipWriter = Cc['@mozilla.org/zipwriter;1'] + .createInstance(Ci.nsIZipWriter); + zipWriter.open(zipFile, 0x04 | 0x08 | 0x20); + + var root = profileDir.clone(); + root.append('storage'); + root.append('default'); + root.append('chrome'); + + var entryList = [{path: 'storage/default/chrome', file: root}]; + enumerate_tree(entryList); + + entryList.forEach(function(entry) { + if (entry.file.isDirectory()) { + zipWriter.addEntryDirectory(entry.path, entry.file.lastModifiedTime, + false); + } else { + var istream = Cc['@mozilla.org/network/file-input-stream;1'] + .createInstance(Ci.nsIFileInputStream); + istream.init(entry.file, -1, -1, 0); + zipWriter.addEntryStream(entry.path, entry.file.lastModifiedTime, + Ci.nsIZipWriter.COMPRESSION_DEFAULT, istream, + false); + istream.close(); + } + }); + + zipWriter.close(); +} + +function exactGC() { + return new Promise(function(resolve) { + var count = 0; + function doPreciseGCandCC() { + function scheduleGCCallback() { + Cu.forceCC(); + + if (++count < 2) { + doPreciseGCandCC(); + } else { + resolve(); + } + } + Cu.schedulePreciseGC(scheduleGCCallback); + } + doPreciseGCandCC(); + }); +} + +function resetQuotaManager() { + return new Promise(function(resolve) { + var qm = Cc['@mozilla.org/dom/quota/manager;1'] + .getService(Ci.nsIQuotaManager); + + var prefService = Cc['@mozilla.org/preferences-service;1'] + .getService(Ci.nsIPrefService); + + // enable quota manager testing mode + var pref = 'dom.quotaManager.testing'; + prefService.getBranch(null).setBoolPref(pref, true); + + qm.reset(); + + // disable quota manager testing mode + //prefService.getBranch(null).setBoolPref(pref, false); + + var uri = Cc['@mozilla.org/network/io-service;1'] + .getService(Ci.nsIIOService) + .newURI('http://example.com', null, null); + var principal = Cc['@mozilla.org/scriptsecuritymanager;1'] + .getService(Ci.nsIScriptSecurityManager) + .getSystemPrincipal(); + + // use getUsageForPrincipal() to get a callback when the reset() is done + qm.getUsageForPrincipal(principal, function(principal, usage, fileUsage) { + resolve(usage); + }); + }); +} + +function run_test() { + do_test_pending(); + do_get_profile(); + + var directoryService = Cc['@mozilla.org/file/directory_service;1'] + .getService(Ci.nsIProperties); + var profileDir = directoryService.get('ProfD', Ci.nsIFile); + var currentDir = directoryService.get('CurWorkD', Ci.nsIFile); + + var zipFile = currentDir.clone(); + zipFile.append('new_profile.zip'); + if (zipFile.exists()) { + zipFile.remove(false); + } + ok(!zipFile.exists()); + + caches.open('xpcshell-test').then(function(c) { + var request = new Request('http://example.com/index.html'); + var response = new Response('hello world'); + return c.put(request, response); + }).then(exactGC).then(resetQuotaManager).then(function() { + zip_profile(zipFile, profileDir); + dump('### ### created zip at: ' + zipFile.path + '\n'); + do_test_finished(); + }).catch(function(e) { + do_test_finished(); + ok(false, e); + }); +} diff --git a/dom/cache/test/xpcshell/schema_15_profile.zip b/dom/cache/test/xpcshell/schema_15_profile.zip new file mode 100644 index 0000000000000000000000000000000000000000..6d742275b30921a721dddbcbbd12439d3081bdce GIT binary patch literal 2577 zcmbW23piA17{^a5X?4*)n-!YqiAIS@%#4UO8*Pb3jcj5x=EAr%nu|;0u8~$(w@9U2 zhB8P7wKc zA0`N(iBos>*~b1j3L1;1pg&+Rhm!3_`G&*r!9NMvL35kCS2_d#kOUqEc#4pa`18+i`)uOFpmUgu+vRcrB> z+P-&myc>VxrZWOxcW)wuMc$_x+q2BL7Z>@!2hMVI2Pz{fm26(9brf~zM5^fNNdUDi zx~O1bRD53{HIcE~^T&K~t_HZvGDFO@_TcOFNYzr-?{d|z_XtX&I<5Tv#Iak457@+I zho6iCt&?`gqsp7LM@_V>#Nc%|D;A$gt*^S#0HG3V0?G@xWE-g&knl60> zw<=ozbU6F4EwPN6no|WObqDh6Vh2OJ8YGW>7a2-I+sg*uz*GDTT?r1ni`14p%R*NF z%Rz}ht*XwbtDr3=Cug0gxQ%sqgOMOEvrw3VySlgcKB3@gd9S)<`5}RtDRix2)q^A| zv6IUmNp|*aK=E7lApEN9u*X#z-w0##p8QJ68pyt$D~^7QL@k4CA2xK0L$Sa;9Ithz z?6XkUnwTvdln)f`;GLIF_~)@8u;`gdmE_O`ZQZOoTsYu(6^r$OC~#Y6Zc| zwS!@`S z)U|4))iOSzptkSL(H>jh^zc%|8&!+UbVQ(5XK*8T^g*u6Sj_6TBc6{+ozfZ-HAQtA zVGut73y%3lbv0dl1jPw*);5KD1LsL-<$1P}pm+OPwT+_Js1Di+b8b zDtg)%&AQY(dNc1%$qS@5n-l`gAvdvos`$wC;mi7E^--%{`=U(D)9wY@?rcbHFYh*R z4|4a4*iKl|nTLOC_a|w2QX66{g%nV3r%8imt>!a4oGjS;2R)U7>aU!_EiM{@s9qaO zzHsoGWs~j^fvQO8GLVyxt$)`4vWIsZvKd6Z%?S>}k3Q4P!o`viOle=q-AA{>bpz=w z#6$_llfc6UlzOuh%^rJkR~)?ed_LRKenX>922*GY3YK=mY~oLoEOz+U4qt#aIk~+; zd0w$XGh>?;gS+f#22a}94V++S;fdcVCOMciwI!xjFg(j~zHRJreYdq3h;spjOkRFThE>>*P~*&=@qo^Jf%Vb5 z<}a8b4%DWC{a6S6_T6#B^BRfu?PRZJ{H`4Pmd2_^dhf}L=UNQOU0pZ(dq>h*ZbO3# zDFjKPWK34wU1X!85EIaSse6yZ?rh0;XQWuB5Ew8-&ydMb-HPYH(&U|4;-S(cQBxB0 z8uJ;`ULXF1!tO!(i0|NPh(A;NwvF$Xklu~R1oWwJ#I10+d|K+R8!D55r#{&{+fg$t zrg@)srmSfq7SlOvP4O|O){k1dudf;YMJ

J@R+XiGfT1FTxHas zNb;h7q9V^k8GIqi!~{N@1G)ND^{<|gE#GzISKw%yFi=l3JRWX@H^rM6nc>WEMw?+9 zVMZ9}Ml&eP69dD;p<%{&!eO-E2P+t>udYP&o?n4jR;zoz@`knBTZ?i#J0C|!j!j6o zlcVZ{fzA6yLuqz*os91?pl_SJH@DYL(?0FClQaOxjMXAM}wgM1Pd8)A-3< j{5WH$q)&vV@+zUe= literal 0 HcmV?d00001 diff --git a/dom/cache/test/xpcshell/test_migration.js b/dom/cache/test/xpcshell/test_migration.js new file mode 100644 index 0000000000..3076f3f590 --- /dev/null +++ b/dom/cache/test/xpcshell/test_migration.js @@ -0,0 +1,38 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/ + * and are CC licensed by https://www.flickr.com/photos/legofenris/. + */ + +function run_test() { + do_test_pending(); + create_test_profile('schema_15_profile.zip'); + + var cache; + caches.open('xpcshell-test').then(function(c) { + cache = c; + ok(cache, 'cache exists'); + return cache.keys(); + }).then(function(requestList) { + ok(requestList.length > 0, 'should have at least one request in cache'); + requestList.forEach(function(request) { + ok(request, 'each request in list should be non-null'); + ok(request.redirect === 'follow', 'request.redirect should default to "follow"'); + }); + return Promise.all(requestList.map(function(request) { + return cache.match(request); + })); + }).then(function(responseList) { + ok(responseList.length > 0, 'should have at least one response in cache'); + responseList.forEach(function(response) { + ok(response, 'each request in list should be non-null'); + }); + }).then(function() { + do_test_finished(); + }).catch(function(e) { + ok(false, 'caught exception ' + e); + do_test_finished(); + }); +} diff --git a/dom/cache/test/xpcshell/xpcshell.ini b/dom/cache/test/xpcshell/xpcshell.ini new file mode 100644 index 0000000000..2bd3f90747 --- /dev/null +++ b/dom/cache/test/xpcshell/xpcshell.ini @@ -0,0 +1,16 @@ +# 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/. + +[DEFAULT] +head = head.js +tail = +skip-if = toolkit == 'gonk' +support-files = + schema_15_profile.zip + +# dummy test entry to generate profile zip files +[make_profile.js] + skip-if = true + +[test_migration.js] diff --git a/dom/fetch/ChannelInfo.cpp b/dom/fetch/ChannelInfo.cpp index 4c1ef0d660..614ea6a16f 100644 --- a/dom/fetch/ChannelInfo.cpp +++ b/dom/fetch/ChannelInfo.cpp @@ -6,7 +6,9 @@ #include "mozilla/dom/ChannelInfo.h" #include "nsCOMPtr.h" +#include "nsContentUtils.h" #include "nsIChannel.h" +#include "nsIDocument.h" #include "nsIHttpChannel.h" #include "nsSerializationHelper.h" #include "mozilla/net/HttpBaseChannel.h" @@ -18,6 +20,26 @@ using namespace mozilla; using namespace mozilla::dom; +void +ChannelInfo::InitFromDocument(nsIDocument* aDoc) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mInited, "Cannot initialize the object twice"); + + nsCOMPtr securityInfo = aDoc->GetSecurityInfo(); + if (securityInfo) { + SetSecurityInfo(securityInfo); + } + + // mRedirected flag and mRedirectedURISpec are only important for maintaining + // the channel's redirected status. If the ChannelInfo is initialized from + // a document, that document has already asked the channel from which it was + // loaded about the current channel URI, so it won't matter if a future + // ResurrectInfoOnChannel() call misses whether the channel was redirected. + mRedirected = false; + mInited = true; +} + void ChannelInfo::InitFromChannel(nsIChannel* aChannel) { @@ -47,6 +69,20 @@ ChannelInfo::InitFromChannel(nsIChannel* aChannel) mInited = true; } +void +ChannelInfo::InitFromChromeGlobal(nsIGlobalObject* aGlobal) +{ + MOZ_ASSERT(!mInited, "Cannot initialize the object twice"); + MOZ_ASSERT(aGlobal); + + MOZ_RELEASE_ASSERT( + nsContentUtils::IsSystemPrincipal(aGlobal->PrincipalOrNull())); + + mSecurityInfo.Truncate(); + mRedirected = false; + mInited = true; +} + void ChannelInfo::InitFromIPCChannelInfo(const mozilla::ipc::IPCChannelInfo& aChannelInfo) { diff --git a/dom/fetch/ChannelInfo.h b/dom/fetch/ChannelInfo.h index ebedbcf0ae..90bb924af9 100644 --- a/dom/fetch/ChannelInfo.h +++ b/dom/fetch/ChannelInfo.h @@ -11,6 +11,8 @@ #include "nsCOMPtr.h" class nsIChannel; +class nsIDocument; +class nsIGlobalObject; class nsIURI; namespace mozilla { @@ -66,7 +68,9 @@ public: return *this; } + void InitFromDocument(nsIDocument* aDoc); void InitFromChannel(nsIChannel* aChannel); + void InitFromChromeGlobal(nsIGlobalObject* aGlobal); void InitFromIPCChannelInfo(const IPCChannelInfo& aChannelInfo); // This restores every possible information stored from a previous channel diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp index d2c32d65aa..4e212c7841 100644 --- a/dom/fetch/FetchDriver.cpp +++ b/dom/fetch/FetchDriver.cpp @@ -30,6 +30,7 @@ #include "mozilla/dom/File.h" #include "mozilla/dom/workers/Workers.h" +#include "mozilla/unused.h" #include "Fetch.h" #include "InternalRequest.h" @@ -40,7 +41,7 @@ namespace dom { NS_IMPL_ISUPPORTS(FetchDriver, nsIStreamListener, nsIChannelEventSink, nsIInterfaceRequestor, - nsIAsyncVerifyRedirectCallback) + nsIAsyncVerifyRedirectCallback, nsIThreadRetargetableStreamListener) FetchDriver::FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup) @@ -48,6 +49,7 @@ FetchDriver::FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal, , mLoadGroup(aLoadGroup) , mRequest(aRequest) , mFetchRecursionCount(0) + , mCORSFlagEverSet(false) , mResponseAvailableCalled(false) { } @@ -93,8 +95,8 @@ FetchDriver::Fetch(bool aCORSFlag) MOZ_CRASH("Synchronous fetch not supported"); } -nsresult -FetchDriver::ContinueFetch(bool aCORSFlag) +FetchDriver::MainFetchOp +FetchDriver::SetTaintingAndGetNextOp(bool aCORSFlag) { workers::AssertIsOnMainThread(); @@ -104,7 +106,7 @@ FetchDriver::ContinueFetch(bool aCORSFlag) nsresult rv = NS_NewURI(getter_AddRefs(requestURI), url, nullptr, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { - return FailWithNetworkError(); + return MainFetchOp(NETWORK_ERROR); } // CSP/mixed content checks. @@ -122,47 +124,91 @@ FetchDriver::ContinueFetch(bool aCORSFlag) nsContentUtils::GetSecurityManager()); if (NS_WARN_IF(NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad))) { // Disallowed by content policy. - return FailWithNetworkError(); + return MainFetchOp(NETWORK_ERROR); } - // Begin Step 4 of the Fetch algorithm + // Begin Step 8 of the Main Fetch algorithm // https://fetch.spec.whatwg.org/#fetching nsAutoCString scheme; rv = requestURI->GetScheme(scheme); if (NS_WARN_IF(NS_FAILED(rv))) { - return FailWithNetworkError(); + return MainFetchOp(NETWORK_ERROR); } - rv = mPrincipal->CheckMayLoad(requestURI, false /* report */, false /* allowIfInheritsPrincipal */); + // request's current url's origin is request's origin and the CORS flag is unset + // request's current url's scheme is "data" and request's same-origin data-URL flag is set + // request's current url's scheme is "about" + rv = mPrincipal->CheckMayLoad(requestURI, false /* report */, + false /* allowIfInheritsPrincipal */); if ((!aCORSFlag && NS_SUCCEEDED(rv)) || (scheme.EqualsLiteral("data") && mRequest->SameOriginDataURL()) || scheme.EqualsLiteral("about")) { - return BasicFetch(); + return MainFetchOp(BASIC_FETCH); } + // request's mode is "same-origin" if (mRequest->Mode() == RequestMode::Same_origin) { - return FailWithNetworkError(); + return MainFetchOp(NETWORK_ERROR); } + // request's mode is "no-cors" if (mRequest->Mode() == RequestMode::No_cors) { mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUE); - return BasicFetch(); + return MainFetchOp(BASIC_FETCH); } + // request's current url's scheme is not one of "http" and "https" if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) { + return MainFetchOp(NETWORK_ERROR); + } + + // request's mode is "cors-with-forced-preflight" + // request's unsafe-request flag is set and either request's method is not + // a simple method or a header in request's header list is not a simple header + if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight || + (mRequest->UnsafeRequest() && (!mRequest->HasSimpleMethod() || + !mRequest->Headers()->HasOnlySimpleHeaders()))) { + mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS); + mRequest->SetRedirectMode(RequestRedirect::Error); + + // Note, the following text from Main Fetch step 8 is handled in + // nsCORSListenerProxy when CheckRequestApproved() fails: + // + // The result of performing an HTTP fetch using request with the CORS + // flag and CORS-preflight flag set. If the result is a network error, + // clear cache entries using request. + + return MainFetchOp(HTTP_FETCH, true /* cors */, true /* preflight */); + } + + // Otherwise + mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS); + return MainFetchOp(HTTP_FETCH, true /* cors */, false /* preflight */); +} + +nsresult +FetchDriver::ContinueFetch(bool aCORSFlag) +{ + workers::AssertIsOnMainThread(); + + MainFetchOp nextOp = SetTaintingAndGetNextOp(aCORSFlag); + + if (nextOp.mType == NETWORK_ERROR) { return FailWithNetworkError(); } - bool corsPreflight = false; - if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight || - (mRequest->UnsafeRequest() && (!mRequest->HasSimpleMethod() || !mRequest->Headers()->HasOnlySimpleHeaders()))) { - corsPreflight = true; + if (nextOp.mType == BASIC_FETCH) { + return BasicFetch(); } - mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS); - return HttpFetch(true /* aCORSFlag */, corsPreflight); -} + if (nextOp.mType == HTTP_FETCH) { + return HttpFetch(nextOp.mCORSFlag, nextOp.mCORSPreflightFlag); + } + + MOZ_ASSERT_UNREACHABLE("Unexpected main fetch operation!"); + return FailWithNetworkError(); + } nsresult FetchDriver::BasicFetch() @@ -313,6 +359,11 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica mResponse = nullptr; nsresult rv; + // We need to track the CORS flag through redirects. Since there is no way + // for us to go from CORS mode to non-CORS mode, we just need to remember + // if it has ever been set. + mCORSFlagEverSet = mCORSFlagEverSet || aCORSFlag; + nsCOMPtr ios = do_GetIOService(&rv); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); @@ -473,6 +524,13 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica // Auth may require prompting, we don't support it yet. // The next patch in this same bug prevents this from aborting the request. // Credentials checks for CORS are handled by nsCORSListenerProxy, + + nsCOMPtr internalChan = do_QueryInterface(httpChan); + + // Conversion between enumerations is safe due to static asserts in + // dom/workers/ServiceWorkerManager.cpp + internalChan->SetCorsMode(static_cast(mRequest->Mode())); + internalChan->SetRedirectMode(static_cast(mRequest->GetRedirectMode())); } // Step 5. Proxy authentication will be handled by Necko. @@ -520,10 +578,10 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica nsCOMPtr listener = this; - // Unless the cors mode is explicitly no-cors, we set up a cors proxy even in - // the same-origin case, since the proxy does not enforce cors header checks - // in the same-origin case. - if (mRequest->Mode() != RequestMode::No_cors) { + // Only use nsCORSListenerProxy if we are in CORS mode. Otherwise it + // will overwrite the CorsMode flag unconditionally to "cors" or + // "cors-with-forced-preflight". + if (mRequest->Mode() == RequestMode::Cors) { // Set up a CORS proxy that will handle the various requirements of the CORS // protocol. It handles the preflight cache and CORS response headers. // If the request is allowed, it will start our original request @@ -544,6 +602,8 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica // unsafeHeaders so they can be verified against the response's // "Access-Control-Allow-Headers" header. if (aCORSPreflightFlag) { + MOZ_ASSERT(mRequest->Mode() != RequestMode::No_cors, + "FetchDriver::ContinueFetch() should ensure that the request is not no-cors"); nsCOMPtr preflightChannel; nsAutoTArray unsafeHeaders; mRequest->Headers()->GetUnsafeHeaders(unsafeHeaders); @@ -599,6 +659,9 @@ FetchDriver::BeginAndGetFilteredResponse(InternalResponse* aResponse, nsIURI* aF case InternalRequest::RESPONSETAINT_OPAQUE: filteredResponse = aResponse->OpaqueResponse(); break; + case InternalRequest::RESPONSETAINT_OPAQUEREDIRECT: + filteredResponse = aResponse->OpaqueRedirectResponse(); + break; default: MOZ_CRASH("Unexpected case"); } @@ -679,8 +742,11 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) { workers::AssertIsOnMainThread(); - MOZ_ASSERT(!mPipeOutputStream); - MOZ_ASSERT(mObserver); + + // Note, this can be called multiple times if we are doing an opaqueredirect. + // In that case we will get a simulated OnStartRequest() and then the real + // channel will call in with an errored OnStartRequest(). + nsresult rv; aRequest->GetStatus(&rv); if (NS_WARN_IF(NS_FAILED(rv))) { @@ -688,6 +754,10 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest, return rv; } + // We should only get to the following code once. + MOZ_ASSERT(!mPipeOutputStream); + MOZ_ASSERT(mObserver); + nsRefPtr response; nsCOMPtr httpChannel = do_QueryInterface(aRequest); if (httpChannel) { @@ -756,9 +826,8 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest, } // Try to retarget off main thread. - nsCOMPtr rr = do_QueryInterface(aRequest); - if (rr) { - rr->RetargetDeliveryTo(sts); + if (nsCOMPtr rr = do_QueryInterface(aRequest)) { + NS_WARN_IF(NS_FAILED(rr->RetargetDeliveryTo(sts))); } return NS_OK; } @@ -770,6 +839,10 @@ FetchDriver::OnDataAvailable(nsIRequest* aRequest, uint64_t aOffset, uint32_t aCount) { + // NB: This can be called on any thread! But we're guaranteed that it is + // called between OnStartRequest and OnStopRequest, so we don't need to worry + // about races. + uint32_t aRead; MOZ_ASSERT(mResponse); MOZ_ASSERT(mPipeOutputStream); @@ -816,33 +889,59 @@ FetchDriver::AsyncOnChannelRedirect(nsIChannel* aOldChannel, nsresult rv; - // Section 4.2, Step 4.6-4.7, enforcing a redirect count is done by Necko. - // The pref used is "network.http.redirection-limit" which is set to 20 by - // default. - // - // Step 4.8. We only unset this for spec compatibility. Any actions we take - // on mRequest here do not affect what the channel does. + // HTTP Fetch step 5, "redirect status", step 1 + if (NS_WARN_IF(mRequest->GetRedirectMode() == RequestRedirect::Error)) { + aOldChannel->Cancel(NS_BINDING_FAILED); + return NS_BINDING_FAILED; + } + + // HTTP Fetch step 5, "redirect status", steps 2 through 6 are automatically + // handled by necko before calling AsyncOnChannelRedirect() with the new + // nsIChannel. + + // HTTP Fetch step 5, "redirect status", steps 7 and 8 enforcing a redirect + // count are done by Necko. The pref used is "network.http.redirection-limit" + // which is set to 20 by default. + + // HTTP Fetch Step 9, "redirect status". We only unset this for spec + // compatibility. Any actions we take on mRequest here do not affect what the + //channel does. mRequest->UnsetSameOriginDataURL(); - // - // Requests that require preflight are not permitted to redirect. - // Fetch spec section 4.2 "HTTP Fetch", step 4.9 just uses the manual - // redirect flag to decide whether to execute step 4.10 or not. We do not - // represent it in our implementation. - // The only thing we do is to check if the request requires a preflight (part - // of step 4.9), in which case we abort. This part cannot be done by - // nsCORSListenerProxy since it does not have access to mRequest. - // which case. Step 4.10.3 is handled by OnRedirectVerifyCallback(), and all - // the other steps are handled by nsCORSListenerProxy. - if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) { - rv = DoesNotRequirePreflight(aNewChannel); - if (NS_FAILED(rv)) { - NS_WARNING("FetchDriver::OnChannelRedirect: " - "DoesNotRequirePreflight returned failure"); - return rv; - } + // HTTP Fetch step 5, "redirect status", step 10 requires us to halt the + // redirect, but successfully return an opaqueredirect Response to the + // initiating Fetch. + if (mRequest->GetRedirectMode() == RequestRedirect::Manual) { + // Ideally we would simply not cancel the old channel and allow it to + // be processed as normal. Unfortunately this is quite fragile and + // other redirect handlers can easily break it for certain use cases. + // + // For example, nsCORSListenerProxy cancels vetoed redirect channels. + // The HTTP cache will also error on vetoed redirects when the + // redirect has been previously cached. + // + // Therefore simulate the completion of the channel to produce the + // opaqueredirect Response and then cancel the original channel. This + // will result in OnStartRequest() getting called twice, but the second + // time will be with an error response (from the Cancel) which will + // be ignored. + mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUEREDIRECT); + unused << OnStartRequest(aOldChannel, nullptr); + unused << OnStopRequest(aOldChannel, nullptr, NS_OK); + + aOldChannel->Cancel(NS_BINDING_FAILED); + + return NS_BINDING_FAILED; } + // The following steps are from HTTP Fetch step 5, "redirect status", step 11 + // which requires the RequestRedirect to be "follow". + MOZ_ASSERT(mRequest->GetRedirectMode() == RequestRedirect::Follow); + + // HTTP Fetch step 5, "redirect status", steps 11.1 and 11.2 block redirecting + // to a URL with credentials in CORS mode. This is implemented in + // nsCORSListenerProxy. + mRedirectCallback = aCallback; mOldRedirectChannel = aOldChannel; mNewRedirectChannel = aNewChannel; @@ -867,6 +966,12 @@ FetchDriver::AsyncOnChannelRedirect(nsIChannel* aOldChannel, return NS_OK; } +NS_IMETHODIMP +FetchDriver::CheckListenerChain() +{ + return NS_OK; +} + // Returns NS_OK if no preflight is required, error otherwise. nsresult FetchDriver::DoesNotRequirePreflight(nsIChannel* aChannel) @@ -929,12 +1034,12 @@ FetchDriver::GetInterface(const nsIID& aIID, void **aResult) NS_IMETHODIMP FetchDriver::OnRedirectVerifyCallback(nsresult aResult) { - // On a successful redirect we perform the following substeps of Section 4.2, - // step 4.10. + // On a successful redirect we perform the following substeps of HTTP Fetch, + // step 5, "redirect status", step 11. if (NS_SUCCEEDED(aResult)) { - // Step 4.10.3 "Set request's url to locationURL." so that when we set the - // Response's URL from the Request's URL in Section 4, step 6, we get the - // final value. + // Step 11.5 "Append locationURL to request's url list." so that when we set the + // Response's URL from the Request's URL in Main Fetch, step 15, we get the + // final value. Note, we still use a single URL value instead of a list. nsCOMPtr newURI; nsresult rv = NS_GetFinalChannelURI(mNewRedirectChannel, getter_AddRefs(newURI)); if (NS_WARN_IF(NS_FAILED(rv))) { @@ -951,6 +1056,22 @@ FetchDriver::OnRedirectVerifyCallback(nsresult aResult) mOldRedirectChannel->Cancel(aResult); } + // Implement Main Fetch step 8 again on redirect. + MainFetchOp nextOp = SetTaintingAndGetNextOp(mCORSFlagEverSet); + + if (nextOp.mType == NETWORK_ERROR) { + // Cancel the channel if Main Fetch blocks the redirect from continuing. + aResult = NS_ERROR_DOM_BAD_URI; + mOldRedirectChannel->Cancel(aResult); + } else { + // Otherwise, we rely on necko and the CORS proxy to do the right thing + // as the redirect is followed. In general this means basic or http + // fetch. If we've ever been CORS, we need to stay CORS. + MOZ_ASSERT(nextOp.mType == BASIC_FETCH || nextOp.mType == HTTP_FETCH); + MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mType == HTTP_FETCH); + MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mCORSFlag); + } + mOldRedirectChannel = nullptr; mNewRedirectChannel = nullptr; mRedirectCallback->OnRedirectVerifyCallback(aResult); diff --git a/dom/fetch/FetchDriver.h b/dom/fetch/FetchDriver.h index ecc911e9ab..9a56485798 100644 --- a/dom/fetch/FetchDriver.h +++ b/dom/fetch/FetchDriver.h @@ -12,6 +12,7 @@ #include "nsIChannelEventSink.h" #include "nsIInterfaceRequestor.h" #include "nsIStreamListener.h" +#include "nsIThreadRetargetableStreamListener.h" #include "mozilla/nsRefPtr.h" #include "mozilla/DebugOnly.h" @@ -57,7 +58,8 @@ private: class FetchDriver final : public nsIStreamListener, public nsIChannelEventSink, public nsIInterfaceRequestor, - public nsIAsyncVerifyRedirectCallback + public nsIAsyncVerifyRedirectCallback, + public nsIThreadRetargetableStreamListener { public: NS_DECL_ISUPPORTS @@ -66,6 +68,7 @@ public: NS_DECL_NSICHANNELEVENTSINK NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER explicit FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup); @@ -87,6 +90,7 @@ private: nsCOMPtr mNewRedirectChannel; nsCOMPtr mDocument; uint32_t mFetchRecursionCount; + bool mCORSFlagEverSet; DebugOnly mResponseAvailableCalled; @@ -95,7 +99,29 @@ private: FetchDriver& operator=(const FetchDriver&) = delete; ~FetchDriver(); + enum MainFetchOpType + { + NETWORK_ERROR, + BASIC_FETCH, + HTTP_FETCH, + NUM_MAIN_FETCH_OPS + }; + + struct MainFetchOp + { + explicit MainFetchOp(MainFetchOpType aType, bool aCORSFlag = false, + bool aCORSPreflightFlag = false) + : mType(aType), mCORSFlag(aCORSFlag), + mCORSPreflightFlag(aCORSPreflightFlag) + { } + + MainFetchOpType mType; + bool mCORSFlag; + bool mCORSPreflightFlag; + }; + nsresult Fetch(bool aCORSFlag); + MainFetchOp SetTaintingAndGetNextOp(bool aCORSFlag); nsresult ContinueFetch(bool aCORSFlag); nsresult BasicFetch(); nsresult HttpFetch(bool aCORSFlag = false, bool aCORSPreflightFlag = false, bool aAuthenticationFlag = false); diff --git a/dom/fetch/InternalRequest.cpp b/dom/fetch/InternalRequest.cpp index 77801e10e5..ad34ea7f19 100644 --- a/dom/fetch/InternalRequest.cpp +++ b/dom/fetch/InternalRequest.cpp @@ -42,6 +42,7 @@ InternalRequest::GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult copy->mMode = mMode; copy->mCredentialsMode = mCredentialsMode; copy->mCacheMode = mCacheMode; + copy->mRedirectMode = mRedirectMode; copy->mCreatedByFetchEvent = mCreatedByFetchEvent; return copy.forget(); } @@ -80,6 +81,7 @@ InternalRequest::InternalRequest(const InternalRequest& aOther) , mCredentialsMode(aOther.mCredentialsMode) , mResponseTainting(aOther.mResponseTainting) , mCacheMode(aOther.mCacheMode) + , mRedirectMode(aOther.mRedirectMode) , mAuthenticationFlag(aOther.mAuthenticationFlag) , mForceOriginHeader(aOther.mForceOriginHeader) , mPreserveContentCodings(aOther.mPreserveContentCodings) diff --git a/dom/fetch/InternalRequest.h b/dom/fetch/InternalRequest.h index 44caa61bb8..0442a5126f 100644 --- a/dom/fetch/InternalRequest.h +++ b/dom/fetch/InternalRequest.h @@ -92,6 +92,7 @@ public: RESPONSETAINT_BASIC, RESPONSETAINT_CORS, RESPONSETAINT_OPAQUE, + RESPONSETAINT_OPAQUEREDIRECT, }; explicit InternalRequest() @@ -102,6 +103,7 @@ public: , mCredentialsMode(RequestCredentials::Omit) , mResponseTainting(RESPONSETAINT_BASIC) , mCacheMode(RequestCache::Default) + , mRedirectMode(RequestRedirect::Follow) , mAuthenticationFlag(false) , mForceOriginHeader(false) , mPreserveContentCodings(false) @@ -264,6 +266,18 @@ public: mCacheMode = aCacheMode; } + RequestRedirect + GetRedirectMode() const + { + return mRedirectMode; + } + + void + SetRedirectMode(RequestRedirect aRedirectMode) + { + mRedirectMode = aRedirectMode; + } + nsContentPolicyType ContentPolicyType() const { @@ -389,6 +403,7 @@ private: RequestCredentials mCredentialsMode; ResponseTainting mResponseTainting; RequestCache mCacheMode; + RequestRedirect mRedirectMode; bool mAuthenticationFlag; bool mForceOriginHeader; diff --git a/dom/fetch/InternalResponse.cpp b/dom/fetch/InternalResponse.cpp index 4ff6d8d5c9..f1d97f59de 100644 --- a/dom/fetch/InternalResponse.cpp +++ b/dom/fetch/InternalResponse.cpp @@ -92,7 +92,6 @@ InternalResponse::OpaqueResponse() nsRefPtr response = new InternalResponse(0, EmptyCString()); response->mType = ResponseType::Opaque; response->mTerminationReason = mTerminationReason; - response->mURL = mURL; response->mChannelInfo = mChannelInfo; if (mPrincipalInfo) { response->mPrincipalInfo = MakeUnique(*mPrincipalInfo); @@ -101,6 +100,16 @@ InternalResponse::OpaqueResponse() return response.forget(); } +already_AddRefed +InternalResponse::OpaqueRedirectResponse() +{ + MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueRedirectResponse a already wrapped response"); + nsRefPtr response = OpaqueResponse(); + response->mType = ResponseType::Opaqueredirect; + response->mURL = mURL; + return response.forget(); +} + already_AddRefed InternalResponse::CreateIncompleteCopy() { diff --git a/dom/fetch/InternalResponse.h b/dom/fetch/InternalResponse.h index 769a560d9c..e2db2ef308 100644 --- a/dom/fetch/InternalResponse.h +++ b/dom/fetch/InternalResponse.h @@ -48,6 +48,9 @@ public: already_AddRefed OpaqueResponse(); + already_AddRefed + OpaqueRedirectResponse(); + already_AddRefed BasicResponse(); @@ -62,6 +65,7 @@ public: MOZ_ASSERT_IF(mType == ResponseType::Basic, mWrappedResponse); MOZ_ASSERT_IF(mType == ResponseType::Cors, mWrappedResponse); MOZ_ASSERT_IF(mType == ResponseType::Opaque, mWrappedResponse); + MOZ_ASSERT_IF(mType == ResponseType::Opaqueredirect, mWrappedResponse); return mType; } @@ -90,12 +94,32 @@ public: return mStatus; } + uint16_t + GetUnfilteredStatus() const + { + if (mWrappedResponse) { + return mWrappedResponse->GetStatus(); + } + + return GetStatus(); + } + const nsCString& GetStatusText() const { return mStatusText; } + const nsCString& + GetUnfilteredStatusText() const + { + if (mWrappedResponse) { + return mWrappedResponse->GetStatusText(); + } + + return GetStatusText(); + } + InternalHeaders* Headers() { @@ -113,7 +137,7 @@ public: } void - GetInternalBody(nsIInputStream** aStream) + GetUnfilteredBody(nsIInputStream** aStream) { if (mWrappedResponse) { MOZ_ASSERT(!mBody); @@ -126,12 +150,13 @@ public: void GetBody(nsIInputStream** aStream) { - if (Type() == ResponseType::Opaque) { + if (Type() == ResponseType::Opaque || + Type() == ResponseType::Opaqueredirect) { *aStream = nullptr; return; } - return GetInternalBody(aStream); + return GetUnfilteredBody(aStream); } void diff --git a/dom/fetch/Request.cpp b/dom/fetch/Request.cpp index 0347ad90aa..ff1f28d095 100644 --- a/dom/fetch/Request.cpp +++ b/dom/fetch/Request.cpp @@ -239,6 +239,10 @@ Request::Constructor(const GlobalObject& aGlobal, request->SetCacheMode(cache); } + if (aInit.mRedirect.WasPassed()) { + request->SetRedirectMode(aInit.mRedirect.Value()); + } + // Request constructor step 14. if (aInit.mMethod.WasPassed()) { nsAutoCString method(aInit.mMethod.Value()); diff --git a/dom/fetch/Request.h b/dom/fetch/Request.h index 98bf3c303d..13fd154713 100644 --- a/dom/fetch/Request.h +++ b/dom/fetch/Request.h @@ -76,6 +76,12 @@ public: return mRequest->GetCacheMode(); } + RequestRedirect + Redirect() const + { + return mRequest->GetRedirectMode(); + } + RequestContext Context() const { diff --git a/dom/fetch/Response.cpp b/dom/fetch/Response.cpp index 95c8947d48..fa2fe13a90 100644 --- a/dom/fetch/Response.cpp +++ b/dom/fetch/Response.cpp @@ -156,6 +156,25 @@ Response::Constructor(const GlobalObject& aGlobal, nsRefPtr internalResponse = new InternalResponse(aInit.mStatus, statusText); + // Grab a valid channel info from the global so this response is 'valid' for + // interception. + if (NS_IsMainThread()) { + ChannelInfo info; + nsCOMPtr window = do_QueryInterface(global); + if (window) { + nsIDocument* doc = window->GetExtantDoc(); + MOZ_ASSERT(doc); + info.InitFromDocument(doc); + } else { + info.InitFromChromeGlobal(global); + } + internalResponse->InitChannelInfo(info); + } else { + workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + internalResponse->InitChannelInfo(worker->GetChannelInfo()); + } + nsRefPtr r = new Response(global, internalResponse); if (aInit.mHeaders.WasPassed()) { diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp index e9d4fde8ee..0343191186 100644 --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -52,7 +52,6 @@ #include "nsIContentPolicy.h" #include "nsContentPolicyUtils.h" -#include "nsCORSListenerProxy.h" #include "nsCycleCollectionParticipant.h" #include "nsICachingChannel.h" #include "nsLayoutUtils.h" diff --git a/dom/indexedDB/IDBFactory.cpp b/dom/indexedDB/IDBFactory.cpp index 715b781363..2e39d6711f 100644 --- a/dom/indexedDB/IDBFactory.cpp +++ b/dom/indexedDB/IDBFactory.cpp @@ -363,8 +363,13 @@ IDBFactory::AllowedForWindowInternal(nsPIDOMWindow* aWindow, return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } - nsIDocument* document = aWindow->GetExtantDoc(); - if (document->GetSandboxFlags() & SANDBOXED_ORIGIN) { + nsContentUtils::StorageAccess access = + nsContentUtils::StorageAllowedForWindow(aWindow); + + // the factory callsite records whether the browser is in private browsing. + // and thus we don't have to respect that setting here. IndexedDB has no + // concept of session-local storage, and thus ignores it. + if (access == nsContentUtils::StorageAccess::eDeny) { return NS_ERROR_DOM_SECURITY_ERR; } @@ -374,26 +379,21 @@ IDBFactory::AllowedForWindowInternal(nsPIDOMWindow* aWindow, nsCOMPtr principal = sop->GetPrincipal(); if (NS_WARN_IF(!principal)) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } - bool isSystemPrincipal; - if (!AllowedForPrincipal(principal, &isSystemPrincipal)) { - return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; - } - - if (isSystemPrincipal) { + if (nsContentUtils::IsSystemPrincipal(principal)) { principal.forget(aPrincipal); return NS_OK; } - // Whitelist about:home, since it doesn't have a base domain it would not - // pass the ThirdPartyUtil check, though it should be able to use indexedDB. - bool skipThirdPartyCheck = false; - + // About URIs shouldn't be able to access IndexedDB unless they have the + // nsIAboutModule::ENABLE_INDEXED_DB flag set on them. nsCOMPtr uri; MOZ_ALWAYS_TRUE(NS_SUCCEEDED(principal->GetURI(getter_AddRefs(uri)))); + MOZ_ASSERT(uri); - bool isAbout; + bool isAbout = false; MOZ_ALWAYS_TRUE(NS_SUCCEEDED(uri->SchemeIs("about", &isAbout))); if (isAbout) { @@ -401,29 +401,13 @@ IDBFactory::AllowedForWindowInternal(nsPIDOMWindow* aWindow, if (NS_SUCCEEDED(NS_GetAboutModule(uri, getter_AddRefs(module)))) { uint32_t flags; if (NS_SUCCEEDED(module->GetURIFlags(uri, &flags))) { - skipThirdPartyCheck = flags & nsIAboutModule::ENABLE_INDEXED_DB; + if (!(flags & nsIAboutModule::ENABLE_INDEXED_DB)) { + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + } } else { - NS_WARNING("GetURIFlags failed!"); + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; } } else { - NS_WARNING("NS_GetAboutModule failed!"); - } - } - - if (!skipThirdPartyCheck) { - nsCOMPtr thirdPartyUtil = - do_GetService(THIRDPARTYUTIL_CONTRACTID); - MOZ_ASSERT(thirdPartyUtil); - - bool isThirdParty; - if (NS_WARN_IF(NS_FAILED( - thirdPartyUtil->IsThirdPartyWindow(aWindow, - nullptr, - &isThirdParty)))) { - return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; - } - - if (isThirdParty) { return NS_ERROR_DOM_NOT_SUPPORTED_ERR; } } diff --git a/dom/indexedDB/test/test_third_party.html b/dom/indexedDB/test/test_third_party.html index a3670634e4..91ea44fc30 100644 --- a/dom/indexedDB/test/test_third_party.html +++ b/dom/indexedDB/test/test_third_party.html @@ -10,11 +10,31 @@ + + + + + + + diff --git a/dom/tests/mochitest/general/frameStorageChrome.html b/dom/tests/mochitest/general/frameStorageChrome.html new file mode 100644 index 0000000000..d1a82edd64 --- /dev/null +++ b/dom/tests/mochitest/general/frameStorageChrome.html @@ -0,0 +1,19 @@ + + +frame for storage allowed test + + + + + + + + + diff --git a/dom/tests/mochitest/general/frameStorageNullprincipal.sjs b/dom/tests/mochitest/general/frameStorageNullprincipal.sjs new file mode 100644 index 0000000000..4f58a296f6 --- /dev/null +++ b/dom/tests/mochitest/general/frameStorageNullprincipal.sjs @@ -0,0 +1,33 @@ +// This is a sjs file which reads in frameStoragePrevented.html, and writes it out as a data: URI, which this page redirects to. +// This produces a URI with the null principal, which should be unable to access storage. +// We append the #nullprincipal hash to the end of the data: URI to tell the script that it shouldn't try to spawn a webworker, +// as it won't be allowed to, as it has a null principal. + +function handleRequest(request, response) { + // Get the nsIFile for frameStoragePrevented.html + var file; + getObjectState("SERVER_ROOT", function(serverRoot) { + file = serverRoot.getFile("/tests/dom/tests/mochitest/general/frameStoragePrevented.html"); + }); + + // Set up the file streams to read in the file as UTF-8 + let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. + createInstance(Components.interfaces.nsIFileInputStream); + fstream.init(file, -1, 0, 0); + let cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Components.interfaces.nsIConverterInputStream); + cstream.init(fstream, "UTF-8", 0, 0); + + // Read in the file, and concatenate it onto the data string + let data = ""; + let str = {}; + let read = 0; + do { + read = cstream.readString(0xffffffff, str); + data += str.value; + } while (read != 0); + + // Write out the file as a data: URI, and redirect to it + response.setStatusLine('1.1', 302, 'Found'); + response.setHeader('Location', 'data:text/html,' + encodeURIComponent(data) + "#nullprincipal"); +} diff --git a/dom/tests/mochitest/general/frameStoragePrevented.html b/dom/tests/mochitest/general/frameStoragePrevented.html new file mode 100644 index 0000000000..b29cf2c9d0 --- /dev/null +++ b/dom/tests/mochitest/general/frameStoragePrevented.html @@ -0,0 +1,35 @@ + + + +frame for storage prevented test + + + + + + + + + diff --git a/dom/tests/mochitest/general/mochitest.ini b/dom/tests/mochitest/general/mochitest.ini index 097ca9aece..29b7618bd9 100644 --- a/dom/tests/mochitest/general/mochitest.ini +++ b/dom/tests/mochitest/general/mochitest.ini @@ -38,6 +38,13 @@ support-files = resource_timing.js navigation_timing.html test_bug1012662_common.js + frameStorageAllowed.html + frameStoragePrevented.html + frameStorageChrome.html + frameStorageNullprincipal.sjs + workerStorageAllowed.js + workerStoragePrevented.js + storagePermissionsUtils.js frameSelectEvents.html [test_497898.html] @@ -103,5 +110,13 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop spec skip-if = buildapp == 'b2g' || buildapp == 'mulet' [test_bug1012662_editor.html] [test_bug1012662_noeditor.html] +[test_storagePermissionsAccept.html] +skip-if = buildapp == 'b2g' # Bug 1184427 - no SSL certs on b2g +[test_storagePermissionsRejectForeign.html] +skip-if = buildapp == 'b2g' # Bug 1184427 - no SSL certs on b2g +[test_storagePermissionsReject.html] +skip-if = buildapp == 'b2g' # Bug 1184427 - no SSL certs on b2g +[test_storagePermissionsLimitForeign.html] +skip-if = buildapp == 'b2g' # Bug 1184427 - no SSL certs on b2g [test_selectevents.html] skip-if = buildapp == 'b2g' || buildapp == 'mulet' # Mouse doesn't select in the same way on b2g diff --git a/dom/tests/mochitest/general/storagePermissionsUtils.js b/dom/tests/mochitest/general/storagePermissionsUtils.js new file mode 100644 index 0000000000..d161a59852 --- /dev/null +++ b/dom/tests/mochitest/general/storagePermissionsUtils.js @@ -0,0 +1,232 @@ +const BEHAVIOR_ACCEPT = 0; +const BEHAVIOR_REJECT_FOREIGN = 1; +const BEHAVIOR_REJECT = 2; +const BEHAVIOR_LIMIT_FOREIGN = 3; + +const kPrefName = "network.cookie.cookieBehavior"; + +// Check if we are in frame, and declare ok and finishTest appropriately +const inFrame = ("" + location).match(/frame/); +if (inFrame) { + ok = function(a, message) { + if (!a) { + parent.postMessage("FAILURE: " + message, "http://mochi.test:8888"); + } else { + parent.postMessage(message, "http://mochi.test:8888"); + } + }; + + finishTest = function() { + parent.postMessage("done", "http://mochi.test:8888"); + }; +} else { + finishTest = function() { + SimpleTest.finish(); + }; +} + +function setCookieBehavior(behavior) { + return new Promise((resolve, reject) => { + SpecialPowers.pushPrefEnv({"set": [[kPrefName, behavior]]}, resolve); + }); +} + +function runIFrame(url) { + return new Promise((resolve, reject) => { + function onMessage(e) { + if (e.data == "done") { + resolve(); + window.removeEventListener('message', onMessage); + return; + } + + ok(!e.data.match(/^FAILURE/), e.data + " (IFRAME = " + url + ")"); + } + window.addEventListener('message', onMessage, false); + + document.querySelector('iframe').src = url; + }); +} + +function runWorker(url) { + return new Promise((resolve, reject) => { + var worker = new Worker(url); + worker.addEventListener('message', function(e) { + if (e.data == "done") { + resolve(); + return; + } + + ok(!e.data.match(/^FAILURE/), e.data + " (WORKER = " + url + ")"); + }); + }); +} + +function chromePower() { + try { + SpecialPowers.wrap(window).localStorage.getItem("X"); + ok(true, "getting localStorage didn't throw"); + } catch (e) { + ok(false, "getting localStorage should not throw"); + } + + try { + SpecialPowers.wrap(window).sessionStorage.getItem("X"); + ok(true, "getting sessionStorage didn't throw"); + } catch (e) { + ok(false, "getting sessionStorage should not throw"); + } + + try { + SpecialPowers.wrap(window).indexedDB; + ok(true, "getting indexedDB didn't throw"); + } catch (e) { + ok(false, "getting indexedDB should not throw"); + } + + try { + var promise = SpecialPowers.wrap(window).caches.keys(); + ok(true, "getting caches didn't throw"); + + return new Promise((resolve, reject) => { + promise.then(function() { + ok(location.protocol == "https:", "The promise was not rejected"); + resolve(); + }, function(e) { + ok(location.protocol != "https:", "The promise should not have been rejected: " + e); + resolve(); + }); + }); + } catch (e) { + ok(false, "getting caches should not have thrown"); + return Promise.resolve(); + } +} + +function storageAllowed() { + try { + localStorage.getItem("X"); + ok(true, "getting localStorage didn't throw"); + } catch (e) { + ok(false, "getting localStorage should not throw"); + } + + try { + sessionStorage.getItem("X"); + ok(true, "getting sessionStorage didn't throw"); + } catch (e) { + ok(false, "getting sessionStorage should not throw"); + } + + try { + indexedDB; + ok(true, "getting indexedDB didn't throw"); + } catch (e) { + ok(false, "getting indexedDB should not throw"); + } + + try { + var promise = caches.keys(); + ok(true, "getting caches didn't throw"); + + return new Promise((resolve, reject) => { + promise.then(function() { + ok(location.protocol == "https:", "The promise was not rejected"); + resolve(); + }, function() { + ok(location.protocol != "https:", "The promise should not have been rejected"); + resolve(); + }); + }); + } catch (e) { + ok(false, "getting caches should not have thrown"); + return Promise.resolve(); + } +} + +function storagePrevented() { + try { + localStorage.getItem("X"); + ok(false, "getting localStorage should have thrown"); + } catch (e) { + ok(true, "getting localStorage threw"); + } + + if (location.hash == "#thirdparty") { + // No matter what the user's preferences are, we don't block + // sessionStorage in 3rd-party iframes. We do block them everywhere + // else however. + try { + sessionStorage.getItem("X"); + ok(true, "getting sessionStorage didn't throw"); + } catch (e) { + ok(false, "getting sessionStorage should not have thrown"); + } + } else { + try { + sessionStorage.getItem("X"); + ok(false, "getting sessionStorage should have thrown"); + } catch (e) { + ok(true, "getting sessionStorage threw"); + } + } + + try { + indexedDB; + ok(false, "getting indexedDB should have thrown"); + } catch (e) { + ok(true, "getting indexedDB threw"); + } + + try { + var promise = caches.keys(); + ok(true, "getting caches didn't throw"); + + return new Promise((resolve, reject) => { + promise.then(function() { + ok(false, "The promise should have rejected"); + resolve(); + }, function() { + ok(true, "The promise was rejected"); + resolve(); + }); + }); + } catch (e) { + ok(false, "getting caches should not have thrown"); + + return Promise.resolve(); + } +} + +function task(fn) { + if (!inFrame) { + SimpleTest.waitForExplicitFinish(); + } + + var gen = fn(); + + function next_step(val, e) { + var it; + try { + if (typeof e !== "undefined") { + it = gen.throw(e); + } else { + it = gen.next(val); + } + } catch (e) { + ok(false, "An error was thrown while stepping: " + e); + ok(false, "Stack: " + e.stack); + finishTest(); + } + + if (it.done) { + finishTest(); + return; + } + it.value.then(next_step, (e) => next_step(null, e)); + } + + next_step(); +} + +var thirdparty = "https://example.com/tests/dom/tests/mochitest/general/"; diff --git a/dom/tests/mochitest/general/test_storagePermissionsAccept.html b/dom/tests/mochitest/general/test_storagePermissionsAccept.html new file mode 100644 index 0000000000..bbe34e9a0e --- /dev/null +++ b/dom/tests/mochitest/general/test_storagePermissionsAccept.html @@ -0,0 +1,42 @@ + + + Storage Permission Restrictions + + + + + + + + + + + diff --git a/dom/tests/mochitest/general/test_storagePermissionsLimitForeign.html b/dom/tests/mochitest/general/test_storagePermissionsLimitForeign.html new file mode 100644 index 0000000000..fa18b47e4a --- /dev/null +++ b/dom/tests/mochitest/general/test_storagePermissionsLimitForeign.html @@ -0,0 +1,42 @@ + + + Storage Permission Restrictions + + + + + + + + + + + diff --git a/dom/tests/mochitest/general/test_storagePermissionsReject.html b/dom/tests/mochitest/general/test_storagePermissionsReject.html new file mode 100644 index 0000000000..28495cdbb8 --- /dev/null +++ b/dom/tests/mochitest/general/test_storagePermissionsReject.html @@ -0,0 +1,41 @@ + + + Storage Permission Restrictions + + + + + + + + + + + diff --git a/dom/tests/mochitest/general/test_storagePermissionsRejectForeign.html b/dom/tests/mochitest/general/test_storagePermissionsRejectForeign.html new file mode 100644 index 0000000000..7088d89d2f --- /dev/null +++ b/dom/tests/mochitest/general/test_storagePermissionsRejectForeign.html @@ -0,0 +1,42 @@ + + + Storage Permission Restrictions + + + + + + + + + + + diff --git a/dom/tests/mochitest/general/workerStorageAllowed.js b/dom/tests/mochitest/general/workerStorageAllowed.js new file mode 100644 index 0000000000..da76a7a0e1 --- /dev/null +++ b/dom/tests/mochitest/general/workerStorageAllowed.js @@ -0,0 +1,61 @@ +// Unfortunately, workers can't share the code from storagePermissionsUtils. +// These are basic mechanisms for communicating to the test runner. + +function ok(condition, text) { + if (!condition) { + self.postMessage("FAILURE: " + text); + } else { + self.postMessage(text); + } +} + +function finishTest() { + self.postMessage("done"); + self.close(); +} + +// Workers don't have access to localstorage or sessionstorage +ok(typeof self.localStorage == "undefined", "localStorage should be undefined"); +ok(typeof self.sessionStorage == "undefined", "sessionStorage should be undefined"); + +// Make sure that we can access indexedDB +try { + indexedDB; + ok(true, "WORKER getting indexedDB didn't throw"); +} catch (e) { + ok(false, "WORKER getting indexedDB should not throw"); +} + +// Make sure that we can access caches +try { + var promise = caches.keys(); + ok(true, "WORKER getting caches didn't throw"); + + promise.then(function() { + ok(location.protocol == "https:", "WORKER The promise was not rejected"); + workerTest(); + }, function() { + ok(location.protocol != "https:", "WORKER The promise should not have been rejected"); + workerTest(); + }); +} catch (e) { + ok(false, "WORKER getting caches should not have thrown"); +} + +// Try to spawn an inner worker, and make sure that it can also access storage +function workerTest() { + if (location.hash == "#inner") { // Don't recurse infinitely, if we are the inner worker, don't spawn another + finishTest(); + return; + } + // Create the inner worker, and listen for test messages from it + var worker = new Worker("workerStorageAllowed.js#inner"); + worker.addEventListener('message', function(e) { + if (e.data == "done") { + finishTest(); + return; + } + + ok(!e.data.match(/^FAILURE/), e.data + " (WORKER = workerStorageAllowed.js#inner)"); + }); +} diff --git a/dom/tests/mochitest/general/workerStoragePrevented.js b/dom/tests/mochitest/general/workerStoragePrevented.js new file mode 100644 index 0000000000..93a81eb757 --- /dev/null +++ b/dom/tests/mochitest/general/workerStoragePrevented.js @@ -0,0 +1,61 @@ +// Unfortunately, workers can't share the code from storagePermissionsUtils. +// These are basic mechanisms for communicating to the test runner. + +function ok(condition, text) { + if (!condition) { + self.postMessage("FAILURE: " + text); + } else { + self.postMessage(text); + } +} + +function finishTest() { + self.postMessage("done"); + self.close(); +} + +// Workers don't have access to localstorage or sessionstorage +ok(typeof self.localStorage == "undefined", "localStorage should be undefined"); +ok(typeof self.sessionStorage == "undefined", "sessionStorage should be undefined"); + +// Make sure that we can't access indexedDB +try { + indexedDB; + ok(false, "WORKER getting indexedDB should have thrown"); +} catch (e) { + ok(true, "WORKER getting indexedDB threw"); +} + +// Make sure that we can't access caches +try { + var promise = caches.keys(); + ok(true, "WORKER getting caches didn't throw"); + + promise.then(function() { + ok(false, "WORKER The promise should have rejected"); + workerTest(); + }, function() { + ok(true, "WORKER The promise was rejected"); + workerTest(); + }); +} catch (e) { + ok(false, "WORKER getting caches should not have thrown"); +} + +// Try to spawn an inner worker, and make sure that it also can't access storage +function workerTest() { + if (location.hash == "#inner") { // Don't recurse infinitely, if we are the inner worker, don't spawn another + finishTest(); + return; + } + // Create the inner worker, and listen for test messages from it + var worker = new Worker("workerStoragePrevented.js#inner"); + worker.addEventListener('message', function(e) { + if (e.data == "done") { + finishTest(); + return; + } + + ok(!e.data.match(/^FAILURE/), e.data + " (WORKER = workerStoragePrevented.js#inner)"); + }); +} diff --git a/dom/webidl/PerformanceEntryEvent.webidl b/dom/webidl/PerformanceEntryEvent.webidl new file mode 100644 index 0000000000..2cf75c5e44 --- /dev/null +++ b/dom/webidl/PerformanceEntryEvent.webidl @@ -0,0 +1,27 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + */ + +dictionary PerformanceEntryEventInit : EventInit +{ + DOMString name = ""; + DOMString entryType = ""; + DOMHighResTimeStamp startTime = 0; + DOMHighResTimeStamp duration = 0; + double epoch = 0; + DOMString origin = ""; +}; + +[Constructor(DOMString type, optional PerformanceEntryEventInit eventInitDict), + ChromeOnly] +interface PerformanceEntryEvent : Event +{ + readonly attribute DOMString name; + readonly attribute DOMString entryType; + readonly attribute DOMHighResTimeStamp startTime; + readonly attribute DOMHighResTimeStamp duration; + readonly attribute double epoch; + readonly attribute DOMString origin; +}; diff --git a/dom/webidl/Request.webidl b/dom/webidl/Request.webidl index dfaf2c398c..5774946d36 100644 --- a/dom/webidl/Request.webidl +++ b/dom/webidl/Request.webidl @@ -23,6 +23,7 @@ interface Request { readonly attribute RequestMode mode; readonly attribute RequestCredentials credentials; readonly attribute RequestCache cache; + readonly attribute RequestRedirect redirect; [Throws, NewObject] Request clone(); @@ -40,6 +41,7 @@ dictionary RequestInit { RequestMode mode; RequestCredentials credentials; RequestCache cache; + RequestRedirect redirect; }; // Gecko currently does not ship RequestContext, so please don't use it in IDL @@ -61,3 +63,4 @@ enum RequestContext { enum RequestMode { "same-origin", "no-cors", "cors", "cors-with-forced-preflight" }; enum RequestCredentials { "omit", "same-origin", "include" }; enum RequestCache { "default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached" }; +enum RequestRedirect { "follow", "error", "manual" }; diff --git a/dom/webidl/Response.webidl b/dom/webidl/Response.webidl index 2ebc78bc4b..edc4a520d2 100644 --- a/dom/webidl/Response.webidl +++ b/dom/webidl/Response.webidl @@ -34,4 +34,4 @@ dictionary ResponseInit { HeadersInit headers; }; -enum ResponseType { "basic", "cors", "default", "error", "opaque" }; +enum ResponseType { "basic", "cors", "default", "error", "opaque", "opaqueredirect" }; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 17cce16241..f2f0cc26a0 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -777,6 +777,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [ 'MozStkCommandEvent.webidl', 'MozVoicemailEvent.webidl', 'PageTransitionEvent.webidl', + 'PerformanceEntryEvent.webidl', 'PluginCrashedEvent.webidl', 'PopStateEvent.webidl', 'PopupBlockedEvent.webidl', diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp index 21beedfe7d..efc60f7cda 100644 --- a/dom/workers/ScriptLoader.cpp +++ b/dom/workers/ScriptLoader.cpp @@ -1028,9 +1028,7 @@ private: mWorkerPrivate->SetBaseURI(finalURI); // Store the channel info if needed. - if (mWorkerPrivate->IsServiceWorker()) { - mWorkerPrivate->InitChannelInfo(channel); - } + mWorkerPrivate->InitChannelInfo(channel); // Now to figure out which principal to give this worker. WorkerPrivate* parent = mWorkerPrivate->GetParent(); diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp index 470b081724..3cfb4899c3 100644 --- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -130,7 +130,8 @@ public: return rv; } - mChannel->SynthesizeStatus(mInternalResponse->GetStatus(), mInternalResponse->GetStatusText()); + mChannel->SynthesizeStatus(mInternalResponse->GetUnfilteredStatus(), + mInternalResponse->GetUnfilteredStatusText()); nsAutoTArray entries; mInternalResponse->UnfilteredHeaders()->GetEntries(entries); @@ -148,18 +149,21 @@ class RespondWithHandler final : public PromiseNativeHandler { nsMainThreadPtrHandle mInterceptedChannel; nsMainThreadPtrHandle mServiceWorker; - RequestMode mRequestMode; - bool mIsClientRequest; + const RequestMode mRequestMode; + const bool mIsClientRequest; + const bool mIsNavigationRequest; public: NS_DECL_ISUPPORTS RespondWithHandler(nsMainThreadPtrHandle& aChannel, nsMainThreadPtrHandle& aServiceWorker, - RequestMode aRequestMode, bool aIsClientRequest) + RequestMode aRequestMode, bool aIsClientRequest, + bool aIsNavigationRequest) : mInterceptedChannel(aChannel) , mServiceWorker(aServiceWorker) , mRequestMode(aRequestMode) , mIsClientRequest(aIsClientRequest) + , mIsNavigationRequest(aIsNavigationRequest) { } @@ -263,12 +267,14 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle aValu return; } - // Section 4.2, step 2.2: + // Section "HTTP Fetch", step 2.2: // If one of the following conditions is true, return a network error: // * response's type is "error". // * request's mode is not "no-cors" and response's type is "opaque". // * request is a client request and response's type is neither "basic" // nor "default". + // * request is not a navigation request and response's type is + // "opaqueredirect". if (response->Type() == ResponseType::Error) { autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_ERROR_RESPONSE); @@ -280,12 +286,19 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle aValu return; } + // TODO: remove this case as its no longer in the spec (bug 1184967) if (mIsClientRequest && response->Type() != ResponseType::Basic && - response->Type() != ResponseType::Default) { + response->Type() != ResponseType::Default && + response->Type() != ResponseType::Opaqueredirect) { autoCancel.SetCancelStatus(NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION); return; } + if (!mIsNavigationRequest && response->Type() == ResponseType::Opaqueredirect) { + autoCancel.SetCancelStatus(NS_ERROR_BAD_OPAQUE_REDIRECT_INTERCEPTION); + return; + } + if (NS_WARN_IF(response->BodyUsed())) { autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_USED_RESPONSE); return; @@ -299,7 +312,7 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle aValu nsAutoPtr closure( new RespondWithClosure(mInterceptedChannel, ir, worker->GetChannelInfo())); nsCOMPtr body; - ir->GetInternalBody(getter_AddRefs(body)); + ir->GetUnfilteredBody(getter_AddRefs(body)); // Errors and redirects may not have a body. if (body) { response->SetBodyUsed(); @@ -358,7 +371,7 @@ FetchEvent::RespondWith(Promise& aArg, ErrorResult& aRv) mWaitToRespond = true; nsRefPtr handler = new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode(), - ir->IsClientRequest()); + ir->IsClientRequest(), ir->IsNavigationRequest()); aArg.AppendNativeHandler(handler); } diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index f4fd105976..a57e5f32b9 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -48,6 +48,7 @@ #include "mozilla/ipc/PBackgroundChild.h" #include "mozilla/ipc/PBackgroundSharedTypes.h" #include "mozilla/unused.h" +#include "mozilla/EnumSet.h" #include "nsContentUtils.h" #include "nsGlobalWindow.h" @@ -95,6 +96,15 @@ static_assert(nsIHttpChannelInternal::CORS_MODE_CORS == static_cast(Re static_assert(nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT == static_cast(RequestMode::Cors_with_forced_preflight), "RequestMode enumeration value should match Necko CORS mode value."); +static_assert(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW == static_cast(RequestRedirect::Follow), + "RequestRedirect enumeration value should make Necko Redirect mode value."); +static_assert(nsIHttpChannelInternal::REDIRECT_MODE_ERROR == static_cast(RequestRedirect::Error), + "RequestRedirect enumeration value should make Necko Redirect mode value."); +static_assert(nsIHttpChannelInternal::REDIRECT_MODE_MANUAL == static_cast(RequestRedirect::Manual), + "RequestRedirect enumeration value should make Necko Redirect mode value."); +static_assert(3 == static_cast(RequestRedirect::EndGuard_), + "RequestRedirect enumeration value should make Necko Redirect mode value."); + static StaticRefPtr gInstance; // Tracks the "dom.disable_open_click_delay" preference. Modified on main @@ -3620,7 +3630,9 @@ class FetchEventRunnable : public WorkerRunnable nsCString mSpec; nsCString mMethod; bool mIsReload; + DebugOnly mIsHttpChannel; RequestMode mRequestMode; + RequestRedirect mRequestRedirect; RequestCredentials mRequestCredentials; nsContentPolicyType mContentPolicyType; nsCOMPtr mUploadStream; @@ -3636,7 +3648,9 @@ public: , mServiceWorker(aServiceWorker) , mClientInfo(aClientInfo) , mIsReload(aIsReload) + , mIsHttpChannel(false) , mRequestMode(RequestMode::No_cors) + , mRequestRedirect(RequestRedirect::Follow) // By default we set it to same-origin since normal HTTP fetches always // send credentials to same-origin websites unless explicitly forbidden. , mRequestCredentials(RequestCredentials::Same_origin) @@ -3692,15 +3706,17 @@ public: nsCOMPtr httpChannel = do_QueryInterface(channel); if (httpChannel) { + mIsHttpChannel = true; + rv = httpChannel->GetRequestMethod(mMethod); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr internalChannel = do_QueryInterface(httpChannel); NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE); - uint32_t mode; - internalChannel->GetCorsMode(&mode); - switch (mode) { + uint32_t corsMode; + internalChannel->GetCorsMode(&corsMode); + switch (corsMode) { case nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN: mRequestMode = RequestMode::Same_origin; break; @@ -3715,6 +3731,11 @@ public: MOZ_CRASH("Unexpected CORS mode"); } + // This is safe due to static_asserts at top of file. + uint32_t redirectMode; + internalChannel->GetRedirectMode(&redirectMode); + mRequestRedirect = static_cast(redirectMode); + if (loadFlags & nsIRequest::LOAD_ANONYMOUS) { mRequestCredentials = RequestCredentials::Omit; } else { @@ -3806,6 +3827,7 @@ private: reqInit.mHeaders.Value().SetAsHeaders() = headers; reqInit.mMode.Construct(mRequestMode); + reqInit.mRedirect.Construct(mRequestRedirect); reqInit.mCredentials.Construct(mRequestCredentials); ErrorResult result; @@ -3823,6 +3845,11 @@ private: request->SetContentPolicyType(mContentPolicyType); + // TODO: remove conditional on http here once app protocol support is + // removed from service worker interception + MOZ_ASSERT_IF(mIsHttpChannel && internalReq->IsNavigationRequest(), + request->Redirect() == RequestRedirect::Manual); + RootedDictionary init(aCx); init.mRequest.Construct(); init.mRequest.Value() = request; @@ -3848,6 +3875,82 @@ private: } }; +namespace { + +class ContinueDispatchFetchEventRunnable : public nsRunnable +{ + WorkerPrivate* mWorkerPrivate; + nsMainThreadPtrHandle mChannel; + nsMainThreadPtrHandle mServiceWorker; + nsAutoPtr mClientInfo; + bool mIsReload; +public: + ContinueDispatchFetchEventRunnable(WorkerPrivate* aWorkerPrivate, + nsMainThreadPtrHandle& aChannel, + nsMainThreadPtrHandle& aServiceWorker, + nsAutoPtr& aClientInfo, + bool aIsReload) + : mWorkerPrivate(aWorkerPrivate) + , mChannel(aChannel) + , mServiceWorker(aServiceWorker) + , mClientInfo(aClientInfo) + , mIsReload(aIsReload) + { + } + + void + HandleError() + { + MOZ_ASSERT(NS_IsMainThread()); + NS_WARNING("Unexpected error while dispatching fetch event!"); + DebugOnly rv = mChannel->ResetInterception(); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to resume intercepted network request"); + } + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr channel; + nsresult rv = mChannel->GetChannel(getter_AddRefs(channel)); + if (NS_WARN_IF(NS_FAILED(rv))) { + HandleError(); + return NS_OK; + } + + // The channel might have encountered an unexpected error while ensuring + // the upload stream is cloneable. Check here and reset the interception + // if that happens. + nsresult status; + rv = channel->GetStatus(&status); + if (NS_WARN_IF(NS_FAILED(rv) || NS_FAILED(status))) { + HandleError(); + return NS_OK; + } + + nsRefPtr event = + new FetchEventRunnable(mWorkerPrivate, mChannel, mServiceWorker, + mClientInfo, mIsReload); + rv = event->Init(); + if (NS_WARN_IF(NS_FAILED(rv))) { + HandleError(); + return NS_OK; + } + + AutoJSAPI jsapi; + jsapi.Init(); + if (NS_WARN_IF(!event->Dispatch(jsapi.cx()))) { + HandleError(); + return NS_OK; + } + + return NS_OK; + } +}; + +} // anonymous namespace + NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable, nsIHttpHeaderVisitor) void @@ -3921,21 +4024,28 @@ ServiceWorkerManager::DispatchFetchEvent(const OriginAttributes& aOriginAttribut nsMainThreadPtrHandle serviceWorkerHandle( new nsMainThreadPtrHolder(sw)); - // clientInfo is null if we don't have a controlled document - nsRefPtr event = - new FetchEventRunnable(sw->GetWorkerPrivate(), handle, serviceWorkerHandle, - clientInfo, aIsReload); - aRv = event->Init(); + nsCOMPtr continueRunnable = + new ContinueDispatchFetchEventRunnable(sw->GetWorkerPrivate(), handle, + serviceWorkerHandle, clientInfo, + aIsReload); + + nsCOMPtr innerChannel; + aRv = aChannel->GetChannel(getter_AddRefs(innerChannel)); if (NS_WARN_IF(aRv.Failed())) { return; } - AutoJSAPI api; - api.Init(); - if (NS_WARN_IF(!event->Dispatch(api.cx()))) { - aRv.Throw(NS_ERROR_FAILURE); + nsCOMPtr uploadChannel = do_QueryInterface(innerChannel); + + // If there is no upload stream, then continue immediately + if (!uploadChannel) { + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(continueRunnable->Run())); return; } + + // Otherwise, ensure the upload stream can be cloned directly. This may + // require some async copying, so provide a callback. + aRv = uploadChannel->EnsureUploadStreamIsCloneable(continueRunnable); } bool @@ -4059,6 +4169,9 @@ ServiceWorkerManager::CreateServiceWorker(nsIPrincipal* aPrincipal, AssertIsOnMainThread(); MOZ_ASSERT(aPrincipal); + // Ensure that the IndexedDatabaseManager is initialized + NS_WARN_IF(!indexedDB::IndexedDatabaseManager::GetOrCreate()); + WorkerLoadInfo info; nsresult rv = NS_NewURI(getter_AddRefs(info.mBaseURI), aInfo->ScriptSpec(), nullptr, nullptr); @@ -4078,9 +4191,12 @@ ServiceWorkerManager::CreateServiceWorker(nsIPrincipal* aPrincipal, info.mPrincipal = aPrincipal; - info.mIndexedDBAllowed = - indexedDB::IDBFactory::AllowedForPrincipal(aPrincipal); - info.mPrivateBrowsing = false; + nsContentUtils::StorageAccess access = + nsContentUtils::StorageAllowedForPrincipal(aPrincipal); + info.mStorageAllowed = + access > nsContentUtils::StorageAccess::ePrivateBrowsing; + + info.mPrivateBrowsing = false; nsCOMPtr csp; rv = aPrincipal->GetCsp(getter_AddRefs(csp)); diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index e35b1642d4..0854ca060c 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -1674,7 +1674,7 @@ WorkerLoadInfo::WorkerLoadInfo() , mPrincipalIsSystem(false) , mIsInPrivilegedApp(false) , mIsInCertifiedApp(false) - , mIndexedDBAllowed(false) + , mStorageAllowed(false) , mPrivateBrowsing(true) , mServiceWorkersTestingInWindow(false) { @@ -1733,7 +1733,7 @@ WorkerLoadInfo::StealFrom(WorkerLoadInfo& aOther) mPrincipalIsSystem = aOther.mPrincipalIsSystem; mIsInPrivilegedApp = aOther.mIsInPrivilegedApp; mIsInCertifiedApp = aOther.mIsInCertifiedApp; - mIndexedDBAllowed = aOther.mIndexedDBAllowed; + mStorageAllowed = aOther.mStorageAllowed; mPrivateBrowsing = aOther.mPrivateBrowsing; mServiceWorkersTestingInWindow = aOther.mServiceWorkersTestingInWindow; } @@ -4242,13 +4242,16 @@ WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindow* aWindow, loadInfo.mDomain = aParent->Domain(); loadInfo.mFromWindow = aParent->IsFromWindow(); loadInfo.mWindowID = aParent->WindowID(); - loadInfo.mIndexedDBAllowed = aParent->IsIndexedDBAllowed(); + loadInfo.mStorageAllowed = aParent->IsStorageAllowed(); loadInfo.mPrivateBrowsing = aParent->IsInPrivateBrowsing(); loadInfo.mServiceWorkersTestingInWindow = aParent->ServiceWorkersTestingInWindow(); } else { AssertIsOnMainThread(); + // Make sure that the IndexedDatabaseManager is set up + NS_WARN_IF(!indexedDB::IndexedDatabaseManager::GetOrCreate()); + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); MOZ_ASSERT(ssm); @@ -4364,7 +4367,9 @@ WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindow* aWindow, loadInfo.mIsInCertifiedApp = (appStatus == nsIPrincipal::APP_STATUS_CERTIFIED); loadInfo.mFromWindow = true; loadInfo.mWindowID = globalWindow->WindowID(); - loadInfo.mIndexedDBAllowed = IDBFactory::AllowedForWindow(globalWindow); + nsContentUtils::StorageAccess access = + nsContentUtils::StorageAllowedForWindow(globalWindow); + loadInfo.mStorageAllowed = access > nsContentUtils::StorageAccess::eDeny; loadInfo.mPrivateBrowsing = nsContentUtils::IsInPrivateBrowsing(document); } else { // Not a window @@ -4406,7 +4411,7 @@ WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindow* aWindow, loadInfo.mXHRParamsAllowed = true; loadInfo.mFromWindow = false; loadInfo.mWindowID = UINT64_MAX; - loadInfo.mIndexedDBAllowed = true; + loadInfo.mStorageAllowed = true; loadInfo.mPrivateBrowsing = false; } diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index 8b03e1b5cd..f90b97b5fa 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -500,14 +500,12 @@ public: const ChannelInfo& GetChannelInfo() const { - MOZ_ASSERT(IsServiceWorker()); return mLoadInfo.mChannelInfo; } void SetChannelInfo(const ChannelInfo& aChannelInfo) { - MOZ_ASSERT(IsServiceWorker()); AssertIsOnMainThread(); MOZ_ASSERT(!mLoadInfo.mChannelInfo.IsInitialized()); MOZ_ASSERT(aChannelInfo.IsInitialized()); @@ -768,9 +766,9 @@ public: } bool - IsIndexedDBAllowed() const + IsStorageAllowed() const { - return mLoadInfo.mIndexedDBAllowed; + return mLoadInfo.mStorageAllowed; } bool diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index 460829ab11..28afa0a9ad 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -358,8 +358,9 @@ WorkerGlobalScope::GetIndexedDB(ErrorResult& aErrorResult) nsRefPtr indexedDB = mIndexedDB; if (!indexedDB) { - if (!mWorkerPrivate->IsIndexedDBAllowed()) { + if (!mWorkerPrivate->IsStorageAllowed()) { NS_WARNING("IndexedDB is not allowed in this worker!"); + aErrorResult = NS_ERROR_DOM_SECURITY_ERR; return nullptr; } diff --git a/dom/workers/Workers.h b/dom/workers/Workers.h index 19bb033838..cc72d48053 100644 --- a/dom/workers/Workers.h +++ b/dom/workers/Workers.h @@ -272,7 +272,7 @@ struct WorkerLoadInfo bool mPrincipalIsSystem; bool mIsInPrivilegedApp; bool mIsInCertifiedApp; - bool mIndexedDBAllowed; + bool mStorageAllowed; bool mPrivateBrowsing; bool mServiceWorkersTestingInWindow; diff --git a/dom/workers/test/serviceworkers/fetch/https/https_test.js b/dom/workers/test/serviceworkers/fetch/https/https_test.js index bb0aee13e5..6f87bb5ee1 100644 --- a/dom/workers/test/serviceworkers/fetch/https/https_test.js +++ b/dom/workers/test/serviceworkers/fetch/https/https_test.js @@ -1,12 +1,21 @@ self.addEventListener("install", function(event) { event.waitUntil(caches.open("cache").then(function(cache) { - return cache.add("index.html"); + var synth = new Response('', + {headers:{"Content-Type": "text/html"}}); + return Promise.all([ + cache.add("index.html"), + cache.put("synth-sw.html", synth), + ]); })); }); self.addEventListener("fetch", function(event) { if (event.request.url.indexOf("index.html") >= 0) { event.respondWith(caches.match(event.request)); + } else if (event.request.url.indexOf("synth-sw.html") >= 0) { + event.respondWith(caches.match(event.request)); + } else if (event.request.url.indexOf("synth-window.html") >= 0) { + event.respondWith(caches.match(event.request)); } else if (event.request.url.indexOf("synth.html") >= 0) { event.respondWith(new Response('', {headers:{"Content-Type": "text/html"}})); diff --git a/dom/workers/test/serviceworkers/fetch/https/register.html b/dom/workers/test/serviceworkers/fetch/https/register.html index 41774f70d1..fa666fe957 100644 --- a/dom/workers/test/serviceworkers/fetch/https/register.html +++ b/dom/workers/test/serviceworkers/fetch/https/register.html @@ -9,6 +9,12 @@ window.parent.postMessage({status: "registrationdone"}, "*"); } - navigator.serviceWorker.ready.then(done); + navigator.serviceWorker.ready.then(reg => { + return window.caches.open("cache").then(function(cache) { + var synth = new Response(' diff --git a/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js b/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js index ddbf02d4c6..cf9bdae973 100644 --- a/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js +++ b/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js @@ -4,7 +4,8 @@ self.addEventListener("install", function(event) { event.waitUntil( self.caches.open("origin-cache") .then(c => { - return c.add(prefix + 'index-https.sjs'); + return c.add(new Request(prefix + 'index-https.sjs', + { redirect: 'manual' })); }) ); }); diff --git a/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^ b/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^ deleted file mode 100644 index 5ed82fd065..0000000000 --- a/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^ +++ /dev/null @@ -1 +0,0 @@ -Access-Control-Allow-Origin: https://example.com diff --git a/dom/workers/test/serviceworkers/fetch/origin/origin_test.js b/dom/workers/test/serviceworkers/fetch/origin/origin_test.js index 7b5bac2282..a68dac0eca 100644 --- a/dom/workers/test/serviceworkers/fetch/origin/origin_test.js +++ b/dom/workers/test/serviceworkers/fetch/origin/origin_test.js @@ -6,8 +6,10 @@ self.addEventListener("install", function(event) { .then(c => { return Promise.all( [ - c.add(prefix + 'index.sjs'), - c.add(prefix + 'index-to-https.sjs'), + c.add(new Request(prefix + 'index.sjs', + { redirect: 'manual' } )), + c.add(new Request(prefix + 'index-to-https.sjs', + { redirect: 'manual' } )) ] ); }) diff --git a/dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^ b/dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^ deleted file mode 100644 index 3a6a85d894..0000000000 --- a/dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^ +++ /dev/null @@ -1 +0,0 @@ -Access-Control-Allow-Origin: http://mochi.test:8888 diff --git a/dom/workers/test/serviceworkers/mochitest.ini b/dom/workers/test/serviceworkers/mochitest.ini index aaa955898c..f3d928b421 100644 --- a/dom/workers/test/serviceworkers/mochitest.ini +++ b/dom/workers/test/serviceworkers/mochitest.ini @@ -54,13 +54,11 @@ support-files = fetch/origin/index.sjs fetch/origin/index-to-https.sjs fetch/origin/realindex.html - fetch/origin/realindex.html^headers^ fetch/origin/register.html fetch/origin/unregister.html fetch/origin/origin_test.js fetch/origin/https/index-https.sjs fetch/origin/https/realindex.html - fetch/origin/https/realindex.html^headers^ fetch/origin/https/register.html fetch/origin/https/unregister.html fetch/origin/https/origin_test.js diff --git a/dom/workers/test/serviceworkers/test_https_fetch.html b/dom/workers/test/serviceworkers/test_https_fetch.html index 5b0dc31b1b..01e18b03d2 100644 --- a/dom/workers/test/serviceworkers/test_https_fetch.html +++ b/dom/workers/test/serviceworkers/test_https_fetch.html @@ -31,6 +31,10 @@ ios.offline = true; iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/index.html"; } else if (e.data.status == "done") { + iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/synth-sw.html"; + } else if (e.data.status == "done-synth-sw") { + iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/synth-window.html"; + } else if (e.data.status == "done-synth-window") { iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/synth.html"; } else if (e.data.status == "done-synth") { ios.offline = false; diff --git a/intl/unicharutil/util/internal/Makefile.in b/intl/unicharutil/util/internal/Makefile.in deleted file mode 100644 index 04a39b16e0..0000000000 --- a/intl/unicharutil/util/internal/Makefile.in +++ /dev/null @@ -1,8 +0,0 @@ -# -# 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/. - -ifdef ENABLE_INTL_API -LOCAL_INCLUDES += $(MOZ_ICU_CFLAGS) -endif diff --git a/intl/unicharutil/util/internal/moz.build b/intl/unicharutil/util/internal/moz.build index 4ea25526d3..d8d3228de6 100644 --- a/intl/unicharutil/util/internal/moz.build +++ b/intl/unicharutil/util/internal/moz.build @@ -18,4 +18,7 @@ LOCAL_INCLUDES += [ '..', ] +if CONFIG['ENABLE_INTL_API']: + CXXFLAGS += CONFIG['MOZ_ICU_CFLAGS'] + DIST_INSTALL = True diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 1050db518c..77d788adca 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -65,26 +65,6 @@ jsconfig_TARGET := export include $(topsrcdir)/config/rules.mk -############################################# -# BEGIN ECMAScript Internationalization API -# - -# ICU headers need to be available whether we build with the complete -# Internationalization API or not - ICU stubs rely on them. - -LOCAL_INCLUDES += $(MOZ_ICU_CFLAGS) - -ifdef ENABLE_INTL_API -ifndef MOZ_NATIVE_ICU - -endif -endif - -# -# END ECMAScript Internationalization API -############################################# - - # check_vanilla_allocations.py is tailored to Linux, so only run it there. # That should be enough to catch any problems. check-vanilla-allocations: diff --git a/js/src/moz.build b/js/src/moz.build index 20296478c4..7303cbb669 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -35,7 +35,7 @@ for header in ('GCAPI.h', 'HeapAPI.h', 'RootingAPI.h', 'SliceBudget.h', 'TraceKi with Files('../public/' + header): BUG_COMPONENT = component_gc -for stlfile in ['jsarray.*', 'jsbool*', 'jsdate.*', 'jsnum.*', 'json.*', 'jsreflect.*', 'jsstr.*']: +for stlfile in ['jsarray.*', 'jsbool*', 'jsdate.*', 'jsnum.*', 'json.*', 'jsstr.*']: with Files(stlfile): BUG_COMPONENT = component_stl @@ -177,7 +177,6 @@ UNIFIED_SOURCES += [ 'gc/Nursery.cpp', 'gc/RootMarking.cpp', 'gc/Statistics.cpp', - 'gc/StoreBuffer.cpp', 'gc/Tracer.cpp', 'gc/Verifier.cpp', 'gc/Zone.cpp', @@ -344,9 +343,13 @@ UNIFIED_SOURCES += [ # from on Windows through a preprocessor define. # jsutil.cpp cannot be built in unified mode because it is needed for # check-vanilla-allocations. +# StoreBuffer.cpp cannot be built in unified because its template +# instantiations may or may not be needed depending on what it gets bundled +# with. SOURCES += [ 'builtin/RegExp.cpp', 'frontend/Parser.cpp', + 'gc/StoreBuffer.cpp', 'jsarray.cpp', 'jsatom.cpp', 'jsmath.cpp', @@ -623,6 +626,9 @@ if CONFIG['OS_ARCH'] == 'SunOS': 'socket', ] +CFLAGS += CONFIG['MOZ_ICU_CFLAGS'] +CXXFLAGS += CONFIG['MOZ_ICU_CFLAGS'] + if not CONFIG['GNU_CXX']: ALLOW_COMPILER_WARNINGS = True diff --git a/js/xpconnect/src/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp index 7372acefbd..fce9fc9ee8 100644 --- a/js/xpconnect/src/Sandbox.cpp +++ b/js/xpconnect/src/Sandbox.cpp @@ -30,6 +30,7 @@ #include "Crypto.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/BlobBinding.h" +#include "mozilla/dom/cache/CacheStorage.h" #include "mozilla/dom/CSSBinding.h" #include "mozilla/dom/indexedDB/IndexedDatabaseManager.h" #include "mozilla/dom/Fetch.h" @@ -897,6 +898,8 @@ xpc::GlobalProperties::Parse(JSContext* cx, JS::HandleObject obj) #endif } else if (!strcmp(name.ptr(), "fetch")) { fetch = true; + } else if (!strcmp(name.ptr(), "caches")) { + caches = true; } else { JS_ReportError(cx, "Unknown property name: %s", name.ptr()); return false; @@ -962,6 +965,9 @@ xpc::GlobalProperties::Define(JSContext* cx, JS::HandleObject obj) if (fetch && !SandboxCreateFetch(cx, obj)) return false; + if (caches && !dom::cache::CacheStorage::DefineCaches(cx, obj)) + return false; + return true; } diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index 626657f693..c38cd7ef0e 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -3422,6 +3422,7 @@ struct GlobalProperties { bool crypto : 1; bool rtcIdentityProvider : 1; bool fetch : 1; + bool caches : 1; }; // Infallible. diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index b85b676e0f..8d17392cae 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -175,6 +175,9 @@ pref("dom.enable_user_timing", true); // Enable printing performance marks/measures to log pref("dom.performance.enable_user_timing_logging", false); +// Enable notification of performance timing +pref("dom.performance.enable_notify_performance_timing", false); + // Whether the Gamepad API is enabled pref("dom.gamepad.enabled", true); #ifdef RELEASE_BUILD diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index 863d305a52..72d83f63da 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -90,7 +90,10 @@ HttpBaseChannel::HttpBaseChannel() , mForcePending(false) , mCorsIncludeCredentials(false) , mCorsMode(nsIHttpChannelInternal::CORS_MODE_NO_CORS) + , mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW) , mOnStartRequestCalled(false) + , mRequireCORSPreflight(false) + , mWithCredentials(false) { LOG(("Creating HttpBaseChannel @%x\n", this)); @@ -2162,6 +2165,20 @@ HttpBaseChannel::SetCorsMode(uint32_t aMode) return NS_OK; } +NS_IMETHODIMP +HttpBaseChannel::GetRedirectMode(uint32_t* aMode) +{ + *aMode = mRedirectMode; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetRedirectMode(uint32_t aMode) +{ + mRedirectMode = aMode; + return NS_OK; +} + //----------------------------------------------------------------------------- // HttpBaseChannel::nsISupportsPriority //----------------------------------------------------------------------------- @@ -2435,14 +2452,6 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, } } - // Preserve any skip-serviceworker-flag if possible. - if (mForceNoIntercept) { - nsCOMPtr httpChan = do_QueryInterface(newChannel); - if (httpChan) { - httpChan->ForceNoIntercept(); - } - } - // Propagate our loadinfo if needed. newChannel->SetLoadInfo(mLoadInfo); @@ -2542,6 +2551,17 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, "[this=%p] transferring chain of redirect cache-keys", this)); httpInternal->SetCacheKeysRedirectChain(mRedirectedCachekeys.forget()); } + + // Preserve any skip-serviceworker-flag. + if (mForceNoIntercept) { + httpInternal->ForceNoIntercept(); + } + + // Preserve CORS mode flag. + httpInternal->SetCorsMode(mCorsMode); + + // Preserve Redirect mode flag. + httpInternal->SetRedirectMode(mRedirectMode); } // transfer application cache information @@ -2940,6 +2960,20 @@ HttpBaseChannel::EnsureSchedulingContextID() return true; } +NS_IMETHODIMP +HttpBaseChannel::SetCorsPreflightParameters(const nsTArray& aUnsafeHeaders, + bool aWithCredentials, + nsIPrincipal* aPrincipal) +{ + ENSURE_CALLED_BEFORE_CONNECT(); + + mRequireCORSPreflight = true; + mUnsafeHeaders = aUnsafeHeaders; + mWithCredentials = aWithCredentials; + mPreflightPrincipal = aPrincipal; + return NS_OK; +} + } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index c605a21293..2b75f3f0c9 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -196,8 +196,13 @@ public: NS_IMETHOD SetCorsIncludeCredentials(bool aInclude) override; NS_IMETHOD GetCorsMode(uint32_t* aCorsMode) override; NS_IMETHOD SetCorsMode(uint32_t aCorsMode) override; + NS_IMETHOD GetRedirectMode(uint32_t* aRedirectMode) override; + NS_IMETHOD SetRedirectMode(uint32_t aRedirectMode) override; NS_IMETHOD GetTopWindowURI(nsIURI **aTopWindowURI) override; NS_IMETHOD GetProxyURI(nsIURI **proxyURI) override; + NS_IMETHOD SetCorsPreflightParameters(const nsTArray& unsafeHeaders, + bool aWithCredentials, + nsIPrincipal* aPrincipal) override; inline void CleanRedirectCacheChainIfNecessary() { @@ -440,6 +445,7 @@ protected: bool mCorsIncludeCredentials; uint32_t mCorsMode; + uint32_t mRedirectMode; // This parameter is used to ensure that we do not call OnStartRequest more // than once. @@ -450,6 +456,11 @@ protected: nsID mSchedulingContextID; bool EnsureSchedulingContextID(); + + bool mRequireCORSPreflight; + bool mWithCredentials; + nsTArray mUnsafeHeaders; + nsCOMPtr mPreflightPrincipal; }; // Share some code while working around C++'s absurd inability to handle casting diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp index b81334c711..7a06c81cdc 100644 --- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -496,9 +496,6 @@ HttpChannelChild::DoOnStartRequest(nsIRequest* aRequest, nsISupports* aContext) return; } - if (mResponseHead) - SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie)); - if (mDivertingToParent) { mListener = nullptr; mListenerContext = nullptr; @@ -785,6 +782,8 @@ HttpChannelChild::OnStopRequest(const nsresult& channelStatus, DoOnStopRequest(this, mListenerContext); } + ReleaseListeners(); + if (mLoadFlags & LOAD_DOCUMENT_URI) { // Keep IPDL channel open, but only for updating security info. mKeptAlive = true; @@ -1033,17 +1032,19 @@ class Redirect1Event : public ChannelEvent const uint32_t& newChannelId, const URIParams& newURI, const uint32_t& redirectFlags, - const nsHttpResponseHead& responseHead) + const nsHttpResponseHead& responseHead, + const nsACString& securityInfoSerialization) : mChild(child) , mNewChannelId(newChannelId) , mNewURI(newURI) , mRedirectFlags(redirectFlags) - , mResponseHead(responseHead) {} + , mResponseHead(responseHead) + , mSecurityInfoSerialization(securityInfoSerialization) {} void Run() { mChild->Redirect1Begin(mNewChannelId, mNewURI, mRedirectFlags, - mResponseHead); + mResponseHead, mSecurityInfoSerialization); } private: HttpChannelChild* mChild; @@ -1051,20 +1052,25 @@ class Redirect1Event : public ChannelEvent URIParams mNewURI; uint32_t mRedirectFlags; nsHttpResponseHead mResponseHead; + nsCString mSecurityInfoSerialization; }; bool HttpChannelChild::RecvRedirect1Begin(const uint32_t& newChannelId, const URIParams& newUri, const uint32_t& redirectFlags, - const nsHttpResponseHead& responseHead) + const nsHttpResponseHead& responseHead, + const nsCString& securityInfoSerialization) { + // TODO: handle security info LOG(("HttpChannelChild::RecvRedirect1Begin [this=%p]\n", this)); if (mEventQ->ShouldEnqueue()) { mEventQ->Enqueue(new Redirect1Event(this, newChannelId, newUri, - redirectFlags, responseHead)); + redirectFlags, responseHead, + securityInfoSerialization)); } else { - Redirect1Begin(newChannelId, newUri, redirectFlags, responseHead); + Redirect1Begin(newChannelId, newUri, redirectFlags, responseHead, + securityInfoSerialization); } return true; } @@ -1073,7 +1079,8 @@ void HttpChannelChild::Redirect1Begin(const uint32_t& newChannelId, const URIParams& newUri, const uint32_t& redirectFlags, - const nsHttpResponseHead& responseHead) + const nsHttpResponseHead& responseHead, + const nsACString& securityInfoSerialization) { LOG(("HttpChannelChild::Redirect1Begin [this=%p]\n", this)); @@ -1105,7 +1112,11 @@ HttpChannelChild::Redirect1Begin(const uint32_t& newChannelId, // We won't get OnStartRequest, set cookies here. mResponseHead = new nsHttpResponseHead(responseHead); - SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie)); + + if (!securityInfoSerialization.IsEmpty()) { + NS_DeserializeObject(securityInfoSerialization, + getter_AddRefs(mSecurityInfo)); + } bool rewriteToGET = HttpBaseChannel::ShouldRewriteRedirectToGET(mResponseHead->Status(), mRequestHead.ParsedMethod()); @@ -1252,6 +1263,11 @@ HttpChannelChild::Redirect3Complete() // Release ref to new channel. mRedirectChannelChild = nullptr; + + if (mInterceptListener) { + mInterceptListener->Cleanup(); + mInterceptListener = nullptr; + } } //----------------------------------------------------------------------------- @@ -2234,8 +2250,7 @@ HttpChannelChild::OverrideWithSynthesizedResponse(nsAutoPtr& mResponseHead = aResponseHead; mSynthesizedResponse = true; - uint16_t status = mResponseHead->Status(); - if (status != 200 && status != 404) { + if (WillRedirect(mResponseHead)) { // Continue with the original cross-process request nsresult rv = ContinueAsyncOpen(); NS_ENSURE_SUCCESS_VOID(rv); diff --git a/netwerk/protocol/http/HttpChannelChild.h b/netwerk/protocol/http/HttpChannelChild.h index 770a3bafed..79406a91c6 100644 --- a/netwerk/protocol/http/HttpChannelChild.h +++ b/netwerk/protocol/http/HttpChannelChild.h @@ -136,7 +136,8 @@ protected: bool RecvRedirect1Begin(const uint32_t& newChannel, const URIParams& newURI, const uint32_t& redirectFlags, - const nsHttpResponseHead& responseHead) override; + const nsHttpResponseHead& responseHead, + const nsCString& securityInfoSerialization) override; bool RecvRedirect3Complete() override; bool RecvAssociateApplicationCache(const nsCString& groupID, const nsCString& clientID) override; @@ -240,7 +241,8 @@ private: void Redirect1Begin(const uint32_t& newChannelId, const URIParams& newUri, const uint32_t& redirectFlags, - const nsHttpResponseHead& responseHead); + const nsHttpResponseHead& responseHead, + const nsACString& securityInfoSerialization); void Redirect3Complete(); void DeleteSelf(); diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp index 54c3de583b..7ecdc3bb27 100644 --- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -399,6 +399,7 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI, if (aSynthesizedResponseHead.type() == OptionalHttpResponseHead::TnsHttpResponseHead) { mSynthesizedResponseHead = new nsHttpResponseHead(aSynthesizedResponseHead.get_nsHttpResponseHead()); mShouldIntercept = true; + mChannel->SetCouldBeSynthesized(); if (!aSecurityInfoSerialization.IsEmpty()) { nsCOMPtr secInfo; @@ -847,14 +848,7 @@ HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) chan->GetStatus(&channelStatus); nsCString secInfoSerialization; - nsCOMPtr secInfoSupp; - chan->GetSecurityInfo(getter_AddRefs(secInfoSupp)); - if (secInfoSupp) { - mAssociatedContentSecurity = do_QueryInterface(secInfoSupp); - nsCOMPtr secInfoSer = do_QueryInterface(secInfoSupp); - if (secInfoSer) - NS_SerializeToString(secInfoSer, secInfoSerialization); - } + UpdateAndSerializeSecurityInfo(secInfoSerialization); uint16_t redirectCount = 0; mChannel->GetRedirectCount(&redirectCount); @@ -1059,10 +1053,14 @@ HttpChannelParent::StartRedirect(uint32_t newChannelId, URIParams uriParams; SerializeURI(newURI, uriParams); + nsCString secInfoSerialization; + UpdateAndSerializeSecurityInfo(secInfoSerialization); + nsHttpResponseHead *responseHead = mChannel->GetResponseHead(); bool result = SendRedirect1Begin(newChannelId, uriParams, redirectFlags, responseHead ? *responseHead - : nsHttpResponseHead()); + : nsHttpResponseHead(), + secInfoSerialization); if (!result) { // Bug 621446 investigation mSentRedirect1BeginFailed = true; @@ -1337,6 +1335,20 @@ HttpChannelParent::GetAuthPrompt(uint32_t aPromptReason, const nsIID& iid, return NS_OK; } +void +HttpChannelParent::UpdateAndSerializeSecurityInfo(nsACString& aSerializedSecurityInfoOut) +{ + nsCOMPtr secInfoSupp; + mChannel->GetSecurityInfo(getter_AddRefs(secInfoSupp)); + if (secInfoSupp) { + mAssociatedContentSecurity = do_QueryInterface(secInfoSupp); + nsCOMPtr secInfoSer = do_QueryInterface(secInfoSupp); + if (secInfoSer) { + NS_SerializeToString(secInfoSer, aSerializedSecurityInfoOut); + } + } +} + //----------------------------------------------------------------------------- // HttpChannelSecurityWarningReporter //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h index ae8b62c910..5cb9060dcb 100644 --- a/netwerk/protocol/http/HttpChannelParent.h +++ b/netwerk/protocol/http/HttpChannelParent.h @@ -159,6 +159,8 @@ protected: const nsAString& aMessageCategory) override; private: + void UpdateAndSerializeSecurityInfo(nsACString& aSerializedSecurityInfoOut); + nsRefPtr mChannel; nsCOMPtr mCacheEntry; nsCOMPtr mAssociatedContentSecurity; diff --git a/netwerk/protocol/http/PHttpChannel.ipdl b/netwerk/protocol/http/PHttpChannel.ipdl index b7718aa12b..4ead186b28 100644 --- a/netwerk/protocol/http/PHttpChannel.ipdl +++ b/netwerk/protocol/http/PHttpChannel.ipdl @@ -121,7 +121,8 @@ child: Redirect1Begin(uint32_t newChannelId, URIParams newUri, uint32_t redirectFlags, - nsHttpResponseHead responseHead); + nsHttpResponseHead responseHead, + nsCString securityInfoSerialization); // Called if redirect successful so that child can complete setup. Redirect3Complete(); diff --git a/netwerk/protocol/http/moz.build b/netwerk/protocol/http/moz.build index 2b1ef5c45e..01e6950d5a 100644 --- a/netwerk/protocol/http/moz.build +++ b/netwerk/protocol/http/moz.build @@ -21,6 +21,7 @@ XPIDL_SOURCES += [ XPIDL_MODULE = 'necko_http' EXPORTS += [ + 'nsCORSListenerProxy.h', 'nsHttp.h', 'nsHttpAtomList.h', 'nsHttpHeaderArray.h', @@ -61,6 +62,7 @@ UNIFIED_SOURCES += [ 'HttpChannelParentListener.cpp', 'HttpInfo.cpp', 'InterceptedChannel.cpp', + 'nsCORSListenerProxy.cpp', 'nsHttp.cpp', 'nsHttpActivityDistributor.cpp', 'nsHttpAuthManager.cpp', diff --git a/dom/security/nsCORSListenerProxy.cpp b/netwerk/protocol/http/nsCORSListenerProxy.cpp similarity index 98% rename from dom/security/nsCORSListenerProxy.cpp rename to netwerk/protocol/http/nsCORSListenerProxy.cpp index 587e6f2f98..e4e4d750ee 100644 --- a/dom/security/nsCORSListenerProxy.cpp +++ b/netwerk/protocol/http/nsCORSListenerProxy.cpp @@ -411,7 +411,8 @@ nsPreflightCache::GetCacheKey(nsIURI* aURI, NS_IMPL_ISUPPORTS(nsCORSListenerProxy, nsIStreamListener, nsIRequestObserver, nsIChannelEventSink, - nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback) + nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback, + nsIThreadRetargetableStreamListener) /* static */ void @@ -674,11 +675,15 @@ nsCORSListenerProxy::OnStopRequest(nsIRequest* aRequest, NS_IMETHODIMP nsCORSListenerProxy::OnDataAvailable(nsIRequest* aRequest, - nsISupports* aContext, + nsISupports* aContext, nsIInputStream* aInputStream, uint64_t aOffset, uint32_t aCount) { + // NB: This can be called on any thread! But we're guaranteed that it is + // called between OnStartRequest and OnStopRequest, so we don't need to worry + // about races. + MOZ_ASSERT(mInited, "nsCORSListenerProxy has not been initialized properly"); if (!mRequestApproved) { return NS_ERROR_DOM_BAD_URI; @@ -825,6 +830,19 @@ nsCORSListenerProxy::OnRedirectVerifyCallback(nsresult result) return NS_OK; } +NS_IMETHODIMP +nsCORSListenerProxy::CheckListenerChain() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (nsCOMPtr retargetableListener = + do_QueryInterface(mOuterListener)) { + return retargetableListener->CheckListenerChain(); + } + + return NS_ERROR_NO_INTERFACE; +} + // Please note that the CSP directive 'upgrade-insecure-requests' relies // on the promise that channels get updated from http: to https: before // the channel fetches any data from the netwerk. Such channels should @@ -1247,7 +1265,6 @@ nsCORSPreflightListener::GetInterface(const nsIID & aIID, void **aResult) return QueryInterface(aIID, aResult); } - nsresult NS_StartCORSPreflight(nsIChannel* aRequestChannel, nsIStreamListener* aListener, diff --git a/dom/security/nsCORSListenerProxy.h b/netwerk/protocol/http/nsCORSListenerProxy.h similarity index 95% rename from dom/security/nsCORSListenerProxy.h rename to netwerk/protocol/http/nsCORSListenerProxy.h index 4ae5ef781e..76a79619f2 100644 --- a/dom/security/nsCORSListenerProxy.h +++ b/netwerk/protocol/http/nsCORSListenerProxy.h @@ -16,6 +16,7 @@ #include "nsIInterfaceRequestor.h" #include "nsIChannelEventSink.h" #include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIThreadRetargetableStreamListener.h" #include "mozilla/Attributes.h" class nsIURI; @@ -39,7 +40,8 @@ enum class DataURIHandling class nsCORSListenerProxy final : public nsIStreamListener, public nsIInterfaceRequestor, public nsIChannelEventSink, - public nsIAsyncVerifyRedirectCallback + public nsIAsyncVerifyRedirectCallback, + public nsIThreadRetargetableStreamListener { public: nsCORSListenerProxy(nsIStreamListener* aOuter, @@ -57,6 +59,7 @@ public: NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSICHANNELEVENTSINK NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER // Must be called at startup. static void Startup(); diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 5eb2da6d7b..e8932b1dab 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -4981,7 +4981,7 @@ nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context) if (ShouldIntercept()) { mInterceptCache = MAYBE_INTERCEPT; - mResponseCouldBeSynthesized = true; + SetCouldBeSynthesized(); } // Remember the cookie header that was set, if any @@ -6926,5 +6926,11 @@ nsHttpChannel::OnPush(const nsACString &url, Http2PushedStream *pushedStream) return rv; } +void +nsHttpChannel::SetCouldBeSynthesized() +{ + mResponseCouldBeSynthesized = true; +} + } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index 349cf58286..d7460a65da 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -235,6 +235,7 @@ public: /* internal necko use only */ void MarkIntercepted(); NS_IMETHOD GetResponseSynthesized(bool* aSynthesized) override; bool AwaitingCacheCallbacks(); + void SetCouldBeSynthesized(); protected: virtual ~nsHttpChannel(); diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl index da3dca3f0b..3f93024e5b 100644 --- a/netwerk/protocol/http/nsIHttpChannelInternal.idl +++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl @@ -11,6 +11,7 @@ template class nsCOMArray; class nsCString; %} [ptr] native StringArray(nsTArray); +[ref] native StringArrayRef(const nsTArray); [ref] native securityMessagesArray(nsCOMArray); interface nsIAsyncInputStream; @@ -25,7 +26,7 @@ interface nsIURI; * The callback interface for nsIHttpChannelInternal::HTTPUpgrade() */ -[scriptable, uuid(7b48d081-1dc1-4d08-b7a5-81491bf28c0e)] +[scriptable, uuid(5b515449-ab64-4dba-b3cd-da8fc2f83064)] interface nsIHttpUpgradeListener : nsISupports { void onTransportAvailable(in nsISocketTransport aTransport, @@ -38,7 +39,7 @@ interface nsIHttpUpgradeListener : nsISupports * using any feature exposed by this interface, be aware that this interface * will change and you will be broken. You have been warned. */ -[scriptable, uuid(c4fab96f-0b10-4b14-b45b-517fc3f36842)] +[scriptable, uuid(46ef729f-4c9b-4084-b9e2-498992a31aee)] interface nsIHttpChannelInternal : nsISupports { @@ -239,6 +240,16 @@ interface nsIHttpChannelInternal : nsISupports */ attribute unsigned long corsMode; + const unsigned long REDIRECT_MODE_FOLLOW = 0; + const unsigned long REDIRECT_MODE_ERROR = 1; + const unsigned long REDIRECT_MODE_MANUAL = 2; + /** + * Set to indicate Request.redirect mode exposed during ServiceWorker + * interception. No policy enforcement is performed by the channel for this + * value. + */ + attribute unsigned long redirectMode; + /** * The URI of the top-level window that's associated with this channel. */ @@ -254,4 +265,13 @@ interface nsIHttpChannelInternal : nsISupports * proxies for this channel. */ readonly attribute nsIURI proxyURI; + + /** + * Make cross-origin CORS loads happen with a CORS preflight, and specify + * the CORS preflight parameters. + */ + [noscript] + void setCorsPreflightParameters(in StringArrayRef unsafeHeaders, + in boolean withCredentials, + in nsIPrincipal preflightPrincipal); }; diff --git a/testing/web-platform/mozilla/meta/MANIFEST.json b/testing/web-platform/mozilla/meta/MANIFEST.json index 9f0d11e7bc..4e676dbaa7 100644 --- a/testing/web-platform/mozilla/meta/MANIFEST.json +++ b/testing/web-platform/mozilla/meta/MANIFEST.json @@ -142,6 +142,12 @@ "url": "/_mozilla/service-workers/service-worker/fetch-event-network-error.https.html" } ], + "service-workers/service-worker/fetch-event-redirect.https.html": [ + { + "path": "service-workers/service-worker/fetch-event-redirect.https.html", + "url": "/_mozilla/service-workers/service-worker/fetch-event-redirect.https.html" + } + ], "service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html": [ { "path": "service-workers/service-worker/fetch-event-respond-with-stops-propagation.https.html", diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event-redirect.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event-redirect.https.html new file mode 100644 index 0000000000..a6eb4bb491 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event-redirect.https.html @@ -0,0 +1,997 @@ + +Service Worker: Fetch Event Redirect Handling + + + + + + + + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-redirect-iframe.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-redirect-iframe.html new file mode 100644 index 0000000000..f5a51bd591 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-redirect-iframe.html @@ -0,0 +1,13 @@ + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js index b6fdc27705..1b1c8192fc 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-rewrite-worker.js @@ -12,23 +12,18 @@ function get_query_params(url) { return ret; } -function get_request_init(params) { +function get_request_init(base, params) { var init = {}; - if (params['method']) { - init['method'] = params['method']; - } - if (params['mode']) { - init['mode'] = params['mode']; - } - if (params['credentials']) { - init['credentials'] = params['credentials']; - } + init['method'] = params['method'] || base['method']; + init['mode'] = params['mode'] || base['mode']; + init['credentials'] = params['credentials'] || base['credentials']; + init['redirect'] = params['redirect'] || base['redirect']; return init; } self.addEventListener('fetch', function(event) { var params = get_query_params(event.request.url); - var init = get_request_init(params); + var init = get_request_init(event.request, params); var url = params['url']; if (params['ignore']) { return; @@ -62,6 +57,19 @@ self.addEventListener('fetch', function(event) { if (url) { request = new Request(url, init); } - fetch(request).then(resolve, reject); + fetch(request).then(function(response) { + var expectedType = params['expected_type']; + if (expectedType && response.type !== expectedType) { + // Resolve a JSON object with a failure instead of rejecting + // in order to distinguish this from a NetworkError, which + // may be expected even if the type is correct. + resolve(new Response(JSON.stringify({ + result: 'failure', + detail: 'got ' + response.type + ' Response.type instead of ' + + expectedType + }))); + } + resolve(response); + }, reject) })); }); diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/get-host-info.sub.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/get-host-info.sub.js index 2bc2af2272..b64334df66 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/get-host-info.sub.js +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/get-host-info.sub.js @@ -17,8 +17,10 @@ function get_host_info() { return { HTTP_ORIGIN: 'http://' + ORIGINAL_HOST + ':' + HTTP_PORT, HTTPS_ORIGIN: 'https://' + ORIGINAL_HOST + ':' + HTTPS_PORT, + HTTPS_ORIGIN_WITH_CREDS: 'https://foo:bar@' + ORIGINAL_HOST + ':' + HTTPS_PORT, HTTP_REMOTE_ORIGIN: 'http://' + REMOTE_HOST + ':' + HTTP_PORT, HTTPS_REMOTE_ORIGIN: 'https://' + REMOTE_HOST + ':' + HTTPS_PORT, + HTTPS_REMOTE_ORIGIN_WITH_CREDS: 'https://foo:bar@' + REMOTE_HOST + ':' + HTTPS_PORT, UNAUTHENTICATED_ORIGIN: 'http://' + UNAUTHENTICATED_HOST + ':' + HTTP_PORT }; } diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/success.py b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/success.py new file mode 100644 index 0000000000..bcbb487d2b --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/success.py @@ -0,0 +1,8 @@ +def main(request, response): + headers = [] + + if "ACAOrigin" in request.GET: + for item in request.GET["ACAOrigin"].split(","): + headers.append(("Access-Control-Allow-Origin", item)) + + return headers, "{ \"result\": \"success\" }" diff --git a/toolkit/devtools/server/actors/performance-entries.js b/toolkit/devtools/server/actors/performance-entries.js new file mode 100644 index 0000000000..f02c431bee --- /dev/null +++ b/toolkit/devtools/server/actors/performance-entries.js @@ -0,0 +1,83 @@ +/* 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/. */ + +/** + * The performanceEntries actor emits events corresponding to performance + * entries. It receives `performanceentry` events containing the performance + * entry details and emits an event containing the name, type, origin, and + * epoch of the performance entry. + */ + +const { + method, Arg, Option, RetVal, Front, FrontClass, Actor, ActorClass +} = require("devtools/server/protocol"); +const events = require("sdk/event/core"); + +let PerformanceEntriesActor = exports.PerformanceEntriesActor = ActorClass({ + + typeName: "performanceEntries", + + listenerAdded: false, + + events: { + "entry" : { + type: "entry", + detail: Arg(0, "json") // object containing performance entry name, type, + // origin, and epoch. + } + }, + + initialize: function(conn, tabActor) { + Actor.prototype.initialize.call(this, conn); + this.window = tabActor.window; + }, + + /** + * Start tracking the user timings. + */ + start: method(function() { + if (!this.listenerAdded) { + this.onPerformanceEntry = this.onPerformanceEntry.bind(this); + this.window.addEventListener("performanceentry", this.onPerformanceEntry, true); + this.listenerAdded = true; + } + }), + + /** + * Stop tracking the user timings. + */ + stop: method(function() { + if (this.listenerAdded) { + this.window.removeEventListener("performanceentry", this.onPerformanceEntry, true); + this.listenerAdded = false; + } + }), + + disconnect: function() { + this.destroy(); + }, + + destroy: function() { + this.stop(); + Actor.prototype.destroy.call(this); + }, + + onPerformanceEntry: function (e) { + let emitDetail = { + type: e.entryType, + name: e.name, + origin: e.origin, + epoch: e.epoch + }; + events.emit(this, 'entry', emitDetail); + } +}); + +exports.PerformanceEntriesFront = FrontClass(PerformanceEntriesActor, { + initialize: function(client, form) { + Front.prototype.initialize.call(this, client); + this.actorID = form.performanceEntriesActor; + this.manage(this); + }, +}); diff --git a/toolkit/devtools/server/main.js b/toolkit/devtools/server/main.js index da474bea25..0f6d9a04e8 100644 --- a/toolkit/devtools/server/main.js +++ b/toolkit/devtools/server/main.js @@ -554,6 +554,11 @@ var DebuggerServer = { constructor: "PromisesActor", type: { global: true, tab: true } }); + this.registerModule("devtools/server/actors/performance-entries", { + prefix: "performanceEntries", + constructor: "PerformanceEntriesActor", + type: { tab: true } + }); }, /** diff --git a/toolkit/devtools/server/moz.build b/toolkit/devtools/server/moz.build index 07950b75f0..f222519fb5 100644 --- a/toolkit/devtools/server/moz.build +++ b/toolkit/devtools/server/moz.build @@ -75,6 +75,7 @@ EXTRA_JS_MODULES.devtools.server.actors += [ 'actors/memprof.js', 'actors/monitor.js', 'actors/object.js', + 'actors/performance-entries.js', 'actors/preference.js', 'actors/pretty-print-worker.js', 'actors/profiler.js', diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h index 1e075874bd..5374575a70 100644 --- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -335,6 +335,8 @@ ERROR(NS_ERROR_INTERCEPTED_USED_RESPONSE, FAILURE(104)), /* Service worker intercepted a client request with an opaque response */ ERROR(NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION, FAILURE(105)), + /* Service worker intercepted a non-navigation with an opaque redirect */ + ERROR(NS_ERROR_BAD_OPAQUE_REDIRECT_INTERCEPTION, FAILURE(106)), #undef MODULE