Merge inbound to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2015-03-24 11:34:55 -04:00
commit 305b3df92d
229 changed files with 5959 additions and 3393 deletions

View File

@ -268,7 +268,7 @@ xpcAccessibleTable::GetSelectedCells(nsIArray** aSelectedCells)
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);

View File

@ -0,0 +1,8 @@
{
"gaia": {
"l10n": {
"vcs": "hgtool",
"root": "https://hg.mozilla.org/gaia-l10n"
}
}
}

View File

@ -36,6 +36,8 @@ relativesrcdir toolkit/locales:
#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
@ -54,6 +56,7 @@ relativesrcdir toolkit/locales:
% 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:

View File

@ -342,6 +342,17 @@ nsBrowserContentHandler.prototype = {
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))) {

View File

@ -4651,8 +4651,7 @@ nsDocShell::IsNavigationAllowed(bool aDisplayPrintErrorDialog,
bool aCheckIfUnloadFired)
{
bool isAllowed = !IsPrintingOrPP(aDisplayPrintErrorDialog) &&
(!aCheckIfUnloadFired || !mFiredUnloadEvent) &&
!mBlockNavigation;
(!aCheckIfUnloadFired || !mFiredUnloadEvent);
if (!isAllowed) {
return false;
}
@ -9563,8 +9562,6 @@ nsDocShell::InternalLoad(nsIURI* aURI,
nsIDocShell** aDocShell,
nsIRequest** aRequest)
{
MOZ_RELEASE_ASSERT(!mBlockNavigation);
nsresult rv = NS_OK;
mOriginalUriString.Truncate();
@ -10020,19 +10017,6 @@ nsDocShell::InternalLoad(nsIURI* aURI,
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.
@ -10122,16 +10106,6 @@ nsDocShell::InternalLoad(nsIURI* aURI,
}
}
/* 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.
*/
@ -10166,12 +10140,36 @@ nsDocShell::InternalLoad(nsIURI* 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);
@ -10187,10 +10185,6 @@ nsDocShell::InternalLoad(nsIURI* aURI,
}
}
// Inform the favicon service that the favicon for oldURI also
// applies to aURI.
CopyFavicon(currentURI, aURI, mInPrivateBrowsing);
return NS_OK;
}
}
@ -13995,7 +13989,8 @@ nsDocShell::ChannelIntercepted(nsIInterceptedChannel* aChannel)
}
}
return swm->DispatchFetchEvent(doc, aChannel);
bool isReload = mLoadType & LOAD_CMD_RELOAD;
return swm->DispatchFetchEvent(doc, aChannel, isReload);
}
NS_IMETHODIMP

View File

@ -898,7 +898,6 @@ protected:
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

View File

@ -442,6 +442,9 @@ AnimationPlayer::DoPlay()
// 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();

View File

@ -199,27 +199,6 @@ function EventWatcher(watchedNode, eventTypes)
// 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)
{
@ -294,41 +273,92 @@ function checkStateAtActiveIntervalEndTime(player)
'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');

View File

@ -190,23 +190,6 @@ function EventWatcher(watchedNode, eventTypes)
// 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)
{
@ -268,37 +251,28 @@ function checkStateAtActiveIntervalEndTime(player)
'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');
// 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.
test(function(t)
{
var div = addDiv(t, {'class': 'animated-div'});
flushComputedStyle(div);
div.style.marginLeft = '200px'; // initiate transition
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');

View File

@ -24,6 +24,7 @@
#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__)
@ -389,10 +390,27 @@ nsPerformanceNavigation::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenP
}
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)
@ -412,6 +430,7 @@ nsPerformance::nsPerformance(nsPIDOMWindow* aWindow,
nsPerformance::~nsPerformance()
{
mozilla::DropJSObjects(this);
}
// QueryInterface implementation for nsPerformance
@ -420,6 +439,18 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPerformance)
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()

View File

@ -13,6 +13,7 @@
#include "nsContentUtils.h"
#include "nsPIDOMWindow.h"
#include "js/TypeDecls.h"
#include "js/RootingAPI.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/DOMEventTargetHelper.h"
@ -297,7 +298,7 @@ public:
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
{
@ -344,6 +345,8 @@ public:
mozilla::ErrorResult& aRv);
void ClearMeasures(const mozilla::dom::Optional<nsAString>& aName);
void GetMozMemory(JSContext *aCx, JS::MutableHandle<JSObject*> aObj);
IMPL_EVENT_HANDLER(resourcetimingbufferfull)
private:
@ -364,6 +367,7 @@ private:
nsTArray<nsRefPtr<PerformanceEntry> > mEntries;
nsRefPtr<nsPerformance> mParentPerformance;
uint64_t mPrimaryBufferSize;
JS::Heap<JSObject*> mMozMemory;
static const uint64_t kDefaultBufferSize = 150;

View File

@ -10,13 +10,13 @@ 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;
}

20
dom/cache/Action.h vendored
View File

@ -18,21 +18,19 @@ 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
@ -51,6 +49,9 @@ public:
// 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();
@ -63,8 +64,7 @@ public:
// 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();

429
dom/cache/Context.cpp vendored
View File

