From 393bc6639f69dfe4ef1ac3002b95149b98a2c847 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Thu, 16 Mar 2023 14:16:15 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1209162 - Create OriginAttributes subtypes. IGNORE IDL r=sicking. (c2cbe04ef3) - Bug 1220570 - Potential cookie lost while downgrading from Aurora 44 to 43. r=jduell (1a0111c842) - Bug 1217456: Add a security flag for controlling redirects. Use this flag in fetch() implementation. r=bkelly,jduell (79d449e479) - Bug 1112040 - Add a mochitest. r=bholley (566a05f720) - Bug 1171215 - Compute third-partyness in the loadinfo instead of nsIHttpChannelInternal so that other protocols correctly respect the third-party cookie pref. r=sicking/ckerschb (06f7a10a83) - better backport of Bug 485941 - Stack overflow using overly-deep XML tree (DoS). r=bzbarsky (ac43feeffa) - Bug 1182546 - Use channel->Open2() in parser/htmlparser/nsExpatDriver.cpp (r=bz) (42768f373a) - Bug 1163435 part 1 - [css-grid][css-flexbox] Propagate an explicit CB width/height to the reflow state to resolve percentage lengths for grid items properly. Resolve percent against the size in the same axis for abs.pos. children too. r=dholbert (a55463fb05) - Bug 1163435 part 2 - tests. (bb683c5fc6) - Bug 1223282 - Make NS_AUTOMARGIN be a different value than NS_UNCONSTRAINEDSIZE to avoid having clamped huge margin values be interpreted as auto margins. r=roc (4cdfe0f277) - Bug 1224230 - Explicitly store the lineContainer's writing mode in InlineIntrinsicISizeData. r=dbaron (6474515223) - Bug 1221043. Revert to including trailing whitespace for accessibility APIs. r=marcoz,mats (406018c163) - Bug 1227113 - Fix some indentation issues in ServiceWorkerManager, r=janv (2b343bde09) - Bug 1223116 P1 Expose nsIServiceWorkerManager.shouldReportToWindow(). r=catalinb (02899e429d) - Bug 1226441 - Part 1: Add wpt test verifying fetch event waits for activate to complete; r=catalinb (e8eb3e6e7a) - Bug 1209865 - Add gecko profiler marker when mark() of User Timing API is called. r=baku (f48d76e395) - Bug 1169068 - Performance.translateTime(), r=bz (38cd1c31b2) - Bug 1226441 - Part 2: Delay functional event dispatch until service worker is activated; r=catalinb (778cd3dd24) - Bug 1178233 - [non-e10s] The update process doesn't work within about:serviceworkers in non-e10s mode. Test. r=baku (4f8b6f53f8) - Bug 1188545 - Disable unstable test: test_aboutserviceworkers.html. a=testonly (4bbe106693) - Bug 1219255 - We should be able to attach to a service worker;r=amarchesini (0d6b71b4ec) - Bug 1222464 - Part 2: Implement FetchEvent.clientId; r=jdm (9c8abd62dd) - Bug 1218150 - Mark the members of Clients as NewObject; r=bzbarsky (b6b00a586c) - Bug 1222464 - Part 3: Implement Clients.get(); r=jdm (f5ca60d801) - Bug 1222464 - Part 1: Save a client ID for top-level navigations on the docshell and assign it as the document ID when we start loading the document; r=jdm (7dcb5ce2b6) - Bug 1218141 - Add some SameObject and NewObject annotations to ServiceWorkerGlobalScope; r=bzbarsky (5019f58c7a) - Bug 1218190 - Add a pref to enable Clients.openWindow, r=catalinb (dbb6d007dd) - Bug 1218142 - Remove ServiceWorkerGlobalScope.onbeforeevicted/onevicted; r=bzbarsky (029de6f8ec) - Bug 1218146 - Move WindowClient.frameType to Client.frameType; r=bzbarsky (00f0211276) - Bug 1218147 - Make WindowClient.focus() NewObject; r=bzbarsky (3c6aea4b67) - Bug 1189659 - Part 1 - Continue service worker job queue when life cycle events expire. r=bkelly (aa09cd9c60) - Bug 1227932 - Fix Service Workers SoftUpdate and registration.update code paths. r=ehsan (24567b23c0) - Bug 1189659 - Part 2 - Remove set of scopes being updated from ServiceWorkerManager. r=bkelly (ce581b095c) - Bug 1189659 - Part 3 - Use separate synchronization queues for service worker register jobs and install jobs. r=bkelly (9c408a22ed) - Bug 1189659 - Part 4 - Fix race in test_install_event.html. r=bkelly (3186ffb808) - Bug 1189659 - Part 5 - Fix race in skip-waiting.https.html and add some logging for SkipWaitingFlag in ServiceWorkerManager. r=ehsan (4e5ddda6f3) - Bug 1229056 - Implement ClientQueryOptions.includeUncontrolled; r=jdm (dbe56aa60d) - namespace (3b0863d42d) - Bug 1201127 - Return the same ServiceWorkerRegistration object from service worker APIs dealing with the same underlying registration object; r=jdm (c542688ae0) - Bug 1171583 - Remove mutable warning from |nsSimpleURI::SetUserPass|. r=bz (73934deaad) - Bug 1206199 - Extend channelwrapper to mediate OnStartRequest, OnStopRequest, OnDataAvailable (r=sicking) (758a7ec65c) - Bug 1186783 (part 4) - Replace nsBaseHashtable::EnumerateRead() calls in netwerk/ with iterators. r=valentin. (681bdba278) - Bug 1186783 (part 3) - Replace nsBaseHashtable::EnumerateRead() calls in netwerk/ with iterators. r=valentin. (76b8b7191e) - Bug 1186783 (part 2) - Replace nsBaseHashtable::EnumerateRead() calls in netwerk/ with iterators. r=valentin. (5c0743ac49) - Bug 1186783 (part 1) - Replace nsBaseHashtable::EnumerateRead() calls in netwerk/ with iterators. r=valentin. (952cc720cc) - Bug 1186783 (part 5) - Replace nsBaseHashtable::EnumerateRead() calls in netwerk/ with iterators. r=valentin. (25b9735c52) - Bug 1186783 (part 1) - Replace nsBaseHashtable::EnumerateRead() calls in netwerk/. r=michal. (ae52425809) - Bug 1186783 (follow-up) - Bustage fix for Gonk. (d4a1b769bd) - add back some hotfix stuff, even if unused (fe32076c5b) - Bug 1068087: Switch about:plugins to run remotely. r=mconley (bc4316dd03) - Bug 1214058: Part 1 - Add a simplified JSON-based add-on update protocol. r=Mossop (a3198884d5) - Bug 1214058: Part 2 - Run add-on update tests against comparable JSON and RDF manifests. r=Mossop (aa6a796e6f) - Bug 1152977 - Enable by default DEAA for desktop platforms that use OpenGL compositor. r=jmuizelaar (bfa9efd5c8) --- accessible/base/NotificationController.cpp | 4 +- accessible/base/nsAccessibilityService.cpp | 4 +- accessible/base/nsTextEquivUtils.cpp | 4 +- accessible/generic/Accessible.cpp | 4 +- accessible/generic/HyperTextAccessible.cpp | 6 +- .../tests/mochitest/jsat/test_traversal.html | 2 +- .../mochitest/pivot/test_virtualcursor.html | 4 +- accessible/tests/mochitest/text/test_doc.html | 4 +- .../tests/mochitest/text/test_hypertext.html | 2 +- .../mochitest/text/test_lineboundary.html | 10 +- .../mochitest/textattrs/test_general.html | 18 +- .../mochitest/textcaret/test_general.html | 2 +- .../tests/mochitest/tree/test_txtcntr.html | 8 +- browser/app/profile/palemoon.js | 2 + caps/BasePrincipal.cpp | 80 +- caps/BasePrincipal.h | 127 +- caps/nsJSPrincipals.cpp | 2 +- caps/nsNullPrincipal.cpp | 4 +- caps/nsNullPrincipal.h | 4 +- caps/nsPrincipal.cpp | 4 +- caps/nsPrincipal.h | 2 +- caps/nsScriptSecurityManager.cpp | 30 +- caps/nsScriptSecurityManager.h | 4 +- caps/tests/gtest/TestOriginAttributes.cpp | 18 +- docshell/base/LoadContext.cpp | 3 +- docshell/base/LoadContext.h | 8 +- docshell/base/SerializedLoadContext.h | 2 +- docshell/base/nsDocShell.cpp | 39 +- docshell/base/nsDocShell.h | 11 +- docshell/base/nsILoadContext.idl | 7 +- dom/base/ChromeUtils.cpp | 4 +- dom/base/Console.cpp | 14 +- dom/base/ThirdPartyUtil.cpp | 115 +- dom/base/nsDocument.cpp | 78 +- dom/base/nsFrameLoader.cpp | 27 +- dom/base/nsFrameLoader.h | 2 +- dom/base/nsGlobalWindow.cpp | 29 +- dom/base/nsIDocument.h | 4 +- dom/base/nsPIDOMWindow.h | 11 + dom/base/nsPerformance.cpp | 69 +- dom/base/nsPerformance.h | 30 +- dom/base/test/empty_worker.js | 1 + dom/base/test/mochitest.ini | 2 + dom/base/test/test_performance_translate.html | 75 + dom/base/test/unit/test_thirdpartyutil.js | 12 +- dom/cache/DBSchema.cpp | 2 +- dom/datastore/DataStoreService.cpp | 4 +- dom/events/Event.cpp | 2 +- dom/events/test/test_eventTimeStamp.html | 5 +- dom/fetch/FetchDriver.cpp | 60 +- dom/fetch/FetchDriver.h | 4 +- .../base/nsIServiceWorkerManager.idl | 16 +- dom/ipc/AppProcessChecker.cpp | 2 +- dom/ipc/TabContext.cpp | 9 +- dom/ipc/TabContext.h | 12 +- dom/quota/QuotaManager.cpp | 2 +- dom/security/nsContentSecurityManager.cpp | 10 +- .../mochitest/bugs/file_cookieOutputter.html | 13 + .../mochitest/bugs/file_prime_cookie.html | 14 + dom/tests/mochitest/bugs/file_redirector.sjs | 9 + dom/tests/mochitest/bugs/mochitest.ini | 5 + dom/tests/mochitest/bugs/test_bug1112040.html | 32 + dom/tests/mochitest/bugs/test_bug1171215.html | 89 + dom/webidl/ChromeUtils.webidl | 2 +- dom/webidl/Client.webidl | 6 +- dom/webidl/Clients.webidl | 9 +- dom/webidl/FetchEvent.webidl | 2 + dom/webidl/Performance.webidl | 9 +- dom/webidl/ServiceWorkerGlobalScope.webidl | 8 +- dom/workers/PServiceWorkerManager.ipdl | 6 +- dom/workers/Performance.cpp | 18 +- dom/workers/Performance.h | 5 +- dom/workers/RuntimeService.cpp | 10 + dom/workers/ServiceWorker.cpp | 7 + dom/workers/ServiceWorker.h | 3 + dom/workers/ServiceWorkerClient.cpp | 2 +- dom/workers/ServiceWorkerClient.h | 13 + dom/workers/ServiceWorkerClients.cpp | 184 +- dom/workers/ServiceWorkerClients.h | 3 + dom/workers/ServiceWorkerEvents.cpp | 1 + dom/workers/ServiceWorkerEvents.h | 7 + dom/workers/ServiceWorkerManager.cpp | 1336 +++++++---- dom/workers/ServiceWorkerManager.h | 83 +- dom/workers/ServiceWorkerManagerChild.cpp | 4 +- dom/workers/ServiceWorkerManagerChild.h | 4 +- dom/workers/ServiceWorkerManagerParent.cpp | 2 +- dom/workers/ServiceWorkerManagerParent.h | 12 +- dom/workers/ServiceWorkerManagerService.cpp | 2 +- dom/workers/ServiceWorkerManagerService.h | 4 +- dom/workers/ServiceWorkerPrivate.cpp | 344 ++- dom/workers/ServiceWorkerPrivate.h | 40 +- dom/workers/ServiceWorkerRegistrar.cpp | 2 +- dom/workers/ServiceWorkerRegistration.cpp | 38 +- dom/workers/ServiceWorkerRegistration.h | 12 +- dom/workers/ServiceWorkerWindowClient.h | 10 +- dom/workers/WorkerPrivate.cpp | 13 - dom/workers/WorkerPrivate.h | 21 +- dom/workers/WorkerScope.cpp | 13 +- dom/workers/WorkerScope.h | 5 +- dom/workers/Workers.h | 1 + dom/workers/test/gtest/TestReadWrite.cpp | 5 +- .../test/serviceworkers/app2/client.html | 17 + .../test/serviceworkers/app2/index.html | 81 + .../test/serviceworkers/app2/manifest.webapp | 5 + .../app2/manifest.webapp^headers^ | 1 + dom/workers/test/serviceworkers/app2/sw.sjs | 22 + .../test/serviceworkers/app2/version.html | 19 + .../serviceworkers/app2/wait_for_update.html | 25 + dom/workers/test/serviceworkers/chrome.ini | 5 + .../test/serviceworkers/chrome_helpers.js | 15 + .../serviceworkerinfo_iframe.html | 26 + .../serviceworkers/test_install_event.html | 17 +- .../serviceworkers/test_serviceworkerinfo.xul | 78 + extensions/cookie/nsPermissionManager.cpp | 2 +- ipc/glue/BackgroundUtils.cpp | 2 + ipc/glue/BackgroundUtils.h | 24 +- ipc/glue/PBackgroundSharedTypes.ipdlh | 4 +- layout/generic/nsBlockFrame.cpp | 6 +- layout/generic/nsContainerFrame.cpp | 6 +- layout/generic/nsFrame.cpp | 8 +- layout/generic/nsGridContainerFrame.cpp | 5 +- layout/generic/nsHTMLReflowState.cpp | 7 +- layout/generic/nsHTMLReflowState.h | 6 +- layout/generic/nsIFrame.h | 46 +- layout/generic/nsIFrameInlines.h | 18 +- layout/generic/nsRubyBaseContainerFrame.cpp | 6 +- layout/generic/nsTextFrame.cpp | 26 +- layout/generic/nsTextFrame.h | 4 +- .../grid-item-sizing-percent-001-ref.html | 94 + .../grid-item-sizing-percent-001.html | 96 + .../css-grid/grid-item-sizing-px-001.html | 94 + layout/reftests/css-grid/reftest.list | 2 + modules/libpref/init/all.js | 9 +- netwerk/base/LoadContextInfo.cpp | 27 +- netwerk/base/LoadContextInfo.h | 6 +- netwerk/base/LoadInfo.cpp | 65 +- netwerk/base/LoadInfo.h | 9 +- netwerk/base/Predictor.cpp | 2 +- netwerk/base/nsAsyncRedirectVerifyHelper.cpp | 10 + netwerk/base/nsBaseChannel.cpp | 17 +- netwerk/base/nsILoadContextInfo.idl | 6 +- netwerk/base/nsILoadInfo.idl | 42 +- netwerk/base/nsIOService.cpp | 28 +- netwerk/base/nsIOService.h | 1 - netwerk/base/nsNetUtil.cpp | 6 +- netwerk/base/nsNetUtil.h | 6 +- netwerk/base/nsSecCheckWrapChannel.cpp | 59 +- netwerk/base/nsSimpleURI.cpp | 2 - netwerk/cache/nsApplicationCacheService.cpp | 4 +- netwerk/cache/nsDiskCacheDeviceSQL.cpp | 36 +- netwerk/cache/nsDiskCacheDeviceSQL.h | 8 +- netwerk/cache2/AppCacheStorage.cpp | 2 +- netwerk/cache2/CacheFileMetadata.h | 4 +- netwerk/cache2/CacheFileUtils.cpp | 4 +- netwerk/cache2/CacheObserver.cpp | 8 +- netwerk/cache2/CacheStorageService.cpp | 171 +- netwerk/cache2/OldWrappers.cpp | 2 +- netwerk/cookie/CookieServiceParent.cpp | 23 +- netwerk/cookie/CookieServiceParent.h | 4 +- netwerk/cookie/nsCookieService.cpp | 71 +- netwerk/cookie/nsCookieService.h | 15 +- netwerk/ipc/NeckoChannelParams.ipdlh | 4 +- netwerk/ipc/NeckoParent.cpp | 15 +- netwerk/ipc/NeckoParent.h | 2 +- netwerk/protocol/http/HttpBaseChannel.cpp | 16 +- netwerk/protocol/http/HttpChannelChild.cpp | 19 +- netwerk/protocol/http/HttpChannelParent.cpp | 12 +- netwerk/protocol/http/PackagedAppService.cpp | 2 +- netwerk/protocol/http/PackagedAppVerifier.cpp | 2 +- netwerk/protocol/http/nsHttpAuthCache.cpp | 4 +- netwerk/protocol/http/nsHttpChannel.cpp | 44 +- netwerk/protocol/http/nsHttpChannel.h | 3 + .../http/nsHttpChannelAuthProvider.cpp | 2 +- .../protocol/http/nsIHttpChannelInternal.idl | 17 +- netwerk/protocol/wyciwyg/nsWyciwygChannel.h | 2 +- .../streamconv/nsStreamConverterService.cpp | 19 +- netwerk/test/mochitests/mochitest.ini | 2 + .../mochitests/signed_web_packaged_app.sjs | 18 +- .../test_origin_attributes_conversion.html | 135 ++ .../test_signed_web_packaged_app_origin.html | 2 +- parser/htmlparser/nsExpatDriver.cpp | 95 +- parser/htmlparser/nsExpatDriver.h | 2 +- .../web-platform/mozilla/meta/MANIFEST.json | 19 + ...tchall-include-uncontrolled.https.html.ini | 6 - ...t-forever-in-install-worker.https.html.ini | 6 - .../clients-get-cross-origin.https.html | 42 + .../service-worker/clients-get.https.html | 52 + ...s-matchall-include-uncontrolled.https.html | 14 +- .../service-worker/fetch-event.https.html | 35 + .../fetch-waits-for-activate.https.html | 62 + .../resources/clients-get-other-origin.html | 64 + .../resources/clients-get-worker.js | 45 + .../resources/clients-matchall-worker.js | 9 +- .../resources/fetch-event-test-worker.js | 11 + .../fetch-waits-for-activate-worker.js | 17 + .../resources/interfaces-worker.sub.js | 6 + .../resources/skip-waiting-worker.js | 6 +- .../service-worker/skip-waiting.https.html | 1 - .../nsUrlClassifierStreamUpdater.cpp | 2 +- toolkit/content/plugins.html | 6 +- toolkit/mozapps/extensions/AddonManager.jsm | 82 +- .../internal/AddonUpdateChecker.jsm | 248 +- .../test/xpcshell/data/test_update.json | 215 ++ .../test/xpcshell/data/test_updatecheck.json | 327 +++ .../test/xpcshell/data/test_updatecheck.rdf | 2 +- .../extensions/test/xpcshell/head_addons.js | 114 +- .../xpcshell/test_AddonRepository_cache.js | 7 +- .../test/xpcshell/test_blocklistchange.js | 9 +- .../test/xpcshell/test_bug542391.js | 13 +- .../test/xpcshell/test_bug570173.js | 82 +- .../test/xpcshell/test_json_updatecheck.js | 373 +++ .../test/xpcshell/test_metadata_update.js | 15 +- .../extensions/test/xpcshell/test_update.js | 2132 +++++++++-------- .../test/xpcshell/test_update_ignorecompat.js | 144 +- .../test/xpcshell/test_update_strictcompat.js | 1862 +++++++------- .../test/xpcshell/test_updatecheck.js | 462 ++-- .../test/xpcshell/xpcshell-shared.ini | 1 + .../prefetch/OfflineCacheUpdateParent.cpp | 9 +- uriloader/prefetch/OfflineCacheUpdateParent.h | 4 +- 219 files changed, 7737 insertions(+), 4026 deletions(-) create mode 100644 dom/base/test/empty_worker.js create mode 100644 dom/base/test/test_performance_translate.html create mode 100644 dom/tests/mochitest/bugs/file_cookieOutputter.html create mode 100644 dom/tests/mochitest/bugs/file_prime_cookie.html create mode 100644 dom/tests/mochitest/bugs/file_redirector.sjs create mode 100644 dom/tests/mochitest/bugs/test_bug1112040.html create mode 100644 dom/tests/mochitest/bugs/test_bug1171215.html create mode 100644 dom/workers/test/serviceworkers/app2/client.html create mode 100644 dom/workers/test/serviceworkers/app2/index.html create mode 100644 dom/workers/test/serviceworkers/app2/manifest.webapp create mode 100644 dom/workers/test/serviceworkers/app2/manifest.webapp^headers^ create mode 100644 dom/workers/test/serviceworkers/app2/sw.sjs create mode 100644 dom/workers/test/serviceworkers/app2/version.html create mode 100644 dom/workers/test/serviceworkers/app2/wait_for_update.html create mode 100644 dom/workers/test/serviceworkers/serviceworkerinfo_iframe.html create mode 100644 dom/workers/test/serviceworkers/test_serviceworkerinfo.xul create mode 100644 layout/reftests/css-grid/grid-item-sizing-percent-001-ref.html create mode 100644 layout/reftests/css-grid/grid-item-sizing-percent-001.html create mode 100644 layout/reftests/css-grid/grid-item-sizing-px-001.html create mode 100644 netwerk/test/mochitests/test_origin_attributes_conversion.html delete mode 100644 testing/web-platform/mozilla/meta/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html.ini delete mode 100644 testing/web-platform/mozilla/meta/service-workers/service-worker/register-wait-forever-in-install-worker.https.html.ini create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/clients-get-cross-origin.https.html create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/clients-get.https.html create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-waits-for-activate.https.html create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/resources/clients-get-other-origin.html create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/resources/clients-get-worker.js create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_update.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js diff --git a/accessible/base/NotificationController.cpp b/accessible/base/NotificationController.cpp index 33e2d43552..ec7d7cd46c 100644 --- a/accessible/base/NotificationController.cpp +++ b/accessible/base/NotificationController.cpp @@ -230,7 +230,9 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime) nsIContent* containerElm = containerNode->IsElement() ? containerNode->AsElement() : nullptr; - nsIFrame::RenderedText text = textFrame->GetRenderedText(); + nsIFrame::RenderedText text = textFrame->GetRenderedText(0, + UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT, + nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE); // Remove text accessible if rendered text is empty. if (textAcc) { diff --git a/accessible/base/nsAccessibilityService.cpp b/accessible/base/nsAccessibilityService.cpp index 95cf3f6fb7..10b316c395 100644 --- a/accessible/base/nsAccessibilityService.cpp +++ b/accessible/base/nsAccessibilityService.cpp @@ -1091,7 +1091,9 @@ nsAccessibilityService::GetOrCreateAccessible(nsINode* aNode, // Create accessible for visible text frames. if (content->IsNodeOfType(nsINode::eTEXT)) { - nsIFrame::RenderedText text = frame->GetRenderedText(); + nsIFrame::RenderedText text = frame->GetRenderedText(0, + UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT, + nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE); // Ignore not rendered text nodes and whitespace text nodes between table // cells. if (text.mString.IsEmpty() || diff --git a/accessible/base/nsTextEquivUtils.cpp b/accessible/base/nsTextEquivUtils.cpp index bb7b3d918d..b7e703c2f0 100644 --- a/accessible/base/nsTextEquivUtils.cpp +++ b/accessible/base/nsTextEquivUtils.cpp @@ -139,7 +139,9 @@ nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent *aContent, if (aContent->TextLength() > 0) { nsIFrame *frame = aContent->GetPrimaryFrame(); if (frame) { - nsIFrame::RenderedText text = frame->GetRenderedText(); + nsIFrame::RenderedText text = frame->GetRenderedText(0, + UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT, + nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE); aString->Append(text.mString); } else { // If aContent is an object that is display: none, we have no a frame. diff --git a/accessible/generic/Accessible.cpp b/accessible/generic/Accessible.cpp index 0b8f536899..05ee9a9db2 100644 --- a/accessible/generic/Accessible.cpp +++ b/accessible/generic/Accessible.cpp @@ -394,7 +394,9 @@ Accessible::VisibilityState() if (frame->GetType() == nsGkAtoms::textFrame && !(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) && frame->GetRect().IsEmpty()) { - nsIFrame::RenderedText text = frame->GetRenderedText(); + nsIFrame::RenderedText text = frame->GetRenderedText(0, + UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT, + nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE); if (text.mString.IsEmpty()) { return states::INVISIBLE; } diff --git a/accessible/generic/HyperTextAccessible.cpp b/accessible/generic/HyperTextAccessible.cpp index 76b8fe49ca..b6f47dca8a 100644 --- a/accessible/generic/HyperTextAccessible.cpp +++ b/accessible/generic/HyperTextAccessible.cpp @@ -1985,7 +1985,8 @@ HyperTextAccessible::ContentToRenderedOffset(nsIFrame* aFrame, int32_t aContentO "Call on primary frame only"); nsIFrame::RenderedText text = aFrame->GetRenderedText(aContentOffset, - aContentOffset + 1); + aContentOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT, + nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE); *aRenderedOffset = text.mOffsetWithinNodeRenderedText; return NS_OK; @@ -2009,7 +2010,8 @@ HyperTextAccessible::RenderedToContentOffset(nsIFrame* aFrame, uint32_t aRendere "Call on primary frame only"); nsIFrame::RenderedText text = aFrame->GetRenderedText(aRenderedOffset, - aRenderedOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_RENDERED_TEXT); + aRenderedOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_RENDERED_TEXT, + nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE); *aContentOffset = text.mOffsetWithinNodeText; return NS_OK; diff --git a/accessible/tests/mochitest/jsat/test_traversal.html b/accessible/tests/mochitest/jsat/test_traversal.html index 7e1edd04c3..533e9d89ff 100644 --- a/accessible/tests/mochitest/jsat/test_traversal.html +++ b/accessible/tests/mochitest/jsat/test_traversal.html @@ -113,7 +113,7 @@ 'A esoteric weapon wielded by only the most ' + 'formidable warriors, for its unrelenting strict' + ' power is unfathomable.', - '• Lists of Programming Languages', 'Lisp', + '• Lists of Programming Languages', 'Lisp ', '1. Scheme', '2. Racket', '3. Clojure', '4. Standard Lisp', 'link-0', ' Lisp', 'checkbox-1-5', ' LeLisp', '• JavaScript', diff --git a/accessible/tests/mochitest/pivot/test_virtualcursor.html b/accessible/tests/mochitest/pivot/test_virtualcursor.html index 9bb1734843..2fb339964a 100644 --- a/accessible/tests/mochitest/pivot/test_virtualcursor.html +++ b/accessible/tests/mochitest/pivot/test_virtualcursor.html @@ -52,7 +52,7 @@ gQueue, docAcc, ObjectTraversalRule, null, ['Main Title', 'Lorem ipsum ', 'dolor', ' sit amet. Integer vitae urna leo, id ', - 'semper', ' nulla.', 'Second Section Title', + 'semper', ' nulla. ', 'Second Section Title', 'Sed accumsan luctus lacus, vitae mollis arcu tristique vulputate.', 'An ', 'embedded', ' document.', 'Hide me', 'Link 1', 'Link 2', 'Link 3', 'Hello', 'World']); @@ -90,7 +90,7 @@ gQueue, docAcc, ObjectTraversalRule, getAccessible(doc.getElementById('paragraph-1')), ['Lorem ipsum ', 'dolor', ' sit amet. Integer vitae urna leo, id ', - 'semper', ' nulla.']); + 'semper', ' nulla. ']); gQueue.push(new setModalRootInvoker(docAcc, docAcc.parent, NS_ERROR_INVALID_ARG)); diff --git a/accessible/tests/mochitest/text/test_doc.html b/accessible/tests/mochitest/text/test_doc.html index 76b801c277..9c6788275a 100644 --- a/accessible/tests/mochitest/text/test_doc.html +++ b/accessible/tests/mochitest/text/test_doc.html @@ -16,8 +16,8 @@ function doTest() { var iframeDoc = [ getNode("iframe").contentDocument ]; - testCharacterCount(iframeDoc, 13); - testText(iframeDoc, 0, 13, "outbodyinbody"); + testCharacterCount(iframeDoc, 15); + testText(iframeDoc, 0, 15, "outbody inbody "); SimpleTest.finish(); } diff --git a/accessible/tests/mochitest/text/test_hypertext.html b/accessible/tests/mochitest/text/test_hypertext.html index 6df2419aa6..2d71e11a9d 100644 --- a/accessible/tests/mochitest/text/test_hypertext.html +++ b/accessible/tests/mochitest/text/test_hypertext.html @@ -58,7 +58,7 @@ //////////////////////////////////////////////////////////////////////// // getTextAtOffset line boundary - testTextAtOffset(0, BOUNDARY_LINE_START, "line", 0, 4, + testTextAtOffset(0, BOUNDARY_LINE_START, "line ", 0, 5, "hypertext3", kOk, kOk, kOk); // XXX: see bug 634202. diff --git a/accessible/tests/mochitest/text/test_lineboundary.html b/accessible/tests/mochitest/text/test_lineboundary.html index 628cc9c4e6..e2e170e2f4 100644 --- a/accessible/tests/mochitest/text/test_lineboundary.html +++ b/accessible/tests/mochitest/text/test_lineboundary.html @@ -14,9 +14,9 @@ function doTest() { testTextAtOffset("line_test_1", BOUNDARY_LINE_START, - [[0, 6, "Line 1", 0, 6], - [6, 6, "", 6, 6], - [7, 13, "Line 3", 7, 13]]); + [[0, 6, "Line 1 ", 0, 7], + [7, 7, "", 7, 7], + [8, 15, "Line 3 ", 8, 15]]); ////////////////////////////////////////////////////////////////////////// // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__ @@ -114,10 +114,10 @@ [ [ 0, 3, "foo\n", 0, 4 ], [ 4, 4, "", 4, 4 ] ]); ////////////////////////////////////////////////////////////////////////// - // 'Hello world' + // 'Hello world ' (\n is rendered as space) testTextAtOffset([ "ht_4" ], BOUNDARY_LINE_START, - [ [ 0, 11, "Hello world", 0, 11 ] ]); + [ [ 0, 12, "Hello world ", 0, 12 ] ]); ////////////////////////////////////////////////////////////////////////// // list items diff --git a/accessible/tests/mochitest/textattrs/test_general.html b/accessible/tests/mochitest/textattrs/test_general.html index b94b8ff3a2..f024c45835 100644 --- a/accessible/tests/mochitest/textattrs/test_general.html +++ b/accessible/tests/mochitest/textattrs/test_general.html @@ -89,7 +89,7 @@ gComputedStyle = document.defaultView.getComputedStyle(tempElem, ""); attrs = {"color": gComputedStyle.color, "background-color": gComputedStyle.backgroundColor}; - testTextAttrs(ID, 27, attrs, defAttrs, 27, 49); + testTextAttrs(ID, 27, attrs, defAttrs, 27, 50); ////////////////////////////////////////////////////////////////////////// // area4 @@ -110,7 +110,7 @@ tempElem = tempElem.parentNode; gComputedStyle = document.defaultView.getComputedStyle(tempElem, ""); attrs = {"color": gComputedStyle.color}; - testTextAttrs(ID, 34, attrs, defAttrs, 33, 45); + testTextAttrs(ID, 34, attrs, defAttrs, 33, 46); ////////////////////////////////////////////////////////////////////////// // area5: "Green!*!RedNormal" @@ -144,7 +144,7 @@ // Normal attrs = {}; - testTextAttrs(ID, 11, attrs, defAttrs, 11, 17); + testTextAttrs(ID, 11, attrs, defAttrs, 11, 18); ////////////////////////////////////////////////////////////////////////// // area6 (CSS vertical-align property, refer to bug 445938 for details @@ -323,7 +323,7 @@ testTextAttrs(ID, 152, attrs, defAttrs, 151, 164); attrs = {}; - testTextAttrs(ID, 165, attrs, defAttrs, 164, 171); + testTextAttrs(ID, 165, attrs, defAttrs, 164, 172); ////////////////////////////////////////////////////////////////////////// // area10, different single style spans in non-styled paragraph @@ -383,7 +383,7 @@ testTextAttrs(ID, 111, attrs, defAttrs, 110, 123); attrs = {}; - testTextAttrs(ID, 124, attrs, defAttrs, 123, 130); + testTextAttrs(ID, 124, attrs, defAttrs, 123, 131); ////////////////////////////////////////////////////////////////////////// // area11, "font-weight" tests @@ -410,7 +410,7 @@ testTextAttrs(ID, 51, attrs, defAttrs, 51, 57); attrs = { }; - testTextAttrs(ID, 57, attrs, defAttrs, 57, 96); + testTextAttrs(ID, 57, attrs, defAttrs, 57, 97); ////////////////////////////////////////////////////////////////////////// // test out of range offset @@ -485,7 +485,7 @@ testTextAttrs(ID, 27, attrs, defAttrs, 27, 31); attrs = { }; - testTextAttrs(ID, 31, attrs, defAttrs, 31, 44); + testTextAttrs(ID, 31, attrs, defAttrs, 31, 45); ////////////////////////////////////////////////////////////////////////// // area17, "text-decoration" tests @@ -527,7 +527,7 @@ "text-line-through-style": "wavy", "text-line-through-color": "rgb(0, 0, 0)", }; - testTextAttrs(ID, 39, attrs, defAttrs, 39, 43); + testTextAttrs(ID, 39, attrs, defAttrs, 39, 44); ////////////////////////////////////////////////////////////////////////// // area18, "auto-generation text" tests @@ -557,7 +557,7 @@ testTextAttrs(ID, 11, attrs, defAttrs, 10, 17); attrs = {}; - testTextAttrs(ID, 18, attrs, defAttrs, 17, 27); + testTextAttrs(ID, 18, attrs, defAttrs, 17, 28); ////////////////////////////////////////////////////////////////////////// // area20, "aOffset as -1 (Mozilla Bug 789621)" test diff --git a/accessible/tests/mochitest/textcaret/test_general.html b/accessible/tests/mochitest/textcaret/test_general.html index 903bb16265..69f83959f1 100644 --- a/accessible/tests/mochitest/textcaret/test_general.html +++ b/accessible/tests/mochitest/textcaret/test_general.html @@ -71,7 +71,7 @@ turnCaretBrowsing(true); // test caret offsets - testCaretOffset(document, 15); + testCaretOffset(document, 16); testCaretOffset("textbox", -1); testCaretOffset("textarea", -1); testCaretOffset("p", -1); diff --git a/accessible/tests/mochitest/tree/test_txtcntr.html b/accessible/tests/mochitest/tree/test_txtcntr.html index 54e42b7f37..80a225ce78 100644 --- a/accessible/tests/mochitest/tree/test_txtcntr.html +++ b/accessible/tests/mochitest/tree/test_txtcntr.html @@ -49,14 +49,14 @@ }, { role: ROLE_TEXT_LEAF, - name: "Hello3" + name: "Hello3 " }, { role: ROLE_PARAGRAPH, children: [ { role: ROLE_TEXT_LEAF, - name: "Hello4" + name: "Hello4 " } ] } @@ -71,7 +71,7 @@ children: [ { role: ROLE_TEXT_LEAF, - name: "helllo" + name: "helllo " }, { role: ROLE_PARAGRAPH, @@ -84,7 +84,7 @@ }, { role: ROLE_TEXT_LEAF, - name: "hello" + name: "hello " } ] }; diff --git a/browser/app/profile/palemoon.js b/browser/app/profile/palemoon.js index 46f1c28d38..e095225ac2 100644 --- a/browser/app/profile/palemoon.js +++ b/browser/app/profile/palemoon.js @@ -1270,6 +1270,8 @@ pref("view_source.tab", false); // Interception is still disabled. pref("dom.serviceWorkers.enabled", true); +pref("dom.serviceWorkers.openWindow.enabled", true); + // Enable Push API. pref("dom.push.enabled", true); diff --git a/caps/BasePrincipal.cpp b/caps/BasePrincipal.cpp index 1f8907c932..6db7c3120a 100644 --- a/caps/BasePrincipal.cpp +++ b/caps/BasePrincipal.cpp @@ -28,26 +28,74 @@ namespace mozilla { using dom::URLParams; -void OriginAttributes::InheritFromDocShellParent(const OriginAttributes& aParent) +void +PrincipalOriginAttributes::InheritFromDocShellToDoc(const DocShellOriginAttributes& aAttrs, + const nsIURI* aURI) { - mAppId = aParent.mAppId; - mInBrowser = aParent.mInBrowser; - mUserContextId = aParent.mUserContextId; - mSignedPkg = aParent.mSignedPkg; + mAppId = aAttrs.mAppId; + mInBrowser = aAttrs.mInBrowser; + + // addonId is computed from the principal URI and never propagated + mUserContextId = aAttrs.mUserContextId; + + // TODO: + // Bug 1225349 - PrincipalOriginAttributes should inherit mSignedPkg + // accordingly by URI + mSignedPkg = aAttrs.mSignedPkg; } -bool OriginAttributes::CopyFromLoadContext(nsILoadContext* aLoadContext) +void +PrincipalOriginAttributes::InheritFromNecko(const NeckoOriginAttributes& aAttrs) { - OriginAttributes attrs; - bool result = aLoadContext->GetOriginAttributes(attrs); - NS_ENSURE_TRUE(result, false); + mAppId = aAttrs.mAppId; + mInBrowser = aAttrs.mInBrowser; - mAppId = attrs.mAppId; - mInBrowser = attrs.mInBrowser; - mAddonId = attrs.mAddonId; - mUserContextId = attrs.mUserContextId; - mSignedPkg = attrs.mSignedPkg; - return true; + // addonId is computed from the principal URI and never propagated + mUserContextId = aAttrs.mUserContextId; + mSignedPkg = aAttrs.mSignedPkg; +} + +void +DocShellOriginAttributes::InheritFromDocToChildDocShell(const PrincipalOriginAttributes& aAttrs) +{ + mAppId = aAttrs.mAppId; + mInBrowser = aAttrs.mInBrowser; + + // addonId is computed from the principal URI and never propagated + mUserContextId = aAttrs.mUserContextId; + + // TODO: + // Bug 1225353 - DocShell/NeckoOriginAttributes should inherit + // mSignedPkg accordingly by mSignedPkgInBrowser + mSignedPkg = aAttrs.mSignedPkg; +} + +void +NeckoOriginAttributes::InheritFromDocToNecko(const PrincipalOriginAttributes& aAttrs) +{ + mAppId = aAttrs.mAppId; + mInBrowser = aAttrs.mInBrowser; + + // addonId is computed from the principal URI and never propagated + mUserContextId = aAttrs.mUserContextId; + + // TODO: + // Bug 1225353 - DocShell/NeckoOriginAttributes should inherit + // mSignedPkg accordingly by mSignedPkgInBrowser +} + +void +NeckoOriginAttributes::InheritFromDocShellToNecko(const DocShellOriginAttributes& aAttrs) +{ + mAppId = aAttrs.mAppId; + mInBrowser = aAttrs.mInBrowser; + + // addonId is computed from the principal URI and never propagated + mUserContextId = aAttrs.mUserContextId; + + // TODO: + // Bug 1225353 - DocShell/NeckoOriginAttributes should inherit + // mSignedPkg accordingly by mSignedPkgInBrowser } void @@ -442,7 +490,7 @@ BasePrincipal::GetUnknownAppId(bool* aUnknownAppId) } already_AddRefed -BasePrincipal::CreateCodebasePrincipal(nsIURI* aURI, const OriginAttributes& aAttrs) +BasePrincipal::CreateCodebasePrincipal(nsIURI* aURI, const PrincipalOriginAttributes& aAttrs) { // If the URI is supposed to inherit the security context of whoever loads it, // we shouldn't make a codebase principal for it. diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h index e3918582a3..e8efd66180 100644 --- a/caps/BasePrincipal.h +++ b/caps/BasePrincipal.h @@ -14,26 +14,19 @@ #include "mozilla/dom/ChromeUtilsBinding.h" class nsIContentSecurityPolicy; -class nsILoadContext; class nsIObjectOutputStream; class nsIObjectInputStream; +class nsIURI; class nsExpandedPrincipal; namespace mozilla { +// Base OriginAttributes class. This has several subclass flavors, and is not +// directly constructable itself. class OriginAttributes : public dom::OriginAttributesDictionary { public: - OriginAttributes() {} - OriginAttributes(uint32_t aAppId, bool aInBrowser) - { - mAppId = aAppId; - mInBrowser = aInBrowser; - } - explicit OriginAttributes(const OriginAttributesDictionary& aOther) - : OriginAttributesDictionary(aOther) {} - bool operator==(const OriginAttributes& aOther) const { return mAppId == aOther.mAppId && @@ -47,28 +40,6 @@ public: return !(*this == aOther); } - // The docshell often influences the origin attributes of content loaded - // inside of it, and in some cases also influences the origin attributes of - // content loaded in child docshells. We say that a given attribute "lives on - // the docshell" to indicate that this attribute is specified by the docshell - // (if any) associated with a given content document. - // - // In practice, this usually means that we need to store a copy of those - // attributes on each docshell, or provide methods on the docshell to compute - // them on-demand. - // We could track each of these attributes individually, but since the - // majority of the existing origin attributes currently live on the docshell, - // it's cleaner to simply store an entire OriginAttributes struct on each - // docshell, and selectively copy them to child docshells and content - // principals in a manner that implements our desired semantics. - // - // This method is used to propagate attributes from parent to child - // docshells. - void InheritFromDocShellParent(const OriginAttributes& aParent); - - // Copy from the origin attributes of the nsILoadContext. - bool CopyFromLoadContext(nsILoadContext* aLoadContext); - // Serializes/Deserializes non-default values into the suffix format, i.e. // |!key1=value1&key2=value2|. If there are no non-default attributes, this // returns an empty string. @@ -79,6 +50,92 @@ public: // |uri!key1=value1&key2=value2| and returns the uri without the suffix. bool PopulateFromOrigin(const nsACString& aOrigin, nsACString& aOriginNoSuffix); + +protected: + OriginAttributes() {} + explicit OriginAttributes(const OriginAttributesDictionary& aOther) + : OriginAttributesDictionary(aOther) {} +}; + +class PrincipalOriginAttributes; +class DocShellOriginAttributes; +class NeckoOriginAttributes; + +// Various classes in Gecko contain OriginAttributes members, and those +// OriginAttributes get propagated to other classes according to certain rules. +// For example, the OriginAttributes on the docshell affect the OriginAttributes +// for the principal of a document loaded inside it, whose OriginAttributes in +// turn affect those of network loads and child docshells. To codify and +// centralize these rules, we introduce separate subclasses for the different +// flavors, and a variety of InheritFrom* methods to implement the transfer +// behavior. + +// For OriginAttributes stored on principals. +class PrincipalOriginAttributes : public OriginAttributes +{ +public: + PrincipalOriginAttributes() {} + PrincipalOriginAttributes(uint32_t aAppId, bool aInBrowser) + { + mAppId = aAppId; + mInBrowser = aInBrowser; + } + + // Inheriting OriginAttributes from docshell to document when user navigates. + // + // @param aAttrs Origin Attributes of the docshell. + // @param aURI The URI of the document. + void InheritFromDocShellToDoc(const DocShellOriginAttributes& aAttrs, + const nsIURI* aURI); + + // Inherit OriginAttributes from Necko. + void InheritFromNecko(const NeckoOriginAttributes& aAttrs); +}; + +// For OriginAttributes stored on docshells / loadcontexts / browsing contexts. +class DocShellOriginAttributes : public OriginAttributes +{ +public: + DocShellOriginAttributes() {} + DocShellOriginAttributes(uint32_t aAppId, bool aInBrowser) + { + mAppId = aAppId; + mInBrowser = aInBrowser; + } + + // Inheriting OriginAttributes from document to child docshell when an + // + + + +Mozilla Bug 1022869 +

+ +
+
+ + diff --git a/dom/webidl/ChromeUtils.webidl b/dom/webidl/ChromeUtils.webidl index 8c43ffb712..fb9b4db605 100644 --- a/dom/webidl/ChromeUtils.webidl +++ b/dom/webidl/ChromeUtils.webidl @@ -39,7 +39,7 @@ interface ChromeUtils : ThreadSafeChromeUtils { * IMPORTANT: If you add any members here, you need to do the following: * (1) Add them to both dictionaries. * (2) Update the methods on mozilla::OriginAttributes, including equality, - * serialization, and deserialization. + * serialization, deserialization, and inheritance. * (3) Update the methods on mozilla::OriginAttributesPattern, including matching. */ dictionary OriginAttributesDictionary { diff --git a/dom/webidl/Client.webidl b/dom/webidl/Client.webidl index a2111a8d61..45027bd71d 100644 --- a/dom/webidl/Client.webidl +++ b/dom/webidl/Client.webidl @@ -10,8 +10,9 @@ [Exposed=ServiceWorker] interface Client { - readonly attribute DOMString id; readonly attribute USVString url; + readonly attribute FrameType frameType; + readonly attribute DOMString id; [Throws] void postMessage(any message, optional sequence transfer); @@ -21,9 +22,8 @@ interface Client { interface WindowClient : Client { readonly attribute VisibilityState visibilityState; readonly attribute boolean focused; - readonly attribute FrameType frameType; - [Throws] + [Throws, NewObject] Promise focus(); }; diff --git a/dom/webidl/Clients.webidl b/dom/webidl/Clients.webidl index 2e7b41ecad..b207221306 100644 --- a/dom/webidl/Clients.webidl +++ b/dom/webidl/Clients.webidl @@ -11,11 +11,14 @@ [Exposed=ServiceWorker] interface Clients { // The objects returned will be new instances every time - [Throws] + [NewObject] + Promise get(DOMString id); + [NewObject] Promise?> matchAll(optional ClientQueryOptions options); - [Throws] + [NewObject, + Func="mozilla::dom::workers::ServiceWorkerGlobalScope::OpenWindowEnabled"] Promise openWindow(USVString url); - [Throws] + [NewObject] Promise claim(); }; diff --git a/dom/webidl/FetchEvent.webidl b/dom/webidl/FetchEvent.webidl index 105ae12c02..751cf13271 100644 --- a/dom/webidl/FetchEvent.webidl +++ b/dom/webidl/FetchEvent.webidl @@ -12,6 +12,7 @@ Exposed=(ServiceWorker)] interface FetchEvent : ExtendableEvent { [SameObject] readonly attribute Request? request; + readonly attribute DOMString? clientId; readonly attribute boolean isReload; [Throws] @@ -20,5 +21,6 @@ interface FetchEvent : ExtendableEvent { dictionary FetchEventInit : EventInit { Request request; + DOMString? clientId = null; boolean isReload = false; }; diff --git a/dom/webidl/Performance.webidl b/dom/webidl/Performance.webidl index 64c4bdb7b4..6df0dd2ae7 100644 --- a/dom/webidl/Performance.webidl +++ b/dom/webidl/Performance.webidl @@ -4,10 +4,10 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. * * The origin of this IDL file is - * http://www.w3.org/TR/hr-time/ + * http://w3c.github.io/hr-time/ * - * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C - * liability, trademark and document use rules apply. + * Copyright © 2015 W3C® (MIT, ERCIM, Keio, Beihang). + * W3C liability, trademark and document use rules apply. */ typedef double DOMHighResTimeStamp; @@ -17,6 +17,9 @@ typedef sequence PerformanceEntryList; interface Performance { [DependsOn=DeviceState, Affects=Nothing] DOMHighResTimeStamp now(); + + [Throws] + DOMHighResTimeStamp translateTime(DOMHighResTimeStamp time, (Window or Worker or SharedWorker or ServiceWorker) timeSource); }; [Exposed=Window] diff --git a/dom/webidl/ServiceWorkerGlobalScope.webidl b/dom/webidl/ServiceWorkerGlobalScope.webidl index 1f35055187..8902c46762 100644 --- a/dom/webidl/ServiceWorkerGlobalScope.webidl +++ b/dom/webidl/ServiceWorkerGlobalScope.webidl @@ -13,10 +13,10 @@ [Global=(Worker,ServiceWorker), Exposed=ServiceWorker] interface ServiceWorkerGlobalScope : WorkerGlobalScope { - readonly attribute Clients clients; - readonly attribute ServiceWorkerRegistration registration; + [SameObject] readonly attribute Clients clients; + [SameObject] readonly attribute ServiceWorkerRegistration registration; - [Throws] + [Throws, NewObject] Promise skipWaiting(); attribute EventHandler oninstall; @@ -24,8 +24,6 @@ interface ServiceWorkerGlobalScope : WorkerGlobalScope { [Func="mozilla::dom::workers::ServiceWorkerGlobalScope::InterceptionEnabled"] attribute EventHandler onfetch; - attribute EventHandler onbeforeevicted; - attribute EventHandler onevicted; // The event.source of these MessageEvents are instances of Client attribute EventHandler onmessage; diff --git a/dom/workers/PServiceWorkerManager.ipdl b/dom/workers/PServiceWorkerManager.ipdl index 8716815fcd..d77e7f2e82 100644 --- a/dom/workers/PServiceWorkerManager.ipdl +++ b/dom/workers/PServiceWorkerManager.ipdl @@ -7,7 +7,7 @@ include protocol PBackground; include PBackgroundSharedTypes; include ServiceWorkerRegistrarTypes; -using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h"; +using mozilla::PrincipalOriginAttributes from "mozilla/ipc/BackgroundUtils.h"; namespace mozilla { namespace dom { @@ -21,7 +21,7 @@ parent: Unregister(PrincipalInfo principalInfo, nsString scope); - PropagateSoftUpdate(OriginAttributes originAttributes, + PropagateSoftUpdate(PrincipalOriginAttributes originAttributes, nsString scope); PropagateUnregister(PrincipalInfo principalInfo, nsString scope); @@ -33,7 +33,7 @@ parent: child: NotifyRegister(ServiceWorkerRegistrationData data); - NotifySoftUpdate(OriginAttributes originAttributes, nsString scope); + NotifySoftUpdate(PrincipalOriginAttributes originAttributes, nsString scope); NotifyUnregister(PrincipalInfo principalInfo, nsString scope); NotifyRemove(nsCString host); NotifyRemoveAll(); diff --git a/dom/workers/Performance.cpp b/dom/workers/Performance.cpp index ec60b859b7..6ed8b4d3ab 100644 --- a/dom/workers/Performance.cpp +++ b/dom/workers/Performance.cpp @@ -32,7 +32,7 @@ DOMHighResTimeStamp Performance::Now() const { TimeDuration duration = - TimeStamp::Now() - mWorkerPrivate->NowBaseTimeStamp(); + TimeStamp::Now() - mWorkerPrivate->CreationTimeStamp(); double nowTime = duration.ToMilliseconds(); // Round down to the nearest 20us, because if the timer is too accurate people // can do nasty timing attacks with it. See similar code in the non-worker @@ -57,7 +57,7 @@ Performance::GetPerformanceTimingFromString(const nsAString& aProperty) } if (aProperty.EqualsLiteral("navigationStart")) { - return mWorkerPrivate->NowBaseTimeHighRes(); + return mWorkerPrivate->CreationTime(); } MOZ_CRASH("IsPerformanceTimingAttribute and GetPerformanceTimingFromString are out of sync"); @@ -74,14 +74,16 @@ Performance::InsertUserEntry(PerformanceEntry* aEntry) PerformanceBase::InsertUserEntry(aEntry); } -DOMHighResTimeStamp -Performance::DeltaFromNavigationStart(DOMHighResTimeStamp aTime) +TimeStamp +Performance::CreationTimeStamp() const { - if (aTime == 0) { - return 0; - } + return mWorkerPrivate->CreationTimeStamp(); +} - return aTime - mWorkerPrivate->NowBaseTimeHighRes(); +DOMHighResTimeStamp +Performance::CreationTime() const +{ + return mWorkerPrivate->CreationTime(); } void diff --git a/dom/workers/Performance.h b/dom/workers/Performance.h index ab18b078ea..c0b5af8560 100644 --- a/dom/workers/Performance.h +++ b/dom/workers/Performance.h @@ -55,8 +55,9 @@ private: DOMHighResTimeStamp GetPerformanceTimingFromString(const nsAString& aTimingName) override; - DOMHighResTimeStamp - DeltaFromNavigationStart(DOMHighResTimeStamp aTime) override; + TimeStamp CreationTimeStamp() const override; + + DOMHighResTimeStamp CreationTime() const override; }; END_WORKERS_NAMESPACE diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index 602e3ebdfd..059da67e9a 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -168,6 +168,7 @@ static_assert(MAX_WORKERS_PER_DOMAIN >= 1, #define PREF_SERVICEWORKERS_TESTING_ENABLED "dom.serviceWorkers.testing.enabled" #define PREF_INTERCEPTION_ENABLED "dom.serviceWorkers.interception.enabled" #define PREF_INTERCEPTION_OPAQUE_ENABLED "dom.serviceWorkers.interception.opaque.enabled" +#define PREF_OPEN_WINDOW_ENABLED "dom.serviceWorkers.openWindow.enabled" #define PREF_PUSH_ENABLED "dom.push.enabled" #define PREF_REQUESTCONTEXT_ENABLED "dom.requestcontext.enabled" #define PREF_OFFSCREENCANVAS_ENABLED "gfx.offscreencanvas.enabled" @@ -1924,6 +1925,10 @@ RuntimeService::Init() WorkerPrefChanged, PREF_INTERCEPTION_ENABLED, reinterpret_cast(WORKERPREF_INTERCEPTION_ENABLED))) || + NS_FAILED(Preferences::RegisterCallbackAndCall( + WorkerPrefChanged, + PREF_OPEN_WINDOW_ENABLED, + reinterpret_cast(WORKERPREF_OPEN_WINDOW_ENABLED))) || NS_FAILED(Preferences::RegisterCallbackAndCall( WorkerPrefChanged, PREF_INTERCEPTION_OPAQUE_ENABLED, @@ -2167,6 +2172,10 @@ RuntimeService::Cleanup() WorkerPrefChanged, PREF_INTERCEPTION_ENABLED, reinterpret_cast(WORKERPREF_INTERCEPTION_ENABLED))) || + NS_FAILED(Preferences::UnregisterCallback( + WorkerPrefChanged, + PREF_OPEN_WINDOW_ENABLED, + reinterpret_cast(WORKERPREF_OPEN_WINDOW_ENABLED))) || NS_FAILED(Preferences::UnregisterCallback( WorkerPrefChanged, PREF_SERVICEWORKERS_ENABLED, @@ -2711,6 +2720,7 @@ RuntimeService::WorkerPrefChanged(const char* aPrefName, void* aClosure) #endif case WORKERPREF_INTERCEPTION_ENABLED: case WORKERPREF_INTERCEPTION_OPAQUE_ENABLED: + case WORKERPREF_OPEN_WINDOW_ENABLED: case WORKERPREF_SERVICEWORKERS: case WORKERPREF_SERVICEWORKERS_TESTING: case WORKERPREF_PUSH: diff --git a/dom/workers/ServiceWorker.cpp b/dom/workers/ServiceWorker.cpp index 4542514f62..777d7ef1b3 100644 --- a/dom/workers/ServiceWorker.cpp +++ b/dom/workers/ServiceWorker.cpp @@ -101,6 +101,13 @@ ServiceWorker::PostMessage(JSContext* aCx, JS::Handle aMessage, aRv = workerPrivate->SendMessageEvent(aCx, aMessage, aTransferable, Move(clientInfo)); } +WorkerPrivate* +ServiceWorker::GetWorkerPrivate() const +{ + ServiceWorkerPrivate* workerPrivate = mInfo->WorkerPrivate(); + return workerPrivate->GetWorkerPrivate(); +} + } // namespace workers } // namespace dom } // namespace mozilla diff --git a/dom/workers/ServiceWorker.h b/dom/workers/ServiceWorker.h index 15fc4fbc7a..26cf53eb7b 100644 --- a/dom/workers/ServiceWorker.h +++ b/dom/workers/ServiceWorker.h @@ -67,6 +67,9 @@ public: const Optional>& aTransferable, ErrorResult& aRv); + WorkerPrivate* + GetWorkerPrivate() const; + private: // This class can only be created from the ServiceWorkerManager. ServiceWorker(nsPIDOMWindow* aWindow, ServiceWorkerInfo* aInfo); diff --git a/dom/workers/ServiceWorkerClient.cpp b/dom/workers/ServiceWorkerClient.cpp index cbabd85d3a..6d5633fe4c 100644 --- a/dom/workers/ServiceWorkerClient.cpp +++ b/dom/workers/ServiceWorkerClient.cpp @@ -34,7 +34,7 @@ ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc) : mWindowId(0) { MOZ_ASSERT(aDoc); - nsresult rv = aDoc->GetId(mClientId); + nsresult rv = aDoc->GetOrCreateId(mClientId); if (NS_FAILED(rv)) { NS_WARNING("Failed to get the UUID of the document."); } diff --git a/dom/workers/ServiceWorkerClient.h b/dom/workers/ServiceWorkerClient.h index fcaf2cc647..2b48e52884 100644 --- a/dom/workers/ServiceWorkerClient.h +++ b/dom/workers/ServiceWorkerClient.h @@ -33,6 +33,11 @@ class ServiceWorkerClientInfo final public: explicit ServiceWorkerClientInfo(nsIDocument* aDoc); + const nsString& ClientId() const + { + return mClientId; + } + private: nsString mClientId; uint64_t mWindowId; @@ -57,6 +62,7 @@ public: , mId(aClientInfo.mClientId) , mUrl(aClientInfo.mUrl) , mWindowId(aClientInfo.mWindowId) + , mFrameType(aClientInfo.mFrameType) { MOZ_ASSERT(aOwner); } @@ -78,6 +84,12 @@ public: aUrl.Assign(mUrl); } + mozilla::dom::FrameType + FrameType() const + { + return mFrameType; + } + void PostMessage(JSContext* aCx, JS::Handle aMessage, const Optional>& aTransferable, @@ -97,6 +109,7 @@ private: protected: uint64_t mWindowId; + mozilla::dom::FrameType mFrameType; }; } // namespace workers diff --git a/dom/workers/ServiceWorkerClients.cpp b/dom/workers/ServiceWorkerClients.cpp index e671fdf1fc..4153b6a756 100644 --- a/dom/workers/ServiceWorkerClients.cpp +++ b/dom/workers/ServiceWorkerClients.cpp @@ -55,53 +55,140 @@ ServiceWorkerClients::WrapObject(JSContext* aCx, JS::Handle aGivenPro namespace { -class ResolvePromiseWorkerRunnable final : public WorkerRunnable +class GetRunnable final : public nsRunnable { - RefPtr mPromiseProxy; - nsTArray mValue; - -public: - ResolvePromiseWorkerRunnable(WorkerPrivate* aWorkerPrivate, - PromiseWorkerProxy* aPromiseProxy, - nsTArray& aValue) - : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount), - mPromiseProxy(aPromiseProxy) + class ResolvePromiseWorkerRunnable final : public WorkerRunnable { - AssertIsOnMainThread(); - mValue.SwapElements(aValue); - } + RefPtr mPromiseProxy; + UniquePtr mValue; + nsresult mRv; - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) - { - MOZ_ASSERT(aWorkerPrivate); - aWorkerPrivate->AssertIsOnWorkerThread(); - - Promise* promise = mPromiseProxy->WorkerPromise(); - MOZ_ASSERT(promise); - - nsTArray> ret; - for (size_t i = 0; i < mValue.Length(); i++) { - ret.AppendElement(RefPtr( - new ServiceWorkerWindowClient(promise->GetParentObject(), - mValue.ElementAt(i)))); + public: + ResolvePromiseWorkerRunnable(WorkerPrivate* aWorkerPrivate, + PromiseWorkerProxy* aPromiseProxy, + UniquePtr&& aValue, + nsresult aRv) + : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount), + mPromiseProxy(aPromiseProxy), + mValue(Move(aValue)), + mRv(Move(aRv)) + { + AssertIsOnMainThread(); } - promise->MaybeResolve(ret); - mPromiseProxy->CleanUp(aCx); - return true; + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + Promise* promise = mPromiseProxy->WorkerPromise(); + MOZ_ASSERT(promise); + + if (NS_FAILED(mRv)) { + promise->MaybeReject(mRv); + } else if (mValue) { + RefPtr windowClient = + new ServiceWorkerWindowClient(promise->GetParentObject(), *mValue); + promise->MaybeResolve(windowClient.get()); + } else { + promise->MaybeResolve(JS::UndefinedHandleValue); + } + mPromiseProxy->CleanUp(aCx); + return true; + } + }; + + RefPtr mPromiseProxy; + nsString mClientId; +public: + GetRunnable(PromiseWorkerProxy* aPromiseProxy, + const nsAString& aClientId) + : mPromiseProxy(aPromiseProxy), + mClientId(aClientId) + { + } + + NS_IMETHOD + Run() override + { + AssertIsOnMainThread(); + + MutexAutoLock lock(mPromiseProxy->Lock()); + if (mPromiseProxy->CleanedUp()) { + return NS_OK; + } + + WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + RefPtr swm = ServiceWorkerManager::GetInstance(); + ErrorResult rv; + UniquePtr result = swm->GetClient(workerPrivate->GetPrincipal(), + mClientId, rv); + RefPtr r = + new ResolvePromiseWorkerRunnable(mPromiseProxy->GetWorkerPrivate(), + mPromiseProxy, Move(result), + rv.StealNSResult()); + rv.SuppressException(); + + AutoJSAPI jsapi; + jsapi.Init(); + r->Dispatch(jsapi.cx()); + return NS_OK; } }; class MatchAllRunnable final : public nsRunnable { + class ResolvePromiseWorkerRunnable final : public WorkerRunnable + { + RefPtr mPromiseProxy; + nsTArray mValue; + + public: + ResolvePromiseWorkerRunnable(WorkerPrivate* aWorkerPrivate, + PromiseWorkerProxy* aPromiseProxy, + nsTArray& aValue) + : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount), + mPromiseProxy(aPromiseProxy) + { + AssertIsOnMainThread(); + mValue.SwapElements(aValue); + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + Promise* promise = mPromiseProxy->WorkerPromise(); + MOZ_ASSERT(promise); + + nsTArray> ret; + for (size_t i = 0; i < mValue.Length(); i++) { + ret.AppendElement(RefPtr( + new ServiceWorkerWindowClient(promise->GetParentObject(), + mValue.ElementAt(i)))); + } + + promise->MaybeResolve(ret); + mPromiseProxy->CleanUp(aCx); + return true; + } + }; + RefPtr mPromiseProxy; nsCString mScope; + bool mIncludeUncontrolled; public: MatchAllRunnable(PromiseWorkerProxy* aPromiseProxy, - const nsCString& aScope) + const nsCString& aScope, + bool aIncludeUncontrolled) : mPromiseProxy(aPromiseProxy), - mScope(aScope) + mScope(aScope), + mIncludeUncontrolled(aIncludeUncontrolled) { MOZ_ASSERT(mPromiseProxy); } @@ -119,7 +206,8 @@ public: RefPtr swm = ServiceWorkerManager::GetInstance(); nsTArray result; - swm->GetAllClients(mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), mScope, result); + swm->GetAllClients(mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), mScope, + mIncludeUncontrolled, result); RefPtr r = new ResolvePromiseWorkerRunnable(mPromiseProxy->GetWorkerPrivate(), mPromiseProxy, result); @@ -569,6 +657,31 @@ private: } // namespace +already_AddRefed +ServiceWorkerClients::Get(const nsAString& aClientId, ErrorResult& aRv) +{ + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + workerPrivate->AssertIsOnWorkerThread(); + + RefPtr promise = Promise::Create(mWorkerScope, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + RefPtr promiseProxy = + PromiseWorkerProxy::Create(workerPrivate, promise); + if (!promiseProxy) { + promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + return promise.forget(); + } + + RefPtr r = + new GetRunnable(promiseProxy, aClientId); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r))); + return promise.forget(); +} + already_AddRefed ServiceWorkerClients::MatchAll(const ClientQueryOptions& aOptions, ErrorResult& aRv) @@ -580,7 +693,7 @@ ServiceWorkerClients::MatchAll(const ClientQueryOptions& aOptions, nsString scope; mWorkerScope->GetScope(scope); - if (aOptions.mIncludeUncontrolled || aOptions.mType != ClientType::Window) { + if (aOptions.mType != ClientType::Window) { aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return nullptr; } @@ -599,7 +712,8 @@ ServiceWorkerClients::MatchAll(const ClientQueryOptions& aOptions, RefPtr r = new MatchAllRunnable(promiseProxy, - NS_ConvertUTF16toUTF8(scope)); + NS_ConvertUTF16toUTF8(scope), + aOptions.mIncludeUncontrolled); MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r))); return promise.forget(); } diff --git a/dom/workers/ServiceWorkerClients.h b/dom/workers/ServiceWorkerClients.h index e962d1647f..2f7fb5ecec 100644 --- a/dom/workers/ServiceWorkerClients.h +++ b/dom/workers/ServiceWorkerClients.h @@ -29,6 +29,9 @@ public: explicit ServiceWorkerClients(ServiceWorkerGlobalScope* aWorkerScope); + already_AddRefed + Get(const nsAString& aClientId, ErrorResult& aRv); + already_AddRefed MatchAll(const ClientQueryOptions& aOptions, ErrorResult& aRv); diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp index bafab52942..54cf1af2d4 100644 --- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -102,6 +102,7 @@ FetchEvent::Constructor(const GlobalObject& aGlobal, e->SetTrusted(trusted); e->mRequest = aOptions.mRequest.WasPassed() ? &aOptions.mRequest.Value() : nullptr; + e->mClientId = aOptions.mClientId; e->mIsReload = aOptions.mIsReload; return e.forget(); } diff --git a/dom/workers/ServiceWorkerEvents.h b/dom/workers/ServiceWorkerEvents.h index 7159cb7421..3d4f01366a 100644 --- a/dom/workers/ServiceWorkerEvents.h +++ b/dom/workers/ServiceWorkerEvents.h @@ -107,6 +107,7 @@ class FetchEvent final : public ExtendableEvent RefPtr mRequest; nsCString mScriptSpec; nsCString mPreventDefaultScriptSpec; + nsString mClientId; uint32_t mPreventDefaultLineNumber; uint32_t mPreventDefaultColumnNumber; bool mIsReload; @@ -149,6 +150,12 @@ public: return mRequest; } + void + GetClientId(nsAString& aClientId) const + { + aClientId = mClientId; + } + bool IsReload() const { diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index 97f8fd2184..c944eb4cd8 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -20,11 +20,13 @@ #include "nsINetworkInterceptController.h" #include "nsIMutableArray.h" #include "nsIScriptError.h" +#include "nsISimpleEnumerator.h" #include "nsIUploadChannel2.h" #include "nsPIDOMWindow.h" #include "nsScriptLoader.h" #include "nsServiceManagerUtils.h" #include "nsDebug.h" +#include "nsISupportsPrimitives.h" #include "jsapi.h" @@ -114,7 +116,7 @@ static_assert(3 == static_cast(RequestRedirect::EndGuard_), static StaticRefPtr gInstance; -struct ServiceWorkerManager::RegistrationDataPerPrincipal +struct ServiceWorkerManager::RegistrationDataPerPrincipal final { // Ordered list of scopes for glob matching. // Each entry is an absolute URL representing the scope. @@ -133,11 +135,9 @@ struct ServiceWorkerManager::RegistrationDataPerPrincipal // Maps scopes to job queues. nsClassHashtable mJobQueues; - - nsDataHashtable mSetOfScopesBeingUpdated; }; -struct ServiceWorkerManager::PendingOperation +struct ServiceWorkerManager::PendingOperation final { nsCOMPtr mRunnable; @@ -149,27 +149,42 @@ struct ServiceWorkerManager::PendingOperation class ServiceWorkerJob : public nsISupports { + friend class ServiceWorkerJobQueue; + +public: + NS_DECL_ISUPPORTS + + enum Type + { + RegisterJob, + UpdateJob, + InstallJob, + UnregisterJob + }; + + virtual void Start() = 0; + + bool + IsRegisterOrInstallJob() const + { + return mJobType == RegisterJob || mJobType == UpdateJob || + mJobType == InstallJob; + } + protected: // The queue keeps the jobs alive, so they can hold a rawptr back to the // queue. ServiceWorkerJobQueue* mQueue; -public: - NS_DECL_ISUPPORTS + Type mJobType; - virtual void Start() = 0; - - virtual bool - IsRegisterJob() const { return false; } - -protected: - explicit ServiceWorkerJob(ServiceWorkerJobQueue* aQueue) + explicit ServiceWorkerJob(ServiceWorkerJobQueue* aQueue, Type aJobType) : mQueue(aQueue) - { - } + , mJobType(aJobType) + {} virtual ~ServiceWorkerJob() - { } + {} void Done(nsresult aStatus); @@ -179,30 +194,44 @@ class ServiceWorkerJobQueue final { friend class ServiceWorkerJob; - nsTArray> mJobs; + struct QueueData final + { + QueueData() + : mPopping(false) + { } + + ~QueueData() + { + if (!mJobs.IsEmpty()) { + NS_WARNING("Pending/running jobs still around on shutdown!"); + } + } + + nsTArray> mJobs; + bool mPopping; + }; + const nsCString mOriginAttributesSuffix; - bool mPopping; + QueueData mRegistrationJobQueue; + QueueData mInstallationJobQueue; public: explicit ServiceWorkerJobQueue(const nsACString& aScopeKey) : mOriginAttributesSuffix(aScopeKey) - , mPopping(false) {} ~ServiceWorkerJobQueue() - { - if (!mJobs.IsEmpty()) { - NS_WARNING("Pending/running jobs still around on shutdown!"); - } - } + { } void Append(ServiceWorkerJob* aJob) { MOZ_ASSERT(aJob); - MOZ_ASSERT(!mJobs.Contains(aJob)); - bool wasEmpty = mJobs.IsEmpty(); - mJobs.AppendElement(aJob); + QueueData& queue = GetQueue(aJob->mJobType); + MOZ_ASSERT(!queue.mJobs.Contains(aJob)); + + bool wasEmpty = queue.mJobs.IsEmpty(); + queue.mJobs.AppendElement(aJob); if (wasEmpty) { aJob->Start(); } @@ -211,29 +240,47 @@ public: void CancelJobs(); - // Only used by HandleError, keep it that way! - ServiceWorkerJob* - Peek() - { - if (mJobs.IsEmpty()) { - return nullptr; - } - return mJobs[0]; - } - private: void - Pop() + CancelJobs(QueueData& aQueue); + + // Internal helper function used to assign jobs to the correct queue. + QueueData& + GetQueue(ServiceWorkerJob::Type aType) { - MOZ_ASSERT(!mPopping, + switch (aType) { + case ServiceWorkerJob::Type::RegisterJob: + case ServiceWorkerJob::Type::UpdateJob: + case ServiceWorkerJob::Type::UnregisterJob: + return mRegistrationJobQueue; + case ServiceWorkerJob::Type::InstallJob: + return mInstallationJobQueue; + default: + MOZ_CRASH("Invalid job queue type."); + return mRegistrationJobQueue; + } + } + + bool + IsEmpty() + { + return mRegistrationJobQueue.mJobs.IsEmpty() && + mInstallationJobQueue.mJobs.IsEmpty(); + } + + void + Pop(QueueData& aQueue) + { + MOZ_ASSERT(!aQueue.mPopping, "Pop() called recursively, did you write a job which calls Done() synchronously from Start()?"); - AutoRestore savePopping(mPopping); - mPopping = true; - MOZ_ASSERT(!mJobs.IsEmpty()); - mJobs.RemoveElementAt(0); - if (!mJobs.IsEmpty()) { - mJobs[0]->Start(); - } else { + + AutoRestore savePopping(aQueue.mPopping); + aQueue.mPopping = true; + MOZ_ASSERT(!aQueue.mJobs.IsEmpty()); + aQueue.mJobs.RemoveElementAt(0); + if (!aQueue.mJobs.IsEmpty()) { + aQueue.mJobs[0]->Start(); + } else if (IsEmpty()) { RefPtr swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm); swm->MaybeRemoveRegistrationInfo(mOriginAttributesSuffix); @@ -243,9 +290,11 @@ private: void Done(ServiceWorkerJob* aJob) { - MOZ_ASSERT(!mJobs.IsEmpty()); - MOZ_ASSERT(mJobs[0] == aJob); - Pop(); + MOZ_ASSERT(aJob); + QueueData& queue = GetQueue(aJob->mJobType); + MOZ_ASSERT(!queue.mJobs.IsEmpty()); + MOZ_ASSERT(queue.mJobs[0] == aJob); + Pop(queue); } }; @@ -376,8 +425,9 @@ ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(const nsACString& a , mScope(aScope) , mPrincipal(aPrincipal) , mLastUpdateCheckTime(0) + , mUpdating(false) , mPendingUninstall(false) -{ } +{} ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo() { @@ -549,7 +599,7 @@ class ContinueLifecycleTask : public nsISupports protected: virtual ~ContinueLifecycleTask() - { } + {} public: virtual void ContinueAfterWorkerEvent(bool aSuccess) = 0; @@ -557,16 +607,16 @@ public: NS_IMPL_ISUPPORTS0(ContinueLifecycleTask); -class ServiceWorkerRegisterJob; +class ServiceWorkerInstallJob; class ContinueInstallTask final : public ContinueLifecycleTask { - RefPtr mJob; + RefPtr mJob; public: - explicit ContinueInstallTask(ServiceWorkerRegisterJob* aJob) + explicit ContinueInstallTask(ServiceWorkerInstallJob* aJob) : mJob(aJob) - { } + {} void ContinueAfterWorkerEvent(bool aSuccess) override; }; @@ -578,7 +628,7 @@ class ContinueActivateTask final : public ContinueLifecycleTask public: explicit ContinueActivateTask(ServiceWorkerRegistrationInfo* aReg) : mRegistration(aReg) - { } + {} void ContinueAfterWorkerEvent(bool aSuccess) override; @@ -620,21 +670,19 @@ class ServiceWorkerResolveWindowPromiseOnUpdateCallback final : public ServiceWo RefPtr mPromise; ~ServiceWorkerResolveWindowPromiseOnUpdateCallback() - { } + {} public: ServiceWorkerResolveWindowPromiseOnUpdateCallback(nsPIDOMWindow* aWindow, Promise* aPromise) : mWindow(aWindow) , mPromise(aPromise) - { - } + {} void UpdateSucceeded(ServiceWorkerRegistrationInfo* aInfo) override { RefPtr swr = - new ServiceWorkerRegistrationMainThread(mWindow, - NS_ConvertUTF8toUTF16(aInfo->mScope)); + mWindow->GetServiceWorkerRegistration(NS_ConvertUTF8toUTF16(aInfo->mScope)); mPromise->MaybeResolve(swr); } @@ -645,16 +693,24 @@ public: } }; -class ContinueUpdateRunnable final : public nsRunnable +class ContinueUpdateRunnable final : public LifeCycleEventCallback { nsMainThreadPtrHandle mJob; + bool mScriptEvaluationResult; public: explicit ContinueUpdateRunnable(const nsMainThreadPtrHandle aJob) : mJob(aJob) + , mScriptEvaluationResult(false) { AssertIsOnMainThread(); } + void + SetResult(bool aResult) + { + mScriptEvaluationResult = aResult; + } + NS_IMETHOD Run(); }; @@ -723,7 +779,7 @@ GetRequiredScopeStringPrefix(nsIURI* aScriptURI, nsACString& aPrefix, class PropagateSoftUpdateRunnable final : public nsRunnable { public: - PropagateSoftUpdateRunnable(const OriginAttributes& aOriginAttributes, + PropagateSoftUpdateRunnable(const PrincipalOriginAttributes& aOriginAttributes, const nsAString& aScope) : mOriginAttributes(aOriginAttributes) , mScope(aScope) @@ -731,7 +787,7 @@ public: NS_IMETHOD Run() override { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); RefPtr swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm); @@ -744,7 +800,7 @@ private: ~PropagateSoftUpdateRunnable() {} - const OriginAttributes mOriginAttributes; + const PrincipalOriginAttributes mOriginAttributes; const nsString mScope; }; @@ -763,7 +819,7 @@ public: NS_IMETHOD Run() override { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); RefPtr swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm); @@ -793,7 +849,7 @@ public: NS_IMETHOD Run() override { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); RefPtr swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm); @@ -817,7 +873,7 @@ public: NS_IMETHOD Run() override { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); RefPtr swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm); @@ -841,7 +897,7 @@ public: NS_IMETHOD Run() override { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); RefPtr swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm); @@ -857,30 +913,267 @@ private: } // namespace -class ServiceWorkerRegisterJob final : public ServiceWorkerJob, - public serviceWorkerScriptCache::CompareCallback +class ServiceWorkerJobBase : public ServiceWorkerJob +{ +public: + ServiceWorkerJobBase(ServiceWorkerJobQueue* aQueue, + ServiceWorkerJob::Type aJobType, + ServiceWorkerUpdateFinishCallback* aCallback) + : ServiceWorkerJobBase(aQueue, aJobType, aCallback, nullptr, nullptr) + { } + + ServiceWorkerJobBase(ServiceWorkerJobQueue* aQueue, + ServiceWorkerJob::Type aJobType, + ServiceWorkerUpdateFinishCallback* aCallback, + ServiceWorkerRegistrationInfo* aRegistration, + ServiceWorkerInfo* aServiceWorkerInfo) + : ServiceWorkerJob(aQueue, aJobType) + , mCallback(aCallback) + , mCanceled(false) + , mRegistration(aRegistration) + , mUpdateAndInstallInfo(aServiceWorkerInfo) + { + AssertIsOnMainThread(); + } + + void + Cancel() + { + mQueue = nullptr; + mCanceled = true; + } + +protected: + RefPtr mCallback; + bool mCanceled; + RefPtr mRegistration; + RefPtr mUpdateAndInstallInfo; + + ~ServiceWorkerJobBase() + { } + + // This MUST only be called when the job is still performing actions related + // to registration or update. After the spec resolves the update promise, use + // Done() with the failure code instead. + // Callers MUST hold a strong ref before calling this! + void + Fail(ErrorResult& aRv) + { + AssertIsOnMainThread(); + MOZ_ASSERT(mRegistration); + + // With cancellation support, we may only be running with one reference + // from another object like a stream loader or something. + RefPtr kungFuDeathGrip = this; + + // Save off the plain error code to pass to Done() where its logged to + // stderr as a warning. + nsresult origStatus = static_cast(aRv.ErrorCodeAsInt()); + + // Ensure that we only surface SecurityErr or TypeErr to script. + if (aRv.Failed() && !aRv.ErrorCodeIs(NS_ERROR_DOM_SECURITY_ERR) && + !aRv.ErrorCodeIs(NS_ERROR_DOM_TYPE_ERR)) { + + // Remove the old error code so we can replace it with a TypeError. + aRv.SuppressException(); + + NS_ConvertUTF8toUTF16 scriptSpec(mRegistration->mScriptSpec); + NS_ConvertUTF8toUTF16 scope(mRegistration->mScope); + + // Throw the type error with a generic error message. + aRv.ThrowTypeError(scriptSpec, scope); + } + + if (mCallback) { + mCallback->UpdateFailed(aRv); + mCallback = nullptr; + } + // In case the callback does not consume the exception + aRv.SuppressException(); + + mUpdateAndInstallInfo = nullptr; + if (mRegistration->mInstallingWorker) { + nsresult rv = serviceWorkerScriptCache::PurgeCache(mRegistration->mPrincipal, + mRegistration->mInstallingWorker->CacheName()); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to purge the installing worker cache."); + } + } + + RefPtr swm = ServiceWorkerManager::GetInstance(); + swm->MaybeRemoveRegistration(mRegistration); + // Ensures that the job can't do anything useful from this point on. + mRegistration = nullptr; + Done(origStatus); + } + + void + Fail(nsresult aRv) + { + ErrorResult rv(aRv); + Fail(rv); + } + + void + Succeed() + { + AssertIsOnMainThread(); + // We don't have a callback for soft updates. + if (mCallback) { + mCallback->UpdateSucceeded(mRegistration); + mCallback = nullptr; + } + } +}; + +class ServiceWorkerInstallJob final : public ServiceWorkerJobBase { friend class ContinueInstallTask; +public: + ServiceWorkerInstallJob(ServiceWorkerJobQueue* aQueue, + ServiceWorkerUpdateFinishCallback* aCallback, + ServiceWorkerRegistrationInfo* aRegistration, + ServiceWorkerInfo* aServiceWorkerInfo) + : ServiceWorkerJobBase(aQueue, Type::InstallJob, aCallback, + aRegistration, aServiceWorkerInfo) + { + MOZ_ASSERT(aRegistration); + } + + void + Start() + { + AssertIsOnMainThread(); + nsCOMPtr r = + NS_NewRunnableMethod(this, &ServiceWorkerInstallJob::Install); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r))); + } + + void + Install() + { + RefPtr kungFuDeathGrip = this; + if (mCanceled) { + return Fail(NS_ERROR_DOM_ABORT_ERR); + } + MOZ_ASSERT(mRegistration); + + // Begin [[Install]] atomic step 3. + if (mRegistration->mInstallingWorker) { + mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant); + mRegistration->mInstallingWorker->WorkerPrivate()->TerminateWorker(); + } + + RefPtr swm = ServiceWorkerManager::GetInstance(); + swm->InvalidateServiceWorkerRegistrationWorker(mRegistration, + WhichServiceWorker::INSTALLING_WORKER); + + mRegistration->mInstallingWorker = mUpdateAndInstallInfo.forget(); + mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Installing); + mRegistration->NotifyListenersOnChange(); + + Succeed(); + + // The job should NOT call fail from this point on. + + // Step 8 "Queue a task..." for updatefound. + nsCOMPtr upr = + NS_NewRunnableMethodWithArg( + swm, + &ServiceWorkerManager::FireUpdateFoundOnServiceWorkerRegistrations, + mRegistration); + + NS_DispatchToMainThread(upr); + + // Call ContinueAfterInstallEvent(false) on main thread if the SW + // script fails to load. + nsCOMPtr failRunnable = NS_NewRunnableMethodWithArgs + (this, &ServiceWorkerInstallJob::ContinueAfterInstallEvent, false); + + nsMainThreadPtrHandle installTask( + new nsMainThreadPtrHolder(new ContinueInstallTask(this))); + RefPtr callback = new ContinueLifecycleRunnable(installTask); + + // This triggers Step 4.7 "Queue a task to run the following substeps..." + // which sends the install event to the worker. + ServiceWorkerPrivate* workerPrivate = + mRegistration->mInstallingWorker->WorkerPrivate(); + nsresult rv = workerPrivate->SendLifeCycleEvent(NS_LITERAL_STRING("install"), + callback, failRunnable); + + if (NS_WARN_IF(NS_FAILED(rv))) { + ContinueAfterInstallEvent(false /* aSuccess */); + } + } + + void + ContinueAfterInstallEvent(bool aInstallEventSuccess) + { + if (mCanceled) { + return Done(NS_ERROR_DOM_ABORT_ERR); + } + + if (!mRegistration->mInstallingWorker) { + NS_WARNING("mInstallingWorker was null."); + return Done(NS_ERROR_DOM_ABORT_ERR); + } + + RefPtr swm = ServiceWorkerManager::GetInstance(); + + // "If installFailed is true" + if (NS_WARN_IF(!aInstallEventSuccess)) { + mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant); + mRegistration->mInstallingWorker = nullptr; + swm->InvalidateServiceWorkerRegistrationWorker(mRegistration, + WhichServiceWorker::INSTALLING_WORKER); + swm->MaybeRemoveRegistration(mRegistration); + return Done(NS_ERROR_DOM_ABORT_ERR); + } + + // "If registration's waiting worker is not null" + if (mRegistration->mWaitingWorker) { + mRegistration->mWaitingWorker->WorkerPrivate()->TerminateWorker(); + mRegistration->mWaitingWorker->UpdateState(ServiceWorkerState::Redundant); + + nsresult rv = + serviceWorkerScriptCache::PurgeCache(mRegistration->mPrincipal, + mRegistration->mWaitingWorker->CacheName()); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to purge the old waiting cache."); + } + } + + mRegistration->mWaitingWorker = mRegistration->mInstallingWorker.forget(); + mRegistration->mWaitingWorker->UpdateState(ServiceWorkerState::Installed); + mRegistration->NotifyListenersOnChange(); + swm->InvalidateServiceWorkerRegistrationWorker(mRegistration, + WhichServiceWorker::INSTALLING_WORKER | WhichServiceWorker::WAITING_WORKER); + + // "If registration's waiting worker's skip waiting flag is set" + if (mRegistration->mWaitingWorker->SkipWaitingFlag()) { + mRegistration->PurgeActiveWorker(); + } + + Done(NS_OK); + // Activate() is invoked out of band of atomic. + mRegistration->TryToActivate(); + } +}; + +class ServiceWorkerRegisterJob final : public ServiceWorkerJobBase, + public serviceWorkerScriptCache::CompareCallback +{ + friend class ContinueUpdateRunnable; + nsCString mScope; nsCString mScriptSpec; - RefPtr mRegistration; - nsTArray> mCallbacks; nsCOMPtr mPrincipal; - RefPtr mUpdateAndInstallInfo; nsCOMPtr mLoadGroup; ~ServiceWorkerRegisterJob() { } - enum - { - REGISTER_JOB = 0, - UPDATE_JOB = 1, - } mJobType; - - bool mCanceled; - public: NS_DECL_ISUPPORTS_INHERITED @@ -891,63 +1184,31 @@ public: ServiceWorkerUpdateFinishCallback* aCallback, nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup) - : ServiceWorkerJob(aQueue) + : ServiceWorkerJobBase(aQueue, Type::RegisterJob, aCallback) , mScope(aScope) , mScriptSpec(aScriptSpec) , mPrincipal(aPrincipal) , mLoadGroup(aLoadGroup) - , mJobType(REGISTER_JOB) - , mCanceled(false) { AssertIsOnMainThread(); MOZ_ASSERT(mLoadGroup); MOZ_ASSERT(aCallback); - - mCallbacks.AppendElement(aCallback); } // [[Update]] ServiceWorkerRegisterJob(ServiceWorkerJobQueue* aQueue, ServiceWorkerRegistrationInfo* aRegistration, ServiceWorkerUpdateFinishCallback* aCallback) - : ServiceWorkerJob(aQueue) - , mRegistration(aRegistration) - , mJobType(UPDATE_JOB) - , mCanceled(false) + : ServiceWorkerJobBase(aQueue, Type::UpdateJob, aCallback, + aRegistration, nullptr) { AssertIsOnMainThread(); - MOZ_ASSERT(aCallback); - - mCallbacks.AppendElement(aCallback); - } - - bool - IsRegisterJob() const override - { - return true; - } - - void - AppendCallback(ServiceWorkerUpdateFinishCallback* aCallback) - { - AssertIsOnMainThread(); - MOZ_ASSERT(aCallback); - MOZ_ASSERT(!mCallbacks.Contains(aCallback)); - - mCallbacks.AppendElement(aCallback); - } - - void - Cancel() - { - mQueue = nullptr; - mCanceled = true; } void Start() override { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); MOZ_ASSERT(!mCanceled); RefPtr swm = ServiceWorkerManager::GetInstance(); @@ -958,7 +1219,7 @@ public: return; } - if (mJobType == REGISTER_JOB) { + if (mJobType == RegisterJob) { mRegistration = swm->GetRegistration(mPrincipal, mScope); if (mRegistration) { @@ -987,11 +1248,7 @@ public: mRegistration->NotifyListenersOnChange(); swm->StoreRegistration(mPrincipal, mRegistration); } else { - MOZ_ASSERT(mJobType == UPDATE_JOB); - MOZ_ASSERT(mRegistration); - MOZ_ASSERT(mRegistration->mUpdateJob == nullptr); - - mRegistration->mUpdateJob = this; + MOZ_ASSERT(mJobType == UpdateJob); } Update(); @@ -1073,17 +1330,6 @@ public: return Fail(NS_ERROR_FAILURE); } - nsAutoString cacheName; - // We have to create a ServiceWorker here simply to ensure there are no - // errors. Ideally we should just pass this worker on to ContinueInstall. - MOZ_ASSERT(!data->mSetOfScopesBeingUpdated.Contains(mRegistration->mScope)); - data->mSetOfScopesBeingUpdated.Put(mRegistration->mScope, true); - - // Call FailScopeUpdate on main thread if the SW script load fails below. - nsCOMPtr failRunnable = NS_NewRunnableMethodWithArgs - , nsCString> - (this, &ServiceWorkerRegisterJob::FailScopeUpdate, swm, scopeKey); - MOZ_ASSERT(!mUpdateAndInstallInfo); mUpdateAndInstallInfo = new ServiceWorkerInfo(mRegistration, mRegistration->mScriptSpec, @@ -1092,197 +1338,61 @@ public: RefPtr upcasted = this; nsMainThreadPtrHandle handle( new nsMainThreadPtrHolder(upcasted)); - RefPtr callback = new ContinueUpdateRunnable(handle); + RefPtr callback = new ContinueUpdateRunnable(handle); ServiceWorkerPrivate* workerPrivate = mUpdateAndInstallInfo->WorkerPrivate(); - rv = workerPrivate->ContinueOnSuccessfulScriptEvaluation(callback); + rv = workerPrivate->CheckScriptEvaluation(callback); if (NS_WARN_IF(NS_FAILED(rv))) { - return FailScopeUpdate(swm, scopeKey); + Fail(NS_ERROR_DOM_ABORT_ERR); } } +private: + // This will perform steps 27 and 28 from [[Update]] + // Remove the job from the registration queue and invoke [[Install]] void - FailScopeUpdate(ServiceWorkerManager* aSwm, const nsACString& aScopeKey) - { - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(aSwm); - ServiceWorkerManager::RegistrationDataPerPrincipal* data; - if (aSwm->mRegistrationInfos.Get(aScopeKey, &data)) { - data->mSetOfScopesBeingUpdated.Remove(aScopeKey); - } - Fail(NS_ERROR_DOM_ABORT_ERR); - } - - // This MUST only be called when the job is still performing actions related - // to registration or update. After the spec resolves the update promise, use - // Done() with the failure code instead. - // Callers MUST hold a strong ref before calling this! - void - Fail(ErrorResult& aRv) + ContinueInstall(bool aScriptEvaluationResult) { AssertIsOnMainThread(); - MOZ_ASSERT(mCallbacks.Length()); - - // With cancellation support, we may only be running with one reference - // from another object like a stream loader or something. - RefPtr kungFuDeathGrip = this; - - // Save off the plain error code to pass to Done() where its logged to - // stderr as a warning. - nsresult origStatus = static_cast(aRv.ErrorCodeAsInt()); - - // Ensure that we only surface SecurityErr or TypeErr to script. - if (aRv.Failed() && !aRv.ErrorCodeIs(NS_ERROR_DOM_SECURITY_ERR) && - !aRv.ErrorCodeIs(NS_ERROR_DOM_TYPE_ERR)) { - - // Remove the old error code so we can replace it with a TypeError. - aRv.SuppressException(); - - // Depending on how the job was created and where we are in the - // state machine the spec and scope may be stored in different ways. - // Extract the current scope and script spec. - nsString scriptSpec; - nsString scope; - if (mRegistration) { - CopyUTF8toUTF16(mRegistration->mScriptSpec, scriptSpec); - CopyUTF8toUTF16(mRegistration->mScope, scope); - } else { - CopyUTF8toUTF16(mScriptSpec, scriptSpec); - CopyUTF8toUTF16(mScope, scope); - } - - // Throw the type error with a generic error message. - aRv.ThrowTypeError(scriptSpec, scope); - } - - for (uint32_t i = 1; i < mCallbacks.Length(); ++i) { - ErrorResult rv; - aRv.CloneTo(rv); - mCallbacks[i]->UpdateFailed(rv); - rv.SuppressException(); - } - - mCallbacks[0]->UpdateFailed(aRv); - - // In case the callback does not consume the exception - aRv.SuppressException(); - - mUpdateAndInstallInfo = nullptr; - if (mRegistration->mInstallingWorker) { - nsresult rv = serviceWorkerScriptCache::PurgeCache(mRegistration->mPrincipal, - mRegistration->mInstallingWorker->CacheName()); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to purge the installing worker cache."); - } - } - - RefPtr swm = ServiceWorkerManager::GetInstance(); - swm->MaybeRemoveRegistration(mRegistration); - // Ensures that the job can't do anything useful from this point on. - mRegistration->mUpdateJob = nullptr; - mRegistration = nullptr; - Done(origStatus); - } - - void - Fail(nsresult aRv) - { - ErrorResult rv(aRv); - Fail(rv); - } - - // Public so our error handling code can continue with a successful worker. - void - ContinueInstall() - { - // mRegistration will be null if we have already Fail()ed. - if (!mRegistration) { - return; - } - - // Even if we are canceled, ensure integrity of mSetOfScopesBeingUpdated - // first. - RefPtr swm = ServiceWorkerManager::GetInstance(); - - nsAutoCString scopeKey; - nsresult rv = swm->PrincipalToScopeKey(mRegistration->mPrincipal, scopeKey); - if (NS_WARN_IF(NS_FAILED(rv))) { - return Fail(NS_ERROR_FAILURE); - } - - ServiceWorkerManager::RegistrationDataPerPrincipal* data; - if (!swm->mRegistrationInfos.Get(scopeKey, &data)) { - return Fail(NS_ERROR_FAILURE); - } - - MOZ_ASSERT(data->mSetOfScopesBeingUpdated.Contains(mRegistration->mScope)); - data->mSetOfScopesBeingUpdated.Remove(mRegistration->mScope); - // This is effectively the end of Step 4.3 of the [[Update]] algorithm. - // The invocation of [[Install]] is not part of the atomic block. + MOZ_ASSERT(mRegistration); + mRegistration->mUpdating = false; RefPtr kungFuDeathGrip = this; if (mCanceled) { return Fail(NS_ERROR_DOM_ABORT_ERR); } - // Begin [[Install]] atomic step 4. - if (mRegistration->mInstallingWorker) { - mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant); - mRegistration->mInstallingWorker->WorkerPrivate()->TerminateWorker(); + if (NS_WARN_IF(!aScriptEvaluationResult)) { + ErrorResult error; + + NS_ConvertUTF8toUTF16 scriptSpec(mRegistration->mScriptSpec); + NS_ConvertUTF8toUTF16 scope(mRegistration->mScope); + error.ThrowTypeError(scriptSpec, scope); + return Fail(error); } - swm->InvalidateServiceWorkerRegistrationWorker(mRegistration, - WhichServiceWorker::INSTALLING_WORKER); - - mRegistration->mInstallingWorker = mUpdateAndInstallInfo.forget(); - mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Installing); - mRegistration->NotifyListenersOnChange(); - - Succeed(); - // The job should NOT call fail from this point on. - - // Step 4.6 "Queue a task..." for updatefound. - nsCOMPtr upr = - NS_NewRunnableMethodWithArg( - swm, - &ServiceWorkerManager::FireUpdateFoundOnServiceWorkerRegistrations, - mRegistration); - - NS_DispatchToMainThread(upr); - - // Call ContinueAfterInstallEvent(false) on main thread if the SW - // script fails to load. - nsCOMPtr failRunnable = NS_NewRunnableMethodWithArgs - (this, &ServiceWorkerRegisterJob::ContinueAfterInstallEvent, false); - - nsMainThreadPtrHandle installTask( - new nsMainThreadPtrHolder(new ContinueInstallTask(this))); - RefPtr callback = new ContinueLifecycleRunnable(installTask); - - // This triggers Step 4.7 "Queue a task to run the following substeps..." - // which sends the install event to the worker. - ServiceWorkerPrivate* workerPrivate = - mRegistration->mInstallingWorker->WorkerPrivate(); - rv = workerPrivate->SendLifeCycleEvent(NS_LITERAL_STRING("install"), - callback, failRunnable); - - if (NS_WARN_IF(NS_FAILED(rv))) { - ContinueAfterInstallEvent(false /* aSuccess */); - } + RefPtr job = + new ServiceWorkerInstallJob(mQueue, mCallback, + mRegistration, mUpdateAndInstallInfo); + mQueue->Append(job); + Done(NS_OK); } -private: void Update() { + AssertIsOnMainThread(); + // Since Update() is called synchronously from Start(), we can assert this. MOZ_ASSERT(!mCanceled); MOZ_ASSERT(mRegistration); nsCOMPtr r = NS_NewRunnableMethod(this, &ServiceWorkerRegisterJob::ContinueUpdate); NS_DispatchToMainThread(r); + + mRegistration->mUpdating = true; } // Aspects of (actually the whole algorithm) of [[Update]] after @@ -1298,7 +1408,7 @@ private: if (mRegistration->mInstallingWorker) { mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant); - // This will terminate the installing worker thread. + mRegistration->mInstallingWorker->WorkerPrivate()->TerminateWorker(); mRegistration->mInstallingWorker = nullptr; } @@ -1321,80 +1431,16 @@ private: } } - void - Succeed() - { - AssertIsOnMainThread(); - MOZ_ASSERT(mCallbacks.Length()); - - for (uint32_t i = 0; i < mCallbacks.Length(); ++i) { - mCallbacks[i]->UpdateSucceeded(mRegistration); - } - mCallbacks.Clear(); - } - - void - ContinueAfterInstallEvent(bool aInstallEventSuccess) - { - if (mCanceled) { - return Done(NS_ERROR_DOM_ABORT_ERR); - } - - if (!mRegistration->mInstallingWorker) { - NS_WARNING("mInstallingWorker was null."); - return Done(NS_ERROR_DOM_ABORT_ERR); - } - - RefPtr swm = ServiceWorkerManager::GetInstance(); - - // "If installFailed is true" - if (NS_WARN_IF(!aInstallEventSuccess)) { - mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant); - mRegistration->mInstallingWorker = nullptr; - swm->InvalidateServiceWorkerRegistrationWorker(mRegistration, - WhichServiceWorker::INSTALLING_WORKER); - swm->MaybeRemoveRegistration(mRegistration); - return Done(NS_ERROR_DOM_ABORT_ERR); - } - - // "If registration's waiting worker is not null" - if (mRegistration->mWaitingWorker) { - mRegistration->mWaitingWorker->WorkerPrivate()->TerminateWorker(); - mRegistration->mWaitingWorker->UpdateState(ServiceWorkerState::Redundant); - - nsresult rv = serviceWorkerScriptCache::PurgeCache(mRegistration->mPrincipal, - mRegistration->mWaitingWorker->CacheName()); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to purge the old waiting cache."); - } - } - - mRegistration->mWaitingWorker = mRegistration->mInstallingWorker.forget(); - mRegistration->mWaitingWorker->UpdateState(ServiceWorkerState::Installed); - mRegistration->NotifyListenersOnChange(); - swm->InvalidateServiceWorkerRegistrationWorker(mRegistration, - WhichServiceWorker::INSTALLING_WORKER | WhichServiceWorker::WAITING_WORKER); - - // "If registration's waiting worker's skip waiting flag is set" - if (mRegistration->mWaitingWorker->SkipWaitingFlag()) { - mRegistration->PurgeActiveWorker(); - } - - Done(NS_OK); - // Activate() is invoked out of band of atomic. - mRegistration->TryToActivate(); - } - void Done(nsresult aStatus) { - ServiceWorkerJob::Done(aStatus); + AssertIsOnMainThread(); - if (mJobType == UPDATE_JOB && mRegistration) { - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(mRegistration->mUpdateJob); - mRegistration->mUpdateJob = nullptr; + if (mRegistration) { + mRegistration->mUpdating = false; } + + ServiceWorkerJob::Done(aStatus); } }; @@ -1403,23 +1449,31 @@ NS_IMPL_ISUPPORTS_INHERITED0(ServiceWorkerRegisterJob, ServiceWorkerJob); void ServiceWorkerJobQueue::CancelJobs() { - if (mJobs.IsEmpty()) { + // The order doesn't matter. Cancel() just sets a flag on these jobs. + CancelJobs(mRegistrationJobQueue); + CancelJobs(mInstallationJobQueue); +} + +void +ServiceWorkerJobQueue::CancelJobs(QueueData& aQueue) +{ + if (aQueue.mJobs.IsEmpty()) { return; } // We have to treat the first job specially. It is the running job and needs // to be notified correctly. - RefPtr runningJob = mJobs[0]; + RefPtr runningJob = aQueue.mJobs[0]; // We can just let an Unregister job run to completion. - if (runningJob->IsRegisterJob()) { - ServiceWorkerRegisterJob* job = static_cast(runningJob.get()); + if (runningJob->IsRegisterOrInstallJob()) { + ServiceWorkerJobBase* job = static_cast(runningJob.get()); job->Cancel(); } // Get rid of everything. Non-main thread objects may still be holding a ref // to the running register job. Since we called Cancel() on it, the job's // main thread functions will just exit. - mJobs.Clear(); + aQueue.mJobs.Clear(); } NS_IMETHODIMP @@ -1428,7 +1482,7 @@ ContinueUpdateRunnable::Run() AssertIsOnMainThread(); RefPtr job = static_cast(mJob.get()); RefPtr upjob = static_cast(job.get()); - upjob->ContinueInstall(); + upjob->ContinueInstall(mScriptEvaluationResult); return NS_OK; } @@ -1740,14 +1794,14 @@ ServiceWorkerRegistrationInfo::Activate() /* * Implements the async aspects of the getRegistrations algorithm. */ -class GetRegistrationsRunnable : public nsRunnable +class GetRegistrationsRunnable final : public nsRunnable { nsCOMPtr mWindow; RefPtr mPromise; public: GetRegistrationsRunnable(nsPIDOMWindow* aWindow, Promise* aPromise) : mWindow(aWindow), mPromise(aPromise) - { } + {} NS_IMETHODIMP Run() @@ -1807,7 +1861,7 @@ public: } RefPtr swr = - new ServiceWorkerRegistrationMainThread(mWindow, scope); + mWindow->GetServiceWorkerRegistration(scope); array.AppendElement(swr); } @@ -1855,7 +1909,7 @@ ServiceWorkerManager::GetRegistrations(nsIDOMWindow* aWindow, /* * Implements the async aspects of the getRegistration algorithm. */ -class GetRegistrationRunnable : public nsRunnable +class GetRegistrationRunnable final : public nsRunnable { nsCOMPtr mWindow; RefPtr mPromise; @@ -1865,7 +1919,7 @@ public: GetRegistrationRunnable(nsPIDOMWindow* aWindow, Promise* aPromise, const nsAString& aDocumentURL) : mWindow(aWindow), mPromise(aPromise), mDocumentURL(aDocumentURL) - { } + {} NS_IMETHODIMP Run() @@ -1914,7 +1968,7 @@ public: NS_ConvertUTF8toUTF16 scope(registration->mScope); RefPtr swr = - new ServiceWorkerRegistrationMainThread(mWindow, scope); + mWindow->GetServiceWorkerRegistration(scope); mPromise->MaybeResolve(swr); return NS_OK; @@ -1957,7 +2011,7 @@ ServiceWorkerManager::GetRegistration(nsIDOMWindow* aWindow, return NS_DispatchToCurrentThread(runnable); } -class GetReadyPromiseRunnable : public nsRunnable +class GetReadyPromiseRunnable final : public nsRunnable { nsCOMPtr mWindow; RefPtr mPromise; @@ -1965,7 +2019,7 @@ class GetReadyPromiseRunnable : public nsRunnable public: GetReadyPromiseRunnable(nsPIDOMWindow* aWindow, Promise* aPromise) : mWindow(aWindow), mPromise(aPromise) - { } + {} NS_IMETHODIMP Run() @@ -2002,7 +2056,7 @@ ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes, #ifdef MOZ_SIMPLEPUSH return NS_ERROR_NOT_AVAILABLE; #else - OriginAttributes attrs; + PrincipalOriginAttributes attrs; if (!attrs.PopulateFromSuffix(aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } @@ -2035,7 +2089,7 @@ ServiceWorkerManager::SendPushSubscriptionChangeEvent(const nsACString& aOriginA #ifdef MOZ_SIMPLEPUSH return NS_ERROR_NOT_AVAILABLE; #else - OriginAttributes attrs; + PrincipalOriginAttributes attrs; if (!attrs.PopulateFromSuffix(aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } @@ -2061,7 +2115,7 @@ ServiceWorkerManager::SendNotificationClickEvent(const nsACString& aOriginSuffix const nsAString& aData, const nsAString& aBehavior) { - OriginAttributes attrs; + PrincipalOriginAttributes attrs; if (!attrs.PopulateFromSuffix(aOriginSuffix)) { return NS_ERROR_INVALID_ARG; } @@ -2153,7 +2207,7 @@ ServiceWorkerManager::CheckPendingReadyPromises() if (CheckReadyPromise(window, pendingReadyPromise->mURI, pendingReadyPromise->mPromise)) { iter.Remove(); - } + } } } @@ -2176,7 +2230,7 @@ ServiceWorkerManager::CheckReadyPromise(nsPIDOMWindow* aWindow, if (registration && registration->mActiveWorker) { NS_ConvertUTF8toUTF16 scope(registration->mScope); RefPtr swr = - new ServiceWorkerRegistrationMainThread(aWindow, scope); + aWindow->GetServiceWorkerRegistration(scope); aPromise->MaybeResolve(swr); return true; } @@ -2185,8 +2239,8 @@ ServiceWorkerManager::CheckReadyPromise(nsPIDOMWindow* aWindow, } ServiceWorkerInfo* -ServiceWorkerManager::GetActiveWorkerInfoForScope(const OriginAttributes& aOriginAttributes, - const nsACString& aScope) +ServiceWorkerManager::GetActiveWorkerInfoForScope(const PrincipalOriginAttributes& aOriginAttributes, + const nsACString& aScope) { AssertIsOnMainThread(); @@ -2227,14 +2281,14 @@ class ServiceWorkerUnregisterJob final : public ServiceWorkerJob nsCOMPtr mPrincipal; ~ServiceWorkerUnregisterJob() - { } + {} public: ServiceWorkerUnregisterJob(ServiceWorkerJobQueue* aQueue, const nsACString& aScope, nsIServiceWorkerUnregisterCallback* aCallback, nsIPrincipal* aPrincipal) - : ServiceWorkerJob(aQueue) + : ServiceWorkerJob(aQueue, Type::UnregisterJob) , mScope(aScope) , mCallback(aCallback) , mPrincipal(aPrincipal) @@ -2589,28 +2643,6 @@ ServiceWorkerManager::HandleError(JSContext* aCx, return; } - // If this is a failure, we may need to cancel an in-progress registration. - if (!JSREPORT_IS_WARNING(aFlags) && - data->mSetOfScopesBeingUpdated.Contains(aScope)) { - - data->mSetOfScopesBeingUpdated.Remove(aScope); - - ServiceWorkerJobQueue* queue = data->mJobQueues.Get(aScope); - MOZ_ASSERT(queue); - - ServiceWorkerJob* job = queue->Peek(); - if (job) { - MOZ_ASSERT(job->IsRegisterJob()); - RefPtr regJob = - static_cast(job); - - ErrorResult rv; - NS_ConvertUTF8toUTF16 scope(aScope); - rv.ThrowTypeError(aWorkerURL, scope); - regJob->Fail(rv); - } - } - // Always report any uncaught exceptions or errors to the console of // each client. ReportToAllClients(aScope, aMessage, aFilename, aLine, aLineNumber, @@ -2633,14 +2665,14 @@ ServiceWorkerRegistrationInfo::FinishActivate(bool aSuccess) void ServiceWorkerRegistrationInfo::RefreshLastUpdateCheckTime() { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); mLastUpdateCheckTime = PR_IntervalNow() / PR_MSEC_PER_SEC; } bool ServiceWorkerRegistrationInfo::IsLastUpdateCheckTimeOverOneDay() const { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); // For testing. if (Preferences::GetBool("dom.serviceWorkers.testUpdateOverOneDay")) { @@ -2666,23 +2698,6 @@ ServiceWorkerRegistrationInfo::NotifyListenersOnChange() } } -bool -ServiceWorkerRegistrationInfo::IsUpdating() const -{ - MOZ_ASSERT(NS_IsMainThread()); - return mUpdateJob != nullptr; -} - -void -ServiceWorkerRegistrationInfo::AppendUpdateCallback(ServiceWorkerUpdateFinishCallback* aCallback) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(aCallback); - MOZ_ASSERT(mUpdateJob); - - mUpdateJob->AppendCallback(aCallback); -} - void ServiceWorkerManager::LoadRegistration( const ServiceWorkerRegistrationData& aRegistration) @@ -2831,7 +2846,7 @@ ServiceWorkerManager::GetServiceWorkerRegistrationInfo(nsIPrincipal* aPrincipal, } already_AddRefed -ServiceWorkerManager::GetServiceWorkerRegistrationInfo(const OriginAttributes& aOriginAttributes, +ServiceWorkerManager::GetServiceWorkerRegistrationInfo(const PrincipalOriginAttributes& aOriginAttributes, nsIURI* aURI) { MOZ_ASSERT(aURI); @@ -3017,6 +3032,7 @@ ServiceWorkerManager::RemoveScopeAndRegistration(ServiceWorkerRegistrationInfo* swm->NotifyListenersOnUnregister(info); swm->MaybeRemoveRegistrationInfo(scopeKey); + swm->NotifyServiceWorkerRegistrationRemoved(aRegistration); } void @@ -3033,7 +3049,8 @@ ServiceWorkerManager::MaybeRemoveRegistrationInfo(const nsACString& aScopeKey) } void -ServiceWorkerManager::MaybeStartControlling(nsIDocument* aDoc) +ServiceWorkerManager::MaybeStartControlling(nsIDocument* aDoc, + const nsAString& aDocumentId) { AssertIsOnMainThread(); @@ -3046,7 +3063,7 @@ ServiceWorkerManager::MaybeStartControlling(nsIDocument* aDoc) GetServiceWorkerRegistrationInfo(aDoc); if (registration) { MOZ_ASSERT(!mControlledDocuments.Contains(aDoc)); - StartControllingADocument(registration, aDoc); + StartControllingADocument(registration, aDoc, aDocumentId); } } @@ -3068,13 +3085,17 @@ ServiceWorkerManager::MaybeStopControlling(nsIDocument* aDoc) void ServiceWorkerManager::StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration, - nsIDocument* aDoc) + nsIDocument* aDoc, + const nsAString& aDocumentId) { MOZ_ASSERT(aRegistration); MOZ_ASSERT(aDoc); aRegistration->StartControllingADocument(); mControlledDocuments.Put(aDoc, aRegistration); + if (!aDocumentId.IsEmpty()) { + aDoc->SetId(aDocumentId); + } Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1); } @@ -3252,18 +3273,18 @@ class ContinueDispatchFetchEventRunnable : public nsRunnable RefPtr mServiceWorkerPrivate; nsCOMPtr mChannel; nsCOMPtr mLoadGroup; - UniquePtr mClientInfo; + nsString mDocumentId; bool mIsReload; public: ContinueDispatchFetchEventRunnable(ServiceWorkerPrivate* aServiceWorkerPrivate, nsIInterceptedChannel* aChannel, nsILoadGroup* aLoadGroup, - UniquePtr&& aClientInfo, + const nsAString& aDocumentId, bool aIsReload) : mServiceWorkerPrivate(aServiceWorkerPrivate) , mChannel(aChannel) , mLoadGroup(aLoadGroup) - , mClientInfo(Move(aClientInfo)) + , mDocumentId(aDocumentId) , mIsReload(aIsReload) { MOZ_ASSERT(aServiceWorkerPrivate); @@ -3273,7 +3294,7 @@ public: void HandleError() { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); 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"); @@ -3282,7 +3303,7 @@ public: NS_IMETHOD Run() override { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); nsCOMPtr channel; nsresult rv = mChannel->GetChannel(getter_AddRefs(channel)); @@ -3302,7 +3323,7 @@ public: } rv = mServiceWorkerPrivate->SendFetchEvent(mChannel, mLoadGroup, - Move(mClientInfo), mIsReload); + mDocumentId, mIsReload); if (NS_WARN_IF(NS_FAILED(rv))) { HandleError(); } @@ -3314,25 +3335,29 @@ public: } // anonymous namespace already_AddRefed -ServiceWorkerManager::PrepareFetchEvent(const OriginAttributes& aOriginAttributes, +ServiceWorkerManager::PrepareFetchEvent(const PrincipalOriginAttributes& aOriginAttributes, nsIDocument* aDoc, + const nsAString& aDocumentIdForTopLevelNavigation, nsIInterceptedChannel* aChannel, bool aIsReload, bool aIsSubresourceLoad, ErrorResult& aRv) { MOZ_ASSERT(aChannel); - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); RefPtr serviceWorker; nsCOMPtr loadGroup; - UniquePtr clientInfo; + nsAutoString documentId; if (aIsSubresourceLoad) { MOZ_ASSERT(aDoc); serviceWorker = GetActiveWorkerInfoForDocument(aDoc); loadGroup = aDoc->GetDocumentLoadGroup(); - clientInfo.reset(new ServiceWorkerClientInfo(aDoc)); + nsresult rv = aDoc->GetOrCreateId(documentId); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } } else { nsCOMPtr internalChannel; aRv = aChannel->GetChannel(getter_AddRefs(internalChannel)); @@ -3342,6 +3367,8 @@ ServiceWorkerManager::PrepareFetchEvent(const OriginAttributes& aOriginAttribute internalChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + documentId = aDocumentIdForTopLevelNavigation; + nsCOMPtr uri; aRv = internalChannel->GetURI(getter_AddRefs(uri)); if (NS_WARN_IF(aRv.Failed())) { @@ -3370,7 +3397,7 @@ ServiceWorkerManager::PrepareFetchEvent(const OriginAttributes& aOriginAttribute nsCOMPtr continueRunnable = new ContinueDispatchFetchEventRunnable(serviceWorker->WorkerPrivate(), aChannel, loadGroup, - Move(clientInfo), aIsReload); + documentId, aIsReload); return continueRunnable.forget(); } @@ -3382,7 +3409,7 @@ ServiceWorkerManager::DispatchPreparedFetchEvent(nsIInterceptedChannel* aChannel { MOZ_ASSERT(aChannel); MOZ_ASSERT(aPreparedRunnable); - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); nsCOMPtr innerChannel; aRv = aChannel->GetChannel(getter_AddRefs(innerChannel)); @@ -3403,7 +3430,7 @@ ServiceWorkerManager::DispatchPreparedFetchEvent(nsIInterceptedChannel* aChannel } bool -ServiceWorkerManager::IsAvailable(const OriginAttributes& aOriginAttributes, +ServiceWorkerManager::IsAvailable(const PrincipalOriginAttributes& aOriginAttributes, nsIURI* aURI) { MOZ_ASSERT(aURI); @@ -3529,55 +3556,34 @@ ServiceWorkerManager::InvalidateServiceWorkerRegistrationWorker(ServiceWorkerReg } void -ServiceWorkerManager::SoftUpdate(nsIPrincipal* aPrincipal, - const nsACString& aScope, - ServiceWorkerUpdateFinishCallback* aCallback) +ServiceWorkerManager::NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration) { - MOZ_ASSERT(aPrincipal); + AssertIsOnMainThread(); + nsTObserverArray::ForwardIterator it(mServiceWorkerRegistrationListeners); + while (it.HasMore()) { + RefPtr target = it.GetNext(); + nsAutoString regScope; + target->GetScope(regScope); + MOZ_ASSERT(!regScope.IsEmpty()); - nsAutoCString scopeKey; - nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey); - if (NS_WARN_IF(NS_FAILED(rv))) { - return; + NS_ConvertUTF16toUTF8 utf8Scope(regScope); + + if (utf8Scope.Equals(aRegistration->mScope)) { + target->RegistrationRemoved(); + } } - - SoftUpdate(scopeKey, aScope, aCallback); } void ServiceWorkerManager::SoftUpdate(const OriginAttributes& aOriginAttributes, - const nsACString& aScope, - ServiceWorkerUpdateFinishCallback* aCallback) + const nsACString& aScope) { + AssertIsOnMainThread(); nsAutoCString scopeKey; aOriginAttributes.CreateSuffix(scopeKey); - SoftUpdate(scopeKey, aScope, aCallback); -} -namespace { - -// Empty callback. Only use when you really want to ignore errors. -class EmptyUpdateFinishCallback final : public ServiceWorkerUpdateFinishCallback -{ -public: - void - UpdateSucceeded(ServiceWorkerRegistrationInfo* aInfo) override - { } - - void - UpdateFailed(ErrorResult& aStatus) override - { } -}; - -} // anonymous namespace - -void -ServiceWorkerManager::SoftUpdate(const nsACString& aScopeKey, - const nsACString& aScope, - ServiceWorkerUpdateFinishCallback* aCallback) -{ RefPtr registration = - GetRegistration(aScopeKey, aScope); + GetRegistration(scopeKey, aScope); if (NS_WARN_IF(!registration)) { return; } @@ -3603,28 +3609,71 @@ ServiceWorkerManager::SoftUpdate(const nsACString& aScopeKey, // "Set registration's registering script url to newestWorker's script url." registration->mScriptSpec = newest->ScriptSpec(); - ServiceWorkerJobQueue* queue = - GetOrCreateJobQueue(aScopeKey, aScope); - MOZ_ASSERT(queue); + // "If the registration queue for registration is empty, invoke Update algorithm, + // or its equivalent, with client, registration as its argument." + // TODO(catalinb): We don't implement the force bypass cache flag. + // See: https://github.com/slightlyoff/ServiceWorker/issues/759 + if (!registration->mUpdating) { + ServiceWorkerJobQueue* queue = GetOrCreateJobQueue(scopeKey, aScope); + MOZ_ASSERT(queue); - RefPtr cb(aCallback); - if (!cb) { - cb = new EmptyUpdateFinishCallback(); + RefPtr job = + new ServiceWorkerRegisterJob(queue, registration, nullptr); + queue->Append(job); } +} + +void +ServiceWorkerManager::Update(nsIPrincipal* aPrincipal, + const nsACString& aScope, + ServiceWorkerUpdateFinishCallback* aCallback) +{ + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(aCallback); + + nsAutoCString scopeKey; + nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + RefPtr registration = + GetRegistration(scopeKey, aScope); + if (NS_WARN_IF(!registration)) { + return; + } + + // "If registration's uninstalling flag is set, abort these steps." + if (registration->mPendingUninstall) { + return; + } + + // "Let newestWorker be the result of running Get Newest Worker algorithm + // passing registration as its argument. + // If newestWorker is null, return a promise rejected with "InvalidStateError" + RefPtr newest = registration->Newest(); + if (!newest) { + ErrorResult error(NS_ERROR_DOM_INVALID_STATE_ERR); + aCallback->UpdateFailed(error); + + // In case the callback does not consume the exception + error.SuppressException(); + + return; + } + + // "Set registration's registering script url to newestWorker's script url." + registration->mScriptSpec = newest->ScriptSpec(); + + ServiceWorkerJobQueue* queue = + GetOrCreateJobQueue(scopeKey, aScope); + MOZ_ASSERT(queue); // "Invoke Update algorithm, or its equivalent, with client, registration as // its argument." - if (registration->IsUpdating()) { - // This is used to reduce burst of update events. If there is an update - // job in queue when we try to create a new one, drop current one and - // merge the callback function to existing update job. - // See. https://github.com/slightlyoff/ServiceWorker/issues/759 - registration->AppendUpdateCallback(cb); - } else { - RefPtr job = - new ServiceWorkerRegisterJob(queue, registration, cb); - queue->Append(job); - } + RefPtr job = + new ServiceWorkerRegisterJob(queue, registration, aCallback); + queue->Append(job); } namespace { @@ -3665,10 +3714,55 @@ FireControllerChangeOnDocument(nsIDocument* aDocument) } // anonymous namespace +UniquePtr +ServiceWorkerManager::GetClient(nsIPrincipal* aPrincipal, + const nsAString& aClientId, + ErrorResult& aRv) +{ + UniquePtr clientInfo; + nsCOMPtr ifptr = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID); + if (NS_WARN_IF(!ifptr)) { + return clientInfo; + } + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return clientInfo; + } + + nsresult rv = obs->NotifyObservers(ifptr, "service-worker-get-client", + PromiseFlatString(aClientId).get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return clientInfo; + } + + nsCOMPtr ptr; + ifptr->GetData(getter_AddRefs(ptr)); + nsCOMPtr doc = do_QueryInterface(ptr); + if (NS_WARN_IF(!doc)) { + return clientInfo; + } + + bool equals = false; + aPrincipal->Equals(doc->NodePrincipal(), &equals); + if (!equals) { + return clientInfo; + } + + if (!IsFromAuthenticatedOrigin(doc)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return clientInfo; + } + + clientInfo.reset(new ServiceWorkerClientInfo(doc)); + return clientInfo; +} + void ServiceWorkerManager::GetAllClients(nsIPrincipal* aPrincipal, const nsCString& aScope, - nsTArray& aControlledDocuments) + bool aIncludeUncontrolled, + nsTArray& aDocuments) { MOZ_ASSERT(aPrincipal); @@ -3680,21 +3774,65 @@ ServiceWorkerManager::GetAllClients(nsIPrincipal* aPrincipal, return; } - for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) { - ServiceWorkerRegistrationInfo* thisRegistration = iter.UserData(); - MOZ_ASSERT(thisRegistration); - if (!registration->mScope.Equals(thisRegistration->mScope)) { - continue; + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return; + } + + nsCOMPtr enumerator; + nsresult rv = obs->EnumerateObservers("service-worker-get-client", + getter_AddRefs(enumerator)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + auto ProcessDocument = [&aDocuments](nsIPrincipal* aPrincipal, nsIDocument* aDoc) { + if (!aDoc || !aDoc->GetWindow()) { + return; } - nsCOMPtr document = do_QueryInterface(iter.Key()); - - if (!document || !document->GetWindow()) { - continue; + bool equals = false; + aPrincipal->Equals(aDoc->NodePrincipal(), &equals); + if (!equals) { + return; } - ServiceWorkerClientInfo clientInfo(document); - aControlledDocuments.AppendElement(clientInfo); + if (!Preferences::GetBool("dom.serviceWorkers.testing.enabled") && + !IsFromAuthenticatedOrigin(aDoc)) { + return; + } + + ServiceWorkerClientInfo clientInfo(aDoc); + aDocuments.AppendElement(aDoc); + }; + + // Since it's not simple to check whether a document is in + // mControlledDocuments, we take different code paths depending on whether we + // need to look at all documents. The common parts of the two loops are + // factored out into the ProcessDocument lambda. + if (aIncludeUncontrolled) { + bool loop = true; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&loop)) && loop) { + nsCOMPtr ptr; + rv = enumerator->GetNext(getter_AddRefs(ptr)); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + + nsCOMPtr doc = do_QueryInterface(ptr); + ProcessDocument(aPrincipal, doc); + } + } else { + for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) { + ServiceWorkerRegistrationInfo* thisRegistration = iter.UserData(); + MOZ_ASSERT(thisRegistration); + if (!registration->mScope.Equals(thisRegistration->mScope)) { + continue; + } + + nsCOMPtr doc = do_QueryInterface(iter.Key()); + ProcessDocument(aPrincipal, doc); + } } } @@ -3727,7 +3865,7 @@ ServiceWorkerManager::MaybeClaimClient(nsIDocument* aDocument, StopControllingADocument(controllingRegistration); } - StartControllingADocument(aWorkerRegistration, aDocument); + StartControllingADocument(aWorkerRegistration, aDocument, NS_LITERAL_STRING("")); FireControllerChangeOnDocument(aDocument); } @@ -3760,7 +3898,7 @@ ServiceWorkerManager::SetSkipWaitingFlag(nsIPrincipal* aPrincipal, { RefPtr registration = GetRegistration(aPrincipal, aScope); - if (!registration) { + if (NS_WARN_IF(!registration)) { return NS_ERROR_FAILURE; } @@ -3774,6 +3912,7 @@ ServiceWorkerManager::SetSkipWaitingFlag(nsIPrincipal* aPrincipal, registration->TryToActivate(); } } else { + NS_WARNING("Failed to set skipWaiting flag, no matching worker."); return NS_ERROR_FAILURE; } @@ -4070,7 +4209,7 @@ ServiceWorkerManager::PropagateRemoveAll() } void -ServiceWorkerManager::RemoveAllRegistrations(OriginAttributes* aParams) +ServiceWorkerManager::RemoveAllRegistrations(PrincipalOriginAttributes* aParams) { AssertIsOnMainThread(); @@ -4163,6 +4302,125 @@ ServiceWorkerManager::RemoveListener(nsIServiceWorkerManagerListener* aListener) return NS_OK; } +NS_IMETHODIMP +ServiceWorkerManager::ShouldReportToWindow(nsIDOMWindow* aWindow, + const nsACString& aScope, + bool* aResult) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aResult); + + *aResult = false; + + // Get the inner window ID to compare to our document windows below. + nsCOMPtr targetWin = do_QueryInterface(aWindow); + if (NS_WARN_IF(!targetWin)) { + return NS_OK; + } + + targetWin = targetWin->GetScriptableTop(); + uint64_t winId = targetWin->WindowID(); + + // Check our weak registering document references first. This way we clear + // out as many dead weak references as possible when this method is called. + WeakDocumentList* list = mRegisteringDocuments.Get(aScope); + if (list) { + for (int32_t i = list->Length() - 1; i >= 0; --i) { + nsCOMPtr doc = do_QueryReferent(list->ElementAt(i)); + if (!doc) { + list->RemoveElementAt(i); + continue; + } + + if (!doc->IsCurrentActiveDocument()) { + continue; + } + + nsCOMPtr win = doc->GetWindow(); + if (!win) { + continue; + } + + win = win->GetScriptableTop(); + + // Match. We should report to this window. + if (win && winId == win->WindowID()) { + *aResult = true; + return NS_OK; + } + } + + if (list->IsEmpty()) { + list = nullptr; + nsAutoPtr doomed; + mRegisteringDocuments.RemoveAndForget(aScope, doomed); + } + } + + // Examine any windows performing a navigation that we are currently + // intercepting. + InterceptionList* intList = mNavigationInterceptions.Get(aScope); + if (intList) { + for (uint32_t i = 0; i < intList->Length(); ++i) { + nsCOMPtr channel = intList->ElementAt(i); + + nsCOMPtr inner; + nsresult rv = channel->GetChannel(getter_AddRefs(inner)); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + + uint64_t id = nsContentUtils::GetInnerWindowID(inner); + if (id == 0) { + continue; + } + + nsCOMPtr win = nsGlobalWindow::GetInnerWindowWithId(id); + if (!win) { + continue; + } + + win = win->GetScriptableTop(); + + // Match. We should report to this window. + if (win && winId == win->WindowID()) { + *aResult = true; + return NS_OK; + } + } + } + + // Next examine controlled documents to see if the windows match. + for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) { + ServiceWorkerRegistrationInfo* reg = iter.UserData(); + MOZ_ASSERT(reg); + if (!reg->mScope.Equals(aScope)) { + continue; + } + + nsCOMPtr doc = do_QueryInterface(iter.Key()); + if (!doc || !doc->IsCurrentActiveDocument()) { + continue; + } + + nsCOMPtr win = doc->GetWindow(); + if (!win) { + continue; + } + + win = win->GetScriptableTop(); + + // Match. We should report to this window. + if (win && winId == win->WindowID()) { + *aResult = true; + return NS_OK; + } + } + + // No match. We should not report to this window. + return NS_OK; +} + NS_IMETHODIMP ServiceWorkerManager::Observe(nsISupports* aSubject, const char* aTopic, @@ -4184,7 +4442,7 @@ ServiceWorkerManager::Observe(nsISupports* aSubject, if (strcmp(aTopic, CLEAR_ORIGIN_DATA) == 0) { MOZ_ASSERT(XRE_IsParentProcess()); - OriginAttributes attrs; + PrincipalOriginAttributes attrs; MOZ_ALWAYS_TRUE(attrs.Init(nsAutoString(aData))); RemoveAllRegistrations(&attrs); @@ -4225,9 +4483,9 @@ ServiceWorkerManager::PropagateSoftUpdate(JS::Handle aOriginAttribute const nsAString& aScope, JSContext* aCx) { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); - OriginAttributes attrs; + PrincipalOriginAttributes attrs; if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } @@ -4237,10 +4495,10 @@ ServiceWorkerManager::PropagateSoftUpdate(JS::Handle aOriginAttribute } void -ServiceWorkerManager::PropagateSoftUpdate(const OriginAttributes& aOriginAttributes, +ServiceWorkerManager::PropagateSoftUpdate(const PrincipalOriginAttributes& aOriginAttributes, const nsAString& aScope) { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); if (!mActor) { RefPtr runnable = @@ -4257,7 +4515,7 @@ ServiceWorkerManager::PropagateUnregister(nsIPrincipal* aPrincipal, nsIServiceWorkerUnregisterCallback* aCallback, const nsAString& aScope) { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); MOZ_ASSERT(aPrincipal); if (!mActor) { @@ -4349,7 +4607,7 @@ public: : mScope(aScope) , mChannel(aChannel) { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); MOZ_ASSERT(!aScope.IsEmpty()); MOZ_ASSERT(mChannel); } @@ -4416,6 +4674,28 @@ ServiceWorkerInfo::GetCacheName(nsAString& aCacheName) return NS_OK; } +NS_IMETHODIMP +ServiceWorkerInfo::GetDebugger(nsIWorkerDebugger** aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_FAILURE; + } + + return mServiceWorkerPrivate->GetDebugger(aResult); +} + +NS_IMETHODIMP +ServiceWorkerInfo::AttachDebugger() +{ + return mServiceWorkerPrivate->AttachDebugger(); +} + +NS_IMETHODIMP +ServiceWorkerInfo::DetachDebugger() +{ + return mServiceWorkerPrivate->DetachDebugger(); +} + void ServiceWorkerInfo::AppendWorker(ServiceWorker* aWorker) { @@ -4484,6 +4764,7 @@ private: void ServiceWorkerInfo::UpdateState(ServiceWorkerState aState) { + AssertIsOnMainThread(); #ifdef DEBUG // Any state can directly transition to redundant, but everything else is // ordered. @@ -4496,6 +4777,13 @@ ServiceWorkerInfo::UpdateState(ServiceWorkerState aState) // Activated can only go to redundant. MOZ_ASSERT_IF(mState == ServiceWorkerState::Activated, aState == ServiceWorkerState::Redundant); #endif + // Flush any pending functional events to the worker when it transitions to the + // activated state. + // TODO: Do we care that these events will race with the propagation of the + // state change? + if (aState == ServiceWorkerState::Activated && mState != aState) { + mServiceWorkerPrivate->Activated(); + } mState = aState; nsCOMPtr r = new ChangeStateUpdater(mInstances, mState); MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r.forget()))); @@ -4509,7 +4797,7 @@ ServiceWorkerInfo::ServiceWorkerInfo(ServiceWorkerRegistrationInfo* aReg, , mCacheName(aCacheName) , mState(ServiceWorkerState::EndGuard_) , mServiceWorkerID(GetNextID()) - , mServiceWorkerPrivate(new class ServiceWorkerPrivate(this)) + , mServiceWorkerPrivate(new ServiceWorkerPrivate(this)) , mSkipWaitingFlag(false) { MOZ_ASSERT(mRegistration); diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h index baf916c111..b652480d93 100644 --- a/dom/workers/ServiceWorkerManager.h +++ b/dom/workers/ServiceWorkerManager.h @@ -16,6 +16,7 @@ #include "mozilla/LinkedList.h" #include "mozilla/Preferences.h" #include "mozilla/TypedEnumBits.h" +#include "mozilla/UniquePtr.h" #include "mozilla/WeakPtr.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/Promise.h" @@ -24,9 +25,9 @@ #include "mozilla/dom/ServiceWorkerRegistrar.h" #include "mozilla/dom/ServiceWorkerRegistrarTypes.h" #include "mozilla/ipc/BackgroundUtils.h" -#include "nsIIPCBackgroundChildCreateCallback.h" #include "nsClassHashtable.h" #include "nsDataHashtable.h" +#include "nsIIPCBackgroundChildCreateCallback.h" #include "nsRefPtrHashtable.h" #include "nsTArrayForwardDeclare.h" #include "nsTObserverArray.h" @@ -35,7 +36,7 @@ class mozIApplicationClearPrivateDataParams; namespace mozilla { -class OriginAttributes; +class PrincipalOriginAttributes; namespace dom { @@ -47,13 +48,12 @@ class ServiceWorker; class ServiceWorkerClientInfo; class ServiceWorkerInfo; class ServiceWorkerJob; -class ServiceWorkerRegisterJob; class ServiceWorkerJobQueue; class ServiceWorkerManagerChild; class ServiceWorkerPrivate; -class ServiceWorkerUpdateFinishCallback; -class ServiceWorkerRegistrationInfo final : public nsIServiceWorkerRegistrationInfo +class ServiceWorkerRegistrationInfo final + : public nsIServiceWorkerRegistrationInfo { uint32_t mControlledDocumentsCounter; @@ -78,7 +78,11 @@ public: uint64_t mLastUpdateCheckTime; - RefPtr mUpdateJob; + // According to the spec, Soft Update shouldn't queue an update job + // if the registration queue is not empty. Because our job queue + // works slightly different, we use a flag to determine if the registration + // is already updating. + bool mUpdating; // When unregister() is called on a registration, it is not immediately // removed since documents may be controlled. It is marked as @@ -115,13 +119,14 @@ public: void StopControllingADocument() { + MOZ_ASSERT(mControlledDocumentsCounter); --mControlledDocumentsCounter; } bool IsControllingDocuments() const { - return mActiveWorker && mControlledDocumentsCounter > 0; + return mActiveWorker && mControlledDocumentsCounter; } void @@ -147,19 +152,13 @@ public: void NotifyListenersOnChange(); - - bool - IsUpdating() const; - - void - AppendUpdateCallback(ServiceWorkerUpdateFinishCallback* aCallback); }; class ServiceWorkerUpdateFinishCallback { protected: virtual ~ServiceWorkerUpdateFinishCallback() - { } + {} public: NS_INLINE_DECL_REFCOUNTING(ServiceWorkerUpdateFinishCallback) @@ -281,6 +280,7 @@ public: void SetActivateStateUncheckedWithoutEvent(ServiceWorkerState aState) { + AssertIsOnMainThread(); mState = aState; } @@ -313,7 +313,9 @@ class ServiceWorkerManager final friend class GetRegistrationsRunnable; friend class GetRegistrationRunnable; friend class ServiceWorkerJobQueue; + friend class ServiceWorkerInstallJob; friend class ServiceWorkerRegisterJob; + friend class ServiceWorkerJobBase; friend class ServiceWorkerRegistrationInfo; friend class ServiceWorkerUnregisterJob; @@ -351,17 +353,18 @@ public: nsClassHashtable mNavigationInterceptions; bool - IsAvailable(const OriginAttributes& aOriginAttributes, nsIURI* aURI); + IsAvailable(const PrincipalOriginAttributes& aOriginAttributes, nsIURI* aURI); bool IsControlled(nsIDocument* aDocument, ErrorResult& aRv); already_AddRefed - PrepareFetchEvent(const OriginAttributes& aOriginAttributes, + PrepareFetchEvent(const PrincipalOriginAttributes& aOriginAttributes, nsIDocument* aDoc, + const nsAString& aDocumentIdForTopLevelNavigation, nsIInterceptedChannel* aChannel, bool aIsReload, - bool aIsSubresourceLoad, + bool aIsSubresourceLoad, ErrorResult& aRv); void @@ -370,17 +373,16 @@ public: ErrorResult& aRv); void - SoftUpdate(nsIPrincipal* aPrincipal, - const nsACString& aScope, - ServiceWorkerUpdateFinishCallback* aCallback = nullptr); + Update(nsIPrincipal* aPrincipal, + const nsACString& aScope, + ServiceWorkerUpdateFinishCallback* aCallback); void SoftUpdate(const OriginAttributes& aOriginAttributes, - const nsACString& aScope, - ServiceWorkerUpdateFinishCallback* aCallback = nullptr); + const nsACString& aScope); void - PropagateSoftUpdate(const OriginAttributes& aOriginAttributes, + PropagateSoftUpdate(const PrincipalOriginAttributes& aOriginAttributes, const nsAString& aScope); void @@ -434,10 +436,16 @@ public: uint32_t aFlags, JSExnType aExnType); + UniquePtr + GetClient(nsIPrincipal* aPrincipal, + const nsAString& aClientId, + ErrorResult& aRv); + void GetAllClients(nsIPrincipal* aPrincipal, const nsCString& aScope, - nsTArray& aControlledDocuments); + bool aIncludeUncontrolled, + nsTArray& aDocuments); void MaybeClaimClient(nsIDocument* aDocument, @@ -486,11 +494,6 @@ private: void MaybeRemoveRegistrationInfo(const nsACString& aScopeKey); - void - SoftUpdate(const nsACString& aScopeKey, - const nsACString& aScope, - ServiceWorkerUpdateFinishCallback* aCallback = nullptr); - already_AddRefed GetRegistration(const nsACString& aScopeKey, const nsACString& aScope) const; @@ -502,7 +505,8 @@ private: Update(ServiceWorkerRegistrationInfo* aRegistration); nsresult - GetDocumentRegistration(nsIDocument* aDoc, ServiceWorkerRegistrationInfo** aRegistrationInfo); + GetDocumentRegistration(nsIDocument* aDoc, + ServiceWorkerRegistrationInfo** aRegistrationInfo); NS_IMETHODIMP GetServiceWorkerForScope(nsIDOMWindow* aWindow, @@ -511,7 +515,7 @@ private: nsISupports** aServiceWorker); ServiceWorkerInfo* - GetActiveWorkerInfoForScope(const OriginAttributes& aOriginAttributes, + GetActiveWorkerInfoForScope(const PrincipalOriginAttributes& aOriginAttributes, const nsACString& aScope); ServiceWorkerInfo* @@ -521,9 +525,13 @@ private: InvalidateServiceWorkerRegistrationWorker(ServiceWorkerRegistrationInfo* aRegistration, WhichServiceWorker aWhichOnes); + void + NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration); + void StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration, - nsIDocument* aDoc); + nsIDocument* aDoc, + const nsAString& aDocumentId); void StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration); @@ -538,7 +546,7 @@ private: GetServiceWorkerRegistrationInfo(nsIPrincipal* aPrincipal, nsIURI* aURI); already_AddRefed - GetServiceWorkerRegistrationInfo(const OriginAttributes& aOriginAttributes, + GetServiceWorkerRegistrationInfo(const PrincipalOriginAttributes& aOriginAttributes, nsIURI* aURI); already_AddRefed @@ -577,7 +585,8 @@ private: FireControllerChange(ServiceWorkerRegistrationInfo* aRegistration); void - StorePendingReadyPromise(nsPIDOMWindow* aWindow, nsIURI* aURI, Promise* aPromise); + StorePendingReadyPromise(nsPIDOMWindow* aWindow, nsIURI* aURI, + Promise* aPromise); void CheckPendingReadyPromises(); @@ -585,11 +594,11 @@ private: bool CheckReadyPromise(nsPIDOMWindow* aWindow, nsIURI* aURI, Promise* aPromise); - struct PendingReadyPromise + struct PendingReadyPromise final { PendingReadyPromise(nsIURI* aURI, Promise* aPromise) : mURI(aURI), mPromise(aPromise) - { } + {} nsCOMPtr mURI; RefPtr mPromise; @@ -620,7 +629,7 @@ private: // Removes all service worker registrations that matches the given // mozIApplicationClearPrivateDataParams. void - RemoveAllRegistrations(OriginAttributes* aParams); + RemoveAllRegistrations(PrincipalOriginAttributes* aParams); RefPtr mActor; diff --git a/dom/workers/ServiceWorkerManagerChild.cpp b/dom/workers/ServiceWorkerManagerChild.cpp index a4d1d3678e..48a45ae8b9 100644 --- a/dom/workers/ServiceWorkerManagerChild.cpp +++ b/dom/workers/ServiceWorkerManagerChild.cpp @@ -32,7 +32,7 @@ ServiceWorkerManagerChild::RecvNotifyRegister( bool ServiceWorkerManagerChild::RecvNotifySoftUpdate( - const OriginAttributes& aOriginAttributes, + const PrincipalOriginAttributes& aOriginAttributes, const nsString& aScope) { if (mShuttingDown) { @@ -42,7 +42,7 @@ ServiceWorkerManagerChild::RecvNotifySoftUpdate( RefPtr swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm); - swm->SoftUpdate(aOriginAttributes, NS_ConvertUTF16toUTF8(aScope), nullptr); + swm->SoftUpdate(aOriginAttributes, NS_ConvertUTF16toUTF8(aScope)); return true; } diff --git a/dom/workers/ServiceWorkerManagerChild.h b/dom/workers/ServiceWorkerManagerChild.h index e78f9e4c69..6f42b244c8 100644 --- a/dom/workers/ServiceWorkerManagerChild.h +++ b/dom/workers/ServiceWorkerManagerChild.h @@ -12,7 +12,7 @@ namespace mozilla { -class OriginAttributes; +class PrincipalOriginAttributes; namespace ipc { class BackgroundChildImpl; @@ -36,7 +36,7 @@ public: virtual bool RecvNotifyRegister(const ServiceWorkerRegistrationData& aData) override; - virtual bool RecvNotifySoftUpdate(const OriginAttributes& aOriginAttributes, + virtual bool RecvNotifySoftUpdate(const PrincipalOriginAttributes& aOriginAttributes, const nsString& aScope) override; virtual bool RecvNotifyUnregister(const PrincipalInfo& aPrincipalInfo, diff --git a/dom/workers/ServiceWorkerManagerParent.cpp b/dom/workers/ServiceWorkerManagerParent.cpp index 3885e4e510..03f74d92a0 100644 --- a/dom/workers/ServiceWorkerManagerParent.cpp +++ b/dom/workers/ServiceWorkerManagerParent.cpp @@ -232,7 +232,7 @@ ServiceWorkerManagerParent::RecvUnregister(const PrincipalInfo& aPrincipalInfo, } bool -ServiceWorkerManagerParent::RecvPropagateSoftUpdate(const OriginAttributes& aOriginAttributes, +ServiceWorkerManagerParent::RecvPropagateSoftUpdate(const PrincipalOriginAttributes& aOriginAttributes, const nsString& aScope) { AssertIsOnBackgroundThread(); diff --git a/dom/workers/ServiceWorkerManagerParent.h b/dom/workers/ServiceWorkerManagerParent.h index b7b977816e..4bca5e6024 100644 --- a/dom/workers/ServiceWorkerManagerParent.h +++ b/dom/workers/ServiceWorkerManagerParent.h @@ -11,11 +11,11 @@ namespace mozilla { -class OriginAttributes; +class PrincipalOriginAttributes; namespace ipc { class BackgroundParentImpl; -} +} // namespace ipc namespace dom { namespace workers { @@ -42,7 +42,7 @@ private: virtual bool RecvUnregister(const PrincipalInfo& aPrincipalInfo, const nsString& aScope) override; - virtual bool RecvPropagateSoftUpdate(const OriginAttributes& aOriginAttributes, + virtual bool RecvPropagateSoftUpdate(const PrincipalOriginAttributes& aOriginAttributes, const nsString& aScope) override; virtual bool RecvPropagateUnregister(const PrincipalInfo& aPrincipalInfo, @@ -63,8 +63,8 @@ private: uint64_t mID; }; -} // workers namespace -} // dom namespace -} // mozilla namespace +} // namespace workers +} // namespace dom +} // namespace mozilla #endif // mozilla_dom_ServiceWorkerManagerParent_h diff --git a/dom/workers/ServiceWorkerManagerService.cpp b/dom/workers/ServiceWorkerManagerService.cpp index 7e28a5f949..b73eb86dea 100644 --- a/dom/workers/ServiceWorkerManagerService.cpp +++ b/dom/workers/ServiceWorkerManagerService.cpp @@ -111,7 +111,7 @@ ServiceWorkerManagerService::PropagateRegistration( void ServiceWorkerManagerService::PropagateSoftUpdate( uint64_t aParentID, - const OriginAttributes& aOriginAttributes, + const PrincipalOriginAttributes& aOriginAttributes, const nsAString& aScope) { AssertIsOnBackgroundThread(); diff --git a/dom/workers/ServiceWorkerManagerService.h b/dom/workers/ServiceWorkerManagerService.h index 2e42485dc5..1a11afbbec 100644 --- a/dom/workers/ServiceWorkerManagerService.h +++ b/dom/workers/ServiceWorkerManagerService.h @@ -13,7 +13,7 @@ namespace mozilla { -class OriginAttributes; +class PrincipalOriginAttributes; namespace ipc { class PrincipalInfo; @@ -42,7 +42,7 @@ public: ServiceWorkerRegistrationData& aData); void PropagateSoftUpdate(uint64_t aParentID, - const OriginAttributes& aOriginAttributes, + const PrincipalOriginAttributes& aOriginAttributes, const nsAString& aScope); void PropagateUnregister(uint64_t aParentID, diff --git a/dom/workers/ServiceWorkerPrivate.cpp b/dom/workers/ServiceWorkerPrivate.cpp index 2f5a887795..980d35da98 100644 --- a/dom/workers/ServiceWorkerPrivate.cpp +++ b/dom/workers/ServiceWorkerPrivate.cpp @@ -61,6 +61,7 @@ NS_IMPL_ISUPPORTS0(KeepAliveToken) ServiceWorkerPrivate::ServiceWorkerPrivate(ServiceWorkerInfo* aInfo) : mInfo(aInfo) , mIsPushWorker(false) + , mDebuggerCount(0) , mTokenCount(0) { AssertIsOnMainThread(); @@ -103,46 +104,64 @@ namespace { class CheckScriptEvaluationWithCallback final : public WorkerRunnable { nsMainThreadPtrHandle mKeepAliveToken; - RefPtr mCallback; + RefPtr mCallback; + DebugOnly mDone; public: CheckScriptEvaluationWithCallback(WorkerPrivate* aWorkerPrivate, KeepAliveToken* aKeepAliveToken, - nsRunnable* aCallback) + LifeCycleEventCallback* aCallback) : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount) , mKeepAliveToken(new nsMainThreadPtrHolder(aKeepAliveToken)) , mCallback(aCallback) + , mDone(false) { AssertIsOnMainThread(); } + ~CheckScriptEvaluationWithCallback() + { + MOZ_ASSERT(mDone); + } + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { aWorkerPrivate->AssertIsOnWorkerThread(); - if (aWorkerPrivate->WorkerScriptExecutedSuccessfully()) { - nsresult rv = NS_DispatchToMainThread(mCallback); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to dispatch CheckScriptEvaluation callback."); - } - } + Done(aWorkerPrivate->WorkerScriptExecutedSuccessfully()); return true; } + + NS_IMETHOD + Cancel() override + { + Done(false); + return WorkerRunnable::Cancel(); + } + +private: + void + Done(bool aResult) + { + mDone = true; + mCallback->SetResult(aResult); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(mCallback))); + } }; } // anonymous namespace nsresult -ServiceWorkerPrivate::ContinueOnSuccessfulScriptEvaluation(nsRunnable* aCallback) +ServiceWorkerPrivate::CheckScriptEvaluation(LifeCycleEventCallback* aCallback) { nsresult rv = SpawnWorkerIfNeeded(LifeCycleEvent, nullptr); NS_ENSURE_SUCCESS(rv, rv); MOZ_ASSERT(mKeepAliveToken); RefPtr r = new CheckScriptEvaluationWithCallback(mWorkerPrivate, - mKeepAliveToken, - aCallback); + mKeepAliveToken, + aCallback); AutoJSAPI jsapi; jsapi.Init(); if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) { @@ -211,7 +230,7 @@ public: RefPtr swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm); - OriginAttributes attrs = + PrincipalOriginAttributes attrs = mozilla::BasePrincipal::Cast(mRegistration->mPrincipal)->OriginAttributesRef(); swm->PropagateSoftUpdate(attrs, @@ -352,6 +371,15 @@ public: return DispatchLifecycleEvent(aCx, aWorkerPrivate); } + NS_IMETHOD + Cancel() override + { + mCallback->SetResult(false); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(mCallback))); + + return WorkerRunnable::Cancel(); + } + private: bool DispatchLifecycleEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate); @@ -359,52 +387,116 @@ private: }; /* - * Used to handle ExtendableEvent::waitUntil() and proceed with - * installation/activation. + * Used to handle ExtendableEvent::waitUntil() and catch abnormal worker + * termination during the execution of life cycle events. It is responsible + * with advancing the job queue for install/activate tasks. */ -class LifecycleEventPromiseHandler final : public PromiseNativeHandler +class LifeCycleEventWatcher final : public PromiseNativeHandler, + public WorkerFeature { + WorkerPrivate* mWorkerPrivate; RefPtr mCallback; + bool mDone; - virtual - ~LifecycleEventPromiseHandler() - { } + ~LifeCycleEventWatcher() + { + if (mDone) { + return; + } + + MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate); + // XXXcatalinb: If all the promises passed to waitUntil go out of scope, + // the resulting Promise.all will be cycle collected and it will drop its + // native handlers (including this object). Instead of waiting for a timeout + // we report the failure now. + JSContext* cx = mWorkerPrivate->GetJSContext(); + ReportResult(cx, false); + } public: NS_DECL_ISUPPORTS - explicit LifecycleEventPromiseHandler(LifeCycleEventCallback* aCallback) - : mCallback(aCallback) + LifeCycleEventWatcher(WorkerPrivate* aWorkerPrivate, + LifeCycleEventCallback* aCallback) + : mWorkerPrivate(aWorkerPrivate) + , mCallback(aCallback) + , mDone(false) { - MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + } + + bool + Init() + { + MOZ_ASSERT(mWorkerPrivate); + mWorkerPrivate->AssertIsOnWorkerThread(); + JSContext* cx = mWorkerPrivate->GetJSContext(); + + // We need to listen for worker termination in case the event handler + // never completes or never resolves the waitUntil promise. There are + // two possible scenarios: + // 1. The keepAlive token expires and the worker is terminated, in which + // case the registration/update promise will be rejected + // 2. A new service worker is registered which will terminate the current + // installing worker. + if (NS_WARN_IF(!mWorkerPrivate->AddFeature(cx, this))) { + NS_WARNING("LifeCycleEventWatcher failed to add feature."); + ReportResult(cx, false); + return false; + } + + return true; + } + + bool + Notify(JSContext* aCx, Status aStatus) override + { + if (aStatus < Terminating) { + return true; + } + + MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate); + ReportResult(aCx, false); + + return true; + } + + void + ReportResult(JSContext* aCx, bool aResult) + { + mWorkerPrivate->AssertIsOnWorkerThread(); + + if (mDone) { + return; + } + mDone = true; + + mCallback->SetResult(aResult); + nsresult rv = NS_DispatchToMainThread(mCallback); + if (NS_WARN_IF(NS_FAILED(rv))) { + NS_RUNTIMEABORT("Failed to dispatch life cycle event handler."); + } + + mWorkerPrivate->RemoveFeature(aCx, this); } void ResolvedCallback(JSContext* aCx, JS::Handle aValue) override { - WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); - MOZ_ASSERT(workerPrivate); - workerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate); + mWorkerPrivate->AssertIsOnWorkerThread(); - mCallback->SetResult(true); - nsresult rv = NS_DispatchToMainThread(mCallback); - if (NS_WARN_IF(NS_FAILED(rv))) { - NS_RUNTIMEABORT("Failed to dispatch life cycle event handler."); - } + ReportResult(aCx, true); } void RejectedCallback(JSContext* aCx, JS::Handle aValue) override { - WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); - MOZ_ASSERT(workerPrivate); - workerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate); + mWorkerPrivate->AssertIsOnWorkerThread(); - mCallback->SetResult(false); - nsresult rv = NS_DispatchToMainThread(mCallback); - if (NS_WARN_IF(NS_FAILED(rv))) { - NS_RUNTIMEABORT("Failed to dispatch life cycle event handler."); - } + ReportResult(aCx, false); // Note, all WaitUntil() rejections are reported to client consoles // by the WaitUntilHandler in ServiceWorkerEvents. This ensures that @@ -413,7 +505,7 @@ public: } }; -NS_IMPL_ISUPPORTS0(LifecycleEventPromiseHandler) +NS_IMPL_ISUPPORTS0(LifeCycleEventWatcher) bool LifecycleEventWorkerRunnable::DispatchLifecycleEvent(JSContext* aCx, @@ -436,16 +528,23 @@ LifecycleEventWorkerRunnable::DispatchLifecycleEvent(JSContext* aCx, event->SetTrusted(true); + // It is important to initialize the watcher before actually dispatching + // the event in order to catch worker termination while the event handler + // is still executing. This can happen with infinite loops, for example. + RefPtr watcher = + new LifeCycleEventWatcher(aWorkerPrivate, mCallback); + + if (!watcher->Init()) { + return true; + } + RefPtr waitUntil; DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(), event, getter_AddRefs(waitUntil)); if (waitUntil) { - RefPtr handler = - new LifecycleEventPromiseHandler(mCallback); - waitUntil->AppendNativeHandler(handler); + waitUntil->AppendNativeHandler(watcher); } else { - mCallback->SetResult(false); - MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(mCallback))); + watcher->ReportResult(aCx, false); } return true; @@ -590,6 +689,14 @@ ServiceWorkerPrivate::SendPushEvent(const Maybe>& aData, mKeepAliveToken, aData, regInfo); + + if (mInfo->State() == ServiceWorkerState::Activating) { + mPendingFunctionalEvents.AppendElement(r.forget()); + return NS_OK; + } + + MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated); + AutoJSAPI jsapi; jsapi.Init(); if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) { @@ -909,6 +1016,7 @@ class FetchEventRunnable : public ExtendableFunctionalEventWorkerRunnable nsTArray mHeaderValues; nsCString mSpec; nsCString mMethod; + nsString mClientId; bool mIsReload; DebugOnly mIsHttpChannel; RequestMode mRequestMode; @@ -925,12 +1033,13 @@ public: // later on. const nsACString& aScriptSpec, nsMainThreadPtrHandle& aRegistration, - UniquePtr&& aClientInfo, + const nsAString& aDocumentId, bool aIsReload) : ExtendableFunctionalEventWorkerRunnable( aWorkerPrivate, aKeepAliveToken, aRegistration) , mInterceptedChannel(aChannel) , mScriptSpec(aScriptSpec) + , mClientId(aDocumentId) , mIsReload(aIsReload) , mIsHttpChannel(false) , mRequestMode(RequestMode::No_cors) @@ -1057,6 +1166,17 @@ public: return DispatchFetchEvent(aCx, aWorkerPrivate); } + NS_IMETHOD + Cancel() override + { + nsCOMPtr runnable = new ResumeRequest(mInterceptedChannel); + if (NS_FAILED(NS_DispatchToMainThread(runnable))) { + NS_WARNING("Failed to resume channel on FetchEventRunnable::Cancel()!\n"); + } + WorkerRunnable::Cancel(); + return NS_OK; + } + private: ~FetchEventRunnable() {} @@ -1143,6 +1263,7 @@ private: init.mRequest.Value() = request; init.mBubbles = false; init.mCancelable = true; + init.mClientId = mClientId; init.mIsReload = mIsReload; RefPtr event = FetchEvent::Constructor(globalObj, NS_LITERAL_STRING("fetch"), init, result); @@ -1243,9 +1364,11 @@ NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable, nsIHttpHeaderVis nsresult ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel, nsILoadGroup* aLoadGroup, - UniquePtr&& aClientInfo, + const nsAString& aDocumentId, bool aIsReload) { + AssertIsOnMainThread(); + // if the ServiceWorker script fails to load for some reason, just resume // the original channel. nsCOMPtr failRunnable = @@ -1273,12 +1396,19 @@ ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel, RefPtr r = new FetchEventRunnable(mWorkerPrivate, mKeepAliveToken, handle, mInfo->ScriptSpec(), regInfo, - Move(aClientInfo), aIsReload); + aDocumentId, aIsReload); rv = r->Init(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + if (mInfo->State() == ServiceWorkerState::Activating) { + mPendingFunctionalEvents.AppendElement(r.forget()); + return NS_OK; + } + + MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated); + AutoJSAPI jsapi; jsapi.Init(); if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) { @@ -1303,7 +1433,7 @@ ServiceWorkerPrivate::SpawnWorkerIfNeeded(WakeUpReason aWhy, if (mWorkerPrivate) { mWorkerPrivate->UpdateOverridenLoadGroup(aLoadGroup); - ResetIdleTimeout(aWhy); + RenewKeepAliveToken(aWhy); return NS_OK; } @@ -1383,7 +1513,7 @@ ServiceWorkerPrivate::SpawnWorkerIfNeeded(WakeUpReason aWhy, } mIsPushWorker = false; - ResetIdleTimeout(aWhy); + RenewKeepAliveToken(aWhy); return NS_OK; } @@ -1425,6 +1555,15 @@ ServiceWorkerPrivate::TerminateWorker() NS_WARN_IF(!mWorkerPrivate->Terminate(jsapi.cx())); mWorkerPrivate = nullptr; mSupportsArray.Clear(); + + // Any pending events are never going to fire on this worker. Cancel + // them so that intercepted channels can be reset and other resources + // cleaned up. + nsTArray> pendingEvents; + mPendingFunctionalEvents.SwapElements(pendingEvents); + for (uint32_t i = 0; i < pendingEvents.Length(); ++i) { + pendingEvents[i]->Cancel(); + } } } @@ -1440,13 +1579,98 @@ void ServiceWorkerPrivate::NoteStoppedControllingDocuments() { AssertIsOnMainThread(); - if (mIsPushWorker) { + if (mIsPushWorker || mDebuggerCount) { return; } TerminateWorker(); } +void +ServiceWorkerPrivate::Activated() +{ + AssertIsOnMainThread(); + + // If we had to queue up events due to the worker activating, that means + // the worker must be currently running. We should be called synchronously + // when the worker becomes activated. + MOZ_ASSERT_IF(!mPendingFunctionalEvents.IsEmpty(), mWorkerPrivate); + + nsTArray> pendingEvents; + mPendingFunctionalEvents.SwapElements(pendingEvents); + + for (uint32_t i = 0; i < pendingEvents.Length(); ++i) { + RefPtr r = pendingEvents[i].forget(); + AutoJSAPI jsapi; + jsapi.Init(); + if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) { + NS_WARNING("Failed to dispatch pending functional event!"); + } + } +} + +nsresult +ServiceWorkerPrivate::GetDebugger(nsIWorkerDebugger** aResult) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aResult); + + if (!mDebuggerCount) { + return NS_OK; + } + + MOZ_ASSERT(mWorkerPrivate); + + nsCOMPtr debugger = do_QueryInterface(mWorkerPrivate->Debugger()); + debugger.forget(aResult); + + return NS_OK; +} + +nsresult +ServiceWorkerPrivate::AttachDebugger() +{ + AssertIsOnMainThread(); + + // When the first debugger attaches to a worker, we spawn a worker if needed, + // and cancel the idle timeout. The idle timeout should not be reset until + // the last debugger detached from the worker. + if (!mDebuggerCount) { + nsresult rv = SpawnWorkerIfNeeded(AttachEvent, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + mIdleWorkerTimer->Cancel(); + } + + ++mDebuggerCount; + + return NS_OK; +} + +nsresult +ServiceWorkerPrivate::DetachDebugger() +{ + AssertIsOnMainThread(); + + if (!mDebuggerCount) { + return NS_ERROR_UNEXPECTED; + } + + --mDebuggerCount; + + // When the last debugger detaches from a worker, we either reset the idle + // timeout, or terminate the worker if there are no more active tokens. + if (!mDebuggerCount) { + if (mTokenCount) { + ResetIdleTimeout(); + } else { + TerminateWorker(); + } + } + + return NS_OK; +} + /* static */ void ServiceWorkerPrivate::NoteIdleWorkerCallback(nsITimer* aTimer, void* aPrivate) { @@ -1490,24 +1714,36 @@ ServiceWorkerPrivate::TerminateWorkerCallback(nsITimer* aTimer, void *aPrivate) } void -ServiceWorkerPrivate::ResetIdleTimeout(WakeUpReason aWhy) +ServiceWorkerPrivate::RenewKeepAliveToken(WakeUpReason aWhy) { - // We should have an active worker if we're reseting the idle timeout + // We should have an active worker if we're renewing the keep alive token. MOZ_ASSERT(mWorkerPrivate); if (aWhy == PushEvent || aWhy == PushSubscriptionChangeEvent) { mIsPushWorker = true; } + // If there is at least one debugger attached to the worker, the idle worker + // timeout was canceled when the first debugger attached to the worker. It + // should not be reset until the last debugger detaches from the worker. + if (!mDebuggerCount) { + ResetIdleTimeout(); + } + + if (!mKeepAliveToken) { + mKeepAliveToken = new KeepAliveToken(this); + } +} + +void +ServiceWorkerPrivate::ResetIdleTimeout() +{ uint32_t timeout = Preferences::GetInt("dom.serviceWorkers.idle_timeout"); DebugOnly rv = mIdleWorkerTimer->InitWithFuncCallback(ServiceWorkerPrivate::NoteIdleWorkerCallback, this, timeout, nsITimer::TYPE_ONE_SHOT); MOZ_ASSERT(NS_SUCCEEDED(rv)); - if (!mKeepAliveToken) { - mKeepAliveToken = new KeepAliveToken(this); - } } void diff --git a/dom/workers/ServiceWorkerPrivate.h b/dom/workers/ServiceWorkerPrivate.h index 4ba25ac277..43db837c84 100644 --- a/dom/workers/ServiceWorkerPrivate.h +++ b/dom/workers/ServiceWorkerPrivate.h @@ -72,11 +72,9 @@ public: UniquePtr&& aClientInfo); // This is used to validate the worker script and continue the installation - // process. Note that the callback is dispatched to the main thread - // ONLY if the evaluation was successful. Failure is handled by the JS - // exception handler which will call ServiceWorkerManager::HandleError. + // process. nsresult - ContinueOnSuccessfulScriptEvaluation(nsRunnable* aCallback); + CheckScriptEvaluation(LifeCycleEventCallback* aCallback); nsresult SendLifeCycleEvent(const nsAString& aEventType, @@ -105,7 +103,7 @@ public: nsresult SendFetchEvent(nsIInterceptedChannel* aChannel, nsILoadGroup* aLoadGroup, - UniquePtr&& aClientInfo, + const nsAString& aDocumentId, bool aIsReload); void @@ -128,6 +126,24 @@ public: void NoteStoppedControllingDocuments(); + WorkerPrivate* + GetWorkerPrivate() const + { + return mWorkerPrivate; + } + + void + Activated(); + + nsresult + GetDebugger(nsIWorkerDebugger** aResult); + + nsresult + AttachDebugger(); + + nsresult + DetachDebugger(); + private: enum WakeUpReason { FetchEvent = 0, @@ -135,7 +151,8 @@ private: PushSubscriptionChangeEvent, MessageEvent, NotificationClickEvent, - LifeCycleEvent + LifeCycleEvent, + AttachEvent }; // Timer callbacks @@ -146,7 +163,10 @@ private: TerminateWorkerCallback(nsITimer* aTimer, void *aPrivate); void - ResetIdleTimeout(WakeUpReason aWhy); + RenewKeepAliveToken(WakeUpReason aWhy); + + void + ResetIdleTimeout(); void AddToken(); @@ -184,6 +204,8 @@ private: // worker a grace period after each event. RefPtr mKeepAliveToken; + uint64_t mDebuggerCount; + uint64_t mTokenCount; // Meant for keeping objects alive while handling requests from the worker @@ -191,6 +213,10 @@ private: // |StoreISupports| and |RemoveISupports|. Note that the array is also // cleared whenever the worker is terminated. nsTArray> mSupportsArray; + + // Array of function event worker runnables that are pending due to + // the worker activating. Main thread only. + nsTArray> mPendingFunctionalEvents; }; } // namespace workers diff --git a/dom/workers/ServiceWorkerRegistrar.cpp b/dom/workers/ServiceWorkerRegistrar.cpp index 1b07b0753b..b9d06c47d6 100644 --- a/dom/workers/ServiceWorkerRegistrar.cpp +++ b/dom/workers/ServiceWorkerRegistrar.cpp @@ -326,7 +326,7 @@ ServiceWorkerRegistrar::ReadData() nsAutoCString suffix; GET_LINE(suffix); - OriginAttributes attrs; + PrincipalOriginAttributes attrs; if (!attrs.PopulateFromSuffix(suffix)) { return NS_ERROR_INVALID_ARG; } diff --git a/dom/workers/ServiceWorkerRegistration.cpp b/dom/workers/ServiceWorkerRegistration.cpp index 3367c91f1c..a29f392886 100644 --- a/dom/workers/ServiceWorkerRegistration.cpp +++ b/dom/workers/ServiceWorkerRegistration.cpp @@ -250,6 +250,19 @@ ServiceWorkerRegistrationMainThread::InvalidateWorkers(WhichServiceWorker aWhich if (aWhichOnes & WhichServiceWorker::ACTIVE_WORKER) { mActiveWorker = nullptr; } + +} + +void +ServiceWorkerRegistrationMainThread::RegistrationRemoved() +{ + // If the registration is being removed completely, remove it from the + // window registration hash table so that a new registration would get a new + // wrapper JS object. + nsCOMPtr window = GetOwner(); + if (window) { + window->InvalidateServiceWorkerRegistration(mScope); + } } namespace { @@ -266,8 +279,7 @@ UpdateInternal(nsIPrincipal* aPrincipal, RefPtr swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm); - // The spec defines ServiceWorkerRegistration.update() exactly as Soft Update. - swm->SoftUpdate(aPrincipal, NS_ConvertUTF16toUTF8(aScope), aCallback); + swm->Update(aPrincipal, NS_ConvertUTF16toUTF8(aScope), aCallback); } class MainThreadUpdateCallback final : public ServiceWorkerUpdateFinishCallback @@ -393,14 +405,22 @@ public: AssertIsOnMainThread(); ErrorResult result; - MutexAutoLock lock(mPromiseProxy->Lock()); - if (mPromiseProxy->CleanedUp()) { - return NS_OK; + nsCOMPtr principal; + // UpdateInternal may try to reject the promise synchronously leading + // to a deadlock. + { + MutexAutoLock lock(mPromiseProxy->Lock()); + if (mPromiseProxy->CleanedUp()) { + return NS_OK; + } + + principal = mPromiseProxy->GetWorkerPrivate()->GetPrincipal(); } + MOZ_ASSERT(principal); RefPtr cb = new WorkerThreadUpdateCallback(mPromiseProxy); - UpdateInternal(mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), mScope, cb); + UpdateInternal(principal, mScope, cb); return NS_OK; } @@ -857,6 +877,12 @@ public: // FIXME(nsm); } + void + RegistrationRemoved() override + { + AssertIsOnMainThread(); + } + void GetScope(nsAString& aScope) const override { diff --git a/dom/workers/ServiceWorkerRegistration.h b/dom/workers/ServiceWorkerRegistration.h index eefed46cbc..5002fe611a 100644 --- a/dom/workers/ServiceWorkerRegistration.h +++ b/dom/workers/ServiceWorkerRegistration.h @@ -65,6 +65,9 @@ public: virtual void InvalidateWorkers(WhichServiceWorker aWhichOnes) = 0; + virtual void + RegistrationRemoved() = 0; + virtual void GetScope(nsAString& aScope) const = 0; }; @@ -106,9 +109,6 @@ public: NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerRegistrationMainThread, ServiceWorkerRegistrationBase) - ServiceWorkerRegistrationMainThread(nsPIDOMWindow* aWindow, - const nsAString& aScope); - already_AddRefed Update(ErrorResult& aRv); @@ -154,6 +154,9 @@ public: void InvalidateWorkers(WhichServiceWorker aWhichOnes) override; + void + RegistrationRemoved() override; + void GetScope(nsAString& aScope) const override { @@ -161,6 +164,9 @@ public: } private: + friend nsPIDOMWindow; + ServiceWorkerRegistrationMainThread(nsPIDOMWindow* aWindow, + const nsAString& aScope); ~ServiceWorkerRegistrationMainThread(); already_AddRefed diff --git a/dom/workers/ServiceWorkerWindowClient.h b/dom/workers/ServiceWorkerWindowClient.h index 8ff7b1d7dc..feb9fbf90d 100644 --- a/dom/workers/ServiceWorkerWindowClient.h +++ b/dom/workers/ServiceWorkerWindowClient.h @@ -24,8 +24,7 @@ public: const ServiceWorkerClientInfo& aClientInfo) : ServiceWorkerClient(aOwner, aClientInfo), mVisibilityState(aClientInfo.mVisibilityState), - mFocused(aClientInfo.mFocused), - mFrameType(aClientInfo.mFrameType) + mFocused(aClientInfo.mFocused) { } @@ -44,12 +43,6 @@ public: return mFocused; } - mozilla::dom::FrameType - FrameType() const - { - return mFrameType; - } - already_AddRefed Focus(ErrorResult& aRv) const; @@ -59,7 +52,6 @@ private: mozilla::dom::VisibilityState mVisibilityState; bool mFocused; - mozilla::dom::FrameType mFrameType; }; } // namespace workers diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index 39c2ccd711..9b86a344fe 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -2190,24 +2190,11 @@ WorkerPrivateParent::WorkerPrivateParent( aParent->CopyJSSettings(mJSSettings); MOZ_ASSERT(IsDedicatedWorker()); - mNowBaseTimeStamp = aParent->NowBaseTimeStamp(); - mNowBaseTimeHighRes = aParent->NowBaseTimeHighRes(); } else { AssertIsOnMainThread(); RuntimeService::GetDefaultJSSettings(mJSSettings); - - if (IsDedicatedWorker() && mLoadInfo.mWindow && - mLoadInfo.mWindow->GetPerformance()) { - mNowBaseTimeStamp = mLoadInfo.mWindow->GetPerformance()->GetDOMTiming()-> - GetNavigationStartTimeStamp(); - mNowBaseTimeHighRes = mLoadInfo.mWindow->GetPerformance()->GetDOMTiming()-> - GetNavigationStartHighRes(); - } else { - mNowBaseTimeStamp = CreationTimeStamp(); - mNowBaseTimeHighRes = CreationTimeHighRes(); - } } } diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index e5a4785040..5e24a047c6 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -191,8 +191,6 @@ private: WorkerType mWorkerType; TimeStamp mCreationTimeStamp; DOMHighResTimeStamp mCreationTimeHighRes; - TimeStamp mNowBaseTimeStamp; - DOMHighResTimeStamp mNowBaseTimeHighRes; protected: // The worker is owned by its thread, which is represented here. This is set @@ -549,21 +547,11 @@ public: return mCreationTimeStamp; } - DOMHighResTimeStamp CreationTimeHighRes() const + DOMHighResTimeStamp CreationTime() const { return mCreationTimeHighRes; } - TimeStamp NowBaseTimeStamp() const - { - return mNowBaseTimeStamp; - } - - DOMHighResTimeStamp NowBaseTimeHighRes() const - { - return mNowBaseTimeHighRes; - } - nsIPrincipal* GetPrincipal() const { @@ -1278,6 +1266,13 @@ public: return mPreferences[WORKERPREF_INTERCEPTION_ENABLED]; } + bool + OpenWindowEnabled() const + { + AssertIsOnWorkerThread(); + return mPreferences[WORKERPREF_OPEN_WINDOW_ENABLED]; + } + bool OpaqueInterceptionEnabled() const { diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index 219408da25..52cafd1810 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -591,7 +591,9 @@ public: AutoJSAPI jsapi; jsapi.Init(); - runnable->Dispatch(jsapi.cx()); + if (!runnable->Dispatch(jsapi.cx())) { + NS_WARNING("Failed to dispatch SkipWaitingResultRunnable to the worker."); + } return NS_OK; } }; @@ -634,6 +636,15 @@ ServiceWorkerGlobalScope::InterceptionEnabled(JSContext* aCx, JSObject* aObj) return worker->InterceptionEnabled(); } +bool +ServiceWorkerGlobalScope::OpenWindowEnabled(JSContext* aCx, JSObject* aObj) +{ + WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + worker->AssertIsOnWorkerThread(); + return worker->OpenWindowEnabled(); +} + WorkerDebuggerGlobalScope::WorkerDebuggerGlobalScope( WorkerPrivate* aWorkerPrivate) : mWorkerPrivate(aWorkerPrivate) diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h index 87bf59244b..b4a6d1de94 100644 --- a/dom/workers/WorkerScope.h +++ b/dom/workers/WorkerScope.h @@ -249,6 +249,9 @@ public: static bool InterceptionEnabled(JSContext* aCx, JSObject* aObj); + static bool + OpenWindowEnabled(JSContext* aCx, JSObject* aObj); + void GetScope(nsString& aScope) const { @@ -265,8 +268,6 @@ public: SkipWaiting(ErrorResult& aRv); IMPL_EVENT_HANDLER(activate) - IMPL_EVENT_HANDLER(beforeevicted) - IMPL_EVENT_HANDLER(evicted) IMPL_EVENT_HANDLER(fetch) IMPL_EVENT_HANDLER(install) IMPL_EVENT_HANDLER(message) diff --git a/dom/workers/Workers.h b/dom/workers/Workers.h index d7234aff81..231bfc0873 100644 --- a/dom/workers/Workers.h +++ b/dom/workers/Workers.h @@ -201,6 +201,7 @@ enum WorkerPreference WORKERPREF_DOM_CACHES, // dom.caches.enabled WORKERPREF_SERVICEWORKERS, // dom.serviceWorkers.enabled WORKERPREF_INTERCEPTION_ENABLED, // dom.serviceWorkers.interception.enabled + WORKERPREF_OPEN_WINDOW_ENABLED, // dom.serviceWorkers.openWindow.enabled WORKERPREF_DOM_WORKERNOTIFICATION, // dom.webnotifications.workers.enabled WORKERPREF_DOM_SERVICEWORKERNOTIFICATION, // dom.webnotifications.serviceworker.enabled WORKERPREF_DOM_CACHES_TESTING, // dom.caches.testing.enabled diff --git a/dom/workers/test/gtest/TestReadWrite.cpp b/dom/workers/test/gtest/TestReadWrite.cpp index f9c46abf39..10b4799c7a 100644 --- a/dom/workers/test/gtest/TestReadWrite.cpp +++ b/dom/workers/test/gtest/TestReadWrite.cpp @@ -219,7 +219,7 @@ TEST(ServiceWorkerRegistrar, TestWriteData) nsAutoCString spec; spec.AppendPrintf("spec write %d", i); - d->principal() = mozilla::ipc::ContentPrincipalInfo(mozilla::OriginAttributes(i, i % 2), spec); + d->principal() = mozilla::ipc::ContentPrincipalInfo(mozilla::PrincipalOriginAttributes(i, i % 2), spec); d->scope().AppendPrintf("scope write %d", i); d->scriptSpec().AppendPrintf("scriptSpec write %d", i); d->currentWorkerURL().AppendPrintf("currentWorkerURL write %d", i); @@ -245,8 +245,7 @@ TEST(ServiceWorkerRegistrar, TestWriteData) ASSERT_EQ(data[i].principal().type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo); const mozilla::ipc::ContentPrincipalInfo& cInfo = data[i].principal(); - ASSERT_EQ((uint32_t)i, cInfo.appId()); - mozilla::OriginAttributes attrs(i, i % 2); + mozilla::PrincipalOriginAttributes attrs(i, i % 2); nsAutoCString suffix, expectSuffix; attrs.CreateSuffix(expectSuffix); cInfo.attrs().CreateSuffix(suffix); diff --git a/dom/workers/test/serviceworkers/app2/client.html b/dom/workers/test/serviceworkers/app2/client.html new file mode 100644 index 0000000000..3a48672b9a --- /dev/null +++ b/dom/workers/test/serviceworkers/app2/client.html @@ -0,0 +1,17 @@ + + + + Test app for bug 1178233 + + + + + diff --git a/dom/workers/test/serviceworkers/app2/index.html b/dom/workers/test/serviceworkers/app2/index.html new file mode 100644 index 0000000000..09e7fb3b53 --- /dev/null +++ b/dom/workers/test/serviceworkers/app2/index.html @@ -0,0 +1,81 @@ + + + + Test app for bug 1178233 + + + + + diff --git a/dom/workers/test/serviceworkers/app2/manifest.webapp b/dom/workers/test/serviceworkers/app2/manifest.webapp new file mode 100644 index 0000000000..a002467bde --- /dev/null +++ b/dom/workers/test/serviceworkers/app2/manifest.webapp @@ -0,0 +1,5 @@ +{ + "name": "App", + "launch_path": "/index.html", + "description": "Test app for bug 1178233" +} diff --git a/dom/workers/test/serviceworkers/app2/manifest.webapp^headers^ b/dom/workers/test/serviceworkers/app2/manifest.webapp^headers^ new file mode 100644 index 0000000000..90818c6398 --- /dev/null +++ b/dom/workers/test/serviceworkers/app2/manifest.webapp^headers^ @@ -0,0 +1 @@ +Content-Type: application/manifest+json diff --git a/dom/workers/test/serviceworkers/app2/sw.sjs b/dom/workers/test/serviceworkers/app2/sw.sjs new file mode 100644 index 0000000000..f60400cbf2 --- /dev/null +++ b/dom/workers/test/serviceworkers/app2/sw.sjs @@ -0,0 +1,22 @@ +function handleRequest(request, response) { + var stateName = request.scheme + 'counter'; + if (!getState(stateName)) { + setState(stateName, '1'); + } else { + // Make sure that we pass a string value to setState! + setState(stateName, "" + (parseInt(getState(stateName)) + 1)); + } + response.setHeader("Content-Type", "application/javascript", false); + response.write(getScript(stateName)); +} + +function getScript(stateName) { + return "oninstall = function(evt) {" + + "evt.waitUntil(self.skipWaiting());" + + "}; " + + "onfetch = function(evt) {" + + "if (evt.request.url.indexOf('get-sw-version') > -1) {" + + "evt.respondWith(new Response('" + getState(stateName) + "'));" + + "}" + + "};"; +} diff --git a/dom/workers/test/serviceworkers/app2/version.html b/dom/workers/test/serviceworkers/app2/version.html new file mode 100644 index 0000000000..9d4537648d --- /dev/null +++ b/dom/workers/test/serviceworkers/app2/version.html @@ -0,0 +1,19 @@ + + + + Test app for bug 1178233 + + + + + diff --git a/dom/workers/test/serviceworkers/app2/wait_for_update.html b/dom/workers/test/serviceworkers/app2/wait_for_update.html new file mode 100644 index 0000000000..fb4410f4fc --- /dev/null +++ b/dom/workers/test/serviceworkers/app2/wait_for_update.html @@ -0,0 +1,25 @@ + + + + Test app for bug 1178233 + + + + + diff --git a/dom/workers/test/serviceworkers/chrome.ini b/dom/workers/test/serviceworkers/chrome.ini index 84b658f052..4799c73bec 100644 --- a/dom/workers/test/serviceworkers/chrome.ini +++ b/dom/workers/test/serviceworkers/chrome.ini @@ -2,13 +2,18 @@ skip-if = buildapp == 'b2g' || os == 'android' support-files = app/* + app2/* chrome_helpers.js + serviceworkerinfo_iframe.html serviceworkermanager_iframe.html serviceworkerregistrationinfo_iframe.html worker.js worker2.js +[test_aboutserviceworkers.html] +skip-if = true #bug 1193319 [test_app_installation.html] [test_privateBrowsing.html] +[test_serviceworkerinfo.xul] [test_serviceworkermanager.xul] [test_serviceworkerregistrationinfo.xul] diff --git a/dom/workers/test/serviceworkers/chrome_helpers.js b/dom/workers/test/serviceworkers/chrome_helpers.js index 315019c3fd..a438333e27 100644 --- a/dom/workers/test/serviceworkers/chrome_helpers.js +++ b/dom/workers/test/serviceworkers/chrome_helpers.js @@ -57,3 +57,18 @@ function waitForServiceWorkerRegistrationChange(registration, callback) { registration.addListener(listener); }); } + +function waitForServiceWorkerShutdown() { + return new Promise(function (resolve) { + let observer = { + observe: function (subject, topic, data) { + if (topic !== "service-worker-shutdown") { + return; + } + SpecialPowers.removeObserver(observer, "service-worker-shutdown"); + resolve(); + } + }; + SpecialPowers.addObserver(observer, "service-worker-shutdown", false); + }); +} diff --git a/dom/workers/test/serviceworkers/serviceworkerinfo_iframe.html b/dom/workers/test/serviceworkers/serviceworkerinfo_iframe.html new file mode 100644 index 0000000000..a0a2de7603 --- /dev/null +++ b/dom/workers/test/serviceworkers/serviceworkerinfo_iframe.html @@ -0,0 +1,26 @@ + + + + + + + + This is a test page. + + diff --git a/dom/workers/test/serviceworkers/test_install_event.html b/dom/workers/test/serviceworkers/test_install_event.html index 3c712beb15..dc0fcbf07a 100644 --- a/dom/workers/test/serviceworkers/test_install_event.html +++ b/dom/workers/test/serviceworkers/test_install_event.html @@ -24,13 +24,26 @@ ok(reg instanceof ServiceWorkerRegistration, "reg should be a ServiceWorkerRegistration"); var p = navigator.serviceWorker.register("install_event_worker.js", { scope: "./install_event" }); return p.then(function(swr) { - ok(reg.scope === swr.scope, "Scope for registrations should match."); - return new Promise(function(resolve, reject) { + ok(reg === swr, "register should resolve to the same registration object"); + var update_found_promise = new Promise(function(resolve, reject) { swr.addEventListener('updatefound', function(e) { ok(true, "Received onupdatefound"); resolve(); }); }); + + var worker_activating = new Promise(function(res, reject) { + ok(swr.installing instanceof ServiceWorker, "There should be an installing worker if promise resolves."); + ok(swr.installing.state == "installing", "Installing worker's state should be 'installing'"); + swr.installing.onstatechange = function(e) { + if (e.target.state == "activating") { + e.target.onstatechange = null; + res(); + } + } + }); + + return Promise.all([update_found_promise, worker_activating]); }, function(e) { ok(false, "Unexpected Error in nextRegister! " + e); }); diff --git a/dom/workers/test/serviceworkers/test_serviceworkerinfo.xul b/dom/workers/test/serviceworkers/test_serviceworkerinfo.xul new file mode 100644 index 0000000000..fef7459784 --- /dev/null +++ b/dom/workers/test/serviceworkers/test_serviceworkerinfo.xul @@ -0,0 +1,78 @@ + + + + + + +

+ +

+    
+  
+  
diff --git a/extensions/cookie/nsPermissionManager.cpp b/extensions/cookie/nsPermissionManager.cpp index cd51955ebe..997e7e07cb 100644 --- a/extensions/cookie/nsPermissionManager.cpp +++ b/extensions/cookie/nsPermissionManager.cpp @@ -122,7 +122,7 @@ GetPrincipal(const nsACString& aHost, uint32_t aAppId, bool aIsInBrowserElement, } // TODO: Bug 1165267 - Use OriginAttributes for nsCookieService - mozilla::OriginAttributes attrs(aAppId, aIsInBrowserElement); + mozilla::PrincipalOriginAttributes attrs(aAppId, aIsInBrowserElement); nsCOMPtr principal = mozilla::BasePrincipal::CreateCodebasePrincipal(uri, attrs); NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE); diff --git a/ipc/glue/BackgroundUtils.cpp b/ipc/glue/BackgroundUtils.cpp index a34d3f7c8e..85e6a312e2 100644 --- a/ipc/glue/BackgroundUtils.cpp +++ b/ipc/glue/BackgroundUtils.cpp @@ -249,6 +249,7 @@ LoadInfoToLoadInfoArgs(nsILoadInfo *aLoadInfo, aLoadInfo->GetParentOuterWindowID(), aLoadInfo->GetEnforceSecurity(), aLoadInfo->GetInitialSecurityCheckDone(), + aLoadInfo->GetIsInThirdPartyContext(), aLoadInfo->GetOriginAttributes(), redirectChainIncludingInternalRedirects, redirectChain); @@ -304,6 +305,7 @@ LoadInfoArgsToLoadInfo(const OptionalLoadInfoArgs& aOptionalLoadInfoArgs, loadInfoArgs.parentOuterWindowID(), loadInfoArgs.enforceSecurity(), loadInfoArgs.initialSecurityCheckDone(), + loadInfoArgs.isInThirdPartyContext(), loadInfoArgs.originAttributes(), redirectChainIncludingInternalRedirects, redirectChain); diff --git a/ipc/glue/BackgroundUtils.h b/ipc/glue/BackgroundUtils.h index 62d16a2187..41908c74c4 100644 --- a/ipc/glue/BackgroundUtils.h +++ b/ipc/glue/BackgroundUtils.h @@ -16,10 +16,11 @@ class nsIPrincipal; namespace IPC { -template<> -struct ParamTraits +namespace detail { +template +struct OriginAttributesParamTraits { - typedef mozilla::OriginAttributes paramType; + typedef ParamType paramType; static void Write(Message* aMsg, const paramType& aParam) { @@ -35,6 +36,23 @@ struct ParamTraits aResult->PopulateFromSuffix(suffix); } }; +} // namespace detail + +template<> +struct ParamTraits + : public detail::OriginAttributesParamTraits {}; + +template<> +struct ParamTraits + : public detail::OriginAttributesParamTraits {}; + +template<> +struct ParamTraits + : public detail::OriginAttributesParamTraits {}; + +template<> +struct ParamTraits + : public detail::OriginAttributesParamTraits {}; } // namespace IPC diff --git a/ipc/glue/PBackgroundSharedTypes.ipdlh b/ipc/glue/PBackgroundSharedTypes.ipdlh index f6c9c36e53..58c694fc72 100644 --- a/ipc/glue/PBackgroundSharedTypes.ipdlh +++ b/ipc/glue/PBackgroundSharedTypes.ipdlh @@ -2,7 +2,7 @@ * 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/. */ -using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h"; +using mozilla::PrincipalOriginAttributes from "mozilla/ipc/BackgroundUtils.h"; using struct mozilla::void_t from "ipc/IPCMessageUtils.h"; namespace mozilla { @@ -10,7 +10,7 @@ namespace ipc { struct ContentPrincipalInfo { - OriginAttributes attrs; + PrincipalOriginAttributes attrs; nsCString spec; }; diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp index 28dd43f1a0..3a0b91b5d0 100644 --- a/layout/generic/nsBlockFrame.cpp +++ b/layout/generic/nsBlockFrame.cpp @@ -737,7 +737,7 @@ nsBlockFrame::GetMinISize(nsRenderingContext *aRenderingContext) // XXX Bug NNNNNN Should probably handle percentage text-indent. data.line = &line; - data.lineContainer = curFrame; + data.SetLineContainer(curFrame); nsIFrame *kid = line->mFirstChild; for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; ++i, kid = kid->GetNextSibling()) { @@ -822,7 +822,7 @@ nsBlockFrame::GetPrefISize(nsRenderingContext *aRenderingContext) // XXX Bug NNNNNN Should probably handle percentage text-indent. data.line = &line; - data.lineContainer = curFrame; + data.SetLineContainer(curFrame); nsIFrame *kid = line->mFirstChild; for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; ++i, kid = kid->GetNextSibling()) { @@ -896,7 +896,7 @@ nsBlockFrame::GetPrefWidthTightBounds(nsRenderingContext* aRenderingContext, // XXX Bug NNNNNN Should probably handle percentage text-indent. data.line = &line; - data.lineContainer = curFrame; + data.SetLineContainer(curFrame); nsIFrame *kid = line->mFirstChild; for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; ++i, kid = kid->GetNextSibling()) { diff --git a/layout/generic/nsContainerFrame.cpp b/layout/generic/nsContainerFrame.cpp index 2dcba6521c..2311c6c683 100644 --- a/layout/generic/nsContainerFrame.cpp +++ b/layout/generic/nsContainerFrame.cpp @@ -850,7 +850,7 @@ nsContainerFrame::DoInlineIntrinsicISize(nsRenderingContext *aRenderingContext, } const nsLineList_iterator* savedLine = aData->line; - nsIFrame* const savedLineContainer = aData->lineContainer; + nsIFrame* const savedLineContainer = aData->LineContainer(); nsContainerFrame *lastInFlow; for (nsContainerFrame *nif = this; nif; @@ -870,13 +870,13 @@ nsContainerFrame::DoInlineIntrinsicISize(nsRenderingContext *aRenderingContext, // After we advance to our next-in-flow, the stored line and line container // may no longer be correct. Just forget them. aData->line = nullptr; - aData->lineContainer = nullptr; + aData->SetLineContainer(nullptr); lastInFlow = nif; } aData->line = savedLine; - aData->lineContainer = savedLineContainer; + aData->SetLineContainer(savedLineContainer); // This goes at the end no matter how things are broken and how // messy the bidi situations are, since per CSS2.1 section 8.6 diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 45de378fd8..428c721a1b 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -4139,7 +4139,7 @@ nsIFrame::InlineMinISizeData::OptionallyBreak(nsRenderingContext *aRenderingCont void nsIFrame::InlinePrefISizeData::ForceBreak(nsRenderingContext *aRenderingContext) { - if (lineContainer && floats.Length() != 0) { + if (floats.Length() != 0) { // preferred widths accumulated for floats that have already // been cleared past nscoord floats_done = 0, @@ -4148,12 +4148,10 @@ nsIFrame::InlinePrefISizeData::ForceBreak(nsRenderingContext *aRenderingContext) floats_cur_left = 0, floats_cur_right = 0; - WritingMode wm = lineContainer->GetWritingMode(); - for (uint32_t i = 0, i_end = floats.Length(); i != i_end; ++i) { const FloatInfo& floatInfo = floats[i]; const nsStyleDisplay *floatDisp = floatInfo.Frame()->StyleDisplay(); - uint8_t breakType = floatDisp->PhysicalBreakType(wm); + uint8_t breakType = floatDisp->PhysicalBreakType(lineContainerWM); if (breakType == NS_STYLE_CLEAR_LEFT || breakType == NS_STYLE_CLEAR_RIGHT || breakType == NS_STYLE_CLEAR_BOTH) { @@ -4170,7 +4168,7 @@ nsIFrame::InlinePrefISizeData::ForceBreak(nsRenderingContext *aRenderingContext) } } - uint8_t floatStyle = floatDisp->PhysicalFloats(wm); + uint8_t floatStyle = floatDisp->PhysicalFloats(lineContainerWM); nscoord& floats_cur = floatStyle == NS_STYLE_FLOAT_LEFT ? floats_cur_left : floats_cur_right; nscoord floatWidth = floatInfo.Width(); diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index f1684559e9..7490d9dea9 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -2840,12 +2840,13 @@ nsGridContainerFrame::ReflowChildren(GridReflowState& aState, } WritingMode childWM = child->GetWritingMode(); LogicalSize childCBSize = cb.Size(wm).ConvertTo(childWM, wm); + LogicalSize percentBasis(childCBSize); // XXX temporary workaround to avoid being INCOMPLETE until we have // support for fragmentation (bug 1144096) childCBSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE; Maybe childRS; // Maybe<> so we can reuse the space - childRS.emplace(pc, *aState.mReflowState, child, childCBSize); + childRS.emplace(pc, *aState.mReflowState, child, childCBSize, &percentBasis); // We need the width of the child before we can correctly convert // the writing-mode of its origin, so we reflow at (0, 0) using a dummy // containerSize, and then pass the correct position to FinishReflowChild. @@ -2876,7 +2877,7 @@ nsGridContainerFrame::ReflowChildren(GridReflowState& aState, NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW); childSize.reset(); // In reverse declaration order since it runs childRS.reset(); // destructors. - childRS.emplace(pc, *aState.mReflowState, child, childCBSize); + childRS.emplace(pc, *aState.mReflowState, child, childCBSize, &percentBasis); if ((alignResize && alignResize.value() == eLogicalAxisBlock) || (justifyResize && justifyResize.value() == eLogicalAxisBlock)) { childRS->SetComputedBSize(newContentSize.BSize(childWM)); diff --git a/layout/generic/nsHTMLReflowState.cpp b/layout/generic/nsHTMLReflowState.cpp index 5d8931e833..6fedba7fbc 100644 --- a/layout/generic/nsHTMLReflowState.cpp +++ b/layout/generic/nsHTMLReflowState.cpp @@ -2081,8 +2081,8 @@ IsSideCaption(nsIFrame* aFrame, const nsStyleDisplay* aStyleDisplay, captionSide == NS_STYLE_CAPTION_SIDE_RIGHT; } -// Flex items resolve block-axis percentage margin & padding against the flex -// container's block-size (which is the containing block block-size). +// Flex/grid items resolve block-axis percentage margin & padding against the +// containing block block-size (also for abs/fixed-pos child frames). // For everything else: the CSS21 spec requires that margin and padding // percentage values are calculated with respect to the inline-size of the // containing block, even for margin & padding in the block axis. @@ -2092,7 +2092,8 @@ OffsetPercentBasis(const nsIFrame* aFrame, const LogicalSize& aContainingBlockSize) { LogicalSize offsetPercentBasis = aContainingBlockSize; - if (!aFrame->IsFlexOrGridItem()) { + if (MOZ_LIKELY(!aFrame->GetParent() || + !aFrame->GetParent()->IsFlexOrGridContainer())) { offsetPercentBasis.BSize(aWM) = offsetPercentBasis.ISize(aWM); } else if (offsetPercentBasis.BSize(aWM) == NS_AUTOHEIGHT) { offsetPercentBasis.BSize(aWM) = 0; diff --git a/layout/generic/nsHTMLReflowState.h b/layout/generic/nsHTMLReflowState.h index 39db4f402a..1c8ca63e8f 100644 --- a/layout/generic/nsHTMLReflowState.h +++ b/layout/generic/nsHTMLReflowState.h @@ -652,9 +652,9 @@ public: * @param aFrame The frame for whose reflow state is being constructed. * @param aAvailableSpace See comments for availableHeight and availableWidth * members. - * @param aContainingBlockSize An optional size, in app units, that - * is used by absolute positioning code to override default containing - * block sizes. + * @param aContainingBlockSize An optional size, in app units, specifying + * the containing block size to use instead of the default which is + * to use the aAvailableSpace. * @param aFlags A set of flags used for additional boolean parameters (see * below). */ diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index d40e581db0..9c6a4053ef 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -143,7 +143,8 @@ typedef uint32_t nsSplittableType; #define NS_INTRINSICSIZE NS_UNCONSTRAINEDSIZE #define NS_AUTOHEIGHT NS_UNCONSTRAINEDSIZE -#define NS_AUTOMARGIN NS_UNCONSTRAINEDSIZE +// +1 is to avoid clamped huge margin values being processed as auto margins +#define NS_AUTOMARGIN (NS_UNCONSTRAINEDSIZE + 1) #define NS_AUTOOFFSET NS_UNCONSTRAINEDSIZE // NOTE: there are assumptions all over that these have the same value, namely NS_UNCONSTRAINEDSIZE // if any are changed to be a value other than NS_UNCONSTRAINEDSIZE @@ -1568,17 +1569,34 @@ public: , lineContainer(nullptr) , prevLines(0) , currentLine(0) - , skipWhitespace(true) , trailingWhitespace(0) + , skipWhitespace(true) {} // The line. This may be null if the inlines are not associated with // a block or if we just don't know the line. const nsLineList_iterator* line; - // The line container. + // The line container. Private, to ensure we always use SetLineContainer + // to update it (so that we have a chance to store the lineContainerWM). + // + // Note that nsContainerFrame::DoInlineIntrinsicISize will clear the + // |line| and |lineContainer| fields when following a next-in-flow link, + // so we must not assume these can always be dereferenced. + private: nsIFrame* lineContainer; + // Setter and getter for the lineContainer field: + public: + void SetLineContainer(nsIFrame* aLineContainer) + { + lineContainer = aLineContainer; + if (lineContainer) { + lineContainerWM = lineContainer->GetWritingMode(); + } + } + nsIFrame* LineContainer() const { return lineContainer; } + // The maximum intrinsic width for all previous lines. nscoord prevLines; @@ -1587,14 +1605,18 @@ public: // the caller should call |Break()|. nscoord currentLine; + // This contains the width of the trimmable whitespace at the end of + // |currentLine|; it is zero if there is no such whitespace. + nscoord trailingWhitespace; + // True if initial collapsable whitespace should be skipped. This // should be true at the beginning of a block, after hard breaks // and when the last text ended with whitespace. bool skipWhitespace; - // This contains the width of the trimmable whitespace at the end of - // |currentLine|; it is zero if there is no such whitespace. - nscoord trailingWhitespace; + // Writing mode of the line container (stored here so that we don't + // lose track of it if the lineContainer field is reset). + mozilla::WritingMode lineContainerWM; // Floats encountered in the lines. class FloatInfo { @@ -1908,7 +1930,7 @@ public: * continuations. Returns nothing for non-text frames. * The appended text will often not contain all the whitespace from source, * depending on CSS white-space processing. - * If aEndOffset goes past end, use the text up to the string's end. + * if aEndOffset goes past end, use the text up to the string's end. * Call this on the primary frame for a text node. * aStartOffset and aEndOffset can be content offsets or offsets in the * rendered text, depending on aOffsetType. @@ -1929,10 +1951,17 @@ public: // Passed-in start and end offsets are within the rendered text. OFFSETS_IN_RENDERED_TEXT }; + enum class TrailingWhitespace { + TRIM_TRAILING_WHITESPACE, + // Spaces preceding a caret at the end of a line should not be trimmed + DONT_TRIM_TRAILING_WHITESPACE + }; virtual RenderedText GetRenderedText(uint32_t aStartOffset = 0, uint32_t aEndOffset = UINT32_MAX, TextOffsetType aOffsetType = - TextOffsetType::OFFSETS_IN_CONTENT_TEXT) + TextOffsetType::OFFSETS_IN_CONTENT_TEXT, + TrailingWhitespace aTrimTrailingWhitespace = + TrailingWhitespace::TRIM_TRAILING_WHITESPACE) { return RenderedText(); } /** @@ -2969,6 +2998,7 @@ NS_PTR_TO_INT32(frame->Properties().Get(nsIFrame::ParagraphDepthProperty())) * Is this a flex or grid item? (i.e. a non-abs-pos child of a flex/grid container) */ inline bool IsFlexOrGridItem() const; + inline bool IsFlexOrGridContainer() const; /** * @return true if this frame is used as a table caption. diff --git a/layout/generic/nsIFrameInlines.h b/layout/generic/nsIFrameInlines.h index e2872b2a38..d29df4e4c3 100644 --- a/layout/generic/nsIFrameInlines.h +++ b/layout/generic/nsIFrameInlines.h @@ -19,16 +19,20 @@ nsIFrame::IsFlexItem() const !(GetStateBits() & NS_FRAME_OUT_OF_FLOW); } +bool +nsIFrame::IsFlexOrGridContainer() const +{ + nsIAtom* t = GetType(); + return t == nsGkAtoms::flexContainerFrame || + t == nsGkAtoms::gridContainerFrame; +} + bool nsIFrame::IsFlexOrGridItem() const { - if (GetParent()) { - nsIAtom* t = GetParent()->GetType(); - return (t == nsGkAtoms::flexContainerFrame || - t == nsGkAtoms::gridContainerFrame) && - !(GetStateBits() & NS_FRAME_OUT_OF_FLOW); - } - return false; + return !(GetStateBits() & NS_FRAME_OUT_OF_FLOW) && + GetParent() && + GetParent()->IsFlexOrGridContainer(); } bool diff --git a/layout/generic/nsRubyBaseContainerFrame.cpp b/layout/generic/nsRubyBaseContainerFrame.cpp index be33805ab7..1187eaae68 100644 --- a/layout/generic/nsRubyBaseContainerFrame.cpp +++ b/layout/generic/nsRubyBaseContainerFrame.cpp @@ -148,13 +148,13 @@ CalculateColumnPrefISize(nsRenderingContext* aRenderingContext, if (frame) { nsIFrame::InlinePrefISizeData data; if (i == 0) { - data.lineContainer = aBaseISizeData->lineContainer; + data.SetLineContainer(aBaseISizeData->LineContainer()); data.skipWhitespace = aBaseISizeData->skipWhitespace; data.trailingWhitespace = aBaseISizeData->trailingWhitespace; } else { // The line container of ruby text frames is their parent, // ruby text container frame. - data.lineContainer = frame->GetParent(); + data.SetLineContainer(frame->GetParent()); } frame->AddInlinePrefISize(aRenderingContext, &data); MOZ_ASSERT(data.prevLines == 0, "Shouldn't have prev lines"); @@ -182,7 +182,7 @@ nsRubyBaseContainerFrame::AddInlineMinISize( // Since spans are not breakable internally, use our pref isize // directly if there is any span. nsIFrame::InlinePrefISizeData data; - data.lineContainer = aData->lineContainer; + data.SetLineContainer(aData->LineContainer()); data.skipWhitespace = aData->skipWhitespace; data.trailingWhitespace = aData->trailingWhitespace; AddInlinePrefISize(aRenderingContext, &data); diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index b6bc3ce049..8bd21ae967 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -4046,7 +4046,9 @@ a11y::AccType nsTextFrame::AccessibleType() { if (IsEmpty()) { - RenderedText text = GetRenderedText(); + RenderedText text = GetRenderedText(0, + UINT32_MAX, TextOffsetType::OFFSETS_IN_CONTENT_TEXT, + TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE); if (text.mString.IsEmpty()) { return a11y::eNoType; } @@ -7847,7 +7849,7 @@ nsTextFrame::AddInlineMinISizeForFlow(nsRenderingContext *aRenderingContext, uint32_t flowEndInTextRun; gfxContext* ctx = aRenderingContext->ThebesContext(); gfxSkipCharsIterator iter = - EnsureTextRun(aTextRunType, ctx, aData->lineContainer, + EnsureTextRun(aTextRunType, ctx, aData->LineContainer(), aData->line, &flowEndInTextRun); gfxTextRun *textRun = GetTextRun(aTextRunType); if (!textRun) @@ -7996,12 +7998,12 @@ nsTextFrame::AddInlineMinISize(nsRenderingContext *aRenderingContext, // lastTextRun will only be null for the first text frame. if (f == this || f->GetTextRun(trtype) != lastTextRun) { nsIFrame* lc; - if (aData->lineContainer && - aData->lineContainer != (lc = FindLineContainer(f))) { + if (aData->LineContainer() && + aData->LineContainer() != (lc = FindLineContainer(f))) { NS_ASSERTION(f != this, "wrong InlineMinISizeData container" " for first continuation"); aData->line = nullptr; - aData->lineContainer = lc; + aData->SetLineContainer(lc); } // This will process all the text frames that share the same textrun as f. @@ -8021,7 +8023,7 @@ nsTextFrame::AddInlinePrefISizeForFlow(nsRenderingContext *aRenderingContext, uint32_t flowEndInTextRun; gfxContext* ctx = aRenderingContext->ThebesContext(); gfxSkipCharsIterator iter = - EnsureTextRun(aTextRunType, ctx, aData->lineContainer, + EnsureTextRun(aTextRunType, ctx, aData->LineContainer(), aData->line, &flowEndInTextRun); gfxTextRun *textRun = GetTextRun(aTextRunType); if (!textRun) @@ -8134,12 +8136,12 @@ nsTextFrame::AddInlinePrefISize(nsRenderingContext *aRenderingContext, // lastTextRun will only be null for the first text frame. if (f == this || f->GetTextRun(trtype) != lastTextRun) { nsIFrame* lc; - if (aData->lineContainer && - aData->lineContainer != (lc = FindLineContainer(f))) { + if (aData->LineContainer() && + aData->LineContainer() != (lc = FindLineContainer(f))) { NS_ASSERTION(f != this, "wrong InlinePrefISizeData container" " for first continuation"); aData->line = nullptr; - aData->lineContainer = lc; + aData->SetLineContainer(lc); } // This will process all the text frames that share the same textrun as f. @@ -9244,7 +9246,8 @@ LineEndsInHardLineBreak(nsTextFrame* aFrame) nsIFrame::RenderedText nsTextFrame::GetRenderedText(uint32_t aStartOffset, uint32_t aEndOffset, - TextOffsetType aOffsetType) + TextOffsetType aOffsetType, + TrailingWhitespace aTrimTrailingWhitespace) { NS_ASSERTION(!GetPrevContinuation(), "Must be called on first-in-flow"); @@ -9272,7 +9275,8 @@ nsTextFrame::GetRenderedText(uint32_t aStartOffset, // Skip to the start of the text run, past ignored chars at start of line TrimmedOffsets trimmedOffsets = textFrame->GetTrimmedOffsets(textFrag, - textFrame->IsAtEndOfLine() && LineEndsInHardLineBreak(textFrame)); + textFrame->IsAtEndOfLine() && LineEndsInHardLineBreak(textFrame) && + aTrimTrailingWhitespace == TrailingWhitespace::TRIM_TRAILING_WHITESPACE); bool trimmedSignificantNewline = trimmedOffsets.GetEnd() < GetContentEnd() && HasSignificantTerminalNewline(); diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h index 44bc028797..ffd121a24e 100644 --- a/layout/generic/nsTextFrame.h +++ b/layout/generic/nsTextFrame.h @@ -281,7 +281,9 @@ public: virtual RenderedText GetRenderedText(uint32_t aStartOffset = 0, uint32_t aEndOffset = UINT32_MAX, TextOffsetType aOffsetType = - TextOffsetType::OFFSETS_IN_CONTENT_TEXT) override; + TextOffsetType::OFFSETS_IN_CONTENT_TEXT, + TrailingWhitespace aTrimTrailingWhitespace = + TrailingWhitespace::TRIM_TRAILING_WHITESPACE) override; nsOverflowAreas RecomputeOverflow(nsIFrame* aBlockFrame); diff --git a/layout/reftests/css-grid/grid-item-sizing-percent-001-ref.html b/layout/reftests/css-grid/grid-item-sizing-percent-001-ref.html new file mode 100644 index 0000000000..3a7f2d6bb0 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-sizing-percent-001-ref.html @@ -0,0 +1,94 @@ + + + + Reference 001 + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-item-sizing-percent-001.html b/layout/reftests/css-grid/grid-item-sizing-percent-001.html new file mode 100644 index 0000000000..cc5948968c --- /dev/null +++ b/layout/reftests/css-grid/grid-item-sizing-percent-001.html @@ -0,0 +1,96 @@ + + + + CSS Test: Testing grid item percent sizes + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/grid-item-sizing-px-001.html b/layout/reftests/css-grid/grid-item-sizing-px-001.html new file mode 100644 index 0000000000..a004365fc2 --- /dev/null +++ b/layout/reftests/css-grid/grid-item-sizing-px-001.html @@ -0,0 +1,94 @@ + + + + CSS Test: Testing grid item 'px' sizes + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/layout/reftests/css-grid/reftest.list b/layout/reftests/css-grid/reftest.list index ebfba34bd9..bc380719a4 100644 --- a/layout/reftests/css-grid/reftest.list +++ b/layout/reftests/css-grid/reftest.list @@ -34,6 +34,8 @@ pref(layout.css.vertical-text.enabled,true) == rtl-grid-placement-auto-row-spars pref(layout.css.vertical-text.enabled,true) == vlr-grid-placement-auto-row-sparse-001.html vlr-grid-placement-auto-row-sparse-001-ref.html pref(layout.css.vertical-text.enabled,true) == vrl-grid-placement-auto-row-sparse-001.html vrl-grid-placement-auto-row-sparse-001-ref.html == grid-relpos-items-001.html grid-relpos-items-001-ref.html +== grid-item-sizing-percent-001.html grid-item-sizing-percent-001-ref.html +== grid-item-sizing-px-001.html grid-item-sizing-percent-001-ref.html == grid-item-dir-001.html grid-item-dir-001-ref.html fuzzy-if(winWidget,70,130) fuzzy-if(cocoaWidget,85,180) == grid-col-max-sizing-max-content-001.html grid-col-max-sizing-max-content-001-ref.html fuzzy-if(winWidget,70,130) fuzzy-if(cocoaWidget,85,180) == grid-col-max-sizing-max-content-002.html grid-col-max-sizing-max-content-002-ref.html diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 9ec8fdd8ba..8e83299dcf 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4583,7 +4583,13 @@ pref("layers.acceleration.force-enabled", false); pref("layers.acceleration.draw-fps", false); // Enable DEAA antialiasing for transformed layers in the compositor +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) +// Desktop prefs +pref("layers.deaa.enabled", true); +#else +// Mobile prefs pref("layers.deaa.enabled", false); +#endif pref("layers.dump", false); #ifdef MOZ_DUMP_PAINTING @@ -4722,7 +4728,8 @@ pref("browser.history.maxStateObjectSize", 655360); // XPInstall prefs pref("xpinstall.whitelist.required", true); pref("extensions.alwaysUnpack", false); -pref("extensions.minCompatiblePlatformVersion", "1.8"); +pref("extensions.minCompatiblePlatformVersion", "2.0"); +pref("extensions.webExtensionsMinPlatformVersion", "42.0a1"); pref("network.buffer.cache.count", 24); pref("network.buffer.cache.size", 32768); diff --git a/netwerk/base/LoadContextInfo.cpp b/netwerk/base/LoadContextInfo.cpp index 796f58bd98..72efba7a1a 100644 --- a/netwerk/base/LoadContextInfo.cpp +++ b/netwerk/base/LoadContextInfo.cpp @@ -15,7 +15,7 @@ namespace net { NS_IMPL_ISUPPORTS(LoadContextInfo, nsILoadContextInfo) -LoadContextInfo::LoadContextInfo(bool aIsPrivate, bool aIsAnonymous, OriginAttributes aOriginAttributes) +LoadContextInfo::LoadContextInfo(bool aIsPrivate, bool aIsAnonymous, NeckoOriginAttributes aOriginAttributes) : mIsPrivate(aIsPrivate) , mIsAnonymous(aIsAnonymous) , mOriginAttributes(aOriginAttributes) @@ -38,7 +38,7 @@ NS_IMETHODIMP LoadContextInfo::GetIsAnonymous(bool *aIsAnonymous) return NS_OK; } -OriginAttributes const* LoadContextInfo::OriginAttributesPtr() +NeckoOriginAttributes const* LoadContextInfo::OriginAttributesPtr() { return &mOriginAttributes; } @@ -58,21 +58,21 @@ NS_IMPL_ISUPPORTS(LoadContextInfoFactory, nsILoadContextInfoFactory) NS_IMETHODIMP LoadContextInfoFactory::GetDefault(nsILoadContextInfo * *aDefault) { - nsCOMPtr info = GetLoadContextInfo(false, false, OriginAttributes()); + nsCOMPtr info = GetLoadContextInfo(false, false, NeckoOriginAttributes()); info.forget(aDefault); return NS_OK; } NS_IMETHODIMP LoadContextInfoFactory::GetPrivate(nsILoadContextInfo * *aPrivate) { - nsCOMPtr info = GetLoadContextInfo(true, false, OriginAttributes()); + nsCOMPtr info = GetLoadContextInfo(true, false, NeckoOriginAttributes()); info.forget(aPrivate); return NS_OK; } NS_IMETHODIMP LoadContextInfoFactory::GetAnonymous(nsILoadContextInfo * *aAnonymous) { - nsCOMPtr info = GetLoadContextInfo(false, true, OriginAttributes()); + nsCOMPtr info = GetLoadContextInfo(false, true, NeckoOriginAttributes()); info.forget(aAnonymous); return NS_OK; } @@ -81,7 +81,7 @@ NS_IMETHODIMP LoadContextInfoFactory::Custom(bool aPrivate, bool aAnonymous, JS::HandleValue aOriginAttributes, JSContext *cx, nsILoadContextInfo * *_retval) { - OriginAttributes attrs; + NeckoOriginAttributes attrs; bool status = attrs.Init(cx, aOriginAttributes); NS_ENSURE_TRUE(status, NS_ERROR_FAILURE); @@ -122,7 +122,7 @@ GetLoadContextInfo(nsIChannel * aChannel) anon = !!(loadFlags & nsIChannel::LOAD_ANONYMOUS); } - OriginAttributes oa; + NeckoOriginAttributes oa; NS_GetOriginAttributes(aChannel, oa); return new LoadContextInfo(pb, anon, oa); @@ -133,14 +133,17 @@ GetLoadContextInfo(nsILoadContext *aLoadContext, bool aIsAnonymous) { if (!aLoadContext) { return new LoadContextInfo(false, aIsAnonymous, - OriginAttributes(nsILoadContextInfo::NO_APP_ID, false)); + NeckoOriginAttributes(nsILoadContextInfo::NO_APP_ID, false)); } bool pb = aLoadContext->UsePrivateBrowsing(); - OriginAttributes oa; - aLoadContext->GetOriginAttributes(oa); + DocShellOriginAttributes doa; + aLoadContext->GetOriginAttributes(doa); - return new LoadContextInfo(pb, aIsAnonymous, oa); + NeckoOriginAttributes noa; + noa.InheritFromDocShellToNecko(doa); + + return new LoadContextInfo(pb, aIsAnonymous, noa); } LoadContextInfo* @@ -164,7 +167,7 @@ GetLoadContextInfo(nsILoadContextInfo *aInfo) LoadContextInfo * GetLoadContextInfo(bool const aIsPrivate, bool const aIsAnonymous, - OriginAttributes const &aOriginAttributes) + NeckoOriginAttributes const &aOriginAttributes) { return new LoadContextInfo(aIsPrivate, aIsAnonymous, diff --git a/netwerk/base/LoadContextInfo.h b/netwerk/base/LoadContextInfo.h index c4b13be639..a4eb100a1e 100644 --- a/netwerk/base/LoadContextInfo.h +++ b/netwerk/base/LoadContextInfo.h @@ -19,7 +19,7 @@ public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSILOADCONTEXTINFO - LoadContextInfo(bool aIsPrivate, bool aIsAnonymous, OriginAttributes aOriginAttributes); + LoadContextInfo(bool aIsPrivate, bool aIsAnonymous, NeckoOriginAttributes aOriginAttributes); private: virtual ~LoadContextInfo(); @@ -27,7 +27,7 @@ private: protected: bool mIsPrivate : 1; bool mIsAnonymous : 1; - OriginAttributes mOriginAttributes; + NeckoOriginAttributes mOriginAttributes; }; class LoadContextInfoFactory : public nsILoadContextInfoFactory @@ -55,7 +55,7 @@ GetLoadContextInfo(nsILoadContextInfo *aInfo); LoadContextInfo* GetLoadContextInfo(bool const aIsPrivate, bool const aIsAnonymous, - OriginAttributes const &aOriginAttributes); + NeckoOriginAttributes const &aOriginAttributes); } // namespace net } // namespace mozilla diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp index 73e973f981..2d2f14656e 100644 --- a/netwerk/base/LoadInfo.cpp +++ b/netwerk/base/LoadInfo.cpp @@ -8,6 +8,7 @@ #include "mozilla/Assertions.h" #include "mozilla/dom/ToJSValue.h" +#include "mozIThirdPartyUtil.h" #include "nsFrameLoader.h" #include "nsIDocShell.h" #include "nsIDocument.h" @@ -16,6 +17,7 @@ #include "nsISupportsImpl.h" #include "nsISupportsUtils.h" #include "nsContentUtils.h" +#include "nsGlobalWindow.h" namespace mozilla { @@ -39,6 +41,7 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal, , mParentOuterWindowID(0) , mEnforceSecurity(false) , mInitialSecurityCheckDone(false) + , mIsThirdPartyContext(true) , mIsFromProcessingFrameAttributes(false) { MOZ_ASSERT(mLoadingPrincipal); @@ -78,13 +81,16 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal, nsCOMPtr parent = outerWindow->GetScriptableParent(); mParentOuterWindowID = parent->WindowID(); + + ComputeIsThirdPartyContext(outerWindow); } mUpgradeInsecureRequests = aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(); mUpgradeInsecurePreloads = aLoadingContext->OwnerDoc()->GetUpgradeInsecurePreloads(); } - mOriginAttributes = BasePrincipal::Cast(mLoadingPrincipal)->OriginAttributesRef(); + const PrincipalOriginAttributes attrs = BasePrincipal::Cast(mLoadingPrincipal)->OriginAttributesRef(); + mOriginAttributes.InheritFromDocToNecko(attrs); } LoadInfo::LoadInfo(const LoadInfo& rhs) @@ -101,6 +107,7 @@ LoadInfo::LoadInfo(const LoadInfo& rhs) , mParentOuterWindowID(rhs.mParentOuterWindowID) , mEnforceSecurity(rhs.mEnforceSecurity) , mInitialSecurityCheckDone(rhs.mInitialSecurityCheckDone) + , mIsThirdPartyContext(rhs.mIsThirdPartyContext) , mOriginAttributes(rhs.mOriginAttributes) , mRedirectChainIncludingInternalRedirects( rhs.mRedirectChainIncludingInternalRedirects) @@ -120,7 +127,8 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal, uint64_t aParentOuterWindowID, bool aEnforceSecurity, bool aInitialSecurityCheckDone, - const OriginAttributes& aOriginAttributes, + bool aIsThirdPartyContext, + const NeckoOriginAttributes& aOriginAttributes, nsTArray>& aRedirectChainIncludingInternalRedirects, nsTArray>& aRedirectChain) : mLoadingPrincipal(aLoadingPrincipal) @@ -134,6 +142,7 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal, , mParentOuterWindowID(aParentOuterWindowID) , mEnforceSecurity(aEnforceSecurity) , mInitialSecurityCheckDone(aInitialSecurityCheckDone) + , mIsThirdPartyContext(aIsThirdPartyContext) , mOriginAttributes(aOriginAttributes) , mIsFromProcessingFrameAttributes(false) { @@ -158,6 +167,35 @@ LoadInfo::CloneWithNewSecFlags(nsSecurityFlags aSecurityFlags) const return copy.forget(); } +void +LoadInfo::ComputeIsThirdPartyContext(nsPIDOMWindow* aOuterWindow) +{ + nsContentPolicyType type = + nsContentUtils::InternalContentPolicyTypeToExternal(mInternalContentPolicyType); + if (type == nsIContentPolicy::TYPE_DOCUMENT) { + // Top-level loads are never third-party. + mIsThirdPartyContext = false; + return; + } + + nsPIDOMWindow* win = aOuterWindow; + if (type == nsIContentPolicy::TYPE_SUBDOCUMENT) { + // If we're loading a subdocument, aOuterWindow points to the new window. + // Check if its parent is third-party (and then we can do the same check for + // it as we would do for other sub-resource loads. + + win = aOuterWindow->GetScriptableParent(); + MOZ_ASSERT(win); + } + + nsCOMPtr util(do_GetService(THIRDPARTYUTIL_CONTRACTID)); + if (NS_WARN_IF(!util)) { + return; + } + + util->IsThirdPartyWindow(win, nullptr, &mIsThirdPartyContext); +} + NS_IMPL_ISUPPORTS(LoadInfo, nsILoadInfo) already_AddRefed @@ -238,7 +276,7 @@ LoadInfo::SetWithCredentialsSecFlag() } NS_IMETHODIMP -LoadInfo::GetSecurityMode(uint32_t *aFlags) +LoadInfo::GetSecurityMode(uint32_t* aFlags) { *aFlags = (mSecurityFlags & (nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS | @@ -249,6 +287,13 @@ LoadInfo::GetSecurityMode(uint32_t *aFlags) return NS_OK; } +NS_IMETHODIMP +LoadInfo::GetIsInThirdPartyContext(bool* aIsInThirdPartyContext) +{ + *aIsInThirdPartyContext = mIsThirdPartyContext; + return NS_OK; +} + NS_IMETHODIMP LoadInfo::GetRequireCorsWithCredentials(bool* aResult) { @@ -288,6 +333,14 @@ LoadInfo::GetAllowChrome(bool* aResult) return NS_OK; } +NS_IMETHODIMP +LoadInfo::GetDontFollowRedirects(bool* aResult) +{ + *aResult = + (mSecurityFlags & nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS); + return NS_OK; +} + NS_IMETHODIMP LoadInfo::GetExternalContentPolicyType(nsContentPolicyType* aResult) { @@ -364,7 +417,7 @@ NS_IMETHODIMP LoadInfo::SetScriptableOriginAttributes(JSContext* aCx, JS::Handle aOriginAttributes) { - OriginAttributes attrs; + NeckoOriginAttributes attrs; if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } @@ -374,7 +427,7 @@ LoadInfo::SetScriptableOriginAttributes(JSContext* aCx, } nsresult -LoadInfo::GetOriginAttributes(mozilla::OriginAttributes* aOriginAttributes) +LoadInfo::GetOriginAttributes(mozilla::NeckoOriginAttributes* aOriginAttributes) { NS_ENSURE_ARG(aOriginAttributes); *aOriginAttributes = mOriginAttributes; @@ -382,7 +435,7 @@ LoadInfo::GetOriginAttributes(mozilla::OriginAttributes* aOriginAttributes) } nsresult -LoadInfo::SetOriginAttributes(const mozilla::OriginAttributes& aOriginAttributes) +LoadInfo::SetOriginAttributes(const mozilla::NeckoOriginAttributes& aOriginAttributes) { mOriginAttributes = aOriginAttributes; return NS_OK; diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h index cb81f82c31..f0aad6bb91 100644 --- a/netwerk/base/LoadInfo.h +++ b/netwerk/base/LoadInfo.h @@ -17,6 +17,7 @@ #include "mozilla/BasePrincipal.h" class nsINode; +class nsPIDOMWindow; class nsXMLHttpRequest; namespace mozilla { @@ -84,7 +85,8 @@ private: uint64_t aParentOuterWindowID, bool aEnforceSecurity, bool aInitialSecurityCheckDone, - const OriginAttributes& aOriginAttributes, + bool aIsThirdPartyRequest, + const NeckoOriginAttributes& aOriginAttributes, nsTArray>& aRedirectChainIncludingInternalRedirects, nsTArray>& aRedirectChain); LoadInfo(const LoadInfo& rhs); @@ -96,6 +98,8 @@ private: ~LoadInfo(); + void ComputeIsThirdPartyContext(nsPIDOMWindow* aOuterWindow); + // This function is the *only* function which can change the securityflags // of a loadinfo. It only exists because of the XHR code. Don't call it // from anywhere else! @@ -116,7 +120,8 @@ private: uint64_t mParentOuterWindowID; bool mEnforceSecurity; bool mInitialSecurityCheckDone; - OriginAttributes mOriginAttributes; + bool mIsThirdPartyContext; + NeckoOriginAttributes mOriginAttributes; nsTArray> mRedirectChainIncludingInternalRedirects; nsTArray> mRedirectChain; diff --git a/netwerk/base/Predictor.cpp b/netwerk/base/Predictor.cpp index f62213339f..43f1705c3e 100644 --- a/netwerk/base/Predictor.cpp +++ b/netwerk/base/Predictor.cpp @@ -581,7 +581,7 @@ Predictor::Init() NS_ENSURE_SUCCESS(rv, rv); RefPtr lci = - new LoadContextInfo(false, false, OriginAttributes()); + new LoadContextInfo(false, false, NeckoOriginAttributes()); rv = cacheStorageService->DiskCacheStorage(lci, false, getter_AddRefs(mCacheDiskStorage)); diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.cpp b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp index 7354464c8f..4d7994dfde 100644 --- a/netwerk/base/nsAsyncRedirectVerifyHelper.cpp +++ b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp @@ -12,6 +12,7 @@ #include "nsIChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIAsyncVerifyRedirectCallback.h" +#include "nsILoadInfo.h" static mozilla::LazyLogModule gRedirectLog("nsRedirect"); #undef LOG @@ -65,6 +66,15 @@ nsAsyncRedirectVerifyHelper::Init(nsIChannel* oldChan, nsIChannel* newChan, mFlags = flags; mCallbackThread = do_GetCurrentThread(); + if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL | + nsIChannelEventSink::REDIRECT_STS_UPGRADE))) { + nsCOMPtr loadInfo = oldChan->GetLoadInfo(); + if (loadInfo && loadInfo->GetDontFollowRedirects()) { + ExplicitCallback(NS_BINDING_ABORTED); + return NS_OK; + } + } + if (synchronize) mWaitingForRedirectCallback = true; diff --git a/netwerk/base/nsBaseChannel.cpp b/netwerk/base/nsBaseChannel.cpp index 64c4d39404..6ec5e49103 100644 --- a/netwerk/base/nsBaseChannel.cpp +++ b/netwerk/base/nsBaseChannel.cpp @@ -23,16 +23,6 @@ #include "nsContentSecurityManager.h" #include "LoadInfo.h" -static PLDHashOperator -CopyProperties(const nsAString &key, nsIVariant *data, void *closure) -{ - nsIWritablePropertyBag *bag = - static_cast(closure); - - bag->SetProperty(key, data); - return PL_DHASH_NEXT; -} - // This class is used to suspend a request across a function scope. class ScopedRequestSuspender { public: @@ -121,8 +111,11 @@ nsBaseChannel::Redirect(nsIChannel *newChannel, uint32_t redirectFlags, } nsCOMPtr bag = ::do_QueryInterface(newChannel); - if (bag) - mPropertyHash.EnumerateRead(CopyProperties, bag.get()); + if (bag) { + for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) { + bag->SetProperty(iter.Key(), iter.UserData()); + } + } // Notify consumer, giving chance to cancel redirect. For backwards compat, // we support nsIHttpEventSink if we are an HTTP channel and if this is not diff --git a/netwerk/base/nsILoadContextInfo.idl b/netwerk/base/nsILoadContextInfo.idl index 2caf74da73..79ede04c9d 100644 --- a/netwerk/base/nsILoadContextInfo.idl +++ b/netwerk/base/nsILoadContextInfo.idl @@ -8,7 +8,7 @@ %{ C++ #include "mozilla/BasePrincipal.h" %} -native OriginAttributesNativePtr(const mozilla::OriginAttributes*); +native OriginAttributesNativePtr(const mozilla::NeckoOriginAttributes*); interface nsILoadContext; interface nsIDOMWindow; @@ -37,7 +37,7 @@ interface nsILoadContextInfo : nsISupports readonly attribute boolean isAnonymous; /** - * OriginAttributes hiding all the security context attributes + * NeckoOriginAttributes hiding all the security context attributes */ [implicit_jscontext] readonly attribute jsval originAttributes; @@ -72,7 +72,7 @@ interface nsILoadContextInfo : nsISupports }; /** - * Since OriginAttributes struct limits the implementation of + * Since NeckoOriginAttributes struct limits the implementation of * nsILoadContextInfo (that needs to be thread safe) to C++, * we need a scriptable factory to create instances of that * interface from JS. diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl index 44ebddc21e..e9bfdb7f6a 100644 --- a/netwerk/base/nsILoadInfo.idl +++ b/netwerk/base/nsILoadInfo.idl @@ -18,15 +18,15 @@ interface nsIPrincipal; %} [ref] native const_nsIPrincipalArray(const nsTArray>); -native OriginAttributes(mozilla::OriginAttributes); -[ref] native const_OriginAttributesRef(const mozilla::OriginAttributes); +native NeckoOriginAttributes(mozilla::NeckoOriginAttributes); +[ref] native const_OriginAttributesRef(const mozilla::NeckoOriginAttributes); typedef unsigned long nsSecurityFlags; /** * An nsILoadOwner represents per-load information about who started the load. */ -[scriptable, builtinclass, uuid(542b38c6-1c7b-4630-bd6b-9fee0d1b474d)] +[scriptable, builtinclass, uuid(5a2dce9f-accd-45ae-98cc-319dec0ae4f0)] interface nsILoadInfo : nsISupports { /** @@ -127,10 +127,23 @@ interface nsILoadInfo : nsISupports const unsigned long SEC_ABOUT_BLANK_INHERITS = (1<<8); /** - * Allow chrome: to bypass security checks. + * Allow access to chrome: packages that are content accessible. */ const unsigned long SEC_ALLOW_CHROME = (1<<9); + /** + * Don't follow redirects. Instead the redirect response is returned + * as a successful response for the channel. + * + * Redirects not initiated by a server response, i.e. REDIRECT_INTERNAL and + * REDIRECT_STS_UPGRADE, are still followed. + * + * Note: If this flag is set and the channel response is a redirect, then + * the response body might not be available. + * This can happen if the redirect was cached. + */ + const unsigned long SEC_DONT_FOLLOW_REDIRECTS = (1<<10); + /** * The loadingPrincipal is the principal that is responsible for the load. * It is *NOT* the principal tied to the resource/URI that this @@ -219,6 +232,14 @@ interface nsILoadInfo : nsISupports */ [infallible] readonly attribute unsigned long securityMode; + /** + * True if this request is embedded in a context that can't be third-party + * (i.e. an iframe embedded in a cross-origin parent window). If this is + * false, then this request may be third-party if it's a third-party to + * loadingPrincipal. + */ + [infallible] readonly attribute boolean isInThirdPartyContext; + /** * Determines whether credentials are sent with CORS requests. * Using this flag requires SEC_REQUIRE_CORS_DATA_INHERITS also to be set. @@ -252,6 +273,11 @@ interface nsILoadInfo : nsISupports */ [infallible] readonly attribute boolean allowChrome; + /** + * Returns true if SEC_DONT_FOLLOW_REDIRECTS is set. + */ + [infallible] readonly attribute boolean dontFollowRedirects; + /** * The external contentPolicyType of the channel, used for security checks * like Mixed Content Blocking and Content Security Policy. @@ -317,22 +343,22 @@ interface nsILoadInfo : nsISupports [infallible] readonly attribute unsigned long long parentOuterWindowID; /** - * Customized OriginAttributes within LoadInfo to allow overwriting of the + * Customized NeckoOriginAttributes within LoadInfo to allow overwriting of the * default originAttributes from the loadingPrincipal. */ [implicit_jscontext, binaryname(ScriptableOriginAttributes)] attribute jsval originAttributes; [noscript, nostdcall, binaryname(GetOriginAttributes)] - OriginAttributes binaryGetOriginAttributes(); + NeckoOriginAttributes binaryGetOriginAttributes(); [noscript, nostdcall, binaryname(SetOriginAttributes)] void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs); %{ C++ - inline mozilla::OriginAttributes GetOriginAttributes() + inline mozilla::NeckoOriginAttributes GetOriginAttributes() { - mozilla::OriginAttributes result; + mozilla::NeckoOriginAttributes result; mozilla::DebugOnly rv = GetOriginAttributes(&result); MOZ_ASSERT(NS_SUCCEEDED(rv)); return result; diff --git a/netwerk/base/nsIOService.cpp b/netwerk/base/nsIOService.cpp index 77d7f6ef30..24e5201408 100644 --- a/netwerk/base/nsIOService.cpp +++ b/netwerk/base/nsIOService.cpp @@ -766,9 +766,7 @@ nsIOService::NewChannelFromURIWithProxyFlagsInternal(nsIURI* aURI, // Make sure that all the individual protocolhandlers attach a loadInfo. if (aLoadInfo) { // make sure we have the same instance of loadInfo on the newly created channel - nsCOMPtr loadInfo; - channel->GetLoadInfo(getter_AddRefs(loadInfo)); - + nsCOMPtr loadInfo = channel->GetLoadInfo(); if (aLoadInfo != loadInfo) { MOZ_ASSERT(false, "newly created channel must have a loadinfo attached"); return NS_ERROR_UNEXPECTED; @@ -1336,23 +1334,6 @@ IsWifiActive() #endif } -struct EnumeratorParams { - nsIOService *service; - int32_t status; -}; - -PLDHashOperator -nsIOService::EnumerateWifiAppsChangingState(const unsigned int &aKey, - int32_t aValue, - void *aUserArg) -{ - EnumeratorParams *params = reinterpret_cast(aUserArg); - if (aValue == nsIAppOfflineInfo::WIFI_ONLY) { - params->service->NotifyAppOfflineStatus(aKey, params->status); - } - return PL_DHASH_NEXT; -} - class nsWakeupNotifier : public nsRunnable { @@ -1472,8 +1453,11 @@ nsIOService::Observe(nsISupports *subject, int32_t status = wifiActive ? nsIAppOfflineInfo::ONLINE : nsIAppOfflineInfo::OFFLINE; - EnumeratorParams params = {this, status}; - mAppsOfflineStatus.EnumerateRead(EnumerateWifiAppsChangingState, ¶ms); + for (auto it = mAppsOfflineStatus.Iter(); !it.Done(); it.Next()) { + if (it.UserData() == nsIAppOfflineInfo::WIFI_ONLY) { + NotifyAppOfflineStatus(it.Key(), status); + } + } } mPreviousWifiState = newWifiState; diff --git a/netwerk/base/nsIOService.h b/netwerk/base/nsIOService.h index 023fcc2aa9..af1e112335 100644 --- a/netwerk/base/nsIOService.h +++ b/netwerk/base/nsIOService.h @@ -120,7 +120,6 @@ private: // notify content processes of offline status // 'status' must be a nsIAppOfflineInfo mode constant. void NotifyAppOfflineStatus(uint32_t appId, int32_t status); - static PLDHashOperator EnumerateWifiAppsChangingState(const unsigned int &, int32_t, void*); nsresult NewChannelFromURIWithProxyFlagsInternal(nsIURI* aURI, nsIURI* aProxyURI, diff --git a/netwerk/base/nsNetUtil.cpp b/netwerk/base/nsNetUtil.cpp index 4ec36e29f9..01e9be2465 100644 --- a/netwerk/base/nsNetUtil.cpp +++ b/netwerk/base/nsNetUtil.cpp @@ -1205,7 +1205,7 @@ NS_UsePrivateBrowsing(nsIChannel *channel) bool NS_GetOriginAttributes(nsIChannel *aChannel, - mozilla::OriginAttributes &aAttributes) + mozilla::NeckoOriginAttributes &aAttributes) { nsCOMPtr loadContext; NS_QueryNotificationCallbacks(aChannel, loadContext); @@ -1213,7 +1213,9 @@ NS_GetOriginAttributes(nsIChannel *aChannel, return false; } - loadContext->GetOriginAttributes(aAttributes); + DocShellOriginAttributes doa; + loadContext->GetOriginAttributes(doa); + aAttributes.InheritFromDocShellToNecko(doa); return true; } diff --git a/netwerk/base/nsNetUtil.h b/netwerk/base/nsNetUtil.h index 92b69717c8..e907c08d31 100644 --- a/netwerk/base/nsNetUtil.h +++ b/netwerk/base/nsNetUtil.h @@ -45,7 +45,7 @@ class nsIStreamLoaderObserver; class nsIUnicharStreamLoader; class nsIUnicharStreamLoaderObserver; -namespace mozilla { class OriginAttributes; } +namespace mozilla { class NeckoOriginAttributes; } template class nsCOMPtr; template struct already_AddRefed; @@ -684,10 +684,10 @@ NS_QueryNotificationCallbacks(nsIInterfaceRequestor *callbacks, bool NS_UsePrivateBrowsing(nsIChannel *channel); /** - * Extract the OriginAttributes from the channel's triggering principal. + * Extract the NeckoOriginAttributes from the channel's triggering principal. */ bool NS_GetOriginAttributes(nsIChannel *aChannel, - mozilla::OriginAttributes &aAttributes); + mozilla::NeckoOriginAttributes &aAttributes); // Constants duplicated from nsIScriptSecurityManager so we avoid having necko // know about script security manager. diff --git a/netwerk/base/nsSecCheckWrapChannel.cpp b/netwerk/base/nsSecCheckWrapChannel.cpp index d54f3633cb..7c8b456f97 100644 --- a/netwerk/base/nsSecCheckWrapChannel.cpp +++ b/netwerk/base/nsSecCheckWrapChannel.cpp @@ -99,6 +99,58 @@ nsSecCheckWrapChannel::~nsSecCheckWrapChannel() { } +//--------------------------------------------------------- +// SecWrapChannelStreamListener helper +//--------------------------------------------------------- + +class SecWrapChannelStreamListener final : public nsIStreamListener +{ + public: + SecWrapChannelStreamListener(nsIRequest *aRequest, + nsIStreamListener *aStreamListener) + : mRequest(aRequest) + , mListener(aStreamListener) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + + private: + ~SecWrapChannelStreamListener() {} + + nsCOMPtr mRequest; + nsCOMPtr mListener; +}; + +NS_IMPL_ISUPPORTS(SecWrapChannelStreamListener, + nsIStreamListener, + nsIRequestObserver) + +NS_IMETHODIMP +SecWrapChannelStreamListener::OnStartRequest(nsIRequest *aRequest, + nsISupports *aContext) +{ + return mListener->OnStartRequest(mRequest, aContext); +} + +NS_IMETHODIMP +SecWrapChannelStreamListener::OnStopRequest(nsIRequest *aRequest, + nsISupports *aContext, + nsresult aStatus) +{ + return mListener->OnStopRequest(mRequest, aContext, aStatus); +} + +NS_IMETHODIMP +SecWrapChannelStreamListener::OnDataAvailable(nsIRequest *aRequest, + nsISupports *aContext, + nsIInputStream *aInStream, + uint64_t aOffset, + uint32_t aCount) +{ + return mListener->OnDataAvailable(mRequest, aContext, aInStream, aOffset, aCount); +} + //--------------------------------------------------------- // nsIChannel implementation //--------------------------------------------------------- @@ -122,10 +174,11 @@ nsSecCheckWrapChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) NS_IMETHODIMP nsSecCheckWrapChannel::AsyncOpen2(nsIStreamListener *aListener) { - nsCOMPtr listener = aListener; - nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + nsCOMPtr secWrapChannelListener = + new SecWrapChannelStreamListener(this, aListener); + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, secWrapChannelListener); NS_ENSURE_SUCCESS(rv, rv); - return AsyncOpen(listener, nullptr); + return AsyncOpen(secWrapChannelListener, nullptr); } NS_IMETHODIMP diff --git a/netwerk/base/nsSimpleURI.cpp b/netwerk/base/nsSimpleURI.cpp index c2ec6605c3..e716e4ac2b 100644 --- a/netwerk/base/nsSimpleURI.cpp +++ b/netwerk/base/nsSimpleURI.cpp @@ -261,8 +261,6 @@ nsSimpleURI::GetUserPass(nsACString &result) NS_IMETHODIMP nsSimpleURI::SetUserPass(const nsACString &userPass) { - NS_ENSURE_STATE(mMutable); - return NS_ERROR_FAILURE; } diff --git a/netwerk/cache/nsApplicationCacheService.cpp b/netwerk/cache/nsApplicationCacheService.cpp index 4fed83e5df..a462f38086 100644 --- a/netwerk/cache/nsApplicationCacheService.cpp +++ b/netwerk/cache/nsApplicationCacheService.cpp @@ -40,7 +40,7 @@ nsApplicationCacheService::BuildGroupID(nsIURI *aManifestURL, { nsresult rv; - mozilla::OriginAttributes const *oa = aLoadContextInfo + mozilla::NeckoOriginAttributes const *oa = aLoadContextInfo ? aLoadContextInfo->OriginAttributesPtr() : nullptr; @@ -57,7 +57,7 @@ nsApplicationCacheService::BuildGroupIDForApp(nsIURI *aManifestURL, bool aIsInBrowser, nsACString &_result) { - OriginAttributes oa; + NeckoOriginAttributes oa; oa.mAppId = aAppId; oa.mInBrowser = aIsInBrowser; nsresult rv = nsOfflineCacheDevice::BuildApplicationCacheGroupID( diff --git a/netwerk/cache/nsDiskCacheDeviceSQL.cpp b/netwerk/cache/nsDiskCacheDeviceSQL.cpp index 8e58796d8f..3f8d5f11f4 100644 --- a/netwerk/cache/nsDiskCacheDeviceSQL.cpp +++ b/netwerk/cache/nsDiskCacheDeviceSQL.cpp @@ -47,7 +47,7 @@ using namespace mozilla; using namespace mozilla::storage; -using mozilla::OriginAttributes; +using mozilla::NeckoOriginAttributes; static const char OFFLINE_CACHE_DEVICE_ID[] = { "offline" }; @@ -1302,7 +1302,7 @@ GetGroupForCache(const nsCSubstring &clientID, nsCString &group) } void -AppendJARIdentifier(nsACString &_result, OriginAttributes const *aOriginAttributes) +AppendJARIdentifier(nsACString &_result, NeckoOriginAttributes const *aOriginAttributes) { nsAutoCString suffix; aOriginAttributes->CreateSuffix(suffix); @@ -1314,7 +1314,7 @@ AppendJARIdentifier(nsACString &_result, OriginAttributes const *aOriginAttribut // static nsresult nsOfflineCacheDevice::BuildApplicationCacheGroupID(nsIURI *aManifestURL, - OriginAttributes const *aOriginAttributes, + NeckoOriginAttributes const *aOriginAttributes, nsACString &_result) { nsCOMPtr newURI; @@ -1362,22 +1362,6 @@ nsOfflineCacheDevice::InitActiveCaches() return NS_OK; } -/* static */ -PLDHashOperator -nsOfflineCacheDevice::ShutdownApplicationCache(const nsACString &key, - nsIWeakReference *weakRef, - void *ctx) -{ - nsCOMPtr obj = do_QueryReferent(weakRef); - if (obj) - { - nsApplicationCache *appCache = static_cast(obj.get()); - appCache->MarkInvalid(); - } - - return PL_DHASH_NEXT; -} - nsresult nsOfflineCacheDevice::Shutdown() { @@ -1385,7 +1369,13 @@ nsOfflineCacheDevice::Shutdown() { MutexAutoLock lock(mLock); - mCaches.EnumerateRead(ShutdownApplicationCache, this); + for (auto iter = mCaches.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr obj = do_QueryReferent(iter.UserData()); + if (obj) { + auto appCache = static_cast(obj.get()); + appCache->MarkInvalid(); + } + } } { @@ -2409,11 +2399,11 @@ nsOfflineCacheDevice::DiscardByAppId(int32_t appID, bool browserEntriesOnly) jaridsuffix.Append('%'); - // TODO - this method should accept OriginAttributes* from outside instead. + // TODO - this method should accept NeckoOriginAttributes* from outside instead. // If passed null, we should then delegate to // nsCacheService::GlobalInstance()->EvictEntriesInternal(nsICache::STORE_OFFLINE); - OriginAttributes oa; + NeckoOriginAttributes oa; oa.mAppId = appID; oa.mInBrowser = browserEntriesOnly; AppendJARIdentifier(jaridsuffix, &oa); @@ -2493,7 +2483,7 @@ nsOfflineCacheDevice::CanUseCache(nsIURI *keyURI, // This is check of extended origin. nsAutoCString demandedGroupID; - const OriginAttributes *oa = loadContextInfo + const NeckoOriginAttributes *oa = loadContextInfo ? loadContextInfo->OriginAttributesPtr() : nullptr; rv = BuildApplicationCacheGroupID(groupURI, oa, demandedGroupID); diff --git a/netwerk/cache/nsDiskCacheDeviceSQL.h b/netwerk/cache/nsDiskCacheDeviceSQL.h index a174985ebf..fe2e6d642d 100644 --- a/netwerk/cache/nsDiskCacheDeviceSQL.h +++ b/netwerk/cache/nsDiskCacheDeviceSQL.h @@ -25,7 +25,7 @@ class nsIURI; class nsOfflineCacheDevice; class mozIStorageService; -namespace mozilla { class OriginAttributes; } +namespace mozilla { class NeckoOriginAttributes; } class nsApplicationCacheNamespace final : public nsIApplicationCacheNamespace { @@ -141,7 +141,7 @@ public: nsresult EvictUnownedEntries(const char *clientID); static nsresult BuildApplicationCacheGroupID(nsIURI *aManifestURL, - mozilla::OriginAttributes const *aOriginAttributes, + mozilla::NeckoOriginAttributes const *aOriginAttributes, nsACString &_result); nsresult ActivateCache(const nsCSubstring &group, @@ -199,10 +199,6 @@ private: friend class nsApplicationCache; - static PLDHashOperator ShutdownApplicationCache(const nsACString &key, - nsIWeakReference *weakRef, - void *ctx); - static bool GetStrictFileOriginPolicy(); bool Initialized() { return mDB != nullptr; } diff --git a/netwerk/cache2/AppCacheStorage.cpp b/netwerk/cache2/AppCacheStorage.cpp index a278245be3..c9fd83d676 100644 --- a/netwerk/cache2/AppCacheStorage.cpp +++ b/netwerk/cache2/AppCacheStorage.cpp @@ -126,7 +126,7 @@ NS_IMETHODIMP AppCacheStorage::AsyncEvictStorage(nsICacheEntryDoomCallback* aCal if (!mAppCache) { // TODO - bug 1165256, have an API on nsIApplicationCacheService that takes // optional OAs and decides internally what to do. - const OriginAttributes* oa = LoadInfo()->OriginAttributesPtr(); + const NeckoOriginAttributes* oa = LoadInfo()->OriginAttributesPtr(); if (oa->mAppId == nsILoadContextInfo::NO_APP_ID && !oa->mInBrowser) { // Clear everything. nsCOMPtr serv = diff --git a/netwerk/cache2/CacheFileMetadata.h b/netwerk/cache2/CacheFileMetadata.h index c006485f35..8bfe5ca02f 100644 --- a/netwerk/cache2/CacheFileMetadata.h +++ b/netwerk/cache2/CacheFileMetadata.h @@ -146,7 +146,7 @@ public: nsresult SyncReadMetadata(nsIFile *aFile); bool IsAnonymous() const { return mAnonymous; } - mozilla::OriginAttributes const & OriginAttributes() const { return mOriginAttributes; } + mozilla::NeckoOriginAttributes const & OriginAttributes() const { return mOriginAttributes; } bool Pinned() const { return !!(mMetaHdr.mFlags & kCacheEntryIsPinned); } const char * GetElement(const char *aKey); @@ -212,7 +212,7 @@ private: bool mIsDirty : 1; bool mAnonymous : 1; bool mAllocExactSize : 1; - mozilla::OriginAttributes mOriginAttributes; + mozilla::NeckoOriginAttributes mOriginAttributes; uint32_t mAppId; nsCOMPtr mListener; }; diff --git a/netwerk/cache2/CacheFileUtils.cpp b/netwerk/cache2/CacheFileUtils.cpp index ccc60c0da7..2b05cf5e3f 100644 --- a/netwerk/cache2/CacheFileUtils.cpp +++ b/netwerk/cache2/CacheFileUtils.cpp @@ -37,7 +37,7 @@ public: private: // Results - OriginAttributes originAttribs; + NeckoOriginAttributes originAttribs; bool isPrivate; bool isAnonymous; nsCString idEnhance; @@ -206,7 +206,7 @@ AppendKeyPrefix(nsILoadContextInfo* aInfo, nsACString &_retval) * Keep the attributes list sorted according their ASCII code. */ - OriginAttributes const *oa = aInfo->OriginAttributesPtr(); + NeckoOriginAttributes const *oa = aInfo->OriginAttributesPtr(); nsAutoCString suffix; oa->CreateSuffix(suffix); if (!suffix.IsEmpty()) { diff --git a/netwerk/cache2/CacheObserver.cpp b/netwerk/cache2/CacheObserver.cpp index e58227bf68..6e5105dbaf 100644 --- a/netwerk/cache2/CacheObserver.cpp +++ b/netwerk/cache2/CacheObserver.cpp @@ -330,7 +330,7 @@ namespace CacheStorageEvictHelper { nsresult ClearStorage(bool const aPrivate, bool const aAnonymous, - OriginAttributes const &aOa) + NeckoOriginAttributes const &aOa) { nsresult rv; @@ -355,7 +355,7 @@ nsresult ClearStorage(bool const aPrivate, return NS_OK; } -nsresult Run(OriginAttributes const &aOa) +nsresult Run(NeckoOriginAttributes const &aOa) { nsresult rv; @@ -450,9 +450,9 @@ CacheObserver::Observe(nsISupports* aSubject, } if (!strcmp(aTopic, "clear-origin-data")) { - OriginAttributes oa; + NeckoOriginAttributes oa; if (!oa.Init(nsDependentString(aData))) { - NS_ERROR("Could not parse OriginAttributes JSON in clear-origin-data notification"); + NS_ERROR("Could not parse NeckoOriginAttributes JSON in clear-origin-data notification"); return NS_OK; } diff --git a/netwerk/cache2/CacheStorageService.cpp b/netwerk/cache2/CacheStorageService.cpp index 4bca73e39b..cb04fbcb00 100644 --- a/netwerk/cache2/CacheStorageService.cpp +++ b/netwerk/cache2/CacheStorageService.cpp @@ -1,3 +1,5 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ @@ -237,8 +239,24 @@ private: return NS_ERROR_NOT_INITIALIZED; CacheEntryTable* entries; - if (sGlobalEntryTables->Get(mContextKey, &entries)) - entries->EnumerateRead(&WalkMemoryCacheRunnable::WalkStorage, this); + if (sGlobalEntryTables->Get(mContextKey, &entries)) { + for (auto iter = entries->Iter(); !iter.Done(); iter.Next()) { + CacheEntry* entry = iter.UserData(); + + // Ignore disk entries + if (entry->IsUsingDisk()) { + continue; + } + + mSize += entry->GetMetadataMemoryConsumption(); + + int64_t size; + if (NS_SUCCEEDED(entry->GetDataSize(&size))) { + mSize += size; + } + mEntryArray.AppendElement(entry); + } + } // Next, we dispatch to the main thread } else if (NS_IsMainThread()) { @@ -287,28 +305,6 @@ private: ProxyReleaseMainThread(mCallback); } - static PLDHashOperator - WalkStorage(const nsACString& aKey, - CacheEntry* aEntry, - void* aClosure) - { - WalkMemoryCacheRunnable* walker = - static_cast(aClosure); - - // Ignore disk entries - if (aEntry->IsUsingDisk()) - return PL_DHASH_NEXT; - - walker->mSize += aEntry->GetMetadataMemoryConsumption(); - - int64_t size; - if (NS_SUCCEEDED(aEntry->GetDataSize(&size))) - walker->mSize += size; - - walker->mEntryArray.AppendElement(aEntry); - return PL_DHASH_NEXT; - } - virtual void OnEntryInfo(const nsACString & aURISpec, const nsACString & aIdEnhance, int64_t aDataSize, int32_t aFetchCount, uint32_t aLastModifiedTime, uint32_t aExpirationTime, @@ -505,29 +501,6 @@ private: uint32_t mCount; }; -PLDHashOperator CollectPrivateContexts(const nsACString& aKey, - CacheEntryTable* aTable, - void* aClosure) -{ - nsCOMPtr info = CacheFileUtils::ParseKey(aKey); - if (info && info->IsPrivate()) { - nsTArray* keys = static_cast*>(aClosure); - keys->AppendElement(aKey); - } - - return PL_DHASH_NEXT; -} - -PLDHashOperator CollectContexts(const nsACString& aKey, - CacheEntryTable* aTable, - void* aClosure) -{ - nsTArray* keys = static_cast*>(aClosure); - keys->AppendElement(aKey); - - return PL_DHASH_NEXT; -} - } // namespace void CacheStorageService::DropPrivateBrowsingEntries() @@ -538,10 +511,17 @@ void CacheStorageService::DropPrivateBrowsingEntries() return; nsTArray keys; - sGlobalEntryTables->EnumerateRead(&CollectPrivateContexts, &keys); + for (auto iter = sGlobalEntryTables->Iter(); !iter.Done(); iter.Next()) { + const nsACString& key = iter.Key(); + nsCOMPtr info = CacheFileUtils::ParseKey(key); + if (info && info->IsPrivate()) { + keys.AppendElement(key); + } + } - for (uint32_t i = 0; i < keys.Length(); ++i) + for (uint32_t i = 0; i < keys.Length(); ++i) { DoomStorageEntries(keys[i], nullptr, true, false, nullptr); + } } namespace { @@ -792,7 +772,9 @@ NS_IMETHODIMP CacheStorageService::Clear() NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED); nsTArray keys; - sGlobalEntryTables->EnumerateRead(&CollectContexts, &keys); + for (auto iter = sGlobalEntryTables->Iter(); !iter.Done(); iter.Next()) { + keys.AppendElement(iter.Key()); + } for (uint32_t i = 0; i < keys.Length(); ++i) { DoomStorageEntries(keys[i], nullptr, true, false, nullptr); @@ -1955,57 +1937,6 @@ CacheStorageService::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) con return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf); } -namespace { - -class ReportStorageMemoryData -{ -public: - nsIMemoryReporterCallback *mHandleReport; - nsISupports *mData; -}; - -PLDHashOperator ReportStorageMemory(const nsACString& aKey, - CacheEntryTable* aTable, - void* aClosure) -{ - CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); - - size_t size = 0; - mozilla::MallocSizeOf mallocSizeOf = CacheStorageService::MallocSizeOf; - - size += aTable->ShallowSizeOfIncludingThis(mallocSizeOf); - for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) { - size += iter.Key().SizeOfExcludingThisIfUnshared(mallocSizeOf); - - // Bypass memory-only entries, those will be reported when iterating - // the memory only table. Memory-only entries are stored in both ALL_ENTRIES - // and MEMORY_ONLY hashtables. - RefPtr const& entry = iter.Data(); - if (aTable->Type() == CacheEntryTable::MEMORY_ONLY || - entry->IsUsingDisk()) { - size += entry->SizeOfIncludingThis(mallocSizeOf); - } - } - - ReportStorageMemoryData& data = - *static_cast(aClosure); - // These key names are not privacy-sensitive. - data.mHandleReport->Callback( - EmptyCString(), - nsPrintfCString("explicit/network/cache2/%s-storage(%s)", - aTable->Type() == CacheEntryTable::MEMORY_ONLY ? "memory" : "disk", - aKey.BeginReading()), - nsIMemoryReporter::KIND_HEAP, - nsIMemoryReporter::UNITS_BYTES, - size, - NS_LITERAL_CSTRING("Memory used by the cache storage."), - data.mData); - - return PL_DHASH_NEXT; -} - -} // namespace - NS_IMETHODIMP CacheStorageService::CollectReports(nsIMemoryReporterCallback* aHandleReport, nsISupports* aData, bool aAnonymize) @@ -2048,10 +1979,40 @@ CacheStorageService::CollectReports(nsIMemoryReporterCallback* aHandleReport, // 1 CacheFileOutputStream // N CacheFileInputStream if (sGlobalEntryTables) { - ReportStorageMemoryData data; - data.mHandleReport = aHandleReport; - data.mData = aData; - sGlobalEntryTables->EnumerateRead(&ReportStorageMemory, &data); + for (auto iter1 = sGlobalEntryTables->Iter(); !iter1.Done(); iter1.Next()) { + CacheStorageService::Self()->Lock().AssertCurrentThreadOwns(); + + CacheEntryTable* table = iter1.UserData(); + + size_t size = 0; + mozilla::MallocSizeOf mallocSizeOf = CacheStorageService::MallocSizeOf; + + size += table->ShallowSizeOfIncludingThis(mallocSizeOf); + for (auto iter2 = table->Iter(); !iter2.Done(); iter2.Next()) { + size += iter2.Key().SizeOfExcludingThisIfUnshared(mallocSizeOf); + + // Bypass memory-only entries, those will be reported when iterating the + // memory only table. Memory-only entries are stored in both ALL_ENTRIES + // and MEMORY_ONLY hashtables. + RefPtr const& entry = iter2.Data(); + if (table->Type() == CacheEntryTable::MEMORY_ONLY || + entry->IsUsingDisk()) { + size += entry->SizeOfIncludingThis(mallocSizeOf); + } + } + + // These key names are not privacy-sensitive. + aHandleReport->Callback( + EmptyCString(), + nsPrintfCString("explicit/network/cache2/%s-storage(%s)", + table->Type() == CacheEntryTable::MEMORY_ONLY ? "memory" : "disk", + iter1.Key().BeginReading()), + nsIMemoryReporter::KIND_HEAP, + nsIMemoryReporter::UNITS_BYTES, + size, + NS_LITERAL_CSTRING("Memory used by the cache storage."), + aData); + } } return NS_OK; diff --git a/netwerk/cache2/OldWrappers.cpp b/netwerk/cache2/OldWrappers.cpp index 7ed090b662..51bd610f19 100644 --- a/netwerk/cache2/OldWrappers.cpp +++ b/netwerk/cache2/OldWrappers.cpp @@ -527,7 +527,7 @@ GetCacheSessionNameForStoragePolicy( nsCSubstring const &scheme, nsCacheStoragePolicy storagePolicy, bool isPrivate, - OriginAttributes const *originAttribs, + NeckoOriginAttributes const *originAttribs, nsACString& sessionName) { MOZ_ASSERT(!isPrivate || storagePolicy == nsICache::STORE_IN_MEMORY); diff --git a/netwerk/cookie/CookieServiceParent.cpp b/netwerk/cookie/CookieServiceParent.cpp index 48b48f2389..3074393f22 100644 --- a/netwerk/cookie/CookieServiceParent.cpp +++ b/netwerk/cookie/CookieServiceParent.cpp @@ -19,7 +19,8 @@ using namespace mozilla::ipc; using mozilla::BasePrincipal; -using mozilla::OriginAttributes; +using mozilla::NeckoOriginAttributes; +using mozilla::PrincipalOriginAttributes; using mozilla::dom::PContentParent; using mozilla::net::NeckoParent; @@ -28,13 +29,16 @@ namespace { // Ignore failures from this function, as they only affect whether we do or // don't show a dialog box in private browsing mode if the user sets a pref. void -CreateDummyChannel(nsIURI* aHostURI, OriginAttributes &aAttrs, bool aIsPrivate, - nsIChannel **aChannel) +CreateDummyChannel(nsIURI* aHostURI, NeckoOriginAttributes& aAttrs, bool aIsPrivate, + nsIChannel** aChannel) { MOZ_ASSERT(aAttrs.mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID); + PrincipalOriginAttributes attrs; + attrs.InheritFromNecko(aAttrs); + nsCOMPtr principal = - BasePrincipal::CreateCodebasePrincipal(aHostURI, aAttrs); + BasePrincipal::CreateCodebasePrincipal(aHostURI, attrs); if (!principal) { return; } @@ -66,14 +70,15 @@ namespace net { MOZ_WARN_UNUSED_RESULT bool CookieServiceParent::GetOriginAttributesFromParams(const IPC::SerializedLoadContext &aLoadContext, - OriginAttributes& aAttrs, + NeckoOriginAttributes& aAttrs, bool& aIsPrivate) { aIsPrivate = false; + DocShellOriginAttributes docShellAttrs; const char* error = NeckoParent::GetValidatedAppInfo(aLoadContext, Manager()->Manager(), - aAttrs); + docShellAttrs); if (error) { NS_WARNING(nsPrintfCString("CookieServiceParent: GetOriginAttributesFromParams: " "FATAL error: %s: KILLING CHILD PROCESS\n", @@ -84,6 +89,8 @@ CookieServiceParent::GetOriginAttributesFromParams(const IPC::SerializedLoadCont if (aLoadContext.IsPrivateBitValid()) { aIsPrivate = aLoadContext.mUsePrivateBrowsing; } + + aAttrs.InheritFromDocShellToNecko(docShellAttrs); return true; } @@ -126,7 +133,7 @@ CookieServiceParent::RecvGetCookieString(const URIParams& aHost, if (!hostURI) return false; - OriginAttributes attrs; + NeckoOriginAttributes attrs; bool isPrivate; bool valid = GetOriginAttributesFromParams(aLoadContext, attrs, isPrivate); if (!valid) { @@ -156,7 +163,7 @@ CookieServiceParent::RecvSetCookieString(const URIParams& aHost, if (!hostURI) return false; - OriginAttributes attrs; + NeckoOriginAttributes attrs; bool isPrivate; bool valid = GetOriginAttributesFromParams(aLoadContext, attrs, isPrivate); if (!valid) { diff --git a/netwerk/cookie/CookieServiceParent.h b/netwerk/cookie/CookieServiceParent.h index cf71f9f5d7..164b811a7c 100644 --- a/netwerk/cookie/CookieServiceParent.h +++ b/netwerk/cookie/CookieServiceParent.h @@ -10,7 +10,7 @@ #include "SerializedLoadContext.h" class nsCookieService; -namespace mozilla { class OriginAttributes; } +namespace mozilla { class NeckoOriginAttributes; } namespace mozilla { namespace net { @@ -24,7 +24,7 @@ public: protected: MOZ_WARN_UNUSED_RESULT bool GetOriginAttributesFromParams(const IPC::SerializedLoadContext &aLoadContext, - OriginAttributes& aAttrs, + NeckoOriginAttributes& aAttrs, bool& aIsPrivate); virtual void ActorDestroy(ActorDestroyReason aWhy) override; diff --git a/netwerk/cookie/nsCookieService.cpp b/netwerk/cookie/nsCookieService.cpp index bdfb29f7cf..84a8331ee4 100644 --- a/netwerk/cookie/nsCookieService.cpp +++ b/netwerk/cookie/nsCookieService.cpp @@ -60,7 +60,7 @@ using namespace mozilla::net; // on content processes (see bug 777620), change to use the appropriate app // namespace. For now those IDLs aren't supported on child processes. #define DEFAULT_APP_KEY(baseDomain) \ - nsCookieKey(baseDomain, OriginAttributes()) + nsCookieKey(baseDomain, NeckoOriginAttributes()) /****************************************************************************** * nsCookieService impl: @@ -572,7 +572,7 @@ public: MOZ_ASSERT(!nsCRT::strcmp(aTopic, TOPIC_CLEAR_ORIGIN_DATA)); MOZ_ASSERT(XRE_IsParentProcess()); - OriginAttributes attrs; + NeckoOriginAttributes attrs; MOZ_ALWAYS_TRUE(attrs.Init(nsDependentString(aData))); nsCOMPtr cookieManager @@ -832,7 +832,7 @@ ConvertAppIdToOriginAttrsSQLFunction::OnFunctionCall( // Create an originAttributes object by appId and inBrowserElemnt. // Then create the originSuffix string from this object. - OriginAttributes attrs(appId, (inBrowser ? 1 : 0)); + NeckoOriginAttributes attrs(appId, (inBrowser ? 1 : 0)); nsAutoCString suffix; attrs.CreateSuffix(suffix); @@ -860,7 +860,7 @@ SetAppIdFromOriginAttributesSQLFunction::OnFunctionCall( { nsresult rv; nsAutoCString suffix; - OriginAttributes attrs; + NeckoOriginAttributes attrs; rv = aFunctionArguments->GetUTF8String(0, suffix); NS_ENSURE_SUCCESS(rv, rv); @@ -892,7 +892,7 @@ SetInBrowserFromOriginAttributesSQLFunction::OnFunctionCall( { nsresult rv; nsAutoCString suffix; - OriginAttributes attrs; + NeckoOriginAttributes attrs; rv = aFunctionArguments->GetUTF8String(0, suffix); NS_ENSURE_SUCCESS(rv, rv); @@ -1217,7 +1217,7 @@ nsCookieService::TryInitDB(bool aRecreateDB) NS_ENSURE_SUCCESS(rv, RESULT_RETRY); // Create new table with new fields and new unique constraint. - rv = CreateTable(); + rv = CreateTableForSchemaVersion6(); NS_ENSURE_SUCCESS(rv, RESULT_RETRY); // Copy data from old table without the two deprecated columns appId and @@ -1306,6 +1306,9 @@ nsCookieService::TryInitDB(bool aRecreateDB) rv = mDefaultDBState->dbConn->RemoveFunction(setInBrowserName); NS_ENSURE_SUCCESS(rv, RESULT_RETRY); + + COOKIE_LOGSTRING(LogLevel::Debug, + ("Upgraded database to schema version 7")); } // No more upgrades. Update the schema version. @@ -1457,6 +1460,44 @@ nsCookieService::CreateTable() COOKIES_SCHEMA_VERSION); if (NS_FAILED(rv)) return rv; + // Create the table. + // We default originAttributes to empty string: this is so if users revert to + // an older Firefox version that doesn't know about this field, any cookies + // set will still work once they upgrade back. + rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TABLE moz_cookies (" + "id INTEGER PRIMARY KEY, " + "baseDomain TEXT, " + "originAttributes TEXT NOT NULL DEFAULT '', " + "name TEXT, " + "value TEXT, " + "host TEXT, " + "path TEXT, " + "expiry INTEGER, " + "lastAccessed INTEGER, " + "creationTime INTEGER, " + "isSecure INTEGER, " + "isHttpOnly INTEGER, " + "appId INTEGER DEFAULT 0, " + "inBrowserElement INTEGER DEFAULT 0, " + "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)" + ")")); + if (NS_FAILED(rv)) return rv; + + // Create an index on baseDomain. + return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, " + "originAttributes)")); +} + +// Sets the schema version and creates the moz_cookies table. +nsresult +nsCookieService::CreateTableForSchemaVersion6() +{ + // Set the schema version, before creating the table. + nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion(6); + if (NS_FAILED(rv)) return rv; + // Create the table. // We default originAttributes to empty string: this is so if users revert to // an older Firefox version that doesn't know about this field, any cookies @@ -1848,7 +1889,7 @@ nsCookieService::GetCookieStringCommon(nsIURI *aHostURI, mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign); // Get originAttributes. - OriginAttributes attrs; + NeckoOriginAttributes attrs; if (aChannel) { NS_GetOriginAttributes(aChannel, attrs); } @@ -1921,7 +1962,7 @@ nsCookieService::SetCookieStringCommon(nsIURI *aHostURI, mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign); // Get originAttributes. - OriginAttributes attrs; + NeckoOriginAttributes attrs; if (aChannel) { NS_GetOriginAttributes(aChannel, attrs); } @@ -1942,7 +1983,7 @@ nsCookieService::SetCookieStringInternal(nsIURI *aHostURI, nsDependentCString &aCookieHeader, const nsCString &aServerTime, bool aFromHttp, - const OriginAttributes &aOriginAttrs, + const NeckoOriginAttributes &aOriginAttrs, bool aIsPrivate, nsIChannel *aChannel) { @@ -2253,7 +2294,7 @@ nsCookieService::Add(const nsACString &aHost, nsresult -nsCookieService::Remove(const nsACString& aHost, const OriginAttributes& aAttrs, +nsCookieService::Remove(const nsACString& aHost, const NeckoOriginAttributes& aAttrs, const nsACString& aName, const nsACString& aPath, bool aBlocked) { @@ -2311,7 +2352,7 @@ nsCookieService::Remove(const nsACString &aHost, const nsACString &aPath, bool aBlocked) { - OriginAttributes attrs; + NeckoOriginAttributes attrs; return Remove(aHost, attrs, aName, aPath, aBlocked); } @@ -2635,7 +2676,7 @@ nsCookieService::EnsureReadComplete() stmt->GetUTF8String(IDX_BASE_DOMAIN, baseDomain); nsAutoCString suffix; - OriginAttributes attrs; + NeckoOriginAttributes attrs; stmt->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix); attrs.PopulateFromSuffix(suffix); @@ -2784,7 +2825,7 @@ nsCookieService::ImportCookies(nsIFile *aCookieFile) continue; // pre-existing cookies have appId=0, inBrowser=false set by default - // constructor of OriginAttributes(). + // constructor of NeckoOriginAttributes(). nsCookieKey key = DEFAULT_APP_KEY(baseDomain); // Create a new nsCookie and assign the data. We don't know the cookie @@ -2875,7 +2916,7 @@ void nsCookieService::GetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, bool aHttpBound, - const OriginAttributes aOriginAttrs, + const NeckoOriginAttributes aOriginAttrs, bool aIsPrivate, nsCString &aCookieString) { @@ -4276,7 +4317,7 @@ nsCookieService::RemoveCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement) // // NOTE: we could make this better by getting nsCookieEntry objects instead // of plain nsICookie. - OriginAttributes attrs(aAppId, true); + NeckoOriginAttributes attrs(aAppId, true); Remove(host, attrs, name, path, false); if (!aOnlyBrowserElement) { attrs.mInBrowser = false; diff --git a/netwerk/cookie/nsCookieService.h b/netwerk/cookie/nsCookieService.h index a6e7a306f3..7973fbbc22 100644 --- a/netwerk/cookie/nsCookieService.h +++ b/netwerk/cookie/nsCookieService.h @@ -32,7 +32,7 @@ #include "mozilla/MemoryReporting.h" -using mozilla::OriginAttributes; +using mozilla::NeckoOriginAttributes; class nsICookiePermission; class nsIEffectiveTLDService; @@ -65,7 +65,7 @@ public: nsCookieKey() {} - nsCookieKey(const nsCString &baseDomain, const OriginAttributes &attrs) + nsCookieKey(const nsCString &baseDomain, const NeckoOriginAttributes &attrs) : mBaseDomain(baseDomain) , mOriginAttributes(attrs) {} @@ -110,7 +110,7 @@ public: enum { ALLOW_MEMMOVE = true }; nsCString mBaseDomain; - OriginAttributes mOriginAttributes; + NeckoOriginAttributes mOriginAttributes; }; // Inherit from nsCookieKey so this can be stored in nsTHashTable @@ -274,6 +274,7 @@ class nsCookieService final : public nsICookieService void InitDBStates(); OpenDBResult TryInitDB(bool aDeleteExistingDB); nsresult CreateTable(); + nsresult CreateTableForSchemaVersion6(); nsresult CreateTableForSchemaVersion5(); void CloseDBStates(); void CleanupCachedStatements(); @@ -291,9 +292,9 @@ class nsCookieService final : public nsICookieService nsresult GetBaseDomain(nsIURI *aHostURI, nsCString &aBaseDomain, bool &aRequireHostMatch); nsresult GetBaseDomainFromHost(const nsACString &aHost, nsCString &aBaseDomain); nsresult GetCookieStringCommon(nsIURI *aHostURI, nsIChannel *aChannel, bool aHttpBound, char** aCookie); - void GetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, bool aHttpBound, const OriginAttributes aOriginAttrs, bool aIsPrivate, nsCString &aCookie); + void GetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, bool aHttpBound, const NeckoOriginAttributes aOriginAttrs, bool aIsPrivate, nsCString &aCookie); nsresult SetCookieStringCommon(nsIURI *aHostURI, const char *aCookieHeader, const char *aServerTime, nsIChannel *aChannel, bool aFromHttp); - void SetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, nsDependentCString &aCookieHeader, const nsCString &aServerTime, bool aFromHttp, const OriginAttributes &aOriginAttrs, bool aIsPrivate, nsIChannel* aChannel); + void SetCookieStringInternal(nsIURI *aHostURI, bool aIsForeign, nsDependentCString &aCookieHeader, const nsCString &aServerTime, bool aFromHttp, const NeckoOriginAttributes &aOriginAttrs, bool aIsPrivate, nsIChannel* aChannel); bool SetCookieInternal(nsIURI *aHostURI, const nsCookieKey& aKey, bool aRequireHostMatch, CookieStatus aStatus, nsDependentCString &aCookieHeader, int64_t aServerTime, bool aFromHttp, nsIChannel* aChannel); void AddInternal(const nsCookieKey& aKey, nsCookie *aCookie, int64_t aCurrentTimeInUsec, nsIURI *aHostURI, const char *aCookieHeader, bool aFromHttp); void RemoveCookieFromList(const nsListIter &aIter, mozIStorageBindingParamsArray *aParamsArray = nullptr); @@ -319,10 +320,10 @@ class nsCookieService final : public nsICookieService /** * This method is a helper that allows calling nsICookieManager::Remove() - * with OriginAttributes parameter. + * with NeckoOriginAttributes parameter. * NOTE: this could be added to a public interface if we happen to need it. */ - nsresult Remove(const nsACString& aHost, const OriginAttributes& aAttrs, + nsresult Remove(const nsACString& aHost, const NeckoOriginAttributes& aAttrs, const nsACString& aName, const nsACString& aPath, bool aBlocked); diff --git a/netwerk/ipc/NeckoChannelParams.ipdlh b/netwerk/ipc/NeckoChannelParams.ipdlh index f49b5783f9..982beee231 100644 --- a/netwerk/ipc/NeckoChannelParams.ipdlh +++ b/netwerk/ipc/NeckoChannelParams.ipdlh @@ -12,6 +12,7 @@ include URIParams; include InputStreamParams; include PBackgroundSharedTypes; +using mozilla::NeckoOriginAttributes from "mozilla/ipc/BackgroundUtils.h"; using struct mozilla::void_t from "ipc/IPCMessageUtils.h"; using RequestHeaderTuples from "mozilla/net/PHttpChannelParams.h"; using struct nsHttpAtom from "nsHttp.h"; @@ -37,7 +38,8 @@ struct LoadInfoArgs uint64_t parentOuterWindowID; bool enforceSecurity; bool initialSecurityCheckDone; - OriginAttributes originAttributes; + bool isInThirdPartyContext; + NeckoOriginAttributes originAttributes; PrincipalInfo[] redirectChainIncludingInternalRedirects; PrincipalInfo[] redirectChain; }; diff --git a/netwerk/ipc/NeckoParent.cpp b/netwerk/ipc/NeckoParent.cpp index 0329e71383..5b3e2a7c08 100644 --- a/netwerk/ipc/NeckoParent.cpp +++ b/netwerk/ipc/NeckoParent.cpp @@ -45,7 +45,8 @@ #include "mozilla/net/OfflineObserver.h" #include "nsISpeculativeConnect.h" -using mozilla::OriginAttributes; +using mozilla::DocShellOriginAttributes; +using mozilla::NeckoOriginAttributes; using mozilla::dom::ContentParent; using mozilla::dom::TabContext; using mozilla::dom::TabParent; @@ -111,7 +112,7 @@ PBOverrideStatusFromLoadContext(const SerializedLoadContext& aSerialized) const char* NeckoParent::GetValidatedAppInfo(const SerializedLoadContext& aSerialized, PContentParent* aContent, - OriginAttributes& aAttrs) + DocShellOriginAttributes& aAttrs) { if (UsingNeckoIPCSecurity()) { if (!aSerialized.IsNotNull()) { @@ -148,7 +149,7 @@ NeckoParent::GetValidatedAppInfo(const SerializedLoadContext& aSerialized, aSerialized.mOriginAttributes.mSignedPkg != tabContext.OriginAttributesRef().mSignedPkg) { continue; } - aAttrs = OriginAttributes(appId, inBrowserElement); + aAttrs = DocShellOriginAttributes(appId, inBrowserElement); aAttrs.mSignedPkg = tabContext.OriginAttributesRef().mSignedPkg; return nullptr; } @@ -162,7 +163,7 @@ NeckoParent::GetValidatedAppInfo(const SerializedLoadContext& aSerialized, if (aSerialized.IsNotNull()) { aAttrs = aSerialized.mOriginAttributes; } else { - aAttrs = OriginAttributes(NECKO_NO_APP_ID, false); + aAttrs = DocShellOriginAttributes(NECKO_NO_APP_ID, false); } return nullptr; } @@ -176,7 +177,7 @@ NeckoParent::CreateChannelLoadContext(const PBrowserOrId& aBrowser, const SerializedLoadContext& aSerialized, nsCOMPtr &aResult) { - OriginAttributes attrs; + DocShellOriginAttributes attrs; const char* error = GetValidatedAppInfo(aSerialized, aContent, attrs); if (error) { return error; @@ -889,7 +890,7 @@ NeckoParent::RecvPredPredict(const ipc::OptionalURIParams& aTargetURI, // We only actually care about the loadContext.mPrivateBrowsing, so we'll just // pass dummy params for nestFrameId, and originAttributes. uint64_t nestedFrameId = 0; - OriginAttributes attrs(NECKO_UNKNOWN_APP_ID, false); + DocShellOriginAttributes attrs(NECKO_UNKNOWN_APP_ID, false); nsCOMPtr loadContext; if (aLoadContext.IsNotNull()) { loadContext = new LoadContext(aLoadContext, nestedFrameId, attrs); @@ -921,7 +922,7 @@ NeckoParent::RecvPredLearn(const ipc::URIParams& aTargetURI, // We only actually care about the loadContext.mPrivateBrowsing, so we'll just // pass dummy params for nestFrameId, and originAttributes; uint64_t nestedFrameId = 0; - OriginAttributes attrs(NECKO_UNKNOWN_APP_ID, false); + DocShellOriginAttributes attrs(NECKO_UNKNOWN_APP_ID, false); nsCOMPtr loadContext; if (aLoadContext.IsNotNull()) { loadContext = new LoadContext(aLoadContext, nestedFrameId, attrs); diff --git a/netwerk/ipc/NeckoParent.h b/netwerk/ipc/NeckoParent.h index c548b1b6e0..508ab6fcb3 100644 --- a/netwerk/ipc/NeckoParent.h +++ b/netwerk/ipc/NeckoParent.h @@ -39,7 +39,7 @@ public: static const char * GetValidatedAppInfo(const SerializedLoadContext& aSerialized, PContentParent* aBrowser, - mozilla::OriginAttributes& aAttrs); + mozilla::DocShellOriginAttributes& aAttrs); /* * Creates LoadContext for parent-side of an e10s channel. diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index f2baa831bd..85f4b1181b 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -2530,15 +2530,6 @@ HttpBaseChannel::AddCookiesToRequest() SetRequestHeader(nsDependentCString(nsHttp::Cookie), cookie, false); } -static PLDHashOperator -CopyProperties(const nsAString& aKey, nsIVariant *aData, void *aClosure) -{ - nsIWritablePropertyBag* bag = static_cast - (aClosure); - bag->SetProperty(aKey, aData); - return PL_DHASH_NEXT; -} - bool HttpBaseChannel::ShouldRewriteRedirectToGET(uint32_t httpStatus, nsHttpRequestHead::ParsedMethodType method) @@ -2734,8 +2725,11 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, // transfer any properties nsCOMPtr bag(do_QueryInterface(newChannel)); - if (bag) - mPropertyHash.EnumerateRead(CopyProperties, bag.get()); + if (bag) { + for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) { + bag->SetProperty(iter.Key(), iter.UserData()); + } + } // Transfer the timing data (if we are dealing with an nsITimedChannel). nsCOMPtr newTimedChannel(do_QueryInterface(newChannel)); diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp index 54cf953584..f1f43be3e0 100644 --- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -1856,22 +1856,9 @@ HttpChannelChild::ContinueAsyncOpen() optionalCorsPreflightArgs = mozilla::void_t(); } - nsCOMPtr util(do_GetService(THIRDPARTYUTIL_CONTRACTID)); - if (util) { - bool thirdParty; - nsresult rv = util->IsThirdPartyChannel(this, nullptr, &thirdParty); - if (NS_FAILED(rv)) { - // If we couldn't compute whether this is a third-party load, assume that - // it is. - thirdParty = true; - } - - mThirdPartyFlags |= thirdParty ? - nsIHttpChannelInternal::THIRD_PARTY_PARENT_IS_THIRD_PARTY : - nsIHttpChannelInternal::THIRD_PARTY_PARENT_IS_SAME_PARTY; - nsCOMPtr uri; - GetTopWindowURI(getter_AddRefs(uri)); - } + // NB: This call forces us to cache mTopWindowURI if we haven't already. + nsCOMPtr uri; + GetTopWindowURI(getter_AddRefs(uri)); SerializeURI(mTopWindowURI, openArgs.topWindowURI()); diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp index 343aa6677f..f682934b4b 100644 --- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -41,7 +41,6 @@ #include "nsIDocument.h" using mozilla::BasePrincipal; -using mozilla::OriginAttributes; using namespace mozilla::dom; using namespace mozilla::ipc; @@ -563,11 +562,18 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI, } if (setChooseApplicationCache) { - OriginAttributes attrs; + DocShellOriginAttributes docShellAttrs; if (mLoadContext) { - attrs.CopyFromLoadContext(mLoadContext); + bool result = mLoadContext->GetOriginAttributes(docShellAttrs); + if (!result) { + return SendFailedAsyncOpen(NS_ERROR_FAILURE); + } } + NeckoOriginAttributes neckoAttrs; + neckoAttrs.InheritFromDocShellToNecko(docShellAttrs); + PrincipalOriginAttributes attrs; + attrs.InheritFromNecko(neckoAttrs); nsCOMPtr principal = BasePrincipal::CreateCodebasePrincipal(uri, attrs); diff --git a/netwerk/protocol/http/PackagedAppService.cpp b/netwerk/protocol/http/PackagedAppService.cpp index 52fbcc7e2a..f75dd2793a 100644 --- a/netwerk/protocol/http/PackagedAppService.cpp +++ b/netwerk/protocol/http/PackagedAppService.cpp @@ -897,7 +897,7 @@ static bool AddPackageIdToOrigin(nsACString& aOrigin, const nsACString& aPackageId) { nsAutoCString originNoSuffix; - mozilla::OriginAttributes attrs; + mozilla::NeckoOriginAttributes attrs; if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) { return false; } diff --git a/netwerk/protocol/http/PackagedAppVerifier.cpp b/netwerk/protocol/http/PackagedAppVerifier.cpp index 060014b76a..190cf23076 100644 --- a/netwerk/protocol/http/PackagedAppVerifier.cpp +++ b/netwerk/protocol/http/PackagedAppVerifier.cpp @@ -81,7 +81,7 @@ NS_IMETHODIMP PackagedAppVerifier::Init(nsIPackagedAppVerifierListener* aListene mIsFirstResource = true; mManifest = EmptyCString(); - OriginAttributes().PopulateFromOrigin(aPackageOrigin, mPackageOrigin); + NeckoOriginAttributes().PopulateFromOrigin(aPackageOrigin, mPackageOrigin); mBypassVerification = (mPackageOrigin == Preferences::GetCString("network.http.signed-packages.trusted-origin")); diff --git a/netwerk/protocol/http/nsHttpAuthCache.cpp b/netwerk/protocol/http/nsHttpAuthCache.cpp index e0a7328284..2ef8380986 100644 --- a/netwerk/protocol/http/nsHttpAuthCache.cpp +++ b/netwerk/protocol/http/nsHttpAuthCache.cpp @@ -292,8 +292,8 @@ RemoveEntriesForPattern(PLHashEntry *entry, int32_t number, void *arg) nsDependentCSubstring oaSuffix; oaSuffix.Rebind(key.BeginReading(), colon); - // Build the OriginAttributes object of it... - OriginAttributes oa; + // Build the NeckoOriginAttributes object of it... + NeckoOriginAttributes oa; DebugOnly rv = oa.PopulateFromSuffix(oaSuffix); MOZ_ASSERT(rv); diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index fc213b7c03..8c080aeef3 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -137,13 +137,6 @@ Hash(const char *buf, nsACString &hash) return NS_OK; } -bool IsRedirectStatus(uint32_t status) -{ - // 305 disabled as a security measure (see bug 187996). - return status == 300 || status == 301 || status == 302 || status == 303 || - status == 307 || status == 308; -} - } // unnamed namespace // We only treat 3xx responses as redirects if they have a Location header and @@ -151,7 +144,7 @@ bool IsRedirectStatus(uint32_t status) bool WillRedirect(const nsHttpResponseHead * response) { - return IsRedirectStatus(response->Status()) && + return nsHttpChannel::IsRedirectStatus(response->Status()) && response->PeekHeader(nsHttp::Location); } @@ -604,16 +597,27 @@ nsHttpChannel::ContinueHandleAsyncRedirect(nsresult rv) // If AsyncProcessRedirection fails, then we have to send out the // OnStart/OnStop notifications. LOG(("ContinueHandleAsyncRedirect got failure result [rv=%x]\n", rv)); - mStatus = rv; - DoNotifyListener(); + + bool redirectsEnabled = + !mLoadInfo || !mLoadInfo->GetDontFollowRedirects(); + + if (redirectsEnabled) { + // TODO: stop failing original channel if redirect vetoed? + mStatus = rv; + + DoNotifyListener(); + + // Blow away cache entry if we couldn't process the redirect + // for some reason (the cache entry might be corrupt). + if (mCacheEntry) { + mCacheEntry->AsyncDoom(nullptr); + } + } + else { + DoNotifyListener(); + } } - // close the cache entry. Blow it away if we couldn't process the redirect - // for some reason (the cache entry might be corrupt). - if (mCacheEntry) { - if (NS_FAILED(rv)) - mCacheEntry->AsyncDoom(nullptr); - } CloseCacheEntry(false); mIsPending = false; @@ -7148,6 +7152,14 @@ nsHttpChannel::OnPush(const nsACString &url, Http2PushedStream *pushedStream) return rv; } +// static +bool nsHttpChannel::IsRedirectStatus(uint32_t status) +{ + // 305 disabled as a security measure (see bug 187996). + return status == 300 || status == 301 || status == 302 || status == 303 || + status == 307 || status == 308; +} + void nsHttpChannel::SetCouldBeSynthesized() { diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index e42db216fd..3abffa8a45 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -122,6 +122,9 @@ public: nsresult OnPush(const nsACString &uri, Http2PushedStream *pushedStream); + static bool IsRedirectStatus(uint32_t status); + + // Methods HttpBaseChannel didn't implement for us or that we override. // // nsIRequest diff --git a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp index 4573e0b3f0..594ed8f6f6 100644 --- a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp +++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp @@ -45,7 +45,7 @@ namespace net { static void GetOriginAttributesSuffix(nsIChannel* aChan, nsACString &aSuffix) { - OriginAttributes oa; + NeckoOriginAttributes oa; // Deliberately ignoring the result and going with defaults if (aChan) { diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl index b6779a33f4..cb4f5bc6c9 100644 --- a/netwerk/protocol/http/nsIHttpChannelInternal.idl +++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl @@ -39,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(9eabaac6-cc7c-4ca1-9430-65f2daaa578f)] +[scriptable, uuid(23b3f883-ce09-4350-8f67-1d9858d619e1)] interface nsIHttpChannelInternal : nsISupports { /** @@ -84,21 +84,6 @@ interface nsIHttpChannelInternal : nsISupports */ const unsigned long THIRD_PARTY_FORCE_ALLOW = 1 << 0; - /** - * This flag is set in the parent if the child has already computed that - * it originates from a 3rd party frame (i.e. a 3rd party iframe). - */ - const unsigned long THIRD_PARTY_PARENT_IS_THIRD_PARTY = 1 << 1; - - /** - * This flag is set in the parent if the child has already computed that - * it is not a 3rd party request due to iframe parentage. However, if - * someone calls mozIThirdPartyUtil::IsThirdPartyChannel with a 3rd-party - * URI, the result would be true if the URI is third-party from this - * channel's URI. - */ - const unsigned long THIRD_PARTY_PARENT_IS_SAME_PARTY = 1 << 2; - /** * When set, these flags modify the algorithm used to decide whether to * send 3rd party cookies for a given channel. diff --git a/netwerk/protocol/wyciwyg/nsWyciwygChannel.h b/netwerk/protocol/wyciwyg/nsWyciwygChannel.h index af481e2a2f..4b77b3ca14 100644 --- a/netwerk/protocol/wyciwyg/nsWyciwygChannel.h +++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.h @@ -87,7 +87,7 @@ protected: nsCString mCharset; int64_t mContentLength; uint32_t mLoadFlags; - mozilla::OriginAttributes mOriginAttributes; + mozilla::NeckoOriginAttributes mOriginAttributes; nsCOMPtr mURI; nsCOMPtr mOriginalURI; nsCOMPtr mOwner; diff --git a/netwerk/streamconv/nsStreamConverterService.cpp b/netwerk/streamconv/nsStreamConverterService.cpp index 205d0a419c..9486f26214 100644 --- a/netwerk/streamconv/nsStreamConverterService.cpp +++ b/netwerk/streamconv/nsStreamConverterService.cpp @@ -192,19 +192,6 @@ typedef nsClassHashtable BFSHashTable; // nsObjectHashtable enumerator functions. -// Initializes the BFS state table. -static PLDHashOperator -InitBFSTable(const nsACString &aKey, nsCOMArray *aData, void* aClosure) { - MOZ_ASSERT(aData, "no data in the table enumeration"); - - BFSHashTable *bfsTable = static_cast(aClosure); - if (!bfsTable) return PL_DHASH_STOP; - - BFSTableData *data = new BFSTableData(aKey); - bfsTable->Put(aKey, data); - return PL_DHASH_NEXT; -} - class CStreamConvDeallocator : public nsDequeFunctor { public: virtual void* operator()(void* anObject) { @@ -232,7 +219,11 @@ nsStreamConverterService::FindConverter(const char *aContractID, nsTArray \r ---X3JDIZCX8G\r +--NKWXJUAFXB\r Content-Location: index.html\r Content-Type: text/html\r \r Last updated: 2015/10/28 + \r ---X3JDIZCX8G\r +--NKWXJUAFXB\r Content-Location: scripts/script.js\r Content-Type: text/javascript\r \r // script.js \r ---X3JDIZCX8G\r +--NKWXJUAFXB\r Content-Location: scripts/library.js\r Content-Type: text/javascript\r \r // library.js \r ---X3JDIZCX8G--`; +--NKWXJUAFXB--`; diff --git a/netwerk/test/mochitests/test_origin_attributes_conversion.html b/netwerk/test/mochitests/test_origin_attributes_conversion.html new file mode 100644 index 0000000000..b9fb914ee1 --- /dev/null +++ b/netwerk/test/mochitests/test_origin_attributes_conversion.html @@ -0,0 +1,135 @@ + + + + Bug 1209162 - Test Origin Attributes Conversion + + + + +

+ +
+
+
+ + diff --git a/netwerk/test/mochitests/test_signed_web_packaged_app_origin.html b/netwerk/test/mochitests/test_signed_web_packaged_app_origin.html index 577a97b519..684762415d 100644 --- a/netwerk/test/mochitests/test_signed_web_packaged_app_origin.html +++ b/netwerk/test/mochitests/test_signed_web_packaged_app_origin.html @@ -91,4 +91,4 @@ function getNodePrincipalOrigin() { - \ No newline at end of file + diff --git a/parser/htmlparser/nsExpatDriver.cpp b/parser/htmlparser/nsExpatDriver.cpp index ee6c977f39..4cb0a8b1f8 100644 --- a/parser/htmlparser/nsExpatDriver.cpp +++ b/parser/htmlparser/nsExpatDriver.cpp @@ -14,8 +14,8 @@ #include "nsIURL.h" #include "nsIUnicharInputStream.h" #include "nsISimpleUnicharStreamFactory.h" +#include "nsIProtocolHandler.h" #include "nsNetUtil.h" -#include "nsNullPrincipal.h" #include "prprf.h" #include "prmem.h" #include "nsTextFormatter.h" @@ -28,7 +28,8 @@ #include "nsError.h" #include "nsXPCOMCIDInternal.h" #include "nsUnicharInputStream.h" -#include "nsIFrame.h" +#include "nsContentUtils.h" +#include "mozilla/IntegerTypeTraits.h" #include "mozilla/Logging.h" @@ -47,6 +48,10 @@ GetExpatDriverLog() return sLog; } +// Use the same maximum tree depth as Chromium (see +// https://chromium.googlesource.com/chromium/src/+/f464165c1dedff1c955d3c051c5a9a1c6a0e8f6b/third_party/WebKit/Source/core/xml/parser/XMLDocumentParser.cpp#85). +static const uint16_t sMaxXMLTreeDepth = 5000; + /***************************** EXPAT CALL BACKS ******************************/ // The callback handlers that get called from the expat parser. @@ -344,9 +349,6 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(nsExpatDriver) NS_IMPL_CYCLE_COLLECTION(nsExpatDriver, mSink, mExtendedSink) -// We store the tagdepth in a PRUint8, so make sure the limit fits in a PRUint8. -PR_STATIC_ASSERT(MAX_REFLOW_DEPTH <= PR_UINT8_MAX); - nsExpatDriver::nsExpatDriver() : mExpatParser(nullptr), mInCData(false), @@ -387,7 +389,12 @@ nsExpatDriver::HandleStartElement(const char16_t *aValue, } if (mSink) { - if (++mTagDepth == MAX_REFLOW_DEPTH) { + // We store the tagdepth in a PRUint16, so make sure the limit fits in a + // PRUint16. + static_assert(sMaxXMLTreeDepth <= + mozilla::MaxValue::value, "sMaxXMLTreeDepth overflows PRUint16"); + + if (++mTagDepth > sMaxXMLTreeDepth) { MaybeStopParser(NS_ERROR_HTMLPARSER_HIERARCHYTOODEEP); return; } @@ -764,73 +771,59 @@ nsExpatDriver::OpenInputStreamFromExternalDTD(const char16_t* aFPIStr, baseURI); NS_ENSURE_SUCCESS(rv, rv); - // check if it is alright to load this uri - bool isChrome = false; - uri->SchemeIs("chrome", &isChrome); - if (!isChrome) { - // since the url is not a chrome url, check to see if we can map the DTD - // to a known local DTD, or if a DTD file of the same name exists in the - // special DTD directory + // make sure the URI is allowed to be loaded in sync + bool isUIResource = false; + rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, + &isUIResource); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr localURI; + if (!isUIResource) { + // Check to see if we can map the DTD to a known local DTD, or if a DTD + // file of the same name exists in the special DTD directory if (aFPIStr) { // see if the Formal Public Identifier (FPI) maps to a catalog entry mCatalogData = LookupCatalogData(aFPIStr); + GetLocalDTDURI(mCatalogData, uri, getter_AddRefs(localURI)); } - - nsCOMPtr localURI; - GetLocalDTDURI(mCatalogData, uri, getter_AddRefs(localURI)); if (!localURI) { return NS_ERROR_NOT_IMPLEMENTED; } - - localURI.swap(uri); } - nsCOMPtr doc; - NS_ASSERTION(mSink == nsCOMPtr(do_QueryInterface(mOriginalSink)), - "In nsExpatDriver::OpenInputStreamFromExternalDTD: " - "mOriginalSink not the same object as mSink?"); - if (mOriginalSink) - doc = do_QueryInterface(mOriginalSink->GetTarget()); - int16_t shouldLoad = nsIContentPolicy::ACCEPT; - rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_DTD, - uri, - (doc ? doc->NodePrincipal() : nullptr), - doc, - EmptyCString(), //mime guess - nullptr, //extra - &shouldLoad); - if (NS_FAILED(rv)) return rv; - if (NS_CP_REJECTED(shouldLoad)) { - // Disallowed by content policy - return NS_ERROR_CONTENT_BLOCKED; - } - - nsAutoCString absURL; - uri->GetSpec(absURL); - - CopyUTF8toUTF16(absURL, aAbsURL); - nsCOMPtr channel; - if (doc) { + if (localURI) { + localURI.swap(uri); rv = NS_NewChannel(getter_AddRefs(channel), uri, - doc, - nsILoadInfo::SEC_NORMAL, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, nsIContentPolicy::TYPE_DTD); } else { - nsCOMPtr nullPrincipal = nsNullPrincipal::Create(); - NS_ENSURE_TRUE(nullPrincipal, NS_ERROR_FAILURE); + NS_ASSERTION(mSink == nsCOMPtr(do_QueryInterface(mOriginalSink)), + "In nsExpatDriver::OpenInputStreamFromExternalDTD: " + "mOriginalSink not the same object as mSink?"); + nsCOMPtr doc; + if (mOriginalSink) { + doc = do_QueryInterface(mOriginalSink->GetTarget()); + } + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); rv = NS_NewChannel(getter_AddRefs(channel), uri, - nullPrincipal, - nsILoadInfo::SEC_NORMAL, + doc, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS | + nsILoadInfo::SEC_ALLOW_CHROME, nsIContentPolicy::TYPE_DTD); } NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString absURL; + uri->GetSpec(absURL); + CopyUTF8toUTF16(absURL, aAbsURL); + channel->SetContentType(NS_LITERAL_CSTRING("application/xml")); - return channel->Open(aStream); + return channel->Open2(aStream); } static nsresult diff --git a/parser/htmlparser/nsExpatDriver.h b/parser/htmlparser/nsExpatDriver.h index 1b532e79d0..7738624748 100644 --- a/parser/htmlparser/nsExpatDriver.h +++ b/parser/htmlparser/nsExpatDriver.h @@ -120,7 +120,7 @@ private: // Necko bool mIsFinalChunk; - PRUint8 mTagDepth; + uint16_t mTagDepth; nsresult mInternalState; diff --git a/testing/web-platform/mozilla/meta/MANIFEST.json b/testing/web-platform/mozilla/meta/MANIFEST.json index 6ea675df15..a3e47e4379 100644 --- a/testing/web-platform/mozilla/meta/MANIFEST.json +++ b/testing/web-platform/mozilla/meta/MANIFEST.json @@ -64,6 +64,18 @@ "url": "/_mozilla/service-workers/service-worker/claim-using-registration.https.html" } ], + "service-workers/service-worker/clients-get.https.html": [ + { + "path": "service-workers/service-worker/clients-get.https.html", + "url": "/_mozilla/service-workers/service-worker/clients-get.https.html" + } + ], + "service-workers/service-worker/clients-get-cross-origin.https.html": [ + { + "path": "service-workers/service-worker/clients-get-cross-origin.https.html", + "url": "/_mozilla/service-workers/service-worker/clients-get-cross-origin.https.html" + } + ], "service-workers/service-worker/clients-matchall-client-types.https.html": [ { "path": "service-workers/service-worker/clients-matchall-client-types.https.html", @@ -232,6 +244,13 @@ "url": "/_mozilla/service-workers/service-worker/fetch-response-xhr.https.html" } ], + "service-workers/service-worker/fetch-waits-for-activate.https.html": [ + { + "path": "service-workers/service-worker/fetch-waits-for-activate.https.html", + "timeout": "long", + "url": "/_mozilla/service-workers/service-worker/fetch-waits-for-activate.https.html" + } + ], "service-workers/service-worker/getregistration.https.html": [ { "path": "service-workers/service-worker/getregistration.https.html", diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html.ini deleted file mode 100644 index 6a0a9a997e..0000000000 --- a/testing/web-platform/mozilla/meta/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[clients-matchall-include-uncontrolled.https.html] - type: testharness - expected: TIMEOUT - [Verify matchAll() respect includeUncontrolled] - expected: TIMEOUT - diff --git a/testing/web-platform/mozilla/meta/service-workers/service-worker/register-wait-forever-in-install-worker.https.html.ini b/testing/web-platform/mozilla/meta/service-workers/service-worker/register-wait-forever-in-install-worker.https.html.ini deleted file mode 100644 index 7623760986..0000000000 --- a/testing/web-platform/mozilla/meta/service-workers/service-worker/register-wait-forever-in-install-worker.https.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[register-wait-forever-in-install-worker.https.html] - type: testharness - expected: TIMEOUT - [register worker that calls waitUntil with a promise that never resolves in oninstall] - expected: TIMEOUT - diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/clients-get-cross-origin.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/clients-get-cross-origin.https.html new file mode 100644 index 0000000000..3413acbf9d --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/clients-get-cross-origin.https.html @@ -0,0 +1,42 @@ + +Service Worker: Clients.get across origins + + + + + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/clients-get.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/clients-get.https.html new file mode 100644 index 0000000000..848c2a9e35 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/clients-get.https.html @@ -0,0 +1,52 @@ + +Service Worker: Clients.get + + + + + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html index 4aadb3dd85..66301fe940 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/clients-matchall-include-uncontrolled.https.html @@ -28,10 +28,10 @@ var expected_without_include_uncontrolled = [ var expected_with_include_uncontrolled = [ /* visibilityState, focused, url, frameType */ + ['visible', true, location.href, 'top-level'], ['visible', false, new URL(scope + '#1', location).toString(), 'nested'], ['visible', true, new URL(scope + '#2', location).toString(), 'nested'], - ['visible', false, new URL(base_url, location).toString(), 'nested'], - ['visible', true, location.href, 'top-level'] + ['visible', false, new URL(base_url, location).toString(), 'nested'] ]; function test_matchall(frame, expected, query_options) { @@ -42,9 +42,13 @@ function test_matchall(frame, expected, query_options) { return new Promise(function(resolve, reject) { var channel = new MessageChannel(); channel.port1.onmessage = function(e) { - assert_equals(e.data.length, expected.length); - for (var i = 0; i < e.data.length; i++) - assert_array_equals(e.data[i], expected[i]); + // Ignore hidden clients which may be coming from background tabs. + var data = e.data.filter(function(info) { + return info[0] == 'visible'; + }); + assert_equals(data.length, expected.length); + for (var i = 0; i < data.length; i++) + assert_array_equals(data[i], expected[i]); resolve(frame); }; frame.contentWindow.navigator.serviceWorker.controller.postMessage( diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event.https.html index a4d9f68a6a..cd13df313d 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event.https.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-event.https.html @@ -68,6 +68,41 @@ async_test(function(t) { .catch(unreached_rejection(t)); }, 'Service Worker responds to fetch event with the referrer URL'); +async_test(function(t) { + var scope = 'resources/simple.html?clientId'; + var frame; + var initial_client_id; + service_worker_unregister_and_register(t, worker, scope) + .then(function(reg) { + return wait_for_state(t, reg.installing, 'activated'); + }) + .then(function() { return with_iframe(scope); }) + .then(function(f) { + frame = f; + assert_equals( + frame.contentDocument.body.textContent.substr(0, 15), + 'Client ID Found', + 'Service Worker should respond to fetch with a client id'); + initial_client_id = frame.contentDocument.body.textContent.substr(17); + return frame.contentWindow.fetch('resources/other.html?clientId'); + }) + .then(function(response) { return response.text(); }) + .then(function(response_text) { + var new_client_id = response_text.substr(17); + assert_equals( + response_text.substr(0, 15), + 'Client ID Found', + 'Service Worker should respond to fetch with an existing client id'); + assert_equals( + initial_client_id, + new_client_id, + 'Service Worker should observe the correct client ID for a controlled document'); + frame.remove(); + return service_worker_unregister_and_done(t, scope); + }) + .catch(unreached_rejection(t)); + }, 'Service Worker responds to fetch event with an existing client id'); + async_test(function(t) { var scope = 'resources/simple.html?ignore'; service_worker_unregister_and_register(t, worker, scope) diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-waits-for-activate.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-waits-for-activate.https.html new file mode 100644 index 0000000000..d2b0eb9ed8 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/fetch-waits-for-activate.https.html @@ -0,0 +1,62 @@ + +Service Worker: Fetch Event Waits for Activate Event + + + + + + + + + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/clients-get-other-origin.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/clients-get-other-origin.html new file mode 100644 index 0000000000..cbd3dcc6f3 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/clients-get-other-origin.html @@ -0,0 +1,64 @@ + + + + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/clients-get-worker.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/clients-get-worker.js new file mode 100644 index 0000000000..48ad770934 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/clients-get-worker.js @@ -0,0 +1,45 @@ +self.onfetch = function(e) { + e.respondWith(new Response(e.clientId)); +}; + +self.onmessage = function(e) { + var port = e.data.port; + if (e.data.message == 'get_client_ids') { + var clientIds = e.data.clientIds; + var message = []; + + Promise.all( + clientIds.map(function(clientId) { + return self.clients.get(clientId); + }).concat(self.clients.get("invalid-id")) + ).then(function(clients) { + clients.forEach(function(client) { + if (client instanceof Client) { + message.push([client.visibilityState, + client.focused, + client.url, + client.frameType]); + } else { + message.push(client); + } + }); + port.postMessage(message); + }); + } else if (e.data.message == 'get_other_client_id') { + var clientId = e.data.clientId; + var message; + + self.clients.get(clientId) + .then(function(client) { + if (client instanceof Client) { + message = [client.visibilityState, + client.focused, + client.url, + client.frameType]; + } else { + message = client; + } + port.postMessage(message); + }); + } +}; diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/clients-matchall-worker.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/clients-matchall-worker.js index be69c4b23e..f0ae90d814 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/clients-matchall-worker.js +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/clients-matchall-worker.js @@ -5,10 +5,17 @@ self.onmessage = function(e) { self.clients.matchAll(options).then(function(clients) { var message = []; clients.forEach(function(client) { + var frame_type = client.frameType; + if (client.url.indexOf('clients-matchall-include-uncontrolled.https.html') > -1 && + client.frameType == 'auxiliary') { + // The test tab might be opened using window.open() by the test framework. + // In that case, just pretend it's top-level! + frame_type = 'top-level'; + } message.push([client.visibilityState, client.focused, client.url, - client.frameType]); + frame_type]); }); // Sort by url message.sort(function(a, b) { return a[2] > b[2] ? 1 : -1; }); diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-test-worker.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-test-worker.js index 2cdc0f926e..b56602e0fa 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-test-worker.js +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-event-test-worker.js @@ -11,6 +11,16 @@ function handleReferrer(event) { ['Referrer: ' + event.request.referrer]))); } +function handleClientId(event) { + var body; + if (event.clientId !== '') { + body = 'Client ID Found: ' + event.clientId; + } else { + body = 'Client ID Not Found'; + } + event.respondWith(new Response(body)); +} + function handleNullBody(event) { event.respondWith(new Response()); } @@ -66,6 +76,7 @@ self.addEventListener('fetch', function(event) { { pattern: '?string', fn: handleString }, { pattern: '?blob', fn: handleBlob }, { pattern: '?referrer', fn: handleReferrer }, + { pattern: '?clientId', fn: handleClientId }, { pattern: '?ignore', fn: function() {} }, { pattern: '?null', fn: handleNullBody }, { pattern: '?fetch', fn: handleFetch }, diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js new file mode 100644 index 0000000000..66f3e59361 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-waits-for-activate-worker.js @@ -0,0 +1,17 @@ +var activatePromiseResolve; + +addEventListener('activate', function(evt) { + evt.waitUntil(new Promise(function(resolve) { + activatePromiseResolve = resolve; + })); +}); + +addEventListener('message', function(evt) { + if (typeof activatePromiseResolve === 'function') { + activatePromiseResolve(); + } +}); + +addEventListener('fetch', function(evt) { + evt.respondWith(new Response('Hello world')); +}); diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/interfaces-worker.sub.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/interfaces-worker.sub.js index a4cb4c1073..9c97319d43 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/interfaces-worker.sub.js +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/interfaces-worker.sub.js @@ -83,12 +83,18 @@ test(function() { assert_equals( new FetchEvent('FetchEvent').bubbles, false, 'Default FetchEvent.bubbles should be false'); + assert_equals( + new FetchEvent('FetchEvent').clientId, + null, 'Default FetchEvent.clientId should be null'); assert_equals( new FetchEvent('FetchEvent').isReload, false, 'Default FetchEvent.isReload should be false'); assert_equals( new FetchEvent('FetchEvent', {cancelable: false}).cancelable, false, 'FetchEvent.cancelable should be false'); + assert_equals( + new FetchEvent('FetchEvent', {clientId : 'test-client-id'}).clientId, 'test-client-id', + 'FetchEvent.clientId with option {clientId : "test-client-id"} should be "test-client-id"'); assert_equals( new FetchEvent('FetchEvent', {isReload : true}).isReload, true, 'FetchEvent.isReload with option {isReload : true} should be true'); diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/skip-waiting-worker.js b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/skip-waiting-worker.js index 4b09af52e3..1d6bcb39fa 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/skip-waiting-worker.js +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/skip-waiting-worker.js @@ -1,7 +1,11 @@ importScripts('worker-testharness.js'); promise_test(function() { - return self.skipWaiting() + // wait for the worker to reach "installing" state, otherwise skipWaiting() + // will fail. Bug 1228277 + return new Promise(function(res, rej) { + oninstall = res; + }).then(() => skipWaiting()) .then(function(result) { assert_equals(result, undefined, 'Promise should be resolved with undefined'); diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/skip-waiting.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/skip-waiting.https.html index 7c1c41f3f7..d73a0c81b5 100644 --- a/testing/web-platform/mozilla/tests/service-workers/service-worker/skip-waiting.https.html +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/skip-waiting.https.html @@ -46,7 +46,6 @@ promise_test(function(t) { 'Worker with url2 should be redundant'); assert_equals(sw_registration.active.scriptURL, normalizeURL(url3), 'Worker with url3 should be activated'); - frame.remove(); return service_worker_unregister_and_done(t, scope); }); }, 'Test skipWaiting with both active and waiting workers'); diff --git a/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.cpp b/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.cpp index 55690a539c..a8ad3a3445 100644 --- a/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.cpp +++ b/toolkit/components/url-classifier/nsUrlClassifierStreamUpdater.cpp @@ -102,7 +102,7 @@ nsUrlClassifierStreamUpdater::FetchUpdate(nsIURI *aUpdateUrl, NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr loadInfo = mChannel->GetLoadInfo(); - loadInfo->SetOriginAttributes(mozilla::OriginAttributes(NECKO_SAFEBROWSING_APP_ID, false)); + loadInfo->SetOriginAttributes(mozilla::NeckoOriginAttributes(NECKO_SAFEBROWSING_APP_ID, false)); mBeganStream = false; diff --git a/toolkit/content/plugins.html b/toolkit/content/plugins.html index 34e9644cc8..d2c4ac2dec 100644 --- a/toolkit/content/plugins.html +++ b/toolkit/content/plugins.html @@ -9,8 +9,6 @@ diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 3a2cb72f89..bf23993a15 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -33,10 +33,17 @@ const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; const PREF_EM_UPDATE_BACKGROUND_URL = "extensions.update.background.url"; const PREF_APP_UPDATE_ENABLED = "app.update.enabled"; const PREF_APP_UPDATE_AUTO = "app.update.auto"; +const PREF_EM_HOTFIX_ID = "extensions.hotfix.id"; +const PREF_EM_HOTFIX_LASTVERSION = "extensions.hotfix.lastVersion"; +const PREF_EM_HOTFIX_URL = "extensions.hotfix.url"; +const PREF_EM_CERT_CHECKATTRIBUTES = "extensions.hotfix.cert.checkAttributes"; +const PREF_EM_HOTFIX_CERTS = "extensions.hotfix.certs."; const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; const PREF_SELECTED_LOCALE = "general.useragent.locale"; const UNKNOWN_XPCOM_ABI = "unknownABI"; +const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion"; + const UPDATE_REQUEST_VERSION = 2; const CATEGORY_UPDATE_PARAMS = "extension-update-params"; @@ -620,9 +627,12 @@ var gCheckUpdateSecurityDefault = true; var gCheckUpdateSecurity = gCheckUpdateSecurityDefault; var gUpdateEnabled = true; var gAutoUpdateDefault = true; +var gHotfixID = null; +var gWebExtensionsMinPlatformVersion = null; var gShutdownBarrier = null; var gRepoShutdownState = ""; var gShutdownInProgress = false; +var gPluginPageListener = null; /** * This is the real manager, kept here rather than in AddonManager to keep its @@ -889,6 +899,16 @@ var AddonManagerInternal = { } catch (e) {} Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this, false); + try { + gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); + } catch (e) {} + Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this, false); + + try { + gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); + } catch (e) {} + Services.prefs.addObserver(PREF_MIN_WEBEXT_PLATFORM_VERSION, this, false); + let defaultProvidersEnabled = true; try { defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED); @@ -956,6 +976,13 @@ var AddonManagerInternal = { delete this.startupChanges[type]; } + // Support for remote about:plugins. Note that this module isn't loaded + // at the top because Services.appinfo is defined late in tests. + Cu.import("resource://gre/modules/RemotePageManager.jsm"); + + gPluginPageListener = new RemotePages("about:plugins"); + gPluginPageListener.addMessageListener("RequestPlugins", this.requestPlugins); + gStartupComplete = true; this.recordTimestamp("AMI_startup_end"); } @@ -1166,6 +1193,9 @@ var AddonManagerInternal = { Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this); Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this); Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this); + Services.prefs.removeObserver(PREF_EM_HOTFIX_ID, this); + gPluginPageListener.destroy(); + gPluginPageListener = null; let savedError = null; // Only shut down providers if they've been started. @@ -1209,6 +1239,24 @@ var AddonManagerInternal = { } }), + requestPlugins: function({ target: port }) { + // Lists all the properties that plugins.html needs + const NEEDED_PROPS = ["name", "pluginLibraries", "pluginFullpath", "version", + "isActive", "blocklistState", "description", + "pluginMimeTypes"]; + function filterProperties(plugin) { + let filtered = {}; + for (let prop of NEEDED_PROPS) { + filtered[prop] = plugin[prop]; + } + return filtered; + } + + AddonManager.getAddonsByTypes(["plugin"], function (aPlugins) { + port.sendAsyncMessage("PluginList", [filterProperties(p) for (p of aPlugins)]); + }); + }, + /** * Notified when a preference we're interested in has changed. * @@ -1283,6 +1331,18 @@ var AddonManagerInternal = { this.callManagerListeners("onUpdateModeChanged"); break; } + case PREF_EM_HOTFIX_ID: { + try { + gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); + } catch(e) { + gHotfixID = null; + } + break; + } + case PREF_MIN_WEBEXT_PLATFORM_VERSION: { + gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(PREF_MIN_WEBEXT_PLATFORM_VERSION); + break; + } } }, @@ -1394,6 +1454,10 @@ var AddonManagerInternal = { let updates = []; for (let addon of allAddons) { + if (addon.id == hotfixID) { + continue; + } + // Check all add-ons for updates so that any compatibility updates will // be applied updates.push(new Promise((resolve, reject) => { @@ -2491,6 +2555,10 @@ var AddonManagerInternal = { Services.prefs.setBoolPref(PREF_EM_UPDATE_ENABLED, aValue); return aValue; }, + + get hotfixID() { + return gHotfixID; + }, }; /** @@ -2522,7 +2590,11 @@ this.AddonManagerPrivate = { backgroundUpdateTimerHandler() { // Don't call through to the real update check if no checks are enabled. - if (!AddonManagerInternal.updateEnabled) { + let checkHotfix = AddonManagerInternal.hotfixID && + Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) && + Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO); + + if (!AddonManagerInternal.updateEnabled && !checkHotfix) { logger.info("Skipping background update check"); return; } @@ -2634,6 +2706,10 @@ this.AddonManagerPrivate = { safeCall(listener.onUpdateFinished.bind(listener), addon); } }, + + get webExtensionsMinPlatformVersion() { + return gWebExtensionsMinPlatformVersion; + }, }; /** @@ -3010,6 +3086,10 @@ this.AddonManager = { AddonManagerInternal.autoUpdateDefault = aValue; }, + get hotfixID() { + return AddonManagerInternal.hotfixID; + }, + escapeAddonURI: function AM_escapeAddonURI(aAddon, aUri, aAppVersion) { return AddonManagerInternal.escapeAddonURI(aAddon, aUri, aAppVersion); }, diff --git a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm index 91592262e2..6bae768eec 100644 --- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm +++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm @@ -34,6 +34,8 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", + "resource://gre/modules/AddonManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/addons/AddonRepository.jsm"); @@ -220,6 +222,48 @@ RDFSerializer.prototype = { } } +/** + * Sanitizes the update URL in an update item, as returned by + * parseRDFManifest and parseJSONManifest. Ensures that: + * + * - The URL is secure, or secured by a strong enough hash. + * - The security principal of the update manifest has permission to + * load the URL. + * + * @param aUpdate + * The update item to sanitize. + * @param aRequest + * The XMLHttpRequest used to load the manifest. + * @param aHashPattern + * The regular expression used to validate the update hash. + * @param aHashString + * The human-readable string specifying which hash functions + * are accepted. + */ +function sanitizeUpdateURL(aUpdate, aRequest, aHashPattern, aHashString) { + if (aUpdate.updateURL) { + let scriptSecurity = Services.scriptSecurityManager; + let principal = scriptSecurity.getChannelURIPrincipal(aRequest.channel); + try { + // This logs an error on failure, so no need to log it a second time + scriptSecurity.checkLoadURIStrWithPrincipal(principal, aUpdate.updateURL, + scriptSecurity.DISALLOW_SCRIPT); + } catch (e) { + delete aUpdate.updateURL; + return; + } + + if (AddonManager.checkUpdateSecurity && + !aUpdate.updateURL.startsWith("https:") && + !aHashPattern.test(aUpdate.updateHash)) { + logger.warn(`Update link ${aUpdate.updateURL} is not secure and is not verified ` + + `by a strong enough hash (needs to be ${aHashString}).`); + delete aUpdate.updateURL; + delete aUpdate.updateHash; + } + } +} + /** * Parses an RDF style update manifest into an array of update objects. * @@ -229,10 +273,17 @@ RDFSerializer.prototype = { * An optional update key for the add-on * @param aRequest * The XMLHttpRequest that has retrieved the update manifest + * @param aManifestData + * The pre-parsed manifest, as a bare XML DOM document * @return an array of update objects * @throws if the update manifest is invalid in any way */ -function parseRDFManifest(aId, aUpdateKey, aRequest) { +function parseRDFManifest(aId, aUpdateKey, aRequest, aManifestData) { + if (aManifestData.documentElement.namespaceURI != PREFIX_NS_RDF) { + throw Components.Exception("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI); + return; + } + function EM_R(aProp) { return gRDF.GetResource(PREFIX_NS_EM + aProp); } @@ -369,20 +420,136 @@ function parseRDFManifest(aId, aUpdateKey, aRequest) { targetApplications: [appEntry] }; - if (result.updateURL && AddonManager.checkUpdateSecurity && - result.updateURL.substring(0, 6) != "https:" && - (!result.updateHash || result.updateHash.substring(0, 3) != "sha")) { - logger.warn("updateLink " + result.updateURL + " is not secure and is not verified" + - " by a strong enough hash (needs to be sha1 or stronger)."); - delete result.updateURL; - delete result.updateHash; - } + // The JSON update protocol requires an SHA-2 hash. RDF still + // supports SHA-1, for compatibility reasons. + sanitizeUpdateURL(result, aRequest, /^sha/, "sha1 or stronger"); + results.push(result); } } return results; } +/** + * Parses an JSON update manifest into an array of update objects. + * + * @param aId + * The ID of the add-on being checked for updates + * @param aUpdateKey + * An optional update key for the add-on + * @param aRequest + * The XMLHttpRequest that has retrieved the update manifest + * @param aManifestData + * The pre-parsed manifest, as a JSON object tree + * @return an array of update objects + * @throws if the update manifest is invalid in any way + */ +function parseJSONManifest(aId, aUpdateKey, aRequest, aManifestData) { + if (aUpdateKey) + throw Components.Exception("Update keys are not supported for JSON update manifests"); + + let TYPE_CHECK = { + "array": val => Array.isArray(val), + "object": val => val && typeof val == "object" && !Array.isArray(val), + }; + + function getProperty(aObj, aProperty, aType, aDefault = undefined) { + if (!(aProperty in aObj)) + return aDefault; + + let value = aObj[aProperty]; + + let matchesType = aType in TYPE_CHECK ? TYPE_CHECK[aType](value) : typeof value == aType; + if (!matchesType) + throw Components.Exception(`Update manifest property '${aProperty}' has incorrect type (expected ${aType})`); + + return value; + } + + function getRequiredProperty(aObj, aProperty, aType) { + let value = getProperty(aObj, aProperty, aType); + if (value === undefined) + throw Components.Exception(`Update manifest is missing a required ${aProperty} property.`); + return value; + } + + let manifest = aManifestData; + + if (!TYPE_CHECK["object"](manifest)) + throw Components.Exception("Root element of update manifest must be a JSON object literal"); + + // The set of add-ons this manifest has updates for + let addons = getRequiredProperty(manifest, "addons", "object"); + + // The entry for this particular add-on + let addon = getProperty(addons, aId, "object"); + + // A missing entry doesn't count as a failure, just as no avialable update + // information + if (!addon) { + logger.warn("Update manifest did not contain an entry for " + aId); + return []; + } + + // The list of available updates + let updates = getProperty(addon, "updates", "array", []); + + let results = []; + + for (let update of updates) { + let version = getRequiredProperty(update, "version", "string"); + + logger.debug(`Found an update entry for ${aId} version ${version}`); + + let applications = getProperty(update, "applications", "object", + { gecko: {} }); + + // "gecko" is currently the only supported application entry. If + // it's missing, skip this update. + if (!("gecko" in applications)) + continue; + + let app = getProperty(applications, "gecko", "object"); + + let appEntry = { + id: TOOLKIT_ID, + minVersion: getProperty(app, "strict_min_version", "string", + AddonManagerPrivate.webExtensionsMinPlatformVersion), + maxVersion: "*", + }; + + let result = { + id: aId, + version: version, + multiprocessCompatible: getProperty(update, "multiprocess_compatible", "boolean", true), + updateURL: getProperty(update, "update_link", "string"), + updateHash: getProperty(update, "update_hash", "string"), + updateInfoURL: getProperty(update, "update_info_url", "string"), + strictCompatibility: false, + targetApplications: [appEntry], + }; + + if ("strict_max_version" in app) { + if ("advisory_max_version" in app) { + logger.warn("Ignoring 'advisory_max_version' update manifest property for " + + aId + " property since 'strict_max_version' also present"); + } + + appEntry.maxVersion = getProperty(app, "strict_max_version", "string"); + result.strictCompatibility = appEntry.maxVersion != "*"; + } else if ("advisory_max_version" in app) { + appEntry.maxVersion = getProperty(app, "advisory_max_version", "string"); + } + + // The JSON update protocol requires an SHA-2 hash. RDF still + // supports SHA-1, for compatibility reasons. + sanitizeUpdateURL(result, aRequest, /^sha(256|512):/, "sha256 or sha512"); + + results.push(result); + } + return results; +} + /** * Starts downloading an update manifest and then passes it to an appropriate * parser to convert to an array of update objects @@ -418,7 +585,7 @@ function UpdateParser(aId, aUpdateKey, aUrl, aObserver) { this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; // Prevent the request from writing to cache. this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; - this.request.overrideMimeType("text/xml"); + this.request.overrideMimeType("text/plain"); this.request.setRequestHeader("Moz-XPI-Update", "1", true); this.request.timeout = TIMEOUT; var self = this; @@ -476,41 +643,50 @@ UpdateParser.prototype = { return; } - let xml = request.responseXML; - if (!xml || xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) { - logger.warn("Update manifest was not valid XML"); + // Detect the manifest type by first attempting to parse it as + // JSON, and falling back to parsing it as XML if that fails. + let parser; + try { + try { + let json = JSON.parse(request.responseText); + + parser = () => parseJSONManifest(this.id, this.updateKey, request, json); + } catch (e if e instanceof SyntaxError) { + let domParser = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser); + let xml = domParser.parseFromString(request.responseText, "text/xml"); + + if (xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) + throw new Error("Update manifest was not valid XML or JSON"); + + parser = () => parseRDFManifest(this.id, this.updateKey, request, xml); + } + } catch (e) { + logger.warn("onUpdateCheckComplete failed to determine manifest type"); + this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT); + return; + } + + let results; + try { + results = parser(); + } + catch (e) { + logger.warn("onUpdateCheckComplete failed to parse update manifest", e); this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR); return; } - // We currently only know about RDF update manifests - if (xml.documentElement.namespaceURI == PREFIX_NS_RDF) { - let results = null; - + if ("onUpdateCheckComplete" in this.observer) { try { - results = parseRDFManifest(this.id, this.updateKey, request); + this.observer.onUpdateCheckComplete(results); } catch (e) { - logger.warn("onUpdateCheckComplete failed to parse RDF manifest", e); - this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR); - return; + logger.warn("onUpdateCheckComplete notification failed", e); } - if ("onUpdateCheckComplete" in this.observer) { - try { - this.observer.onUpdateCheckComplete(results); - } - catch (e) { - logger.warn("onUpdateCheckComplete notification failed", e); - } - } - else { - logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker")); - } - return; } - - logger.warn("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI); - this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT); + else { + logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker")); + } }, /** diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json new file mode 100644 index 0000000000..027a9b2333 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_update.json @@ -0,0 +1,215 @@ +{ + "addons": { + "addon1@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_min_version": "1" + } + } + }, + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_min_version": "2" + } + } + }, + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update.xpi", + "update_info_url": "http://example.com/updateInfo.xhtml", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_min_version": "1" + } + } + } + ] + }, + + "addon2@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon2@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon3@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "3", + "advisory_max_version": "3" + } + } + } + ] + }, + + "addon4@tests.mozilla.org": { + "updates": [ + { + "version": "5.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "0" + } + } + } + ] + }, + + "addon7@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "applications": { + "gecko": { + "strict_min_version": "0", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon8@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update8.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + } + } + ] + }, + + "addon9@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_2.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + } + }, + { + "_comment_": "Incompatible when strict compatibility is enabled", + "version": "3.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_3.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.9", + "advisory_max_version": "0.9" + } + } + }, + { + "_comment_": "Incompatible due to compatibility override", + "version": "4.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_4.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.9", + "advisory_max_version": "0.9" + } + } + }, + { + "_comment_": "Addon for future version of app", + "version": "4.0", + "update_link": "http://localhost:%PORT%/addons/test_update9_5.xpi", + "applications": { + "gecko": { + "strict_min_version": "5", + "advisory_max_version": "6" + } + } + } + ] + }, + + "addon10@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "update_link": "http://localhost:%PORT%/addons/test_update10.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "advisory_max_version": "0.4" + } + } + } + ] + }, + + "addon11@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update11.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "strict_max_version": "0.2" + } + } + } + ] + }, + + "addon12@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:%PORT%/addons/test_update12.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "advisory_max_version": "1" + } + } + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json new file mode 100644 index 0000000000..811e50158e --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.json @@ -0,0 +1,327 @@ +{ + "addons": { + "updatecheck1@tests.mozilla.org": { + "updates": [ + { + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + }, + { + "_comment_": "This update is incompatible and so should not be considered a valid update", + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_max_version": "2" + } + } + }, + { + "version": "3.0", + "update_link": "https://localhost:4444/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + }, + { + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "2" + } + } + }, + { + "_comment_": "This update is incompatible and so should not be considered a valid update", + "version": "4.0", + "update_link": "https://localhost:4444/addons/test4.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "strict_max_version": "2" + } + } + } + ] + }, + + "test_bug378216_5@tests.mozilla.org": { + "_comment_": "An update which expects a signature. It will fail since signatures are ", + "_comment_": "supported in this format.", + "_comment_": "The updateLink will also be ignored since it is not secure and there ", + "_comment_": "is no updateHash.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_5@tests.mozilla.org": { + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_7@tests.mozilla.org": { + "_comment_": "An update which expects a signature. It will fail since signatures are ", + "_comment_": "supported in this format.", + "_comment_": "The updateLink will also be ignored since it is not secure ", + "_comment_": "and there is no updateHash.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "2" + } + } + } + ] + }, + + "test_bug378216_8@tests.mozilla.org": { + "_comment_": "The updateLink will be ignored since it is not secure and ", + "_comment_": "there is no updateHash.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_9@tests.mozilla.org": { + "_comment_": "The updateLink will used since there is an updateHash to verify it.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "update_hash": "sha256:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_10@tests.mozilla.org": { + "_comment_": "The updateLink will used since it is a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_11@tests.mozilla.org": { + "_comment_": "The updateLink will used since it is a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://localhost:4444/broken.xpi", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_12@tests.mozilla.org": { + "_comment_": "The updateLink will not be used since the updateHash ", + "_comment_": "verifying it is not strong enough.", + + "updates": [ + { + "version": "2.0", + "update_link": "http://localhost:4444/broken.xpi", + "update_hash": "sha1:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "test_bug378216_13@tests.mozilla.org": { + "_comment_": "An update with a weak hash. The updateLink will used since it is ", + "_comment_": "a secure URL.", + + "updates": [ + { + "version": "2.0", + "update_link": "https://localhost:4444/broken.xpi", + "update_hash": "sha1:78fc1d2887eda35b4ad2e3a0b60120ca271ce6e6", + "applications": { + "gecko": { + "strict_min_version": "1", + "strict_max_version": "1" + } + } + } + ] + }, + + "_comment_": "There should be no information present for test_bug378216_14", + + "test_bug378216_15@tests.mozilla.org": { + "_comment_": "Invalid update JSON", + + "updates": "foo" + }, + + "ignore-compat@tests.mozilla.org": { + "_comment_": "Various updates available - one is not compatible, but compatibility checking is disabled", + + "updates": [ + { + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "advisory_max_version": "0.2" + } + } + }, + { + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.5", + "advisory_max_version": "0.6" + } + } + }, + { + "_comment_": "Update for future app versions - should never be compatible", + "version": "3.0", + "update_link": "https://localhost:4444/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "advisory_max_version": "3" + } + } + } + ] + }, + + "compat-override@tests.mozilla.org": { + "_comment_": "Various updates available - one is not compatible, but compatibility checking is disabled", + + "updates": [ + { + "_comment_": "Has compatibility override, but it doesn't match this app version", + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "advisory_max_version": "0.2" + } + } + }, + { + "_comment_": "Has compatibility override, so is incompaible", + "version": "2.0", + "update_link": "https://localhost:4444/addons/test2.xpi", + "applications": { + "gecko": { + "strict_min_version": "0.5", + "advisory_max_version": "0.6" + } + } + }, + { + "_comment_": "Update for future app versions - should never be compatible", + "version": "3.0", + "update_link": "https://localhost:4444/addons/test3.xpi", + "applications": { + "gecko": { + "strict_min_version": "2", + "advisory_max_version": "3" + } + } + } + ] + }, + + "compat-strict-optin@tests.mozilla.org": { + "_comment_": "Opt-in to strict compatibility checking", + + "updates": [ + { + "version": "1.0", + "update_link": "https://localhost:4444/addons/test1.xpi", + "_comment_": "strictCompatibility: true", + "applications": { + "gecko": { + "strict_min_version": "0.1", + "strict_max_version": "0.2" + } + } + } + ] + } + } +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf index 93c82886a6..c5d97ada0d 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf +++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_updatecheck.rdf @@ -236,7 +236,7 @@ A90eF5zy - diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index da3e8a0001..0ea7b1cd5a 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1365,7 +1365,7 @@ if ("nsIWindowsRegKey" in AM_Ci) { * This is a mock nsIWindowsRegistry implementation. It only implements the * methods that the extension manager requires. */ - function MockWindowsRegKey() { + var MockWindowsRegKey = function MockWindowsRegKey() { } MockWindowsRegKey.prototype = { @@ -1556,6 +1556,30 @@ do_register_cleanup(function addon_cleanup() { } catch (e) {} }); +/** + * Creates a new HttpServer for testing, and begins listening on the + * specified port. Automatically shuts down the server when the test + * unit ends. + * + * @param port + * The port to listen on. If omitted, listen on a random + * port. The latter is the preferred behavior. + * + * @return HttpServer + */ +function createHttpServer(port = -1) { + let server = new HttpServer(); + server.start(port); + + do_register_cleanup(() => { + return new Promise(resolve => { + server.stop(resolve); + }); + }); + + return server; +} + /** * Handler function that responds with the interpolated * static file associated to the URL specified by request.path. @@ -1742,19 +1766,89 @@ function promiseAddonsWithOperationsByTypes(aTypes) { */ function promiseFindAddonUpdates(addon, reason = AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) { return new Promise((resolve, reject) => { + let result = {}; addon.findUpdates({ - install: null, - - onUpdateAvailable: function(addon, install) { - this.install = install; + onNoCompatibilityUpdateAvailable: function(addon2) { + if ("compatibilityUpdate" in result) { + do_throw("Saw multiple compatibility update events"); + } + equal(addon, addon2); + addon.compatibilityUpdate = false; }, - onUpdateFinished: function(addon, error) { - if (error == AddonManager.UPDATE_STATUS_NO_ERROR) - resolve(this.install); - else - reject(error); + onCompatibilityUpdateAvailable: function(addon2) { + if ("compatibilityUpdate" in result) { + do_throw("Saw multiple compatibility update events"); + } + equal(addon, addon2); + addon.compatibilityUpdate = true; + }, + + onNoUpdateAvailable: function(addon2) { + if ("updateAvailable" in result) { + do_throw("Saw multiple update available events"); + } + equal(addon, addon2); + result.updateAvailable = false; + }, + + onUpdateAvailable: function(addon2, install) { + if ("updateAvailable" in result) { + do_throw("Saw multiple update available events"); + } + equal(addon, addon2); + result.updateAvailable = install; + }, + + onUpdateFinished: function(addon2, error) { + equal(addon, addon2); + if (error == AddonManager.UPDATE_STATUS_NO_ERROR) { + resolve(result); + } else { + result.error = error; + reject(result); + } } }, reason); }); } + +/** + * Monitors console output for the duration of a task, and returns a promise + * which resolves to a tuple containing a list of all console messages + * generated during the task's execution, and the result of the task itself. + * + * @param {function} aTask + * The task to run while monitoring console output. May be + * either a generator function, per Task.jsm, or an ordinary + * function which returns promose. + * @return {Promise<[Array, *]>} + */ +var promiseConsoleOutput = Task.async(function*(aTask) { + const DONE = "=== xpcshell test console listener done ==="; + + let listener, messages = []; + let awaitListener = new Promise(resolve => { + listener = msg => { + if (msg == DONE) { + resolve(); + } else { + msg instanceof Components.interfaces.nsIScriptError; + messages.push(msg); + } + } + }); + + Services.console.registerListener(listener); + try { + let result = yield aTask(); + + Services.console.logStringMessage(DONE); + yield awaitListener; + + return { messages, result }; + } + finally { + Services.console.unregisterListener(listener); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js index 0c6c97225f..0011c598a0 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js @@ -505,9 +505,8 @@ add_task(function* setup() { yield promiseInstallAllFiles(ADDON_FILES); yield promiseRestartManager(); - gServer = new HttpServer(); + gServer = createHttpServer(PORT); gServer.registerDirectory("/data/", do_get_file("data")); - gServer.start(PORT); }); // Tests AddonRepository.cacheEnabled @@ -704,7 +703,3 @@ add_task(function* run_test_17() { let aAddons = yield promiseAddonsByIDs(ADDON_IDS); check_results(aAddons, WITH_EXTENSION_CACHE); }); - -add_task(function* end_test() { - yield new Promise((resolve, reject) => gServer.stop(resolve)); -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js index 46f939943b..a0060b55bd 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js @@ -40,8 +40,7 @@ Cu.import("resource://gre/modules/NetUtil.jsm"); Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false) Cu.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; // register static files with server and interpolate port numbers in them @@ -1313,9 +1312,3 @@ add_task(function* run_local_install_test() { check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); }); - -add_task(function* shutdown_httpserver() { - yield new Promise((resolve, reject) => { - testserver.stop(resolve); - }); -}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js index ceb472f98f..e614c953e7 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js @@ -306,10 +306,9 @@ add_task(function* init() { }, profileDir); // Create and configure the HTTP server. - testserver = new HttpServer(); + testserver = createHttpServer(4444); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); - testserver.start(4444); startupManager(); @@ -474,13 +473,3 @@ add_task(function* run_test_6() { "override1x2-1x3@tests.mozilla.org"]); check_state_v1_2(addons); }); - -add_task(function* cleanup() { - return new Promise((resolve, reject) => { - testserver.stop(resolve); - }); -}); - -function run_test() { - run_next_test(); -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js index 70de3b426d..c859c474c7 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug570173.js @@ -16,65 +16,47 @@ function run_test() { createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); // Create and configure the HTTP server. - testserver = new HttpServer(); + testserver = createHttpServer(); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); - testserver.start(-1); gPort = testserver.identity.primaryPort; - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_missing.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - startupManager(); - - do_test_pending(); - run_test_1(); -} - -function end_test() { - testserver.stop(do_test_finished); + run_next_test(); } // Verify that an update check returns the correct errors. -function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "1.0"); +add_task(function* () { + for (let manifestType of ["rdf", "json"]) { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: `http://localhost:${gPort}/data/test_missing.${manifestType}`, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + bootstrap: "true", + }, profileDir); - let sawCompat = false; - let sawUpdate = false; - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - sawCompat = true; - }, + yield promiseRestartManager(); - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen a compatibility update"); - }, + let addon = yield promiseAddonByID("addon1@tests.mozilla.org"); - onNoUpdateAvailable: function(addon) { - sawUpdate = true; - }, + ok(addon); + ok(addon.updateURL.endsWith(manifestType)); + equal(addon.version, "1.0"); - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an update"); - }, + // We're expecting an error, so resolve when the promise is rejected. + let update = yield promiseFindAddonUpdates(addon, AddonManager.UPDATE_WHEN_USER_REQUESTED) + .catch(Promise.resolve); - onUpdateFinished: function(addon, error) { - do_check_true(sawCompat); - do_check_true(sawUpdate); - do_check_eq(error, AddonManager.UPDATE_STATUS_DOWNLOAD_ERROR); - end_test(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} + ok(!update.compatibilityUpdate, "not expecting a compatibility update"); + ok(!update.updateAvailable, "not expecting a compatibility update"); + + equal(update.error, AddonManager.UPDATE_STATUS_DOWNLOAD_ERROR); + + addon.uninstall(); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js b/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js new file mode 100644 index 0000000000..2e8cc6a972 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_json_updatecheck.js @@ -0,0 +1,373 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +// This verifies that AddonUpdateChecker works correctly for JSON +// update manifests, particularly for behavior which does not +// cleanly overlap with RDF manifests. + +const TOOLKIT_ID = "toolkit@mozilla.org"; +const TOOLKIT_MINVERSION = "42.0a1"; + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42.0a2", "42.0a2"); + +Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); +Components.utils.import("resource://testing-common/httpd.js"); + +let testserver = createHttpServer(); +gPort = testserver.identity.primaryPort; + +let gUpdateManifests = {}; + +function mapManifest(aPath, aManifestData) { + gUpdateManifests[aPath] = aManifestData; + testserver.registerPathHandler(aPath, serveManifest); +} + +function serveManifest(request, response) { + let manifest = gUpdateManifests[request.path]; + + response.setHeader("Content-Type", manifest.contentType, false); + response.write(manifest.data); +} + +const extensionsDir = gProfD.clone(); +extensionsDir.append("extensions"); + + +function checkUpdates(aData) { + // Registers JSON update manifest for it with the testing server, + // checks for updates, and yields the list of updates on + // success. + + let extension = aData.manifestExtension || "json"; + + let path = `/updates/${aData.id}.${extension}`; + let updateUrl = `http://localhost:${gPort}${path}` + + let addonData = {}; + if ("updates" in aData) + addonData.updates = aData.updates; + + let manifestJSON = { + "addons": { + [aData.id]: addonData + } + }; + + mapManifest(path.replace(/\?.*/, ""), + { data: JSON.stringify(manifestJSON), + contentType: aData.contentType || "application/json" }); + + + return new Promise((resolve, reject) => { + AddonUpdateChecker.checkForUpdates(aData.id, aData.updateKey, updateUrl, { + onUpdateCheckComplete: resolve, + + onUpdateCheckError: function(status) { + reject(new Error("Update check failed with status " + status)); + } + }); + }); +} + + +add_task(function* test_default_values() { + // Checks that the appropriate defaults are used for omitted values. + + startupManager(); + + let updates = yield checkUpdates({ + id: "updatecheck-defaults@tests.mozilla.org", + version: "0.1", + updates: [{ + version: "0.2" + }] + }); + + equal(updates.length, 1); + let update = updates[0]; + + equal(update.targetApplications.length, 1); + let targetApp = update.targetApplications[0]; + + equal(targetApp.id, TOOLKIT_ID); + equal(targetApp.minVersion, TOOLKIT_MINVERSION); + equal(targetApp.maxVersion, "*"); + + equal(update.version, "0.2"); + equal(update.multiprocessCompatible, true, "multiprocess_compatible flag"); + equal(update.strictCompatibility, false, "inferred strictConpatibility flag"); + equal(update.updateURL, null, "updateURL"); + equal(update.updateHash, null, "updateHash"); + equal(update.updateInfoURL, null, "updateInfoURL"); + + // If there's no applications property, we default to using one + // containing "gecko". If there is an applications property, but + // it doesn't contain "gecko", the update is skipped. + updates = yield checkUpdates({ + id: "updatecheck-defaults@tests.mozilla.org", + version: "0.1", + updates: [{ + version: "0.2", + applications: { "foo": {} } + }] + }); + + equal(updates.length, 0); + + // Updates property is also optional. No updates, but also no error. + updates = yield checkUpdates({ + id: "updatecheck-defaults@tests.mozilla.org", + version: "0.1", + }); + + equal(updates.length, 0); +}); + + +add_task(function* test_explicit_values() { + // Checks that the appropriate explicit values are used when + // provided. + + let updates = yield checkUpdates({ + id: "updatecheck-explicit@tests.mozilla.org", + version: "0.1", + updates: [{ + version: "0.2", + update_link: "https://example.com/foo.xpi", + update_hash: "sha256:0", + update_info_url: "https://example.com/update_info.html", + multiprocess_compatible: false, + applications: { + gecko: { + strict_min_version: "42.0a2.xpcshell", + strict_max_version: "43.xpcshell" + } + } + }] + }); + + equal(updates.length, 1); + let update = updates[0]; + + equal(update.targetApplications.length, 1); + let targetApp = update.targetApplications[0]; + + equal(targetApp.id, TOOLKIT_ID); + equal(targetApp.minVersion, "42.0a2.xpcshell"); + equal(targetApp.maxVersion, "43.xpcshell"); + + equal(update.version, "0.2"); + equal(update.multiprocessCompatible, false, "multiprocess_compatible flag"); + equal(update.strictCompatibility, true, "inferred strictCompatibility flag"); + equal(update.updateURL, "https://example.com/foo.xpi", "updateURL"); + equal(update.updateHash, "sha256:0", "updateHash"); + equal(update.updateInfoURL, "https://example.com/update_info.html", "updateInfoURL"); +}); + + +add_task(function* test_secure_hashes() { + // Checks that only secure hash functions are accepted for + // non-secure update URLs. + + let hashFunctions = ["sha512", + "sha256", + "sha1", + "md5", + "md4", + "xxx"]; + + let updateItems = hashFunctions.map((hash, idx) => ({ + version: `0.${idx}`, + update_link: `http://localhost:${gPort}/updates/${idx}-${hash}.xpi`, + update_hash: `${hash}:08ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a`, + })); + + let { messages, result: updates } = yield promiseConsoleOutput(() => { + return checkUpdates({ + id: "updatecheck-hashes@tests.mozilla.org", + version: "0.1", + updates: updateItems + }); + }); + + equal(updates.length, hashFunctions.length); + + updates = updates.filter(update => update.updateHash || update.updateURL); + equal(updates.length, 2, "expected number of update hashes were accepted"); + + ok(updates[0].updateHash.startsWith("sha512:"), "sha512 hash is present"); + ok(updates[0].updateURL); + + ok(updates[1].updateHash.startsWith("sha256:"), "sha256 hash is present"); + ok(updates[1].updateURL); + + messages = messages.filter(msg => /Update link.*not secure.*strong enough hash \(needs to be sha256 or sha512\)/.test(msg.message)); + equal(messages.length, hashFunctions.length - 2, "insecure hashes generated the expected warning"); +}); + + +add_task(function* test_strict_compat() { + // Checks that strict compatibility is enabled for strict max + // versions other than "*", but not for advisory max versions. + // Also, ensure that strict max versions take precedence over + // advisory versions. + + let { messages, result: updates } = yield promiseConsoleOutput(() => { + return checkUpdates({ + id: "updatecheck-strict@tests.mozilla.org", + version: "0.1", + updates: [ + { version: "0.2", + applications: { gecko: { strict_max_version: "*" } } }, + { version: "0.3", + applications: { gecko: { strict_max_version: "43" } } }, + { version: "0.4", + applications: { gecko: { advisory_max_version: "43" } } }, + { version: "0.5", + applications: { gecko: { advisory_max_version: "43", + strict_max_version: "44" } } }, + ] + }); + }); + + equal(updates.length, 4, "all update items accepted"); + + equal(updates[0].targetApplications[0].maxVersion, "*"); + equal(updates[0].strictCompatibility, false); + + equal(updates[1].targetApplications[0].maxVersion, "43"); + equal(updates[1].strictCompatibility, true); + + equal(updates[2].targetApplications[0].maxVersion, "43"); + equal(updates[2].strictCompatibility, false); + + equal(updates[3].targetApplications[0].maxVersion, "44"); + equal(updates[3].strictCompatibility, true); + + messages = messages.filter(msg => /Ignoring 'advisory_max_version'.*'strict_max_version' also present/.test(msg.message)); + equal(messages.length, 1, "mix of advisory_max_version and strict_max_version generated the expected warning"); +}); + + +add_task(function* test_update_url_security() { + // Checks that update links to privileged URLs are not accepted. + + let { messages, result: updates } = yield promiseConsoleOutput(() => { + return checkUpdates({ + id: "updatecheck-security@tests.mozilla.org", + version: "0.1", + updates: [ + { version: "0.2", + update_link: "chrome://browser/content/browser.xul", + update_hash: "sha256:08ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a" }, + { version: "0.3", + update_link: "http://example.com/update.xpi", + update_hash: "sha256:18ac852190ecd81f40a514ea9299fe9143d9ab5e296b97e73fb2a314de49648a" }, + ] + }); + }); + + equal(updates.length, 2, "both updates were processed"); + equal(updates[0].updateURL, null, "privileged update URL was removed"); + equal(updates[1].updateURL, "http://example.com/update.xpi", "safe update URL was accepted"); + + messages = messages.filter(msg => /http:\/\/localhost.*\/updates\/.*may not load or link to chrome:/.test(msg.message)); + equal(messages.length, 1, "privileged upate URL generated the expected console message"); +}); + + +add_task(function* test_no_update_key() { + // Checks that updates fail when an update key has been specified. + + let { messages } = yield promiseConsoleOutput(function* () { + yield Assert.rejects( + checkUpdates({ + id: "updatecheck-updatekey@tests.mozilla.org", + version: "0.1", + updateKey: "ayzzx=", + updates: [ + { version: "0.2" }, + { version: "0.3" }, + ] + }), + null, "updated expected to fail"); + }); + + messages = messages.filter(msg => /Update keys are not supported for JSON update manifests/.test(msg.message)); + equal(messages.length, 1, "got expected update-key-unsupported error"); +}); + + +add_task(function* test_type_detection() { + // Checks that JSON update manifests are detected correctly + // regardless of extension or MIME type. + + let tests = [ + { contentType: "application/json", + extension: "json", + valid: true }, + { contentType: "application/json", + extension: "php", + valid: true }, + { contentType: "text/plain", + extension: "json", + valid: true }, + { contentType: "application/octet-stream", + extension: "json", + valid: true }, + { contentType: "text/plain", + extension: "json?foo=bar", + valid: true }, + { contentType: "text/plain", + extension: "php", + valid: true }, + { contentType: "text/plain", + extension: "rdf", + valid: true }, + { contentType: "application/json", + extension: "rdf", + valid: true }, + { contentType: "text/xml", + extension: "json", + valid: true }, + { contentType: "application/rdf+xml", + extension: "json", + valid: true }, + ]; + + for (let [i, test] of tests.entries()) { + let { messages } = yield promiseConsoleOutput(function *() { + let id = `updatecheck-typedetection-${i}@tests.mozilla.org`; + let updates; + try { + updates = yield checkUpdates({ + id: id, + version: "0.1", + contentType: test.contentType, + manifestExtension: test.extension, + updates: [{ version: "0.2" }] + }); + } catch (e) { + ok(!test.valid, "update manifest correctly detected as RDF"); + return; + } + + ok(test.valid, "update manifest correctly detected as JSON"); + equal(updates.length, 1, "correct number of updates"); + equal(updates[0].id, id, "update is for correct extension"); + }); + + if (test.valid) { + // Make sure we don't get any XML parsing errors from the + // XMLHttpRequest machinery. + ok(!messages.some(msg => /not well-formed/.test(msg.message)), + "expect XMLHttpRequest not to attempt XML parsing"); + } + + messages = messages.filter(msg => /Update manifest was not valid XML/.test(msg.message)); + equal(messages.length, !test.valid, "expected number of XML parsing errors"); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js index a87b3f45b1..641ad0d3dd 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_metadata_update.js @@ -80,10 +80,9 @@ add_task(function* checkFirstMetadata() { Services.prefs.setBoolPref(PREF_EM_SHOW_MISMATCH_UI, true); // Create and configure the HTTP server. - testserver = new HttpServer(); + testserver = createHttpServer(); testserver.registerDirectory("/data/", do_get_file("data")); testserver.registerDirectory("/addons/", do_get_file("addons")); - testserver.start(-1); gPort = testserver.identity.primaryPort; const BASE_URL = "http://localhost:" + gPort; const GETADDONS_RESULTS = BASE_URL + "/data/test_AddonRepository_cache.xml"; @@ -170,15 +169,3 @@ add_task(function* upgrade_young_pref_lastupdate() { yield promiseRestartManager("2"); do_check_false(WindowWatcher.expected); }); - - - -add_task(function* cleanup() { - return new Promise((resolve, reject) => { - testserver.stop(resolve); - }); -}); - -function run_test() { - run_next_test(); -} diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_update.js index 819840f397..64cdd14b11 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js @@ -24,10 +24,10 @@ const PARAMS = "?%REQ_VERSION%/%ITEM_ID%/%ITEM_VERSION%/%ITEM_MAXAPPVERSION%/" + var gInstallDate; Components.utils.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); +mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -37,386 +37,1160 @@ profileDir.append("extensions"); let originalSyncGUID; function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false); Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "fr-FR"); - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "5", - maxVersion: "5" - }], - name: "Test Addon 3", - }, profileDir); - - startupManager(); - - do_test_pending(); - run_test_1(); + run_next_test(); } -function end_test() { - testserver.stop(do_test_finished); -} +let testParams = [ + { updateFile: "test_update.rdf", + appId: "xpcshell@tests.mozilla.org" }, + { updateFile: "test_update.json", + appId: "toolkit@mozilla.org" }, +]; -// Verify that an update is available and can be installed. -function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "1.0"); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); - do_check_eq(a1.releaseNotesURI, null); - do_check_true(a1.foreignInstall); - do_check_neq(a1.syncGUID, null); +for (let test of testParams) { + let { updateFile, appId } = test; - originalSyncGUID = a1.syncGUID; - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + add_test(function run_test() { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); - prepare_test({ - "addon1@tests.mozilla.org": [ - ["onPropertyChanged", ["applyBackgroundUpdates"]] - ] + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 2", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "5", + maxVersion: "5" + }], + name: "Test Addon 3", + }, profileDir); + + startupManager(); + + run_next_test(); + }); + + // Verify that an update is available and can be installed. + let check_test_1; + add_test(function run_test_1() { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "1.0"); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_eq(a1.releaseNotesURI, null); + do_check_true(a1.foreignInstall); + do_check_neq(a1.syncGUID, null); + + originalSyncGUID = a1.syncGUID; + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + + prepare_test({ + "addon1@tests.mozilla.org": [ + ["onPropertyChanged", ["applyBackgroundUpdates"]] + ] + }); + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + check_test_completed(); + + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + prepare_test({}, [ + "onNewInstall", + ]); + + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(addon, install) { + ensure_test_completed(); + + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + + do_check_eq(addon, a1); + do_check_eq(install.name, addon.name); + do_check_eq(install.version, "2.0"); + do_check_eq(install.state, AddonManager.STATE_AVAILABLE); + do_check_eq(install.existingAddon, addon); + do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + // Verify that another update check returns the same AddonInstall + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(newAddon, newInstall) { + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + do_check_eq(newAddon, addon); + do_check_eq(newInstall, install); + + prepare_test({}, [ + "onDownloadStarted", + "onDownloadEnded", + ], check_test_1); + install.install(); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - check_test_completed(); + }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + let run_test_2; + check_test_1 = (install) => { + ensure_test_completed(); + do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); + run_test_2(install); + return false; + }; - prepare_test({}, [ - "onNewInstall", - ]); - - a1.findUpdates({ + // Continue installing the update. + let check_test_2; + run_test_2 = (install) => { + // Verify that another update check returns no new update + install.existingAddon.findUpdates({ onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); }, onUpdateAvailable: function(addon, install) { - ensure_test_completed(); + ok(false, "Should find no available update when one is already downloading"); + }, + onNoUpdateAvailable: function(addon) { AddonManager.getAllInstalls(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0], install); - do_check_eq(addon, a1); - do_check_eq(install.name, addon.name); - do_check_eq(install.version, "2.0"); - do_check_eq(install.state, AddonManager.STATE_AVAILABLE); - do_check_eq(install.existingAddon, addon); - do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - - // Verify that another update check returns the same AddonInstall - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, - - onUpdateAvailable: function(newAddon, newInstall) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); - do_check_eq(newAddon, addon); - do_check_eq(newInstall, install); - - prepare_test({}, [ - "onDownloadStarted", - "onDownloadEnded", - ], check_test_1); - install.install(); - }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], check_test_2); + install.install(); }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} + }; -function check_test_1(install) { - ensure_test_completed(); - do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); - run_test_2(install); - return false; -} + check_test_2 = () => { + ensure_test_completed(); -// Continue installing the update. -function run_test_2(install) { - // Verify that another update check returns no new update - install.existingAddon.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { + do_check_neq(olda1, null); + do_check_eq(olda1.version, "1.0"); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); - onUpdateAvailable: function(addon, install) { - do_throw("Should find no available update when one is already downloading"); - }, + shutdownManager(); - onNoUpdateAvailable: function(addon) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); + startupManager(); - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], check_test_2); - install.install(); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); + do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + do_check_true(a1.foreignInstall); + do_check_neq(a1.syncGUID, null); + do_check_eq(originalSyncGUID, a1.syncGUID); + + a1.uninstall(); + run_next_test(); }); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); -} + })); + }; -function check_test_2() { - ensure_test_completed(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { - do_check_neq(olda1, null); - do_check_eq(olda1.version, "1.0"); - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + // Check that an update check finds compatibility updates and applies them + let check_test_3; + add_test(function run_test_3() { + restartManager(); - shutdownManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_true(a2.isCompatibleWith("0", "0")); - startupManager(); + a2.findUpdates({ + onCompatibilityUpdateAvailable: function(addon) { + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_true(a2.isActive); + }, - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + onNoUpdateAvailable: function(addon) { + do_check_eq(addon, a2); + do_execute_soon(check_test_3); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + check_test_3 = () => { + restartManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + a2.uninstall(); + + run_next_test(); + }); + } + + // Checks that we see no compatibility information when there is none. + add_test(function run_test_4() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen compatibility information"); + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + this.sawUpdate = true; + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + // Checks that compatibility info for future apps are detected but don't make + // the item compatibile. + let check_test_5; + add_test(function run_test_5() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_false(a3.isActive); + this.sawUpdate = true; + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should have seen some compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + do_execute_soon(check_test_5); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0", "3.0"); + }); + }); + + check_test_5 = () => { + restartManager(); + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + + a3.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks work + let continue_test_6; + add_test(function run_test_6() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + restartManager(); + + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_6); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + + let check_test_6; + continue_test_6 = (install) => { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_6)); + } + + check_test_6 = (install) => { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); + + restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "2.0"); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - do_check_true(a1.foreignInstall); - do_check_neq(a1.syncGUID, null); - do_check_eq(originalSyncGUID, a1.syncGUID); - a1.uninstall(); - do_execute_soon(run_test_3); + run_next_test(); }); - })); -} + } + // Verify the parameter escaping in update urls. + add_test(function run_test_8() { + restartManager(); -// Check that an update check finds compatibility updates and applies them -function run_test_3() { - restartManager(); + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "2" + }], + name: "Test Addon 1", + }, profileDir); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_true(a2.isCompatibleWith("0")); + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "67.0.5b1", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 2", + }, profileDir); - a2.findUpdates({ - onCompatibilityUpdateAvailable: function(addon) { - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_true(a2.isActive); + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.3+", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }, { + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 3", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "0.5ab6", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "5" + }], + name: "Test Addon 4", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon5@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 5", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon6@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 6", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { + a2.userDisabled = true; + restartManager(); + + testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { + do_check_neq(request.queryString, ""); + let [req_version, item_id, item_version, + item_maxappversion, item_status, + app_id, app_version, current_app_version, + app_os, app_abi, app_locale, update_type] = + request.queryString.split("/").map(a => decodeURIComponent(a)); + + do_check_eq(req_version, "2"); + + switch(item_id) { + case "addon1@tests.mozilla.org": + do_check_eq(item_version, "5.0"); + do_check_eq(item_maxappversion, "2"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "97"); + break; + case "addon2@tests.mozilla.org": + do_check_eq(item_version, "67.0.5b1"); + do_check_eq(item_maxappversion, "3"); + do_check_eq(item_status, "userDisabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "49"); + break; + case "addon3@tests.mozilla.org": + do_check_eq(item_version, "1.3+"); + do_check_eq(item_maxappversion, "0"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "112"); + break; + case "addon4@tests.mozilla.org": + do_check_eq(item_version, "0.5ab6"); + do_check_eq(item_maxappversion, "5"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "2"); + do_check_eq(update_type, "98"); + break; + case "addon5@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "35"); + break; + case "addon6@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "99"); + break; + default: + ok(false, "Update request for unexpected add-on " + item_id); + } + + do_check_eq(app_id, "xpcshell@tests.mozilla.org"); + do_check_eq(current_app_version, "1"); + do_check_eq(app_os, "XPCShell"); + do_check_eq(app_abi, "noarch-spidermonkey"); + do_check_eq(app_locale, "fr-FR"); + + request.setStatusLine(null, 500, "Server Error"); + }); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6]) { + let count = 6; + + function next_test() { + a1.uninstall(); + a2.uninstall(); + a3.uninstall(); + a4.uninstall(); + a5.uninstall(); + a6.uninstall(); + + restartManager(); + run_next_test(); + } + + let compatListener = { + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + let updateListener = { + onUpdateAvailable: function(addon, update) { + // Dummy so the update checker knows we care about new versions + }, + + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); + a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); + a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); + a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + })); + }); + + // Tests that if an install.rdf claims compatibility then the add-on will be + // seen as compatible regardless of what the update.rdf says. + add_test(function run_test_9() { + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive, "addon4 is active"); + do_check_true(a4.isCompatible, "addon4 is compatible"); + + run_next_test(); + }); + }); + + // Tests that a normal update check won't decrease a targetApplication's + // maxVersion. + add_test(function run_test_10() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible, "addon4 is compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + }); + }); + + // Tests that an update check for a new application will decrease a + // targetApplication's maxVersion. + add_test(function run_test_11() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible, "addon4 is not compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + }); + + // Check that the decreased maxVersion applied and disables the add-on + add_test(function run_test_12() { + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive); + do_check_true(a4.isCompatible); + + a4.uninstall(); + run_next_test(); + }); + }); + + // Tests that a compatibility update is passed to the listener when there is + // compatibility info for the current version of the app but not for the + // version of the app that the caller requested an update check for, when + // strict compatibility checking is disabled. + let check_test_13; + add_test(function run_test_13() { + restartManager(); + + // Not initially compatible but the update check will make it compatible + writeInstallRDFForExtension({ + id: "addon7@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 7", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + do_check_true(a7.isCompatibleWith("0", "0")); + + a7.findUpdates({ + sawUpdate: false, + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should have seen compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible); + do_execute_soon(check_test_13); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0", "3.0"); + }); + }); + + check_test_13 = () => { + restartManager(); + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + + a7.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that isn't + // allowed to update automatically. + let check_test_14; + add_test(function run_test_14() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); + + do_execute_soon(check_test_14); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_14 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_neq(a8, null); + do_check_eq(a8.version, "1.0"); + a8.uninstall(); + + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that is + // pending uninstall + let check_test_15; + add_test(function run_test_15() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.uninstall(); + do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_execute_soon(check_test_15); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_15 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_eq(a8, null); + + run_next_test(); + }); + } + + add_test(function run_test_16() { + restartManager(); + + restartManager(); + + let url = "http://localhost:" + gPort + "/addons/test_install2_1.xpi"; + AddonManager.getInstallForURL(url, function(aInstall) { + aInstall.addListener({ + onInstallEnded: function() { + do_execute_soon(function install_2_1_ended() { + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a1) { + do_check_neq(a1.syncGUID, null); + let oldGUID = a1.syncGUID; + + let url = "http://localhost:" + gPort + "/addons/test_install2_2.xpi"; + AddonManager.getInstallForURL(url, function(aInstall) { + aInstall.addListener({ + onInstallEnded: function() { + do_execute_soon(function install_2_2_ended() { + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2.syncGUID, null); + do_check_eq(oldGUID, a2.syncGUID); + + a2.uninstall(); + run_next_test(); + }); + }); + } + }); + aInstall.install(); + }, "application/x-xpinstall"); + }); + }); + } + }); + aInstall.install(); + }, "application/x-xpinstall"); + }); + + // Test that the update check correctly observes the + // extensions.strictCompatibility pref and compatibility overrides. + add_test(function run_test_17() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); + restartManager(); + + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + equal(aInstall.existingAddon.id, "addon9@tests.mozilla.org", + "Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "3.0"); }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_eq(addon, a2); - do_execute_soon(check_test_3); + onDownloadFailed: function(aInstall) { + AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { + a9.uninstall(); + run_next_test(); + }); } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + AddonManagerInternal.backgroundUpdateCheck(); }); -} -function check_test_3() { - restartManager(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - a2.uninstall(); + // Tests that compatibility updates are applied to addons when the updated + // compatibility data wouldn't match with strict compatibility enabled. + add_test(function run_test_18() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon10@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 10", + }, profileDir); + restartManager(); - run_test_4(); + AddonManager.getAddonByID("addon10@tests.mozilla.org", function(a10) { + do_check_neq(a10, null); + + a10.findUpdates({ + onNoCompatibilityUpdateAvailable: function() { + ok(false, "Should have seen compatibility information"); + }, + + onUpdateAvailable: function() { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function() { + a10.uninstall(); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); -} -// Checks that we see no compatibility information when there is none. -function run_test_4() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); + // Test that the update check correctly observes when an addon opts-in to + // strict compatibility checking. + add_test(function run_test_19() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + restartManager(); - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen compatibility information"); - }, + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); - onNoCompatibilityUpdateAvailable: function(addon) { - this.sawUpdate = true; - }, + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + ok(false, "Should have not have seen compatibility information"); + }, - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, + onUpdateAvailable: function() { + ok(false, "Should not have seen an available update"); + }, - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - run_test_5(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + onUpdateFinished: function() { + a11.uninstall(); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); -} -// Checks that compatibility info for future apps are detected but don't make -// the item compatibile. -function run_test_5() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); + // Test that the update succeeds when the update.rdf URN contains a type prefix + // different from the add-on type + let continue_test_20; + add_test(function run_test_20() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon12@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 12", + }, profileDir); + restartManager(); - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_false(a3.isActive); - this.sawUpdate = true; - }, + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_20); - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have seen some compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - do_execute_soon(check_test_5); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0"); + AddonManagerPrivate.backgroundUpdateCheck(); }); -} -function check_test_5() { - restartManager(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); + let check_test_20; + continue_test_20 = (install) => { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon12@tests.mozilla.org"); - a3.uninstall(); - do_execute_soon(run_test_6); - }); -} + prepare_test({ + "addon12@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_20)); + } -// Test that background update checks work -function run_test_6() { - restartManager(); + check_test_20 = (install) => { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - restartManager(); + restartManager(); + AddonManager.getAddonByID("addon12@tests.mozilla.org", function(a12) { + do_check_neq(a12, null); + do_check_eq(a12.version, "2.0"); + do_check_eq(a12.type, "extension"); + a12.uninstall(); - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_6); + do_execute_soon(() => { + restartManager(); + run_next_test() + }); + }); + } - AddonManagerInternal.backgroundUpdateCheck(); -} + add_task(function cleanup() { + let addons = yield new Promise(resolve => { + AddonManager.getAddonsByTypes(["extension"], resolve); + }); -function continue_test_6(install) { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); + for (let addon of addons) + addon.uninstall(); - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_6)); -} + yield promiseRestartManager(); -function check_test_6(install) { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); + shutdownManager(); - restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - a1.uninstall(); - do_execute_soon(run_test_7); + yield new Promise(do_execute_soon); }); } // Test that background update checks work for lightweight themes -function run_test_7() { - restartManager(); +add_test(function run_test_7() { + startupManager(); LightweightThemeManager.currentTheme = { id: "1", @@ -484,7 +1258,7 @@ function run_test_7() { AddonManagerInternal.backgroundUpdateCheck(); }); -} +}); function check_test_7() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -501,13 +1275,13 @@ function check_test_7() { gInstallDate = p1.installDate.getTime(); - run_test_7_cache(); + run_next_test(); }); } // Test that background update checks for lightweight themes do not use the cache // The update body from test 7 shouldn't be used since the cache should be bypassed. -function run_test_7_cache() { +add_test(function () { // XXX The lightweight theme manager strips non-https updateURLs so hack it // back in. let themes = JSON.parse(Services.prefs.getCharPref("lightweightThemes.usedThemes")); @@ -550,7 +1324,7 @@ function run_test_7_cache() { AddonManagerInternal.backgroundUpdateCheck(); }); -} +}); function check_test_7_cache() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -571,740 +1345,6 @@ function check_test_7_cache() { do_check_eq(p1.installDate.getTime(), gInstallDate); do_check_true(p1.installDate.getTime() < p1.updateDate.getTime()); - do_execute_soon(run_test_8); - }); -} - -// Verify the parameter escaping in update urls. -function run_test_8() { - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "67.0.5b1", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.3+", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }, { - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 3", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "0.5ab6", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "5" - }], - name: "Test Addon 4", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon5@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 5", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon6@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 6", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { - a2.userDisabled = true; - restartManager(); - - testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { - do_check_neq(request.queryString, ""); - let [req_version, item_id, item_version, - item_maxappversion, item_status, - app_id, app_version, current_app_version, - app_os, app_abi, app_locale, update_type] = - request.queryString.split("/").map(a => decodeURIComponent(a)); - - do_check_eq(req_version, "2"); - - switch(item_id) { - case "addon1@tests.mozilla.org": - do_check_eq(item_version, "5.0"); - do_check_eq(item_maxappversion, "2"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "97"); - break; - case "addon2@tests.mozilla.org": - do_check_eq(item_version, "67.0.5b1"); - do_check_eq(item_maxappversion, "3"); - do_check_eq(item_status, "userDisabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "49"); - break; - case "addon3@tests.mozilla.org": - do_check_eq(item_version, "1.3+"); - do_check_eq(item_maxappversion, "0"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "112"); - break; - case "addon4@tests.mozilla.org": - do_check_eq(item_version, "0.5ab6"); - do_check_eq(item_maxappversion, "5"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "2"); - do_check_eq(update_type, "98"); - break; - case "addon5@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "35"); - break; - case "addon6@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "99"); - break; - default: - do_throw("Update request for unexpected add-on " + item_id); - } - - do_check_eq(app_id, "xpcshell@tests.mozilla.org"); - do_check_eq(current_app_version, "1"); - do_check_eq(app_os, "XPCShell"); - do_check_eq(app_abi, "noarch-spidermonkey"); - do_check_eq(app_locale, "fr-FR"); - - request.setStatusLine(null, 500, "Server Error"); - }); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org"], - function([a1, a2, a3, a4, a5, a6]) { - let count = 6; - - function run_next_test() { - a1.uninstall(); - a2.uninstall(); - a3.uninstall(); - a4.uninstall(); - a5.uninstall(); - a6.uninstall(); - - restartManager(); - run_test_9(); - } - - let compatListener = { - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - let updateListener = { - onUpdateAvailable: function(addon, update) { - // Dummy so the update checker knows we care about new versions - }, - - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); - a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); - a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); - a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); - })); -} - -// Tests that if an install.rdf claims compatibility then the add-on will be -// seen as compatible regardless of what the update.rdf says. -function run_test_9() { - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive); - do_check_true(a4.isCompatible); - - run_test_10(); - }); -} - -// Tests that a normal update check won't decrease a targetApplication's -// maxVersion. -function run_test_10() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - - run_test_11(); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - }); -} - -// Tests that an update check for a new application will decrease a -// targetApplication's maxVersion. -function run_test_11() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - - do_execute_soon(run_test_12); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); -} - -// Check that the decreased maxVersion applied and disables the add-on -function run_test_12() { - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive); - do_check_true(a4.isCompatible); - - a4.uninstall(); - do_execute_soon(run_test_13); - }); -} - -// Tests that a compatibility update is passed to the listener when there is -// compatibility info for the current version of the app but not for the -// version of the app that the caller requested an update check for, when -// strict compatibility checking is disabled. -function run_test_13() { - restartManager(); - - // Not initially compatible but the update check will make it compatible - writeInstallRDFForExtension({ - id: "addon7@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 7", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - do_check_true(a7.isCompatibleWith("0")); - - a7.findUpdates({ - sawUpdate: false, - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have seen compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - do_execute_soon(check_test_13); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0"); - }); -} - -function check_test_13() { - restartManager(); - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - - a7.uninstall(); - do_execute_soon(run_test_14); - }); -} - -// Test that background update checks doesn't update an add-on that isn't -// allowed to update automatically. -function run_test_14() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); - - do_execute_soon(check_test_14); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_14() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_neq(a8, null); - do_check_eq(a8.version, "1.0"); - a8.uninstall(); - - do_execute_soon(run_test_15); - }); -} - -// Test that background update checks doesn't update an add-on that is -// pending uninstall -function run_test_15() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.uninstall(); - do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_execute_soon(check_test_15); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_15() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_eq(a8, null); - - do_execute_soon(run_test_16); - }); -} - -function run_test_16() { - restartManager(); - - restartManager(); - - let url = "http://localhost:" + gPort + "/addons/test_install2_1.xpi"; - AddonManager.getInstallForURL(url, function(aInstall) { - aInstall.addListener({ - onInstallEnded: function() { - do_execute_soon(function install_2_1_ended() { - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a1) { - do_check_neq(a1.syncGUID, null); - let oldGUID = a1.syncGUID; - - let url = "http://localhost:" + gPort + "/addons/test_install2_2.xpi"; - AddonManager.getInstallForURL(url, function(aInstall) { - aInstall.addListener({ - onInstallEnded: function() { - do_execute_soon(function install_2_2_ended() { - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2.syncGUID, null); - do_check_eq(oldGUID, a2.syncGUID); - - a2.uninstall(); - do_execute_soon(run_test_17); - }); - }); - } - }); - aInstall.install(); - }, "application/x-xpinstall"); - }); - }); - } - }); - aInstall.install(); - }, "application/x-xpinstall"); -} - -// Test that the update check correctly observes the -// extensions.strictCompatibility pref and compatibility overrides. -function run_test_17() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - restartManager(); - - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "3.0"); - }, - onDownloadFailed: function(aInstall) { - AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { - a9.uninstall(); - do_execute_soon(run_test_18); - }); - } - }); - - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); - - AddonManagerInternal.backgroundUpdateCheck(); -} - -// Tests that compatibility updates are applied to addons when the updated -// compatibility data wouldn't match with strict compatibility enabled. -function run_test_18() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon10@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 10", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon10@tests.mozilla.org", function(a10) { - do_check_neq(a10, null); - - a10.findUpdates({ - onNoCompatibilityUpdateAvailable: function() { - do_throw("Should have seen compatibility information"); - }, - - onUpdateAvailable: function() { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function() { - a10.uninstall(); - do_execute_soon(run_test_19); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} - -// Test that the update check correctly observes when an addon opts-in to -// strict compatibility checking. -function run_test_19() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); - - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - do_throw("Should have not have seen compatibility information"); - }, - - onUpdateAvailable: function() { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function() { - a11.uninstall(); - do_execute_soon(run_test_20); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} - -// Test that the update succeeds when the update.rdf URN contains a type prefix -// different from the add-on type -function run_test_20() { - restartManager(); - writeInstallRDFForExtension({ - id: "addon12@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 12", - }, profileDir); - restartManager(); - - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_20); - - AddonManagerPrivate.backgroundUpdateCheck(); -} - -function continue_test_20(install) { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon12@tests.mozilla.org"); - - prepare_test({ - "addon12@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_20)); -} - -function check_test_20(install) { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); - - restartManager(); - AddonManager.getAddonByID("addon12@tests.mozilla.org", function(a12) { - do_check_neq(a12, null); - do_check_eq(a12.version, "2.0"); - do_check_eq(a12.type, "extension"); - a12.uninstall(); - - do_execute_soon(() => { - restartManager(); - end_test(); - }); + run_next_test(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js index 672594088f..16fa3a4c18 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js @@ -9,12 +9,13 @@ const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); +Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false); Components.utils.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); +mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -22,77 +23,86 @@ const profileDir = gProfD.clone(); profileDir.append("extensions"); -function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); - run_test_1(); -} +let testParams = [ + { updateFile: "test_update.rdf", + appId: "xpcshell@tests.mozilla.org" }, + { updateFile: "test_update.json", + appId: "toolkit@mozilla.org" }, +]; -// Test that the update check correctly observes the -// extensions.strictCompatibility pref and compatibility overrides. -function run_test_1() { - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - restartManager(); +for (let test of testParams) { + let { updateFile, appId } = test; - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "4.0"); - }, - onDownloadFailed: function(aInstall) { - do_execute_soon(run_test_2); - } - }); + // Test that the update check correctly observes the + // extensions.strictCompatibility pref and compatibility overrides. + add_test(function () { + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + restartManager(); - AddonManagerInternal.backgroundUpdateCheck(); -} - -// Test that the update check correctly observes when an addon opts-in to -// strict compatibility checking. -function run_test_2() { - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); - - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - do_throw("Should have not have seen compatibility information"); + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") + do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "4.0"); }, - - onNoUpdateAvailable: function() { - do_throw("Should have seen an available update"); - }, - - onUpdateFinished: function() { - end_test(); + onDownloadFailed: function(aInstall) { + run_next_test(); } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/" + updateFile); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + + // Test that the update check correctly observes when an addon opts-in to + // strict compatibility checking. + add_test(function () { + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); + + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + do_throw("Should not have seen compatibility information"); + }, + + onUpdateAvailable: function() { + do_throw("Should not have seen an available update"); + }, + + onUpdateFinished: function() { + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js index 3c56f9adb8..3fc16a0f7e 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_strictcompat.js @@ -9,7 +9,8 @@ const PREF_SELECTED_LOCALE = "general.useragent.locale"; const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; // The test extension uses an insecure update url. -Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); +Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true); // This test requires lightweight themes update to be enabled even if the app // doesn't support lightweight themes. Services.prefs.setBoolPref("lightweightThemes.update.enabled", true); @@ -23,10 +24,10 @@ const PARAMS = "?%REQ_VERSION%/%ITEM_ID%/%ITEM_VERSION%/%ITEM_MAXAPPVERSION%/" + var gInstallDate; Components.utils.import("resource://testing-common/httpd.js"); -var testserver = new HttpServer(); -testserver.start(-1); +var testserver = createHttpServer(); gPort = testserver.identity.primaryPort; mapFile("/data/test_update.rdf", testserver); +mapFile("/data/test_update.json", testserver); mapFile("/data/test_update.xml", testserver); testserver.registerDirectory("/addons/", do_get_file("addons")); @@ -34,383 +35,1013 @@ const profileDir = gProfD.clone(); profileDir.append("extensions"); function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false); Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "fr-FR"); - Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true); - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "5", - maxVersion: "5" - }], - name: "Test Addon 3", - }, profileDir); - - startupManager(); - - do_test_pending(); - run_test_1(); + run_next_test(); } -function end_test() { - Services.prefs.clearUserPref(PREF_EM_STRICT_COMPATIBILITY); +let testParams = [ + { updateFile: "test_update.rdf", + appId: "xpcshell@tests.mozilla.org" }, + { updateFile: "test_update.json", + appId: "toolkit@mozilla.org" }, +]; - testserver.stop(do_test_finished); -} +for (let test of testParams) { + let { updateFile, appId } = test; -// Verify that an update is available and can be installed. -function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "1.0"); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); - do_check_eq(a1.releaseNotesURI, null); + add_test(function run_test() { + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 2", + }, profileDir); - prepare_test({ - "addon1@tests.mozilla.org": [ - ["onPropertyChanged", ["applyBackgroundUpdates"]] - ] + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "5", + maxVersion: "5" + }], + name: "Test Addon 3", + }, profileDir); + + startupManager(); + + run_next_test(); + }); + + // Verify that an update is available and can be installed. + let check_test_1; + add_test(function run_test_1() { + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "1.0"); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + do_check_eq(a1.releaseNotesURI, null); + + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; + + prepare_test({ + "addon1@tests.mozilla.org": [ + ["onPropertyChanged", ["applyBackgroundUpdates"]] + ] + }); + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + check_test_completed(); + + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + prepare_test({}, [ + "onNewInstall", + ]); + + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(addon, install) { + ensure_test_completed(); + + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + + do_check_eq(addon, a1); + do_check_eq(install.name, addon.name); + do_check_eq(install.version, "2.0"); + do_check_eq(install.state, AddonManager.STATE_AVAILABLE); + do_check_eq(install.existingAddon, addon); + do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + // Verify that another update check returns the same AddonInstall + a1.findUpdates({ + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); + }, + + onUpdateAvailable: function(newAddon, newInstall) { + AddonManager.getAllInstalls(function(aInstalls) { + do_check_eq(aInstalls.length, 1); + do_check_eq(aInstalls[0], install); + do_check_eq(newAddon, addon); + do_check_eq(newInstall, install); + + prepare_test({}, [ + "onDownloadStarted", + "onDownloadEnded", + ], check_test_1); + install.install(); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }, + + onNoUpdateAvailable: function(addon) { + ok(false, "Should not have seen onNoUpdateAvailable notification"); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - check_test_completed(); + }); - a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + let run_test_2; + check_test_1 = (install) => { + ensure_test_completed(); + do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); + run_test_2(install); + return false; + }; - prepare_test({}, [ - "onNewInstall", - ]); - - a1.findUpdates({ + // Continue installing the update. + let check_test_2; + run_test_2 = (install) => { + // Verify that another update check returns no new update + install.existingAddon.findUpdates({ onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); + ok(false, "Should not have seen onNoCompatibilityUpdateAvailable notification"); }, onUpdateAvailable: function(addon, install) { - ensure_test_completed(); + ok(false, "Should find no available update when one is already downloading"); + }, + onNoUpdateAvailable: function(addon) { AddonManager.getAllInstalls(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0], install); - do_check_eq(addon, a1); - do_check_eq(install.name, addon.name); - do_check_eq(install.version, "2.0"); - do_check_eq(install.state, AddonManager.STATE_AVAILABLE); - do_check_eq(install.existingAddon, addon); - do_check_eq(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - - // Verify that another update check returns the same AddonInstall - a1.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, - - onUpdateAvailable: function(newAddon, newInstall) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); - do_check_eq(newAddon, addon); - do_check_eq(newInstall, install); - - prepare_test({}, [ - "onDownloadStarted", - "onDownloadEnded", - ], check_test_1); - install.install(); - }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], check_test_2); + install.install(); }); - }, - - onNoUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoUpdateAvailable notification"); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} + }; -function check_test_1(install) { - ensure_test_completed(); - do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); - run_test_2(install); - return false; -} + check_test_2 = () => { + ensure_test_completed(); -// Continue installing the update. -function run_test_2(install) { - // Verify that another update check returns no new update - install.existingAddon.findUpdates({ - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen onNoCompatibilityUpdateAvailable notification"); - }, + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { + do_check_neq(olda1, null); + do_check_eq(olda1.version, "1.0"); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); - onUpdateAvailable: function(addon, install) { - do_throw("Should find no available update when one is already downloading"); - }, + shutdownManager(); - onNoUpdateAvailable: function(addon) { - AddonManager.getAllInstalls(function(aInstalls) { - do_check_eq(aInstalls.length, 1); - do_check_eq(aInstalls[0], install); + startupManager(); - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], check_test_2); - install.install(); + do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + + AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); + do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + a1.uninstall(); + run_next_test(); }); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); -} + })); + }; -function check_test_2() { - ensure_test_completed(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(olda1) { - do_check_neq(olda1, null); - do_check_eq(olda1.version, "1.0"); - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + // Check that an update check finds compatibility updates and applies them + let check_test_3; + add_test(function run_test_3() { + restartManager(); - shutdownManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_false(a2.isActive); + do_check_false(a2.isCompatible); + do_check_true(a2.appDisabled); + do_check_true(a2.isCompatibleWith("0", "0")); - startupManager(); + a2.findUpdates({ + onCompatibilityUpdateAvailable: function(addon) { + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + do_check_false(a2.isActive); + }, - do_check_true(isExtensionInAddonsList(profileDir, olda1.id)); + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + onNoUpdateAvailable: function(addon) { + do_check_eq(addon, a2); + do_execute_soon(check_test_3); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + check_test_3 = () => { + restartManager(); + AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + do_check_neq(a2, null); + do_check_true(a2.isActive); + do_check_true(a2.isCompatible); + do_check_false(a2.appDisabled); + a2.uninstall(); + + run_next_test(); + }); + } + + // Checks that we see no compatibility information when there is none. + add_test(function run_test_4() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen compatibility information"); + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + this.sawUpdate = true; + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + }); + + // Checks that compatibility info for future apps are detected but don't make + // the item compatibile. + let check_test_5; + add_test(function run_test_5() { + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_true(a3.isCompatibleWith("5", "5")); + do_check_false(a3.isCompatibleWith("2", "2")); + + a3.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + do_check_false(a3.isActive); + this.sawUpdate = true; + }, + + onNoCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should have seen some compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onNoUpdateAvailable: function(addon) { + do_check_true(this.sawUpdate); + do_execute_soon(check_test_5); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0", "3.0"); + }); + }); + + check_test_5 = () => { + restartManager(); + AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { + do_check_neq(a3, null); + do_check_false(a3.isActive); + do_check_false(a3.isCompatible); + do_check_true(a3.appDisabled); + + a3.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks work + let continue_test_6; + add_test(function run_test_6() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + restartManager(); + + prepare_test({}, [ + "onNewInstall", + "onDownloadStarted", + "onDownloadEnded" + ], continue_test_6); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + + let check_test_6; + continue_test_6 = (install) => { + do_check_neq(install.existingAddon, null); + do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onInstalling" + ] + }, [ + "onInstallStarted", + "onInstallEnded", + ], callback_soon(check_test_6)); + } + + check_test_6 = (install) => { + do_check_eq(install.existingAddon.pendingUpgrade.install, install); + + restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "2.0"); - do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_check_eq(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - a1.uninstall(); - do_execute_soon(run_test_3); + run_next_test(); }); - })); -} + } + // Verify the parameter escaping in update urls. + add_test(function run_test_8() { + restartManager(); -// Check that an update check finds compatibility updates and applies them -function run_test_3() { - restartManager(); + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "2" + }], + name: "Test Addon 1", + }, profileDir); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_false(a2.isActive); - do_check_false(a2.isCompatible); - do_check_true(a2.appDisabled); - do_check_true(a2.isCompatibleWith("0")); + writeInstallRDFForExtension({ + id: "addon2@tests.mozilla.org", + version: "67.0.5b1", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 2", + }, profileDir); - a2.findUpdates({ - onCompatibilityUpdateAvailable: function(addon) { - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - do_check_false(a2.isActive); + writeInstallRDFForExtension({ + id: "addon3@tests.mozilla.org", + version: "1.3+", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }, { + id: "toolkit@mozilla.org", + minVersion: "0", + maxVersion: "3" + }], + name: "Test Addon 3", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "0.5ab6", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "5" + }], + name: "Test Addon 4", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon5@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 5", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon6@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 6", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { + a2.userDisabled = true; + restartManager(); + + testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { + do_check_neq(request.queryString, ""); + let [req_version, item_id, item_version, + item_maxappversion, item_status, + app_id, app_version, current_app_version, + app_os, app_abi, app_locale, update_type] = + request.queryString.split("/").map(a => decodeURIComponent(a)); + + do_check_eq(req_version, "2"); + + switch(item_id) { + case "addon1@tests.mozilla.org": + do_check_eq(item_version, "5.0"); + do_check_eq(item_maxappversion, "2"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "97"); + break; + case "addon2@tests.mozilla.org": + do_check_eq(item_version, "67.0.5b1"); + do_check_eq(item_maxappversion, "3"); + do_check_eq(item_status, "userDisabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "49"); + break; + case "addon3@tests.mozilla.org": + do_check_eq(item_version, "1.3+"); + do_check_eq(item_maxappversion, "0"); + do_check_eq(item_status, "userEnabled,incompatible"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "112"); + break; + case "addon4@tests.mozilla.org": + do_check_eq(item_version, "0.5ab6"); + do_check_eq(item_maxappversion, "5"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "2"); + do_check_eq(update_type, "98"); + break; + case "addon5@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "35"); + break; + case "addon6@tests.mozilla.org": + do_check_eq(item_version, "1.0"); + do_check_eq(item_maxappversion, "1"); + do_check_eq(item_status, "userEnabled"); + do_check_eq(app_version, "1"); + do_check_eq(update_type, "99"); + break; + default: + ok(false, "Update request for unexpected add-on " + item_id); + } + + do_check_eq(app_id, "xpcshell@tests.mozilla.org"); + do_check_eq(current_app_version, "1"); + do_check_eq(app_os, "XPCShell"); + do_check_eq(app_abi, "noarch-spidermonkey"); + do_check_eq(app_locale, "fr-FR"); + + request.setStatusLine(null, 500, "Server Error"); + }); + + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon2@tests.mozilla.org", + "addon3@tests.mozilla.org", + "addon4@tests.mozilla.org", + "addon5@tests.mozilla.org", + "addon6@tests.mozilla.org"], + function([a1, a2, a3, a4, a5, a6]) { + let count = 6; + + function next_test() { + a1.uninstall(); + a2.uninstall(); + a3.uninstall(); + a4.uninstall(); + a5.uninstall(); + a6.uninstall(); + + restartManager(); + run_next_test(); + } + + let compatListener = { + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + let updateListener = { + onUpdateAvailable: function(addon, update) { + // Dummy so the update checker knows we care about new versions + }, + + onUpdateFinished: function(addon, error) { + if (--count == 0) + do_execute_soon(next_test); + } + }; + + a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); + a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); + a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); + a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + })); + }); + + // Tests that if an install.rdf claims compatibility then the add-on will be + // seen as compatible regardless of what the update.rdf says. + add_test(function run_test_9() { + writeInstallRDFForExtension({ + id: "addon4@tests.mozilla.org", + version: "5.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_true(a4.isActive, "addon4 is active"); + do_check_true(a4.isCompatible, "addon4 is compatible"); + + run_next_test(); + }); + }); + + // Tests that a normal update check won't decrease a targetApplication's + // maxVersion. + add_test(function run_test_10() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible, "addon4 is compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); + }); + }); + + // Tests that an update check for a new application will decrease a + // targetApplication's maxVersion. + add_test(function run_test_11() { + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + a4.findUpdates({ + onUpdateFinished: function(addon) { + do_check_false(addon.isCompatible, "addon4 is compatible"); + + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + }); + }); + + // Check that the decreased maxVersion applied and disables the add-on + add_test(function run_test_12() { + restartManager(); + + AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { + do_check_false(a4.isActive, "addon4 is active"); + do_check_false(a4.isCompatible, "addon4 is compatible"); + + a4.uninstall(); + run_next_test(); + }); + }); + + // Tests that no compatibility update is passed to the listener when there is + // compatibility info for the current version of the app but not for the + // version of the app that the caller requested an update check for. + let check_test_13; + add_test(function run_test_13() { + restartManager(); + + // Not initially compatible but the update check will make it compatible + writeInstallRDFForExtension({ + id: "addon7@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0", + maxVersion: "0" + }], + name: "Test Addon 7", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_false(a7.isActive); + do_check_false(a7.isCompatible); + do_check_true(a7.appDisabled); + do_check_true(a7.isCompatibleWith("0", "0")); + + a7.findUpdates({ + sawUpdate: false, + onCompatibilityUpdateAvailable: function(addon) { + ok(false, "Should not have seen compatibility information"); + }, + + onUpdateAvailable: function(addon, install) { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function(addon) { + do_check_true(addon.isCompatible); + do_execute_soon(check_test_13); + } + }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0", "3.0"); + }); + }); + + check_test_13 = () => { + restartManager(); + AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { + do_check_neq(a7, null); + do_check_true(a7.isActive); + do_check_true(a7.isCompatible); + do_check_false(a7.appDisabled); + + a7.uninstall(); + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that isn't + // allowed to update automatically. + let check_test_14; + add_test(function run_test_14() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); + + do_execute_soon(check_test_14); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_14 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_neq(a8, null); + do_check_eq(a8.version, "1.0"); + a8.uninstall(); + + run_next_test(); + }); + } + + // Test that background update checks doesn't update an add-on that is + // pending uninstall + let check_test_15; + add_test(function run_test_15() { + restartManager(); + + // Have an add-on there that will be updated so we see some events from it + writeInstallRDFForExtension({ + id: "addon1@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 1", + }, profileDir); + + writeInstallRDFForExtension({ + id: "addon8@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "1", + maxVersion: "1" + }], + name: "Test Addon 8", + }, profileDir); + restartManager(); + + AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { + a8.uninstall(); + do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + let id = aInstall.existingAddon.id; + ok((id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org"), + "Saw unexpected onNewInstall for " + id); + }, + + onDownloadStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed: function(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled: function(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded: function(aInstall) { + do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + do_execute_soon(check_test_15); + }, + + onInstallFailed: function(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled: function(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }); + + AddonManagerInternal.backgroundUpdateCheck(); + }); + }); + + check_test_15 = () => { + restartManager(); + AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", + "addon8@tests.mozilla.org"], function([a1, a8]) { + do_check_neq(a1, null); + do_check_eq(a1.version, "2.0"); + a1.uninstall(); + + do_check_eq(a8, null); + + run_next_test(); + }); + } + + // Test that the update check correctly observes the + // extensions.strictCompatibility pref and compatibility overrides. + add_test(function run_test_17() { + restartManager(); + + writeInstallRDFForExtension({ + id: "addon9@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 9", + }, profileDir); + restartManager(); + + AddonManager.addInstallListener({ + onNewInstall: function(aInstall) { + equal(aInstall.existingAddon.id, "addon9@tests.mozilla.org", + "Saw unexpected onNewInstall for " + aInstall.existingAddon.id); + do_check_eq(aInstall.version, "2.0"); }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_eq(addon, a2); - do_execute_soon(check_test_3); + onDownloadFailed: function(aInstall) { + AddonManager.getAddonByID("addon9@tests.mozilla.org", function(a9) { + a9.uninstall(); + run_next_test(); + }); } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); + + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, + "http://localhost:" + gPort + "/data/test_update.xml"); + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + + AddonManagerInternal.backgroundUpdateCheck(); }); -} -function check_test_3() { - restartManager(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { - do_check_neq(a2, null); - do_check_true(a2.isActive); - do_check_true(a2.isCompatible); - do_check_false(a2.appDisabled); - a2.uninstall(); + // Test that the update check correctly observes when an addon opts-in to + // strict compatibility checking. + add_test(function run_test_19() { + restartManager(); + writeInstallRDFForExtension({ + id: "addon11@tests.mozilla.org", + version: "1.0", + updateURL: "http://localhost:" + gPort + "/data/" + updateFile, + targetApplications: [{ + id: appId, + minVersion: "0.1", + maxVersion: "0.2" + }], + name: "Test Addon 11", + }, profileDir); + restartManager(); - run_test_4(); + AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { + do_check_neq(a11, null); + + a11.findUpdates({ + onCompatibilityUpdateAvailable: function() { + ok(false, "Should have not have seen compatibility information"); + }, + + onUpdateAvailable: function() { + ok(false, "Should not have seen an available update"); + }, + + onUpdateFinished: function() { + run_next_test(); + } + }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + }); }); -} -// Checks that we see no compatibility information when there is none. -function run_test_4() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); + add_task(function cleanup() { + let addons = yield new Promise(resolve => { + AddonManager.getAddonsByTypes(["extension"], resolve); + }); - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should not have seen compatibility information"); - }, + for (let addon of addons) + addon.uninstall(); - onNoCompatibilityUpdateAvailable: function(addon) { - this.sawUpdate = true; - }, + yield promiseRestartManager(); - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, + shutdownManager(); - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - run_test_5(); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); - }); -} - -// Checks that compatibility info for future apps are detected but don't make -// the item compatibile. -function run_test_5() { - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_true(a3.isCompatibleWith("5")); - do_check_false(a3.isCompatibleWith("2")); - - a3.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - do_check_false(a3.isActive); - this.sawUpdate = true; - }, - - onNoCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have seen some compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onNoUpdateAvailable: function(addon) { - do_check_true(this.sawUpdate); - do_execute_soon(check_test_5); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3.0"); - }); -} - -function check_test_5() { - restartManager(); - AddonManager.getAddonByID("addon3@tests.mozilla.org", function(a3) { - do_check_neq(a3, null); - do_check_false(a3.isActive); - do_check_false(a3.isCompatible); - do_check_true(a3.appDisabled); - - a3.uninstall(); - do_execute_soon(run_test_6); - }); -} - -// Test that background update checks work -function run_test_6() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - restartManager(); - - prepare_test({}, [ - "onNewInstall", - "onDownloadStarted", - "onDownloadEnded" - ], continue_test_6); - - AddonManagerInternal.backgroundUpdateCheck(); -} - -function continue_test_6(install) { - do_check_neq(install.existingAddon, null); - do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org"); - - prepare_test({ - "addon1@tests.mozilla.org": [ - "onInstalling" - ] - }, [ - "onInstallStarted", - "onInstallEnded", - ], callback_soon(check_test_6)); -} - -function check_test_6(install) { - do_check_eq(install.existingAddon.pendingUpgrade.install, install); - - restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - do_check_eq(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); - a1.uninstall(); - do_execute_soon(run_test_7); + yield new Promise(do_execute_soon); }); } // Test that background update checks work for lightweight themes -function run_test_7() { - restartManager(); +add_test(function run_test_7() { + startupManager(); LightweightThemeManager.currentTheme = { id: "1", @@ -474,7 +1105,7 @@ function run_test_7() { AddonManagerInternal.backgroundUpdateCheck(); }); -} +}); function check_test_7() { AddonManager.getAddonByID("1@personas.mozilla.org", function(p1) { @@ -491,595 +1122,6 @@ function check_test_7() { gInstallDate = p1.installDate.getTime(); - do_execute_soon(run_test_8); - }); -} - -// Verify the parameter escaping in update urls. -function run_test_8() { - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "2" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon2@tests.mozilla.org", - version: "67.0.5b1", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 2", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon3@tests.mozilla.org", - version: "1.3+", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }, { - id: "toolkit@mozilla.org", - minVersion: "0", - maxVersion: "3" - }], - name: "Test Addon 3", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "0.5ab6", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "5" - }], - name: "Test Addon 4", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon5@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 5", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon6@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/param_test.rdf" + PARAMS, - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 6", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { - a2.userDisabled = true; - restartManager(); - - testserver.registerPathHandler("/data/param_test.rdf", function(request, response) { - do_check_neq(request.queryString, ""); - let [req_version, item_id, item_version, - item_maxappversion, item_status, - app_id, app_version, current_app_version, - app_os, app_abi, app_locale, update_type] = - request.queryString.split("/").map(a => decodeURIComponent(a)); - - do_check_eq(req_version, "2"); - - switch(item_id) { - case "addon1@tests.mozilla.org": - do_check_eq(item_version, "5.0"); - do_check_eq(item_maxappversion, "2"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "97"); - break; - case "addon2@tests.mozilla.org": - do_check_eq(item_version, "67.0.5b1"); - do_check_eq(item_maxappversion, "3"); - do_check_eq(item_status, "userDisabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "49"); - break; - case "addon3@tests.mozilla.org": - do_check_eq(item_version, "1.3+"); - do_check_eq(item_maxappversion, "0"); - do_check_eq(item_status, "userEnabled,incompatible"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "112"); - break; - case "addon4@tests.mozilla.org": - do_check_eq(item_version, "0.5ab6"); - do_check_eq(item_maxappversion, "5"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "2"); - do_check_eq(update_type, "98"); - break; - case "addon5@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "35"); - break; - case "addon6@tests.mozilla.org": - do_check_eq(item_version, "1.0"); - do_check_eq(item_maxappversion, "1"); - do_check_eq(item_status, "userEnabled"); - do_check_eq(app_version, "1"); - do_check_eq(update_type, "99"); - break; - default: - do_throw("Update request for unexpected add-on " + item_id); - } - - do_check_eq(app_id, "xpcshell@tests.mozilla.org"); - do_check_eq(current_app_version, "1"); - do_check_eq(app_os, "XPCShell"); - do_check_eq(app_abi, "noarch-spidermonkey"); - do_check_eq(app_locale, "fr-FR"); - - request.setStatusLine(null, 500, "Server Error"); - }); - - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon2@tests.mozilla.org", - "addon3@tests.mozilla.org", - "addon4@tests.mozilla.org", - "addon5@tests.mozilla.org", - "addon6@tests.mozilla.org"], - function([a1, a2, a3, a4, a5, a6]) { - let count = 6; - - function run_next_test() { - a1.uninstall(); - a2.uninstall(); - a3.uninstall(); - a4.uninstall(); - a5.uninstall(); - a6.uninstall(); - - restartManager(); - run_test_9(); - } - - let compatListener = { - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - let updateListener = { - onUpdateAvailable: function(addon, update) { - // Dummy so the update checker knows we care about new versions - }, - - onUpdateFinished: function(addon, error) { - if (--count == 0) - do_execute_soon(run_next_test); - } - }; - - a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); - a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED); - a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"); - a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - a6.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); - })); -} - -// Tests that if an install.rdf claims compatibility then the add-on will be -// seen as compatible regardless of what the update.rdf says. -function run_test_9() { - writeInstallRDFForExtension({ - id: "addon4@tests.mozilla.org", - version: "5.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_true(a4.isActive); - do_check_true(a4.isCompatible); - - run_test_10(); - }); -} - -// Tests that a normal update check won't decrease a targetApplication's -// maxVersion. -function run_test_10() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - - run_test_11(); - } - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - }); -} - -// Tests that an update check for a new application will decrease a -// targetApplication's maxVersion. -function run_test_11() { - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - a4.findUpdates({ - onUpdateFinished: function(addon) { - do_check_false(addon.isCompatible); - - do_execute_soon(run_test_12); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); -} - -// Check that the decreased maxVersion applied and disables the add-on -function run_test_12() { - restartManager(); - - AddonManager.getAddonByID("addon4@tests.mozilla.org", function(a4) { - do_check_false(a4.isActive); - do_check_false(a4.isCompatible); - - a4.uninstall(); - do_execute_soon(run_test_13); - }); -} - -// Tests that no compatibility update is passed to the listener when there is -// compatibility info for the current version of the app but not for the -// version of the app that the caller requested an update check for. -function run_test_13() { - restartManager(); - - // Not initially compatible but the update check will make it compatible - writeInstallRDFForExtension({ - id: "addon7@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0", - maxVersion: "0" - }], - name: "Test Addon 7", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_false(a7.isActive); - do_check_false(a7.isCompatible); - do_check_true(a7.appDisabled); - do_check_true(a7.isCompatibleWith("0")); - - a7.findUpdates({ - sawUpdate: false, - onCompatibilityUpdateAvailable: function(addon) { - do_throw("Should have not have seen compatibility information"); - }, - - onUpdateAvailable: function(addon, install) { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function(addon) { - do_check_true(addon.isCompatible); - do_execute_soon(check_test_13); - } - }, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "3.0"); - }); -} - -function check_test_13() { - restartManager(); - AddonManager.getAddonByID("addon7@tests.mozilla.org", function(a7) { - do_check_neq(a7, null); - do_check_true(a7.isActive); - do_check_true(a7.isCompatible); - do_check_false(a7.appDisabled); - - a7.uninstall(); - do_execute_soon(run_test_14); - }); -} - -// Test that background update checks doesn't update an add-on that isn't -// allowed to update automatically. -function run_test_14() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_check_eq(aInstall.existingAddon.pendingUpgrade.install, aInstall); - do_execute_soon(check_test_14); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_14() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_neq(a8, null); - do_check_eq(a8.version, "1.0"); - a8.uninstall(); - - do_execute_soon(run_test_15); - }); -} - -// Test that background update checks doesn't update an add-on that is -// pending uninstall -function run_test_15() { - restartManager(); - - // Have an add-on there that will be updated so we see some events from it - writeInstallRDFForExtension({ - id: "addon1@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 1", - }, profileDir); - - writeInstallRDFForExtension({ - id: "addon8@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "1", - maxVersion: "1" - }], - name: "Test Addon 8", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) { - a8.uninstall(); - do_check_false(hasFlag(a8.permissions, AddonManager.PERM_CAN_UPGRADE)); - - // The background update check will find updates for both add-ons but only - // proceed to install one of them. - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon1@tests.mozilla.org" && - aInstall.existingAddon.id != "addon8@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - }, - - onDownloadStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onDownloadFailed: function(aInstall) { - do_throw("Should not have seen onDownloadFailed event"); - }, - - onDownloadCancelled: function(aInstall) { - do_throw("Should not have seen onDownloadCancelled event"); - }, - - onInstallStarted: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - }, - - onInstallEnded: function(aInstall) { - do_check_eq(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); - do_execute_soon(check_test_15); - }, - - onInstallFailed: function(aInstall) { - do_throw("Should not have seen onInstallFailed event"); - }, - - onInstallCancelled: function(aInstall) { - do_throw("Should not have seen onInstallCancelled event"); - }, - }); - - AddonManagerInternal.backgroundUpdateCheck(); - }); -} - -function check_test_15() { - restartManager(); - AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", - "addon8@tests.mozilla.org"], function([a1, a8]) { - do_check_neq(a1, null); - do_check_eq(a1.version, "2.0"); - a1.uninstall(); - - do_check_eq(a8, null); - - do_execute_soon(run_test_16); - }); -} - -// Test that the update check correctly observes the -// extensions.strictCompatibility pref and compatibility overrides. -function run_test_16() { - restartManager(); - - writeInstallRDFForExtension({ - id: "addon9@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 9", - }, profileDir); - restartManager(); - - AddonManager.addInstallListener({ - onNewInstall: function(aInstall) { - if (aInstall.existingAddon.id != "addon9@tests.mozilla.org") - do_throw("Saw unexpected onNewInstall for " + aInstall.existingAddon.id); - do_check_eq(aInstall.version, "2.0"); - }, - onDownloadFailed: function(aInstall) { - do_execute_soon(run_test_17); - } - }); - - Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, - "http://localhost:" + gPort + "/data/test_update.xml"); - Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); - - AddonManagerInternal.backgroundUpdateCheck(); -} - -// Test that the update check correctly observes when an addon opts-in to -// strict compatibility checking. -function run_test_17() { - - writeInstallRDFForExtension({ - id: "addon11@tests.mozilla.org", - version: "1.0", - updateURL: "http://localhost:" + gPort + "/data/test_update.rdf", - targetApplications: [{ - id: "xpcshell@tests.mozilla.org", - minVersion: "0.1", - maxVersion: "0.2" - }], - name: "Test Addon 11", - }, profileDir); - restartManager(); - - AddonManager.getAddonByID("addon11@tests.mozilla.org", function(a11) { - do_check_neq(a11, null); - - a11.findUpdates({ - onCompatibilityUpdateAvailable: function() { - do_throw("Should have not have seen compatibility information"); - }, - - onUpdateAvailable: function() { - do_throw("Should not have seen an available update"); - }, - - onUpdateFinished: function() { - do_execute_soon(end_test); - } - }, AddonManager.UPDATE_WHEN_USER_REQUESTED); + run_next_test(); }); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js b/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js index d2e15103ba..bdb7a20748 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_updatecheck.js @@ -7,52 +7,47 @@ Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); Components.utils.import("resource://testing-common/httpd.js"); -var testserver; -function run_test() { - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); +var testserver = createHttpServer(4444); +testserver.registerDirectory("/data/", do_get_file("data")); - // Create and configure the HTTP server. - testserver = new HttpServer(); - testserver.registerDirectory("/data/", do_get_file("data")); - testserver.start(4444); +function checkUpdates(aId, aUpdateKey, aUpdateFile) { + return new Promise((resolve, reject) => { + AddonUpdateChecker.checkForUpdates(aId, aUpdateKey, `http://localhost:4444/data/${aUpdateFile}`, { + onUpdateCheckComplete: resolve, - do_test_pending(); - run_test_1(); -} - -function end_test() { - testserver.stop(do_test_finished); -} - -// Test that a basic update check returns the expected available updates -function run_test_1() { - AddonUpdateChecker.checkForUpdates("updatecheck1@tests.mozilla.org", null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - check_test_1(updates); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } + onUpdateCheckError: function(status) { + let error = new Error("Update check failed with status " + status); + error.status = status; + reject(error); + } + }); }); } -function check_test_1(updates) { - do_check_eq(updates.length, 5); - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates); - do_check_neq(update, null); - do_check_eq(update.version, 3); - update = AddonUpdateChecker.getCompatibilityUpdate(updates, "2"); - do_check_neq(update, null); - do_check_eq(update.version, 2); - do_check_eq(update.targetApplications[0].minVersion, 1); - do_check_eq(update.targetApplications[0].maxVersion, 2); +function run_test() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); - run_test_2(); + run_next_test(); } +// Test that a basic update check returns the expected available updates +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("updatecheck1@tests.mozilla.org", null, file); + + equal(updates.length, 5); + let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates); + notEqual(update, null); + equal(update.version, "3.0"); + update = AddonUpdateChecker.getCompatibilityUpdate(updates, "2"); + notEqual(update, null); + equal(update.version, "2.0"); + equal(update.targetApplications[0].minVersion, "1"); + equal(update.targetApplications[0].maxVersion, "2"); + } +}); + /* * Tests that the security checks are applied correctly * @@ -73,240 +68,169 @@ let updateKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK426erD/H3XtsjvaB5+PJqbh "NyeP6i4LuUYjTURnn7Yw/IgzyIJ2oKsYa32RuxAyteqAWqPT/J63wBixIeCxmysf" + "awB/zH4KaPiY3vnrzQIDAQAB"; -function run_test_2() { - AddonUpdateChecker.checkForUpdates("test_bug378216_5@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_throw("Expected the update check to fail"); - }, +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + try { + yield checkUpdates("test_bug378216_5@tests.mozilla.org", + updateKey, file); + throw "Expected the update check to fail"; + } catch (e) {} + } +}); - onUpdateCheckError: function(status) { - run_test_3(); +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + try { + yield checkUpdates("test_bug378216_7@tests.mozilla.org", + updateKey, file); + + throw "Expected the update check to fail"; + } catch (e) {} + } +}); + +add_task(function* () { + // Make sure that the JSON manifest is rejected when an update key is + // required, but perform the remaining tests which aren't expected to fail + // because of the update key, without requiring one for the JSON variant. + + try { + let updates = yield checkUpdates("test_bug378216_8@tests.mozilla.org", + updateKey, "test_updatecheck.json"); + + throw "Expected the update check to fail"; + } catch(e) {} + + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_8@tests.mozilla.org", + key, file); + equal(updates.length, 1); + ok(!("updateURL" in updates[0])); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_9@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_10@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_11@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_12@tests.mozilla.org", + key, file); + equal(updates.length, 1); + do_check_false("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let [file, key] of [["test_updatecheck.rdf", updateKey], + ["test_updatecheck.json", null]]) { + let updates = yield checkUpdates("test_bug378216_13@tests.mozilla.org", + key, file); + equal(updates.length, 1); + equal(updates[0].version, "2.0"); + ok("updateURL" in updates[0]); + } +}); + +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("test_bug378216_14@tests.mozilla.org", + null, file); + equal(updates.length, 0); + } +}); + +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + try { + yield checkUpdates("test_bug378216_15@tests.mozilla.org", + null, file); + + throw "Update check should have failed"; + } catch (e) { + equal(e.status, AddonUpdateChecker.ERROR_PARSE_ERROR); } - }); -} + } +}); -function run_test_3() { - AddonUpdateChecker.checkForUpdates("test_bug378216_7@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_throw("Expected the update check to fail"); - }, +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("ignore-compat@tests.mozilla.org", + null, file); + equal(updates.length, 3); + let update = AddonUpdateChecker.getNewestCompatibleUpdate( + updates, null, null, true); + notEqual(update, null); + equal(update.version, 2); + } +}); - onUpdateCheckError: function(status) { - run_test_4(); - } - }); -} +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("compat-override@tests.mozilla.org", + null, file); + equal(updates.length, 3); + let overrides = [{ + type: "incompatible", + minVersion: 1, + maxVersion: 2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 0.1, + appMaxVersion: 0.2 + }, { + type: "incompatible", + minVersion: 2, + maxVersion: 2, + appID: "xpcshell@tests.mozilla.org", + appMinVersion: 1, + appMaxVersion: 2 + }]; + let update = AddonUpdateChecker.getNewestCompatibleUpdate( + updates, null, null, true, false, overrides); + notEqual(update, null); + equal(update.version, 1); + } +}); -function run_test_4() { - AddonUpdateChecker.checkForUpdates("test_bug378216_8@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_false("updateURL" in updates[0]); - run_test_5(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_5() { - AddonUpdateChecker.checkForUpdates("test_bug378216_9@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_6(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_6() { - AddonUpdateChecker.checkForUpdates("test_bug378216_10@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_7(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_7() { - AddonUpdateChecker.checkForUpdates("test_bug378216_11@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_8(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_8() { - AddonUpdateChecker.checkForUpdates("test_bug378216_12@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_false("updateURL" in updates[0]); - run_test_9(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_9() { - AddonUpdateChecker.checkForUpdates("test_bug378216_13@tests.mozilla.org", - updateKey, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - do_check_eq(updates[0].version, "2.0"); - do_check_true("updateURL" in updates[0]); - run_test_10(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_10() { - AddonUpdateChecker.checkForUpdates("test_bug378216_14@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 0); - run_test_11(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_11() { - AddonUpdateChecker.checkForUpdates("test_bug378216_15@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_throw("Update check should have failed"); - }, - - onUpdateCheckError: function(status) { - do_check_eq(status, AddonUpdateChecker.ERROR_PARSE_ERROR); - run_test_12(); - } - }); -} - -function run_test_12() { - AddonUpdateChecker.checkForUpdates("ignore-compat@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 3); - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, - null, - null, - true); - do_check_neq(update, null); - do_check_eq(update.version, 2); - run_test_13(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_13() { - AddonUpdateChecker.checkForUpdates("compat-override@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 3); - let overrides = [{ - type: "incompatible", - minVersion: 1, - maxVersion: 2, - appID: "xpcshell@tests.mozilla.org", - appMinVersion: 0.1, - appMaxVersion: 0.2 - }, { - type: "incompatible", - minVersion: 2, - maxVersion: 2, - appID: "xpcshell@tests.mozilla.org", - appMinVersion: 1, - appMaxVersion: 2 - }]; - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, - null, - null, - true, - false, - overrides); - do_check_neq(update, null); - do_check_eq(update.version, 1); - run_test_14(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} - -function run_test_14() { - AddonUpdateChecker.checkForUpdates("compat-strict-optin@tests.mozilla.org", - null, - "http://localhost:4444/data/test_updatecheck.rdf", { - onUpdateCheckComplete: function(updates) { - do_check_eq(updates.length, 1); - let update = AddonUpdateChecker.getNewestCompatibleUpdate(updates, - null, - null, - true, - false); - do_check_eq(update, null); - end_test(); - }, - - onUpdateCheckError: function(status) { - do_throw("Update check failed with status " + status); - } - }); -} +add_task(function* () { + for (let file of ["test_updatecheck.rdf", "test_updatecheck.json"]) { + let updates = yield checkUpdates("compat-strict-optin@tests.mozilla.org", + null, file); + equal(updates.length, 1); + let update = AddonUpdateChecker.getNewestCompatibleUpdate( + updates, null, null, true, false); + equal(update, null); + } +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini index bab072e830..812ef99d15 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini @@ -262,6 +262,7 @@ skip-if = os == "android" # Bug 676992: test consistently hangs on Android skip-if = os == "android" run-sequentially = Uses hardcoded ports in xpi files. +[test_json_updatecheck.js] [test_updateid.js] # Bug 676992: test consistently hangs on Android skip-if = os == "android" diff --git a/uriloader/prefetch/OfflineCacheUpdateParent.cpp b/uriloader/prefetch/OfflineCacheUpdateParent.cpp index e50432e800..3fb8d0f828 100644 --- a/uriloader/prefetch/OfflineCacheUpdateParent.cpp +++ b/uriloader/prefetch/OfflineCacheUpdateParent.cpp @@ -17,7 +17,8 @@ using namespace mozilla::ipc; using mozilla::BasePrincipal; -using mozilla::OriginAttributes; +using mozilla::DocShellOriginAttributes; +using mozilla::PrincipalOriginAttributes; using mozilla::dom::TabParent; // @@ -52,7 +53,7 @@ NS_IMPL_ISUPPORTS(OfflineCacheUpdateParent, // OfflineCacheUpdateParent //----------------------------------------------------------------------------- -OfflineCacheUpdateParent::OfflineCacheUpdateParent(const OriginAttributes& aAttrs) +OfflineCacheUpdateParent::OfflineCacheUpdateParent(const DocShellOriginAttributes& aAttrs) : mIPCClosed(false) , mOriginAttributes(aAttrs) { @@ -93,8 +94,10 @@ OfflineCacheUpdateParent::Schedule(const URIParams& aManifestURI, bool offlinePermissionAllowed = false; + PrincipalOriginAttributes principalAttrs; + principalAttrs.InheritFromDocShellToDoc(mOriginAttributes, manifestURI); nsCOMPtr principal = - BasePrincipal::CreateCodebasePrincipal(manifestURI, mOriginAttributes); + BasePrincipal::CreateCodebasePrincipal(manifestURI, principalAttrs); nsresult rv = service->OfflineAppAllowed( principal, nullptr, &offlinePermissionAllowed); diff --git a/uriloader/prefetch/OfflineCacheUpdateParent.h b/uriloader/prefetch/OfflineCacheUpdateParent.h index c7a83223fa..b98bcc9bd1 100644 --- a/uriloader/prefetch/OfflineCacheUpdateParent.h +++ b/uriloader/prefetch/OfflineCacheUpdateParent.h @@ -45,7 +45,7 @@ public: mIPCClosed = true; } - explicit OfflineCacheUpdateParent(const mozilla::OriginAttributes& aAttrs); + explicit OfflineCacheUpdateParent(const mozilla::DocShellOriginAttributes& aAttrs); virtual void ActorDestroy(ActorDestroyReason aWhy) override; private: @@ -53,7 +53,7 @@ private: bool mIPCClosed; - mozilla::OriginAttributes mOriginAttributes; + mozilla::DocShellOriginAttributes mOriginAttributes; }; } // namespace docshell