author | Ryan VanderMeulen <ryanvm@gmail.com> |
Tue, 24 Mar 2015 11:34:55 -0400 | |
changeset 235308 | 08f09b24606efcb5afcc69dc612dcc426f7f20ec |
parent 235307 | 840cfd5bc9712a9dbccb829b71abc5fdcfd00020 (current diff) |
parent 235262 | 5329cda711c8f3ca57768a3f37e6a4f17996acd9 (diff) |
child 235335 | 97be9569b8701ed5d85ef469b4d4d442855a07fd |
push id | 57400 |
push user | ryanvm@gmail.com |
push date | Tue, 24 Mar 2015 15:59:13 +0000 |
treeherder | mozilla-inbound@47fa87252df0 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 39.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/accessible/xpcom/xpcAccessibleTable.cpp +++ b/accessible/xpcom/xpcAccessibleTable.cpp @@ -263,17 +263,17 @@ NS_IMETHODIMP xpcAccessibleTable::GetSelectedCells(nsIArray** aSelectedCells) { NS_ENSURE_ARG_POINTER(aSelectedCells); *aSelectedCells = nullptr; if (!Intl()) return NS_ERROR_FAILURE; - NS_IMETHODIMP rv = NS_OK; + nsresult rv = NS_OK; nsCOMPtr<nsIMutableArray> selCells = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsAutoTArray<Accessible*, XPC_TABLE_DEFAULT_SIZE> cellsArray; Intl()->SelectedCells(&cellsArray); uint32_t totalCount = cellsArray.Length();
new file mode 100644 --- /dev/null +++ b/b2g/config/desktop/config.json @@ -0,0 +1,8 @@ +{ + "gaia": { + "l10n": { + "vcs": "hgtool", + "root": "https://hg.mozilla.org/gaia-l10n" + } + } +}
--- a/b2g/locales/jar.mn +++ b/b2g/locales/jar.mn @@ -31,16 +31,18 @@ relativesrcdir toolkit/locales: #about:crashes locale/@AB_CD@/b2g-l10n/overrides/crashreporter/crashes.dtd (%crashreporter/crashes.dtd) locale/@AB_CD@/b2g-l10n/overrides/crashreporter/crashes.properties (%crashreporter/crashes.properties) #about:mozilla locale/@AB_CD@/b2g-l10n/overrides/global/mozilla.dtd (%chrome/global/mozilla.dtd) #about:telemetry locale/@AB_CD@/b2g-l10n/overrides/global/aboutTelemetry.dtd (%chrome/global/aboutTelemetry.dtd) locale/@AB_CD@/b2g-l10n/overrides/global/aboutTelemetry.properties (%chrome/global/aboutTelemetry.properties) +#about:webrtc + locale/@AB_CD@/b2g-l10n/overrides/global/aboutWebrtc.properties (%chrome/global/aboutWebrtc.properties) % override chrome://global/locale/about.dtd chrome://b2g-l10n/locale/overrides/about.dtd % override chrome://global/locale/aboutAbout.dtd chrome://b2g-l10n/locale/overrides/aboutAbout.dtd % override chrome://global/locale/aboutRights.dtd chrome://b2g-l10n/locale/overrides/aboutRights.dtd % override chrome://global/locale/commonDialogs.properties chrome://b2g-l10n/locale/overrides/commonDialogs.properties % override chrome://mozapps/locale/handling/handling.properties chrome://b2g-l10n/locale/overrides/handling/handling.properties % override chrome://global/locale/intl.properties chrome://b2g-l10n/locale/overrides/intl.properties % override chrome://global/locale/intl.css chrome://b2g-l10n/locale/overrides/intl.css @@ -49,16 +51,17 @@ relativesrcdir toolkit/locales: % override chrome://mozapps/locale/update/updates.properties chrome://b2g-l10n/locale/overrides/update/updates.properties % override chrome://global/locale/aboutSupport.dtd chrome://b2g-l10n/locale/overrides/global/aboutSupport.dtd % override chrome://global/locale/aboutSupport.properties chrome://b2g-l10n/locale/overrides/global/aboutSupport.properties % override chrome://global/locale/crashes.dtd chrome://b2g-l10n/locale/overrides/crashreporter/crashes.dtd % override chrome://global/locale/crashes.properties chrome://b2g-l10n/locale/overrides/crashreporter/crashes.properties % override chrome://global/locale/mozilla.dtd chrome://b2g-l10n/locale/overrides/global/mozilla.dtd % override chrome://global/locale/aboutTelemetry.dtd chrome://b2g-l10n/locale/overrides/global/aboutTelemetry.dtd % override chrome://global/locale/aboutTelemetry.properties chrome://b2g-l10n/locale/overrides/global/aboutTelemetry.properties +% override chrome://global/locale/aboutWebrtc.properties chrome://b2g-l10n/locale/overrides/global/aboutWebrtc.properties # overrides for dom l10n, also for en-US relativesrcdir dom/locales: locale/@AB_CD@/b2g-l10n/overrides/global.dtd (%chrome/global.dtd) locale/@AB_CD@/b2g-l10n/overrides/AccessFu.properties (%chrome/accessibility/AccessFu.properties) locale/@AB_CD@/b2g-l10n/overrides/dom/dom.properties (%chrome/dom/dom.properties) #about:plugins locale/@AB_CD@/b2g-l10n/overrides/plugins.properties (%chrome/plugins.properties)
--- a/browser/components/nsBrowserContentHandler.js +++ b/browser/components/nsBrowserContentHandler.js @@ -337,16 +337,27 @@ nsBrowserContentHandler.prototype = { if (cmdLine.handleFlag("browser", false)) { // Passing defaultArgs, so use NO_EXTERNAL_URIS openWindow(null, this.chromeURL, "_blank", "chrome,dialog=no,all" + this.getFeatures(cmdLine), this.defaultArgs, NO_EXTERNAL_URIS); cmdLine.preventDefault = true; } + // In the past, when an instance was not already running, the -remote + // option returned an error code. Any script or application invoking the + // -remote option is expected to be handling this case, otherwise they + // wouldn't be doing anything when there is no Firefox already running. + // Making the -remote option always return an error code makes those + // scripts or applications handle the situation as if Firefox was not + // already running. + if (cmdLine.handleFlag("remote", true)) { + throw NS_ERROR_ABORT; + } + var uriparam; try { while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) { var uri = resolveURIInternal(cmdLine, uriparam); if (!shouldLoadURI(uri)) continue; openWindow(null, this.chromeURL, "_blank", "chrome,dialog=no,all" + this.getFeatures(cmdLine),
--- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -4646,18 +4646,17 @@ nsDocShell::IsPrintingOrPP(bool aDisplay return mIsPrintingOrPP; } bool nsDocShell::IsNavigationAllowed(bool aDisplayPrintErrorDialog, bool aCheckIfUnloadFired) { bool isAllowed = !IsPrintingOrPP(aDisplayPrintErrorDialog) && - (!aCheckIfUnloadFired || !mFiredUnloadEvent) && - !mBlockNavigation; + (!aCheckIfUnloadFired || !mFiredUnloadEvent); if (!isAllowed) { return false; } if (!mContentViewer) { return true; } bool firingBeforeUnload; mContentViewer->GetBeforeUnloadFiring(&firingBeforeUnload); @@ -9558,18 +9557,16 @@ nsDocShell::InternalLoad(nsIURI* aURI, nsISHEntry* aSHEntry, bool aFirstParty, const nsAString& aSrcdoc, nsIDocShell* aSourceDocShell, nsIURI* aBaseURI, nsIDocShell** aDocShell, nsIRequest** aRequest) { - MOZ_RELEASE_ASSERT(!mBlockNavigation); - nsresult rv = NS_OK; mOriginalUriString.Truncate(); #ifdef PR_LOGGING if (gDocShellLeakLog && PR_LOG_TEST(gDocShellLeakLog, PR_LOG_DEBUG)) { nsAutoCString spec; if (aURI) { aURI->GetSpec(spec); @@ -10015,29 +10012,16 @@ nsDocShell::InternalLoad(nsIURI* aURI, sameExceptHashes && !newHash.IsEmpty()); if (doShortCircuitedLoad) { // Save the position of the scrollers. nscoord cx = 0, cy = 0; GetCurScrollPos(ScrollOrientation_X, &cx); GetCurScrollPos(ScrollOrientation_Y, &cy); - { - AutoRestore<bool> scrollingToAnchor(mBlockNavigation); - mBlockNavigation = true; - - // ScrollToAnchor doesn't necessarily cause us to scroll the window; - // the function decides whether a scroll is appropriate based on the - // arguments it receives. But even if we don't end up scrolling, - // ScrollToAnchor performs other important tasks, such as informing - // the presShell that we have a new hash. See bug 680257. - rv = ScrollToAnchor(curHash, newHash, aLoadType); - NS_ENSURE_SUCCESS(rv, rv); - } - // Reset mLoadType to its original value once we exit this block, // because this short-circuited load might have started after a // normal, network load, and we don't want to clobber its load type. // See bug 737307. AutoRestore<uint32_t> loadTypeResetter(mLoadType); // If a non-short-circuit load (i.e., a network load) is pending, // make this a replacement load, so that we don't add a SHEntry here @@ -10117,26 +10101,16 @@ nsDocShell::InternalLoad(nsIURI* aURI, // Make sure we won't just repost without hitting the // cache first if (cacheKey) { mOSHE->SetCacheKey(cacheKey); } } - /* restore previous position of scroller(s), if we're moving - * back in history (bug 59774) - */ - if (mOSHE && (aLoadType == LOAD_HISTORY || - aLoadType == LOAD_RELOAD_NORMAL)) { - nscoord bx, by; - mOSHE->GetScrollPosition(&bx, &by); - SetCurScrollPosEx(bx, by); - } - /* Restore the original LSHE if we were loading something * while short-circuited load was initiated. */ SetHistoryEntry(&mLSHE, oldLSHE); /* Set the title for the SH entry for this target url. so that * SH menus in go/back/forward buttons won't be empty for this. */ if (mSessionHistory) { @@ -10161,41 +10135,61 @@ nsDocShell::InternalLoad(nsIURI* aURI, // Set the doc's URI according to the new history entry's URI. nsCOMPtr<nsIDocument> doc = GetDocument(); NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); doc->SetDocumentURI(aURI); SetDocCurrentStateObj(mOSHE); + // Inform the favicon service that the favicon for oldURI also + // applies to aURI. + CopyFavicon(currentURI, aURI, mInPrivateBrowsing); + + nsRefPtr<nsGlobalWindow> win = mScriptGlobal ? + mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + + // ScrollToAnchor doesn't necessarily cause us to scroll the window; + // the function decides whether a scroll is appropriate based on the + // arguments it receives. But even if we don't end up scrolling, + // ScrollToAnchor performs other important tasks, such as informing + // the presShell that we have a new hash. See bug 680257. + rv = ScrollToAnchor(curHash, newHash, aLoadType); + NS_ENSURE_SUCCESS(rv, rv); + + /* restore previous position of scroller(s), if we're moving + * back in history (bug 59774) + */ + if (mOSHE && (aLoadType == LOAD_HISTORY || + aLoadType == LOAD_RELOAD_NORMAL)) { + nscoord bx, by; + mOSHE->GetScrollPosition(&bx, &by); + SetCurScrollPosEx(bx, by); + } + // Dispatch the popstate and hashchange events, as appropriate. // // The event dispatch below can cause us to re-enter script and // destroy the docshell, nulling out mScriptGlobal. Hold a stack // reference to avoid null derefs. See bug 914521. - nsRefPtr<nsGlobalWindow> win = mScriptGlobal; if (win) { // Fire a hashchange event URIs differ, and only in their hashes. bool doHashchange = sameExceptHashes && !curHash.Equals(newHash); if (historyNavBetweenSameDoc || doHashchange) { win->DispatchSyncPopState(); } if (doHashchange) { // Note that currentURI hasn't changed because it's on the // stack, so we can just use it directly as the old URI. win->DispatchAsyncHashchange(currentURI, aURI); } } - // Inform the favicon service that the favicon for oldURI also - // applies to aURI. - CopyFavicon(currentURI, aURI, mInPrivateBrowsing); - return NS_OK; } } // Check if the webbrowser chrome wants the load to proceed; this can be // used to cancel attempts to load URIs in the wrong process. nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner); if (browserChrome3) { @@ -13990,17 +13984,18 @@ nsDocShell::ChannelIntercepted(nsIInterc if (!isNavigation) { doc = GetDocument(); if (!doc) { return NS_ERROR_NOT_AVAILABLE; } } - return swm->DispatchFetchEvent(doc, aChannel); + bool isReload = mLoadType & LOAD_CMD_RELOAD; + return swm->DispatchFetchEvent(doc, aChannel, isReload); } NS_IMETHODIMP nsDocShell::SetPaymentRequestId(const nsAString& aPaymentRequestId) { mPaymentRequestId = aPaymentRequestId; return NS_OK; }
--- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -893,17 +893,16 @@ protected: bool mIsActive; bool mIsPrerendered; bool mIsAppTab; bool mUseGlobalHistory; bool mInPrivateBrowsing; bool mUseRemoteTabs; bool mDeviceSizeIsPageSize; bool mWindowDraggingAllowed; - bool mBlockNavigation; // Because scriptability depends on the mAllowJavascript values of our // ancestors, we cache the effective scriptability and recompute it when // it might have changed; bool mCanExecuteScripts; void RecomputeCanExecuteScripts(); // This boolean is set to true right before we fire pagehide and generally
--- a/dom/animation/AnimationPlayer.cpp +++ b/dom/animation/AnimationPlayer.cpp @@ -437,16 +437,19 @@ AnimationPlayer::DoPlay() if (mHoldTime.IsNull()) { return; } // Clear ready promise. We'll create a new one lazily. mReady = nullptr; + // Clear the start time until we resolve a new one + mStartTime.SetNull(); + mIsPending = true; nsIDocument* doc = GetRenderedDocument(); if (!doc) { StartOnNextTick(Nullable<TimeDuration>()); return; }
--- a/dom/animation/test/css-animations/test_animation-player-starttime.html +++ b/dom/animation/test/css-animations/test_animation-player-starttime.html @@ -194,37 +194,16 @@ function EventWatcher(watchedNode, event // animation. The terms can be found here: // // http://w3c.github.io/web-animations/#animation-node-phases-and-states // // Note the distinction between "player start time" and "animation start time". // The former is the start of the start delay. The latter is the start of the // active interval. (If there is no delay, they are the same.) -// Called when startTime is set to the time the start delay would ideally -// start (not accounting for any delay to next paint tick). -function checkStateOnSettingStartTimeToAnimationCreationTime(player) -{ - // We don't test player.startTime since our caller just set it. - - assert_equals(player.playState, 'running', - 'AnimationPlayer.playState should be "running" at the start of ' + - 'the start delay'); - - assert_equals(player.source.target.style.animationPlayState, 'running', - 'AnimationPlayer.source.target.style.animationPlayState should be ' + - '"running" at the start of the start delay'); - - var div = player.source.target; - var marginLeft = parseFloat(getComputedStyle(div).marginLeft); - assert_equals(marginLeft, UNANIMATED_POSITION, - 'the computed value of margin-left should be unaffected ' + - 'at the beginning of the start delay'); -} - // Called when the ready Promise's callbacks should happen function checkStateOnReadyPromiseResolved(player) { assert_less_than_equal(player.startTime, player.timeline.currentTime, 'AnimationPlayer.startTime should be less than the timeline\'s ' + 'currentTime on the first paint tick after animation creation'); assert_equals(player.playState, 'running', @@ -289,51 +268,102 @@ function checkStateAtActiveIntervalEndTi var div = player.source.target; var marginLeft = parseFloat(getComputedStyle(div).marginLeft); assert_equals(marginLeft, UNANIMATED_POSITION, 'the computed value of margin-left should be unaffected ' + 'by the animation at the end of the active duration when the ' + 'animation-fill-mode is none'); } +test(function(t) +{ + var div = addDiv(t, { 'style': 'animation: anim 100s' }); + var player = div.getAnimationPlayers()[0]; + assert_equals(player.startTime, null, 'startTime is unresolved'); +}, 'startTime of a newly created (play-pending) animation is unresolved'); + +test(function(t) +{ + var div = addDiv(t, { 'style': 'animation: anim 100s paused' }); + var player = div.getAnimationPlayers()[0]; + assert_equals(player.startTime, null, 'startTime is unresolved'); +}, 'startTime of a newly created (pause-pending) animation is unresolved'); + +async_test(function(t) +{ + var div = addDiv(t, { 'style': 'animation: anim 100s' }); + var player = div.getAnimationPlayers()[0]; + player.ready.then(t.step_func(function() { + assert_true(player.startTime > 0, + 'startTime is resolved when running'); + t.done(); + })); +}, 'startTime is resolved when running'); + +async_test(function(t) +{ + var div = addDiv(t, { 'style': 'animation: anim 100s paused' }); + var player = div.getAnimationPlayers()[0]; + player.ready.then(t.step_func(function() { + assert_equals(player.startTime, null, + 'startTime is unresolved when paused'); + t.done(); + })); +}, 'startTime is unresolved when paused'); + +async_test(function(t) +{ + var div = addDiv(t, { 'style': 'animation: anim 100s' }); + var player = div.getAnimationPlayers()[0]; + player.ready.then(t.step_func(function() { + div.style.animationPlayState = 'paused'; + getComputedStyle(div).animationPlayState; + /* FIXME: Switch this on once deferred pausing is enabled + assert_not_equals(player.startTime, null, + 'startTime is resolved when pause-pending'); + */ + + div.style.animationPlayState = 'running'; + getComputedStyle(div).animationPlayState; + assert_equals(player.startTime, null, + 'startTime is unresolved when play-pending'); + t.done(); + })); +}, 'startTime while pause-pending and play-pending'); + +async_test(function(t) +{ + var div = addDiv(t, { 'style': 'animation: anim 100s' }); + var player = div.getAnimationPlayers()[0]; + // Seek to end to put us in the finished state + // FIXME: Once we implement finish(), use that here. + player.currentTime = 100 * 1000; + player.ready.then(t.step_func(function() { + // Call play() which puts us back in the running state + player.play(); + // FIXME: Enable this once we implement finishing behavior (bug 1074630) + /* + assert_equals(player.startTime, null, 'startTime is unresolved'); + */ + t.done(); + })); +}, 'startTime while play-pending from finished state'); + test(function(t) { var div = addDiv(t, {'class': 'animated-div'}); - div.style.animation = ANIM_PROPERTY_VAL; var player = div.getAnimationPlayers()[0]; - - // Animations shouldn't start until the next paint tick, so: - assert_equals(player.startTime, null, - 'AnimationPlayer.startTime should be unresolved when an animation ' + - 'is initially created'); - - assert_equals(player.playState, "pending", - 'AnimationPlayer.playState should be "pending" when an animation ' + - 'is initially created'); - - assert_equals(player.source.target.style.animationPlayState, 'running', - 'AnimationPlayer.source.target.style.animationPlayState should be ' + - '"running" when an animation is initially created'); - - // XXX Ideally we would have a test to check the ready Promise is initially - // unresolved, but currently there is no Web API to do that. Waiting for the - // ready Promise with a timeout doesn't work because the resolved callback - // will be called (async) regardless of whether the Promise was resolved in - // the past or is resolved in the future. - var currentTime = player.timeline.currentTime; player.startTime = currentTime; assert_approx_equals(player.startTime, currentTime, 0.0001, // rounding error 'Check setting of startTime actually works'); - - checkStateOnSettingStartTimeToAnimationCreationTime(player); -}, 'Sanity test to check round-tripping assigning to new animation\'s ' + +}, 'Sanity test to check round-tripping assigning to a new animation\'s ' + 'startTime'); async_test(function(t) { var div = addDiv(t, {'class': 'animated-div'}); var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS); div.style.animation = ANIM_PROPERTY_VAL;
--- a/dom/animation/test/css-transitions/test_animation-player-starttime.html +++ b/dom/animation/test/css-transitions/test_animation-player-starttime.html @@ -185,33 +185,16 @@ function EventWatcher(watchedNode, event // animation. The terms can be found here: // // http://w3c.github.io/web-animations/#animation-node-phases-and-states // // Note the distinction between "player start time" and "animation start time". // The former is the start of the start delay. The latter is the start of the // active interval. (If there is no delay, they are the same.) -// Called when startTime is set to the time the start delay would ideally -// start (not accounting for any delay to next paint tick). -function checkStateOnSettingStartTimeToAnimationCreationTime(player) -{ - // We don't test player.startTime since our caller just set it. - - assert_equals(player.playState, 'running', - 'AnimationPlayer.playState should be "running" at the start of ' + - 'the start delay'); - - var div = player.source.target; - var marginLeft = parseFloat(getComputedStyle(div).marginLeft); - assert_equals(marginLeft, INITIAL_POSITION, - 'the computed value of margin-left should be unaffected ' + - 'at the beginning of the start delay'); -} - // Called when the ready Promise's callbacks should happen function checkStateOnReadyPromiseResolved(player) { assert_less_than_equal(player.startTime, player.timeline.currentTime, 'AnimationPlayer.startTime should be less than the timeline\'s ' + 'currentTime on the first paint tick after animation creation'); assert_equals(player.playState, 'running', @@ -263,47 +246,38 @@ function checkStateAtActiveIntervalEndTi var div = player.source.target; var marginLeft = parseFloat(getComputedStyle(div).marginLeft); assert_equals(marginLeft, END_POSITION, 'the computed value of margin-left should be the final transitioned-to ' + 'value at the end of the active duration'); } - test(function(t) { var div = addDiv(t, {'class': 'animated-div'}); - flushComputedStyle(div); div.style.marginLeft = '200px'; // initiate transition var player = div.getAnimationPlayers()[0]; + assert_equals(player.startTime, null, 'startTime is unresolved'); +}, 'startTime of a newly created transition is unresolved'); - // Animations shouldn't start until the next paint tick, so: - assert_equals(player.startTime, null, - 'AnimationPlayer.startTime should be unresolved when an animation ' + - 'is initially created'); - assert_equals(player.playState, "pending", - 'AnimationPlayer.playState should be "pending" when an animation ' + - 'is initially created'); +test(function(t) +{ + var div = addDiv(t, {'class': 'animated-div'}); + flushComputedStyle(div); + div.style.marginLeft = '200px'; // initiate transition - // XXX Ideally we would have a test to check the ready Promise is initially - // unresolved, but currently there is no Web API to do that. Waiting for the - // ready Promise with a timeout doesn't work because the resolved callback - // will be called (async) regardless of whether the Promise was resolved in - // the past or is resolved in the future. - + var player = div.getAnimationPlayers()[0]; var currentTime = player.timeline.currentTime; player.startTime = currentTime; assert_approx_equals(player.startTime, currentTime, 0.0001, // rounding error 'Check setting of startTime actually works'); - - checkStateOnSettingStartTimeToAnimationCreationTime(player); }, 'Sanity test to check round-tripping assigning to new animation\'s ' + 'startTime'); async_test(function(t) { var div = addDiv(t, {'class': 'animated-div'}); var eventWatcher = new EventWatcher(div, 'transitionend');
--- a/dom/base/nsPerformance.cpp +++ b/dom/base/nsPerformance.cpp @@ -19,16 +19,17 @@ #include "PerformanceMeasure.h" #include "PerformanceResourceTiming.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/PerformanceBinding.h" #include "mozilla/dom/PerformanceTimingBinding.h" #include "mozilla/dom/PerformanceNavigationBinding.h" #include "mozilla/IntegerPrintfMacros.h" #include "mozilla/TimeStamp.h" +#include "js/HeapAPI.h" #ifdef MOZ_WIDGET_GONK #define PERFLOG(msg, ...) __android_log_print(ANDROID_LOG_INFO, "PerformanceTiming", msg, ##__VA_ARGS__) #else #define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__) #endif using namespace mozilla; @@ -384,20 +385,37 @@ nsPerformanceNavigation::~nsPerformanceN JSObject* nsPerformanceNavigation::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) { return PerformanceNavigationBinding::Wrap(cx, this, aGivenProto); } -NS_IMPL_CYCLE_COLLECTION_INHERITED(nsPerformance, DOMEventTargetHelper, - mWindow, mTiming, - mNavigation, mEntries, - mParentPerformance) +NS_IMPL_CYCLE_COLLECTION_CLASS(nsPerformance) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsPerformance, DOMEventTargetHelper) +NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow, mTiming, + mNavigation, mEntries, + mParentPerformance) + tmp->mMozMemory = nullptr; + mozilla::DropJSObjects(this); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsPerformance, DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mTiming, + mNavigation, mEntries, + mParentPerformance) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsPerformance, DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMozMemory) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + NS_IMPL_ADDREF_INHERITED(nsPerformance, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(nsPerformance, DOMEventTargetHelper) nsPerformance::nsPerformance(nsPIDOMWindow* aWindow, nsDOMNavigationTiming* aDOMTiming, nsITimedChannel* aChannel, nsPerformance* aParentPerformance) : DOMEventTargetHelper(aWindow), @@ -407,24 +425,37 @@ nsPerformance::nsPerformance(nsPIDOMWind mParentPerformance(aParentPerformance), mPrimaryBufferSize(kDefaultBufferSize) { MOZ_ASSERT(aWindow, "Parent window object should be provided"); } nsPerformance::~nsPerformance() { + mozilla::DropJSObjects(this); } // QueryInterface implementation for nsPerformance NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPerformance) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) +void +nsPerformance::GetMozMemory(JSContext *aCx, JS::MutableHandle<JSObject*> aObj) +{ + if (!mMozMemory) { + mMozMemory = js::gc::NewMemoryInfoObject(aCx); + if (mMozMemory) { + mozilla::HoldJSObjects(this); + } + } + + aObj.set(mMozMemory); +} nsPerformanceTiming* nsPerformance::Timing() { if (!mTiming) { // For navigation timing, the third argument (an nsIHtttpChannel) is null // since the cross-domain redirect were already checked. // The last argument (zero time) for performance.timing is the navigation
--- a/dom/base/nsPerformance.h +++ b/dom/base/nsPerformance.h @@ -8,16 +8,17 @@ #include "nsCOMPtr.h" #include "nsAutoPtr.h" #include "mozilla/Attributes.h" #include "nsWrapperCache.h" #include "nsDOMNavigationTiming.h" #include "nsContentUtils.h" #include "nsPIDOMWindow.h" #include "js/TypeDecls.h" +#include "js/RootingAPI.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/DOMEventTargetHelper.h" class nsITimedChannel; class nsPerformance; class nsIHttpChannel; namespace mozilla { @@ -292,17 +293,17 @@ class nsPerformance final : public mozil public: typedef mozilla::dom::PerformanceEntry PerformanceEntry; nsPerformance(nsPIDOMWindow* aWindow, nsDOMNavigationTiming* aDOMTiming, nsITimedChannel* aChannel, nsPerformance* aParentPerformance); NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsPerformance, DOMEventTargetHelper) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsPerformance, DOMEventTargetHelper) nsDOMNavigationTiming* GetDOMTiming() const { return mDOMTiming; } nsITimedChannel* GetChannel() const { @@ -339,16 +340,18 @@ public: void Mark(const nsAString& aName, mozilla::ErrorResult& aRv); void ClearMarks(const mozilla::dom::Optional<nsAString>& aName); void Measure(const nsAString& aName, const mozilla::dom::Optional<nsAString>& aStartMark, const mozilla::dom::Optional<nsAString>& aEndMark, mozilla::ErrorResult& aRv); void ClearMeasures(const mozilla::dom::Optional<nsAString>& aName); + void GetMozMemory(JSContext *aCx, JS::MutableHandle<JSObject*> aObj); + IMPL_EVENT_HANDLER(resourcetimingbufferfull) private: ~nsPerformance(); bool IsPerformanceTimingAttribute(const nsAString& aName); DOMHighResTimeStamp ResolveTimestampFromName(const nsAString& aName, mozilla::ErrorResult& aRv); DOMTimeMilliSec GetPerformanceTimingFromString(const nsAString& aTimingName); DOMHighResTimeStamp ConvertDOMMilliSecToHighRes(const DOMTimeMilliSec aTime); @@ -359,16 +362,17 @@ private: nsCOMPtr<nsPIDOMWindow> mWindow; nsRefPtr<nsDOMNavigationTiming> mDOMTiming; nsCOMPtr<nsITimedChannel> mChannel; nsRefPtr<nsPerformanceTiming> mTiming; nsRefPtr<nsPerformanceNavigation> mNavigation; nsTArray<nsRefPtr<PerformanceEntry> > mEntries; nsRefPtr<nsPerformance> mParentPerformance; uint64_t mPrimaryBufferSize; + JS::Heap<JSObject*> mMozMemory; static const uint64_t kDefaultBufferSize = 150; // Helper classes class PerformanceEntryComparator { public: bool Equals(const PerformanceEntry* aElem1, const PerformanceEntry* aElem2) const;
--- a/dom/cache/Action.cpp +++ b/dom/cache/Action.cpp @@ -5,23 +5,23 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/cache/Action.h" namespace mozilla { namespace dom { namespace cache { -NS_IMPL_ISUPPORTS0(mozilla::dom::cache::Action::Resolver); - void Action::CancelOnInitiatingThread() { NS_ASSERT_OWNINGTHREAD(Action); - MOZ_ASSERT(!mCanceled); + // It is possible for cancellation to be duplicated. For example, an + // individual Cache could have its Actions canceled and then shutdown + // could trigger a second action. mCanceled = true; } Action::Action() : mCanceled(false) { }
--- a/dom/cache/Action.h +++ b/dom/cache/Action.h @@ -13,31 +13,29 @@ namespace mozilla { namespace dom { namespace cache { class Action { public: - class Resolver : public nsISupports + class Resolver { - protected: - // virtual because deleted through base class pointer - virtual ~Resolver() { } - public: // Note: Action must drop Resolver ref after calling Resolve()! // Note: Must be called on the same thread used to execute // Action::RunOnTarget(). virtual void Resolve(nsresult aRv) = 0; - // We must use ISUPPORTS for our refcounting here because sub-classes also - // want to inherit interfaces like nsIRunnable. - NS_DECL_THREADSAFE_ISUPPORTS + NS_IMETHOD_(MozExternalRefCountType) + AddRef(void) = 0; + + NS_IMETHOD_(MozExternalRefCountType) + Release(void) = 0; }; // Execute operations on the target thread. Once complete call // Resolver::Resolve(). This can be done sync or async. // Note: Action should hold Resolver ref until its ready to call Resolve(). // Note: The "target" thread is determined when the Action is scheduled on // Context. The Action should not assume any particular thread is used. virtual void RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo) = 0; @@ -46,30 +44,32 @@ public: // responsible for calling Resolver::Resolve() as normal; either with a // normal error code or NS_ERROR_ABORT. If CancelOnInitiatingThread() is // called after Resolve() has already occurred, then the cancel can be // ignored. // // Cancellation is a best effort to stop processing as soon as possible, but // does not guarantee the Action will not run. // + // CancelOnInitiatingThread() may be called more than once. Subsequent + // calls should have no effect. + // // Default implementation sets an internal cancellation flag that can be // queried with IsCanceled(). virtual void CancelOnInitiatingThread(); // Executed on the initiating thread and is passed the nsresult given to // Resolver::Resolve(). virtual void CompleteOnInitiatingThread(nsresult aRv) { } // Executed on the initiating thread. If this Action will operate on the // given cache ID then override this to return true. virtual bool MatchesCacheId(CacheId aCacheId) const { return false; } - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(cache::Action) - NS_DECL_OWNINGTHREAD + NS_INLINE_DECL_REFCOUNTING(cache::Action) protected: Action(); // virtual because deleted through base class pointer virtual ~Action(); // Check if this Action has been canceled. May be called from any thread,
--- a/dom/cache/Context.cpp +++ b/dom/cache/Context.cpp @@ -1,62 +1,62 @@ /* -*- 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/. */ #include "mozilla/dom/cache/Context.h" +#include "mozilla/AutoRestore.h" #include "mozilla/DebugOnly.h" #include "mozilla/dom/cache/Action.h" #include "mozilla/dom/cache/Manager.h" #include "mozilla/dom/cache/ManagerId.h" +#include "mozilla/dom/cache/OfflineStorage.h" #include "mozilla/dom/quota/OriginOrPatternString.h" #include "mozilla/dom/quota/QuotaManager.h" #include "nsIFile.h" #include "nsIPrincipal.h" #include "nsIRunnable.h" #include "nsThreadUtils.h" namespace { using mozilla::dom::Nullable; using mozilla::dom::cache::QuotaInfo; +using mozilla::dom::quota::Client; using mozilla::dom::quota::OriginOrPatternString; using mozilla::dom::quota::QuotaManager; using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT; using mozilla::dom::quota::PersistenceType; -// Executed when the context is destroyed to release our lock on the -// QuotaManager. +// Release our lock on the QuotaManager directory asynchronously. class QuotaReleaseRunnable final : public nsRunnable { public: - QuotaReleaseRunnable(const QuotaInfo& aQuotaInfo, const nsACString& aQuotaId) + explicit QuotaReleaseRunnable(const QuotaInfo& aQuotaInfo) : mQuotaInfo(aQuotaInfo) - , mQuotaId(aQuotaId) { } NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); QuotaManager* qm = QuotaManager::Get(); MOZ_ASSERT(qm); qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin), Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT), - mQuotaId); + mQuotaInfo.mStorageId); return NS_OK; } private: ~QuotaReleaseRunnable() { } const QuotaInfo mQuotaInfo; - const nsCString mQuotaId; }; } // anonymous namespace namespace mozilla { namespace dom { namespace cache { @@ -65,30 +65,29 @@ using mozilla::dom::quota::OriginOrPatte using mozilla::dom::quota::QuotaManager; using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT; using mozilla::dom::quota::PersistenceType; // Executed to perform the complicated dance of steps necessary to initialize // the QuotaManager. This must be performed for each origin before any disk // IO occurrs. class Context::QuotaInitRunnable final : public nsIRunnable - , public Action::Resolver { public: QuotaInitRunnable(Context* aContext, Manager* aManager, - const nsACString& aQuotaId, Action* aQuotaIOThreadAction) : mContext(aContext) + , mThreadsafeHandle(aContext->CreateThreadsafeHandle()) , mManager(aManager) - , mQuotaId(aQuotaId) , mQuotaIOThreadAction(aQuotaIOThreadAction) , mInitiatingThread(NS_GetCurrentThread()) + , mResult(NS_OK) , mState(STATE_INIT) - , mResult(NS_OK) + , mNeedsQuotaRelease(false) { MOZ_ASSERT(mContext); MOZ_ASSERT(mManager); MOZ_ASSERT(mInitiatingThread); } nsresult Dispatch() { @@ -99,36 +98,45 @@ public: nsresult rv = NS_DispatchToMainThread(this, nsIThread::DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) { mState = STATE_COMPLETE; Clear(); } return rv; } - virtual void Resolve(nsresult aRv) override +private: + class SyncResolver final : public Action::Resolver { - // Depending on the error or success path, this can run on either the - // main thread or the QuotaManager IO thread. The IO thread is an - // idle thread which may be destroyed and recreated, so its hard to - // assert on. - MOZ_ASSERT(mState == STATE_RUNNING || NS_FAILED(aRv)); + public: + SyncResolver() + : mResolved(false) + , mResult(NS_OK) + { } - mResult = aRv; - mState = STATE_COMPLETING; + virtual void + Resolve(nsresult aRv) override + { + MOZ_ASSERT(!mResolved); + mResolved = true; + mResult = aRv; + }; - nsresult rv = mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL); - if (NS_FAILED(rv)) { - // Shutdown must be delayed until all Contexts are destroyed. Crash for - // this invariant violation. - MOZ_CRASH("Failed to dispatch QuotaInitRunnable to initiating thread."); - } - } + bool Resolved() const { return mResolved; } + nsresult Result() const { return mResult; } + + private: + ~SyncResolver() { } -private: + bool mResolved; + nsresult mResult; + + NS_INLINE_DECL_REFCOUNTING(Context::QuotaInitRunnable::SyncResolver, override) + }; + ~QuotaInitRunnable() { MOZ_ASSERT(mState == STATE_COMPLETE); MOZ_ASSERT(!mContext); MOZ_ASSERT(!mQuotaIOThreadAction); } enum State @@ -147,31 +155,32 @@ private: NS_ASSERT_OWNINGTHREAD(Action::Resolver); MOZ_ASSERT(mContext); mContext = nullptr; mManager = nullptr; mQuotaIOThreadAction = nullptr; } nsRefPtr<Context> mContext; + nsRefPtr<ThreadsafeHandle> mThreadsafeHandle; nsRefPtr<Manager> mManager; - const nsCString mQuotaId; nsRefPtr<Action> mQuotaIOThreadAction; nsCOMPtr<nsIThread> mInitiatingThread; - State mState; nsresult mResult; QuotaInfo mQuotaInfo; + nsMainThreadPtrHandle<OfflineStorage> mOfflineStorage; + State mState; + bool mNeedsQuotaRelease; public: - NS_DECL_ISUPPORTS_INHERITED + NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIRUNNABLE }; -NS_IMPL_ISUPPORTS_INHERITED(mozilla::dom::cache::Context::QuotaInitRunnable, - Action::Resolver, nsIRunnable); +NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::QuotaInitRunnable, nsIRunnable); // The QuotaManager init state machine is represented in the following diagram: // // +---------------+ // | Start | Resolve(error) // | (Orig Thread) +---------------------+ // +-------+-------+ | // | | @@ -186,79 +195,94 @@ NS_IMPL_ISUPPORTS_INHERITED(mozilla::dom // +--------+---------+ | // | | // +----------v------------+ | // |EnsureOriginInitialized| Resolve(error) | // | (Quota IO Thread) +----------------+ // +----------+------------+ | // | | // +---------v---------+ +------v------+ -// | Running | Resolve() | Completing | +// | Running | | Completing | // | (Quota IO Thread) +------------>(Orig Thread)| // +-------------------+ +------+------+ // | // +-----v----+ // | Complete | // +----------+ // // The initialization process proceeds through the main states. If an error -// occurs, then we transition back to Completing state back on the original -// thread. +// occurs, then we transition to Completing state back on the original thread. NS_IMETHODIMP Context::QuotaInitRunnable::Run() { // May run on different threads depending on the state. See individual // state cases for thread assertions. + nsRefPtr<SyncResolver> resolver = new SyncResolver(); + switch(mState) { // ----------------------------------- case STATE_CALL_WAIT_FOR_OPEN_ALLOWED: { MOZ_ASSERT(NS_IsMainThread()); QuotaManager* qm = QuotaManager::GetOrCreate(); if (!qm) { - Resolve(NS_ERROR_FAILURE); - return NS_OK; + resolver->Resolve(NS_ERROR_FAILURE); + break; } nsRefPtr<ManagerId> managerId = mManager->GetManagerId(); nsCOMPtr<nsIPrincipal> principal = managerId->Principal(); nsresult rv = qm->GetInfoFromPrincipal(principal, &mQuotaInfo.mGroup, &mQuotaInfo.mOrigin, &mQuotaInfo.mIsApp); if (NS_WARN_IF(NS_FAILED(rv))) { - Resolve(rv); - return NS_OK; + resolver->Resolve(rv); + break; } + QuotaManager::GetStorageId(PERSISTENCE_TYPE_DEFAULT, + mQuotaInfo.mOrigin, + Client::DOMCACHE, + NS_LITERAL_STRING("cache"), + mQuotaInfo.mStorageId); + // QuotaManager::WaitForOpenAllowed() will hold a reference to us as // a callback. We will then get executed again on the main thread when // it is safe to open the quota directory. mState = STATE_WAIT_FOR_OPEN_ALLOWED; rv = qm->WaitForOpenAllowed(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin), Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT), - mQuotaId, this); + mQuotaInfo.mStorageId, this); if (NS_FAILED(rv)) { - Resolve(rv); - return NS_OK; + resolver->Resolve(rv); + break; } break; } // ------------------------------ case STATE_WAIT_FOR_OPEN_ALLOWED: { MOZ_ASSERT(NS_IsMainThread()); + + mNeedsQuotaRelease = true; + QuotaManager* qm = QuotaManager::Get(); MOZ_ASSERT(qm); + + nsRefPtr<OfflineStorage> offlineStorage = + OfflineStorage::Register(mThreadsafeHandle, mQuotaInfo); + mOfflineStorage = new nsMainThreadPtrHolder<OfflineStorage>(offlineStorage); + mState = STATE_ENSURE_ORIGIN_INITIALIZED; nsresult rv = qm->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) { - Resolve(rv); - return NS_OK; + resolver->Resolve(rv); + break; } break; } // ---------------------------------- case STATE_ENSURE_ORIGIN_INITIALIZED: { // Can't assert quota IO thread because its an idle thread that can get // recreated. At least assert we're not on main thread or owning thread. @@ -268,75 +292,94 @@ Context::QuotaInitRunnable::Run() QuotaManager* qm = QuotaManager::Get(); MOZ_ASSERT(qm); nsresult rv = qm->EnsureOriginIsInitialized(PERSISTENCE_TYPE_DEFAULT, mQuotaInfo.mGroup, mQuotaInfo.mOrigin, mQuotaInfo.mIsApp, getter_AddRefs(mQuotaInfo.mDir)); if (NS_FAILED(rv)) { - Resolve(rv); - return NS_OK; + resolver->Resolve(rv); + break; } mState = STATE_RUNNING; if (!mQuotaIOThreadAction) { - Resolve(NS_OK); - return NS_OK; + resolver->Resolve(NS_OK); + break; } - // Execute the provided initialization Action. We pass ourselves as the - // Resolver. The Action must either call Resolve() immediately or hold - // a ref to us and call Resolve() later. - mQuotaIOThreadAction->RunOnTarget(this, mQuotaInfo); + // Execute the provided initialization Action. The Action must Resolve() + // before returning. + mQuotaIOThreadAction->RunOnTarget(resolver, mQuotaInfo); + MOZ_ASSERT(resolver->Resolved()); break; } // ------------------- case STATE_COMPLETING: { NS_ASSERT_OWNINGTHREAD(Action::Resolver); if (mQuotaIOThreadAction) { mQuotaIOThreadAction->CompleteOnInitiatingThread(mResult); } - mContext->OnQuotaInit(mResult, mQuotaInfo); + mContext->OnQuotaInit(mResult, mQuotaInfo, mOfflineStorage); mState = STATE_COMPLETE; + + if (mNeedsQuotaRelease) { + // Unlock the quota dir if we locked it previously + nsCOMPtr<nsIRunnable> runnable = new QuotaReleaseRunnable(mQuotaInfo); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable))); + } + // Explicitly cleanup here as the destructor could fire on any of // the threads we have bounced through. Clear(); break; } // ----- default: { MOZ_CRASH("unexpected state in QuotaInitRunnable"); - break; } } + if (resolver->Resolved()) { + MOZ_ASSERT(mState == STATE_RUNNING || NS_FAILED(resolver->Result())); + + MOZ_ASSERT(NS_SUCCEEDED(mResult)); + mResult = resolver->Result(); + + mState = STATE_COMPLETING; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED( + mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL))); + } + return NS_OK; } // Runnable wrapper around Action objects dispatched on the Context. This // runnable executes the Action on the appropriate threads while the Context // is initialized. class Context::ActionRunnable final : public nsIRunnable , public Action::Resolver + , public Context::Activity { public: ActionRunnable(Context* aContext, nsIEventTarget* aTarget, Action* aAction, const QuotaInfo& aQuotaInfo) : mContext(aContext) , mTarget(aTarget) , mAction(aAction) , mQuotaInfo(aQuotaInfo) , mInitiatingThread(NS_GetCurrentThread()) , mState(STATE_INIT) , mResult(NS_OK) + , mExecutingRunOnTarget(false) { MOZ_ASSERT(mContext); MOZ_ASSERT(mTarget); MOZ_ASSERT(mAction); MOZ_ASSERT(mQuotaInfo.mDir); MOZ_ASSERT(mInitiatingThread); } @@ -349,99 +392,123 @@ public: nsresult rv = mTarget->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) { mState = STATE_COMPLETE; Clear(); } return rv; } - bool MatchesCacheId(CacheId aCacheId) { + virtual bool + MatchesCacheId(CacheId aCacheId) const override + { NS_ASSERT_OWNINGTHREAD(Action::Resolver); return mAction->MatchesCacheId(aCacheId); } - void Cancel() + virtual void + Cancel() override { NS_ASSERT_OWNINGTHREAD(Action::Resolver); mAction->CancelOnInitiatingThread(); } virtual void Resolve(nsresult aRv) override { MOZ_ASSERT(mTarget == NS_GetCurrentThread()); MOZ_ASSERT(mState == STATE_RUNNING); + mResult = aRv; - mState = STATE_COMPLETING; - nsresult rv = mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL); - if (NS_FAILED(rv)) { - // Shutdown must be delayed until all Contexts are destroyed. Crash - // for this invariant violation. - MOZ_CRASH("Failed to dispatch ActionRunnable to initiating thread."); + + // We ultimately must complete on the initiating thread, but bounce through + // the current thread again to ensure that we don't destroy objects and + // state out from under the currently running action's stack. + mState = STATE_RESOLVING; + + // If we were resolved synchronously within Action::RunOnTarget() then we + // can avoid a thread bounce and just resolve once RunOnTarget() returns. + // The Run() method will handle this by looking at mState after + // RunOnTarget() returns. + if (mExecutingRunOnTarget) { + return; } + + // Otherwise we are in an asynchronous resolve. And must perform a thread + // bounce to run on the target thread again. + MOZ_ALWAYS_TRUE(NS_SUCCEEDED( + mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL))); } private: ~ActionRunnable() { MOZ_ASSERT(mState == STATE_COMPLETE); MOZ_ASSERT(!mContext); MOZ_ASSERT(!mAction); } void Clear() { NS_ASSERT_OWNINGTHREAD(Action::Resolver); MOZ_ASSERT(mContext); MOZ_ASSERT(mAction); - mContext->OnActionRunnableComplete(this); + mContext->RemoveActivity(this); mContext = nullptr; mAction = nullptr; } enum State { STATE_INIT, STATE_RUN_ON_TARGET, STATE_RUNNING, + STATE_RESOLVING, STATE_COMPLETING, STATE_COMPLETE }; nsRefPtr<Context> mContext; nsCOMPtr<nsIEventTarget> mTarget; nsRefPtr<Action> mAction; const QuotaInfo mQuotaInfo; nsCOMPtr<nsIThread> mInitiatingThread; State mState; nsresult mResult; + // Only accessible on target thread; + bool mExecutingRunOnTarget; + public: - NS_DECL_ISUPPORTS_INHERITED + NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIRUNNABLE }; -NS_IMPL_ISUPPORTS_INHERITED(mozilla::dom::cache::Context::ActionRunnable, - Action::Resolver, nsIRunnable); +NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::ActionRunnable, nsIRunnable); // The ActionRunnable has a simpler state machine. It basically needs to run // the action on the target thread and then complete on the original thread. // // +-------------+ // | Start | // |(Orig Thread)| // +-----+-------+ // | // +-------v---------+ // | RunOnTarget | -// |Target IO Thread)+-------------------------------+ -// +-------+---------+ | -// | | -// +-------v----------+ Resolve() +-------v-----+ -// | Running | | Completing | +// |Target IO Thread)+---+ Resolve() +// +-------+---------+ | +// | | +// +-------v----------+ | +// | Running | | +// |(Target IO Thread)| | +// +------------------+ | +// | Resolve() | +// +-------v----------+ | +// | Resolving <--+ +-------------+ +// | | | Completing | // |(Target IO Thread)+---------------------->(Orig Thread)| // +------------------+ +-------+-----+ // | // | // +----v---+ // |Complete| // +--------+ // @@ -452,18 +519,49 @@ NS_IMPL_ISUPPORTS_INHERITED(mozilla::dom NS_IMETHODIMP Context::ActionRunnable::Run() { switch(mState) { // ---------------------- case STATE_RUN_ON_TARGET: { MOZ_ASSERT(NS_GetCurrentThread() == mTarget); + MOZ_ASSERT(!mExecutingRunOnTarget); + + // Note that we are calling RunOnTarget(). This lets us detect + // if Resolve() is called synchronously. + AutoRestore<bool> executingRunOnTarget(mExecutingRunOnTarget); + mExecutingRunOnTarget = true; + mState = STATE_RUNNING; mAction->RunOnTarget(this, mQuotaInfo); + + // Resolve was called synchronously from RunOnTarget(). We can + // immediately move to completing now since we are sure RunOnTarget() + // completed. + if (mState == STATE_RESOLVING) { + // Use recursion instead of switch case fall-through... Seems slightly + // easier to understand. + Run(); + } + + break; + } + // ----------------- + case STATE_RESOLVING: + { + MOZ_ASSERT(NS_GetCurrentThread() == mTarget); + // The call to Action::RunOnTarget() must have returned now if we + // are running on the target thread again. We may now proceed + // with completion. + mState = STATE_COMPLETING; + // Shutdown must be delayed until all Contexts are destroyed. Crash + // for this invariant violation. + MOZ_ALWAYS_TRUE(NS_SUCCEEDED( + mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL))); break; } // ------------------- case STATE_COMPLETING: { NS_ASSERT_OWNINGTHREAD(Action::Resolver); mAction->CompleteOnInitiatingThread(mResult); mState = STATE_COMPLETE; @@ -477,25 +575,117 @@ Context::ActionRunnable::Run() { MOZ_CRASH("unexpected state in ActionRunnable"); break; } } return NS_OK; } +void +Context::ThreadsafeHandle::AllowToClose() +{ + if (mOwningThread == NS_GetCurrentThread()) { + AllowToCloseOnOwningThread(); + return; + } + + // Dispatch is guaranteed to succeed here because we block shutdown until + // all Contexts have been destroyed. + nsCOMPtr<nsIRunnable> runnable = + NS_NewRunnableMethod(this, &ThreadsafeHandle::AllowToCloseOnOwningThread); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED( + mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL))); +} + +void +Context::ThreadsafeHandle::InvalidateAndAllowToClose() +{ + if (mOwningThread == NS_GetCurrentThread()) { + InvalidateAndAllowToCloseOnOwningThread(); + return; + } + + // Dispatch is guaranteed to succeed here because we block shutdown until + // all Contexts have been destroyed. + nsCOMPtr<nsIRunnable> runnable = + NS_NewRunnableMethod(this, &ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED( + mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL))); +} + +Context::ThreadsafeHandle::ThreadsafeHandle(Context* aContext) + : mStrongRef(aContext) + , mWeakRef(aContext) + , mOwningThread(NS_GetCurrentThread()) +{ +} + +Context::ThreadsafeHandle::~ThreadsafeHandle() +{ + // Normally we only touch mStrongRef on the owning thread. This is safe, + // however, because when we do use mStrongRef on the owning thread we are + // always holding a strong ref to the ThreadsafeHandle via the owning + // runnable. So we cannot run the ThreadsafeHandle destructor simultaneously. + if (!mStrongRef || mOwningThread == NS_GetCurrentThread()) { + return; + } + + // Dispatch is guaranteed to succeed here because we block shutdown until + // all Contexts have been destroyed. + nsCOMPtr<nsIRunnable> runnable = + NS_NewNonOwningRunnableMethod(mStrongRef.forget().take(), &Context::Release); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED( + mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL))); +} + +void +Context::ThreadsafeHandle::AllowToCloseOnOwningThread() +{ + MOZ_ASSERT(mOwningThread == NS_GetCurrentThread()); + // A Context "closes" when its ref count drops to zero. Dropping this + // strong ref is necessary, but not sufficient for the close to occur. + // Any outstanding IO will continue and keep the Context alive. Once + // the Context is idle, it will be destroyed. + mStrongRef = nullptr; +} + +void +Context::ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread() +{ + MOZ_ASSERT(mOwningThread == NS_GetCurrentThread()); + // Cancel the Context through the weak reference. This means we can + // allow the Context to close by dropping the strong ref, but then + // still cancel ongoing IO if necessary. + if (mWeakRef) { + mWeakRef->Invalidate(); + } + // We should synchronously have AllowToCloseOnOwningThread called when + // the Context is canceled. + MOZ_ASSERT(!mStrongRef); +} + +void +Context::ThreadsafeHandle::ContextDestroyed(Context* aContext) +{ + MOZ_ASSERT(mOwningThread == NS_GetCurrentThread()); + MOZ_ASSERT(!mStrongRef); + MOZ_ASSERT(mWeakRef); + MOZ_ASSERT(mWeakRef == aContext); + mWeakRef = nullptr; +} + // static already_AddRefed<Context> Context::Create(Manager* aManager, Action* aQuotaIOThreadAction) { nsRefPtr<Context> context = new Context(aManager); nsRefPtr<QuotaInitRunnable> runnable = - new QuotaInitRunnable(context, aManager, NS_LITERAL_CSTRING("Cache"), - aQuotaIOThreadAction); + new QuotaInitRunnable(context, aManager, aQuotaIOThreadAction); nsresult rv = runnable->Dispatch(); if (NS_FAILED(rv)) { // Shutdown must be delayed until all Contexts are destroyed. Shutdown // must also prevent any new Contexts from being constructed. Crash // for this invariant violation. MOZ_CRASH("Failed to dispatch QuotaInitRunnable."); } @@ -530,52 +720,71 @@ Context::Dispatch(nsIEventTarget* aTarge } void Context::CancelAll() { NS_ASSERT_OWNINGTHREAD(Context); mState = STATE_CONTEXT_CANCELED; mPendingActions.Clear(); - for (uint32_t i = 0; i < mActionRunnables.Length(); ++i) { - nsRefPtr<ActionRunnable> runnable = mActionRunnables[i]; - runnable->Cancel(); + { + ActivityList::ForwardIterator iter(mActivityList); + while (iter.HasMore()) { + iter.GetNext()->Cancel(); + } + } + AllowToClose(); +} + +void +Context::Invalidate() +{ + NS_ASSERT_OWNINGTHREAD(Context); + mManager->Invalidate(); + CancelAll(); +} + +void +Context::AllowToClose() +{ + NS_ASSERT_OWNINGTHREAD(Context); + if (mThreadsafeHandle) { + mThreadsafeHandle->AllowToClose(); } } void Context::CancelForCacheId(CacheId aCacheId) { NS_ASSERT_OWNINGTHREAD(Context); - for (uint32_t i = 0; i < mPendingActions.Length(); ++i) { + + // Remove matching pending actions + for (int32_t i = mPendingActions.Length() - 1; i >= 0; --i) { if (mPendingActions[i].mAction->MatchesCacheId(aCacheId)) { mPendingActions.RemoveElementAt(i); } } - for (uint32_t i = 0; i < mActionRunnables.Length(); ++i) { - nsRefPtr<ActionRunnable> runnable = mActionRunnables[i]; - if (runnable->MatchesCacheId(aCacheId)) { - runnable->Cancel(); + + // Cancel activities and let them remove themselves + ActivityList::ForwardIterator iter(mActivityList); + while (iter.HasMore()) { + Activity* activity = iter.GetNext(); + if (activity->MatchesCacheId(aCacheId)) { + activity->Cancel(); } } } Context::~Context() { NS_ASSERT_OWNINGTHREAD(Context); MOZ_ASSERT(mManager); - // Unlock the quota dir as we go out of scope. - nsCOMPtr<nsIRunnable> runnable = - new QuotaReleaseRunnable(mQuotaInfo, NS_LITERAL_CSTRING("Cache")); - nsresult rv = NS_DispatchToMainThread(runnable, nsIThread::DISPATCH_NORMAL); - if (NS_FAILED(rv)) { - // Shutdown must be delayed until all Contexts are destroyed. Crash - // for this invariant violation. - MOZ_CRASH("Failed to dispatch QuotaReleaseRunnable to main thread."); + if (mThreadsafeHandle) { + mThreadsafeHandle->ContextDestroyed(this); } mManager->RemoveContext(this); } void Context::DispatchAction(nsIEventTarget* aTarget, Action* aAction) { @@ -584,47 +793,75 @@ Context::DispatchAction(nsIEventTarget* nsRefPtr<ActionRunnable> runnable = new ActionRunnable(this, aTarget, aAction, mQuotaInfo); nsresult rv = runnable->Dispatch(); if (NS_FAILED(rv)) { // Shutdown must be delayed until all Contexts are destroyed. Crash // for this invariant violation. MOZ_CRASH("Failed to dispatch ActionRunnable to target thread."); } - mActionRunnables.AppendElement(runnable); + AddActivity(runnable); } void -Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo) +Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo, + nsMainThreadPtrHandle<OfflineStorage>& aOfflineStorage) { NS_ASSERT_OWNINGTHREAD(Context); mQuotaInfo = aQuotaInfo; + // Always save the offline storage to ensure QuotaManager does not shutdown + // before the Context has gone away. + MOZ_ASSERT(!mOfflineStorage); + mOfflineStorage = aOfflineStorage; + if (mState == STATE_CONTEXT_CANCELED || NS_FAILED(aRv)) { for (uint32_t i = 0; i < mPendingActions.Length(); ++i) { mPendingActions[i].mAction->CompleteOnInitiatingThread(aRv); } mPendingActions.Clear(); + mThreadsafeHandle->AllowToClose(); // Context will destruct after return here and last ref is released. return; } MOZ_ASSERT(mState == STATE_CONTEXT_INIT); mState = STATE_CONTEXT_READY; for (uint32_t i = 0; i < mPendingActions.Length(); ++i) { DispatchAction(mPendingActions[i].mTarget, mPendingActions[i].mAction); } mPendingActions.Clear(); } void -Context::OnActionRunnableComplete(ActionRunnable* aActionRunnable) +Context::AddActivity(Activity* aActivity) +{ + NS_ASSERT_OWNINGTHREAD(Context); + MOZ_ASSERT(aActivity); + MOZ_ASSERT(!mActivityList.Contains(aActivity)); + mActivityList.AppendElement(aActivity); +} + +void +Context::RemoveActivity(Activity* aActivity) { NS_ASSERT_OWNINGTHREAD(Context); - MOZ_ASSERT(aActionRunnable); - MOZ_ALWAYS_TRUE(mActionRunnables.RemoveElement(aActionRunnable)); + MOZ_ASSERT(aActivity); + MOZ_ALWAYS_TRUE(mActivityList.RemoveElement(aActivity)); + MOZ_ASSERT(!mActivityList.Contains(aActivity)); +} + +already_AddRefed<Context::ThreadsafeHandle> +Context::CreateThreadsafeHandle() +{ + NS_ASSERT_OWNINGTHREAD(Context); + if (!mThreadsafeHandle) { + mThreadsafeHandle = new ThreadsafeHandle(this); + } + nsRefPtr<ThreadsafeHandle> ref = mThreadsafeHandle; + return ref.forget(); } } // namespace cache } // namespace dom } // namespace mozilla
--- a/dom/cache/Context.h +++ b/dom/cache/Context.h @@ -6,71 +6,153 @@ #ifndef mozilla_dom_cache_Context_h #define mozilla_dom_cache_Context_h #include "mozilla/dom/cache/Types.h" #include "nsAutoPtr.h" #include "nsCOMPtr.h" #include "nsISupportsImpl.h" +#include "nsProxyRelease.h" #include "nsString.h" #include "nsTArray.h" +#include "nsTObserverArray.h" class nsIEventTarget; +class nsIThread; namespace mozilla { namespace dom { namespace cache { class Action; class Manager; +class OfflineStorage; // The Context class is RAII-style class for managing IO operations within the // Cache. // // When a Context is created it performs the complicated steps necessary to // initialize the QuotaManager. Action objects dispatched on the Context are // delayed until this initialization is complete. They are then allow to // execute on any specified thread. Once all references to the Context are // gone, then the steps necessary to release the QuotaManager are performed. -// Since pending Action objects reference the Context, this allows overlapping -// IO to opportunistically run without re-initializing the QuotaManager again. +// After initialization the Context holds a self reference, so it will stay +// alive until one of three conditions occur: +// +// 1) The Manager will call Context::AllowToClose() when all of the actors +// have removed themselves as listener. This means an idle context with +// no active DOM objects will close gracefully. +// 2) The QuotaManager invalidates the storage area so it can delete the +// files. In this case the OfflineStorage calls Cache::Invalidate() which +// in turn cancels all existing Action objects and then marks the Manager +// as invalid. +// 3) Browser shutdown occurs and the Manager calls Context::CancelAll(). +// +// In either case, though, the Action objects must be destroyed first to +// allow the Context to be destroyed. // // While the Context performs operations asynchronously on threads, all of // methods in its public interface must be called on the same thread // originally used to create the Context. // // As an invariant, all Context objects must be destroyed before permitting // the "profile-before-change" shutdown event to complete. This is ensured // via the code in ShutdownObserver.cpp. class Context final { public: + // Define a class allowing other threads to hold the Context alive. This also + // allows these other threads to safely close or cancel the Context. + class ThreadsafeHandle final + { + friend class Context; + public: + void AllowToClose(); + void InvalidateAndAllowToClose(); + private: + explicit ThreadsafeHandle(Context* aContext); + ~ThreadsafeHandle(); + + // disallow copying + ThreadsafeHandle(const ThreadsafeHandle&) = delete; + ThreadsafeHandle& operator=(const ThreadsafeHandle&) = delete; + + void AllowToCloseOnOwningThread(); + void InvalidateAndAllowToCloseOnOwningThread(); + + void ContextDestroyed(Context* aContext); + + // Cleared to allow the Context to close. Only safe to access on + // owning thread. + nsRefPtr<Context> mStrongRef; + + // Used to support cancelation even while the Context is already allowed + // to close. Cleared by ~Context() calling ContextDestroyed(). Only + // safe to access on owning thread. + Context* mWeakRef; + + nsCOMPtr<nsIThread> mOwningThread; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(cache::Context::ThreadsafeHandle) + }; + + // Different objects hold references to the Context while some work is being + // performed asynchronously. These objects must implement the Activity + // interface and register themselves with the AddActivity(). When they are + // destroyed they must call RemoveActivity(). This allows the Context to + // cancel any outstanding Activity work when the Context is cancelled. + class Activity + { + public: + virtual void Cancel() = 0; + virtual bool MatchesCacheId(CacheId aCacheId) const = 0; + }; + + // Create a Context attached to the given Manager. The given Action + // will run on the QuotaManager IO thread. Note, this Action must + // be execute synchronously. static already_AddRefed<Context> Create(Manager* aManager, Action* aQuotaIOThreadAction); // Execute given action on the target once the quota manager has been // initialized. // // Only callable from the thread that created the Context. void Dispatch(nsIEventTarget* aTarget, Action* aAction); // Cancel any Actions running or waiting to run. This should allow the // Context to be released and Listener::RemoveContext() will be called // when complete. // // Only callable from the thread that created the Context. void CancelAll(); + // Like CancelAll(), but also marks the Manager as "invalid". + void Invalidate(); + + // Remove any self references and allow the Context to be released when + // there are no more Actions to process. + void AllowToClose(); + // Cancel any Actions running or waiting to run that operate on the given // cache ID. // // Only callable from the thread that created the Context. void CancelForCacheId(CacheId aCacheId); + void AddActivity(Activity* aActivity); + void RemoveActivity(Activity* aActivity); + + const QuotaInfo& + GetQuotaInfo() const + { + return mQuotaInfo; + } + private: class QuotaInitRunnable; class ActionRunnable; enum State { STATE_CONTEXT_INIT, STATE_CONTEXT_READY, @@ -81,26 +163,38 @@ private: { nsCOMPtr<nsIEventTarget> mTarget; nsRefPtr<Action> mAction; }; explicit Context(Manager* aManager); ~Context(); void DispatchAction(nsIEventTarget* aTarget, Action* aAction); - void OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo); - void OnActionRunnableComplete(ActionRunnable* const aAction); + void OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo, + nsMainThreadPtrHandle<OfflineStorage>& aOfflineStorage); + + already_AddRefed<ThreadsafeHandle> + CreateThreadsafeHandle(); nsRefPtr<Manager> mManager; State mState; QuotaInfo mQuotaInfo; nsTArray<PendingAction> mPendingActions; - // weak refs since ~ActionRunnable() removes itself from this list - nsTArray<ActionRunnable*> mActionRunnables; + // Weak refs since activites must remove themselves from this list before + // being destroyed by calling RemoveActivity(). + typedef nsTObserverArray<Activity*> ActivityList; + ActivityList mActivityList; + + // The ThreadsafeHandle may have a strong ref back to us. This creates + // a ref-cycle that keeps the Context alive. The ref-cycle is broken + // when ThreadsafeHandle::AllowToClose() is called. + nsRefPtr<ThreadsafeHandle> mThreadsafeHandle; + + nsMainThreadPtrHandle<OfflineStorage> mOfflineStorage; public: NS_INLINE_DECL_REFCOUNTING(cache::Context) }; } // namespace cache } // namespace dom } // namespace mozilla
--- a/dom/cache/DBSchema.cpp +++ b/dom/cache/DBSchema.cpp @@ -17,18 +17,18 @@ #include "nsCRT.h" #include "nsHttp.h" namespace mozilla { namespace dom { namespace cache { -const int32_t DBSchema::kMaxWipeSchemaVersion = 3; -const int32_t DBSchema::kLatestSchemaVersion = 3; +const int32_t DBSchema::kMaxWipeSchemaVersion = 4; +const int32_t DBSchema::kLatestSchemaVersion = 4; const int32_t DBSchema::kMaxEntriesPerStatement = 255; using mozilla::void_t; // static nsresult DBSchema::CreateSchema(mozIStorageConnection* aConn) { @@ -84,16 +84,17 @@ DBSchema::CreateSchema(mozIStorageConnec "id INTEGER NOT NULL PRIMARY KEY, " "request_method TEXT NOT NULL, " "request_url TEXT NOT NULL, " "request_url_no_query TEXT NOT NULL, " "request_referrer TEXT NOT NULL, " "request_headers_guard INTEGER NOT NULL, " "request_mode INTEGER NOT NULL, " "request_credentials INTEGER NOT NULL, " + "request_cache INTEGER NOT NULL, " "request_body_id TEXT NULL, " "response_type INTEGER NOT NULL, " "response_url TEXT NOT NULL, " "response_status INTEGER NOT NULL, " "response_status_text TEXT NOT NULL, " "response_headers_guard INTEGER NOT NULL, " "response_body_id TEXT NULL, " "response_security_info BLOB NULL, " @@ -965,26 +966,27 @@ DBSchema::InsertEntry(mozIStorageConnect "INSERT INTO entries (" "request_method, " "request_url, " "request_url_no_query, " "request_referrer, " "request_headers_guard, " "request_mode, " "request_credentials, " + "request_cache, " "request_body_id, " "response_type, " "response_url, " "response_status, " "response_status_text, " "response_headers_guard, " "response_body_id, " "response_security_info, " "cache_id " - ") VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)" + ") VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17)" ), getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->BindUTF8StringParameter(0, aRequest.method()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->BindStringParameter(1, aRequest.url()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1001,44 +1003,48 @@ DBSchema::InsertEntry(mozIStorageConnect rv = state->BindInt32Parameter(5, static_cast<int32_t>(aRequest.mode())); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->BindInt32Parameter(6, static_cast<int32_t>(aRequest.credentials())); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = BindId(state, 7, aRequestBodyId); + rv = state->BindInt32Parameter(7, + static_cast<int32_t>(aRequest.requestCache())); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindInt32Parameter(8, static_cast<int32_t>(aResponse.type())); + rv = BindId(state, 8, aRequestBodyId); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = state->BindInt32Parameter(9, static_cast<int32_t>(aResponse.type())); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindStringParameter(9, aResponse.url()); + rv = state->BindStringParameter(10, aResponse.url()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindInt32Parameter(10, aResponse.status()); + rv = state->BindInt32Parameter(11, aResponse.status()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindUTF8StringParameter(11, aResponse.statusText()); + rv = state->BindUTF8StringParameter(12, aResponse.statusText()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindInt32Parameter(12, + rv = state->BindInt32Parameter(13, static_cast<int32_t>(aResponse.headersGuard())); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = BindId(state, 13, aResponseBodyId); + rv = BindId(state, 14, aResponseBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindBlobParameter(14, reinterpret_cast<const uint8_t*> + rv = state->BindBlobParameter(15, reinterpret_cast<const uint8_t*> (aResponse.securityInfo().get()), aResponse.securityInfo().Length()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindInt32Parameter(15, aCacheId); + rv = state->BindInt32Parameter(16, aCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT last_insert_rowid()" ), getter_AddRefs(state)); @@ -1214,16 +1220,17 @@ DBSchema::ReadRequest(mozIStorageConnect "SELECT " "request_method, " "request_url, " "request_url_no_query, " "request_referrer, " "request_headers_guard, " "request_mode, " "request_credentials, " + "request_cache, " "request_body_id " "FROM entries " "WHERE id=?1;" ), getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->BindInt32Parameter(0, aEntryId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1256,23 +1263,29 @@ DBSchema::ReadRequest(mozIStorageConnect aSavedRequestOut->mValue.mode() = static_cast<RequestMode>(mode); int32_t credentials; rv = state->GetInt32(6, &credentials); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } aSavedRequestOut->mValue.credentials() = static_cast<RequestCredentials>(credentials); + int32_t requestCache; + rv = state->GetInt32(7, &requestCache); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + aSavedRequestOut->mValue.requestCache() = + static_cast<RequestCache>(requestCache); + bool nullBody = false; - rv = state->GetIsNull(7, &nullBody); + rv = state->GetIsNull(8, &nullBody); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } aSavedRequestOut->mHasBodyId = !nullBody; if (aSavedRequestOut->mHasBodyId) { - rv = ExtractId(state, 7, &aSavedRequestOut->mBodyId); + rv = ExtractId(state, 8, &aSavedRequestOut->mBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } rv = aConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT " "name, " "value " "FROM request_headers "
--- a/dom/cache/Manager.cpp +++ b/dom/cache/Manager.cpp @@ -172,17 +172,20 @@ public: mozilla::ipc::AssertIsOnBackgroundThread(); nsresult rv = MaybeCreateInstance(); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } ManagerList::ForwardIterator iter(sFactory->mManagerList); while (iter.HasMore()) { nsRefPtr<Manager> manager = iter.GetNext(); - if (*manager->mManagerId == *aManagerId) { + // If there is an invalid Manager finishing up and a new Manager + // is created for the same origin, then the new Manager will + // be blocked until QuotaManager finishes clearing the origin. + if (manager->IsValid() && *manager->mManagerId == *aManagerId) { return manager.forget(); } } return nullptr; } static void @@ -906,25 +909,29 @@ private: mCopyContextList.Clear(); } static void AsyncCopyCompleteFunc(void* aClosure, nsresult aRv) { // May be on any thread, including STS event target. MOZ_ASSERT(aClosure); - nsRefPtr<CachePutAllAction> action = static_cast<CachePutAllAction*>(aClosure); + // Weak ref as we are guaranteed to the action is alive until + // CompleteOnInitiatingThread is called. + CachePutAllAction* action = static_cast<CachePutAllAction*>(aClosure); action->CallOnAsyncCopyCompleteOnTargetThread(aRv); } void CallOnAsyncCopyCompleteOnTargetThread(nsresult aRv) { - // May be on any thread, including STS event target. - nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableMethodWithArg<nsresult>( + // May be on any thread, including STS event target. Non-owning runnable + // here since we are guaranteed the Action will survive until + // CompleteOnInitiatingThread is called. + nsCOMPtr<nsIRunnable> runnable = NS_NewNonOwningRunnableMethodWithArgs<nsresult>( this, &CachePutAllAction::OnAsyncCopyComplete, aRv); MOZ_ALWAYS_TRUE(NS_SUCCEEDED( mTargetThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL))); } void DoResolve(nsresult aRv) { @@ -1399,16 +1406,19 @@ void Manager::RemoveListener(Listener* aListener) { NS_ASSERT_OWNINGTHREAD(Manager); // There may not be a listener here in the case where an actor is killed // before it can perform any actual async requests on Manager. mListeners.RemoveElement(aListener, ListenerEntryListenerComparator()); MOZ_ASSERT(!mListeners.Contains(aListener, ListenerEntryListenerComparator())); + if (mListeners.IsEmpty() && mContext) { + mContext->AllowToClose(); + } } void Manager::RemoveContext(Context* aContext) { NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(mContext); MOZ_ASSERT(mContext == aContext); @@ -1417,16 +1427,31 @@ Manager::RemoveContext(Context* aContext // If we're trying to shutdown, then note that we're done. This is the // delayed case from Manager::Shutdown(). if (mShuttingDown) { Factory::Remove(this); } } void +Manager::Invalidate() +{ + NS_ASSERT_OWNINGTHREAD(Manager); + // QuotaManager can trigger this more than once. + mValid = false; +} + +bool +Manager::IsValid() const +{ + NS_ASSERT_OWNINGTHREAD(Manager); + return mValid; +} + +void Manager::AddRefCacheId(CacheId aCacheId) { NS_ASSERT_OWNINGTHREAD(Manager); for (uint32_t i = 0; i < mCacheIdRefs.Length(); ++i) { if (mCacheIdRefs[i].mCacheId == aCacheId) { mCacheIdRefs[i].mCount += 1; return; } @@ -1445,17 +1470,17 @@ Manager::ReleaseCacheId(CacheId aCacheId if (mCacheIdRefs[i].mCacheId == aCacheId) { DebugOnly<uint32_t> oldRef = mCacheIdRefs[i].mCount; mCacheIdRefs[i].mCount -= 1; MOZ_ASSERT(mCacheIdRefs[i].mCount < oldRef); if (mCacheIdRefs[i].mCount == 0) { bool orphaned = mCacheIdRefs[i].mOrphaned; mCacheIdRefs.RemoveElementAt(i); // TODO: note that we need to check this cache for staleness on startup (bug 1110446) - if (orphaned && !mShuttingDown) { + if (orphaned && !mShuttingDown && mValid) { nsRefPtr<Context> context = CurrentContext(); context->CancelForCacheId(aCacheId); nsRefPtr<Action> action = new DeleteOrphanedCacheAction(this, aCacheId); context->Dispatch(mIOThread, action); } } return; @@ -1488,17 +1513,17 @@ Manager::ReleaseBodyId(const nsID& aBody if (mBodyIdRefs[i].mBodyId == aBodyId) { DebugOnly<uint32_t> oldRef = mBodyIdRefs[i].mCount; mBodyIdRefs[i].mCount -= 1; MOZ_ASSERT(mBodyIdRefs[i].mCount < oldRef); if (mBodyIdRefs[i].mCount < 1) { bool orphaned = mBodyIdRefs[i].mOrphaned; mBodyIdRefs.RemoveElementAt(i); // TODO: note that we need to check this body for staleness on startup (bug 1110446) - if (orphaned && !mShuttingDown) { + if (orphaned && !mShuttingDown && mValid) { nsRefPtr<Action> action = new DeleteOrphanedBodyAction(aBodyId); nsRefPtr<Context> context = CurrentContext(); context->Dispatch(mIOThread, action); } } return; } } @@ -1530,19 +1555,18 @@ Manager::RemoveStreamList(StreamList* aS void Manager::CacheMatch(Listener* aListener, RequestId aRequestId, CacheId aCacheId, const PCacheRequest& aRequest, const PCacheQueryParams& aParams) { NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(aListener); - if (mShuttingDown) { - aListener->OnCacheMatch(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, - nullptr, nullptr); + if (mShuttingDown || !mValid) { + aListener->OnCacheMatch(aRequestId, NS_ERROR_FAILURE, nullptr, nullptr); return; } nsRefPtr<Context> context = CurrentContext(); nsRefPtr<StreamList> streamList = new StreamList(this, context); ListenerId listenerId = SaveListener(aListener); nsRefPtr<Action> action = new CacheMatchAction(this, listenerId, aRequestId, aCacheId, aRequest, aParams, streamList); @@ -1551,18 +1575,18 @@ Manager::CacheMatch(Listener* aListener, void Manager::CacheMatchAll(Listener* aListener, RequestId aRequestId, CacheId aCacheId, const PCacheRequestOrVoid& aRequest, const PCacheQueryParams& aParams) { NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(aListener); - if (mShuttingDown) { - aListener->OnCacheMatchAll(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, + if (mShuttingDown || !mValid) { + aListener->OnCacheMatchAll(aRequestId, NS_ERROR_FAILURE, nsTArray<SavedResponse>(), nullptr); return; } nsRefPtr<Context> context = CurrentContext(); nsRefPtr<StreamList> streamList = new StreamList(this, context); ListenerId listenerId = SaveListener(aListener); nsRefPtr<Action> action = new CacheMatchAllAction(this, listenerId, aRequestId, aCacheId, aRequest, aParams, @@ -1573,18 +1597,18 @@ Manager::CacheMatchAll(Listener* aListen void Manager::CachePutAll(Listener* aListener, RequestId aRequestId, CacheId aCacheId, const nsTArray<CacheRequestResponse>& aPutList, const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList, const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList) { NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(aListener); - if (mShuttingDown) { - aListener->OnCachePutAll(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN); + if (mShuttingDown || !mValid) { + aListener->OnCachePutAll(aRequestId, NS_ERROR_FAILURE); return; } ListenerId listenerId = SaveListener(aListener); nsRefPtr<Action> action = new CachePutAllAction(this, listenerId, aRequestId, aCacheId, aPutList, aRequestStreamList, aResponseStreamList); nsRefPtr<Context> context = CurrentContext(); @@ -1593,36 +1617,36 @@ Manager::CachePutAll(Listener* aListener void Manager::CacheDelete(Listener* aListener, RequestId aRequestId, CacheId aCacheId, const PCacheRequest& aRequest, const PCacheQueryParams& aParams) { NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(aListener); - if (mShuttingDown) { - aListener->OnCacheDelete(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, false); + if (mShuttingDown || !mValid) { + aListener->OnCacheDelete(aRequestId, NS_ERROR_FAILURE, false); return; } ListenerId listenerId = SaveListener(aListener); nsRefPtr<Action> action = new CacheDeleteAction(this, listenerId, aRequestId, aCacheId, aRequest, aParams); nsRefPtr<Context> context = CurrentContext(); context->Dispatch(mIOThread, action); } void Manager::CacheKeys(Listener* aListener, RequestId aRequestId, CacheId aCacheId, const PCacheRequestOrVoid& aRequestOrVoid, const PCacheQueryParams& aParams) { NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(aListener); - if (mShuttingDown) { - aListener->OnCacheKeys(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, + if (mShuttingDown || !mValid) { + aListener->OnCacheKeys(aRequestId, NS_ERROR_FAILURE, nsTArray<SavedRequest>(), nullptr); return; } nsRefPtr<Context> context = CurrentContext(); nsRefPtr<StreamList> streamList = new StreamList(this, context); ListenerId listenerId = SaveListener(aListener); nsRefPtr<Action> action = new CacheKeysAction(this, listenerId, aRequestId, aCacheId, aRequestOrVoid, @@ -1632,18 +1656,18 @@ Manager::CacheKeys(Listener* aListener, void Manager::StorageMatch(Listener* aListener, RequestId aRequestId, Namespace aNamespace, const PCacheRequest& aRequest, const PCacheQueryParams& aParams) { NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(aListener); - if (mShuttingDown) { - aListener->OnStorageMatch(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, + if (mShuttingDown || !mValid) { + aListener->OnStorageMatch(aRequestId, NS_ERROR_FAILURE, nullptr, nullptr); return; } nsRefPtr<Context> context = CurrentContext(); nsRefPtr<StreamList> streamList = new StreamList(this, context); ListenerId listenerId = SaveListener(aListener); nsRefPtr<Action> action = new StorageMatchAction(this, listenerId, aRequestId, aNamespace, aRequest, @@ -1652,86 +1676,87 @@ Manager::StorageMatch(Listener* aListene } void Manager::StorageHas(Listener* aListener, RequestId aRequestId, Namespace aNamespace, const nsAString& aKey) { NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(aListener); - if (mShuttingDown) { - aListener->OnStorageHas(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, + if (mShuttingDown || !mValid) { + aListener->OnStorageHas(aRequestId, NS_ERROR_FAILURE, false); return; } ListenerId listenerId = SaveListener(aListener); nsRefPtr<Action> action = new StorageHasAction(this, listenerId, aRequestId, aNamespace, aKey); nsRefPtr<Context> context = CurrentContext(); context->Dispatch(mIOThread, action); } void Manager::StorageOpen(Listener* aListener, RequestId aRequestId, Namespace aNamespace, const nsAString& aKey) { NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(aListener); - if (mShuttingDown) { - aListener->OnStorageOpen(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, 0); + if (mShuttingDown || !mValid) { + aListener->OnStorageOpen(aRequestId, NS_ERROR_FAILURE, 0); return; } ListenerId listenerId = SaveListener(aListener); nsRefPtr<Action> action = new StorageOpenAction(this, listenerId, aRequestId, aNamespace, aKey); nsRefPtr<Context> context = CurrentContext(); context->Dispatch(mIOThread, action); } void Manager::StorageDelete(Listener* aListener, RequestId aRequestId, Namespace aNamespace, const nsAString& aKey) { NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(aListener); - if (mShuttingDown) { - aListener->OnStorageDelete(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, + if (mShuttingDown || !mValid) { + aListener->OnStorageDelete(aRequestId, NS_ERROR_FAILURE, false); return; } ListenerId listenerId = SaveListener(aListener); nsRefPtr<Action> action = new StorageDeleteAction(this, listenerId, aRequestId, aNamespace, aKey); nsRefPtr<Context> context = CurrentContext(); context->Dispatch(mIOThread, action); } void Manager::StorageKeys(Listener* aListener, RequestId aRequestId, Namespace aNamespace) { NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(aListener); - if (mShuttingDown) { - aListener->OnStorageKeys(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, + if (mShuttingDown || !mValid) { + aListener->OnStorageKeys(aRequestId, NS_ERROR_FAILURE, nsTArray<nsString>()); return; } ListenerId listenerId = SaveListener(aListener); nsRefPtr<Action> action = new StorageKeysAction(this, listenerId, aRequestId, aNamespace); nsRefPtr<Context> context = CurrentContext(); context->Dispatch(mIOThread, action); } Manager::Manager(ManagerId* aManagerId, nsIThread* aIOThread) : mManagerId(aManagerId) , mIOThread(aIOThread) , mContext(nullptr) , mShuttingDown(false) + , mValid(true) { MOZ_ASSERT(mManagerId); MOZ_ASSERT(mIOThread); } Manager::~Manager() { NS_ASSERT_OWNINGTHREAD(Manager); @@ -1760,21 +1785,16 @@ Manager::Shutdown() return; } // Set a flag to prevent any new requests from coming in and creating // a new Context. We must ensure all Contexts and IO operations are // complete before shutdown proceeds. mShuttingDown = true; - for (uint32_t i = 0; i < mStreamLists.Length(); ++i) { - nsRefPtr<StreamList> streamList = mStreamLists[i]; - streamList->CloseAll(); - } - // If there is a context, then we must wait for it to complete. Cancel and // only note that we are done after its cleaned up. if (mContext) { nsRefPtr<Context> context = mContext; context->CancelAll(); return; } @@ -1784,16 +1804,17 @@ Manager::Shutdown() already_AddRefed<Context> Manager::CurrentContext() { NS_ASSERT_OWNINGTHREAD(Manager); nsRefPtr<Context> ref = mContext; if (!ref) { MOZ_ASSERT(!mShuttingDown); + MOZ_ASSERT(mValid); nsRefPtr<Action> setupAction = new SetupAction(); ref = Context::Create(this, setupAction); mContext = ref; } return ref.forget(); } Manager::ListenerId
--- a/dom/cache/Manager.h +++ b/dom/cache/Manager.h @@ -120,16 +120,21 @@ public: static void ShutdownAllOnMainThread(); // Must be called by Listener objects before they are destroyed. void RemoveListener(Listener* aListener); // Must be called by Context objects before they are destroyed. void RemoveContext(Context* aContext); + // Marks the Manager "invalid". Once the Context completes no new operations + // will be permitted with this Manager. New actors will get a new Manager. + void Invalidate(); + bool IsValid() const; + // If an actor represents a long term reference to a cache or body stream, // then they must call AddRefCacheId() or AddRefBodyId(). This will // cause the Manager to keep the backing data store alive for the given // object. The actor must then call ReleaseCacheId() or ReleaseBodyId() // exactly once for every AddRef*() call it made. Any delayed deletion // will then be performed. void AddRefCacheId(CacheId aCacheId); void ReleaseCacheId(CacheId aCacheId); @@ -209,18 +214,18 @@ private: // Weak reference cleared by RemoveContext() in Context destructor. Context* MOZ_NON_OWNING_REF mContext; // Weak references cleared by RemoveListener() in Listener destructors. struct ListenerEntry { ListenerEntry() - : mId(UINT64_MAX), - mListener(nullptr) + : mId(UINT64_MAX) + , mListener(nullptr) { } ListenerEntry(ListenerId aId, Listener* aListener) : mId(aId) , mListener(aListener) { } @@ -250,16 +255,17 @@ private: typedef nsTArray<ListenerEntry> ListenerList; ListenerList mListeners; static ListenerId sNextListenerId; // Weak references cleared by RemoveStreamList() in StreamList destructors. nsTArray<StreamList*> mStreamLists; bool mShuttingDown; + bool mValid; struct CacheIdRefCounter { CacheId mCacheId; MozRefCountType mCount; bool mOrphaned; }; nsTArray<CacheIdRefCounter> mCacheIdRefs;
new file mode 100644 --- /dev/null +++ b/dom/cache/OfflineStorage.cpp @@ -0,0 +1,135 @@ +/* -*- 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/. */ + +#include "mozilla/dom/cache/OfflineStorage.h" + +#include "mozilla/dom/cache/Context.h" +#include "mozilla/dom/cache/QuotaClient.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace dom { +namespace cache { + +using mozilla::dom::quota::Client; +using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT; +using mozilla::dom::quota::QuotaManager; + +NS_IMPL_ISUPPORTS(OfflineStorage, nsIOfflineStorage); + +// static +already_AddRefed<OfflineStorage> +OfflineStorage::Register(Context::ThreadsafeHandle* aContext, + const QuotaInfo& aQuotaInfo) +{ + MOZ_ASSERT(NS_IsMainThread()); + + QuotaManager* qm = QuotaManager::Get(); + if (NS_WARN_IF(!qm)) { + return nullptr; + } + + nsRefPtr<Client> client = qm->GetClient(Client::DOMCACHE); + + nsRefPtr<OfflineStorage> storage = + new OfflineStorage(aContext, aQuotaInfo, client); + + if (NS_WARN_IF(!qm->RegisterStorage(storage))) { + return nullptr; + } + + return storage.forget(); +} + +void +OfflineStorage::AddDestroyCallback(nsIRunnable* aCallback) +{ + MOZ_ASSERT(aCallback); + MOZ_ASSERT(!mDestroyCallbacks.Contains(aCallback)); + mDestroyCallbacks.AppendElement(aCallback); +} + +OfflineStorage::OfflineStorage(Context::ThreadsafeHandle* aContext, + const QuotaInfo& aQuotaInfo, + Client* aClient) + : mContext(aContext) + , mQuotaInfo(aQuotaInfo) + , mClient(aClient) +{ + MOZ_ASSERT(mContext); + MOZ_ASSERT(mClient); + + mPersistenceType = PERSISTENCE_TYPE_DEFAULT; + mGroup = mQuotaInfo.mGroup; +} + +OfflineStorage::~OfflineStorage() +{ + MOZ_ASSERT(NS_IsMainThread()); + QuotaManager* qm = QuotaManager::Get(); + MOZ_ASSERT(qm); + qm->UnregisterStorage(this); + for (uint32_t i = 0; i < mDestroyCallbacks.Length(); ++i) { + mDestroyCallbacks[i]->Run(); + } +} + +NS_IMETHODIMP_(const nsACString&) +OfflineStorage::Id() +{ + MOZ_ASSERT(NS_IsMainThread()); + return mQuotaInfo.mStorageId; +} + +NS_IMETHODIMP_(Client*) +OfflineStorage::GetClient() +{ + MOZ_ASSERT(NS_IsMainThread()); + return mClient; +} + +NS_IMETHODIMP_(bool) +OfflineStorage::IsOwnedByProcess(ContentParent* aOwner) +{ + MOZ_ASSERT(NS_IsMainThread()); + // The Cache and Context can be shared by multiple client processes. They + // are not exclusively owned by a single process. + // + // As far as I can tell this is used by QuotaManager to shutdown storages + // when a particular process goes away. We definitely don't want this + // since we are shared. Also, the Cache actor code already properly + // handles asynchronous actor destruction when the child process dies. + // + // Therefore, always return false here. + return false; +} + +NS_IMETHODIMP_(const nsACString&) +OfflineStorage::Origin() +{ + MOZ_ASSERT(NS_IsMainThread()); + return mQuotaInfo.mOrigin; +} + +NS_IMETHODIMP_(nsresult) +OfflineStorage::Close() +{ + MOZ_ASSERT(NS_IsMainThread()); + mContext->AllowToClose(); + return NS_OK; +} + +NS_IMETHODIMP_(void) +OfflineStorage::Invalidate() +{ + MOZ_ASSERT(NS_IsMainThread()); + mContext->InvalidateAndAllowToClose(); +} + +} // namespace cache +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/cache/OfflineStorage.h @@ -0,0 +1,50 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_cache_QuotaOfflineStorage_h +#define mozilla_dom_cache_QuotaOfflineStorage_h + +#include "nsISupportsImpl.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/dom/cache/Context.h" +#include "nsIOfflineStorage.h" +#include "nsTArray.h" + +class nsIThread; + +namespace mozilla { +namespace dom { +namespace cache { + +class OfflineStorage final : public nsIOfflineStorage +{ +public: + static already_AddRefed<OfflineStorage> + Register(Context::ThreadsafeHandle* aContext, const QuotaInfo& aQuotaInfo); + + void + AddDestroyCallback(nsIRunnable* aCallback); + +private: + OfflineStorage(Context::ThreadsafeHandle* aContext, + const QuotaInfo& aQuotaInfo, + Client* aClient); + ~OfflineStorage(); + + nsRefPtr<Context::ThreadsafeHandle> mContext; + const QuotaInfo mQuotaInfo; + nsRefPtr<Client> mClient; + nsTArray<nsCOMPtr<nsIRunnable>> mDestroyCallbacks; + + NS_DECL_ISUPPORTS + NS_DECL_NSIOFFLINESTORAGE +}; + +} // namespace cache +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_cache_QuotaOfflineStorage_h
--- a/dom/cache/PCacheTypes.ipdlh +++ b/dom/cache/PCacheTypes.ipdlh @@ -5,16 +5,17 @@ include protocol PCachePushStream; include protocol PCacheStreamControl; include PHeaders; include InputStreamParams; using HeadersGuardEnum from "mozilla/dom/FetchIPCUtils.h"; using RequestCredentials from "mozilla/dom/FetchIPCUtils.h"; using RequestMode from "mozilla/dom/FetchIPCUtils.h"; +using RequestCache from "mozilla/dom/FetchIPCUtils.h"; using mozilla::dom::ResponseType from "mozilla/dom/FetchIPCUtils.h"; using mozilla::void_t from "ipc/IPCMessageUtils.h"; using struct nsID from "nsID.h"; namespace mozilla { namespace dom { namespace cache { @@ -49,16 +50,17 @@ struct PCacheRequest nsString urlWithoutQuery; PHeadersEntry[] headers; HeadersGuardEnum headersGuard; nsString referrer; RequestMode mode; RequestCredentials credentials; PCacheReadStreamOrVoid body; uint32_t context; + RequestCache requestCache; }; union PCacheRequestOrVoid { void_t; PCacheRequest; };
--- a/dom/cache/QuotaClient.cpp +++ b/dom/cache/QuotaClient.cpp @@ -3,26 +3,28 @@ /* 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/. */ #include "mozilla/dom/cache/QuotaClient.h" #include "mozilla/DebugOnly.h" #include "mozilla/dom/cache/Manager.h" +#include "mozilla/dom/cache/OfflineStorage.h" #include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/quota/UsageInfo.h" #include "nsIFile.h" #include "nsISimpleEnumerator.h" #include "nsThreadUtils.h" namespace { using mozilla::DebugOnly; using mozilla::dom::cache::Manager; +using mozilla::dom::cache::OfflineStorage; using mozilla::dom::quota::Client; using mozilla::dom::quota::PersistenceType; using mozilla::dom::quota::QuotaManager; using mozilla::dom::quota::UsageInfo; static nsresult GetBodyUsage(nsIFile* aDir, UsageInfo* aUsageInfo) { @@ -55,16 +57,51 @@ GetBodyUsage(nsIFile* aDir, UsageInfo* a MOZ_ASSERT(fileSize >= 0); aUsageInfo->AppendToFileUsage(fileSize); } return NS_OK; } +class StoragesDestroyedRunnable final : public nsRunnable +{ + uint32_t mExpectedCalls; + nsCOMPtr<nsIRunnable> mCallback; + +public: + StoragesDestroyedRunnable(uint32_t aExpectedCalls, nsIRunnable* aCallback) + : mExpectedCalls(aExpectedCalls) + , mCallback(aCallback) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mExpectedCalls); + MOZ_ASSERT(mCallback); + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mExpectedCalls); + mExpectedCalls -= 1; + if (!mExpectedCalls) { + mCallback->Run(); + } + return NS_OK; + } + +private: + ~StoragesDestroyedRunnable() + { + // This is a callback runnable and not used for thread dispatch. It should + // always be destroyed on the main thread. + MOZ_ASSERT(NS_IsMainThread()); + } +}; + class CacheQuotaClient final : public Client { public: virtual Type GetType() override { return DOMCACHE; } @@ -146,59 +183,73 @@ public: return NS_OK; } virtual void OnOriginClearCompleted(PersistenceType aPersistenceType, const nsACString& aOrigin) override { - // nothing to do + // Nothing to do here. } virtual void ReleaseIOThreadObjects() override { - // nothing to do + // Nothing to do here as the Context handles cleaning everything up + // automatically. } virtual bool IsFileServiceUtilized() override { return false; } virtual bool IsTransactionServiceActivated() override { - // TODO: implement nsIOfflineStorage interface (bug 1110487) - return false; + return true; } virtual void WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages, nsIRunnable* aCallback) override { - // TODO: implement nsIOfflineStorage interface (bug 1110487) + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aStorages.IsEmpty()); + + nsCOMPtr<nsIRunnable> callback = + new StoragesDestroyedRunnable(aStorages.Length(), aCallback); + + for (uint32_t i = 0; i < aStorages.Length(); ++i) { + MOZ_ASSERT(aStorages[i]->GetClient()); + MOZ_ASSERT(aStorages[i]->GetClient()->GetType() == Client::DOMCACHE); + nsRefPtr<OfflineStorage> storage = + static_cast<OfflineStorage*>(aStorages[i]); + storage->AddDestroyCallback(callback); + } } virtual void ShutdownTransactionService() override { MOZ_ASSERT(NS_IsMainThread()); // spins the event loop and synchronously shuts down all Managers Manager::ShutdownAllOnMainThread(); } private: - ~CacheQuotaClient() { } + ~CacheQuotaClient() + { + MOZ_ASSERT(NS_IsMainThread()); + } -public: NS_INLINE_DECL_REFCOUNTING(CacheQuotaClient, override) }; } // anonymous namespace; namespace mozilla { namespace dom { namespace cache {
--- a/dom/cache/QuotaClient.h +++ b/dom/cache/QuotaClient.h @@ -9,15 +9,16 @@ #include "mozilla/Attributes.h" #include "mozilla/dom/quota/Client.h" namespace mozilla { namespace dom { namespace cache { -already_AddRefed<quota::Client> CreateQuotaClient(); +already_AddRefed<quota::Client> +CreateQuotaClient(); } // namespace cache } // namespace dom } // namespace mozilla #endif // mozilla_dom_cache_QuotaClient_h
--- a/dom/cache/StreamList.cpp +++ b/dom/cache/StreamList.cpp @@ -18,17 +18,17 @@ namespace cache { StreamList::StreamList(Manager* aManager, Context* aContext) : mManager(aManager) , mContext(aContext) , mCacheId(0) , mStreamControl(nullptr) , mActivated(false) { MOZ_ASSERT(mManager); - MOZ_ASSERT(mContext); + mContext->AddActivity(this); } void StreamList::SetStreamControl(CacheStreamControlParent* aStreamControl) { NS_ASSERT_OWNINGTHREAD(StreamList); MOZ_ASSERT(aStreamControl); @@ -137,24 +137,39 @@ void StreamList::CloseAll() { NS_ASSERT_OWNINGTHREAD(StreamList); if (mStreamControl) { mStreamControl->CloseAll(); } } +void +StreamList::Cancel() +{ + NS_ASSERT_OWNINGTHREAD(StreamList); + CloseAll(); +} + +bool +StreamList::MatchesCacheId(CacheId aCacheId) const +{ + NS_ASSERT_OWNINGTHREAD(StreamList); + return aCacheId == mCacheId; +} + StreamList::~StreamList() { NS_ASSERT_OWNINGTHREAD(StreamList); MOZ_ASSERT(!mStreamControl); if (mActivated) { mManager->RemoveStreamList(this); for (uint32_t i = 0; i < mList.Length(); ++i) { mManager->ReleaseBodyId(mList[i].mId); } mManager->ReleaseCacheId(mCacheId); } + mContext->RemoveActivity(this); } } // namespace cache } // namespace dom } // namespace mozilla
--- a/dom/cache/StreamList.h +++ b/dom/cache/StreamList.h @@ -2,31 +2,31 @@ /* 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/. */ #ifndef mozilla_dom_cache_StreamList_h #define mozilla_dom_cache_StreamList_h +#include "mozilla/dom/cache/Context.h" #include "mozilla/dom/cache/Types.h" #include "nsRefPtr.h" #include "nsTArray.h" class nsIInputStream; namespace mozilla { namespace dom { namespace cache { class CacheStreamControlParent; -class Context; class Manager; -class StreamList final +class StreamList final : public Context::Activity { public: StreamList(Manager* aManager, Context* aContext); void SetStreamControl(CacheStreamControlParent* aStreamControl); void RemoveStreamControl(CacheStreamControlParent* aStreamControl); void Activate(CacheId aCacheId); @@ -34,16 +34,20 @@ public: void Add(const nsID& aId, nsIInputStream* aStream); already_AddRefed<nsIInputStream> Extract(const nsID& aId); void NoteClosed(const nsID& aId); void NoteClosedAll(); void Close(const nsID& aId); void CloseAll(); + // Context::Activity methods + virtual void Cancel() override; + virtual bool MatchesCacheId(CacheId aCacheId) const override; + private: ~StreamList(); struct Entry { nsID mId; nsCOMPtr<nsIInputStream> mStream; }; nsRefPtr<Manager> mManager;
--- a/dom/cache/TypeUtils.cpp +++ b/dom/cache/TypeUtils.cpp @@ -220,16 +220,17 @@ TypeUtils::ToPCacheRequest(PCacheRequest nsRefPtr<InternalHeaders> headers = aIn->Headers(); MOZ_ASSERT(headers); headers->GetPHeaders(aOut.headers()); aOut.headersGuard() = headers->Guard(); aOut.mode() = aIn->Mode(); aOut.credentials() = aIn->GetCredentialsMode(); aOut.context() = aIn->ContentPolicyType(); + aOut.requestCache() = aIn->GetCacheMode(); if (aBodyAction == IgnoreBody) { aOut.body() = void_t(); return; } // BodyUsed flag is checked and set previously in ToInternalRequest() @@ -362,16 +363,17 @@ TypeUtils::ToInternalRequest(const PCach nsRefPtr<InternalRequest> internalRequest = new InternalRequest(); internalRequest->SetMethod(aIn.method()); internalRequest->SetURL(NS_ConvertUTF16toUTF8(aIn.url())); internalRequest->SetReferrer(aIn.referrer()); internalRequest->SetMode(aIn.mode()); internalRequest->SetCredentialsMode(aIn.credentials()); internalRequest->SetContentPolicyType(aIn.context()); + internalRequest->SetCacheMode(aIn.requestCache()); nsRefPtr<InternalHeaders> internalHeaders = new InternalHeaders(aIn.headers(), aIn.headersGuard()); ErrorResult result; internalRequest->Headers()->SetGuard(aIn.headersGuard(), result); MOZ_ASSERT(!result.Failed()); internalRequest->Headers()->Fill(*internalHeaders, result); MOZ_ASSERT(!result.Failed());
--- a/dom/cache/Types.h +++ b/dom/cache/Types.h @@ -29,16 +29,17 @@ static const RequestId INVALID_REQUEST_I typedef int32_t CacheId; struct QuotaInfo { QuotaInfo() : mIsApp(false) { } nsCOMPtr<nsIFile> mDir; nsCString mGroup; nsCString mOrigin; + nsCString mStorageId; bool mIsApp; }; } // namespace cache } // namespace dom } // namespace mozilla #endif // mozilla_dom_cache_Types_h
--- a/dom/cache/moz.build +++ b/dom/cache/moz.build @@ -23,16 +23,17 @@ EXPORTS.mozilla.dom.cache += [ 'DBAction.h', 'DBSchema.h', 'Feature.h', 'FetchPut.h', 'FileUtils.h', 'IPCUtils.h', 'Manager.h', 'ManagerId.h', + 'OfflineStorage.h', 'PrincipalVerifier.h', 'QuotaClient.h', 'ReadStream.h', 'SavedTypes.h', 'StreamControl.h', 'StreamList.h', 'StreamUtils.h', 'Types.h', @@ -56,16 +57,17 @@ UNIFIED_SOURCES += [ 'Context.cpp', 'DBAction.cpp', 'DBSchema.cpp', 'Feature.cpp', 'FetchPut.cpp', 'FileUtils.cpp', 'Manager.cpp', 'ManagerId.cpp', + 'OfflineStorage.cpp', 'PrincipalVerifier.cpp', 'QuotaClient.cpp', 'ReadStream.cpp', 'StreamControl.cpp', 'StreamList.cpp', 'StreamUtils.cpp', 'TypeUtils.cpp', ]
--- a/dom/cache/test/mochitest/driver.js +++ b/dom/cache/test/mochitest/driver.js @@ -25,16 +25,32 @@ function runTests(testFile, order) { ["dom.serviceWorkers.testing.enabled", true], ["dom.serviceWorkers.exemptFromPerDomainMax", true]] }, function() { resolve(); }); }); } + // adapted from dom/indexedDB/test/helpers.js + function clearStorage() { + return new Promise(function(resolve, reject) { + var principal = SpecialPowers.wrap(document).nodePrincipal; + var appId, inBrowser; + var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal; + if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID && + principal.appId != nsIPrincipal.NO_APP_ID) { + appId = principal.appId; + inBrowser = principal.isInBrowserElement; + } + SpecialPowers.clearStorageForURI(document.documentURI, resolve, appId, + inBrowser); + }); + } + function loadScript(script) { return new Promise(function(resolve, reject) { var s = document.createElement("script"); s.src = script; s.onerror = reject; s.onload = resolve; document.body.appendChild(s); }); @@ -95,22 +111,26 @@ function runTests(testFile, order) { info("Running tests in parallel mode"); return runTests(testFile, "parallel"); }); } if (order == "sequential") { return setupPrefs() .then(importDrivers) .then(runWorkerTest) + .then(clearStorage) .then(runServiceWorkerTest) + .then(clearStorage) .then(runFrameTest) + .then(clearStorage) .catch(function(e) { ok(false, "A promise was rejected during test execution: " + e); }); } return setupPrefs() .then(importDrivers) .then(() => Promise.all([runWorkerTest(), runServiceWorkerTest(), runFrameTest()])) + .then(clearStorage) .catch(function(e) { ok(false, "A promise was rejected during test execution: " + e); }); }
--- a/dom/cache/test/mochitest/mochitest.ini +++ b/dom/cache/test/mochitest/mochitest.ini @@ -12,18 +12,20 @@ support-files = test_cache_matchAll_request.js test_cache_overwrite.js mirror.sjs test_cache_match_vary.js vary.sjs test_caches.js test_cache_keys.js test_cache_put.js + test_cache_requestCache.js [test_cache.html] [test_cache_add.html] [test_cache_match_request.html] [test_cache_matchAll_request.html] [test_cache_overwrite.html] [test_cache_match_vary.html] [test_caches.html] [test_cache_keys.html] [test_cache_put.html] +[test_cache_requestCache.html]
--- a/dom/cache/test/mochitest/test_cache_keys.js +++ b/dom/cache/test/mochitest/test_cache_keys.js @@ -52,18 +52,16 @@ caches.open(name).then(function(cache) { }) ); }).then(function() { // But HEAD should be allowed even without ignoreMethod return c.keys(new Request(tests[0], {method: "HEAD"})); }).then(function(keys) { is(keys.length, 1, "One match should be found"); ok(keys[0].url.indexOf(tests[0]) >= 0, "Valid URL"); - // TODO: Add tests for ignoreVary - // Make sure cacheName is ignored. return c.keys(tests[0], {cacheName: "non-existing-cache"}); }).then(function(keys) { is(keys.length, 1, "One match should be found"); ok(keys[0].url.indexOf(tests[0]) >= 0, "Valid URL"); return caches.delete(name); }).then(function(deleted) { ok(deleted, "The cache should be successfully deleted");
--- a/dom/cache/test/mochitest/test_cache_match_vary.js +++ b/dom/cache/test/mochitest/test_cache_match_vary.js @@ -74,16 +74,43 @@ function testBasics() { // Ensure that searching with a non-matching value for the Cookie header but with ignoreVary set succeeds. return test.cache.match(new Request(requestURL, {headers: {"Cookie": "foo=bar"}}), {ignoreVary: true}); }).then(function(r) { return checkResponse(r, test.response, test.responseText); }); } +function testBasicKeys() { + function checkRequest(reqs) { + is(reqs.length, 1, "One request expected"); + ok(reqs[0].url.indexOf(requestURL) >= 0, "The correct request expected"); + ok(reqs[0].headers.get("WhatToVary"), "Cookie", "The correct request headers expected"); + } + var test; + return setupTest({"WhatToVary": "Cookie"}) + .then(function(t) { + test = t; + // Ensure that searching without specifying a Cookie header succeeds. + return test.cache.keys(requestURL); + }).then(function(r) { + return checkRequest(r); + }).then(function() { + // Ensure that searching with a non-matching value for the Cookie header fails. + return test.cache.keys(new Request(requestURL, {headers: {"Cookie": "foo=bar"}})); + }).then(function(r) { + is(r.length, 0, "Searching for a request with an unknown Vary header should not succeed"); + // Ensure that searching with a non-matching value for the Cookie header but with ignoreVary set succeeds. + return test.cache.keys(new Request(requestURL, {headers: {"Cookie": "foo=bar"}}), + {ignoreVary: true}); + }).then(function(r) { + return checkRequest(r); + }); +} + function testStar() { var test; return setupTest({"WhatToVary": "*", "Cookie": "foo=bar"}) .then(function(t) { test = t; // Ensure that searching with a different Cookie header with Vary:* succeeds. return test.cache.match(new Request(requestURL, {headers: {"Cookie": "bar=baz"}})); }).then(function(r) { @@ -257,16 +284,18 @@ function testMultipleCacheEntries() { // Make sure to clean up after each test step. function step(testPromise) { return testPromise.then(function() { caches.delete(name); }); } step(testBasics()).then(function() { + return step(testBasicKeys()); +}).then(function() { return step(testStar()); }).then(function() { return step(testMatch()); }).then(function() { return step(testStarAndAnotherHeader()); }).then(function() { return step(testInvalidHeaderName()); }).then(function() {
new file mode 100644 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_requestCache.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate the Cache.keys() method</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + runTests("test_cache_requestCache.js") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_requestCache.js @@ -0,0 +1,27 @@ +var name = "requestCache" + context; +var c; + +var reqWithoutCache = new Request("//mochi.test:8888/?noCache" + context); +var reqWithCache = new Request("//mochi.test:8888/?withCache" + context, + {cache: "force-cache"}); + +// Sanity check +is(reqWithoutCache.cache, "default", "Correct default value"); +is(reqWithCache.cache, "force-cache", "Correct value set by the ctor"); + +caches.open(name).then(function(cache) { + c = cache; + return c.addAll([reqWithoutCache, reqWithCache]); +}).then(function() { + return c.keys(); +}).then(function(keys) { + is(keys.length, 2, "Correct number of requests"); + is(keys[0].url, reqWithoutCache.url, "Correct URL"); + is(keys[0].cache, reqWithoutCache.cache, "Correct cache attribute"); + is(keys[1].url, reqWithCache.url, "Correct URL"); + is(keys[1].cache, reqWithCache.cache, "Correct cache attribute"); + return caches.delete(name); +}).then(function(deleted) { + ok(deleted, "The cache should be successfully deleted"); + testDone(); +});
--- a/dom/canvas/WebGL1Context.h +++ b/dom/canvas/WebGL1Context.h @@ -29,15 +29,16 @@ public: // nsWrapperCache virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override; private: virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, GLsizei* alignment, const char* info) override; virtual bool ValidateBufferTarget(GLenum target, const char* info) override; virtual bool ValidateBufferIndexedTarget(GLenum target, const char* info) override; virtual bool ValidateBufferForTarget(GLenum target, WebGLBuffer* buffer, const char* info) override; - + virtual bool ValidateBufferUsageEnum(GLenum usage, const char* info) override; + virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) override; }; } // namespace mozilla #endif // WEBGL_1_CONTEXT_H_
--- a/dom/canvas/WebGL1ContextBuffers.cpp +++ b/dom/canvas/WebGL1ContextBuffers.cpp @@ -45,8 +45,24 @@ WebGL1Context::ValidateBufferForTarget(G if (buffer->HasEverBeenBound() && target != buffer->Target()) { ErrorInvalidOperation("%s: buffer already bound to a different target", info); return false; } return true; } + +bool +WebGL1Context::ValidateBufferUsageEnum(GLenum usage, const char* info) +{ + switch (usage) { + case LOCAL_GL_STREAM_DRAW: + case LOCAL_GL_STATIC_DRAW: + case LOCAL_GL_DYNAMIC_DRAW: + return true; + default: + break; + } + + ErrorInvalidEnumInfo(info, usage); + return false; +}
--- a/dom/canvas/WebGL2Context.cpp +++ b/dom/canvas/WebGL2Context.cpp @@ -82,17 +82,18 @@ static const gl::GLFeature kRequiredFeat gl::GLFeature::instanced_arrays, gl::GLFeature::instanced_non_arrays, gl::GLFeature::map_buffer_range, gl::GLFeature::occlusion_query2, gl::GLFeature::packed_depth_stencil, gl::GLFeature::query_objects, gl::GLFeature::renderbuffer_color_float, gl::GLFeature::renderbuffer_color_half_float, - gl::GLFeature::sRGB, + gl::GLFeature::sRGB_framebuffer, + gl::GLFeature::sRGB_texture, gl::GLFeature::sampler_objects, gl::GLFeature::standard_derivatives, gl::GLFeature::texture_3D, gl::GLFeature::texture_3D_compressed, gl::GLFeature::texture_3D_copy, gl::GLFeature::texture_float, gl::GLFeature::texture_float_linear, gl::GLFeature::texture_half_float, @@ -115,22 +116,32 @@ WebGLContext::InitWebGL2() !gl->IsSupported(gl::GLFeature::occlusion_query_boolean)) { // On desktop, we fake occlusion_query_boolean with occlusion_query if // necessary. (See WebGL2ContextQueries.cpp) GenerateWarning("WebGL 2 unavailable. Requires occlusion queries."); return false; } + std::vector<gl::GLFeature> missingList; + for (size_t i = 0; i < ArrayLength(kRequiredFeatures); i++) { - if (!gl->IsSupported(kRequiredFeatures[i])) { - GenerateWarning("WebGL 2 unavailable. Requires feature %s.", - gl::GLContext::GetFeatureName(kRequiredFeatures[i])); - return false; + if (!gl->IsSupported(kRequiredFeatures[i])) + missingList.push_back(kRequiredFeatures[i]); + } + + if (missingList.size()) { + nsAutoCString exts; + for (auto itr = missingList.begin(); itr != missingList.end(); ++itr) { + exts.AppendLiteral("\n "); + exts.Append(gl::GLContext::GetFeatureName(*itr)); } + GenerateWarning("WebGL 2 unavailable. The following required features are" + " unavailible: %s", exts.BeginReading()); + return false; } // ok WebGL 2 is compatible, we can enable natively supported extensions. for (size_t i = 0; i < ArrayLength(kNativelySupportedExtensions); i++) { EnableExtension(kNativelySupportedExtensions[i]); MOZ_ASSERT(IsExtensionEnabled(kNativelySupportedExtensions[i])); }
--- a/dom/canvas/WebGL2Context.h +++ b/dom/canvas/WebGL2Context.h @@ -343,15 +343,16 @@ private: bool ValidateTexStorage(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, const char* info); virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, GLsizei* alignment, const char* info) override; virtual bool ValidateBufferTarget(GLenum target, const char* info) override; virtual bool ValidateBufferIndexedTarget(GLenum target, const char* info) override; virtual bool ValidateBufferForTarget(GLenum target, WebGLBuffer* buffer, const char* info) override; + virtual bool ValidateBufferUsageEnum(GLenum usage, const char* info) override; virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) override; }; } // namespace mozilla #endif
--- a/dom/canvas/WebGL2ContextBuffers.cpp +++ b/dom/canvas/WebGL2ContextBuffers.cpp @@ -70,16 +70,38 @@ WebGL2Context::ValidateBufferForTarget(G buffer->Target() != LOCAL_GL_ELEMENT_ARRAY_BUFFER; } ErrorInvalidOperation("%s: buffer already bound to a incompatible target %s", info, EnumName(buffer->Target().get())); return false; } +bool +WebGL2Context::ValidateBufferUsageEnum(GLenum usage, const char* info) +{ + switch (usage) { + case LOCAL_GL_DYNAMIC_COPY: + case LOCAL_GL_DYNAMIC_DRAW: + case LOCAL_GL_DYNAMIC_READ: + case LOCAL_GL_STATIC_COPY: + case LOCAL_GL_STATIC_DRAW: + case LOCAL_GL_STATIC_READ: + case LOCAL_GL_STREAM_COPY: + case LOCAL_GL_STREAM_DRAW: + case LOCAL_GL_STREAM_READ: + return true; + default: + break; + } + + ErrorInvalidEnumInfo(info, usage); + return false; +} + // ------------------------------------------------------------------------- // Buffer objects void WebGL2Context::CopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size) {
--- a/dom/canvas/WebGLContext.h +++ b/dom/canvas/WebGLContext.h @@ -927,17 +927,16 @@ protected: WebGLRefPtr<WebGLBuffer> mBoundUniformBuffer; nsTArray<WebGLRefPtr<WebGLBuffer>> mBoundUniformBuffers; nsTArray<WebGLRefPtr<WebGLBuffer>> mBoundTransformFeedbackBuffers; WebGLRefPtr<WebGLBuffer>& GetBufferSlotByTarget(GLenum target); WebGLRefPtr<WebGLBuffer>& GetBufferSlotByTargetIndexed(GLenum target, GLuint index); - bool ValidateBufferUsageEnum(GLenum target, const char* info); // ----------------------------------------------------------------------------- // Queries (WebGL2ContextQueries.cpp) protected: WebGLRefPtr<WebGLQuery>* GetQueryTargetSlot(GLenum target); WebGLRefPtr<WebGLQuery> mActiveOcclusionQuery; WebGLRefPtr<WebGLQuery> mActiveTransformFeedbackQuery; @@ -1384,16 +1383,17 @@ private: private: // ------------------------------------------------------------------------- // Context customization points virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, GLsizei* alignment, const char* info) = 0; virtual bool ValidateBufferTarget(GLenum target, const char* info) = 0; virtual bool ValidateBufferIndexedTarget(GLenum target, const char* info) = 0; virtual bool ValidateBufferForTarget(GLenum target, WebGLBuffer* buffer, const char* info) = 0; + virtual bool ValidateBufferUsageEnum(GLenum usage, const char* info) = 0; virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) = 0; protected: int32_t MaxTextureSizeForTarget(TexTarget target) const { return (target == LOCAL_GL_TEXTURE_2D) ? mGLMaxTextureSize : mGLMaxCubeMapTextureSize; }
--- a/dom/canvas/WebGLContextGL.cpp +++ b/dom/canvas/WebGLContextGL.cpp @@ -885,16 +885,21 @@ WebGLContext::FramebufferTexture2D(GLenu GLint level) { if (IsContextLost()) return; if (!ValidateFramebufferTarget(target, "framebufferTexture2D")) return; + if (!IsWebGL2() && level != 0) { + ErrorInvalidValue("framebufferTexture2D: level must be 0."); + return; + } + WebGLFramebuffer* fb; switch (target) { case LOCAL_GL_FRAMEBUFFER: case LOCAL_GL_DRAW_FRAMEBUFFER: fb = mBoundDrawFramebuffer; break; case LOCAL_GL_READ_FRAMEBUFFER:
--- a/dom/canvas/WebGLExtensionSRGB.cpp +++ b/dom/canvas/WebGLExtensionSRGB.cpp @@ -29,15 +29,16 @@ WebGLExtensionSRGB::~WebGLExtensionSRGB( { } bool WebGLExtensionSRGB::IsSupported(const WebGLContext* webgl) { gl::GLContext* gl = webgl->GL(); - return gl->IsSupported(gl::GLFeature::sRGB); + return gl->IsSupported(gl::GLFeature::sRGB_framebuffer) && + gl->IsSupported(gl::GLFeature::sRGB_texture); } IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionSRGB) } // namespace mozilla
--- a/dom/canvas/WebGLFramebuffer.cpp +++ b/dom/canvas/WebGLFramebuffer.cpp @@ -494,37 +494,29 @@ WebGLFramebuffer::FramebufferRenderbuffe void WebGLFramebuffer::FramebufferTexture2D(FBAttachment attachPoint, TexImageTarget texImageTarget, WebGLTexture* tex, GLint level) { MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this || mContext->mBoundReadFramebuffer == this); - if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture", - tex)) - { + if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture", tex)) return; - } if (tex) { bool isTexture2D = tex->Target() == LOCAL_GL_TEXTURE_2D; bool isTexTarget2D = texImageTarget == LOCAL_GL_TEXTURE_2D; if (isTexture2D != isTexTarget2D) { mContext->ErrorInvalidOperation("framebufferTexture2D: Mismatched" " texture and texture target."); return; } } - if (level != 0) { - mContext->ErrorInvalidValue("framebufferTexture2D: Level must be 0."); - return; - } - /* Get the requested attachment. If result is NULL, attachment is invalid * and an error is generated. * * Don't use GetAttachment(...) here because it opt builds it returns * mColorAttachment[0] for invalid attachment, which we really don't want to * mess with. */ Attachment* attachment = GetAttachmentOrNull(attachPoint);
--- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -4641,17 +4641,17 @@ EventStateManager::DoStateChange(nsICont /* static */ void EventStateManager::UpdateAncestorState(nsIContent* aStartNode, nsIContent* aStopBefore, EventStates aState, bool aAddState) { for (; aStartNode && aStartNode != aStopBefore; - aStartNode = aStartNode->GetParent()) { + aStartNode = aStartNode->GetParentElementCrossingShadowRoot()) { // We might be starting with a non-element (e.g. a text node) and // if someone is doing something weird might be ending with a // non-element too (e.g. a document fragment) if (!aStartNode->IsElement()) { continue; } Element* element = aStartNode->AsElement(); DoStateChange(element, aState, aAddState); @@ -4669,17 +4669,17 @@ EventStateManager::UpdateAncestorState(n // same node, and while one is no longer hovered the other still // is. In that situation, the label that's still hovered will be // aStopBefore or some ancestor of it, and the call we just made // to UpdateAncestorState with aAddState = false would have // removed the hover state from the node. But the node should // still be in hover state. To handle this situation we need to // keep walking up the tree and any time we find a label mark its // corresponding node as still in our state. - for ( ; aStartNode; aStartNode = aStartNode->GetParent()) { + for ( ; aStartNode; aStartNode = aStartNode->GetParentElementCrossingShadowRoot()) { if (!aStartNode->IsElement()) { continue; } Element* labelTarget = GetLabelTarget(aStartNode->AsElement()); if (labelTarget && !labelTarget->State().HasState(aState)) { DoStateChange(labelTarget, aState, true); }
--- a/dom/events/test/mochitest.ini +++ b/dom/events/test/mochitest.ini @@ -136,16 +136,17 @@ skip-if = buildapp == 'b2g' skip-if = toolkit == "gonk" || e10s [test_bug985988.html] [test_bug998809.html] [test_bug1017086_disable.html] support-files = bug1017086_inner.html [test_bug1017086_enable.html] support-files = bug1017086_inner.html [test_bug1079236.html] +[test_bug1145910.html] [test_clickevent_on_input.html] skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM [test_continuous_wheel_events.html] skip-if = buildapp == 'b2g' || e10s # b2g(5535 passed, 108 failed - more tests running than desktop) b2g-debug(5535 passed, 108 failed - more tests running than desktop) b2g-desktop(5535 passed, 108 failed - more tests running than desktop) [test_dblclick_explicit_original_target.html] [test_dom_keyboard_event.html] skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM [test_dom_mouse_event.html]
new file mode 100644 --- /dev/null +++ b/dom/events/test/test_bug1145910.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1145910 +--> +<head> + <title>Test for Bug 1145910</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<style> +div:active { + color: rgb(0, 255, 0); +} +</style> +<div id="host">Foo</div> +<script type="application/javascript"> + +/** Test for Bug 1145910 **/ +SimpleTest.waitForExplicitFinish(); + +SimpleTest.waitForFocus(function() { + var host = document.getElementById("host"); + var shadow = host.createShadowRoot(); + shadow.innerHTML = '<style>div:active { color: rgb(0, 255, 0); }</style><div id="inner">Bar</div>'; + var inner = shadow.getElementById("inner"); + + is(window.getComputedStyle(host).color, "rgb(0, 0, 0)", "The host should not be active"); + is(window.getComputedStyle(inner).color, "rgb(0, 0, 0)", "The div inside the shadow root should not be active."); + + synthesizeMouseAtCenter(host, { type: "mousedown" }); + + is(window.getComputedStyle(inner).color, "rgb(0, 255, 0)", "Div inside shadow root should be active."); + is(window.getComputedStyle(host).color, "rgb(0, 255, 0)", "Host should be active when the inner div is made active."); + + synthesizeMouseAtCenter(host, { type: "mouseup" }); + + is(window.getComputedStyle(inner).color, "rgb(0, 0, 0)", "Div inside shadow root should no longer be active."); + is(window.getComputedStyle(host).color, "rgb(0, 0, 0)", "Host should no longer be active."); + + SimpleTest.finish(); +}); + +</script> +</body> +</html>
--- a/dom/fetch/FetchIPCUtils.h +++ b/dom/fetch/FetchIPCUtils.h @@ -27,15 +27,20 @@ namespace IPC { mozilla::dom::RequestMode::Same_origin, mozilla::dom::RequestMode::EndGuard_> {}; template<> struct ParamTraits<mozilla::dom::RequestCredentials> : public ContiguousEnumSerializer<mozilla::dom::RequestCredentials, mozilla::dom::RequestCredentials::Omit, mozilla::dom::RequestCredentials::EndGuard_> {}; template<> + struct ParamTraits<mozilla::dom::RequestCache> : + public ContiguousEnumSerializer<mozilla::dom::RequestCache, + mozilla::dom::RequestCache::Default, + mozilla::dom::RequestCache::EndGuard_> {}; + template<> struct ParamTraits<mozilla::dom::ResponseType> : public ContiguousEnumSerializer<mozilla::dom::ResponseType, mozilla::dom::ResponseType::Basic, mozilla::dom::ResponseType::EndGuard_> {}; } #endif // mozilla_dom_FetchIPCUtils_h
--- a/dom/fetch/InternalRequest.h +++ b/dom/fetch/InternalRequest.h @@ -203,16 +203,22 @@ public: } RequestCache GetCacheMode() const { return mCacheMode; } + void + SetCacheMode(RequestCache aCacheMode) + { + mCacheMode = aCacheMode; + } + nsContentPolicyType ContentPolicyType() const { return mContentPolicyType; } void SetContentPolicyType(nsContentPolicyType aContentPolicyType)
--- a/dom/fetch/Request.cpp +++ b/dom/fetch/Request.cpp @@ -77,16 +77,17 @@ Request::Constructor(const GlobalObject& request = request->GetRequestConstructorCopy(global, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } RequestMode fallbackMode = RequestMode::EndGuard_; RequestCredentials fallbackCredentials = RequestCredentials::EndGuard_; + RequestCache fallbackCache = RequestCache::EndGuard_; if (aInput.IsUSVString()) { nsString input; input.Assign(aInput.GetAsUSVString()); nsString requestURL; if (NS_IsMainThread()) { nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global); MOZ_ASSERT(window); @@ -122,16 +123,17 @@ Request::Constructor(const GlobalObject& url->Stringify(requestURL, aRv); if (aRv.Failed()) { return nullptr; } } request->SetURL(NS_ConvertUTF16toUTF8(requestURL)); fallbackMode = RequestMode::Cors; fallbackCredentials = RequestCredentials::Omit; + fallbackCache = RequestCache::Default; } // CORS-with-forced-preflight is not publicly exposed and should not be // considered a valid value. if (aInit.mMode.WasPassed() && aInit.mMode.Value() == RequestMode::Cors_with_forced_preflight) { NS_NAMED_LITERAL_STRING(sourceDescription, "'mode' member of RequestInit"); NS_NAMED_LITERAL_STRING(value, "cors-with-forced-preflight"); @@ -147,16 +149,22 @@ Request::Constructor(const GlobalObject& if (mode != RequestMode::EndGuard_) { request->SetMode(mode); } if (credentials != RequestCredentials::EndGuard_) { request->SetCredentialsMode(credentials); } + RequestCache cache = aInit.mCache.WasPassed() ? + aInit.mCache.Value() : fallbackCache; + if (cache != RequestCache::EndGuard_) { + request->SetCacheMode(cache); + } + // Request constructor step 14. if (aInit.mMethod.WasPassed()) { nsAutoCString method(aInit.mMethod.Value()); nsAutoCString upperCaseMethod = method; ToUpperCase(upperCaseMethod); // Step 14.1. Disallow forbidden methods, and anything that is not a HTTP // token, since HTTP states that Method may be any of the defined values or
--- a/dom/fetch/Response.cpp +++ b/dom/fetch/Response.cpp @@ -223,23 +223,10 @@ Headers* Response::Headers_() { if (!mHeaders) { mHeaders = new Headers(mOwner, mInternalResponse->Headers()); } return mHeaders; } - -void -Response::SetFinalURL(bool aFinalURL, ErrorResult& aRv) -{ - nsCString url; - mInternalResponse->GetUrl(url); - if (url.IsEmpty()) { - aRv.ThrowTypeError(MSG_RESPONSE_URL_IS_NULL); - return; - } - - mInternalResponse->SetFinalURL(aFinalURL); -} } // namespace dom } // namespace mozilla
--- a/dom/fetch/Response.h +++ b/dom/fetch/Response.h @@ -51,25 +51,16 @@ public: void GetUrl(DOMString& aUrl) const { nsCString url; mInternalResponse->GetUrl(url); aUrl.AsAString() = NS_ConvertUTF8toUTF16(url); } - bool - GetFinalURL(ErrorResult& aRv) const - { - return mInternalResponse->FinalURL(); - } - - void - SetFinalURL(bool aFinalURL, ErrorResult& aRv); - uint16_t Status() const { return mInternalResponse->GetStatus(); } bool Ok() const
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl +++ b/dom/interfaces/base/nsIServiceWorkerManager.idl @@ -14,17 +14,17 @@ interface nsIURI; interface nsIServiceWorkerUnregisterCallback : nsISupports { // aState is true if the unregistration succeded. // It's false if this ServiceWorkerRegistration doesn't exist. [noscript] void UnregisterSucceeded(in bool aState); [noscript] void UnregisterFailed(); }; -[builtinclass, uuid(706c3e6b-c9d2-4857-893d-4b4845fec48f)] +[builtinclass, uuid(e4c8baa5-237a-4bf6-82d4-ea06eb4b76ba)] interface nsIServiceWorkerManager : nsISupports { /** * Registers a ServiceWorker with script loaded from `aScriptURI` to act as * the ServiceWorker for aScope. Requires a valid entry settings object on * the stack. This means you must call this from content code 'within' * a window. * @@ -54,17 +54,18 @@ interface nsIServiceWorkerManager : nsIS // Returns true if a ServiceWorker is available for the scope of aURI. bool isAvailableForURI(in nsIURI aURI); // Returns true if a given document is currently controlled by a ServiceWorker bool isControlled(in nsIDocument aDocument); // Cause a fetch event to be dispatched to the worker global associated with the given document. - void dispatchFetchEvent(in nsIDocument aDoc, in nsIInterceptedChannel aChannel); + void dispatchFetchEvent(in nsIDocument aDoc, in nsIInterceptedChannel aChannel, + in boolean aIsReload); // aTarget MUST be a ServiceWorkerRegistration. [noscript] void AddRegistrationEventListener(in DOMString aScope, in nsIDOMEventTarget aTarget); [noscript] void RemoveRegistrationEventListener(in DOMString aScope, in nsIDOMEventTarget aTarget); /** * Call this to request that document `aDoc` be controlled by a ServiceWorker * if a registration exists for it's scope.
--- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -1426,17 +1426,16 @@ void MediaDecoder::UpdateEstimatedMediaD return; } NS_ENSURE_TRUE_VOID(GetStateMachine()); GetStateMachine()->UpdateEstimatedDuration(aDuration); } void MediaDecoder::SetMediaSeekable(bool aMediaSeekable) { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); - MOZ_ASSERT(NS_IsMainThread() || OnDecodeThread()); mMediaSeekable = aMediaSeekable; } bool MediaDecoder::IsTransportSeekable() { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); return GetResource()->IsTransportSeekable(); @@ -1636,19 +1635,21 @@ void MediaDecoder::NotifyDataArrived(con // Provide access to the state machine object MediaDecoderStateMachine* MediaDecoder::GetStateMachine() const { return mDecoderStateMachine; } void MediaDecoder::NotifyWaitingForResourcesStatusChanged() { - ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); if (mDecoderStateMachine) { - mDecoderStateMachine->NotifyWaitingForResourcesStatusChanged(); + RefPtr<nsRunnable> task = + NS_NewRunnableMethod(mDecoderStateMachine, + &MediaDecoderStateMachine::NotifyWaitingForResourcesStatusChanged); + mDecoderStateMachine->TaskQueue()->Dispatch(task.forget()); } } bool MediaDecoder::IsShutdown() const { NS_ENSURE_TRUE(GetStateMachine(), true); return GetStateMachine()->IsShutdown(); } @@ -1736,17 +1737,16 @@ MediaDecoder::SetCDMProxy(CDMProxy* aPro NotifyWaitingForResourcesStatusChanged(); return NS_OK; } CDMProxy* MediaDecoder::GetCDMProxy() { GetReentrantMonitor().AssertCurrentThreadIn(); - MOZ_ASSERT(OnDecodeThread() || NS_IsMainThread()); return mProxy; } #endif #ifdef MOZ_RAW bool MediaDecoder::IsRawEnabled() {
--- a/dom/media/MediaDecoderReader.cpp +++ b/dom/media/MediaDecoderReader.cpp @@ -5,16 +5,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MediaDecoderReader.h" #include "AbstractMediaDecoder.h" #include "MediaResource.h" #include "VideoUtils.h" #include "ImageContainer.h" +#include "nsPrintfCString.h" #include "mozilla/mozalloc.h" #include <stdint.h> #include <algorithm> namespace mozilla { // Un-comment to enable logging of seek bisections. //#define SEEK_LOGGING @@ -22,16 +23,22 @@ namespace mozilla { #ifdef PR_LOGGING extern PRLogModuleInfo* gMediaDecoderLog; #define DECODER_LOG(x, ...) \ PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, ("Decoder=%p " x, mDecoder, ##__VA_ARGS__)) #else #define DECODER_LOG(x, ...) #endif +// Same workaround as MediaDecoderStateMachine.cpp. +#define DECODER_WARN_HELPER(a, b) NS_WARNING b +#define DECODER_WARN(x, ...) \ + DECODER_WARN_HELPER(0, (nsPrintfCString("Decoder=%p " x, mDecoder, ##__VA_ARGS__).get())) + + PRLogModuleInfo* gMediaPromiseLog; void EnsureMediaPromiseLog() { if (!gMediaPromiseLog) { gMediaPromiseLog = PR_NewLogModule("MediaPromise"); } @@ -178,16 +185,53 @@ MediaDecoderReader::ComputeStartTime(con startTime = 0; } DECODER_LOG("ComputeStartTime first video frame start %lld", aVideo ? aVideo->mTime : -1); DECODER_LOG("ComputeStartTime first audio frame start %lld", aAudio ? aAudio->mTime : -1); NS_ASSERTION(startTime >= 0, "Start time is negative"); return startTime; } +nsRefPtr<MediaDecoderReader::MetadataPromise> +MediaDecoderReader::CallReadMetadata() +{ + typedef ReadMetadataFailureReason Reason; + + MOZ_ASSERT(OnDecodeThread()); + mDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn(); + DECODER_LOG("MediaDecoderReader::CallReadMetadata"); + + // PreReadMetadata causes us to try to allocate various hardware and OS + // resources, which may not be available at the moment. + PreReadMetadata(); + if (IsWaitingMediaResources()) { + return MetadataPromise::CreateAndReject(Reason::WAITING_FOR_RESOURCES, __func__); + } + + // Attempt to read the metadata. + nsRefPtr<MetadataHolder> metadata = new MetadataHolder(); + nsresult rv = ReadMetadata(&metadata->mInfo, getter_Transfers(metadata->mTags)); + + // Reading metadata can cause us to discover that we need resources (like + // encryption keys). + if (IsWaitingMediaResources()) { + return MetadataPromise::CreateAndReject(Reason::WAITING_FOR_RESOURCES, __func__); + } + + // We're not waiting for anything. If we didn't get the metadata, that's an + // error. + if (NS_FAILED(rv) || !metadata->mInfo.HasValidMedia()) { + DECODER_WARN("ReadMetadata failed, rv=%x HasValidMedia=%d", rv, metadata->mInfo.HasValidMedia()); + return MetadataPromise::CreateAndReject(Reason::METADATA_ERROR, __func__); + } + + // Success! + return MetadataPromise::CreateAndResolve(metadata, __func__); +} + class ReRequestVideoWithSkipTask : public nsRunnable { public: ReRequestVideoWithSkipTask(MediaDecoderReader* aReader, int64_t aTimeThreshold) : mReader(aReader) , mTimeThreshold(aTimeThreshold) { @@ -352,8 +396,12 @@ MediaDecoderReader::Shutdown() } mDecoder = nullptr; return p; } } // namespace mozilla + +#undef DECODER_LOG +#undef DECODER_WARN +#undef DECODER_WARN_HELPER
--- a/dom/media/MediaDecoderReader.h +++ b/dom/media/MediaDecoderReader.h @@ -17,43 +17,62 @@ namespace mozilla { namespace dom { class TimeRanges; } class MediaDecoderReader; class SharedDecoderManager; -struct WaitForDataRejectValue { +struct WaitForDataRejectValue +{ enum Reason { SHUTDOWN, CANCELED }; WaitForDataRejectValue(MediaData::Type aType, Reason aReason) :mType(aType), mReason(aReason) {} MediaData::Type mType; Reason mReason; }; +class MetadataHolder +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MetadataHolder) + MediaInfo mInfo; + nsAutoPtr<MetadataTags> mTags; + +private: + virtual ~MetadataHolder() {} +}; + +enum class ReadMetadataFailureReason : int8_t +{ + WAITING_FOR_RESOURCES, + METADATA_ERROR +}; + // Encapsulates the decoding and reading of media data. Reading can either // synchronous and done on the calling "decode" thread, or asynchronous and // performed on a background thread, with the result being returned by // callback. Never hold the decoder monitor when calling into this class. // Unless otherwise specified, methods and fields of this class can only // be accessed on the decode task queue. class MediaDecoderReader { public: enum NotDecodedReason { END_OF_STREAM, DECODE_ERROR, WAITING_FOR_DATA, CANCELED }; + typedef MediaPromise<nsRefPtr<MetadataHolder>, ReadMetadataFailureReason, /* IsExclusive = */ true> MetadataPromise; typedef MediaPromise<nsRefPtr<AudioData>, NotDecodedReason, /* IsExclusive = */ true> AudioDataPromise; typedef MediaPromise<nsRefPtr<VideoData>, NotDecodedReason, /* IsExclusive = */ true> VideoDataPromise; typedef MediaPromise<int64_t, nsresult, /* IsExclusive = */ true> SeekPromise; // Note that, conceptually, WaitForData makes sense in a non-exclusive sense. // But in the current architecture it's only ever used exclusively (by MDSM), // so we mark it that way to verify our assumptions. If you have a use-case // for multiple WaitForData consumers, feel free to flip the exclusivity here. @@ -143,16 +162,21 @@ public: // in buffering mode. Some readers support a promise-based mechanism by which // they notify the state machine when the data arrives. virtual bool IsWaitForDataSupported() { return false; } virtual nsRefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType) { MOZ_CRASH(); } virtual bool HasAudio() = 0; virtual bool HasVideo() = 0; + // The ReadMetadata API is unfortunately synchronous. We should fix that at + // some point, but for now we can make things a bit better by using a + // promise-y API on top of a synchronous call. + nsRefPtr<MetadataPromise> CallReadMetadata(); + // A function that is called before ReadMetadata() call. virtual void PreReadMetadata() {}; // Read header data for all bitstreams in the file. Fills aInfo with // the data required to present the media, and optionally fills *aTags // with tag metadata from the file. // Returns NS_OK on success, or NS_ERROR_FAILURE on failure. virtual nsresult ReadMetadata(MediaInfo* aInfo,
--- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -1494,24 +1494,30 @@ void MediaDecoderStateMachine::SetDorman nsRefPtr<MediaDecoder::SeekPromise> unused = mQueuedSeek.mPromise.Ensure(__func__); } mPendingSeek.RejectIfExists(__func__); mCurrentSeek.RejectIfExists(__func__); SetState(DECODER_STATE_DORMANT); if (IsPlaying()) { StopPlayback(); } - StopAudioThread(); - FlushDecoding(); - // Now that those threads are stopped, there's no possibility of - // mPendingWakeDecoder being needed again. Revoke it. - mPendingWakeDecoder = nullptr; + + Reset(); + + // Note that we do not wait for the decode task queue to go idle before + // queuing the ReleaseMediaResources task - instead, we disconnect promises, + // reset state, and put a ResetDecode in the decode task queue. Any tasks + // that run after ResetDecode are supposed to run with a clean slate. We rely + // on that in other places (i.e. seeking), so it seems reasonable to rely on + // it here as well. DebugOnly<nsresult> rv = DecodeTaskQueue()->Dispatch( NS_NewRunnableMethod(mReader, &MediaDecoderReader::ReleaseMediaResources)); MOZ_ASSERT(NS_SUCCEEDED(rv)); + // There's now no possibility of mPendingWakeDecoder being needed again. Revoke it. + mPendingWakeDecoder = nullptr; mDecoder->GetReentrantMonitor().NotifyAll(); } else if ((aDormant != true) && (mState == DECODER_STATE_DORMANT)) { mDecodingFrozenAtStateDecoding = true; ScheduleStateMachine(); mCurrentFrameTime = 0; SetState(DECODER_STATE_DECODING_NONE); mDecoder->GetReentrantMonitor().NotifyAll(); } @@ -1555,53 +1561,31 @@ void MediaDecoderStateMachine::StartDeco mIsVideoPrerolling = !DonePrerollingVideo(); // Ensure that we've got tasks enqueued to decode data if we need to. DispatchDecodeTasksIfNeeded(); ScheduleStateMachine(); } -void MediaDecoderStateMachine::StartWaitForResources() -{ - ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); - NS_ASSERTION(OnDecodeThread(), - "Should be on decode thread."); - SetState(DECODER_STATE_WAIT_FOR_RESOURCES); - DECODER_LOG("StartWaitForResources"); -} - void MediaDecoderStateMachine::NotifyWaitingForResourcesStatusChanged() { - AssertCurrentThreadInMonitor(); + MOZ_ASSERT(OnStateMachineThread()); + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); DECODER_LOG("NotifyWaitingForResourcesStatusChanged"); - RefPtr<nsIRunnable> task( - NS_NewRunnableMethod(this, - &MediaDecoderStateMachine::DoNotifyWaitingForResourcesStatusChanged)); - DecodeTaskQueue()->Dispatch(task); -} - -void MediaDecoderStateMachine::DoNotifyWaitingForResourcesStatusChanged() -{ - NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); - ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); - - DECODER_LOG("DoNotifyWaitingForResourcesStatusChanged"); if (mState == DECODER_STATE_WAIT_FOR_RESOURCES) { - // The reader is no longer waiting for resources (say a hardware decoder), - // we can now proceed to decode metadata. + // Try again. SetState(DECODER_STATE_DECODING_NONE); + ScheduleStateMachine(); } else if (mState == DECODER_STATE_WAIT_FOR_CDM && !mReader->IsWaitingOnCDMResource()) { SetState(DECODER_STATE_DECODING_FIRSTFRAME); EnqueueDecodeFirstFrameTask(); } - - ScheduleStateMachine(); } void MediaDecoderStateMachine::PlayInternal() { NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread."); ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); // Once we start playing, we don't want to minimize our prerolling, as we @@ -1636,48 +1620,16 @@ void MediaDecoderStateMachine::PlayInter // when the state machine notices the decoder's state change to PLAYING. if (mState == DECODER_STATE_BUFFERING) { StartDecoding(); } ScheduleStateMachine(); } -void MediaDecoderStateMachine::ResetPlayback() -{ - MOZ_ASSERT(OnStateMachineThread()); - - // We should be reseting because we're seeking, shutting down, or - // entering dormant state. We could also be in the process of going dormant, - // and have just switched to exiting dormant before we finished entering - // dormant, hence the DECODING_NONE case below. - AssertCurrentThreadInMonitor(); - MOZ_ASSERT(mState == DECODER_STATE_SEEKING || - mState == DECODER_STATE_SHUTDOWN || - mState == DECODER_STATE_DORMANT || - mState == DECODER_STATE_DECODING_NONE); - - // Audio thread should've been stopped at the moment. Otherwise, AudioSink - // might be accessing AudioQueue outside of the decoder monitor while we - // are clearing the queue and causes crash for no samples to be popped. - MOZ_ASSERT(!mAudioSink); - - mVideoFrameEndTime = -1; - mDecodedVideoEndTime = -1; - mAudioStartTime = -1; - mAudioEndTime = -1; - mDecodedAudioEndTime = -1; - mAudioCompleted = false; - AudioQueue().Reset(); - VideoQueue().Reset(); - mFirstVideoFrameAfterSeek = nullptr; - mDropAudioUntilNextDiscontinuity = true; - mDropVideoUntilNextDiscontinuity = true; -} - void MediaDecoderStateMachine::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) { NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); mReader->NotifyDataArrived(aBuffer, aLength, aOffset); // While playing an unseekable stream of unknown duration, mEndTime is @@ -1767,29 +1719,16 @@ void MediaDecoderStateMachine::StopAudio } mAudioSink = nullptr; } // Wake up those waiting for audio sink to finish. mDecoder->GetReentrantMonitor().NotifyAll(); } nsresult -MediaDecoderStateMachine::EnqueueDecodeMetadataTask() -{ - AssertCurrentThreadInMonitor(); - MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA); - - RefPtr<nsIRunnable> task( - NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeMetadata)); - nsresult rv = DecodeTaskQueue()->Dispatch(task); - NS_ENSURE_SUCCESS(rv, rv); - return NS_OK; -} - -nsresult MediaDecoderStateMachine::EnqueueDecodeFirstFrameTask() { AssertCurrentThreadInMonitor(); MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME); RefPtr<nsIRunnable> task( NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeFirstFrame)); nsresult rv = TaskQueue()->Dispatch(task); @@ -1921,24 +1860,18 @@ MediaDecoderStateMachine::InitiateSeek() // to display nsCOMPtr<nsIRunnable> startEvent = NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>( mDecoder, &MediaDecoder::SeekingStarted, mCurrentSeek.mTarget.mEventVisibility); NS_DispatchToMainThread(startEvent, NS_DISPATCH_NORMAL); - // The seek target is different than the current playback position, - // we'll need to seek the playback position, so shutdown our decode - // thread and audio sink. - StopAudioThread(); - ResetPlayback(); - - // Put a reset in the pipe before seek. - ResetDecode(); + // Reset our state machine and decoding pipeline before seeking. + Reset(); // Do the seek. mSeekRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__, &MediaDecoderReader::Seek, mCurrentSeek.mTarget.mTime, GetEndTime()) ->RefableThen(TaskQueue(), __func__, this, &MediaDecoderStateMachine::OnSeekCompleted, &MediaDecoderStateMachine::OnSeekFailed)); @@ -2200,69 +2133,26 @@ MediaDecoderStateMachine::DecodeError() nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError); ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); NS_DispatchToMainThread(event, NS_DISPATCH_SYNC); } } void -MediaDecoderStateMachine::CallDecodeMetadata() +MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata) { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); - if (mState != DECODER_STATE_DECODING_METADATA) { - return; - } - if (NS_FAILED(DecodeMetadata())) { - DECODER_WARN("Decode metadata failed, shutting down decoder"); - DecodeError(); - } -} - -nsresult MediaDecoderStateMachine::DecodeMetadata() -{ - AssertCurrentThreadInMonitor(); - NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); + MOZ_ASSERT(OnStateMachineThread()); MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA); - DECODER_LOG("Decoding Media Headers"); - - nsresult res; - MediaInfo info; - bool isAwaitingResources = false; - { - ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); - mReader->PreReadMetadata(); - - if (mReader->IsWaitingMediaResources()) { - StartWaitForResources(); - return NS_OK; - } - res = mReader->ReadMetadata(&info, getter_Transfers(mMetadataTags)); - isAwaitingResources = mReader->IsWaitingMediaResources(); - } - - if (NS_SUCCEEDED(res) && - mState == DECODER_STATE_DECODING_METADATA && - isAwaitingResources) { - // change state to DECODER_STATE_WAIT_FOR_RESOURCES - StartWaitForResources(); - // affect values only if ReadMetadata succeeds - return NS_OK; - } - - if (NS_FAILED(res) || (!info.HasValidMedia())) { - DECODER_WARN("ReadMetadata failed, res=%x HasValidMedia=%d", res, info.HasValidMedia()); - return NS_ERROR_FAILURE; - } - - if (NS_SUCCEEDED(res)) { - mDecoder->SetMediaSeekable(mReader->IsMediaSeekable()); - } - - mInfo = info; + mMetadataRequest.Complete(); + + mDecoder->SetMediaSeekable(mReader->IsMediaSeekable()); + mInfo = aMetadata->mInfo; + mMetadataTags = aMetadata->mTags.forget(); if (HasVideo()) { DECODER_LOG("Video decode isAsync=%d HWAccel=%d videoQueueSize=%d", mReader->IsAsync(), mReader->VideoIsHardwareAccelerated(), GetAmpleVideoFrames()); } @@ -2270,33 +2160,43 @@ nsresult MediaDecoderStateMachine::Decod mGotDurationFromMetaData = (GetDuration() != -1) || mDurationSet; if (mGotDurationFromMetaData) { // We have all the information required: duration and size // Inform the element that we've loaded the metadata. EnqueueLoadedMetadataEvent(); } - if (mState == DECODER_STATE_DECODING_METADATA) { - if (mReader->IsWaitingOnCDMResource()) { - // Metadata parsing was successful but we're still waiting for CDM caps - // to become available so that we can build the correct decryptor/decoder. - SetState(DECODER_STATE_WAIT_FOR_CDM); - return NS_OK; - } - - SetState(DECODER_STATE_DECODING_FIRSTFRAME); - res = EnqueueDecodeFirstFrameTask(); - if (NS_FAILED(res)) { - return NS_ERROR_FAILURE; - } + if (mReader->IsWaitingOnCDMResource()) { + // Metadata parsing was successful but we're still waiting for CDM caps + // to become available so that we can build the correct decryptor/decoder. + SetState(DECODER_STATE_WAIT_FOR_CDM); + return; } + + SetState(DECODER_STATE_DECODING_FIRSTFRAME); + EnqueueDecodeFirstFrameTask(); ScheduleStateMachine(); - - return NS_OK; +} + +void +MediaDecoderStateMachine::OnMetadataNotRead(ReadMetadataFailureReason aReason) +{ + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + MOZ_ASSERT(OnStateMachineThread()); + MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA); + mMetadataRequest.Complete(); + + if (aReason == ReadMetadataFailureReason::WAITING_FOR_RESOURCES) { + SetState(DECODER_STATE_WAIT_FOR_RESOURCES); + } else { + MOZ_ASSERT(aReason == ReadMetadataFailureReason::METADATA_ERROR); + DECODER_WARN("Decode metadata failed, shutting down decoder"); + DecodeError(); + } } void MediaDecoderStateMachine::EnqueueLoadedMetadataEvent() { nsAutoPtr<MediaInfo> info(new MediaInfo()); *info = mInfo; MediaDecoderEventVisibility visibility = mSentLoadedMetadataEvent? @@ -2669,18 +2569,17 @@ nsresult MediaDecoderStateMachine::RunSt mQueuedSeek.RejectIfExists(__func__); mPendingSeek.RejectIfExists(__func__); mCurrentSeek.RejectIfExists(__func__); if (IsPlaying()) { StopPlayback(); } - StopAudioThread(); - FlushDecoding(); + Reset(); // Put a task in the decode queue to shutdown the reader. // the queue to spin down. RefPtr<nsIRunnable> task; task = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::ShutdownReader); DebugOnly<nsresult> rv = DecodeTaskQueue()->Dispatch(task); MOZ_ASSERT(NS_SUCCEEDED(rv)); @@ -2694,26 +2593,35 @@ nsresult MediaDecoderStateMachine::RunSt case DECODER_STATE_WAIT_FOR_CDM: case DECODER_STATE_WAIT_FOR_RESOURCES: { return NS_OK; } case DECODER_STATE_DECODING_NONE: { SetState(DECODER_STATE_DECODING_METADATA); - // Ensure we have a decode thread to decode metadata. - return EnqueueDecodeMetadataTask(); + ScheduleStateMachine(); + return NS_OK; } case DECODER_STATE_DECODING_METADATA: { + if (!mMetadataRequest.Exists()) { + DECODER_LOG("Dispatching CallReadMetadata"); + mMetadataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__, + &MediaDecoderReader::CallReadMetadata) + ->RefableThen(TaskQueue(), __func__, this, + &MediaDecoderStateMachine::OnMetadataRead, + &MediaDecoderStateMachine::OnMetadataNotRead)); + + } return NS_OK; } case DECODER_STATE_DECODING_FIRSTFRAME: { - // DECODER_STATE_DECODING_FIRSTFRAME will be started by DecodeMetadata + // DECODER_STATE_DECODING_FIRSTFRAME will be started by OnMetadataRead. return NS_OK; } case DECODER_STATE_DECODING: { if (mDecoder->GetState() != MediaDecoder::PLAY_STATE_PLAYING && IsPlaying()) { // We're playing, but the element/decoder is in paused state. Stop @@ -2833,58 +2741,56 @@ nsresult MediaDecoderStateMachine::RunSt return NS_OK; } } return NS_OK; } void -MediaDecoderStateMachine::FlushDecoding() +MediaDecoderStateMachine::Reset() { MOZ_ASSERT(OnStateMachineThread()); AssertCurrentThreadInMonitor(); - - // Put a task in the decode queue to abort any decoding operations. - // The reader is not supposed to put any tasks to deliver samples into - // the queue after this runs (unless we request another sample from it). - ResetDecode(); - { - // Wait for the ResetDecode to run and for the decoder to abort - // decoding operations and run any pending callbacks. This is - // important, as we don't want any pending tasks posted to the task - // queue by the reader to deliver any samples after we've posted the - // reader Shutdown() task below, as the sample-delivery tasks will - // keep video frames alive until after we've called Reader::Shutdown(), - // and shutdown on B2G will fail as there are outstanding video frames - // alive. - ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); - DecodeTaskQueue()->AwaitIdle(); - } - - // We must reset playback so that all references to frames queued - // in the state machine are dropped, else subsequent calls to Shutdown() - // or ReleaseMediaResources() can fail on B2G. - ResetPlayback(); -} - -void -MediaDecoderStateMachine::ResetDecode() -{ - MOZ_ASSERT(OnStateMachineThread()); - AssertCurrentThreadInMonitor(); - + DECODER_LOG("MediaDecoderStateMachine::Reset"); + + // We should be resetting because we're seeking, shutting down, or entering + // dormant state. We could also be in the process of going dormant, and have + // just switched to exiting dormant before we finished entering dormant, + // hence the DECODING_NONE case below. + MOZ_ASSERT(mState == DECODER_STATE_SEEKING || + mState == DECODER_STATE_SHUTDOWN || + mState == DECODER_STATE_DORMANT || + mState == DECODER_STATE_DECODING_NONE); + + // Stop the audio thread. Otherwise, AudioSink might be accessing AudioQueue + // outside of the decoder monitor while we are clearing the queue and causes + // crash for no samples to be popped. + StopAudioThread(); + + mVideoFrameEndTime = -1; + mDecodedVideoEndTime = -1; + mAudioStartTime = -1; + mAudioEndTime = -1; + mDecodedAudioEndTime = -1; + mAudioCompleted = false; + AudioQueue().Reset(); + VideoQueue().Reset(); + mFirstVideoFrameAfterSeek = nullptr; + mDropAudioUntilNextDiscontinuity = true; + mDropVideoUntilNextDiscontinuity = true; + mDecodeToSeekTarget = false; + + mMetadataRequest.DisconnectIfExists(); mAudioDataRequest.DisconnectIfExists(); mAudioWaitRequest.DisconnectIfExists(); mVideoDataRequest.DisconnectIfExists(); mVideoWaitRequest.DisconnectIfExists(); mSeekRequest.DisconnectIfExists(); - mDecodeToSeekTarget = false; - RefPtr<nsRunnable> resetTask = NS_NewRunnableMethod(mReader, &MediaDecoderReader::ResetDecode); DecodeTaskQueue()->Dispatch(resetTask); } void MediaDecoderStateMachine::RenderVideoFrame(VideoData* aData, TimeStamp aTarget) {
--- a/dom/media/MediaDecoderStateMachine.h +++ b/dom/media/MediaDecoderStateMachine.h @@ -362,19 +362,18 @@ public: void QueueMetadata(int64_t aPublishTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags); // Returns true if we're currently playing. The decoder monitor must // be held. bool IsPlaying() const; - // Dispatch DoNotifyWaitingForResourcesStatusChanged task to the task queue. // Called when the reader may have acquired the hardware resources required - // to begin decoding. The decoder monitor must be held while calling this. + // to begin decoding. void NotifyWaitingForResourcesStatusChanged(); // Notifies the state machine that should minimize the number of samples // decoded we preroll, until playback starts. The first time playback starts // the state machine is free to return to prerolling normally. Note // "prerolling" in this context refers to when we decode and buffer decoded // samples in advance of when they're needed for playback. void SetMinimizePrerollUntilPlaybackStarts(); @@ -402,18 +401,19 @@ public: } void OnWaitForDataRejected(WaitForDataRejectValue aRejection) { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); WaitRequestRef(aRejection.mType).Complete(); } - // Resets all state related to decoding, emptying all buffers etc. - void ResetDecode(); + // Resets all state related to decoding and playback, emptying all buffers + // and aborting all pending operations on the decode task queue. + void Reset(); private: void AcquireMonitorAndInvokeDecodeError(); protected: virtual ~MediaDecoderStateMachine(); void AssertCurrentThreadInMonitor() const { mDecoder->GetReentrantMonitor().AssertCurrentThreadIn(); } @@ -460,18 +460,16 @@ protected: }; WakeDecoderRunnable* GetWakeDecoderRunnable(); MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; } MediaQueue<VideoData>& VideoQueue() { return mVideoQueue; } nsresult FinishDecodeFirstFrame(); - nsAutoPtr<MetadataTags> mMetadataTags; - // True if our buffers of decoded audio are not full, and we should // decode more. bool NeedToDecodeAudio(); // True if our buffers of decoded video are not full, and we should // decode more. bool NeedToDecodeVideo(); @@ -509,24 +507,16 @@ protected: bool HasFutureAudio(); // Returns true if we recently exited "quick buffering" mode. bool JustExitedQuickBuffering(); // Dispatches an asynchronous event to update the media element's ready state. void UpdateReadyState(); - // Resets playback timing data. Called when we seek, on the decode thread. - void ResetPlayback(); - - // Orders the Reader to stop decoding, and blocks until the Reader - // has stopped decoding and finished delivering samples, then calls - // ResetPlayback() to discard all enqueued data. - void FlushDecoding(); - // Called when AudioSink reaches the end. |mPlayStartTime| and // |mPlayDuration| are updated to provide a good base for calculating video // stream time. void ResyncAudioClock(); // Returns the audio clock, if we have audio, or -1 if we don't. // Called on the state machine thread. int64_t GetAudioClock() const; @@ -585,18 +575,16 @@ protected: void StartDecoding(); // Moves the decoder into the shutdown state, and dispatches an error // event to the media element. This begins shutting down the decoder. // The decoder monitor must be held. This is only called on the // decode thread. void DecodeError(); - void StartWaitForResources(); - // Dispatches a task to the decode task queue to begin decoding metadata. // This is threadsafe and can be called on any thread. // The decoder monitor must be held. nsresult EnqueueDecodeMetadataTask(); // Dispatches a LoadedMetadataEvent. // This is threadsafe and can be called on any thread. // The decoder monitor must be held. @@ -654,22 +642,19 @@ protected: // decoded and playable. This is the sum of the number of usecs of audio which // is decoded and in the reader's audio queue, and the usecs of unplayed audio // which has been pushed to the audio hardware for playback. Note that after // calling this, the audio hardware may play some of the audio pushed to // hardware, so this can only be used as a upper bound. The decoder monitor // must be held when calling this. Called on the decode thread. int64_t GetDecodedAudioDuration(); - // Load metadata. Called on the decode thread. The decoder monitor - // must be held with exactly one lock count. - nsresult DecodeMetadata(); - - // Wraps the call to DecodeMetadata(), signals a DecodeError() on failure. - void CallDecodeMetadata(); + // Promise callbacks for metadata reading. + void OnMetadataRead(MetadataHolder* aMetadata); + void OnMetadataNotRead(ReadMetadataFailureReason aReason); // Initiate first content decoding. Called on the state machine thread. // The decoder monitor must be held with exactly one lock count. nsresult DecodeFirstFrame(); // Wraps the call to DecodeFirstFrame(), signals a DecodeError() on failure. void CallDecodeFirstFrame(); @@ -728,20 +713,16 @@ protected: // Called by the AudioSink to signal that all outstanding work is complete // and the sink is shutting down. void OnAudioSinkComplete(); // Called by the AudioSink to signal errors. void OnAudioSinkError(); - // The state machine may move into DECODING_METADATA if we are in - // DECODER_STATE_WAIT_FOR_RESOURCES. - void DoNotifyWaitingForResourcesStatusChanged(); - // Return true if the video decoder's decode speed can not catch up the // play time. bool NeedToSkipToNextKeyframe(); // The decoder object that created this state machine. The state machine // holds a strong reference to the decoder to ensure that the decoder stays // alive once media element has started the decoder shutdown process, and has // dropped its reference to the decoder. This enables the state machine to @@ -1181,20 +1162,25 @@ protected: // Track the current seek promise made by the reader. MediaPromiseConsumerHolder<MediaDecoderReader::SeekPromise> mSeekRequest; // We record the playback position before we seek in order to // determine where the seek terminated relative to the playback position // we were at before the seek. int64_t mCurrentTimeBeforeSeek; + // Track our request for metadata from the reader. + MediaPromiseConsumerHolder<MediaDecoderReader::MetadataPromise> mMetadataRequest; + // Stores presentation info required for playback. The decoder monitor // must be held when accessing this. MediaInfo mInfo; + nsAutoPtr<MetadataTags> mMetadataTags; + mozilla::MediaMetadataManager mMetadataManager; MediaDecoderOwner::NextFrameStatus mLastFrameStatus; mozilla::RollingMean<uint32_t, uint32_t> mCorruptFrames; bool mDisabledHardwareAcceleration;
--- a/dom/media/fmp4/AVCCDecoderModule.cpp +++ b/dom/media/fmp4/AVCCDecoderModule.cpp @@ -269,18 +269,18 @@ already_AddRefed<MediaDataDecoder> AVCCDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig, layers::LayersBackend aLayersBackend, layers::ImageContainer* aImageContainer, FlushableMediaTaskQueue* aVideoTaskQueue, MediaDataDecoderCallback* aCallback) { nsRefPtr<MediaDataDecoder> decoder; - if ((strcmp(aConfig.mime_type, "video/avc") && - strcmp(aConfig.mime_type, "video/mp4")) || + if ((!aConfig.mime_type.EqualsLiteral("video/avc") && + !aConfig.mime_type.EqualsLiteral("video/mp4")) || !mPDM->DecoderNeedsAVCC(aConfig)) { // There is no need for an AVCC wrapper for non-AVC content. decoder = mPDM->CreateVideoDecoder(aConfig, aLayersBackend, aImageContainer, aVideoTaskQueue, aCallback); } else { @@ -300,20 +300,20 @@ AVCCDecoderModule::CreateAudioDecoder(co MediaDataDecoderCallback* aCallback) { return mPDM->CreateAudioDecoder(aConfig, aAudioTaskQueue, aCallback); } bool -AVCCDecoderModule::SupportsAudioMimeType(const char* aMimeType) +AVCCDecoderModule::SupportsAudioMimeType(const nsACString& aMimeType) { return mPDM->SupportsAudioMimeType(aMimeType); } bool -AVCCDecoderModule::SupportsVideoMimeType(const char* aMimeType) +AVCCDecoderModule::SupportsVideoMimeType(const nsACString& aMimeType) { return mPDM->SupportsVideoMimeType(aMimeType); } } // namespace mozilla
--- a/dom/media/fmp4/AVCCDecoderModule.h +++ b/dom/media/fmp4/AVCCDecoderModule.h @@ -36,18 +36,18 @@ public: FlushableMediaTaskQueue* aVideoTaskQueue, MediaDataDecoderCallback* aCallback) override; virtual already_AddRefed<MediaDataDecoder> CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig, FlushableMediaTaskQueue* aAudioTaskQueue, MediaDataDecoderCallback* aCallback) override; - virtual bool SupportsAudioMimeType(const char* aMimeType) override; - virtual bool SupportsVideoMimeType(const char* aMimeType) override; + virtual bool SupportsAudioMimeType(const nsACString& aMimeType) override; + virtual bool SupportsVideoMimeType(const nsACString& aMimeType) override; private: nsRefPtr<PlatformDecoderModule> mPDM; }; } // namespace mozilla #endif // mozilla_AVCCDecoderModule_h
--- a/dom/media/fmp4/BlankDecoderModule.cpp +++ b/dom/media/fmp4/BlankDecoderModule.cpp @@ -233,17 +233,17 @@ public: nsRefPtr<MediaDataDecoder> decoder = new BlankMediaDataDecoder<BlankAudioDataCreator>(creator, aAudioTaskQueue, aCallback); return decoder.forget(); } virtual bool - SupportsAudioMimeType(const char* aMimeType) override + SupportsAudioMimeType(const nsACString& aMimeType) override { return true; } }; already_AddRefed<PlatformDecoderModule> CreateBlankDecoderModule() {
--- a/dom/media/fmp4/MP4Reader.cpp +++ b/dom/media/fmp4/MP4Reader.cpp @@ -306,29 +306,29 @@ MP4Reader::ExtractCryptoInitData(nsTArra MOZ_ASSERT(mCrypto.valid); const nsTArray<mp4_demuxer::PsshInfo>& psshs = mCrypto.pssh; for (uint32_t i = 0; i < psshs.Length(); i++) { aInitData.AppendElements(psshs[i].data); } } bool -MP4Reader::IsSupportedAudioMimeType(const char* aMimeType) +MP4Reader::IsSupportedAudioMimeType(const nsACString& aMimeType) { - return (!strcmp(aMimeType, "audio/mpeg") || - !strcmp(aMimeType, "audio/mp4a-latm")) && + return (aMimeType.EqualsLiteral("audio/mpeg") || + aMimeType.EqualsLiteral("audio/mp4a-latm")) && mPlatform->SupportsAudioMimeType(aMimeType); } bool -MP4Reader::IsSupportedVideoMimeType(const char* aMimeType) +MP4Reader::IsSupportedVideoMimeType(const nsACString& aMimeType) { - return (!strcmp(aMimeType, "video/mp4") || - !strcmp(aMimeType, "video/avc") || - !strcmp(aMimeType, "video/x-vnd.on2.vp6")) && + return (aMimeType.EqualsLiteral("video/mp4") || + aMimeType.EqualsLiteral("video/avc") || + aMimeType.EqualsLiteral("video/x-vnd.on2.vp6")) && mPlatform->SupportsVideoMimeType(aMimeType); } void MP4Reader::PreReadMetadata() { if (mPlatform) { RequestCodecResource();
--- a/dom/media/fmp4/MP4Reader.h +++ b/dom/media/fmp4/MP4Reader.h @@ -128,18 +128,18 @@ private: // DecoderCallback proxies the MediaDataDecoderCallback calls to these // functions. void Output(mp4_demuxer::TrackType aType, MediaData* aSample); void InputExhausted(mp4_demuxer::TrackType aTrack); void Error(mp4_demuxer::TrackType aTrack); void Flush(mp4_demuxer::TrackType aTrack); void DrainComplete(mp4_demuxer::TrackType aTrack); void UpdateIndex(); - bool IsSupportedAudioMimeType(const char* aMimeType); - bool IsSupportedVideoMimeType(const char* aMimeType); + bool IsSupportedAudioMimeType(const nsACString& aMimeType); + bool IsSupportedVideoMimeType(const nsACString& aMimeType); void NotifyResourcesStatusChanged(); void RequestCodecResource(); virtual bool IsWaitingOnCDMResource() override; Microseconds GetNextKeyframeTime(); bool ShouldSkip(bool aSkipToNextKeyframe, int64_t aTimeThreshold); size_t SizeOfQueue(TrackType aTrack);
--- a/dom/media/fmp4/PlatformDecoderModule.cpp +++ b/dom/media/fmp4/PlatformDecoderModule.cpp @@ -175,25 +175,25 @@ PlatformDecoderModule::CreatePDM() if (sGMPDecoderEnabled) { nsRefPtr<PlatformDecoderModule> m(new AVCCDecoderModule(new GMPDecoderModule())); return m.forget(); } return nullptr; } bool -PlatformDecoderModule::SupportsAudioMimeType(const char* aMimeType) +PlatformDecoderModule::SupportsAudioMimeType(const nsACString& aMimeType) { - return !strcmp(aMimeType, "audio/mp4a-latm"); + return aMimeType.EqualsLiteral("audio/mp4a-latm"); } bool -PlatformDecoderModule::SupportsVideoMimeType(const char* aMimeType) +PlatformDecoderModule::SupportsVideoMimeType(const nsACString& aMimeType) { - return !strcmp(aMimeType, "video/mp4") || !strcmp(aMimeType, "video/avc"); + return aMimeType.EqualsLiteral("video/mp4") || aMimeType.EqualsLiteral("video/avc"); } bool PlatformDecoderModule::DecoderNeedsAVCC(const mp4_demuxer::VideoDecoderConfig& aConfig) { return false; }
--- a/dom/media/fmp4/PlatformDecoderModule.h +++ b/dom/media/fmp4/PlatformDecoderModule.h @@ -117,18 +117,18 @@ public: virtual already_AddRefed<MediaDataDecoder> CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig, FlushableMediaTaskQueue* aAudioTaskQueue, MediaDataDecoderCallback* aCallback) = 0; // An audio decoder module must support AAC by default. // If more audio codec is to be supported, SupportsAudioMimeType will have // to be extended - virtual bool SupportsAudioMimeType(const char* aMimeType); - virtual bool SupportsVideoMimeType(const char* aMimeType); + virtual bool SupportsAudioMimeType(const nsACString& aMimeType); + virtual bool SupportsVideoMimeType(const nsACString& aMimeType); // Indicates if the video decoder requires AVCC format. virtual bool DecoderNeedsAVCC(const mp4_demuxer::VideoDecoderConfig& aConfig); virtual void DisableHardwareAcceleration() {} virtual bool SupportsSharedDecoders(const mp4_demuxer::VideoDecoderConfig& aConfig) const { return true;
--- a/dom/media/fmp4/android/AndroidDecoderModule.cpp +++ b/dom/media/fmp4/android/AndroidDecoderModule.cpp @@ -13,34 +13,31 @@ #include "MediaData.h" #include "mp4_demuxer/AnnexB.h" #include "mp4_demuxer/DecoderData.h" #include "nsThreadUtils.h" #include "nsAutoPtr.h" +#include "nsPromiseFlatString.h" #include <jni.h> #include <string.h> using namespace mozilla; using namespace mozilla::gl; using namespace mozilla::widget::sdk; namespace mozilla { -static MediaCodec::LocalRef CreateDecoder(const char* aMimeType) +static MediaCodec::LocalRef CreateDecoder(const nsACString& aMimeType) { - if (!aMimeType) { - return nullptr; - } - MediaCodec::LocalRef codec; - NS_ENSURE_SUCCESS(MediaCodec::CreateDecoderByType(aMimeType, &codec), nullptr); + NS_ENSURE_SUCCESS(MediaCodec::CreateDecoderByType(PromiseFlatCString(aMimeType).get(), &codec), nullptr); return codec; } class VideoDataDecoder : public MediaCodecDataDecoder { public: VideoDataDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig, MediaFormat::Param aFormat, MediaDataDecoderCallback* aCallback, layers::ImageContainer* aImageContainer) @@ -246,17 +243,17 @@ public: numChannels, sampleRate); mCallback->Output(data); return NS_OK; } }; -bool AndroidDecoderModule::SupportsAudioMimeType(const char* aMimeType) { +bool AndroidDecoderModule::SupportsAudioMimeType(const nsACString& aMimeType) { return static_cast<bool>(CreateDecoder(aMimeType)); } already_AddRefed<MediaDataDecoder> AndroidDecoderModule::CreateVideoDecoder( const mp4_demuxer::VideoDecoderConfig& aConfig, layers::LayersBackend aLayersBackend, layers::ImageContainer* aImageContainer, @@ -295,21 +292,21 @@ AndroidDecoderModule::CreateAudioDecoder nsRefPtr<MediaDataDecoder> decoder = new AudioDataDecoder(aConfig, format, aCallback); return decoder.forget(); } MediaCodecDataDecoder::MediaCodecDataDecoder(MediaData::Type aType, - const char* aMimeType, + const nsACString& aMimeType, MediaFormat::Param aFormat, MediaDataDecoderCallback* aCallback) : mType(aType) - , mMimeType(strdup(aMimeType)) + , mMimeType(aMimeType) , mFormat(aFormat) , mCallback(aCallback) , mInputBuffers(nullptr) , mOutputBuffers(nullptr) , mMonitor("MediaCodecDataDecoder::mMonitor") , mFlushing(false) , mDraining(false) , mStopping(false)
--- a/dom/media/fmp4/android/AndroidDecoderModule.h +++ b/dom/media/fmp4/android/AndroidDecoderModule.h @@ -30,41 +30,41 @@ public: CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig, FlushableMediaTaskQueue* aAudioTaskQueue, MediaDataDecoderCallback* aCallback) override; AndroidDecoderModule() {} virtual ~AndroidDecoderModule() {} - virtual bool SupportsAudioMimeType(const char* aMimeType) override; + virtual bool SupportsAudioMimeType(const nsACString& aMimeType) override; }; class MediaCodecDataDecoder : public MediaDataDecoder { public: MediaCodecDataDecoder(MediaData::Type aType, - const char* aMimeType, + const nsACString& aMimeType, widget::sdk::MediaFormat::Param aFormat, MediaDataDecoderCallback* aCallback); virtual ~MediaCodecDataDecoder(); virtual nsresult Init() override; virtual nsresult Flush() override; virtual nsresult Drain() override; virtual nsresult Shutdown() override; virtual nsresult Input(mp4_demuxer::MP4Sample* aSample); protected: friend class AndroidDecoderModule; MediaData::Type mType; - nsAutoPtr<char> mMimeType; + nsAutoCString mMimeType; widget::sdk::MediaFormat::GlobalRef mFormat; MediaDataDecoderCallback* mCallback; widget::sdk::MediaCodec::GlobalRef mDecoder; jni::ObjectArray::GlobalRef mInputBuffers; jni::ObjectArray::GlobalRef mOutputBuffers;
--- a/dom/media/fmp4/apple/AppleATDecoder.cpp +++ b/dom/media/fmp4/apple/AppleATDecoder.cpp @@ -30,24 +30,24 @@ AppleATDecoder::AppleATDecoder(const mp4 , mTaskQueue(aAudioTaskQueue) , mCallback(aCallback) , mConverter(nullptr) , mStream(nullptr) { MOZ_COUNT_CTOR(AppleATDecoder); LOG("Creating Apple AudioToolbox decoder"); LOG("Audio Decoder configuration: %s %d Hz %d channels %d bits per channel", - mConfig.mime_type, + mConfig.mime_type.get(), mConfig.samples_per_second, mConfig.channel_count, mConfig.bits_per_sample); - if (!strcmp(mConfig.mime_type, "audio/mpeg")) { + if (mConfig.mime_type.EqualsLiteral("audio/mpeg")) { mFormatID = kAudioFormatMPEGLayer3; - } else if (!strcmp(mConfig.mime_type, "audio/mp4a-latm")) { + } else if (mConfig.mime_type.EqualsLiteral("audio/mp4a-latm")) { mFormatID = kAudioFormatMPEG4AAC; } else { mFormatID = 0; } } AppleATDecoder::~AppleATDecoder() {
--- a/dom/media/fmp4/apple/AppleDecoderModule.cpp +++ b/dom/media/fmp4/apple/AppleDecoderModule.cpp @@ -187,19 +187,19 @@ AppleDecoderModule::CreateAudioDecoder(c MediaDataDecoderCallback* aCallback) { nsRefPtr<MediaDataDecoder> decoder = new AppleATDecoder(aConfig, aAudioTaskQueue, aCallback); return decoder.forget(); } bool -AppleDecoderModule::SupportsAudioMimeType(const char* aMimeType) +AppleDecoderModule::SupportsAudioMimeType(const nsACString& aMimeType) { - return !strcmp(aMimeType, "audio/mp4a-latm") || !strcmp(aMimeType, "audio/mpeg"); + return aMimeType.EqualsLiteral("audio/mp4a-latm") || aMimeType.EqualsLiteral("audio/mpeg"); } bool AppleDecoderModule::DecoderNeedsAVCC(const mp4_demuxer::VideoDecoderConfig& aConfig) { return true; }
--- a/dom/media/fmp4/apple/AppleDecoderModule.h +++ b/dom/media/fmp4/apple/AppleDecoderModule.h @@ -27,17 +27,17 @@ public: MediaDataDecoderCallback* aCallback) override; // Decode thread. virtual already_AddRefed<MediaDataDecoder> CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig, FlushableMediaTaskQueue* aAudioTaskQueue, MediaDataDecoderCallback* aCallback) override; - virtual bool SupportsAudioMimeType(const char* aMimeType) override; + virtual bool SupportsAudioMimeType(const nsACString& aMimeType) override; virtual bool DecoderNeedsAVCC(const mp4_demuxer::VideoDecoderConfig& aConfig) override; static void Init(); static nsresult CanDecode(); private: friend class InitTask;
--- a/dom/media/fmp4/ffmpeg/FFmpegAudioDecoder.cpp +++ b/dom/media/fmp4/ffmpeg/FFmpegAudioDecoder.cpp @@ -165,23 +165,23 @@ nsresult FFmpegAudioDecoder<LIBAV_VER>::Drain() { mTaskQueue->AwaitIdle(); mCallback->DrainComplete(); return Flush(); } AVCodecID -FFmpegAudioDecoder<LIBAV_VER>::GetCodecId(const char* aMimeType) +FFmpegAudioDecoder<LIBAV_VER>::GetCodecId(const nsACString& aMimeType) { - if (!strcmp(aMimeType, "audio/mpeg")) { + if (aMimeType.EqualsLiteral("audio/mpeg")) { return AV_CODEC_ID_MP3; } - if (!strcmp(aMimeType, "audio/mp4a-latm")) { + if (aMimeType.EqualsLiteral("audio/mp4a-latm")) { return AV_CODEC_ID_AAC; } return AV_CODEC_ID_NONE; } FFmpegAudioDecoder<LIBAV_VER>::~FFmpegAudioDecoder() {
--- a/dom/media/fmp4/ffmpeg/FFmpegAudioDecoder.h +++ b/dom/media/fmp4/ffmpeg/FFmpegAudioDecoder.h @@ -3,17 +3,16 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef __FFmpegAACDecoder_h__ #define __FFmpegAACDecoder_h__ #include "FFmpegDataDecoder.h" -#include "mp4_demuxer/DecoderData.h" namespace mozilla { template <int V> class FFmpegAudioDecoder { }; @@ -24,17 +23,17 @@ public: FFmpegAudioDecoder(FlushableMediaTaskQueue* aTaskQueue, MediaDataDecoderCallback* aCallback, const mp4_demuxer::AudioDecoderConfig& aConfig); virtual ~FFmpegAudioDecoder(); virtual nsresult Init() override; virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) override; virtual nsresult Drain() override; - static AVCodecID GetCodecId(const char* aMimeType); + static AVCodecID GetCodecId(const nsACString& aMimeType); private: void DecodePacket(mp4_demuxer::MP4Sample* aSample); MediaDataDecoderCallback* mCallback; }; } // namespace mozilla
--- a/dom/media/fmp4/ffmpeg/FFmpegDecoderModule.h +++ b/dom/media/fmp4/ffmpeg/FFmpegDecoderModule.h @@ -46,22 +46,22 @@ public: FlushableMediaTaskQueue* aAudioTaskQueue, MediaDataDecoderCallback* aCallback) override { nsRefPtr<MediaDataDecoder> decoder = new FFmpegAudioDecoder<V>(aAudioTaskQueue, aCallback, aConfig); return decoder.forget(); } - virtual bool SupportsAudioMimeType(const char* aMimeType) override + virtual bool SupportsAudioMimeType(const nsACString& aMimeType) override { return FFmpegAudioDecoder<V>::GetCodecId(aMimeType) != AV_CODEC_ID_NONE; } - virtual bool SupportsVideoMimeType(const char* aMimeType) override + virtual bool SupportsVideoMimeType(const nsACString& aMimeType) override { return FFmpegH264Decoder<V>::GetCodecId(aMimeType) != AV_CODEC_ID_NONE; } virtual bool DecoderNeedsAVCC(const mp4_demuxer::VideoDecoderConfig& aConfig) override { return true; }
--- a/dom/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp +++ b/dom/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp @@ -282,22 +282,22 @@ FFmpegH264Decoder<LIBAV_VER>::Flush() } FFmpegH264Decoder<LIBAV_VER>::~FFmpegH264Decoder() { MOZ_COUNT_DTOR(FFmpegH264Decoder); } AVCodecID -FFmpegH264Decoder<LIBAV_VER>::GetCodecId(const char* aMimeType) +FFmpegH264Decoder<LIBAV_VER>::GetCodecId(const nsACString& aMimeType) { - if (!strcmp(aMimeType, "video/avc") || !strcmp(aMimeType, "video/mp4")) { + if (aMimeType.EqualsLiteral("video/avc") || aMimeType.EqualsLiteral("video/mp4")) { return AV_CODEC_ID_H264; } - if (!strcmp(aMimeType, "video/x-vnd.on2.vp6")) { + if (aMimeType.EqualsLiteral("video/x-vnd.on2.vp6")) { return AV_CODEC_ID_VP6F; } return AV_CODEC_ID_NONE; } } // namespace mozilla
--- a/dom/media/fmp4/ffmpeg/FFmpegH264Decoder.h +++ b/dom/media/fmp4/ffmpeg/FFmpegH264Decoder.h @@ -35,17 +35,17 @@ public: const mp4_demuxer::VideoDecoderConfig& aConfig, ImageContainer* aImageContainer); virtual ~FFmpegH264Decoder(); virtual nsresult Init() override; virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) override; virtual nsresult Drain() override; virtual nsresult Flush() override; - static AVCodecID GetCodecId(const char* aMimeType); + static AVCodecID GetCodecId(const nsACString& aMimeType); private: void DecodeFrame(mp4_demuxer::MP4Sample* aSample); DecodeResult DoDecodeFrame(mp4_demuxer::MP4Sample* aSample); void DoDrain(); void OutputDelayedFrames(); /**
--- a/dom/media/fmp4/gmp/GMPDecoderModule.cpp +++ b/dom/media/fmp4/gmp/GMPDecoderModule.cpp @@ -41,17 +41,17 @@ CreateDecoderWrapper(MediaDataDecoderCal already_AddRefed<MediaDataDecoder> GMPDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig, layers::LayersBackend aLayersBackend, layers::ImageContainer* aImageContainer, FlushableMediaTaskQueue* aVideoTaskQueue, MediaDataDecoderCallback* aCallback) { - if (strcmp(aConfig.mime_type, "video/avc") != 0) { + if (!aConfig.mime_type.EqualsLiteral("video/avc")) { return nullptr; } nsRefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback); wrapper->SetProxyTarget(new GMPVideoDecoder(aConfig, aLayersBackend, aImageContainer, aVideoTaskQueue, @@ -59,17 +59,17 @@ GMPDecoderModule::CreateVideoDecoder(con return wrapper.forget(); } already_AddRefed<MediaDataDecoder> GMPDecoderModule::CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig, FlushableMediaTaskQueue* aAudioTaskQueue, MediaDataDecoderCallback* aCallback) { - if (strcmp(aConfig.mime_type, "audio/mp4a-latm") != 0) { + if (!aConfig.mime_type.EqualsLiteral("audio/mp4a-latm")) { return nullptr; } nsRefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback); wrapper->SetProxyTarget(new GMPAudioDecoder(aConfig, aAudioTaskQueue, wrapper->Callback())); return wrapper.forget();
--- a/dom/media/fmp4/gonk/GonkAudioDecoderManager.cpp +++ b/dom/media/fmp4/gonk/GonkAudioDecoderManager.cpp @@ -46,17 +46,17 @@ GonkAudioDecoderManager::GonkAudioDecode , mUseAdts(true) , mAudioBuffer(nullptr) { MOZ_COUNT_CTOR(GonkAudioDecoderManager); MOZ_ASSERT(mAudioChannels); mUserData.AppendElements(aConfig.audio_specific_config->Elements(), aConfig.audio_specific_config->Length()); // Pass through mp3 without applying an ADTS header. - if (strcmp(aConfig.mime_type, "audio/mp4a-latm") != 0) { + if (!aConfig.mime_type.EqualsLiteral("audio/mp4a-latm")) { mUseAdts = false; } } GonkAudioDecoderManager::~GonkAudioDecoderManager() { MOZ_COUNT_DTOR(GonkAudioDecoderManager); }
--- a/dom/media/fmp4/wmf/WMFAudioMFTManager.cpp +++ b/dom/media/fmp4/wmf/WMFAudioMFTManager.cpp @@ -72,19 +72,19 @@ WMFAudioMFTManager::WMFAudioMFTManager( : mAudioChannels(aConfig.channel_count) , mAudioRate(aConfig.samples_per_second) , mAudioFrameOffset(0) , mAudioFrameSum(0) , mMustRecaptureAudioPosition(true) { MOZ_COUNT_CTOR(WMFAudioMFTManager); - if (!strcmp(aConfig.mime_type, "audio/mpeg")) { + if (aConfig.mime_type.EqualsLiteral("audio/mpeg")) { mStreamType = MP3; - } else if (!strcmp(aConfig.mime_type, "audio/mp4a-latm")) { + } else if (aConfig.mime_type.EqualsLiteral("audio/mp4a-latm")) { mStreamType = AAC; AACAudioSpecificConfigToUserData(aConfig.aac_profile, aConfig.audio_specific_config->Elements(), aConfig.audio_specific_config->Length(), mUserData); } else { mStreamType = Unknown; }
--- a/dom/media/fmp4/wmf/WMFDecoderModule.cpp +++ b/dom/media/fmp4/wmf/WMFDecoderModule.cpp @@ -113,29 +113,29 @@ bool WMFDecoderModule::SupportsSharedDecoders(const mp4_demuxer::VideoDecoderConfig& aConfig) const { // If DXVA is enabled, but we're not going to use it for this specific config, then // we can't use the shared decoder. return !sDXVAEnabled || ShouldUseDXVA(aConfig); } bool -WMFDecoderModule::SupportsVideoMimeType(const char* aMimeType) +WMFDecoderModule::SupportsVideoMimeType(const nsACString& aMimeType) { - return !strcmp(aMimeType, "video/mp4") || - !strcmp(aMimeType, "video/avc") || - !strcmp(aMimeType, "video/webm; codecs=vp8") || - !strcmp(aMimeType, "video/webm; codecs=vp9"); + return aMimeType.EqualsLiteral("video/mp4") || + aMimeType.EqualsLiteral("video/avc") || + aMimeType.EqualsLiteral("video/webm; codecs=vp8") || + aMimeType.EqualsLiteral("video/webm; codecs=vp9"); } bool -WMFDecoderModule::SupportsAudioMimeType(const char* aMimeType) +WMFDecoderModule::SupportsAudioMimeType(const nsACString& aMimeType) { - return !strcmp(aMimeType, "audio/mp4a-latm") || - !strcmp(aMimeType, "audio/mpeg"); + return aMimeType.EqualsLiteral("audio/mp4a-latm") || + aMimeType.EqualsLiteral("audio/mpeg"); } static bool ClassesRootRegKeyExists(const nsAString& aRegKeyPath) { nsresult rv; nsCOMPtr<nsIWindowsRegKey> regKey =
--- a/dom/media/fmp4/wmf/WMFDecoderModule.h +++ b/dom/media/fmp4/wmf/WMFDecoderModule.h @@ -26,18 +26,18 @@ public: FlushableMediaTaskQueue* aVideoTaskQueue, MediaDataDecoderCallback* aCallback) override; virtual already_AddRefed<MediaDataDecoder> CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig, FlushableMediaTaskQueue* aAudioTaskQueue, MediaDataDecoderCallback* aCallback) override; - bool SupportsVideoMimeType(const char* aMimeType) override; - bool SupportsAudioMimeType(const char* aMimeType) override; + bool SupportsVideoMimeType(const nsACString& aMimeType) override; + bool SupportsAudioMimeType(const nsACString& aMimeType) override; virtual void DisableHardwareAcceleration() override { sDXVAEnabled = false; } virtual bool SupportsSharedDecoders(const mp4_demuxer::VideoDecoderConfig& aConfig) const override;
--- a/dom/media/fmp4/wmf/WMFVideoMFTManager.cpp +++ b/dom/media/fmp4/wmf/WMFVideoMFTManager.cpp @@ -79,22 +79,22 @@ WMFVideoMFTManager::WMFVideoMFTManager( // mVideoStride, mVideoWidth, mVideoHeight, mUseHwAccel are initialized in // Init(). { NS_ASSERTION(!NS_IsMainThread(), "Should not be on main thread."); MOZ_ASSERT(mImageContainer); MOZ_COUNT_CTOR(WMFVideoMFTManager); // Need additional checks/params to check vp8/vp9 - if (!strcmp(aConfig.mime_type, "video/mp4") || - !strcmp(aConfig.mime_type, "video/avc")) { + if (aConfig.mime_type.EqualsLiteral("video/mp4") || + aConfig.mime_type.EqualsLiteral("video/avc")) { mStreamType = H264; - } else if (!strcmp(aConfig.mime_type, "video/webm; codecs=vp8")) { + } else if (aConfig.mime_type.EqualsLiteral("video/webm; codecs=vp8")) { mStreamType = VP8; - } else if (!strcmp(aConfig.mime_type, "video/webm; codecs=vp9")) { + } else if (aConfig.mime_type.EqualsLiteral("video/webm; codecs=vp9")) { mStreamType = VP9; } else { mStreamType = Unknown; } } WMFVideoMFTManager::~WMFVideoMFTManager() {
--- a/dom/media/mediasource/MediaSourceReader.cpp +++ b/dom/media/mediasource/MediaSourceReader.cpp @@ -66,17 +66,19 @@ MediaSourceReader::MediaSourceReader(Med void MediaSourceReader::PrepareInitialization() { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); MSE_DEBUG("trackBuffers=%u", mTrackBuffers.Length()); mEssentialTrackBuffers.AppendElements(mTrackBuffers); mHasEssentialTrackBuffers = true; - mDecoder->NotifyWaitingForResourcesStatusChanged(); + if (!IsWaitingMediaResources()) { + mDecoder->NotifyWaitingForResourcesStatusChanged(); + } } bool MediaSourceReader::IsWaitingMediaResources() { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); for (uint32_t i = 0; i < mEssentialTrackBuffers.Length(); ++i) { @@ -756,17 +758,20 @@ MediaSourceReader::OnTrackBufferConfigur if (aInfo.HasAudio() && !mAudioTrack) { MSE_DEBUG("%p audio", aTrackBuffer); mAudioTrack = aTrackBuffer; } if (aInfo.HasVideo() && !mVideoTrack) { MSE_DEBUG("%p video", aTrackBuffer); mVideoTrack = aTrackBuffer; } - mDecoder->NotifyWaitingForResourcesStatusChanged(); + + if (!IsWaitingMediaResources()) { + mDecoder->NotifyWaitingForResourcesStatusChanged(); + } } bool MediaSourceReader::TrackBuffersContainTime(int64_t aTime) { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); if (mAudioTrack && !mAudioTrack->ContainsTime(aTime, EOS_FUZZ_US)) { return false;
--- a/dom/media/webm/IntelWebMVideoDecoder.cpp +++ b/dom/media/webm/IntelWebMVideoDecoder.cpp @@ -107,20 +107,20 @@ IntelWebMVideoDecoder::Create(WebMReader decoder->mTaskQueue = aReader->GetVideoTaskQueue(); NS_ENSURE_TRUE(decoder->mTaskQueue, nullptr); return decoder.forget(); } bool -IntelWebMVideoDecoder::IsSupportedVideoMimeType(const char* aMimeType) +IntelWebMVideoDecoder::IsSupportedVideoMimeType(const nsACString& aMimeType) { - return (!strcmp(aMimeType, "video/webm; codecs=vp8") || - !strcmp(aMimeType, "video/webm; codecs=vp9")) && + return (aMimeType.EqualsLiteral("video/webm; codecs=vp8") || + aMimeType.EqualsLiteral("video/webm; codecs=vp9")) && mPlatform->SupportsVideoMimeType(aMimeType); } nsresult IntelWebMVideoDecoder::Init(unsigned int aWidth, unsigned int aHeight) { mPlatform = PlatformDecoderModule::Create(); if (!mPlatform) {
--- a/dom/media/webm/IntelWebMVideoDecoder.h +++ b/dom/media/webm/IntelWebMVideoDecoder.h @@ -49,17 +49,17 @@ private: void InitLayersBackendType(); bool Decode(); bool Demux(nsAutoPtr<VP8Sample>& aSample, bool* aEOS); bool SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed); - bool IsSupportedVideoMimeType(const char* aMimeType); + bool IsSupportedVideoMimeType(const nsACString& aMimeType); VP8Sample* PopSample(); nsRefPtr<WebMReader> mReader; nsRefPtr<PlatformDecoderModule> mPlatform; nsRefPtr<MediaDataDecoder> mMediaDataDecoder; // TaskQueue on which decoder can choose to decode.
--- a/dom/security/nsCORSListenerProxy.cpp +++ b/dom/security/nsCORSListenerProxy.cpp @@ -4,16 +4,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/Assertions.h" #include "mozilla/LinkedList.h" #include "nsCORSListenerProxy.h" #include "nsIChannel.h" #include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" #include "nsError.h" #include "nsContentUtils.h" #include "nsIScriptSecurityManager.h" #include "nsNetUtil.h" #include "nsMimeTypes.h" #include "nsIStreamConverterService.h" #include "nsStringStream.h" #include "nsGkAtoms.h" @@ -814,16 +815,32 @@ nsCORSListenerProxy::UpdateChannel(nsICh bool dataScheme = false; rv = uri->SchemeIs("data", &dataScheme); NS_ENSURE_SUCCESS(rv, rv); if (dataScheme) { return NS_OK; } } + // Set CORS attributes on channel so that intercepted requests get correct + // values. We have to do this here because the CheckMayLoad checks may lead + // to early return. We can't be sure this is an http channel though, so we + // can't return early on failure. + nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aChannel); + if (internal) { + if (mIsPreflight) { + rv = internal->SetCorsMode(nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT); + } else { + rv = internal->SetCorsMode(nsIHttpChannelInternal::CORS_MODE_CORS); + } + NS_ENSURE_SUCCESS(rv, rv); + rv = internal->SetCorsIncludeCredentials(mWithCredentials); + NS_ENSURE_SUCCESS(rv, rv); + } + // Check that the uri is ok to load rv = nsContentUtils::GetSecurityManager()-> CheckLoadURIWithPrincipal(mRequestingPrincipal, uri, nsIScriptSecurityManager::STANDARD); NS_ENSURE_SUCCESS(rv, rv); if (originalURI != uri) { rv = nsContentUtils::GetSecurityManager()->
--- a/dom/tests/mochitest/fetch/test_request.js +++ b/dom/tests/mochitest/fetch/test_request.js @@ -2,48 +2,52 @@ function testDefaultCtor() { var req = new Request(""); is(req.method, "GET", "Default Request method is GET"); ok(req.headers instanceof Headers, "Request should have non-null Headers object"); is(req.url, self.location.href, "URL should be resolved with entry settings object's API base URL"); is(req.context, "fetch", "Default context is fetch."); is(req.referrer, "about:client", "Default referrer is `client` which serializes to about:client."); is(req.mode, "cors", "Request mode for string input is cors"); is(req.credentials, "omit", "Default Request credentials is omit"); + is(req.cache, "default", "Default Request cache is default"); var req = new Request(req); is(req.method, "GET", "Default Request method is GET"); ok(req.headers instanceof Headers, "Request should have non-null Headers object"); is(req.url, self.location.href, "URL should be resolved with entry settings object's API base URL"); is(req.context, "fetch", "Default context is fetch."); is(req.referrer, "about:client", "Default referrer is `client` which serializes to about:client."); is(req.mode, "cors", "Request mode string input is cors"); is(req.credentials, "omit", "Default Request credentials is omit"); + is(req.cache, "default", "Default Request cache is default"); } function testClone() { var orig = new Request("./cloned_request.txt", { method: 'POST', headers: { "Content-Length": 5 }, body: "Sample body", mode: "same-origin", credentials: "same-origin", + cache: "no-store", }); var clone = orig.clone(); ok(clone.method === "POST", "Request method is POST"); ok(clone.headers instanceof Headers, "Request should have non-null Headers object"); is(clone.headers.get('content-length'), "5", "Response content-length should be 5."); orig.headers.set('content-length', 6); is(clone.headers.get('content-length'), "5", "Request content-length should be 5."); ok(clone.url === (new URL("./cloned_request.txt", self.location.href)).href, "URL should be resolved with entry settings object's API base URL"); ok(clone.referrer === "about:client", "Default referrer is `client` which serializes to about:client."); ok(clone.mode === "same-origin", "Request mode is same-origin"); ok(clone.credentials === "same-origin", "Default credentials is same-origin"); + ok(clone.cache === "no-store", "Default cache is no-store"); ok(!orig.bodyUsed, "Original body is not consumed."); ok(!clone.bodyUsed, "Clone body is not consumed."); var origBody = null; var clone2 = null; return orig.text().then(function (body) { origBody = body;
--- a/dom/tests/mochitest/fetch/test_response.js +++ b/dom/tests/mochitest/fetch/test_response.js @@ -116,35 +116,16 @@ function testOk() { var r3 = new Response("", { status: 299}); ok(r3.ok, "Response with status 299 should have ok true"); var r4 = new Response("", { status: 302}); ok(!r4.ok, "Response with status 302 should have ok false"); } -// It is not possible to test setting finalURL until we have ServiceWorker -// interception. This is because synthetic Responses do not have a url, the url -// is set based on the request, so a SW could initiate a fetch() on behalf of -// a client and set the resulting Response's finalURL before returning it to -// the client, in which case the "set response's url to request's url" from the -// client's point of view would not happen. A test for this will be added by -// Bug 1134352. -function testFinalURL() { - var r1 = new Response(); - ok(!r1.finalURL, "Response.finalURL is false by default."); - - try { - r1.finalURL = true; - ok(false, "Setting Response.finalURL of Response with null url should fail."); - } catch(e) { - ok(true, "Setting Response.finalURL of Response with null url should fail."); - } -} - function testBodyUsed() { var res = new Response("Sample body"); ok(!res.bodyUsed, "bodyUsed is initially false."); return res.text().then((v) => { is(v, "Sample body", "Body should match"); ok(res.bodyUsed, "After reading body, bodyUsed should be true."); }).then(() => { return res.blob().then((v) => { @@ -221,17 +202,16 @@ function testBodyExtraction() { }) } function runTest() { testDefaultCtor(); testError(); testRedirect(); testOk(); - testFinalURL(); return Promise.resolve() .then(testBodyCreation) .then(testBodyUsed) .then(testBodyExtraction) .then(testClone) // Put more promise based tests here. }
--- a/dom/webidl/Performance.webidl +++ b/dom/webidl/Performance.webidl @@ -47,16 +47,23 @@ partial interface Performance { [Pref="dom.enable_resource_timing"] void clearResourceTimings(); [Pref="dom.enable_resource_timing"] void setResourceTimingBufferSize(unsigned long maxSize); [Pref="dom.enable_resource_timing"] attribute EventHandler onresourcetimingbufferfull; }; +// GC microbenchmarks, pref-guarded, not for general use (bug 1125412) +[Exposed=Window] +partial interface Performance { + [Pref="dom.enable_memory_stats"] + readonly attribute object mozMemory; +}; + // http://www.w3.org/TR/user-timing/ [Exposed=Window] partial interface Performance { [Pref="dom.enable_user_timing", Throws] void mark(DOMString markName); [Pref="dom.enable_user_timing"] void clearMarks(optional DOMString markName); [Pref="dom.enable_user_timing", Throws]
--- a/dom/webidl/Response.webidl +++ b/dom/webidl/Response.webidl @@ -12,18 +12,16 @@ interface Response { [NewObject] static Response error(); [Throws, NewObject] static Response redirect(USVString url, optional unsigned short status = 302); readonly attribute ResponseType type; readonly attribute USVString url; - [Throws] - attribute boolean finalURL; readonly attribute unsigned short status; readonly attribute boolean ok; readonly attribute ByteString statusText; [SameObject] readonly attribute Headers headers; [Throws, NewObject] Response clone(); };
--- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -2,16 +2,17 @@ /* vim: set ts=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/. */ #include "ServiceWorkerEvents.h" #include "ServiceWorkerClient.h" +#include "nsIHttpChannelInternal.h" #include "nsINetworkInterceptController.h" #include "nsIOutputStream.h" #include "nsContentUtils.h" #include "nsComponentManagerUtils.h" #include "nsServiceManagerUtils.h" #include "nsStreamUtils.h" #include "nsNetCID.h" #include "nsSerializationHelper.h" @@ -115,36 +116,39 @@ public: if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } mChannel->SynthesizeStatus(mInternalResponse->GetStatus(), mInternalResponse->GetStatusText()); nsAutoTArray<InternalHeaders::Entry, 5> entries; - mInternalResponse->Headers()->GetEntries(entries); + mInternalResponse->UnfilteredHeaders()->GetEntries(entries); for (uint32_t i = 0; i < entries.Length(); ++i) { mChannel->SynthesizeHeader(entries[i].mName, entries[i].mValue); } rv = mChannel->FinishSynthesizedResponse(); NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to finish synthesized response"); return rv; } }; class RespondWithHandler final : public PromiseNativeHandler { nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel; nsMainThreadPtrHandle<ServiceWorker> mServiceWorker; + RequestMode mRequestMode; public: RespondWithHandler(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel, - nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker) + nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker, + RequestMode aRequestMode) : mInterceptedChannel(aChannel) , mServiceWorker(aServiceWorker) + , mRequestMode(aRequestMode) { } void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override; void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override; void CancelRequest(); @@ -208,18 +212,20 @@ RespondWithHandler::ResolvedCallback(JSC } nsRefPtr<Response> response; nsresult rv = UNWRAP_OBJECT(Response, &aValue.toObject(), response); if (NS_FAILED(rv)) { return; } - // FIXME(nsm) Bug 1136200 deal with opaque and no-cors (fetch spec 4.2.2.2). - if (response->Type() == ResponseType::Error) { + // Section 4.2, step 2.2 "If either response's type is "opaque" and request's + // mode is not "no-cors" or response's type is error, return a network error." + if (((response->Type() == ResponseType::Opaque) && (mRequestMode != RequestMode::No_cors)) || + response->Type() == ResponseType::Error) { return; } if (NS_WARN_IF(response->BodyUsed())) { return; } nsRefPtr<InternalResponse> ir = response->GetInternalResponse(); @@ -279,17 +285,18 @@ void FetchEvent::RespondWith(Promise& aPromise, ErrorResult& aRv) { if (mWaitToRespond) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } mWaitToRespond = true; - nsRefPtr<RespondWithHandler> handler = new RespondWithHandler(mChannel, mServiceWorker); + nsRefPtr<RespondWithHandler> handler = + new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode()); aPromise.AppendNativeHandler(handler); } void FetchEvent::RespondWith(Response& aResponse, ErrorResult& aRv) { if (mWaitToRespond) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
--- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -55,16 +55,25 @@ #endif using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::ipc; BEGIN_WORKERS_NAMESPACE +static_assert(nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN == static_cast<uint32_t>(RequestMode::Same_origin), + "RequestMode enumeration value should match Necko CORS mode value."); +static_assert(nsIHttpChannelInternal::CORS_MODE_NO_CORS == static_cast<uint32_t>(RequestMode::No_cors), + "RequestMode enumeration value should match Necko CORS mode value."); +static_assert(nsIHttpChannelInternal::CORS_MODE_CORS == static_cast<uint32_t>(RequestMode::Cors), + "RequestMode enumeration value should match Necko CORS mode value."); +static_assert(nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT == static_cast<uint32_t>(RequestMode::Cors_with_forced_preflight), + "RequestMode enumeration value should match Necko CORS mode value."); + struct ServiceWorkerManager::PendingOperation { nsCOMPtr<nsIRunnable> mRunnable; ServiceWorkerJobQueue* mQueue; nsRefPtr<ServiceWorkerJob> mJob; ServiceWorkerRegistrationData mRegistration; @@ -2126,25 +2135,33 @@ class FetchEventRunnable : public Worker nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel; nsMainThreadPtrHandle<ServiceWorker> mServiceWorker; nsTArray<nsCString> mHeaderNames; nsTArray<nsCString> mHeaderValues; nsAutoPtr<ServiceWorkerClientInfo> mClientInfo; nsCString mSpec; nsCString mMethod; bool mIsReload; + RequestMode mRequestMode; + RequestCredentials mRequestCredentials; public: FetchEventRunnable(WorkerPrivate* aWorkerPrivate, nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel, nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker, - nsAutoPtr<ServiceWorkerClientInfo>& aClientInfo) + nsAutoPtr<ServiceWorkerClientInfo>& aClientInfo, + bool aIsReload) : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount) , mInterceptedChannel(aChannel) , mServiceWorker(aServiceWorker) , mClientInfo(aClientInfo) + , mIsReload(aIsReload) + , mRequestMode(RequestMode::No_cors) + // By default we set it to same-origin since normal HTTP fetches always + // send credentials to same-origin websites unless explicitly forbidden. + , mRequestCredentials(RequestCredentials::Same_origin) { MOZ_ASSERT(aWorkerPrivate); } NS_DECL_ISUPPORTS_INHERITED NS_IMETHOD VisitHeader(const nsACString& aHeader, const nsACString& aValue) override @@ -2152,16 +2169,17 @@ public: mHeaderNames.AppendElement(aHeader); mHeaderValues.AppendElement(aValue); return NS_OK; } nsresult Init() { + AssertIsOnMainThread(); nsCOMPtr<nsIChannel> channel; nsresult rv = mInterceptedChannel->GetChannel(getter_AddRefs(channel)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIURI> uri; rv = channel->GetURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); @@ -2173,18 +2191,45 @@ public: rv = httpChannel->GetRequestMethod(mMethod); NS_ENSURE_SUCCESS(rv, rv); uint32_t loadFlags; rv = channel->GetLoadFlags(&loadFlags); NS_ENSURE_SUCCESS(rv, rv); - //TODO(jdm): we should probably include reload-ness in the loadinfo or as a separate load flag - mIsReload = false; + nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel); + NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE); + + uint32_t mode; + internalChannel->GetCorsMode(&mode); + switch (mode) { + case nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN: + mRequestMode = RequestMode::Same_origin; + break; + case nsIHttpChannelInternal::CORS_MODE_NO_CORS: + mRequestMode = RequestMode::No_cors; + break; + case nsIHttpChannelInternal::CORS_MODE_CORS: + case nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT: + mRequestMode = RequestMode::Cors; + break; + default: + MOZ_CRASH("Unexpected CORS mode"); + } + + if (loadFlags & nsIRequest::LOAD_ANONYMOUS) { + mRequestCredentials = RequestCredentials::Omit; + } else { + bool includeCrossOrigin; + internalChannel->GetCorsIncludeCredentials(&includeCrossOrigin); + if (includeCrossOrigin) { + mRequestCredentials = RequestCredentials::Include; + } + } rv = httpChannel->VisitRequestHeaders(this); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } bool @@ -2239,17 +2284,19 @@ private: } } nsRefPtr<Headers> headers = new Headers(globalObj.GetAsSupports(), internalHeaders); reqInit.mHeaders.Construct(); reqInit.mHeaders.Value().SetAsHeaders() = headers; //TODO(jdm): set request body - //TODO(jdm): set request same-origin mode and credentials + + reqInit.mMode.Construct(mRequestMode); + reqInit.mCredentials.Construct(mRequestCredentials); ErrorResult rv; nsRefPtr<Request> request = Request::Constructor(globalObj, requestInfo, reqInit, rv); if (NS_WARN_IF(rv.Failed())) { return false; } RootedDictionary<FetchEventInit> init(aCx); @@ -2275,17 +2322,18 @@ private: } return true; } }; NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable, nsIHttpHeaderVisitor) NS_IMETHODIMP -ServiceWorkerManager::DispatchFetchEvent(nsIDocument* aDoc, nsIInterceptedChannel* aChannel) +ServiceWorkerManager::DispatchFetchEvent(nsIDocument* aDoc, nsIInterceptedChannel* aChannel, + bool aIsReload) { MOZ_ASSERT(aChannel); nsCOMPtr<nsISupports> serviceWorker; bool isNavigation = false; nsresult rv = aChannel->GetIsNavigation(&isNavigation); NS_ENSURE_SUCCESS(rv, rv); @@ -2325,17 +2373,17 @@ ServiceWorkerManager::DispatchFetchEvent new nsMainThreadPtrHolder<nsIInterceptedChannel>(aChannel, false)); nsRefPtr<ServiceWorker> sw = static_cast<ServiceWorker*>(serviceWorker.get()); nsMainThreadPtrHandle<ServiceWorker> serviceWorkerHandle( new nsMainThreadPtrHolder<ServiceWorker>(sw)); // clientInfo is null if we don't have a controlled document nsRefPtr<FetchEventRunnable> event = - new FetchEventRunnable(sw->GetWorkerPrivate(), handle, serviceWorkerHandle, clientInfo); + new FetchEventRunnable(sw->GetWorkerPrivate(), handle, serviceWorkerHandle, clientInfo, aIsReload); rv = event->Init(); NS_ENSURE_SUCCESS(rv, rv); AutoJSAPI api; api.Init(); if (NS_WARN_IF(!event->Dispatch(api.cx()))) { return NS_ERROR_FAILURE; }
--- a/dom/workers/test/serviceworkers/fetch/fetch_tests.js +++ b/dom/workers/test/serviceworkers/fetch/fetch_tests.js @@ -1,105 +1,169 @@ -function fetch(name, onload, onerror, headers) { +function fetchXHR(name, onload, onerror, headers) { expectAsyncResult(); onload = onload || function() { my_ok(false, "XHR load should not complete successfully"); finish(); }; onerror = onerror || function() { - my_ok(false, "XHR load should be intercepted successfully"); + my_ok(false, "XHR load for " + name + " should be intercepted successfully"); finish(); }; var x = new XMLHttpRequest(); x.open('GET', name, true); x.onload = function() { onload(x) }; x.onerror = function() { onerror(x) }; headers = headers || []; headers.forEach(function(header) { x.setRequestHeader(header[0], header[1]); }); x.send(); } -fetch('synthesized.txt', function(xhr) { +fetchXHR('synthesized.txt', function(xhr) { my_ok(xhr.status == 200, "load should be successful"); my_ok(xhr.responseText == "synthesized response body", "load should have synthesized response"); finish(); }); -fetch('test-respondwith-response.txt', function(xhr) { +fetchXHR('test-respondwith-response.txt', function(xhr) { my_ok(xhr.status == 200, "test-respondwith-response load should be successful"); my_ok(xhr.responseText == "test-respondwith-response response body", "load should have response"); finish(); }); -fetch('synthesized-404.txt', function(xhr) { +fetchXHR('synthesized-404.txt', function(xhr) { my_ok(xhr.status == 404, "load should 404"); my_ok(xhr.responseText == "synthesized response body", "404 load should have synthesized response"); finish(); }); -fetch('synthesized-headers.txt', function(xhr) { +fetchXHR('synthesized-headers.txt', function(xhr) { my_ok(xhr.status == 200, "load should be successful"); my_ok(xhr.getResponseHeader("X-Custom-Greeting") === "Hello", "custom header should be set"); my_ok(xhr.responseText == "synthesized response body", "custom header load should have synthesized response"); finish(); }); -fetch('ignored.txt', function(xhr) { +fetchXHR('ignored.txt', function(xhr) { my_ok(xhr.status == 404, "load should be uninterrupted"); finish(); }); -fetch('rejected.txt', null, function(xhr) { +fetchXHR('rejected.txt', null, function(xhr) { my_ok(xhr.status == 0, "load should not complete"); finish(); }); -fetch('nonresponse.txt', null, function(xhr) { +fetchXHR('nonresponse.txt', null, function(xhr) { my_ok(xhr.status == 0, "load should not complete"); finish(); }); -fetch('nonresponse2.txt', null, function(xhr) { +fetchXHR('nonresponse2.txt', null, function(xhr) { my_ok(xhr.status == 0, "load should not complete"); finish(); }); -fetch('headers.txt', function(xhr) { +fetchXHR('headers.txt', function(xhr) { my_ok(xhr.status == 200, "load should be successful"); my_ok(xhr.responseText == "1", "request header checks should have passed"); finish(); }, null, [["X-Test1", "header1"], ["X-Test2", "header2"]]); var expectedUncompressedResponse = ""; for (var i = 0; i < 10; ++i) { expectedUncompressedResponse += "hello"; } expectedUncompressedResponse += "\n"; // ServiceWorker does not intercept, at which point the network request should // be correctly decoded. -fetch('deliver-gzip.sjs', function(xhr) { +fetchXHR('deliver-gzip.sjs', function(xhr) { my_ok(xhr.status == 200, "network gzip load should be successful"); my_ok(xhr.responseText == expectedUncompressedResponse, "network gzip load should have synthesized response."); my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "network Content-Encoding should be gzip."); my_ok(xhr.getResponseHeader("Content-Length") == "35", "network Content-Length should be of original gzipped file."); finish(); }); -fetch('hello.gz', function(xhr) { +fetchXHR('hello.gz', function(xhr) { + my_ok(xhr.status == 200, "gzip load should be successful"); + my_ok(xhr.responseText == expectedUncompressedResponse, "gzip load should have synthesized response."); + my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "Content-Encoding should be gzip."); + my_ok(xhr.getResponseHeader("Content-Length") == "35", "Content-Length should be of original gzipped file."); + finish(); +}); + +fetchXHR('hello-after-extracting.gz', function(xhr) { my_ok(xhr.status == 200, "gzip load should be successful"); my_ok(xhr.responseText == expectedUncompressedResponse, "gzip load should have synthesized response."); my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "Content-Encoding should be gzip."); my_ok(xhr.getResponseHeader("Content-Length") == "35", "Content-Length should be of original gzipped file."); finish(); }); -fetch('hello-after-extracting.gz', function(xhr) { - my_ok(xhr.status == 200, "gzip load should be successful"); - my_ok(xhr.responseText == expectedUncompressedResponse, "gzip load should have synthesized response."); - my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "Content-Encoding should be gzip."); - my_ok(xhr.getResponseHeader("Content-Length") == "35", "Content-Length should be of original gzipped file."); +fetchXHR('http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*', function(xhr) { + my_ok(xhr.status == 200, "cross origin load with correct headers should be successful"); + my_ok(xhr.getResponseHeader("access-control-allow-origin") == null, "cors headers should be filtered out"); + finish(); +}); + +expectAsyncResult(); +fetch('http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*') +.then(function(res) { + my_ok(res.ok, "Valid CORS request should receive valid response"); + my_ok(res.type == "cors", "Response type should be CORS"); + res.text().then(function(body) { + my_ok(body === "<res>hello pass</res>\n", "cors response body should match"); + finish(); + }); +}, function(e) { + my_ok(false, "CORS Fetch failed"); finish(); }); + +expectAsyncResult(); +fetch('http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200', { mode: 'no-cors' }) +.then(function(res) { + my_ok(res.type == "opaque", "Response type should be opaque"); + my_ok(res.status == 0, "Status should be 0"); + res.text().then(function(body) { + my_ok(body === "", "opaque response body should be empty"); + finish(); + }); +}, function(e) { + my_ok(false, "no-cors Fetch failed"); + finish(); +}); + +expectAsyncResult(); +fetch('opaque-on-same-origin') +.then(function(res) { + my_ok(false, "intercepted opaque response for non no-cors request should fail."); + finish(); +}, function(e) { + my_ok(true, "intercepted opaque response for non no-cors request should fail."); + finish(); +}); + +expectAsyncResult(); +fetch('http://example.com/opaque-no-cors', { mode: "no-cors" }) +.then(function(res) { + my_ok(res.type == "opaque", "intercepted opaque response for no-cors request should have type opaque."); + finish(); +}, function(e) { + my_ok(false, "intercepted opaque response for no-cors request should pass."); + finish(); +}); + +expectAsyncResult(); +fetch('http://example.com/cors-for-no-cors', { mode: "no-cors" }) +.then(function(res) { + my_ok(res.type == "opaque", "intercepted non-opaque response for no-cors request should resolve to opaque response."); + finish(); +}, function(e) { + my_ok(false, "intercepted non-opaque response for no-cors request should resolve to opaque response. It should not fail."); + finish(); +});
--- a/dom/workers/test/serviceworkers/fetch/index.html +++ b/dom/workers/test/serviceworkers/fetch/index.html @@ -19,17 +19,17 @@ window.opener.postMessage({status: "ok", result: result, message: msg}, "*"); } function check_intercepted_script() { document.getElementById('intercepted-script').test_result = document.currentScript == document.getElementById('intercepted-script'); } - function fetch(name, onload, onerror, headers) { + function fetchXHR(name, onload, onerror, headers) { gExpected++; onload = onload || function() { my_ok(false, "load should not complete successfully"); finish(); }; onerror = onerror || function() { my_ok(false, "load should be intercepted successfully"); @@ -130,17 +130,17 @@ gExpected++; var worker = new Worker('fetch_worker_script.js'); worker.onmessage = function(e) { if (e.data == "finish") { finish(); } else if (e.data == "expect") { gExpected++; } else if (e.data.type == "ok") { - my_ok(e.data.value, e.data.msg); + my_ok(e.data.value, "Fetch test on worker: " + e.data.msg); } }; worker.onerror = function() { my_ok(false, "worker should not cause any errors"); }; </script> </pre> </body>
--- a/dom/workers/test/serviceworkers/fetch_event_worker.js +++ b/dom/workers/test/serviceworkers/fetch_event_worker.js @@ -1,8 +1,10 @@ +var seenIndex = false; + onfetch = function(ev) { if (ev.request.url.contains("synthesized.txt")) { ev.respondWith(Promise.resolve( new Response("synthesized response body", {}) )); } else if (ev.request.url.contains("synthesized-404.txt")) { @@ -110,9 +112,52 @@ onfetch = function(ev) { else if (ev.request.url.contains("hello-after-extracting.gz")) { ev.respondWith(fetch("fetch/deliver-gzip.sjs").then(function(res) { return res.text().then(function(body) { return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers }); }); })); } + + else if (ev.request.url.contains('opaque-on-same-origin')) { + var url = 'http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200'; + ev.respondWith(fetch(url, { mode: 'no-cors' })); + } + + else if (ev.request.url.contains('opaque-no-cors')) { + if (ev.request.mode != "no-cors") { + ev.respondWith(Promise.reject()); + return; + } + + var url = 'http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200'; + ev.respondWith(fetch(url, { mode: ev.request.mode })); + } + + else if (ev.request.url.contains('cors-for-no-cors')) { + if (ev.request.mode != "no-cors") { + ev.respondWith(Promise.reject()); + return; + } + + var url = 'http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*'; + ev.respondWith(fetch(url)); + } + + else if (ev.request.url.contains('example.com')) { + ev.respondWith(fetch(ev.request)); + } + + else if (ev.request.url.contains("index.html")) { + if (seenIndex) { + var body = "<script>" + + "opener.postMessage({status: 'ok', result: " + ev.isReload + "," + + "message: 'reload status should be indicated'}, '*');" + + "opener.postMessage({status: 'done'}, '*');" + + "</script>"; + ev.respondWith(new Response(body, {headers: {'Content-Type': 'text/html'}})); + } else { + seenIndex = true; + ev.respondWith(fetch(ev.request.url)); + } + } }
--- a/dom/workers/test/serviceworkers/test_fetch_event.html +++ b/dom/workers/test/serviceworkers/test_fetch_event.html @@ -22,23 +22,29 @@ return new Promise(function(resolve) { swr.installing.onstatechange = resolve; }); }); } function testController() { var p = new Promise(function(resolve, reject) { + var reloaded = false; window.onmessage = function(e) { if (e.data.status == "ok") { ok(e.data.result, e.data.message); } else if (e.data.status == "done") { - window.onmessage = null; - w.close(); - resolve(); + if (reloaded) { + window.onmessage = null; + w.close(); + resolve(); + } else { + w.location.reload(); + reloaded = true; + } } } }); var w = window.open("fetch/index.html"); return p; }
--- a/dom/workers/test/serviceworkers/test_https_fetch.html +++ b/dom/workers/test/serviceworkers/test_https_fetch.html @@ -41,16 +41,15 @@ }; } SimpleTest.waitForExplicitFinish(); onload = function() { SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], ["dom.serviceWorkers.enabled", true], - ["dom.fetch.enabled", true], ["dom.caches.enabled", true] ]}, runTest); }; </script> </pre> </body> </html>
--- a/dom/workers/test/serviceworkers/test_https_fetch_cloned_response.html +++ b/dom/workers/test/serviceworkers/test_https_fetch_cloned_response.html @@ -41,16 +41,15 @@ }; } SimpleTest.waitForExplicitFinish(); onload = function() { SpecialPowers.pushPrefEnv({"set": [ ["dom.serviceWorkers.exemptFromPerDomainMax", true], ["dom.serviceWorkers.enabled", true], - ["dom.fetch.enabled", true], ["dom.caches.enabled", true] ]}, runTest); }; </script> </pre> </body> </html>
--- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -114,16 +114,17 @@ static const char *sExtensionNames[] = { "GL_EXT_framebuffer_object", "GL_EXT_framebuffer_sRGB", "GL_EXT_gpu_shader4", "GL_EXT_occlusion_query_boolean", "GL_EXT_packed_depth_stencil", "GL_EXT_read_format_bgra", "GL_EXT_robustness", "GL_EXT_sRGB", + "GL_EXT_sRGB_write_control", "GL_EXT_shader_texture_lod", "GL_EXT_texture3D", "GL_EXT_texture_compression_dxt1", "GL_EXT_texture_compression_s3tc", "GL_EXT_texture_filter_anisotropic", "GL_EXT_texture_format_BGRA8888", "GL_EXT_texture_sRGB", "GL_EXT_texture_storage",
--- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -114,17 +114,18 @@ enum class GLFeature { occlusion_query_boolean, occlusion_query2, packed_depth_stencil, query_objects, read_buffer, renderbuffer_color_float, renderbuffer_color_half_float, robustness, - sRGB, + sRGB_framebuffer, + sRGB_texture, sampler_objects, standard_derivatives, texture_3D, texture_3D_compressed, texture_3D_copy, texture_float, texture_float_linear, texture_half_float, @@ -410,16 +411,17 @@ public: EXT_framebuffer_object, EXT_framebuffer_sRGB, EXT_gpu_shader4, EXT_occlusion_query_boolean, EXT_packed_depth_stencil, EXT_read_format_bgra, EXT_robustness, EXT_sRGB, + EXT_sRGB_write_control, EXT_shader_texture_lod, EXT_texture3D, EXT_texture_compression_dxt1, EXT_texture_compression_s3tc, EXT_texture_filter_anisotropic, EXT_texture_format_BGRA8888, EXT_texture_sRGB, EXT_texture_storage, @@ -1032,18 +1034,26 @@ public: // See bug 737182 and the comment in IsTextureSizeSafeToPassToDriver. level = -1; width = -1; height = -1; border = -1; } BeforeGLReadCall(); - raw_fCopyTexImage2D(target, level, internalformat, - x, y, width, height, border); + bool didCopyTexImage2D = false; + if (mScreen) { + didCopyTexImage2D = mScreen->CopyTexImage2D(target, level, internalformat, x, + y, width, height, border); + } + + if (!didCopyTexImage2D) { + raw_fCopyTexImage2D(target, level, internalformat, x, y, width, height, + border); + } AfterGLReadCall(); } void fCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) { BeforeGLReadCall(); raw_fCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); AfterGLReadCall(); @@ -1927,16 +1937,19 @@ public: void fCompileShader(GLuint shader) { BEFORE_GL_CALL; mSymbols.fCompileShader(shader); AFTER_GL_CALL; } private: + + friend class SharedSurface_IOSurface; + void raw_fCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) { BEFORE_GL_CALL; mSymbols.fCopyTexImage2D(target, level, internalformat, x, y, width, height, border); AFTER_GL_CALL; } void raw_fCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height)
--- a/gfx/gl/GLContextFeatures.cpp +++ b/gfx/gl/GLContextFeatures.cpp @@ -1,14 +1,13 @@ /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* 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/. */ - #include "GLContext.h" #include "nsPrintfCString.h" #ifdef XP_MACOSX #include "nsCocoaFeatures.h" #endif namespace mozilla { @@ -446,22 +445,34 @@ static const FeatureInfo sFeatureInfoArr GLContext::Extension_None, { GLContext::ARB_robustness, GLContext::EXT_robustness, GLContext::Extensions_End } }, { - "sRGB", + "sRGB_framebuffer", GLVersion::GL3, GLESVersion::ES3, + GLContext::ARB_framebuffer_sRGB, + { + GLContext::EXT_framebuffer_sRGB, + GLContext::EXT_sRGB_write_control, + GLContext::Extensions_End + } + }, + { + "sRGB_texture", + GLVersion::GL2_1, + GLESVersion::ES3, GLContext::Extension_None, { GLContext::EXT_sRGB, + GLContext::EXT_texture_sRGB, GLContext::Extensions_End } }, { "sampler_objects", GLVersion::GL3_3, GLESVersion::ES3, GLContext::ARB_sampler_objects, @@ -648,21 +659,20 @@ GetFeatureInfo(GLFeature feature) static inline uint32_t ProfileVersionForFeature(GLFeature feature, ContextProfile profile) { MOZ_ASSERT(profile != ContextProfile::Unknown, "GLContext::ProfileVersionForFeature : unknown <profile>"); const FeatureInfo& featureInfo = GetFeatureInfo(feature); - if (profile == ContextProfile::OpenGLES) { - return (uint32_t) featureInfo.mOpenGLESVersion; - } + if (profile == ContextProfile::OpenGLES) + return (uint32_t)featureInfo.mOpenGLESVersion; - return (uint32_t) featureInfo.mOpenGLVersion; + return (uint32_t)featureInfo.mOpenGLVersion; } bool IsFeaturePartOfProfileVersion(GLFeature feature, ContextProfile profile, unsigned int version) { unsigned int profileVersion = ProfileVersionForFeature(feature, profile); @@ -686,101 +696,77 @@ GLContext::IsFeatureProvidedByCoreSymbol } const char* GLContext::GetFeatureName(GLFeature feature) { return GetFeatureInfo(feature).mName; } -static bool -CanReadSRGBFromFBOTexture(GLContext* gl) -{ - if (!gl->WorkAroundDriverBugs()) - return true; - -#ifdef XP_MACOSX - // Bug 843668: - // MacOSX 10.6 reports to support EXT_framebuffer_sRGB and - // EXT_texture_sRGB but fails to convert from sRGB to linear - // when writing to an sRGB texture attached to an FBO. - if (!nsCocoaFeatures::OnLionOrLater()) { - return false; - } -#endif // XP_MACOSX - return true; -} - void GLContext::InitFeatures() { - for (size_t feature_index = 0; feature_index < size_t(GLFeature::EnumMax); feature_index++) - { - GLFeature feature = GLFeature(feature_index); + for (size_t featureId = 0; featureId < size_t(GLFeature::EnumMax); featureId++) { + GLFeature feature = GLFeature(featureId); if (IsFeaturePartOfProfileVersion(feature, mProfile, mVersion)) { - mAvailableFeatures[feature_index] = true; + mAvailableFeatures[featureId] = true; continue; } - mAvailableFeatures[feature_index] = false; + mAvailableFeatures[featureId] = false; const FeatureInfo& featureInfo = GetFeatureInfo(feature); - if (IsExtensionSupported(featureInfo.mARBExtensionWithoutARBSuffix)) - { - mAvailableFeatures[feature_index] = true; + if (IsExtensionSupported(featureInfo.mARBExtensionWithoutARBSuffix)) { + mAvailableFeatures[featureId] = true; continue; } - for (size_t j = 0; true; j++) - { - MOZ_ASSERT(j < kMAX_EXTENSION_GROUP_SIZE, "kMAX_EXTENSION_GROUP_SIZE too small"); + for (size_t j = 0; true; j++) { + MOZ_ASSERT(j < kMAX_EXTENSION_GROUP_SIZE, + "kMAX_EXTENSION_GROUP_SIZE too small"); - if (featureInfo.mExtensions[j] == GLContext::Extensions_End) { + if (featureInfo.mExtensions[j] == GLContext::Extensions_End) break; - } if (IsExtensionSupported(featureInfo.mExtensions[j])) { - mAvailableFeatures[feature_index] = true; + mAvailableFeatures[featureId] = true; break; } } } - // Bug 843668: Work around limitation of the feature system. - // For sRGB support under OpenGL to match OpenGL ES spec, check for both - // EXT_texture_sRGB and EXT_framebuffer_sRGB is required. - const bool aresRGBExtensionsAvailable = - IsExtensionSupported(EXT_texture_sRGB) && - (IsExtensionSupported(ARB_framebuffer_sRGB) || - IsExtensionSupported(EXT_framebuffer_sRGB)); - - mAvailableFeatures[size_t(GLFeature::sRGB)] = - aresRGBExtensionsAvailable && - CanReadSRGBFromFBOTexture(this); + if (WorkAroundDriverBugs()) { +#ifdef XP_MACOSX + // MacOSX 10.6 reports to support EXT_framebuffer_sRGB and EXT_texture_sRGB but + // fails to convert from sRGB to linear when reading from an sRGB texture attached + // to an FBO. (bug 843668) + if (!nsCocoaFeatures::OnLionOrLater()) + MarkUnsupported(GLFeature::sRGB_framebuffer); +#endif // XP_MACOSX + } } void GLContext::MarkUnsupported(GLFeature feature) { mAvailableFeatures[size_t(feature)] = false; const FeatureInfo& featureInfo = GetFeatureInfo(feature); - for (size_t i = 0; true; i++) - { + for (size_t i = 0; true; i++) { MOZ_ASSERT(i < kMAX_EXTENSION_GROUP_SIZE, "kMAX_EXTENSION_GROUP_SIZE too small"); - if (featureInfo.mExtensions[i] == GLContext::Extensions_End) { + if (featureInfo.mExtensions[i] == GLContext::Extensions_End) break; - } MarkExtensionUnsupported(featureInfo.mExtensions[i]); } MOZ_ASSERT(!IsSupported(feature), "GLContext::MarkUnsupported has failed!"); - NS_WARNING(nsPrintfCString("%s marked as unsupported", GetFeatureName(feature)).get()); + NS_WARNING(nsPrintfCString("%s marked as unsupported", + GetFeatureName(feature)).get()); } } /* namespace gl */ } /* namespace mozilla */
--- a/gfx/gl/GLScreenBuffer.cpp +++ b/gfx/gl/GLScreenBuffer.cpp @@ -309,16 +309,33 @@ GLScreenBuffer::BeforeReadCall() { if (mUserReadFB != 0) return; AssureBlitted(); } bool +GLScreenBuffer::CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, + GLint y, GLsizei width, GLsizei height, GLint border) +{ + SharedSurface* surf; + if (GetReadFB() == 0) { + surf = SharedSurf(); + } else { + surf = mGL->mFBOMapping[GetReadFB()]; + } + if (surf) { + return surf->CopyTexImage2D(target, level, internalformat, x, y, width, height, border); + } + + return false; +} + +bool GLScreenBuffer::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels) { // If the currently bound framebuffer is backed by a SharedSurface // then it might want to override how we read pixel data from it. // This is normally only the default framebuffer, but we can also
--- a/gfx/gl/GLScreenBuffer.h +++ b/gfx/gl/GLScreenBuffer.h @@ -223,16 +223,19 @@ public: void BindAsFramebuffer(GLContext* const gl, GLenum target) const; void RequireBlit(); void AssureBlitted(); void AfterDrawCall(); void BeforeReadCall(); + bool CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, + GLint y, GLsizei width, GLsizei height, GLint border); + void SetReadBuffer(GLenum userMode); /** * Attempts to read pixels from the current bound framebuffer, if * it is backed by a SharedSurface. * * Returns true if the pixel data has been read back, false * otherwise.
--- a/gfx/gl/SharedSurface.h +++ b/gfx/gl/SharedSurface.h @@ -151,16 +151,22 @@ public: MOZ_CRASH("Did you forget to override this function?"); } virtual GLuint ProdRenderbuffer() { MOZ_ASSERT(mAttachType == AttachmentType::GLRenderbuffer); MOZ_CRASH("Did you forget to override this function?"); } + virtual bool CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, + GLint y, GLsizei width, GLsizei height, GLint border) + { + return false; + } + virtual bool ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels) { return false; }
--- a/gfx/gl/SharedSurfaceIO.cpp +++ b/gfx/gl/SharedSurfaceIO.cpp @@ -31,16 +31,59 @@ SharedSurface_IOSurface::Create(const Re void SharedSurface_IOSurface::Fence() { mGL->MakeCurrent(); mGL->fFlush(); } bool +SharedSurface_IOSurface::CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, + GLint x, GLint y, GLsizei width, GLsizei height, + GLint border) +{ + /* Bug 896693 - OpenGL framebuffers that are backed by IOSurface on OSX expose a bug + * in glCopyTexImage2D --- internalformats GL_ALPHA, GL_LUMINANCE, GL_LUMINANCE_ALPHA + * return the wrong results. To work around, copy framebuffer to a temporary texture + * using GL_RGBA (which works), attach as read framebuffer and glCopyTexImage2D + * instead. + */ + + // https://www.opengl.org/sdk/docs/man3/xhtml/glCopyTexImage2D.xml says that width or + // height set to 0 results in a NULL texture. Lets not do any work and punt to + // original glCopyTexImage2D, since the FBO below will fail when trying to attach a + // texture of 0 width or height. + if (width == 0 || height == 0) + return false; + + MOZ_ASSERT(mGL->IsCurrent()); + + ScopedTexture destTex(mGL); + { + ScopedBindTexture bindTex(mGL, destTex.Texture()); + mGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, + LOCAL_GL_NEAREST); + mGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, + LOCAL_GL_NEAREST); + mGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, + LOCAL_GL_CLAMP_TO_EDGE); + mGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, + LOCAL_GL_CLAMP_TO_EDGE); + mGL->raw_fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, x, y, width, + height, 0); + } + + ScopedFramebufferForTexture tmpFB(mGL, destTex.Texture(), LOCAL_GL_TEXTURE_2D); + ScopedBindFramebuffer bindFB(mGL, tmpFB.FB()); + mGL->raw_fCopyTexImage2D(target, level, internalformat, x, y, width, height, border); + + return true; +} + +bool SharedSurface_IOSurface::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels) { // Calling glReadPixels when an IOSurface is bound to the current framebuffer // can cause corruption in following glReadPixel calls (even if they aren't // reading from an IOSurface). // We workaround this by copying to a temporary texture, and doing the readback // from that.
--- a/gfx/gl/SharedSurfaceIO.h +++ b/gfx/gl/SharedSurfaceIO.h @@ -25,16 +25,19 @@ public: virtual void LockProdImpl() override { } virtual void UnlockProdImpl() override { } virtual void Fence() override; virtual bool WaitSync() override { return true; } virtual bool PollSync() override { return true; } + virtual bool CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, + GLint x, GLint y, GLsizei width, GLsizei height, + GLint border) override; virtual bool ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels) override; virtual GLuint ProdTexture() override { return mProdTex; } virtual GLenum ProdTextureTarget() const override {
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -367,18 +367,18 @@ static bool IsCloseToHorizontal(float aA static bool IsCloseToVertical(float aAngle, float aThreshold) { return (fabs(aAngle - (M_PI / 2)) < aThreshold); } template <typename Units> static bool IsZero(const gfx::PointTyped<Units>& aPoint) { - return FuzzyEqualsMultiplicative(aPoint.x, 0.0f) - && FuzzyEqualsMultiplicative(aPoint.y, 0.0f); + return FuzzyEqualsAdditive(aPoint.x, 0.0f) + && FuzzyEqualsAdditive(aPoint.y, 0.0f); } static inline void LogRendertraceRect(const ScrollableLayerGuid& aGuid, const char* aDesc, const char* aColor, const CSSRect& aRect) { #ifdef APZC_ENABLE_RENDERTRACE static const TimeStamp sRenderStart = TimeStamp::Now(); TimeDuration delta = TimeStamp::Now() - sRenderStart; printf_stderr("(%llu,%lu,%llu)%s RENDERTRACE %f rect %s %f %f %f %f\n", @@ -1282,32 +1282,32 @@ nsEventStatus AsyncPanZoomController::On mLastZoomFocus = aEvent.mLocalFocusPoint - mFrameMetrics.mCompositionBounds.TopLeft(); return nsEventStatus_eConsumeNoDefault; } nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) { APZC_LOG("%p got a scale in state %d\n", this, mState); + if (HasReadyTouchBlock() && !CurrentTouchBlock()->TouchActionAllowsPinchZoom()) { + return nsEventStatus_eIgnore; + } + + if (mState != PINCHING) { + return nsEventStatus_eConsumeNoDefault; + } + // Only the root APZC is zoomable, and the root APZC is not allowed to have // different x and y scales. If it did, the calculations in this function // would have to be adjusted (as e.g. it would no longer be valid to take // the minimum or maximum of the ratios of the widths and heights of the // page rect and the composition bounds). MOZ_ASSERT(mFrameMetrics.IsRootScrollable()); MOZ_ASSERT(mFrameMetrics.GetZoom().AreScalesSame()); - if (HasReadyTouchBlock() && !CurrentTouchBlock()->TouchActionAllowsPinchZoom()) { - return nsEventStatus_eIgnore; - } - - if (mState != PINCHING) { - return nsEventStatus_eConsumeNoDefault; - } - float prevSpan = aEvent.mPreviousSpan; if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) { // We're still handling it; we've just decided to throw this event away. return nsEventStatus_eConsumeNoDefault; } float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan;
--- a/gfx/layers/apz/src/Axis.cpp +++ b/gfx/layers/apz/src/Axis.cpp @@ -25,16 +25,22 @@ #include "gfxPrefs.h" // for the preferences #define AXIS_LOG(...) // #define AXIS_LOG(...) printf_stderr("AXIS: " __VA_ARGS__) namespace mozilla { namespace layers { +bool FuzzyEqualsCoordinate(float aValue1, float aValue2) +{ + return FuzzyEqualsAdditive(aValue1, aValue2, COORDINATE_EPSILON) + || FuzzyEqualsMultiplicative(aValue1, aValue2); +} + extern StaticAutoPtr<ComputedTimingFunction> gVelocityCurveFunction; Axis::Axis(AsyncPanZoomController* aAsyncPanZoomController) : mPos(0), mPosTimeMs(0), mVelocity(0.0f), mAxisLocked(false), mAsyncPanZoomController(aAsyncPanZoomController), @@ -175,28 +181,28 @@ ParentLayerCoord Axis::ApplyResistance(P } void Axis::OverscrollBy(ParentLayerCoord aOverscroll) { MOZ_ASSERT(CanScroll()); StopSamplingOverscrollAnimation(); aOverscroll = ApplyResistance(aOverscroll); if (aOverscroll > 0) { #ifdef DEBUG - if (!FuzzyEqualsAdditive(GetCompositionEnd().value, GetPageEnd().value, COORDINATE_EPSILON)) { - nsPrintfCString message("composition end (%f) is not within COORDINATE_EPISLON of page end (%f)\n", + if (!FuzzyEqualsCoordinate(GetCompositionEnd().value, GetPageEnd().value)) { + nsPrintfCString message("composition end (%f) is not equal (within error) to page end (%f)\n", GetCompositionEnd().value, GetPageEnd().value); NS_ASSERTION(false, message.get()); MOZ_CRASH(); } #endif MOZ_ASSERT(mOverscroll >= 0); } else if (aOverscroll < 0) { #ifdef DEBUG - if (!FuzzyEqualsAdditive(GetOrigin().value, GetPageStart().value, COORDINATE_EPSILON)) { - nsPrintfCString message("composition origin (%f) is not within COORDINATE_EPISLON of page origin (%f)\n", + if (!FuzzyEqualsCoordinate(GetOrigin().value, GetPageStart().value)) { + nsPrintfCString message("composition origin (%f) is not equal (within error) to page origin (%f)\n", GetOrigin().value, GetPageStart().value); NS_ASSERTION(false, message.get()); MOZ_CRASH(); } #endif MOZ_ASSERT(mOverscroll <= 0); } mOverscroll += aOverscroll;
--- a/gfx/layers/apz/src/Axis.h +++ b/gfx/layers/apz/src/Axis.h @@ -20,16 +20,26 @@ const float EPSILON = 0.0001f; // Epsilon to be used when comparing 'float' coordinate values // with FuzzyEqualsAdditive. The rationale is that 'float' has 7 decimal // digits of precision, and coordinate values should be no larger than in the // ten thousands. Note also that the smallest legitimate difference in page // coordinates is 1 app unit, which is 1/60 of a (CSS pixel), so this epsilon // isn't too large. const float COORDINATE_EPSILON = 0.01f; +/** + * Compare two coordinates for equality, accounting for rounding error. + * Use both FuzzyEqualsAdditive() with COORDINATE_EPISLON, which accounts for + * things like the error introduced by rounding during a round-trip to app + * units, and FuzzyEqualsMultiplicative(), which accounts for accumulated error + * due to floating-point operations (which can be larger than COORDINATE_EPISLON + * for sufficiently large coordinate values). + */ +bool FuzzyEqualsCoordinate(float aValue1, float aValue2); + struct FrameMetrics; class AsyncPanZoomController; /** * Helper class to maintain each axis of movement (X,Y) for panning and zooming. * Note that everything here is specific to one axis; that is, the X axis knows * nothing about the Y axis and vice versa. */
--- a/hal/cocoa/CocoaGamepad.cpp +++ b/hal/cocoa/CocoaGamepad.cpp @@ -18,105 +18,184 @@ namespace { using mozilla::dom::GamepadService; using std::vector; struct Button { int id; + bool analog; IOHIDElementRef element; + CFIndex min; + CFIndex max; + + Button(int aId, IOHIDElementRef aElement, CFIndex aMin, CFIndex aMax) : + id(aId), + analog((aMax - aMin) > 1), + element(aElement), + min(aMin), + max(aMax) {} }; struct Axis { int id; IOHIDElementRef element; + uint32_t usagePage; + uint32_t usage; CFIndex min; CFIndex max; }; +typedef bool dpad_buttons[4]; + // These values can be found in the USB HID Usage Tables: // http://www.usb.org/developers/hidpage -#define GENERIC_DESKTOP_USAGE_PAGE 0x01 -#define JOYSTICK_USAGE_NUMBER 0x04 -#define GAMEPAD_USAGE_NUMBER 0x05 -#define AXIS_MIN_USAGE_NUMBER 0x30 -#define AXIS_MAX_USAGE_NUMBER 0x35 -#define BUTTON_USAGE_PAGE 0x09 +const unsigned kDesktopUsagePage = 0x01; +const unsigned kSimUsagePage = 0x02; +const unsigned kAcceleratorUsage = 0xC4; +const unsigned kBrakeUsage = 0xC5; +const unsigned kJoystickUsage = 0x04; +const unsigned kGamepadUsage = 0x05; +const unsigned kAxisUsageMin = 0x30; +const unsigned kAxisUsageMax = 0x35; +const unsigned kDpadUsage = 0x39; +const unsigned kButtonUsagePage = 0x09; +const unsigned kConsumerPage = 0x0C; +const unsigned kHomeUsage = 0x223; +const unsigned kBackUsage = 0x224; + class Gamepad { private: IOHIDDeviceRef mDevice; - vector<Button> buttons; - vector<Axis> axes; + nsTArray<Button> buttons; + nsTArray<Axis> axes; + IOHIDElementRef mDpad; + dpad_buttons mDpadState; public: - Gamepad() : mDevice(nullptr), mSuperIndex(-1) {} + Gamepad() : mDevice(nullptr), mDpad(nullptr), mSuperIndex(-1) {} bool operator==(IOHIDDeviceRef device) const { return mDevice == device; } bool empty() const { return mDevice == nullptr; } - void clear() { + void clear() + { mDevice = nullptr; - buttons.clear(); - axes.clear(); + buttons.Clear(); + axes.Clear(); + mDpad = nullptr; mSuperIndex = -1; } void init(IOHIDDeviceRef device); - size_t numButtons() { return buttons.size(); } - size_t numAxes() { return axes.size(); } + size_t numButtons() { return buttons.Length() + (mDpad ? 4 : 0); } + size_t numAxes() { return axes.Length(); } // Index given by our superclass. uint32_t mSuperIndex; - const Button* lookupButton(IOHIDElementRef element) const { - for (size_t i = 0; i < buttons.size(); i++) { + const bool isDpad(IOHIDElementRef element) const + { + return element == mDpad; + } + + const dpad_buttons& getDpadState() const + { + return mDpadState; + } + + void setDpadState(const dpad_buttons& dpadState) + { + for (unsigned i = 0; i < ArrayLength(mDpadState); i++) { + mDpadState[i] = dpadState[i]; + } + } + + const Button* lookupButton(IOHIDElementRef element) const + { + for (unsigned i = 0; i < buttons.Length(); i++) { if (buttons[i].element == element) return &buttons[i]; } return nullptr; } - const Axis* lookupAxis(IOHIDElementRef element) const { - for (size_t i = 0; i < axes.size(); i++) { + const Axis* lookupAxis(IOHIDElementRef element) const + { + for (unsigned i = 0; i < axes.Length(); i++) { if (axes[i].element == element) return &axes[i]; } return nullptr; } }; -void Gamepad::init(IOHIDDeviceRef device) { +class AxisComparator { +public: + bool Equals(const Axis& a1, const Axis& a2) const + { + return a1.usagePage == a2.usagePage && a1.usage == a2.usage; + } + bool LessThan(const Axis& a1, const Axis& a2) const + { + if (a1.usagePage == a2.usagePage) { + return a1.usage < a2.usage; + } + return a1.usagePage < a2.usagePage; + } +}; + +void Gamepad::init(IOHIDDeviceRef device) +{ clear(); mDevice = device; CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, nullptr, kIOHIDOptionsTypeNone); CFIndex n = CFArrayGetCount(elements); for (CFIndex i = 0; i < n; i++) { IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i); uint32_t usagePage = IOHIDElementGetUsagePage(element); uint32_t usage = IOHIDElementGetUsage(element); - if (usagePage == GENERIC_DESKTOP_USAGE_PAGE && - usage >= AXIS_MIN_USAGE_NUMBER && - usage <= AXIS_MAX_USAGE_NUMBER) + if (usagePage == kDesktopUsagePage && + usage >= kAxisUsageMin && + usage <= kAxisUsageMax) { - Axis axis = { int(axes.size()), + Axis axis = { int(axes.Length()), element, + usagePage, + usage, IOHIDElementGetLogicalMin(element), IOHIDElementGetLogicalMax(element) }; - axes.push_back(axis); - } else if (usagePage == BUTTON_USAGE_PAGE) { - Button button = { int(usage) - 1, element }; - buttons.push_back(button); + axes.AppendElement(axis); + } else if (usagePage == kDesktopUsagePage && usage == kDpadUsage && + // Don't know how to handle d-pads that return weird values. + IOHIDElementGetLogicalMax(element) - IOHIDElementGetLogicalMin(element) == 7) { + mDpad = element; + } else if ((usagePage == kSimUsagePage && + (usage == kAcceleratorUsage || + usage == kBrakeUsage)) || + (usagePage == kButtonUsagePage) || + (usagePage == kConsumerPage && + (usage == kHomeUsage || + usage == kBackUsage))) { + Button button(int(buttons.Length()), element, IOHIDElementGetLogicalMin(element), IOHIDElementGetLogicalMax(element)); + buttons.AppendElement(button); } else { //TODO: handle other usage pages } } + + AxisComparator comparator; + axes.Sort(comparator); + for (unsigned i = 0; i < axes.Length(); i++) { + axes[i].id = i; + } } class DarwinGamepadService { private: IOHIDManagerRef mManager; vector<Gamepad> mGamepads; static void DeviceAddedCallback(void* data, IOReturn result, @@ -184,33 +263,100 @@ DarwinGamepadService::DeviceRemoved(IOHI if (mGamepads[i] == device) { service->RemoveGamepad(mGamepads[i].mSuperIndex); mGamepads[i].clear(); return; } } } +/* + * Given a value from a d-pad (POV hat in USB HID terminology), + * represent it as 4 buttons, one for each cardinal direction. + */ +static void +UnpackDpad(int dpad_value, int min, int max, dpad_buttons& buttons) +{ + const unsigned kUp = 0; + const unsigned kDown = 1; + const unsigned kLeft = 2; + const unsigned kRight = 3; + + // Different controllers have different ways of representing + // "nothing is pressed", but they're all outside the range of values. + if (dpad_value < min || dpad_value > max) { + // Nothing is pressed. + return; + } + + // Normalize value to start at 0. + int value = dpad_value - min; + + // Value will be in the range 0-7. The value represents the + // position of the d-pad around a circle, with 0 being straight up, + // 2 being right, 4 being straight down, and 6 being left. + if (value < 2 || value > 6) { + buttons[kUp] = true; + } + if (value > 2 && value < 6) { + buttons[kDown] = true; + } + if (value > 4) { + buttons[kLeft] = true; + } + if (value > 0 && value < 4) { + buttons[kRight] = true; + } +} + void DarwinGamepadService::InputValueChanged(IOHIDValueRef value) { + uint32_t value_length = IOHIDValueGetLength(value); + if (value_length > 4) { + // Workaround for bizarre issue with PS3 controllers that try to return + // massive (30+ byte) values and crash IOHIDValueGetIntegerValue + return; + } nsRefPtr<GamepadService> service(GamepadService::GetService()); IOHIDElementRef element = IOHIDValueGetElement(value); IOHIDDeviceRef device = IOHIDElementGetDevice(element); - for (size_t i = 0; i < mGamepads.size(); i++) { - const Gamepad &gamepad = mGamepads[i]; + for (unsigned i = 0; i < mGamepads.size(); i++) { + Gamepad &gamepad = mGamepads[i]; if (gamepad == device) { - if (const Axis* axis = gamepad.lookupAxis(element)) { + if (gamepad.isDpad(element)) { + const dpad_buttons& oldState = gamepad.getDpadState(); + dpad_buttons newState = { false, false, false, false }; + UnpackDpad(IOHIDValueGetIntegerValue(value), + IOHIDElementGetLogicalMin(element), + IOHIDElementGetLogicalMax(element), + newState); + const int numButtons = gamepad.numButtons(); + for (unsigned b = 0; b < ArrayLength(newState); b++) { + if (newState[b] != oldState[b]) { + service->NewButtonEvent(i, numButtons - 4 + b, newState[b]); + } + } + gamepad.setDpadState(newState); + } else if (const Axis* axis = gamepad.lookupAxis(element)) { double d = IOHIDValueGetIntegerValue(value); double v = 2.0f * (d - axis->min) / (double)(axis->max - axis->min) - 1.0f; service->NewAxisMoveEvent(i, axis->id, v); } else if (const Button* button = gamepad.lookupButton(element)) { - bool pressed = IOHIDValueGetIntegerValue(value) != 0; - service->NewButtonEvent(i, button->id, pressed); + int iv = IOHIDValueGetIntegerValue(value); + bool pressed = iv != 0; + double v = 0; + if (button->analog) { + double dv = iv; + v = (dv - button->min) / (double)(button->max - button->min); + } else { + v = pressed ? 1.0 : 0.0; + } + service->NewButtonEvent(i, button->id, pressed, v); } return; } } } void DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result, @@ -281,25 +427,25 @@ void DarwinGamepadService::Startup() { if (mManager != nullptr) return; IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); CFMutableDictionaryRef criteria_arr[2]; - criteria_arr[0] = MatchingDictionary(GENERIC_DESKTOP_USAGE_PAGE, - JOYSTICK_USAGE_NUMBER); + criteria_arr[0] = MatchingDictionary(kDesktopUsagePage, + kJoystickUsage); if (!criteria_arr[0]) { CFRelease(manager); return; } - criteria_arr[1] = MatchingDictionary(GENERIC_DESKTOP_USAGE_PAGE, - GAMEPAD_USAGE_NUMBER); + criteria_arr[1] = MatchingDictionary(kDesktopUsagePage, + kGamepadUsage); if (!criteria_arr[1]) { CFRelease(criteria_arr[0]); CFRelease(manager); return; } CFArrayRef criteria = CFArrayCreate(kCFAllocatorDefault, (const void**)criteria_arr, 2, nullptr);
--- a/image/src/ProgressTracker.cpp +++ b/image/src/ProgressTracker.cpp @@ -487,25 +487,17 @@ ProgressTracker::OnDiscard() MOZ_ASSERT(NS_IsMainThread()); NOTIFY_IMAGE_OBSERVERS(mObservers, Notify(imgINotificationObserver::DISCARD)); } void ProgressTracker::OnImageAvailable() { - if (!NS_IsMainThread()) { - // Note: SetHasImage calls Image::Lock and Image::IncrementAnimationCounter - // so subsequent calls or dispatches which Unlock or Decrement~ should - // be issued after this to avoid race conditions. - NS_DispatchToMainThread( - NS_NewRunnableMethod(this, &ProgressTracker::OnImageAvailable)); - return; - } - + MOZ_ASSERT(NS_IsMainThread()); // Notify any imgRequestProxys that are observing us that we have an Image. ObserverArray::ForwardIterator iter(mObservers); while (iter.HasMore()) { nsRefPtr<IProgressObserver> observer = iter.GetNext().get(); if (observer) { observer->SetHasImage(); } }
--- a/image/src/ProgressTracker.h +++ b/image/src/ProgressTracker.h @@ -120,17 +120,17 @@ public: // not threadsafe. void SyncNotify(IProgressObserver* aObserver); // Get this ProgressTracker ready for a new request. This resets all the // state that doesn't persist between requests. void ResetForNewRequest(); // Stateless notifications. These are dispatched and immediately forgotten - // about. All except OnImageAvailable are main thread only. + // about. All of these notifications are main thread only. void OnDiscard(); void OnUnlockedDraw(); void OnImageAvailable(); // Compute the difference between this our progress and aProgress. This allows // callers to predict whether SyncNotifyProgress will send any notifications. Progress Difference(Progress aProgress) const {
--- a/image/src/RasterImage.cpp +++ b/image/src/RasterImage.cpp @@ -1431,21 +1431,16 @@ RasterImage::RequestDecode() return RequestDecodeForSize(mSize, DECODE_FLAGS_DEFAULT); } /* void startDecode() */ NS_IMETHODIMP RasterImage::StartDecoding() { - if (!NS_IsMainThread()) { - return NS_DispatchToMainThread( - NS_NewRunnableMethod(this, &RasterImage::StartDecoding)); - } - // For decode-on-draw images, we only act on RequestDecodeForSize. if (mDecodeOnDraw) { return NS_OK; } return RequestDecodeForSize(mSize, FLAG_SYNC_DECODE_IF_FAST); }
--- a/image/src/imgLoader.cpp +++ b/image/src/imgLoader.cpp @@ -23,17 +23,16 @@ #include "nsIHttpChannel.h" #include "nsICachingChannel.h" #include "nsIInterfaceRequestor.h" #include "nsIProgressEventSink.h" #include "nsIChannelEventSink.h" #include "nsIAsyncVerifyRedirectCallback.h" #include "nsIFileURL.h" #include "nsCRT.h" -#include "nsIDocument.h" #include "nsINetworkPredictor.h" #include "nsIApplicationCache.h" #include "nsIApplicationCacheContainer.h" #include "nsIMemoryReporter.h" #include "Image.h" #include "gfxPrefs.h" @@ -1442,34 +1441,34 @@ bool imgLoader::ValidateRequestWithNewCh // now we need to insert a new channel request object inbetween the real // request and the proxy that basically delays loading the image until it // gets a 304 or figures out that this needs to be a new request nsresult rv; // If we're currently in the middle of validating this request, just hand // back a proxy to it; the required work will be done for us. - if (request->mValidator) { + if (request->GetValidator()) { rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, aLoadFlags, aProxyRequest); if (NS_FAILED(rv)) { return false; } if (*aProxyRequest) { imgRequestProxy* proxy = static_cast<imgRequestProxy*>(*aProxyRequest); // We will send notifications from imgCacheValidator::OnStartRequest(). // In the mean time, we must defer notifications because we are added to // the imgRequest's proxy list, and we can get extra notifications // resulting from methods such as RequestDecode(). See bug 579122. proxy->SetNotificationsDeferred(true); // Attach the proxy without notifying - request->mValidator->AddProxy(proxy); + request->GetValidator()->AddProxy(proxy); } return NS_SUCCEEDED(rv); } else { // We will rely on Necko to cache this request when it's possible, and to // tell imgCacheValidator::OnStartRequest whether the request came from its // cache. @@ -1524,17 +1523,17 @@ bool imgLoader::ValidateRequestWithNewCh rv = corsproxy->Init(newChannel); if (NS_FAILED(rv)) { return false; } listener = corsproxy; } - request->mValidator = hvc; + request->SetValidator(hvc); // We will send notifications from imgCacheValidator::OnStartRequest(). // In the mean time, we must defer notifications because we are added to // the imgRequest's proxy list, and we can get extra notifications // resulting from methods such as RequestDecode(). See bug 579122. req->SetNotificationsDeferred(true); // Add the proxy without notifying @@ -1623,17 +1622,17 @@ bool imgLoader::ValidateEntry(imgCacheEn // If the request's loadId is the same as the aCX, then it is ok to use // this one because it has already been validated for this context. // // XXX: nullptr seems to be a 'special' key value that indicates that NO // validation is required. // void *key = (void *)aCX; - if (request->mLoadId != key) { + if (request->LoadId() != key) { // If we would need to revalidate this entry, but we're being told to // bypass the cache, we don't allow this entry to be used. if (aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE) return false; // Determine whether the cache aEntry must be revalidated... validateRequest = ShouldRevalidateEntry(aEntry, aLoadFlags, hasExpired); @@ -1652,20 +1651,22 @@ bool imgLoader::ValidateEntry(imgCacheEn } #endif // We can't use a cached request if it comes from a different // application cache than this load is expecting. nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer; nsCOMPtr<nsIApplicationCache> requestAppCache; nsCOMPtr<nsIApplicationCache> groupAppCache; - if ((appCacheContainer = do_GetInterface(request->mRequest))) + if ((appCacheContainer = do_GetInterface(request->GetRequest()))) { appCacheContainer->GetApplicationCache(getter_AddRefs(requestAppCache)); - if ((appCacheContainer = do_QueryInterface(aLoadGroup))) + } + if ((appCacheContainer = do_QueryInterface(aLoadGroup))) { appCacheContainer->GetApplicationCache(getter_AddRefs(groupAppCache)); + } if (requestAppCache != groupAppCache) { PR_LOG(GetImgLog(), PR_LOG_DEBUG, ("imgLoader::ValidateEntry - Unable to use cached imgRequest " "[request=%p] because of mismatched application caches\n", address_of(request))); return false; } @@ -2041,22 +2042,16 @@ nsresult imgLoader::LoadImage(nsIURI *aU aLoadingPrincipal, corsmode, aReferrerPolicy); // Add the initiator type for this image load nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(newChannel); if (timedChannel) { timedChannel->SetInitiatorType(initiatorType); } - // Pass the inner window ID of the loading document, if possible. - nsCOMPtr<nsIDocument> doc = do_QueryInterface(aCX); - if (doc) { - request->SetInnerWindowID(doc->InnerWindowID()); - } - // create the proxy listener nsCOMPtr<nsIStreamListener> pl = new ProxyListener(request.get()); // See if we need to insert a CORS proxy between the proxy listener and the // request. nsCOMPtr<nsIStreamListener> listener = pl; if (corsmode != imgIRequest::CORS_NONE) { PR_LOG(GetImgLog(), PR_LOG_DEBUG, @@ -2510,30 +2505,31 @@ ProxyListener::CheckListenerChain() NS_IMPL_ISUPPORTS(imgCacheValidator, nsIStreamListener, nsIRequestObserver, nsIThreadRetargetableStreamListener, nsIChannelEventSink, nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback) imgCacheValidator::imgCacheValidator(nsProgressNotificationProxy* progress, imgLoader* loader, imgRequest *request, - void *aContext, bool forcePrincipalCheckForCacheEntry) + nsISupports* aContext, + bool forcePrincipalCheckForCacheEntry) : mProgressProxy(progress), mRequest(request), mContext(aContext), mImgLoader(loader) { NewRequestAndEntry(forcePrincipalCheckForCacheEntry, loader, getter_AddRefs(mNewRequest), getter_AddRefs(mNewEntry)); } imgCacheValidator::~imgCacheValidator() { if (mRequest) { - mRequest->mValidator = nullptr; + mRequest->SetValidator(nullptr); } } void imgCacheValidator::AddProxy(imgRequestProxy *aProxy) { // aProxy needs to be in the loadgroup since we're validating from // the network. aProxy->AddToLoadGroup(); @@ -2551,20 +2547,25 @@ NS_IMETHODIMP imgCacheValidator::OnStart // we have to do is tell them to notify their listeners. nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(aRequest)); nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); if (cacheChan && channel && !mRequest->CacheChanged(aRequest)) { bool isFromCache = false; cacheChan->IsFromCache(&isFromCache); nsCOMPtr<nsIURI> channelURI; + channel->GetURI(getter_AddRefs(channelURI)); + + nsCOMPtr<nsIURI> currentURI; + mRequest->GetCurrentURI(getter_AddRefs(currentURI)); + bool sameURI = false; - channel->GetURI(getter_AddRefs(channelURI)); - if (channelURI) - channelURI->Equals(mRequest->mCurrentURI, &sameURI); + if (channelURI && currentURI) { + channelURI->Equals(currentURI, &sameURI); + } if (isFromCache && sameURI) { uint32_t count = mProxies.Count(); for (int32_t i = count-1; i>=0; i--) { imgRequestProxy *proxy = static_cast<imgRequestProxy *>(mProxies[i]); // Proxies waiting on cache validation should be deferring notifications. // Undefer them. @@ -2577,17 +2578,17 @@ NS_IMETHODIMP imgCacheValidator::OnStart // asynchronously-called function. proxy->SyncNotifyListener(); } // We don't need to load this any more. aRequest->Cancel(NS_BINDING_ABORTED); mRequest->SetLoadId(mContext); - mRequest->mValidator = nullptr; + mRequest->SetValidator(nullptr); mRequest = nullptr; mNewRequest = nullptr; mNewEntry = nullptr; return NS_OK; } @@ -2610,17 +2611,17 @@ NS_IMETHODIMP imgCacheValidator::OnStart int32_t corsmode = mRequest->GetCORSMode(); ReferrerPolicy refpol = mRequest->GetReferrerPolicy(); nsCOMPtr<nsIPrincipal> loadingPrincipal = mRequest->GetLoadingPrincipal(); // Doom the old request's cache entry mRequest->RemoveFromCache(); - mRequest->mValidator = nullptr; + mRequest->SetValidator(nullptr); mRequest = nullptr; // We use originalURI here to fulfil the imgIRequest contract on GetURI. nsCOMPtr<nsIURI> originalURI; channel->GetOriginalURI(getter_AddRefs(originalURI)); mNewRequest->Init(originalURI, uri, aRequest, channel, mNewEntry, mContext, loadingPrincipal, corsmode, refpol);
--- a/image/src/imgLoader.h +++ b/image/src/imgLoader.h @@ -492,17 +492,18 @@ class nsProgressNotificationProxy final class imgCacheValidator : public nsIStreamListener, public nsIThreadRetargetableStreamListener, public nsIChannelEventSink, public nsIInterfaceRequestor, public nsIAsyncVerifyRedirectCallback { public: imgCacheValidator(nsProgressNotificationProxy* progress, imgLoader* loader, - imgRequest *request, void *aContext, bool forcePrincipalCheckForCacheEntry); + imgRequest* aRequest, nsISupports* aContext, + bool forcePrincipalCheckForCacheEntry); void AddProxy(imgRequestProxy *aProxy); NS_DECL_ISUPPORTS NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER NS_DECL_NSISTREAMLISTENER NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSICHANNELEVENTSINK @@ -518,14 +519,14 @@ private: nsCOMPtr<nsIChannel> mRedirectChannel; nsRefPtr<imgRequest> mRequest; nsCOMArray<imgIRequest> mProxies; nsRefPtr<imgRequest> mNewRequest; nsRefPtr<imgCacheEntry> mNewEntry; - void *mContext; + nsCOMPtr<nsISupports> mContext; imgLoader* mImgLoader; }; #endif // imgLoader_h__
--- a/image/src/imgRequest.cpp +++ b/image/src/imgRequest.cpp @@ -13,16 +13,17 @@ #include "ProgressTracker.h" #include "ImageFactory.h" #include "Image.h" #include "MultipartImage.h" #include "RasterImage.h" #include "nsIChannel.h" #include "nsICachingChannel.h" +#include "nsIDocument.h" #include "nsIThreadRetargetableRequest.h" #include "nsIInputStream.h" #include "nsIMultiPartChannel.h" #include "nsIHttpChannel.h" #include "nsIApplicationCache.h" #include "nsIApplicationCacheChannel.h" #include "nsMimeTypes.h" @@ -59,27 +60,27 @@ NS_IMPL_ISUPPORTS(imgRequest, nsIStreamListener, nsIRequestObserver, nsIThreadRetargetableStreamListener, nsIChannelEventSink, nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback) imgRequest::imgRequest(imgLoader* aLoader) : mLoader(aLoader) - , mProgressTracker(new ProgressTracker()) , mValidator(nullptr) - , mMutex("imgRequest") , mInnerWindowId(0) , mCORSMode(imgIRequest::CORS_NONE) , mReferrerPolicy(mozilla::net::RP_Default) , mImageErrorCode(NS_OK) - , mDecodeRequested(false) + , mMutex("imgRequest") + , mProgressTracker(new ProgressTracker()) , mIsMultiPartChannel(false) , mGotData(false) , mIsInCache(false) + , mDecodeRequested(false) , mNewPartPending(false) { } imgRequest::~imgRequest() { if (mLoader) { mLoader->RemoveFromUncachedImages(this); } @@ -91,17 +92,17 @@ imgRequest::~imgRequest() LOG_FUNC(GetImgLog(), "imgRequest::~imgRequest()"); } nsresult imgRequest::Init(nsIURI *aURI, nsIURI *aCurrentURI, nsIRequest *aRequest, nsIChannel *aChannel, imgCacheEntry *aCacheEntry, - void *aLoadId, + nsISupports* aCX, nsIPrincipal* aLoadingPrincipal, int32_t aCORSMode, ReferrerPolicy aReferrerPolicy) { MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!"); LOG_FUNC(GetImgLog(), "imgRequest::Init"); @@ -128,66 +129,49 @@ nsresult imgRequest::Init(nsIURI *aURI, NS_ASSERTION(mPrevChannelSink != this, "Initializing with a channel that already calls back to us!"); mChannel->SetNotificationCallbacks(this); mCacheEntry = aCacheEntry; - SetLoadId(aLoadId); + SetLoadId(aCX); + + // Grab the inner window ID of the loading document, if possible. + nsCOMPtr<nsIDocument> doc = do_QueryInterface(aCX); + if (doc) { + mInnerWindowId = doc->InnerWindowID(); + } return NS_OK; } void imgRequest::ClearLoader() { mLoader = nullptr; } -already_AddRefed<Image> -imgRequest::GetImage() -{ - MutexAutoLock lock(mMutex); - - nsRefPtr<Image> image = mImage; - return image.forget(); -} - already_AddRefed<ProgressTracker> -imgRequest::GetProgressTracker() +imgRequest::GetProgressTracker() const { MutexAutoLock lock(mMutex); if (mImage) { MOZ_ASSERT(!mProgressTracker, "Should have given mProgressTracker to mImage"); return mImage->GetProgressTracker(); } else { MOZ_ASSERT(mProgressTracker, "Should have mProgressTracker until we create mImage"); nsRefPtr<ProgressTracker> progressTracker = mProgressTracker; MOZ_ASSERT(progressTracker); return progressTracker.forget(); } } -void -imgRequest::SetImage(Image* aImage) -{ - MutexAutoLock lock(mMutex); - mImage = aImage; -} - -void -imgRequest::SetProgressTracker(ProgressTracker* aProgressTracker) -{ - MutexAutoLock lock(mMutex); - mProgressTracker = aProgressTracker; -} - void imgRequest::SetCacheEntry(imgCacheEntry *entry) { mCacheEntry = entry; } bool imgRequest::HasCacheEntry() const { return mCacheEntry != nullptr; @@ -377,16 +361,30 @@ void imgRequest::EvictFromCache() // Helper-method used by EvictFromCache() void imgRequest::ContinueEvict() { MOZ_ASSERT(NS_IsMainThread()); RemoveFromCache(); } +void +imgRequest::RequestDecode() +{ + MutexAutoLock lock(mMutex); + mDecodeRequested = true; +} + +bool +imgRequest::IsDecodeRequested() const +{ + MutexAutoLock lock(mMutex); + return mDecodeRequested; +} + nsresult imgRequest::GetURI(ImageURL **aURI) { MOZ_ASSERT(aURI); LOG_FUNC(GetImgLog(), "imgRequest::GetURI"); if (mURI) { *aURI = mURI; @@ -422,38 +420,55 @@ nsresult imgRequest::GetSecurityInfo(nsI LOG_FUNC(GetImgLog(), "imgRequest::GetSecurityInfo"); // Missing security info means this is not a security load // i.e. it is not an error when security info is missing NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); return NS_OK; } -void imgRequest::RemoveFromCache() +void +imgRequest::RemoveFromCache() { LOG_SCOPE(GetImgLog(), "imgRequest::RemoveFromCache"); - if (mIsInCache && mLoader) { + bool isInCache = false; + + { + MutexAutoLock lock(mMutex); + isInCache = mIsInCache; + } + + if (isInCache && mLoader) { // mCacheEntry is nulled out when we have no more observers. if (mCacheEntry) { mLoader->RemoveFromCache(mCacheEntry); } else { mLoader->RemoveFromCache(mURI); } } mCacheEntry = nullptr; } -bool imgRequest::HasConsumers() +bool +imgRequest::HasConsumers() const { nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker(); return progressTracker && progressTracker->ObserverCount() > 0; } +already_AddRefed<Image> +imgRequest::GetImage() const +{ + MutexAutoLock lock(mMutex); + nsRefPtr<Image> image = mImage; + return image.forget(); +} + int32_t imgRequest::Priority() const { int32_t priority = nsISupportsPriority::PRIORITY_NORMAL; nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest); if (p) p->GetPriority(&priority); return priority; } @@ -471,29 +486,42 @@ void imgRequest::AdjustPriority(imgReque if (!progressTracker->FirstObserverIs(proxy)) return; nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel); if (p) p->AdjustPriority(delta); } -void imgRequest::SetIsInCache(bool incache) +bool +imgRequest::HasTransferredData() const { - LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequest::SetIsCacheable", "incache", incache); - mIsInCache = incache; + MutexAutoLock lock(mMutex); + return mGotData; } -void imgRequest::UpdateCacheEntrySize() +void +imgRequest::SetIsInCache(bool aInCache) { - if (mCacheEntry) { - nsRefPtr<Image> image = GetImage(); - size_t size = image->SizeOfSourceWithComputedFallback(moz_malloc_size_of); - mCacheEntry->SetDataSize(size); + LOG_FUNC_WITH_PARAM(GetImgLog(), + "imgRequest::SetIsCacheable", "aInCache", aInCache); + MutexAutoLock lock(mMutex); + mIsInCache = aInCache; +} + +void +imgRequest::UpdateCacheEntrySize() +{ + if (!mCacheEntry) { + return; } + + nsRefPtr<Image> image = GetImage(); + size_t size = image->SizeOfSourceWithComputedFallback(moz_malloc_size_of); + mCacheEntry->SetDataSize(size); } void imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry, nsIRequest* aRequest) { /* get the expires info */ if (aCacheEntry) { nsCOMPtr<nsICachingChannel> cacheChannel(do_QueryInterface(aRequest)); if (cacheChannel) { @@ -600,123 +628,87 @@ imgRequest::CacheChanged(nsIRequest* aNe } // When we get here, app caches differ or app cache is involved // just in one of the loads what we also consider as a change // in a loading cache. return true; } -nsresult -imgRequest::LockImage() -{ - nsRefPtr<Image> image = GetImage(); - return image->LockImage(); -} - -nsresult -imgRequest::UnlockImage() -{ - nsRefPtr<Image> image = GetImage(); - return image->UnlockImage(); -} - -nsresult -imgRequest::RequestDecode() +bool +imgRequest::GetMultipart() const { - // If we've initialized our image, we can request a decode. - nsRefPtr<Image> image = GetImage(); - if (image) { - return image->RequestDecode(); - } - - // Otherwise, flag to do it when we get the image - mDecodeRequested = true; - - return NS_OK; -} - -nsresult -imgRequest::StartDecoding() -{ - // If we've initialized our image, we can request a decode. - nsRefPtr<Image> image = GetImage(); - if (image) { - return image->StartDecoding(); - } - - // Otherwise, flag to do it when we get the image - mDecodeRequested = true; - - return NS_OK; + MutexAutoLock lock(mMutex); + return mIsMultiPartChannel; } /** nsIRequestObserver methods **/ /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */ NS_IMETHODIMP imgRequest::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt) { LOG_SCOPE(GetImgLog(), "imgRequest::OnStartRequest"); - mNewPartPending = true; + nsRefPtr<Image> image; // Figure out if we're multipart. - nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest)); - nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker(); - if (mpchan) { - mIsMultiPartChannel = true; - } else { - MOZ_ASSERT(!mIsMultiPartChannel, "Something went wrong"); + nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest); + MOZ_ASSERT(multiPartChannel || !mIsMultiPartChannel, + "Stopped being multipart?"); + + { + MutexAutoLock lock(mMutex); + mNewPartPending = true; + image = mImage; + mIsMultiPartChannel = bool(multiPartChannel); } // If we're not multipart, we shouldn't have an image yet. - nsRefPtr<Image> image = GetImage(); - if (image && !mIsMultiPartChannel) { + if (image && !multiPartChannel) { MOZ_ASSERT_UNREACHABLE("Already have an image for a non-multipart request"); Cancel(NS_IMAGELIB_ERROR_FAILURE); return NS_ERROR_FAILURE; } /* * If mRequest is null here, then we need to set it so that we'll be able to * cancel it if our Cancel() method is called. Note that this can only * happen for multipart channels. We could simply not null out mRequest for * non-last parts, if GetIsLastPart() were reliable, but it's not. See * https://bugzilla.mozilla.org/show_bug.cgi?id=339610 */ if (!mRequest) { - NS_ASSERTION(mpchan, - "We should have an mRequest here unless we're multipart"); - nsCOMPtr<nsIChannel> chan; - mpchan->GetBaseChannel(getter_AddRefs(chan)); - mRequest = chan; + nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest); + MOZ_ASSERT(multiPartChannel, "Should have mRequest unless we're multipart"); + nsCOMPtr<nsIChannel> baseChannel; + multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel)); + mRequest = baseChannel; } nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); - if (channel) + if (channel) { channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); - /* Get our principal */ - nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest)); - if (chan) { + /* Get our principal */ nsCOMPtr<nsIScriptSecurityManager> secMan = nsContentUtils::GetSecurityManager(); if (secMan) { - nsresult rv = secMan->GetChannelResultPrincipal(chan, + nsresult rv = secMan->GetChannelResultPrincipal(channel, getter_AddRefs(mPrincipal)); if (NS_FAILED(rv)) { return rv; } } } SetCacheValidation(mCacheEntry, aRequest); mApplicationCache = GetApplicationCache(aRequest); // Shouldn't we be dead already if this gets hit? Probably multipart/x-mixed-replace... + nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker(); if (progressTracker->ObserverCount() == 0) { this->Cancel(NS_IMAGELIB_ERROR_FAILURE); } // Try to retarget OnDataAvailable to a decode thread. nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); nsCOMPtr<nsIThreadRetargetableRequest> retargetable = do_QueryInterface(aRequest); @@ -739,16 +731,18 @@ NS_IMETHODIMP imgRequest::OnStartRequest } /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */ NS_IMETHODIMP imgRequest::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status) { LOG_FUNC(GetImgLog(), "imgRequest::OnStopRequest"); MOZ_ASSERT(NS_IsMainThread(), "Can't send notifications off-main-thread"); + nsRefPtr<Image> image = GetImage(); + // XXXldb What if this is a non-last part of a multipart request? // xxx before we release our reference to mRequest, lets // save the last status that we saw so that the // imgRequestProxy will have access to it. if (mRequest) { mRequest = nullptr; // we no longer need the request } @@ -760,17 +754,16 @@ NS_IMETHODIMP imgRequest::OnStopRequest( } bool lastPart = true; nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest)); if (mpchan) mpchan->GetIsLastPart(&lastPart); bool isPartial = false; - nsRefPtr<Image> image = GetImage(); if (image && (status == NS_ERROR_NET_PARTIAL_TRANSFER)) { isPartial = true; status = NS_OK; // fake happy face } // Tell the image that it has all of the source data. Note that this can // trigger a failure, since the image might be waiting for more non-optional // data and this is the point where we break the news that it's not coming. @@ -833,201 +826,246 @@ imgRequest::CheckListenerChain() { // TODO Might need more checking here. NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); return NS_OK; } /** nsIStreamListener methods **/ -/* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */ +struct NewPartResult final +{ + explicit NewPartResult(Image* aExistingImage) + : mImage(aExistingImage) + , mIsFirstPart(!aExistingImage) + , mSucceeded(false) + { } + + nsAutoCString mContentType; + nsAutoCString mContentDisposition; + nsRefPtr<Image> mImage; + const bool mIsFirstPart; + bool mSucceeded; +}; + +static NewPartResult +PrepareForNewPart(nsIRequest* aRequest, nsIInputStream* aInStr, uint32_t aCount, + ImageURL* aURI, bool aIsMultipart, Image* aExistingImage, + ProgressTracker* aProgressTracker, uint32_t aInnerWindowId) +{ + NewPartResult result(aExistingImage); + + mimetype_closure closure; + closure.newType = &result.mContentType; + + // Look at the first few bytes and see if we can tell what the data is from + // that since servers tend to lie. :( + uint32_t out; + aInStr->ReadSegments(sniff_mimetype_callback, &closure, aCount, &out); + + nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest)); + if (result.mContentType.IsEmpty()) { + nsresult rv = NS_ERROR_FAILURE; + if (chan) { + rv = chan->GetContentType(result.mContentType); + chan->GetContentDispositionHeader(result.mContentDisposition); + } + + if (NS_FAILED(rv)) { + PR_LOG(GetImgLog(), PR_LOG_ERROR, + ("imgRequest::PrepareForNewPart -- Content type unavailable from the channel\n")); + return result; + } + } + + PR_LOG(GetImgLog(), PR_LOG_DEBUG, + ("imgRequest::PrepareForNewPart -- Got content type %s\n", + result.mContentType.get())); + + // XXX If server lied about mimetype and it's SVG, we may need to copy + // the data and dispatch back to the main thread, AND tell the channel to + // dispatch there in the future. + + // Create the new image and give it ownership of our ProgressTracker. + if (aIsMultipart) { + // Create the ProgressTracker and image for this part. + nsRefPtr<ProgressTracker> progressTracker = new ProgressTracker(); + nsRefPtr<Image> partImage = + ImageFactory::CreateImage(aRequest, progressTracker, result.mContentType, + aURI, /* aIsMultipart = */ true, + aInnerWindowId); + + if (result.mIsFirstPart) { + // First part for a multipart channel. Create the MultipartImage wrapper. + MOZ_ASSERT(aProgressTracker, "Shouldn't have given away tracker yet"); + result.mImage = new MultipartImage(partImage, aProgressTracker); + } else { + // Transition to the new part. + auto multipartImage = static_cast<MultipartImage*>(aExistingImage); + multipartImage->BeginTransitionToPart(partImage); + } + } else { + MOZ_ASSERT(!aExistingImage, "New part for non-multipart channel?"); + MOZ_ASSERT(aProgressTracker, "Shouldn't have given away tracker yet"); + + // Create an image using our progress tracker. + result.mImage = + ImageFactory::CreateImage(aRequest, aProgressTracker, result.mContentType, + aURI, /* aIsMultipart = */ false, + aInnerWindowId); + } + + MOZ_ASSERT(result.mImage); + if (!result.mImage->HasError() || aIsMultipart) { + // We allow multipart images to fail to initialize (which generally + // indicates a bad content type) without cancelling the load, because + // subsequent parts might be fine. + result.mSucceeded = true; + } + + return result; +} + +class FinishPreparingForNewPartRunnable final : public nsRunnable +{ +public: + FinishPreparingForNewPartRunnable(imgRequest* aImgRequest, + NewPartResult&& aResult) + : mImgRequest(aImgRequest) + , mResult(aResult) + { + MOZ_ASSERT(aImgRequest); + } + + NS_IMETHOD Run() override + { + mImgRequest->FinishPreparingForNewPart(mResult); + return NS_OK; + } + +private: + nsRefPtr<imgRequest> mImgRequest; + NewPartResult mResult; +}; + +void +imgRequest::FinishPreparingForNewPart(const NewPartResult& aResult) +{ + MOZ_ASSERT(NS_IsMainThread()); + + mContentType = aResult.mContentType; + + SetProperties(aResult.mContentType, aResult.mContentDisposition); + + if (aResult.mIsFirstPart) { + // Notify listeners that we have an image. + nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker(); + progressTracker->OnImageAvailable(); + MOZ_ASSERT(progressTracker->HasImage()); + } + + if (IsDecodeRequested()) { + aResult.mImage->RequestDecode(); + } +} + NS_IMETHODIMP -imgRequest::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, - nsIInputStream *inStr, uint64_t sourceOffset, - uint32_t count) +imgRequest::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, + nsIInputStream* aInStr, uint64_t aOffset, + uint32_t aCount) { - LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "count", count); + LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "count", aCount); NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!"); - nsresult rv; - mGotData = true; - nsRefPtr<Image> image = GetImage(); + nsRefPtr<Image> image; + nsRefPtr<ProgressTracker> progressTracker; + bool isMultipart = false; + bool newPartPending = false; - if (mNewPartPending) { - LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |New part; finding MIME type|"); - + // Retrieve and update our state. + { + MutexAutoLock lock(mMutex); + mGotData = true; + image = mImage; + progressTracker = mProgressTracker; + isMultipart = mIsMultiPartChannel; + newPartPending = mNewPartPending; mNewPartPending = false; - - mimetype_closure closure; - nsAutoCString newType; - closure.newType = &newType; + } - /* look at the first few bytes and see if we can tell what the data is from that - * since servers tend to lie. :( - */ - uint32_t out; - inStr->ReadSegments(sniff_mimetype_callback, &closure, count, &out); + // If this is a new part, we need to sniff its content type and create an + // appropriate image. + if (newPartPending) { + NewPartResult result = PrepareForNewPart(aRequest, aInStr, aCount, mURI, + isMultipart, image, + progressTracker, mInnerWindowId); + bool succeeded = result.mSucceeded; - nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest)); - if (newType.IsEmpty()) { - LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |sniffing of mimetype failed|"); + if (result.mImage) { + image = result.mImage; - rv = NS_ERROR_FAILURE; - if (chan) { - rv = chan->GetContentType(newType); + // Update our state to reflect this new part. + { + MutexAutoLock lock(mMutex); + mImage = image; + mProgressTracker = nullptr; } - if (NS_FAILED(rv)) { - PR_LOG(GetImgLog(), PR_LOG_ERROR, - ("[this=%p] imgRequest::OnDataAvailable -- Content type unavailable from the channel\n", - this)); - - this->Cancel(NS_IMAGELIB_ERROR_FAILURE); - - return NS_BINDING_ABORTED; + // Some property objects are not threadsafe, and we need to send + // OnImageAvailable on the main thread, so finish on the main thread. + if (NS_IsMainThread()) { + FinishPreparingForNewPart(result); + } else { + nsCOMPtr<nsIRunnable> runnable = + new FinishPreparingForNewPartRunnable(this, Move(result)); + NS_DispatchToMainThread(runnable); } - - LOG_MSG(GetImgLog(), "imgRequest::OnDataAvailable", "Got content type from the channel"); } - mContentType = newType; - SetProperties(chan); - bool firstPart = !image; - - LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "content type", mContentType.get()); - - // XXX If server lied about mimetype and it's SVG, we may need to copy - // the data and dispatch back to the main thread, AND tell the channel to - // dispatch there in the future. - - nsRefPtr<ProgressTracker> progressTracker; - { - MutexAutoLock lock(mMutex); - progressTracker = mProgressTracker; - } - - // Create the new image and give it ownership of our ProgressTracker. - if (mIsMultiPartChannel) { - // Create the ProgressTracker and image for this part. - nsRefPtr<ProgressTracker> partProgressTracker = new ProgressTracker(); - nsRefPtr<Image> partImage = - ImageFactory::CreateImage(aRequest, partProgressTracker, mContentType, - mURI, /* aIsMultipart = */ true, - static_cast<uint32_t>(mInnerWindowId)); - - if (!image) { - // First part for a multipart channel. Create the MultipartImage wrapper. - MOZ_ASSERT(progressTracker, "Shouldn't have given away tracker yet"); - image = new MultipartImage(partImage, progressTracker); - SetImage(image); - progressTracker = nullptr; - SetProgressTracker(nullptr); - } else { - // Transition to the new part. - static_cast<MultipartImage*>(image.get())->BeginTransitionToPart(partImage); - } - } else { - MOZ_ASSERT(!image, "New part for non-multipart channel?"); - MOZ_ASSERT(progressTracker, "Shouldn't have given away tracker yet"); - - // Create an image using our progress tracker. - image = - ImageFactory::CreateImage(aRequest, progressTracker, mContentType, - mURI, /* aIsMultipart = */ false, - static_cast<uint32_t>(mInnerWindowId)); - SetImage(image); - progressTracker = nullptr; - SetProgressTracker(nullptr); - } - - if (firstPart) { - // Notify listeners that we have an image. - nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker(); - progressTracker->OnImageAvailable(); - MOZ_ASSERT(progressTracker->HasImage()); - } - - if (image->HasError() && !mIsMultiPartChannel) { // Probably bad mimetype - // We allow multipart images to fail to initialize without cancelling the - // load because subsequent images might be fine; thus only single part - // images end up here. - this->Cancel(NS_IMAGELIB_ERROR_FAILURE); + if (!succeeded) { + // Something went wrong; probably a content type issue. + Cancel(NS_IMAGELIB_ERROR_FAILURE); return NS_BINDING_ABORTED; } - - MOZ_ASSERT(!progressTracker, "Should've given tracker to image"); - MOZ_ASSERT(image, "Should have image"); - - if (mDecodeRequested) { - image->StartDecoding(); - } } // Notify the image that it has new data. - rv = image->OnImageDataAvailable(aRequest, ctxt, inStr, sourceOffset, count); + nsresult rv = + image->OnImageDataAvailable(aRequest, aContext, aInStr, aOffset, aCount); if (NS_FAILED(rv)) { PR_LOG(GetImgLog(), PR_LOG_WARNING, ("[this=%p] imgRequest::OnDataAvailable -- " "copy to RasterImage failed\n", this)); - this->Cancel(NS_IMAGELIB_ERROR_FAILURE); + Cancel(NS_IMAGELIB_ERROR_FAILURE); return NS_BINDING_ABORTED; } return NS_OK; } -class SetPropertiesEvent : public nsRunnable +void +imgRequest::SetProperties(const nsACString& aContentType, + const nsACString& aContentDisposition) { -public: - SetPropertiesEvent(imgRequest* aImgRequest, nsIChannel* aChan) - : mImgRequest(aImgRequest) - , mChan(aChan) - { - MOZ_ASSERT(!NS_IsMainThread(), "Should be created off the main thread"); - MOZ_ASSERT(aImgRequest, "aImgRequest cannot be null"); - } - NS_IMETHOD Run() - { - MOZ_ASSERT(NS_IsMainThread(), "Should run on the main thread only"); - MOZ_ASSERT(mImgRequest, "mImgRequest cannot be null"); - mImgRequest->SetProperties(mChan); - return NS_OK; - } -private: - nsRefPtr<imgRequest> mImgRequest; - nsCOMPtr<nsIChannel> mChan; -}; - -void -imgRequest::SetProperties(nsIChannel* aChan) -{ - // Force execution on main thread since some property objects are non - // threadsafe. - if (!NS_IsMainThread()) { - NS_DispatchToMainThread(new SetPropertiesEvent(this, aChan)); - return; - } /* set our mimetype as a property */ - nsCOMPtr<nsISupportsCString> contentType(do_CreateInstance("@mozilla.org/supports-cstring;1")); + nsCOMPtr<nsISupportsCString> contentType = + do_CreateInstance("@mozilla.org/supports-cstring;1"); if (contentType) { - contentType->SetData(mContentType); + contentType->SetData(aContentType); mProperties->Set("type", contentType); } /* set our content disposition as a property */ - nsAutoCString disposition; - if (aChan) { - aChan->GetContentDispositionHeader(disposition); - } - if (!disposition.IsEmpty()) { - nsCOMPtr<nsISupportsCString> contentDisposition(do_CreateInstance("@mozilla.org/supports-cstring;1")); + if (!aContentDisposition.IsEmpty()) { + nsCOMPtr<nsISupportsCString> contentDisposition = + do_CreateInstance("@mozilla.org/supports-cstring;1"); if (contentDisposition) { - contentDisposition->SetData(disposition); + contentDisposition->SetData(aContentDisposition); mProperties->Set("content-disposition", contentDisposition); } } } static NS_METHOD sniff_mimetype_callback(nsIInputStream* in, void* data, const char* fromRawSegment,
--- a/image/src/imgRequest.h +++ b/image/src/imgRequest.h @@ -21,56 +21,60 @@ #include "nsIAsyncVerifyRedirectCallback.h" #include "mozilla/Mutex.h" #include "mozilla/net/ReferrerPolicy.h" class imgCacheValidator; class imgLoader; class imgRequestProxy; class imgCacheEntry; -class imgMemoryReporter; -class imgRequestNotifyRunnable; class nsIApplicationCache; class nsIProperties; class nsIRequest; class nsITimedChannel; class nsIURI; namespace mozilla { namespace image { class Image; class ImageURL; class ProgressTracker; } // namespace image } // namespace mozilla +struct NewPartResult; + class imgRequest final : public nsIStreamListener, public nsIThreadRetargetableStreamListener, public nsIChannelEventSink, public nsIInterfaceRequestor, public nsIAsyncVerifyRedirectCallback { - virtual ~imgRequest(); - -public: typedef mozilla::image::Image Image; typedef mozilla::image::ImageURL ImageURL; typedef mozilla::image::ProgressTracker ProgressTracker; typedef mozilla::net::ReferrerPolicy ReferrerPolicy; +public: explicit imgRequest(imgLoader* aLoader); NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSICHANNELEVENTSINK + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK nsresult Init(nsIURI *aURI, nsIURI *aCurrentURI, nsIRequest *aRequest, nsIChannel *aChannel, imgCacheEntry *aCacheEntry, - void *aLoadId, + nsISupports* aCX, nsIPrincipal* aLoadingPrincipal, int32_t aCORSMode, ReferrerPolicy aReferrerPolicy); void ClearLoader(); // Callers must call imgRequestProxy::Notify later. void AddProxy(imgRequestProxy *proxy); @@ -83,26 +87,18 @@ public: void CancelAndAbort(nsresult aStatus); // Called or dispatched by cancel for main thread only execution. void ContinueCancel(nsresult aStatus); // Called or dispatched by EvictFromCache for main thread only execution. void ContinueEvict(); - // Methods that get forwarded to the Image, or deferred until it's - // instantiated. - nsresult LockImage(); - nsresult UnlockImage(); - nsresult StartDecoding(); - nsresult RequestDecode(); - - inline void SetInnerWindowID(uint64_t aInnerWindowId) { - mInnerWindowId = aInnerWindowId; - } + // Request that we start decoding the image as soon as data becomes available. + void RequestDecode(); inline uint64_t InnerWindowID() const { return mInnerWindowId; } // Set the cache validation information (expiry time, whether we must // validate, etc) on the cache entry based on the request information. // If this function is called multiple times, the information set earliest @@ -110,141 +106,131 @@ public: static void SetCacheValidation(imgCacheEntry* aEntry, nsIRequest* aRequest); // Check if application cache of the original load is different from // application cache of the new load. Also lack of application cache // on one of the loads is considered a change of a loading cache since // HTTP cache may contain a different data then app cache. bool CacheChanged(nsIRequest* aNewRequest); - bool GetMultipart() const { return mIsMultiPartChannel; } + bool GetMultipart() const; // The CORS mode for which we loaded this image. int32_t GetCORSMode() const { return mCORSMode; } // The Referrer Policy in effect when loading this image. ReferrerPolicy GetReferrerPolicy() const { return mReferrerPolicy; } // The principal for the document that loaded this image. Used when trying to // validate a CORS image load. already_AddRefed<nsIPrincipal> GetLoadingPrincipal() const { nsCOMPtr<nsIPrincipal> principal = mLoadingPrincipal; return principal.forget(); } - already_AddRefed<Image> GetImage(); - // Return the ProgressTracker associated with this imgRequest. It may live // in |mProgressTracker| or in |mImage.mProgressTracker|, depending on whether // mImage has been instantiated yet. - already_AddRefed<ProgressTracker> GetProgressTracker(); + already_AddRefed<ProgressTracker> GetProgressTracker() const; + + /// Returns the Image associated with this imgRequest, if it's ready. + already_AddRefed<Image> GetImage() const; // Get the current principal of the image. No AddRefing. inline nsIPrincipal* GetPrincipal() const { return mPrincipal.get(); } // Resize the cache entry to 0 if it exists void ResetCacheEntry(); // OK to use on any thread. nsresult GetURI(ImageURL **aURI); nsresult GetCurrentURI(nsIURI **aURI); nsresult GetImageErrorCode(void); -private: - friend class imgCacheEntry; - friend class imgRequestProxy; - friend class imgLoader; - friend class imgCacheValidator; - friend class imgCacheExpirationTracker; - friend class imgRequestNotifyRunnable; - friend class mozilla::image::ProgressTracker; + /// Returns true if we've received any data. + bool HasTransferredData() const; + + /// Returns a non-owning pointer to this imgRequest's MIME type. + const char* GetMimeType() const { return mContentType.get(); } + + /// @return the priority of the underlying network request, or + /// PRIORITY_NORMAL if it doesn't support nsISupportsPriority. + int32_t Priority() const; + + /// Adjust the priority of the underlying network request by @aDelta on behalf + /// of @aProxy. + void AdjustPriority(imgRequestProxy* aProxy, int32_t aDelta); + + /// Returns a weak pointer to the underlying request. + nsIRequest* GetRequest() const { return mRequest; } + + nsITimedChannel* GetTimedChannel() const { return mTimedChannel; } - void SetImage(Image* aImage); - void SetProgressTracker(ProgressTracker* aProgressTracker); + nsresult GetSecurityInfo(nsISupports** aSecurityInfoOut); + + imgCacheValidator* GetValidator() const { return mValidator; } + void SetValidator(imgCacheValidator* aValidator) { mValidator = aValidator; } + + void* LoadId() const { return mLoadId; } + void SetLoadId(void* aLoadId) { mLoadId = aLoadId; } - inline void SetLoadId(void *aLoadId) { - mLoadId = aLoadId; - } - void Cancel(nsresult aStatus); + /// Reset the cache entry after we've dropped our reference to it. Used by + /// imgLoader when our cache entry is re-requested after we've dropped our + /// reference to it. + void SetCacheEntry(imgCacheEntry* aEntry); + + /// Returns whether we've got a reference to the cache entry. + bool HasCacheEntry() const; + + /// Set whether this request is stored in the cache. If it isn't, regardless + /// of whether this request has a non-null mCacheEntry, this imgRequest won't + /// try to update or modify the image cache. + void SetIsInCache(bool aCacheable); + void EvictFromCache(); void RemoveFromCache(); - nsresult GetSecurityInfo(nsISupports **aSecurityInfo); + // Sets properties for this image; will dispatch to main thread if needed. + void SetProperties(const nsACString& aContentType, + const nsACString& aContentDisposition); + + nsIProperties* Properties() const { return mProperties; } + + bool HasConsumers() const; - inline const char *GetMimeType() const { - return mContentType.get(); - } - inline nsIProperties *Properties() { - return mProperties; - } +private: + friend class FinishPreparingForNewPartRunnable; - // Reset the cache entry after we've dropped our reference to it. Used by the - // imgLoader when our cache entry is re-requested after we've dropped our - // reference to it. - void SetCacheEntry(imgCacheEntry *entry); + virtual ~imgRequest(); - // Returns whether we've got a reference to the cache entry. - bool HasCacheEntry() const; + void FinishPreparingForNewPart(const NewPartResult& aResult); + + void Cancel(nsresult aStatus); // Update the cache entry size based on the image container. void UpdateCacheEntrySize(); - // Return the priority of the underlying network request, or return - // PRIORITY_NORMAL if it doesn't support nsISupportsPriority. - int32_t Priority() const; - - // Adjust the priority of the underlying network request by the given delta - // on behalf of the given proxy. - void AdjustPriority(imgRequestProxy *aProxy, int32_t aDelta); - - // Return whether we've seen some data at this point - bool HasTransferredData() const { return mGotData; } - - // Set whether this request is stored in the cache. If it isn't, regardless - // of whether this request has a non-null mCacheEntry, this imgRequest won't - // try to update or modify the image cache. - void SetIsInCache(bool cacheable); - - bool IsBlockingOnload() const; - void SetBlockingOnload(bool block) const; - - bool HasConsumers(); - -public: - NS_DECL_NSISTREAMLISTENER - NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER - NS_DECL_NSIREQUESTOBSERVER - NS_DECL_NSICHANNELEVENTSINK - NS_DECL_NSIINTERFACEREQUESTOR - NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK - - // Sets properties for this image; will dispatch to main thread if needed. - void SetProperties(nsIChannel* aChan); - -private: - friend class imgMemoryReporter; + /// Returns true if RequestDecode() was called. + bool IsDecodeRequested() const; // Weak reference to parent loader; this request cannot outlive its owner. imgLoader* mLoader; nsCOMPtr<nsIRequest> mRequest; // The original URI we were loaded with. This is the same as the URI we are // keyed on in the cache. We store a string here to avoid off main thread // refcounting issues with nsStandardURL. nsRefPtr<ImageURL> mURI; // The URI of the resource we ended up loading after all redirects, etc. nsCOMPtr<nsIURI> mCurrentURI; // The principal of the document which loaded this image. Used when validating for CORS. nsCOMPtr<nsIPrincipal> mLoadingPrincipal; // The principal of this image. nsCOMPtr<nsIPrincipal> mPrincipal; - // Progress tracker -- transferred to mImage, when it gets instantiated. - nsRefPtr<ProgressTracker> mProgressTracker; - nsRefPtr<Image> mImage; nsCOMPtr<nsIProperties> mProperties; nsCOMPtr<nsISupports> mSecurityInfo; nsCOMPtr<nsIChannel> mChannel; nsCOMPtr<nsIInterfaceRequestor> mPrevChannelSink; nsCOMPtr<nsIApplicationCache> mApplicationCache; nsCOMPtr<nsITimedChannel> mTimedChannel; @@ -253,34 +239,35 @@ private: nsRefPtr<imgCacheEntry> mCacheEntry; /* we hold on to this to this so long as we have observers */ void *mLoadId; imgCacheValidator *mValidator; nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback; nsCOMPtr<nsIChannel> mNewRedirectChannel; - mozilla::Mutex mMutex; - // The ID of the inner window origin, used for error reporting. uint64_t mInnerWindowId; // The CORS mode (defined in imgIRequest) this image was loaded with. By // default, imgIRequest::CORS_NONE. int32_t mCORSMode; // The Referrer Policy (defined in ReferrerPolicy.h) used for this image. ReferrerPolicy mReferrerPolicy; nsresult mImageErrorCode; - // Sometimes consumers want to do things before the image is ready. Let them, - // and apply the action when the image becomes available. - bool mDecodeRequested : 1; + mutable mozilla::Mutex mMutex; + // Member variables protected by mMutex. Note that *all* flags in our bitfield + // are protected by mMutex; if you're adding a new flag that isn'protected, it + // must not be a part of this bitfield. + nsRefPtr<ProgressTracker> mProgressTracker; + nsRefPtr<Image> mImage; bool mIsMultiPartChannel : 1; bool mGotData : 1; bool mIsInCache : 1; - bool mBlockingOnload : 1; + bool mDecodeRequested : 1; bool mNewPartPending : 1; }; #endif
--- a/image/src/imgRequestProxy.cpp +++ b/image/src/imgRequestProxy.cpp @@ -219,18 +219,19 @@ nsresult imgRequestProxy::ChangeOwner(im // (see bug 601723). for (uint32_t i = 0; i < oldAnimationConsumers; i++) IncrementAnimationConsumers(); GetOwner()->AddProxy(this); // If we were decoded, or if we'd previously requested a decode, request a // decode on the new image - if (wasDecoded || mDecodeRequested) - GetOwner()->StartDecoding(); + if (wasDecoded || mDecodeRequested) { + StartDecoding(); + } return NS_OK; } void imgRequestProxy::AddToLoadGroup() { NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!"); @@ -347,38 +348,48 @@ NS_IMETHODIMP imgRequestProxy::CancelAnd return NS_OK; } /* void startDecode (); */ NS_IMETHODIMP imgRequestProxy::StartDecoding() { - if (!GetOwner()) - return NS_ERROR_FAILURE; - // Flag this, so we know to transfer the request if our owner changes mDecodeRequested = true; - // Forward the request - return GetOwner()->StartDecoding(); + nsRefPtr<Image> image = GetImage(); + if (image) { + return image->StartDecoding(); + } + + if (GetOwner()) { + GetOwner()->RequestDecode(); + } + + return NS_OK; } /* void requestDecode (); */ NS_IMETHODIMP imgRequestProxy::RequestDecode() { - if (!GetOwner()) - return NS_ERROR_FAILURE; - // Flag this, so we know to transfer the request if our owner changes mDecodeRequested = true; - // Forward the request - return GetOwner()->RequestDecode(); + nsRefPtr<Image> image = GetImage(); + if (image) { + return image->RequestDecode(); + } + + if (GetOwner()) { + GetOwner()->RequestDecode(); + } + + return NS_OK; } /* void lockImage (); */ NS_IMETHODIMP imgRequestProxy::LockImage() { mLockCount++; @@ -489,24 +500,25 @@ NS_IMETHODIMP imgRequestProxy::GetImage( { NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER); // It's possible that our owner has an image but hasn't notified us of it - // that'll happen if we get Canceled before the owner instantiates its image // (because Canceling unregisters us as a listener on mOwner). If we're // in that situation, just grab the image off of mOwner. nsRefPtr<Image> image = GetImage(); nsCOMPtr<imgIContainer> imageToReturn; - if (image) + if (image) { imageToReturn = do_QueryInterface(image); + } if (!imageToReturn && GetOwner()) { imageToReturn = GetOwner()->GetImage(); } - - if (!imageToReturn) + if (!imageToReturn) { return NS_ERROR_FAILURE; + } imageToReturn.swap(*aImage); return NS_OK; } /* readonly attribute unsigned long imageStatus; */ NS_IMETHODIMP imgRequestProxy::GetImageStatus(uint32_t *aStatus)
--- a/image/src/imgRequestProxy.h +++ b/image/src/imgRequestProxy.h @@ -25,17 +25,16 @@ { /* 20557898-1dd2-11b2-8f65-9c462ee2bc95 */ \ 0x20557898, \ 0x1dd2, \ 0x11b2, \ {0x8f, 0x65, 0x9c, 0x46, 0x2e, 0xe2, 0xbc, 0x95} \ } class imgINotificationObserver; -class imgRequestNotifyRunnable; class imgStatusNotifyRunnable; struct nsIntRect; class ProxyBehaviour; namespace mozilla { namespace image { class Image; class ImageURL; @@ -128,17 +127,16 @@ public: virtual nsresult Clone(imgINotificationObserver* aObserver, imgRequestProxy** aClone); nsresult GetStaticRequest(imgRequestProxy** aReturn); nsresult GetURI(ImageURL **aURI); protected: friend class mozilla::image::ProgressTracker; friend class imgStatusNotifyRunnable; - friend class imgRequestNotifyRunnable; class imgCancelRunnable; friend class imgCancelRunnable; class imgCancelRunnable : public nsRunnable { public: imgCancelRunnable(imgRequestProxy* owner, nsresult status) @@ -168,19 +166,20 @@ protected: // Return the ProgressTracker associated with mOwner and/or mImage. It may // live either on mOwner or mImage, depending on whether // (a) we have an mOwner at all // (b) whether mOwner has instantiated its image yet already_AddRefed<ProgressTracker> GetProgressTracker() const; nsITimedChannel* TimedChannel() { - if (!GetOwner()) + if (!GetOwner()) { return nullptr; - return GetOwner()->mTimedChannel; + } + return GetOwner()->GetTimedChannel(); } already_AddRefed<Image> GetImage() const; bool HasImage() const; imgRequest* GetOwner() const; nsresult PerformClone(imgINotificationObserver* aObserver, imgRequestProxy* (aAllocFn)(imgRequestProxy*),
--- a/js/public/HeapAPI.h +++ b/js/public/HeapAPI.h @@ -378,12 +378,20 @@ IsIncrementalBarrierNeededOnTenuredGCThi MOZ_ASSERT(thing); MOZ_ASSERT(!js::gc::IsInsideNursery(thing.asCell())); if (!rt->needsIncrementalBarrier()) return false; JS::Zone *zone = JS::GetTenuredGCThingZone(thing.asCell()); return JS::shadow::Zone::asShadowZone(zone)->needsIncrementalBarrier(); } +/* + * Create an object providing access to the garbage collector's internal notion + * of the current state of memory (both GC heap memory and GCthing-controlled + * malloc memory. + */ +extern JS_PUBLIC_API(JSObject *) +NewMemoryInfoObject(JSContext *cx); + } /* namespace gc */ } /* namespace js */ #endif /* js_HeapAPI_h */
--- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1490,16 +1490,73 @@ js::testingFunc_bailout(JSContext *cx, u CallArgs args = CallArgsFromVp(argc, vp); // NOP when not in IonMonkey args.rval().setUndefined(); return true; } bool +js::testingFunc_inJit(JSContext *cx, unsigned argc, jsval *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!IsBaselineEnabled(cx)) { + JSString *error = JS_NewStringCopyZ(cx, "Baseline is disabled."); + if(!error) + return false; + + args.rval().setString(error); + return true; + } + + JSScript *script = cx->currentScript(); + if (script && script->getWarmUpResetCount() >= 20) { + JSString *error = JS_NewStringCopyZ(cx, "Compilation is being repeatedly prevented. Giving up."); + if (!error) + return false; + + args.rval().setString(error); + return true; + } + + args.rval().setBoolean(cx->currentlyRunningInJit()); + return true; +} + +bool +js::testingFunc_inIon(JSContext *cx, unsigned argc, jsval *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!IsIonEnabled(cx)) { + JSString *error = JS_NewStringCopyZ(cx, "Ion is disabled."); + if (!error) + return false; + + args.rval().setString(error); + return true; + } + + JSScript *script = cx->currentScript(); + if (script && script->getWarmUpResetCount() >= 20) { + JSString *error = JS_NewStringCopyZ(cx, "Compilation is being repeatedly prevented. Giving up."); + if (!error) + return false; + + args.rval().setString(error); + return true; + } + + // false when not in ionMonkey + args.rval().setBoolean(false); + return true; +} + +bool js::testingFunc_assertFloat32(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); // NOP when not in IonMonkey args.rval().setUndefined(); return true; } @@ -2711,16 +2768,30 @@ gc::ZealModeHelpText), JS_FN_HELP("getObjectMetadata", GetObjectMetadata, 1, 0, "getObjectMetadata(obj)", " Get the metadata for an object."), JS_FN_HELP("bailout", testingFunc_bailout, 0, 0, "bailout()", " Force a bailout out of ionmonkey (if running in ionmonkey)."), + JS_FN_HELP("inJit", testingFunc_inJit, 0, 0, +"inJit()", +" Returns true when called within (jit-)compiled code. When jit compilation is disabled this\n" +" function returns an error string. This function returns false in all other cases.\n" +" Depending on truthiness, you should continue to wait for compilation to happen or stop execution.\n"), + + JS_FN_HELP("inIon", testingFunc_inIon, 0, 0, +"inIon()", +" Returns true when called within ion. When ion is disabled or when compilation is abnormally\n" +" slow to start, this function returns an error string. Otherwise, this function returns false.\n" +" This behaviour ensures that a falsy value means that we are not in ion, but expect a\n" +" compilation to occur in the future. Conversely, a truthy value means that we are either in\n" +" ion or that there is litle or no chance of ion ever compiling the current script."), + JS_FN_HELP("assertJitStackInvariants", TestingFunc_assertJitStackInvariants, 0, 0, "assertJitStackInvariants()", " Iterates the Jit stack and check that stack invariants hold."), JS_FN_HELP("setJitCompilerOption", SetJitCompilerOption, 2, 0, "setCompilerOption(<option>, <number>)", " Set a compiler option indexed in JSCompileOption enum to a number.\n"),
--- a/js/src/builtin/TestingFunctions.h +++ b/js/src/builtin/TestingFunctions.h @@ -15,11 +15,17 @@ bool DefineTestingFunctions(JSContext *cx, HandleObject obj, bool fuzzingSafe); bool testingFunc_bailout(JSContext *cx, unsigned argc, Value *vp); bool testingFunc_assertFloat32(JSContext *cx, unsigned argc, Value *vp); +bool +testingFunc_inJit(JSContext *cx, unsigned argc, Value *vp); + +bool +testingFunc_inIon(JSContext *cx, unsigned argc, Value *vp); + } /* namespace js */ #endif /* builtin_TestingFunctions_h */
--- a/js/src/configure.in +++ b/js/src/configure.in @@ -1559,16 +1559,17 @@ case "$target" in esac if test "$COMPILE_ENVIRONMENT"; then MOZ_CHECK_HEADERS(sys/inttypes.h) fi AC_DEFINE(NSCAP_DISABLE_DEBUG_PTR_TYPES) ;; *-darwin*) + AC_DEFINE(XP_DARWIN) MKSHLIB='$(CXX) $(CXXFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -o $@' MKCSHLIB='$(CC) $(CFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -o $@' MOZ_OPTIMIZE_FLAGS="-O3 -fno-stack-protector" CFLAGS="$CFLAGS -fno-common" CXXFLAGS="$CXXFLAGS -fno-common" DLL_SUFFIX=".dylib" DSO_LDOPTS='' STRIP="$STRIP -x -S"
--- a/js/src/devtools/gc-ubench/harness.js +++ b/js/src/devtools/gc-ubench/harness.js @@ -1,128 +1,316 @@ // Per-frame time sampling infra. Also GC'd: hopefully will not perturb things too badly. var numSamples = 500; var delays = new Array(numSamples); +var gcs = new Array(numSamples); +var minorGCs = new Array(numSamples); +var gcBytes = new Array(numSamples); +var mallocBytes = new Array(numSamples); var sampleIndex = 0; var sampleTime = 16; // ms var gHistogram = new Map(); // {ms: count} // Draw state. var stopped = 0; var start; var prev; +var latencyGraph; +var memoryGraph; var ctx; +var memoryCtx; // Current test state. var activeTest = undefined; var testDuration = undefined; // ms var testState = 'idle'; // One of 'idle' or 'running'. var testStart = undefined; // ms var testQueue = []; // Global defaults var globalDefaultGarbageTotal = "8M"; var globalDefaultGarbagePerFrame = "8K"; -function xpos(index) +function Graph(ctx) { + this.ctx = ctx; + + var { width, height } = ctx.canvas; + this.layout = { + xAxisLabel_Y: height - 20, + }; +} + +Graph.prototype.xpos = index => index * 2; + +Graph.prototype.clear = function () { + var { width, height } = this.ctx.canvas; + this.ctx.clearRect(0, 0, width, height); +}; + +Graph.prototype.drawScale = function (delay) { - return index * 2; + this.drawHBar(delay, `${delay}ms`, 'rgb(150,150,150)'); +} + +Graph.prototype.draw60fps = function () { + this.drawHBar(1000/60, '60fps', '#00cf61', 25); +} + +Graph.prototype.draw30fps = function () { + this.drawHBar(1000/30, '30fps', '#cf0061', 25); } -function ypos(delay) +Graph.prototype.drawAxisLabels = function (x_label, y_label) { - var r = 525 - Math.log(delay) * 64; + var ctx = this.ctx; + var { width, height } = ctx.canvas; + + ctx.fillText(x_label, width / 2, this.layout.xAxisLabel_Y); + + ctx.save(); + ctx.rotate(Math.PI/2); + var start = height / 2 - ctx.measureText(y_label).width / 2; + ctx.fillText(y_label, start, -width+20); + ctx.restore(); +} + +Graph.prototype.drawFrame = function () { + var ctx = this.ctx; + var { width, height } = ctx.canvas; + + // Draw frame to show size + ctx.strokeStyle = 'rgb(0,0,0)'; + ctx.fillStyle = 'rgb(0,0,0)'; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width, 0); + ctx.lineTo(width, height); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.stroke(); +} + +function LatencyGraph(ctx) { + Graph.call(this, ctx); + console.log(this.ctx); +} + +LatencyGraph.prototype = Object.create(Graph.prototype); + +Object.defineProperty(LatencyGraph.prototype, 'constructor', { + enumerable: false, + value: LatencyGraph }); + +LatencyGraph.prototype.ypos = function (delay) { + var { height } = this.ctx.canvas; + + var r = height + 100 - Math.log(delay) * 64; if (r < 5) return 5; return r; } -function drawHBar(delay, color, label) +LatencyGraph.prototype.drawHBar = function (delay, label, color='rgb(0,0,0)', label_offset=0) { + var ctx = this.ctx; + ctx.fillStyle = color; ctx.strokeStyle = color; - ctx.fillText(label, xpos(numSamples) + 4, ypos(delay) + 3); + ctx.fillText(label, this.xpos(numSamples) + 4 + label_offset, this.ypos(delay) + 3); ctx.beginPath(); - ctx.moveTo(xpos(0), ypos(delay)); - ctx.lineTo(xpos(numSamples), ypos(delay)); + ctx.moveTo(this.xpos(0), this.ypos(delay)); + ctx.lineTo(this.xpos(numSamples) + label_offset, this.ypos(delay)); ctx.stroke(); ctx.strokeStyle = 'rgb(0,0,0)'; ctx.fillStyle = 'rgb(0,0,0)'; } -function drawScale(delay) -{ - drawHBar(delay, 'rgb(150,150,150)', `${delay}ms`); -} - -function draw60fps() { - drawHBar(1000/60, '#00cf61', '60fps'); -} - -function draw30fps() { - drawHBar(1000/30, '#cf0061', '30fps'); -} +LatencyGraph.prototype.draw = function () { + var ctx = this.ctx; -function drawGraph() -{ - ctx.clearRect(0, 0, 1100, 550); + this.clear(); + this.drawFrame(); - drawScale(10); - draw60fps(); - drawScale(20); - drawScale(30); - draw30fps(); - drawScale(50); - drawScale(100); - drawScale(200); - drawScale(400); - drawScale(800); + for (var delay of [ 10, 20, 30, 50, 100, 200, 400, 800 ]) + this.drawScale(delay); + this.draw60fps(); + this.draw30fps(); var worst = 0, worstpos = 0; ctx.beginPath(); for (var i = 0; i < numSamples; i++) { - ctx.lineTo(xpos(i), ypos(delays[i])); + ctx.lineTo(this.xpos(i), this.ypos(delays[i])); if (delays[i] >= worst) { worst = delays[i]; worstpos = i; } } ctx.stroke(); + // Draw vertical lines marking minor and major GCs + if (performance.mozMemory) { + var { width, height } = ctx.canvas; + + ctx.strokeStyle = 'rgb(255,100,0)'; + var idx = sampleIndex % numSamples; + var gcCount = gcs[idx]; + for (var i = 0; i < numSamples; i++) { + idx = (sampleIndex + i) % numSamples; + if (gcCount < gcs[idx]) { + ctx.beginPath(); + ctx.moveTo(this.xpos(idx), 0); + ctx.lineTo(this.xpos(idx), this.layout.xAxisLabel_Y); + ctx.stroke(); + } + gcCount = gcs[idx]; + } + + ctx.strokeStyle = 'rgb(0,255,100)'; + idx = sampleIndex % numSamples; + gcCount = gcs[idx]; + for (var i = 0; i < numSamples; i++) { + idx = (sampleIndex + i) % numSamples; + if (gcCount < minorGCs[idx]) { + ctx.beginPath(); + ctx.moveTo(this.xpos(idx), 0); + ctx.lineTo(this.xpos(idx), 20); + ctx.stroke(); + } + gcCount = minorGCs[idx]; + } + } + ctx.fillStyle = 'rgb(255,0,0)'; if (worst) - ctx.fillText(''+worst.toFixed(2)+'ms', xpos(worstpos) - 10, ypos(worst) - 14); + ctx.fillText(`${worst.toFixed(2)}ms`, this.xpos(worstpos) - 10, this.ypos(worst) - 14); + + // Mark and label the slowest frame + ctx.beginPath(); + var where = sampleIndex % numSamples; + ctx.arc(this.xpos(where), this.ypos(delays[where]), 5, 0, Math.PI*2, true); + ctx.fill(); + ctx.fillStyle = 'rgb(0,0,0)'; + + this.drawAxisLabels('Time', 'Pause between frames (log scale)'); +} + +function MemoryGraph(ctx) { + Graph.call(this, ctx); + this.worstEver = this.bestEver = performance.mozMemory.zone.gcBytes; + this.limit = Math.max(this.worstEver, performance.mozMemory.zone.gcAllocTrigger); +} + +MemoryGraph.prototype = Object.create(Graph.prototype); + +Object.defineProperty(MemoryGraph.prototype, 'constructor', { + enumerable: false, + value: MemoryGraph }); + +MemoryGraph.prototype.ypos = function (size) { + var { height } = this.ctx.canvas; + + var range = this.limit - this.bestEver; + var percent = (size - this.bestEver) / range; + + return (1 - percent) * height * 0.9 + 20; +} + +MemoryGraph.prototype.drawHBar = function (size, label, color='rgb(150,150,150)') +{ + var ctx = this.ctx; + + var y = this.ypos(size); + + ctx.fillStyle = color; + ctx.strokeStyle = color; + ctx.fillText(label, this.xpos(numSamples) + 4, y + 3); + + ctx.beginPath(); + ctx.moveTo(this.xpos(0), y); + ctx.lineTo(this.xpos(numSamples), y); + ctx.stroke(); + ctx.strokeStyle = 'rgb(0,0,0)'; + ctx.fillStyle = 'rgb(0,0,0)'; +} + +function format_gcBytes(bytes) { + if (bytes < 4000) + return `${bytes} bytes`; + else if (bytes < 4e6) + return `${(bytes / 1024).toFixed(2)} KB`; + else if (bytes < 4e9) + return `${(bytes / 1024 / 1024).toFixed(2)} MB`; + else + return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`; +}; + +MemoryGraph.prototype.draw = function () { + var ctx = this.ctx; + + this.clear(); + this.drawFrame(); + + var worst = 0, worstpos = 0; + for (var i = 0; i < numSamples; i++) { + if (gcBytes[i] >= worst) { + worst = gcBytes[i]; + worstpos = i; + } + if (gcBytes[i] < this.bestEver) { + this.bestEver = gcBytes[i]; + } + } + + if (this.worstEver < worst) { + this.worstEver = worst; + this.limit = Math.max(this.worstEver, performance.mozMemory.zone.gcAllocTrigger); + } + + this.drawHBar(this.bestEver, `${format_gcBytes(this.bestEver)} min`, '#00cf61'); + this.drawHBar(this.worstEver, `${format_gcBytes(this.worstEver)} max`, '#cc1111'); + this.drawHBar(performance.mozMemory.zone.gcAllocTrigger, `${format_gcBytes(performance.mozMemory.zone.gcAllocTrigger)} trigger`, '#cc11cc'); + + ctx.fillStyle = 'rgb(255,0,0)'; + if (worst) + ctx.fillText(format_gcBytes(worst), this.xpos(worstpos) - 10, this.ypos(worst) - 14); ctx.beginPath(); var where = sampleIndex % numSamples; - ctx.arc(xpos(where), ypos(delays[where]), 5, 0, Math.PI*2, true); + ctx.arc(this.xpos(where), this.ypos(gcBytes[where]), 5, 0, Math.PI*2, true); ctx.fill(); - ctx.fillStyle = 'rgb(0,0,0)'; - ctx.fillText('Time', 550, 420); - ctx.save(); - ctx.rotate(Math.PI/2); - ctx.fillText('Pause between frames (log scale)', 150, -1060); - ctx.restore(); + ctx.beginPath(); + for (var i = 0; i < numSamples; i++) { + if (i == (sampleIndex + 1) % numSamples) + ctx.moveTo(this.xpos(i), this.ypos(gcBytes[i])); + else + ctx.lineTo(this.xpos(i), this.ypos(gcBytes[i])); + if (i == where) + ctx.stroke(); + } + ctx.stroke(); + + this.drawAxisLabels('Time', 'Heap Memory Usage'); } function stopstart() { if (stopped) { window.requestAnimationFrame(handler); prev = performance.now(); start += prev - stopped; document.getElementById('stop').value = 'Pause'; stopped = 0; } else { document.getElementById('stop').value = 'Resume'; stopped = performance.now(); } } +var previous = 0; function handler(timestamp) { if (stopped) return; if (testState === 'running' && (timestamp - testStart) > testDuration) end_test(timestamp); @@ -139,22 +327,52 @@ function handler(timestamp) // a 16.66[666] target with adequate accuracy. update_histogram(gHistogram, Math.round(delay * 100)); var t = timestamp - start; var newIndex = Math.round(t / sampleTime); while (sampleIndex < newIndex) { sampleIndex++; delays[sampleIndex % numSamples] = delay; + if (performance.mozMemory) { + gcBytes[sampleIndex % numSamples] = performance.mozMemory.gcBytes; + gcs[sampleIndex % numSamples] = performance.mozMemory.gcNumber; + minorGCs[sampleIndex % numSamples] = performance.mozMemory.minorGCCount; + } } - drawGraph(); + latencyGraph.draw(); + if (memoryGraph) + memoryGraph.draw(); window.requestAnimationFrame(handler); } +function summarize(arr) { + if (arr.length == 0) + return []; + + var result = []; + var run_start = 0; + var prev = arr[0]; + for (var i = 1; i <= arr.length; i++) { + if (i == arr.length || arr[i] != prev) { + if (i == run_start + 1) { + result.push(arr[i]); + } else { + result.push(prev + " x " + (i - run_start)); + } + run_start = i; + } + if (i != arr.length) + prev = arr[i]; + } + + return result; +} + function update_histogram(histogram, delay) { var current = histogram.has(delay) ? histogram.get(delay) : 0; histogram.set(delay, ++current); } function reset_draw_state() { @@ -197,17 +415,22 @@ function onload() // Polyfill rAF. var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; window.requestAnimationFrame = requestAnimationFrame; // Acquire our canvas. var canvas = document.getElementById('graph'); - ctx = canvas.getContext('2d'); + latencyGraph = new LatencyGraph(canvas.getContext('2d')); + + if (performance.mozMemory) { + var canvas = document.getElementById('memgraph'); + memoryGraph = new MemoryGraph(canvas.getContext('2d')); + } // Start drawing. reset_draw_state(); window.requestAnimationFrame(handler); } function run_one_test() { @@ -327,19 +550,19 @@ function reload_active_test() function change_active_test(new_test_name) { if (activeTest) activeTest.unload(); activeTest = tests.get(new_test_name); if (!activeTest.garbagePerFrame) - activeTest.garbagePerFrame = activeTest.defaultGarbagePerFrame || globalDefaultGarbagePerFrame; + activeTest.garbagePerFrame = parse_units(activeTest.defaultGarbagePerFrame || globalDefaultGarbagePerFrame); if (!activeTest.garbageTotal) - activeTest.garbageTotal = activeTest.defaultGarbageTotal || globalDefaultGarbageTotal; + activeTest.garbageTotal = parse_units(activeTest.defaultGarbageTotal || globalDefaultGarbageTotal); document.getElementById("garbage-per-frame").value = format_units(activeTest.garbagePerFrame); document.getElementById("garbage-total").value = format_units(activeTest.garbageTotal); activeTest.load(activeTest.garbageTotal); } function duration_changed()
--- a/js/src/devtools/gc-ubench/index.html +++ b/js/src/devtools/gc-ubench/index.html @@ -22,17 +22,18 @@ <script src="benchmarks/expandoEvents.js"></script> <script src="harness.js"></script> </head> <body onload="onload()" onunload="onunload()"> -<canvas id="graph" width="1080" height="550" style="padding-left:10px"></canvas> +<canvas id="graph" width="1080" height="400" style="padding-left:10px"></canvas> +<canvas id="memgraph" width="1080" height="400" style="padding-left:10px"></canvas> <div> <input type="button" id="stop" value="Pause" onclick="stopstart()"></input> </div> <div> Duration: <input type="text" id="test-duration" size="3" value="8" onchange="duration_changed()"></input>s <input type="button" id="test-one" value="Run Test" onclick="run_one_test()"></input> @@ -44,16 +45,22 @@ <select id="test-selection" required onchange="test_changed()"></select> </div> <div> Time remaining: <span id="test-progress">(not running)</span> </div <div> + 60 fps: <span id="pct60">n/a</span> + 45 fps: <span id="pct45">n/a</span> + 30 fps: <span id="pct30">n/a</span> +</div + +<div> Garbage items per frame: <input type="text" id="garbage-per-frame" size="5" value="8K" onchange="garbage_per_frame_changed()"></input> </div> <div> Garbage piles: <input type="text" id="garbage-total" size="5" value="8M" onchange="garbage_total_changed()"></input>
--- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2218,20 +2218,20 @@ BytecodeEmitter::emitNewInit(JSProtoKey updateDepth(offset); checkTypeSet(JSOP_NEWINIT); return true; } static bool IteratorResultShape(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned *shape) { - MOZ_ASSERT(bce->script->compileAndGo()); - RootedPlainObject obj(cx); - gc::AllocKind kind = GuessObjectGCKind(2); + // No need to do any guessing for the object kind, since we know exactly how + // many properties we plan to have. + gc::AllocKind kind = gc::GetGCObjectKind(2); obj = NewBuiltinClassInstance<PlainObject>(cx, kind); if (!obj) return false; Rooted<jsid> value_id(cx, AtomToId(cx->names().value)); Rooted<jsid> done_id(cx, AtomToId(cx->names().done)); if (!NativeDefineProperty(cx, obj, value_id, UndefinedHandleValue, nullptr, nullptr, JSPROP_ENUMERATE)) @@ -2251,24 +2251,20 @@ IteratorResultShape(ExclusiveContext *cx *shape = bce->objectList.add(objbox); return true; } bool BytecodeEmitter::emitPrepareIteratorResult() { - if (script->compileAndGo()) { - unsigned shape; - if (!IteratorResultShape(cx, this, &shape)) - return false; - return emitIndex32(JSOP_NEWOBJECT, shape); - } - - return emitNewInit(JSProto_Object); + unsigned shape; + if (!IteratorResultShape(cx, this, &shape)) + return false; + return emitIndex32(JSOP_NEWOBJECT, shape); } bool BytecodeEmitter::emitFinishIteratorResult(bool done) { jsatomid value_id; if (!makeAtomIndex(cx->names().value, &value_id)) return false; @@ -6580,22 +6576,22 @@ BytecodeEmitter::emitObject(ParseNode *p if (!emitNewInit(JSProto_Object)) return false; /* * Try to construct the shape of the object as we go, so we can emit a * JSOP_NEWOBJECT with the final shape instead. */ RootedPlainObject obj(cx); - if (script->compileAndGo()) { - gc::AllocKind kind = GuessObjectGCKind(pn->pn_count); - obj = NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject); - if (!obj) - return false; - } + // No need to do any guessing for the object kind, since we know exactly + // how many properties we plan to have. + gc::AllocKind kind = gc::GetGCObjectKind(pn->pn_count); + obj = NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject); + if (!obj) + return false; if (!emitPropertyList(pn, &obj, ObjectLiteral)) return false; if (obj) { /* * The object survived and has a predictable shape: update the original * bytecode.
--- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -162,30 +162,16 @@ GCRuntime::tryNewNurseryObject(JSContext JSObject *obj = nursery.allocateObject(cx, thingSize, nDynamicSlots, clasp); MOZ_ASSERT(obj); return obj; } } return nullptr; } -typedef mozilla::UniquePtr<HeapSlot, JS::FreePolicy> UniqueSlots; - -static inline UniqueSlots -MakeSlotArray(ExclusiveContext *cx, size_t count) -{ - HeapSlot *slots = nullptr; - if (count) { - slots = cx->zone()->pod_malloc<HeapSlot>(count); - if (slots) - Debug_SetSlotRangeToCrashOnTouch(slots, count); - } - return UniqueSlots(slots); -} - template <AllowGC allowGC> JSObject * GCRuntime::tryNewTenuredObject(ExclusiveContext *cx, AllocKind kind, size_t thingSize, size_t nDynamicSlots) { HeapSlot *slots = nullptr; if (nDynamicSlots) { slots = cx->zone()->pod_malloc<HeapSlot>(nDynamicSlots);
--- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -747,16 +747,17 @@ class GCRuntime void enableCompactingGC(); bool isCompactingGCEnabled(); void setGrayRootsTracer(JSTraceDataOp traceOp, void *data); bool addBlackRootsTracer(JSTraceDataOp traceOp, void *data); void removeBlackRootsTracer(JSTraceDataOp traceOp, void *data); void setMaxMallocBytes(size_t value); + int32_t getMallocBytes() const { return mallocBytesUntilGC; } void resetMallocBytes(); bool isTooMuchMalloc() const { return mallocBytesUntilGC <= 0; } void updateMallocCounter(JS::Zone *zone, size_t nbytes); void onTooMuchMalloc(); void setGCCallback(JSGCCallback callback, void *data); bool addFinalizeCallback(JSFinalizeCallback callback, void *data); void removeFinalizeCallback(JSFinalizeCallback func); @@ -776,16 +777,22 @@ class GCRuntime } JS::Zone *getCurrentZoneGroup() { return currentZoneGroup; } void setFoundBlackGrayEdges() { foundBlackGrayEdges = true; } uint64_t gcNumber() { return number; } void incGcNumber() { ++number; } + uint64_t minorGCCount() { return minorGCNumber; } + void incMinorGcNumber() { ++minorGCNumber; } + + uint64_t majorGCCount() { return majorGCNumber; } + void incMajorGcNumber() { ++majorGCNumber; } + bool isIncrementalGc() { return isIncremental; } bool isFullGc() { return isFull; } bool shouldCleanUpEverything() { return cleanUpEverything; } bool areGrayBitsValid() { return grayBitsValid; } void setGrayBitsInvalid() { grayBitsValid = false; } @@ -1026,17 +1033,16 @@ class GCRuntime bool cleanUpEverything; // Gray marking must be done after all black marking is complete. However, // we do not have write barriers on XPConnect roots. Therefore, XPConnect // roots must be accumulated in the first slice of incremental GC. We // accumulate these roots in each zone's gcGrayRoots vector and then mark // them later, after black marking is complete for each compartment. This // accumulation can fail, but in that case we switch to non-incremental GC. - friend class js::GCMarker; enum class GrayBufferState { Unused, Okay, Failed }; GrayBufferState grayBufferState; bool hasBufferedGrayRoots() const { return grayBufferState == GrayBufferState::Okay; } @@ -1057,16 +1063,19 @@ class GCRuntime mozilla::Atomic<JS::gcreason::Reason, mozilla::Relaxed> majorGCTriggerReason; JS::gcreason::Reason minorGCTriggerReason; /* Perform full GC if rt->keepAtoms() becomes false. */ bool fullGCForAtomsRequested_; + /* Incremented at the start of every minor GC. */ + uint64_t minorGCNumber; + /* Incremented at the start of every major GC. */ uint64_t majorGCNumber; /* The major GC number at which to release observed type information. */ uint64_t jitReleaseNumber; /* Incremented on every GC slice. */ uint64_t number;
--- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -189,19 +189,23 @@ CheckMarkedThing(JSTracer *trc, T **thin MOZ_ASSERT_IF(!MovingTracer::IsMovingTracer(trc), CurrentThreadCanAccessRuntime(rt)); MOZ_ASSERT(zone->runtimeFromAnyThread() == trc->runtime()); MOZ_ASSERT(trc->hasTracingDetails()); MOZ_ASSERT(thing->isAligned()); MOZ_ASSERT(MapTypeToTraceKind<T>::kind == GetGCThingTraceKind(thing)); - bool isGcMarkingTracer = IsMarkingGray(trc) || IsMarkingTracer(trc); + /* + * Do not check IsMarkingTracer directly -- it should only be used in paths + * where we cannot be the gray buffering tracer. + */ + bool isGcMarkingTracer = (trc->callback == nullptr); - MOZ_ASSERT_IF(zone->requireGCTracer(), isGcMarkingTracer); + MOZ_ASSERT_IF(zone->requireGCTracer(), isGcMarkingTracer || IsBufferingGrayRoots(trc)); if (isGcMarkingTracer) { GCMarker *gcMarker = static_cast<GCMarker *>(trc); MOZ_ASSERT_IF(gcMarker->shouldCheckCompartments(), zone->isCollecting() || rt->isAtomsZone(zone)); MOZ_ASSERT_IF(gcMarker->markColor() == GRAY, !zone->isGCMarkingBlack() || rt->isAtomsZone(zone));
--- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -802,16 +802,18 @@ js::Nursery::collect(JSRuntime *rt, JS:: * storebuffer even when the nursery is disabled or empty. It's not * safe to keep these entries as they may refer to tenured cells which * may be freed after this point. */ sb.clear(); return; } + rt->gc.incMinorGcNumber(); + rt->gc.stats.count(gcstats::STAT_MINOR_GC); TraceMinorGCStart(); TIME_START(total); AutoStopVerifyingBarriers av(rt, false);
--- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -413,17 +413,16 @@ js::gc::MarkPersistentRootedChains(JSTra void js::gc::GCRuntime::markRuntime(JSTracer *trc, TraceOrMarkRuntime traceOrMark, TraceRootsOrUsedSaved rootsSource) { gcstats::AutoPhase ap(stats, gcstats::PHASE_MARK_ROOTS); - MOZ_ASSERT(trc->callback != GCMarker::GrayCallback); MOZ_ASSERT(traceOrMark == TraceRuntime || traceOrMark == MarkRuntime); MOZ_ASSERT(rootsSource == TraceRoots || rootsSource == UseSavedRoots); MOZ_ASSERT(!rt->mainThread.suppressGC); if (traceOrMark == MarkRuntime) { gcstats::AutoPhase ap(stats, gcstats::PHASE_MARK_CCWS); @@ -557,34 +556,80 @@ void js::gc::GCRuntime::bufferGrayRoots() { // Precondition: the state has been reset to "unused" after the last GC // and the zone's buffers have been cleared. MOZ_ASSERT(grayBufferState == GrayBufferState::Unused); for (GCZonesIter zone(rt); !zone.done(); zone.next()) MOZ_ASSERT(zone->gcGrayRoots.empty()); - // Transform the GCMarker into an unholy CallbackTracer doppleganger. - MOZ_ASSERT(!IsMarkingGray(&marker)); - MOZ_ASSERT(IsMarkingTracer(&marker)); - MOZ_ASSERT(!marker.callback); - MOZ_ASSERT(!marker.bufferingGrayRootsFailed); - marker.callback = GCMarker::GrayCallback; - MOZ_ASSERT(IsMarkingGray(&marker)); + BufferGrayRootsTracer grayBufferer(rt); if (JSTraceDataOp op = grayRootTracer.op) - (*op)(&marker, grayRootTracer.data); + (*op)(&grayBufferer, grayRootTracer.data); // Propagate the failure flag from the marker to the runtime. - if (marker.bufferingGrayRootsFailed) { + if (grayBufferer.failed()) { grayBufferState = GrayBufferState::Failed; resetBufferedGrayRoots(); } else { grayBufferState = GrayBufferState::Okay; } +} - // Restore the GCMarker to its former correctness. - MOZ_ASSERT(IsMarkingGray(&marker)); - marker.bufferingGrayRootsFailed = false; - marker.callback = nullptr; - MOZ_ASSERT(!IsMarkingGray(&marker)); - MOZ_ASSERT(IsMarkingTracer(&marker)); +void +BufferGrayRootsTracer::appendGrayRoot(void *thing, JSGCTraceKind kind) +{ + MOZ_ASSERT(runtime()->isHeapBusy()); + + if (bufferingGrayRootsFailed) + return; + + GrayRoot root(thing, kind); +#ifdef DEBUG + root.debugPrinter = debugPrinter(); + root.debugPrintArg = debugPrintArg(); + root.debugPrintIndex = debugPrintIndex(); +#endif + + Zone *zone = TenuredCell::fromPointer(thing)->zone(); + if (zone->isCollecting()) { + // See the comment on SetMaybeAliveFlag to see why we only do this for + // objects and scripts. We rely on gray root buffering for this to work, + // but we only need to worry about uncollected dead compartments during + // incremental GCs (when we do gray root buffering). + switch (kind) { + case JSTRACE_OBJECT: + static_cast<JSObject *>(thing)->compartment()->maybeAlive = true; + break; + case JSTRACE_SCRIPT: + static_cast<JSScript *>(thing)->compartment()->maybeAlive = true; + break; + default: + break; + } + if (!zone->gcGrayRoots.append(root)) + bufferingGrayRootsFailed = true; + } } + +void +GCRuntime::markBufferedGrayRoots(JS::Zone *zone) +{ + MOZ_ASSERT(grayBufferState == GrayBufferState::Okay); + MOZ_ASSERT(zone->isGCMarkingGray() || zone->isGCCompacting()); + + for (GrayRoot *elem = zone->gcGrayRoots.begin(); elem != zone->gcGrayRoots.end(); elem++) { +#ifdef DEBUG + marker.setTracingDetails(elem->debugPrinter, elem->debugPrintArg, elem->debugPrintIndex); +#endif + MarkKind(&marker, &elem->thing, elem->kind); + } +} + +void +GCRuntime::resetBufferedGrayRoots() const +{ + MOZ_ASSERT(grayBufferState != GrayBufferState::Okay, + "Do not clear the gray buffers unless we are Failed or becoming Unused"); + for (GCZonesIter zone(rt); !zone.done(); zone.next()) + zone->gcGrayRoots.clearAndFree(); +}
--- a/js/src/gc/Tracer.cpp +++ b/js/src/gc/Tracer.cpp @@ -508,17 +508,16 @@ MarkStack::sizeOfExcludingThis(mozilla:: } /* * DoNotTraceWeakMaps: the GC is recomputing the liveness of WeakMap entries, * so we delay visting entries. */ GCMarker::GCMarker(JSRuntime *rt) : JSTracer(rt, nullptr, DoNotTraceWeakMaps), - bufferingGrayRootsFailed(false), stack(size_t(-1)), color(BLACK), unmarkedArenaStackTop(nullptr), markLaterArenas(0), started(false), strictCompartmentChecking(false) { } @@ -639,91 +638,22 @@ void GCMarker::checkZone(void *p) { MOZ_ASSERT(started); DebugOnly<Cell *> cell = static_cast<Cell *>(p); MOZ_ASSERT_IF(cell->isTenured(), cell->asTenured().zone()->isCollecting()); } #endif -void -GCRuntime::resetBufferedGrayRoots() const -{ - MOZ_ASSERT(grayBufferState != GrayBufferState::Okay, - "Do not clear the gray buffers unless we are Failed or becoming Unused"); - for (GCZonesIter zone(rt); !zone.done(); zone.next()) - zone->gcGrayRoots.clearAndFree(); -} - -void -GCRuntime::markBufferedGrayRoots(JS::Zone *zone) -{ - MOZ_ASSERT(grayBufferState == GrayBufferState::Okay); - MOZ_ASSERT(zone->isGCMarkingGray() || zone->isGCCompacting()); - - for (GrayRoot *elem = zone->gcGrayRoots.begin(); elem != zone->gcGrayRoots.end(); elem++) { -#ifdef DEBUG - marker.setTracingDetails(elem->debugPrinter, elem->debugPrintArg, elem->debugPrintIndex); -#endif - MarkKind(&marker, &elem->thing, elem->kind); - } -} - -void -GCMarker::appendGrayRoot(void *thing, JSGCTraceKind kind) -{ - MOZ_ASSERT(started); - - if (bufferingGrayRootsFailed) - return; - - GrayRoot root(thing, kind); -#ifdef DEBUG - root.debugPrinter = debugPrinter(); - root.debugPrintArg = debugPrintArg(); - root.debugPrintIndex = debugPrintIndex(); -#endif - - Zone *zone = TenuredCell::fromPointer(thing)->zone(); - if (zone->isCollecting()) { - // See the comment on SetMaybeAliveFlag to see why we only do this for - // objects and scripts. We rely on gray root buffering for this to work, - // but we only need to worry about uncollected dead compartments during - // incremental GCs (when we do gray root buffering). - switch (kind) { - case JSTRACE_OBJECT: - static_cast<JSObject *>(thing)->compartment()->maybeAlive = true; - break; - case JSTRACE_SCRIPT: - static_cast<JSScript *>(thing)->compartment()->maybeAlive = true; - break; - default: - break; - } - if (!zone->gcGrayRoots.append(root)) - bufferingGrayRootsFailed = true; - } -} - -void -GCMarker::GrayCallback(JSTracer *trc, void **thingp, JSGCTraceKind kind) -{ - MOZ_ASSERT(thingp); - MOZ_ASSERT(*thingp); - GCMarker *gcmarker = static_cast<GCMarker *>(trc); - gcmarker->appendGrayRoot(*thingp, kind); -} - size_t GCMarker::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { size_t size = stack.sizeOfExcludingThis(mallocSizeOf); for (ZonesIter zone(runtime(), WithAtoms); !zone.done(); zone.next()) size += zone->gcGrayRoots.sizeOfExcludingThis(mallocSizeOf); return size; } void js::SetMarkStackLimit(JSRuntime *rt, size_t limit) { rt->gc.setMarkStackLimit(limit); } -
--- a/js/src/gc/Tracer.h +++ b/js/src/gc/Tracer.h @@ -196,21 +196,16 @@ class GCMarker : public JSTracer } bool isDrained() { return isMarkStackEmpty() && !unmarkedArenaStackTop; } bool drainMarkStack(SliceBudget &budget); - /* Set to false if we OOM while buffering gray roots. */ - bool bufferingGrayRootsFailed; - - static void GrayCallback(JSTracer *trc, void **thing, JSGCTraceKind kind); - void setGCMode(JSGCMode mode) { stack.setGCMode(mode); } size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; #ifdef DEBUG bool shouldCheckCompartments() { return strictCompartmentChecking; } #endif @@ -325,33 +320,54 @@ class GCMarker : public JSTracer /* * If this is true, all marked objects must belong to a compartment being * GCed. This is used to look for compartment bugs. */ mozilla::DebugOnly<bool> strictCompartmentChecking; }; +// Append traced things to a buffer on the zone for use later in the GC. +// See the comment in GCRuntime.h above grayBufferState for details. +class BufferGrayRootsTracer : public JSTracer +{ + // Set to false if we OOM while buffering gray roots. + bool bufferingGrayRootsFailed; + + void appendGrayRoot(void *thing, JSGCTraceKind kind); + + public: + explicit BufferGrayRootsTracer(JSRuntime *rt) + : JSTracer(rt, grayTraceCallback), bufferingGrayRootsFailed(false) + {} + + static void grayTraceCallback(JSTracer *trc, void **thingp, JSGCTraceKind kind) { + static_cast<BufferGrayRootsTracer *>(trc)->appendGrayRoot(*thingp, kind); + } + + bool failed() const { return bufferingGrayRootsFailed; } +}; + void SetMarkStackLimit(JSRuntime *rt, size_t limit); // Return true if this trace is happening on behalf of gray buffering during // the marking phase of incremental GC. inline bool -IsMarkingGray(JSTracer *trc) +IsBufferingGrayRoots(JSTracer *trc) { - return trc->callback == js::GCMarker::GrayCallback; + return trc->callback == BufferGrayRootsTracer::grayTraceCallback; } // Return true if this trace is happening on behalf of the marking phase of GC. inline bool IsMarkingTracer(JSTracer *trc) { // If we call this on the gray-buffering tracer, then we have encountered a // marking path that will be wrong when tracing with a callback marker to // enqueue for deferred gray marking. - MOZ_ASSERT(!IsMarkingGray(trc)); + MOZ_ASSERT(!IsBufferingGrayRoots(trc)); return trc->callback == nullptr; } } /* namespace js */ #endif /* js_Tracer_h */
--- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -38,17 +38,17 @@ class ZoneHeapThreshold public: ZoneHeapThreshold() : gcHeapGrowthFactor_(3.0), gcTriggerBytes_(0) {} double gcHeapGrowthFactor() const { return gcHeapGrowthFactor_; } size_t gcTriggerBytes() const { return gcTriggerBytes_; } - bool isCloseToAllocTrigger(const js::gc::HeapUsage& usage, bool highFrequencyGC) const; + double allocTrigger(bool highFrequencyGC) const; void updateAfterGC(size_t lastBytes, JSGCInvocationKind gckind, const GCSchedulingTunables &tunables, const GCSchedulingState &state); void updateForRemovedArena(const GCSchedulingTunables &tunables); private: static double computeZoneHeapGrowthFactorForHeapSize(size_t lastBytes, const GCSchedulingTunables &tunables,
--- a/js/src/jit-test/tests/basic/bug908915.js +++ b/js/src/jit-test/tests/basic/bug908915.js @@ -1,14 +1,15 @@ // |jit-test| error: 42 function f(y) {} for each(let e in newGlobal()) { if (e.name === "quit" || e.name == "readline" || e.name == "terminate" || e.name == "nestedShell") continue; + print(e.name); try { e(); } catch (r) {} } (function() { arguments.__proto__.__proto__ = newGlobal() function f(y) { y()
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/self-test/inIon.js @@ -0,0 +1,20 @@ +// Test that inIon eventually becomes truthy. +// This code should never timeout. + +function callInIon() { + return inIon(); +}; + +function test() { + // Test with OSR. + while(!inIon()); + + // Test with inlining. + while(!callInIon()); + + // Test with zealous gc preventing compilation. + while(!inIon()) gc(); +}; + +test(); +
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/self-test/inJit.js @@ -0,0 +1,20 @@ +// Test that inJit eventually becomes truthy. +// This code should never timeout. + +function callInJit() { + return inJit(); +}; + +function test() { + // Test with OSR. + while(!inJit()); + + // Test with inlining. + while(!callInJit()); + + // Test with zealous gc preventing compilation. + while(!inJit()) gc(); +}; + +test(); +
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/self-test/notInIon.js @@ -0,0 +1,3 @@ +// |jit-test| --no-ion + +assertEq(inIon(), "Ion is disabled.");
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/self-test/notInJit.js @@ -0,0 +1,4 @@ +// |jit-test| --no-baseline + +assertEq(inJit(), "Baseline is disabled."); +assertEq(inIon(), "Ion is disabled.");
--- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -859,16 +859,17 @@ class IonBuilder const Class *clasp3 = nullptr, const Class *clasp4 = nullptr); InliningStatus inlineIsConstructing(CallInfo &callInfo); InliningStatus inlineSubstringKernel(CallInfo &callInfo); // Testing functions. InliningStatus inlineBailout(CallInfo &callInfo); InliningStatus inlineAssertFloat32(CallInfo &callInfo); + InliningStatus inlineTrue(CallInfo &callInfo); // Bind function. InliningStatus inlineBoundFunction(CallInfo &callInfo, JSFunction *target); // Main inlining functions InliningStatus inlineNativeCall(CallInfo &callInfo, JSFunction *target); InliningStatus inlineNativeGetter(CallInfo &callInfo, JSFunction *target); InliningStatus inlineNonFunctionCall(CallInfo &callInfo, JSObject *target);
--- a/js/src/jit/JitcodeMap.cpp +++ b/js/src/jit/JitcodeMap.cpp @@ -713,16 +713,24 @@ JitcodeGlobalTable::verifySkiplist() } curEntry = curEntry->tower_->next(0); } MOZ_ASSERT(count == skiplistSize_); } #endif // DEBUG +void +JitcodeGlobalTable::setAllEntriesAsExpired(JSRuntime *rt) +{ + AutoSuppressProfilerSampling suppressSampling(rt); + for (Range r(*this); !r.empty(); r.popFront()) + r.front()->setAsExpired(); +} + bool JitcodeGlobalTable::markIteratively(JSTracer *trc) { // JitcodeGlobalTable must keep entries that are in the sampler buffer // alive. This conditionality is akin to holding the entries weakly. // // If this table were marked at the beginning of the mark phase, then // sampling would require a read barrier for sampling in between @@ -757,17 +765,17 @@ JitcodeGlobalTable::markIteratively(JSTr // If an entry is not sampled, reset its generation to the invalid // generation, and conditionally mark the rest of the entry if its // JitCode is not already marked. This conditional marking ensures // that so long as the JitCode *may* be sampled, we keep any // information that may be handed out to the sampler, like tracked // types used by optimizations and scripts used for pc to line number // mapping, alive as well. if (!entry->isSampled(gen, lapCount)) { - entry->setGeneration(UINT32_MAX); + entry->setAsExpired(); if (!entry->baseEntry().isJitcodeMarkedFromAnyThread()) continue; } // The table is runtime-wide. Not all zones may be participating in // the GC. if (!entry->zone()->isCollecting() || entry->zone()->isGCFinished()) continue;
--- a/js/src/jit/JitcodeMap.h +++ b/js/src/jit/JitcodeMap.h @@ -596,16 +596,19 @@ class JitcodeGlobalEntry } uint32_t generation() const { return baseEntry().generation(); } void setGeneration(uint32_t gen) { baseEntry().setGeneration(gen); } + void setAsExpired() { + baseEntry().setGeneration(UINT32_MAX); + } bool isSampled(uint32_t currentGen, uint32_t lapCount) { return baseEntry().isSampled(currentGen, lapCount); } bool startsBelowPointer(void *ptr) const { return base_.startsBelowPointer(ptr); } bool endsAbovePointer(void *ptr) const { @@ -948,16 +951,17 @@ class JitcodeGlobalTable } bool addEntry(const JitcodeGlobalEntry::DummyEntry &entry, JSRuntime *rt) { return addEntry(JitcodeGlobalEntry(entry), rt); } void removeEntry(JitcodeGlobalEntry &entry, JitcodeGlobalEntry **prevTower, JSRuntime *rt); void releaseEntry(JitcodeGlobalEntry &entry, JitcodeGlobalEntry **prevTower, JSRuntime *rt); + void setAllEntriesAsExpired(JSRuntime *rt); bool markIteratively(JSTracer *trc); void sweep(JSRuntime *rt); private: bool addEntry(const JitcodeGlobalEntry &entry, JSRuntime *rt); JitcodeGlobalEntry *lookupInternal(void *ptr);
--- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -245,16 +245,18 @@ IonBuilder::inlineNativeCall(CallInfo &c if (native == js::SetTypedObjectOffset) return inlineSetTypedObjectOffset(callInfo); // Testing Functions if (native == testingFunc_bailout) return inlineBailout(callInfo); if (native == testingFunc_assertFloat32) return inlineAssertFloat32(callInfo); + if (native == testingFunc_inIon || native == testingFunc_inJit) + return inlineTrue(callInfo); // Bound function if (native == js::CallOrConstructBoundFunction) return inlineBoundFunction(callInfo, target); // Simd functions #define INLINE_FLOAT32X4_SIMD_ARITH_(OP) \ if (native == js::simd_float32x4_##OP) \ @@ -2564,16 +2566,25 @@ IonBuilder::inlineBailout(CallInfo &call MConstant *undefined = MConstant::New(alloc(), UndefinedValue()); current->add(undefined); current->push(undefined); return InliningStatus_Inlined; } IonBuilder::InliningStatus +IonBuilder::inlineTrue(CallInfo &callInfo) +{ + callInfo.setImplicitlyUsedUnchecked(); + + pushConstant(BooleanValue(true)); + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus IonBuilder::inlineAssertFloat32(CallInfo &callInfo) { callInfo.setImplicitlyUsedUnchecked(); MDefinition *secondArg = callInfo.getArg(1); MOZ_ASSERT(secondArg->type() == MIRType_Boolean); MOZ_ASSERT(secondArg->isConstantValue(