@ -6,10 +6,12 @@
#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"
@ -21,19 +23,18 @@ 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
@ -43,7 +44,7 @@ public:
MOZ_ASSERT(qm);
qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin),
Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT),
mQuotaId);
mQuotaInfo.mStorageId);
return NS_OK;
}
@ -51,7 +52,6 @@ private:
~QuotaReleaseRunnable() { }
const QuotaInfo mQuotaInfo;
const nsCString mQuotaId;
};
} // anonymous namespace
@ -70,20 +70,19 @@ using mozilla::dom::quota::PersistenceType;
// 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())
, mState(STATE_INIT)
, mResult(NS_OK)
, mState(STATE_INIT)
, mNeedsQuotaRelease(false)
{
MOZ_ASSERT(mContext);
MOZ_ASSERT(mManager);
@ -104,26 +103,35 @@ public:
return rv;
}
virtual void Resolve(nsresult aRv) override
{
// 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));
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 QuotaInitRunnable to initiating thread.");
}
}
private:
class SyncResolver final : public Action::Resolver
{
public:
SyncResolver()
: mResolved(false)
, mResult(NS_OK)
{ }
virtual void
Resolve(nsresult aRv) override
{
MOZ_ASSERT(!mResolved);
mResolved = true;
mResult = aRv;
};
bool Resolved() const { return mResolved; }
nsresult Result() const { return mResult; }
private:
~SyncResolver() { }
bool mResolved;
nsresult mResult;
NS_INLINE_DECL_REFCOUNTING(Context::QuotaInitRunnable::SyncResolver, override)
};
~QuotaInitRunnable()
{
MOZ_ASSERT(mState == STATE_COMPLETE);
@ -152,21 +160,22 @@ private:
}
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:
//
@ -191,7 +200,7 @@ NS_IMPL_ISUPPORTS_INHERITED(mozilla::dom::cache::Context::QuotaInitRunnable,
// +----------+------------+ |
// | |
// +---------v---------+ +------v------+
// | Running | Resolve() | Completing |
// | Running | | Completing |
// | (Quota IO Thread) +------------>(Orig Thread)|
// +-------------------+ +------+------+
// |
@ -200,14 +209,15 @@ NS_IMPL_ISUPPORTS_INHERITED(mozilla::dom::cache::Context::QuotaInitRunnable,
// +----------+
//
// 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:
@ -215,8 +225,8 @@ Context::QuotaInitRunnable::Run()
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();
@ -226,20 +236,26 @@ Context::QuotaInitRunnable::Run()
&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;
}
@ -247,13 +263,21 @@ Context::QuotaInitRunnable::Run()
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;
}
@ -273,21 +297,21 @@ Context::QuotaInitRunnable::Run()
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;
}
@ -298,8 +322,15 @@ Context::QuotaInitRunnable::Run()
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();
@ -309,10 +340,20 @@ Context::QuotaInitRunnable::Run()
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;
}
@ -321,6 +362,7 @@ Context::QuotaInitRunnable::Run()
// is initialized.
class Context::ActionRunnable final : public nsIRunnable
, public Action::Resolver
, public Context::Activity
{
public:
ActionRunnable(Context* aContext, nsIEventTarget* aTarget, Action* aAction,
@ -332,6 +374,7 @@ public:
, mInitiatingThread(NS_GetCurrentThread())
, mState(STATE_INIT)
, mResult(NS_OK)
, mExecutingRunOnTarget(false)
{
MOZ_ASSERT(mContext);
MOZ_ASSERT(mTarget);
@ -354,12 +397,15 @@ public:
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();
@ -369,14 +415,26 @@ public:
{
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:
@ -392,7 +450,7 @@ private:
NS_ASSERT_OWNINGTHREAD(Action::Resolver);
MOZ_ASSERT(mContext);
MOZ_ASSERT(mAction);
mContext->OnActionRunnableComplete(this);
mContext->RemoveActivity(this);
mContext = nullptr;
mAction = nullptr;
}
@ -402,6 +460,7 @@ private:
STATE_INIT,
STATE_RUN_ON_TARGET,
STATE_RUNNING,
STATE_RESOLVING,
STATE_COMPLETING,
STATE_COMPLETE
};
@ -414,13 +473,15 @@ private:
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.
@ -432,11 +493,17 @@ NS_IMPL_ISUPPORTS_INHERITED(mozilla::dom::cache::Context::ActionRunnable,
// |
// +-------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)|
// +------------------+ +-------+-----+
// |
@ -457,8 +524,39 @@ Context::ActionRunnable::Run()
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;
}
// -------------------
@ -482,6 +580,99 @@ Context::ActionRunnable::Run()
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)
@ -489,8 +680,7 @@ 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
@ -535,9 +725,29 @@ 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();
}
}
@ -545,15 +755,20 @@ 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();
}
}
}
@ -563,14 +778,8 @@ 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);
@ -589,21 +798,28 @@ Context::DispatchAction(nsIEventTarget* aTarget, Action* aAction)
// 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;
}
@ -618,11 +834,32 @@ Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo)
}
void
Context::OnActionRunnableComplete(ActionRunnable* aActionRunnable)
Context::AddActivity(Activity* aActivity)
{
NS_ASSERT_OWNINGTHREAD(Context);
MOZ_ASSERT(aActionRunnable);
MOZ_ALWAYS_TRUE(mActionRunnables.RemoveElement(aActionRunnable));
MOZ_ASSERT(aActivity);
MOZ_ASSERT(!mActivityList.Contains(aActivity));
mActivityList.AppendElement(aActivity);
}
void
Context::RemoveActivity(Activity* aActivity)
{
NS_ASSERT_OWNINGTHREAD(Context);
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

106
dom/cache/Context.h vendored
View File

@ -11,10 +11,13 @@
#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 {
@ -22,6 +25,7 @@ namespace cache {
class Action;
class Manager;
class OfflineStorage;
// The Context class is RAII-style class for managing IO operations within the
// Cache.
@ -31,8 +35,20 @@ class Manager;
// 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
@ -44,6 +60,56 @@ class Manager;
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);
@ -60,12 +126,28 @@ public:
// 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;
@ -86,16 +168,28 @@ private:
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)

View File

@ -22,8 +22,8 @@ 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;
@ -89,6 +89,7 @@ DBSchema::CreateSchema(mozIStorageConnection* aConn)
"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, "
@ -970,6 +971,7 @@ DBSchema::InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
"request_headers_guard, "
"request_mode, "
"request_credentials, "
"request_cache, "
"request_body_id, "
"response_type, "
"response_url, "
@ -979,7 +981,7 @@ DBSchema::InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
"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; }
@ -1006,34 +1008,38 @@ DBSchema::InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
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->BindStringParameter(9, aResponse.url());
rv = state->BindInt32Parameter(9, static_cast<int32_t>(aResponse.type()));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = state->BindInt32Parameter(10, aResponse.status());
rv = state->BindStringParameter(10, aResponse.url());
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = state->BindUTF8StringParameter(11, aResponse.statusText());
rv = state->BindInt32Parameter(11, aResponse.status());
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = state->BindInt32Parameter(12,
rv = state->BindUTF8StringParameter(12, aResponse.statusText());
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
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();
@ -1219,6 +1225,7 @@ DBSchema::ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId,
"request_headers_guard, "
"request_mode, "
"request_credentials, "
"request_cache, "
"request_body_id "
"FROM entries "
"WHERE id=?1;"
@ -1261,13 +1268,19 @@ DBSchema::ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId,
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; }
}

85
dom/cache/Manager.cpp vendored
View File

@ -177,7 +177,10 @@ public:
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();
}
}
@ -911,15 +914,19 @@ private:
{
// 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)));
@ -1404,6 +1411,9 @@ Manager::RemoveListener(Listener* aListener)
mListeners.RemoveElement(aListener, ListenerEntryListenerComparator());
MOZ_ASSERT(!mListeners.Contains(aListener,
ListenerEntryListenerComparator()));
if (mListeners.IsEmpty() && mContext) {
mContext->AllowToClose();
}
}
void
@ -1421,6 +1431,21 @@ Manager::RemoveContext(Context* aContext)
}
}
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)
{
@ -1450,7 +1475,7 @@ Manager::ReleaseCacheId(CacheId aCacheId)
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,
@ -1493,7 +1518,7 @@ Manager::ReleaseBodyId(const nsID& aBodyId)
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);
@ -1535,9 +1560,8 @@ Manager::CacheMatch(Listener* aListener, RequestId aRequestId, CacheId aCacheId,
{
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();
@ -1556,8 +1580,8 @@ Manager::CacheMatchAll(Listener* aListener, RequestId aRequestId,
{
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;
}
@ -1578,8 +1602,8 @@ Manager::CachePutAll(Listener* aListener, RequestId aRequestId, CacheId aCacheId
{
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);
@ -1598,8 +1622,8 @@ Manager::CacheDelete(Listener* aListener, RequestId aRequestId,
{
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);
@ -1616,8 +1640,8 @@ Manager::CacheKeys(Listener* aListener, RequestId aRequestId,
{
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;
}
@ -1637,8 +1661,8 @@ Manager::StorageMatch(Listener* aListener, RequestId aRequestId,
{
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;
}
@ -1657,8 +1681,8 @@ Manager::StorageHas(Listener* aListener, RequestId aRequestId,
{
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;
}
@ -1675,8 +1699,8 @@ Manager::StorageOpen(Listener* aListener, RequestId aRequestId,
{
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);
@ -1692,8 +1716,8 @@ Manager::StorageDelete(Listener* aListener, RequestId aRequestId,
{
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;
}
@ -1710,8 +1734,8 @@ Manager::StorageKeys(Listener* aListener, RequestId aRequestId,
{
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;
}
@ -1727,6 +1751,7 @@ Manager::Manager(ManagerId* aManagerId, nsIThread* aIOThread)
, mIOThread(aIOThread)
, mContext(nullptr)
, mShuttingDown(false)
, mValid(true)
{
MOZ_ASSERT(mManagerId);
MOZ_ASSERT(mIOThread);
@ -1765,11 +1790,6 @@ Manager::Shutdown()
// 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) {
@ -1789,6 +1809,7 @@ Manager::CurrentContext()
nsRefPtr<Context> ref = mContext;
if (!ref) {
MOZ_ASSERT(!mShuttingDown);
MOZ_ASSERT(mValid);
nsRefPtr<Action> setupAction = new SetupAction();
ref = Context::Create(this, setupAction);
mContext = ref;

10
dom/cache/Manager.h vendored
View File

@ -125,6 +125,11 @@ public:
// 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
@ -214,8 +219,8 @@ private:
struct ListenerEntry
{
ListenerEntry()
: mId(UINT64_MAX),
mListener(nullptr)
: mId(UINT64_MAX)
, mListener(nullptr)
{
}
@ -255,6 +260,7 @@ private:
nsTArray<StreamList*> mStreamLists;
bool mShuttingDown;
bool mValid;
struct CacheIdRefCounter
{

135
dom/cache/OfflineStorage.cpp vendored Normal file
View File

@ -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

50
dom/cache/OfflineStorage.h vendored Normal file
View File

@ -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

View File

@ -10,6 +10,7 @@ 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";
@ -54,6 +55,7 @@ struct PCacheRequest
RequestCredentials credentials;
PCacheReadStreamOrVoid body;
uint32_t context;
RequestCache requestCache;
};
union PCacheRequestOrVoid

View File

@ -8,6 +8,7 @@
#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"
@ -18,6 +19,7 @@ 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;
@ -60,6 +62,41 @@ GetBodyUsage(nsIFile* aDir, UsageInfo* aUsageInfo)
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:
@ -151,13 +188,14 @@ public:
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
@ -169,15 +207,26 @@ public:
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);
}
}
@ -191,9 +240,11 @@ public:
}
private:
~CacheQuotaClient() { }
~CacheQuotaClient()
{
MOZ_ASSERT(NS_IsMainThread());
}
public:
NS_INLINE_DECL_REFCOUNTING(CacheQuotaClient, override)
};

View File

@ -14,7 +14,8 @@ namespace mozilla {
namespace dom {
namespace cache {
already_AddRefed<quota::Client> CreateQuotaClient();
already_AddRefed<quota::Client>
CreateQuotaClient();
} // namespace cache
} // namespace dom

View File

@ -23,7 +23,7 @@ StreamList::StreamList(Manager* aManager, Context* aContext)
, mActivated(false)
{
MOZ_ASSERT(mManager);
MOZ_ASSERT(mContext);
mContext->AddActivity(this);
}
void
@ -142,6 +142,20 @@ StreamList::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);
@ -153,6 +167,7 @@ StreamList::~StreamList()
}
mManager->ReleaseCacheId(mCacheId);
}
mContext->RemoveActivity(this);
}
} // namespace cache

View File

@ -7,6 +7,7 @@
#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"
@ -18,10 +19,9 @@ 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);
@ -39,6 +39,10 @@ public:
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

View File

@ -225,6 +225,7 @@ TypeUtils::ToPCacheRequest(PCacheRequest& aOut, InternalRequest* aIn,
aOut.mode() = aIn->Mode();
aOut.credentials() = aIn->GetCredentialsMode();
aOut.context() = aIn->ContentPolicyType();
aOut.requestCache() = aIn->GetCacheMode();
if (aBodyAction == IgnoreBody) {
aOut.body() = void_t();
@ -367,6 +368,7 @@ TypeUtils::ToInternalRequest(const PCacheRequest& aIn)
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());

1
dom/cache/Types.h vendored
View File

@ -34,6 +34,7 @@ struct QuotaInfo
nsCOMPtr<nsIFile> mDir;
nsCString mGroup;
nsCString mOrigin;
nsCString mStorageId;
bool mIsApp;
};

2
dom/cache/moz.build vendored
View File

@ -28,6 +28,7 @@ EXPORTS.mozilla.dom.cache += [
'IPCUtils.h',
'Manager.h',
'ManagerId.h',
'OfflineStorage.h',
'PrincipalVerifier.h',
'QuotaClient.h',
'ReadStream.h',
@ -61,6 +62,7 @@ UNIFIED_SOURCES += [
'FileUtils.cpp',
'Manager.cpp',
'ManagerId.cpp',
'OfflineStorage.cpp',
'PrincipalVerifier.cpp',
'QuotaClient.cpp',
'ReadStream.cpp',

View File

@ -30,6 +30,22 @@ function runTests(testFile, order) {
});
}
// 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");
@ -100,8 +116,11 @@ function runTests(testFile, order) {
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);
});
@ -109,6 +128,7 @@ function runTests(testFile, order) {
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);
});

View File

@ -17,6 +17,7 @@ support-files =
test_caches.js
test_cache_keys.js
test_cache_put.js
test_cache_requestCache.js
[test_cache.html]
[test_cache_add.html]
@ -27,3 +28,4 @@ support-files =
[test_caches.html]
[test_cache_keys.html]
[test_cache_put.html]
[test_cache_requestCache.html]

View File

@ -57,8 +57,6 @@ caches.open(name).then(function(cache) {
}).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) {

View File

@ -79,6 +79,33 @@ function testBasics() {
});
}
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"})
@ -262,6 +289,8 @@ function step(testPromise) {
}
step(testBasics()).then(function() {
return step(testBasicKeys());
}).then(function() {
return step(testStar());
}).then(function() {
return step(testMatch());

View File

@ -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>

View File

@ -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();
});

View File

@ -34,7 +34,8 @@ private:
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;
};

View File

@ -50,3 +50,19 @@ WebGL1Context::ValidateBufferForTarget(GLenum target, WebGLBuffer* buffer,
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;
}

View File

@ -87,7 +87,8 @@ static const gl::GLFeature kRequiredFeatures[] = {
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,
@ -120,12 +121,22 @@ WebGLContext::InitWebGL2()
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.

View File

@ -348,6 +348,7 @@ private:
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;
};

View File

@ -75,6 +75,28 @@ WebGL2Context::ValidateBufferForTarget(GLenum target, WebGLBuffer* buffer,
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

View File

@ -932,7 +932,6 @@ protected:
WebGLRefPtr<WebGLBuffer>& GetBufferSlotByTarget(GLenum target);
WebGLRefPtr<WebGLBuffer>& GetBufferSlotByTargetIndexed(GLenum target,
GLuint index);
bool ValidateBufferUsageEnum(GLenum target, const char* info);
// -----------------------------------------------------------------------------
// Queries (WebGL2ContextQueries.cpp)
@ -1389,6 +1388,7 @@ private:
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:

View File

@ -890,6 +890,11 @@ WebGLContext::FramebufferTexture2D(GLenum target,
if (!ValidateFramebufferTarget(target, "framebufferTexture2D"))
return;
if (!IsWebGL2() && level != 0) {
ErrorInvalidValue("framebufferTexture2D: level must be 0.");
return;
}
WebGLFramebuffer* fb;
switch (target) {
case LOCAL_GL_FRAMEBUFFER:

View File

@ -34,7 +34,8 @@ 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);
}

View File

@ -499,11 +499,8 @@ WebGLFramebuffer::FramebufferTexture2D(FBAttachment attachPoint,
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;
@ -515,11 +512,6 @@ WebGLFramebuffer::FramebufferTexture2D(FBAttachment attachPoint,
}
}
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.
*

View File

@ -4646,7 +4646,7 @@ EventStateManager::UpdateAncestorState(nsIContent* aStartNode,
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)
@ -4674,7 +4674,7 @@ EventStateManager::UpdateAncestorState(nsIContent* aStartNode,
// 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;
}

View File

@ -141,6 +141,7 @@ 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]

View File

@ -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>

View File

@ -32,6 +32,11 @@ namespace IPC {
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,

View File

@ -208,6 +208,12 @@ public:
return mCacheMode;
}
void
SetCacheMode(RequestCache aCacheMode)
{
mCacheMode = aCacheMode;
}
nsContentPolicyType
ContentPolicyType() const
{

View File

@ -82,6 +82,7 @@ Request::Constructor(const GlobalObject& aGlobal,
RequestMode fallbackMode = RequestMode::EndGuard_;
RequestCredentials fallbackCredentials = RequestCredentials::EndGuard_;
RequestCache fallbackCache = RequestCache::EndGuard_;
if (aInput.IsUSVString()) {
nsString input;
input.Assign(aInput.GetAsUSVString());
@ -127,6 +128,7 @@ Request::Constructor(const GlobalObject& aGlobal,
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
@ -152,6 +154,12 @@ Request::Constructor(const GlobalObject& aGlobal,
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());

View File

@ -228,18 +228,5 @@ Response::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

View File

@ -56,15 +56,6 @@ public:
aUrl.AsAString() = NS_ConvertUTF8toUTF16(url);
}
bool
GetFinalURL(ErrorResult& aRv) const
{
return mInternalResponse->FinalURL();
}
void
SetFinalURL(bool aFinalURL, ErrorResult& aRv);
uint16_t
Status() const
{

View File

@ -19,7 +19,7 @@ interface nsIServiceWorkerUnregisterCallback : nsISupports
[noscript] void UnregisterFailed();
};
[builtinclass, uuid(706c3e6b-c9d2-4857-893d-4b4845fec48f)]
[builtinclass, uuid(e4c8baa5-237a-4bf6-82d4-ea06eb4b76ba)]
interface nsIServiceWorkerManager : nsISupports
{
/**
@ -59,7 +59,8 @@ interface nsIServiceWorkerManager : nsISupports
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);

View File

@ -1431,7 +1431,6 @@ void MediaDecoder::UpdateEstimatedMediaDuration(int64_t aDuration)
void MediaDecoder::SetMediaSeekable(bool aMediaSeekable) {
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
MOZ_ASSERT(NS_IsMainThread() || OnDecodeThread());
mMediaSeekable = aMediaSeekable;
}
@ -1641,9 +1640,11 @@ MediaDecoderStateMachine* MediaDecoder::GetStateMachine() const {
void
MediaDecoder::NotifyWaitingForResourcesStatusChanged()
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
if (mDecoderStateMachine) {
mDecoderStateMachine->NotifyWaitingForResourcesStatusChanged();
RefPtr<nsRunnable> task =
NS_NewRunnableMethod(mDecoderStateMachine,
&MediaDecoderStateMachine::NotifyWaitingForResourcesStatusChanged);
mDecoderStateMachine->TaskQueue()->Dispatch(task.forget());
}
}
@ -1741,7 +1742,6 @@ CDMProxy*
MediaDecoder::GetCDMProxy()
{
GetReentrantMonitor().AssertCurrentThreadIn();
MOZ_ASSERT(OnDecodeThread() || NS_IsMainThread());
return mProxy;
}
#endif

View File

@ -10,6 +10,7 @@
#include "VideoUtils.h"
#include "ImageContainer.h"
#include "nsPrintfCString.h"
#include "mozilla/mozalloc.h"
#include <stdint.h>
#include <algorithm>
@ -27,6 +28,12 @@ extern PRLogModuleInfo* gMediaDecoderLog;
#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
@ -183,6 +190,43 @@ MediaDecoderReader::ComputeStartTime(const VideoData* aVideo, const AudioData* a
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:
@ -357,3 +401,7 @@ MediaDecoderReader::Shutdown()
}
} // namespace mozilla
#undef DECODER_LOG
#undef DECODER_WARN
#undef DECODER_WARN_HELPER

View File

@ -22,7 +22,8 @@ class TimeRanges;
class MediaDecoderReader;
class SharedDecoderManager;
struct WaitForDataRejectValue {
struct WaitForDataRejectValue
{
enum Reason {
SHUTDOWN,
CANCELED
@ -34,6 +35,23 @@ struct WaitForDataRejectValue {
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
@ -49,6 +67,7 @@ public:
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;
@ -148,6 +167,11 @@ public:
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() {};

View File

@ -1499,14 +1499,20 @@ void MediaDecoderStateMachine::SetDormant(bool aDormant)
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;
@ -1560,43 +1566,21 @@ void MediaDecoderStateMachine::StartDecoding()
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();
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.");
MOZ_ASSERT(OnStateMachineThread());
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
DECODER_LOG("DoNotifyWaitingForResourcesStatusChanged");
DECODER_LOG("NotifyWaitingForResourcesStatusChanged");
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()
@ -1641,38 +1625,6 @@ void MediaDecoderStateMachine::PlayInternal()
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)
@ -1771,19 +1723,6 @@ void MediaDecoderStateMachine::StopAudioThread()
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()
{
@ -1926,14 +1865,8 @@ MediaDecoderStateMachine::InitiateSeek()
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__,
@ -2205,59 +2138,16 @@ MediaDecoderStateMachine::DecodeError()
}
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");
mMetadataRequest.Complete();
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;
mDecoder->SetMediaSeekable(mReader->IsMediaSeekable());
mInfo = aMetadata->mInfo;
mMetadataTags = aMetadata->mTags.forget();
if (HasVideo()) {
DECODER_LOG("Video decode isAsync=%d HWAccel=%d videoQueueSize=%d",
@ -2275,23 +2165,33 @@ nsresult MediaDecoderStateMachine::DecodeMetadata()
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;
}
ScheduleStateMachine();
return NS_OK;
SetState(DECODER_STATE_DECODING_FIRSTFRAME);
EnqueueDecodeFirstFrameTask();
ScheduleStateMachine();
}
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
@ -2674,8 +2574,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
StopPlayback();
}
StopAudioThread();
FlushDecoding();
Reset();
// Put a task in the decode queue to shutdown the reader.
// the queue to spin down.
@ -2699,16 +2598,25 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
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;
}
@ -2838,48 +2746,46 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
}
void
MediaDecoderStateMachine::FlushDecoding()
MediaDecoderStateMachine::Reset()
{
MOZ_ASSERT(OnStateMachineThread());
AssertCurrentThreadInMonitor();
DECODER_LOG("MediaDecoderStateMachine::Reset");
// 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 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);
// 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();
}
// 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();
void
MediaDecoderStateMachine::ResetDecode()
{
MOZ_ASSERT(OnStateMachineThread());
AssertCurrentThreadInMonitor();
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);

View File

@ -367,9 +367,8 @@ public:
// 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
@ -407,8 +406,9 @@ public:
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();
@ -465,8 +465,6 @@ protected:
nsresult FinishDecodeFirstFrame();
nsAutoPtr<MetadataTags> mMetadataTags;
// True if our buffers of decoded audio are not full, and we should
// decode more.
bool NeedToDecodeAudio();
@ -514,14 +512,6 @@ protected:
// 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.
@ -590,8 +580,6 @@ protected:
// 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.
@ -659,12 +647,9 @@ protected:
// 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.
@ -733,10 +718,6 @@ protected:
// 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();
@ -1186,10 +1167,15 @@ protected:
// 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;

View File

@ -274,8 +274,8 @@ AVCCDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aCo
{
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,
@ -305,13 +305,13 @@ AVCCDecoderModule::CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aCo
}
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);
}

View File

@ -41,8 +41,8 @@ public:
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;

View File

@ -238,7 +238,7 @@ public:
}
virtual bool
SupportsAudioMimeType(const char* aMimeType) override
SupportsAudioMimeType(const nsACString& aMimeType) override
{
return true;
}

View File

@ -311,19 +311,19 @@ MP4Reader::ExtractCryptoInitData(nsTArray<uint8_t>& aInitData)
}
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);
}

View File

@ -133,8 +133,8 @@ private:
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;

View File

@ -180,15 +180,15 @@ PlatformDecoderModule::CreatePDM()
}
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

View File

@ -122,8 +122,8 @@ public:
// 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);

View File

@ -18,6 +18,7 @@
#include "nsThreadUtils.h"
#include "nsAutoPtr.h"
#include "nsPromiseFlatString.h"
#include <jni.h>
#include <string.h>
@ -28,14 +29,10 @@ 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;
}
@ -251,7 +248,7 @@ public:
};
bool AndroidDecoderModule::SupportsAudioMimeType(const char* aMimeType) {
bool AndroidDecoderModule::SupportsAudioMimeType(const nsACString& aMimeType) {
return static_cast<bool>(CreateDecoder(aMimeType));
}
@ -300,11 +297,11 @@ AndroidDecoderModule::CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig&
}
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)

View File

@ -35,14 +35,14 @@ public:
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);
@ -59,7 +59,7 @@ protected:
MediaData::Type mType;
nsAutoPtr<char> mMimeType;
nsAutoCString mMimeType;
widget::sdk::MediaFormat::GlobalRef mFormat;
MediaDataDecoderCallback* mCallback;

View File

@ -35,14 +35,14 @@ AppleATDecoder::AppleATDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
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;

View File

@ -192,9 +192,9 @@ AppleDecoderModule::CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aC
}
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

View File

@ -32,7 +32,7 @@ public:
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;

View File

@ -170,13 +170,13 @@ FFmpegAudioDecoder<LIBAV_VER>::Drain()
}
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;
}

View File

@ -8,7 +8,6 @@
#define __FFmpegAACDecoder_h__
#include "FFmpegDataDecoder.h"
#include "mp4_demuxer/DecoderData.h"
namespace mozilla
{
@ -29,7 +28,7 @@ public:
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);

View File

@ -51,12 +51,12 @@ public:
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;
}

View File

@ -287,13 +287,13 @@ FFmpegH264Decoder<LIBAV_VER>::~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;
}

View File

@ -40,7 +40,7 @@ public:
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);

View File

@ -46,7 +46,7 @@ GMPDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aCon
FlushableMediaTaskQueue* aVideoTaskQueue,
MediaDataDecoderCallback* aCallback)
{
if (strcmp(aConfig.mime_type, "video/avc") != 0) {
if (!aConfig.mime_type.EqualsLiteral("video/avc")) {
return nullptr;
}
@ -64,7 +64,7 @@ GMPDecoderModule::CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aCon
FlushableMediaTaskQueue* aAudioTaskQueue,
MediaDataDecoderCallback* aCallback)
{
if (strcmp(aConfig.mime_type, "audio/mp4a-latm") != 0) {
if (!aConfig.mime_type.EqualsLiteral("audio/mp4a-latm")) {
return nullptr;
}

View File

@ -51,7 +51,7 @@ GonkAudioDecoderManager::GonkAudioDecoderManager(
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;
}
}

View File

@ -77,9 +77,9 @@ WMFAudioMFTManager::WMFAudioMFTManager(
{
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(),

View File

@ -118,19 +118,19 @@ WMFDecoderModule::SupportsSharedDecoders(const mp4_demuxer::VideoDecoderConfig&
}
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

View File

@ -31,8 +31,8 @@ public:
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
{

View File

@ -84,12 +84,12 @@ WMFVideoMFTManager::WMFVideoMFTManager(
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;

View File

@ -71,7 +71,9 @@ MediaSourceReader::PrepareInitialization()
MSE_DEBUG("trackBuffers=%u", mTrackBuffers.Length());
mEssentialTrackBuffers.AppendElements(mTrackBuffers);
mHasEssentialTrackBuffers = true;
mDecoder->NotifyWaitingForResourcesStatusChanged();
if (!IsWaitingMediaResources()) {
mDecoder->NotifyWaitingForResourcesStatusChanged();
}
}
bool
@ -761,7 +763,10 @@ MediaSourceReader::OnTrackBufferConfigured(TrackBuffer* aTrackBuffer, const Medi
MSE_DEBUG("%p video", aTrackBuffer);
mVideoTrack = aTrackBuffer;
}
mDecoder->NotifyWaitingForResourcesStatusChanged();
if (!IsWaitingMediaResources()) {
mDecoder->NotifyWaitingForResourcesStatusChanged();
}
}
bool

View File

@ -112,10 +112,10 @@ IntelWebMVideoDecoder::Create(WebMReader* aReader)
}
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);
}

View File

@ -54,7 +54,7 @@ private:
bool SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed);
bool IsSupportedVideoMimeType(const char* aMimeType);
bool IsSupportedVideoMimeType(const nsACString& aMimeType);
VP8Sample* PopSample();

View File

@ -9,6 +9,7 @@
#include "nsCORSListenerProxy.h"
#include "nsIChannel.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsError.h"
#include "nsContentUtils.h"
#include "nsIScriptSecurityManager.h"
@ -819,6 +820,22 @@ nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel, bool aAllowDataURI)
}
}
// 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,

View File

@ -7,6 +7,7 @@ function testDefaultCtor() {
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");
@ -16,6 +17,7 @@ function testDefaultCtor() {
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() {
@ -25,6 +27,7 @@ function testClone() {
body: "Sample body",
mode: "same-origin",
credentials: "same-origin",
cache: "no-store",
});
var clone = orig.clone();
ok(clone.method === "POST", "Request method is POST");
@ -39,6 +42,7 @@ function testClone() {
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.");

View File

@ -121,25 +121,6 @@ function testOk() {
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.");
@ -226,7 +207,6 @@ function runTest() {
testError();
testRedirect();
testOk();
testFinalURL();
return Promise.resolve()
.then(testBodyCreation)

View File

@ -52,6 +52,13 @@ partial interface Performance {
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 {

View File

@ -17,8 +17,6 @@ interface Response {
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;

View File

@ -7,6 +7,7 @@
#include "ServiceWorkerEvents.h"
#include "ServiceWorkerClient.h"
#include "nsIHttpChannelInternal.h"
#include "nsINetworkInterceptController.h"
#include "nsIOutputStream.h"
#include "nsContentUtils.h"
@ -120,7 +121,7 @@ public:
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);
}
@ -135,11 +136,14 @@ 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)
{
}
@ -213,8 +217,10 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
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;
}
@ -284,7 +290,8 @@ FetchEvent::RespondWith(Promise& aPromise, ErrorResult& aRv)
}
mWaitToRespond = true;
nsRefPtr<RespondWithHandler> handler = new RespondWithHandler(mChannel, mServiceWorker);
nsRefPtr<RespondWithHandler> handler =
new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode());
aPromise.AppendNativeHandler(handler);
}

View File

@ -60,6 +60,15 @@ 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;
@ -2131,15 +2140,23 @@ class FetchEventRunnable : public WorkerRunnable
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);
}
@ -2157,6 +2174,7 @@ public:
nsresult
Init()
{
AssertIsOnMainThread();
nsCOMPtr<nsIChannel> channel;
nsresult rv = mInterceptedChannel->GetChannel(getter_AddRefs(channel));
NS_ENSURE_SUCCESS(rv, rv);
@ -2178,8 +2196,35 @@ public:
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);
@ -2244,7 +2289,9 @@ private:
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);
@ -2280,7 +2327,8 @@ private:
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;
@ -2330,7 +2378,7 @@ ServiceWorkerManager::DispatchFetchEvent(nsIDocument* aDoc, nsIInterceptedChanne
// 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);

View File

@ -1,4 +1,4 @@
function fetch(name, onload, onerror, headers) {
function fetchXHR(name, onload, onerror, headers) {
expectAsyncResult();
onload = onload || function() {
@ -6,7 +6,7 @@ function fetch(name, onload, onerror, headers) {
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();
};
@ -21,52 +21,52 @@ function fetch(name, onload, onerror, headers) {
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();
@ -80,7 +80,7 @@ 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.");
@ -88,7 +88,7 @@ fetch('deliver-gzip.sjs', function(xhr) {
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.");
@ -96,10 +96,74 @@ fetch('hello.gz', function(xhr) {
finish();
});
fetch('hello-after-extracting.gz', function(xhr) {
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();
});
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();
});

View File

@ -24,7 +24,7 @@
document.currentScript == document.getElementById('intercepted-script');
}
function fetch(name, onload, onerror, headers) {
function fetchXHR(name, onload, onerror, headers) {
gExpected++;
onload = onload || function() {
@ -135,7 +135,7 @@
} 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() {

View File

@ -1,3 +1,5 @@
var seenIndex = false;
onfetch = function(ev) {
if (ev.request.url.contains("synthesized.txt")) {
ev.respondWith(Promise.resolve(
@ -115,4 +117,47 @@ onfetch = function(ev) {
});
}));
}
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));
}
}
}

View File

@ -27,13 +27,19 @@
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;
}
}
}
});

View File

@ -46,7 +46,6 @@
SpecialPowers.pushPrefEnv({"set": [
["dom.serviceWorkers.exemptFromPerDomainMax", true],
["dom.serviceWorkers.enabled", true],
["dom.fetch.enabled", true],
["dom.caches.enabled", true]
]}, runTest);
};

View File

@ -46,7 +46,6 @@
SpecialPowers.pushPrefEnv({"set": [
["dom.serviceWorkers.exemptFromPerDomainMax", true],
["dom.serviceWorkers.enabled", true],
["dom.fetch.enabled", true],
["dom.caches.enabled", true]
]}, runTest);
};

View File

@ -119,6 +119,7 @@ static const char *sExtensionNames[] = {
"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",

View File

@ -119,7 +119,8 @@ enum class GLFeature {
renderbuffer_color_float,
renderbuffer_color_half_float,
robustness,
sRGB,
sRGB_framebuffer,
sRGB_texture,
sampler_objects,
standard_derivatives,
texture_3D,
@ -415,6 +416,7 @@ public:
EXT_read_format_bgra,
EXT_robustness,
EXT_sRGB,
EXT_sRGB_write_control,
EXT_shader_texture_lod,
EXT_texture3D,
EXT_texture_compression_dxt1,
@ -1037,8 +1039,16 @@ public:
}
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();
}
@ -1932,6 +1942,9 @@ public:
}
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;

View File

@ -3,7 +3,6 @@
* 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"
@ -451,12 +450,24 @@ static const FeatureInfo sFeatureInfoArr[] = {
}
},
{
"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
}
},
@ -653,11 +664,10 @@ ProfileVersionForFeature(GLFeature feature, ContextProfile 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
@ -691,72 +701,49 @@ 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
@ -766,20 +753,19 @@ GLContext::MarkUnsupported(GLFeature feature)
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 */

View File

@ -313,6 +313,23 @@ GLScreenBuffer::BeforeReadCall()
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,

Some files were not shown because too many files have changed in this diff Show More