Merge m-c to inbound, a=merge

This commit is contained in:
Wes Kocher 2016-08-08 16:29:21 -07:00
commit 78ec753b6f
62 changed files with 1107 additions and 368 deletions

View File

@ -16,14 +16,22 @@
"unpack": true
},
{
"version": "rustc 1.8.0 (db2939409 2016-04-11)",
"size": 123218320,
"digest": "7afcd7b39c4d5277db6b28951602aff4c698102ba45d3d811b353ca7446074beceebf03a2a529e323af19d73db4acbe96ec2bdad44def2e218ed36f55e82cab2",
"version": "gecko rustc 1.10.0 (cfcb716cf 2016-07-03)",
"size": 102276708,
"digest": "8cc9ea8347fc7e6e6fdb15a8fd1faae977f1235a426b879b3f9128ec91d8f2b6268297ce80bf4eceb47738bd40bfeda13f143dc3fe85f1434b13adfbc095ab90",
"algorithm": "sha512",
"filename": "rustc.tar.xz",
"unpack": true
},
{
"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
"size": 3123796,
"digest": "4b9d2bcb8488b6649ba6c748e19d33bfceb25c7566e882fc7e00322392e424a5a9c5878c11c61d57cdaecf67bcc110842c6eff95e49736e8f3c83d9ce1677122",
"algorithm": "sha512",
"filename": "cargo.tar.xz",
"unpack": true
},
{
"size": 167175,
"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
"algorithm": "sha512",

View File

@ -952,6 +952,10 @@ GK_ATOM(onwebkitAnimationEnd, "onwebkitAnimationEnd")
GK_ATOM(onwebkitAnimationIteration, "onwebkitAnimationIteration")
GK_ATOM(onwebkitAnimationStart, "onwebkitAnimationStart")
GK_ATOM(onwebkitTransitionEnd, "onwebkitTransitionEnd")
GK_ATOM(onwebkitanimationend, "onwebkitanimationend")
GK_ATOM(onwebkitanimationiteration, "onwebkitanimationiteration")
GK_ATOM(onwebkitanimationstart, "onwebkitanimationstart")
GK_ATOM(onwebkittransitionend, "onwebkittransitionend")
GK_ATOM(onwebsocket, "onwebsocket")
GK_ATOM(onwheel, "onwheel")
GK_ATOM(open, "open")

View File

@ -74,7 +74,7 @@
#define TOUCH_EVENT MESSAGE_TO_EVENT
#define DOCUMENT_ONLY_EVENT MESSAGE_TO_EVENT
#define NON_IDL_EVENT MESSAGE_TO_EVENT
#endif
#endif /* MESSAGE_TO_EVENT */
#ifdef DEFINED_FORWARDED_EVENT
#error "Don't define DEFINED_FORWARDED_EVENT"
@ -956,40 +956,58 @@ NON_IDL_EVENT(MozEdgeUICompleted,
eSimpleGestureEventClass)
// CSS Transition & Animation events:
NON_IDL_EVENT(transitionend,
eTransitionEnd,
EventNameType_None,
eTransitionEventClass)
NON_IDL_EVENT(animationstart,
eAnimationStart,
EventNameType_None,
eAnimationEventClass)
NON_IDL_EVENT(animationend,
eAnimationEnd,
EventNameType_None,
eAnimationEventClass)
NON_IDL_EVENT(animationiteration,
eAnimationIteration,
EventNameType_None,
eAnimationEventClass)
EVENT(transitionend,
eTransitionEnd,
EventNameType_All,
eTransitionEventClass)
EVENT(animationstart,
eAnimationStart,
EventNameType_All,
eAnimationEventClass)
EVENT(animationend,
eAnimationEnd,
EventNameType_All,
eAnimationEventClass)
EVENT(animationiteration,
eAnimationIteration,
EventNameType_All,
eAnimationEventClass)
// Webkit-prefixed versions of Transition & Animation events, for web compat:
NON_IDL_EVENT(webkitTransitionEnd,
eWebkitTransitionEnd,
EventNameType_None,
eTransitionEventClass)
NON_IDL_EVENT(webkitAnimationEnd,
eWebkitAnimationEnd,
EventNameType_None,
eAnimationEventClass)
NON_IDL_EVENT(webkitAnimationIteration,
eWebkitAnimationIteration,
EventNameType_None,
eAnimationEventClass)
NON_IDL_EVENT(webkitAnimationStart,
eWebkitAnimationStart,
EventNameType_None,
eAnimationEventClass)
EVENT(webkitAnimationEnd,
eWebkitAnimationEnd,
EventNameType_All,
eAnimationEventClass)
EVENT(webkitAnimationIteration,
eWebkitAnimationIteration,
EventNameType_All,
eAnimationEventClass)
EVENT(webkitAnimationStart,
eWebkitAnimationStart,
EventNameType_All,
eAnimationEventClass)
EVENT(webkitTransitionEnd,
eWebkitTransitionEnd,
EventNameType_All,
eTransitionEventClass)
#ifndef MESSAGE_TO_EVENT
EVENT(webkitanimationend,
eWebkitAnimationEnd,
EventNameType_All,
eAnimationEventClass)
EVENT(webkitanimationiteration,
eWebkitAnimationIteration,
EventNameType_All,
eAnimationEventClass)
EVENT(webkitanimationstart,
eWebkitAnimationStart,
EventNameType_All,
eAnimationEventClass)
EVENT(webkittransitionend,
eWebkitTransitionEnd,
EventNameType_All,
eTransitionEventClass)
#endif
NON_IDL_EVENT(audioprocess,
eAudioProcess,

View File

@ -870,11 +870,7 @@ HTMLMediaElement::Ended()
return stream->IsFinished();
}
if (mDecoder) {
return mDecoder->IsEndedOrShutdown();
}
return false;
return mDecoder && mDecoder->IsEnded();
}
NS_IMETHODIMP HTMLMediaElement::GetEnded(bool* aEnded)
@ -2670,7 +2666,7 @@ HTMLMediaElement::PlayInternal(bool aCallerIsChrome)
// Even if we just did Load() or ResumeLoad(), we could already have a decoder
// here if we managed to clone an existing decoder.
if (mDecoder) {
if (mDecoder->IsEndedOrShutdown()) {
if (mDecoder->IsEnded()) {
SetCurrentTime(0);
}
if (!mPausedForInactiveDocumentOrChannel) {
@ -4083,7 +4079,7 @@ void HTMLMediaElement::PlaybackEnded()
// We changed state which can affect AddRemoveSelfReference
AddRemoveSelfReference();
NS_ASSERTION(!mDecoder || mDecoder->IsEndedOrShutdown(),
NS_ASSERTION(!mDecoder || mDecoder->IsEnded(),
"Decoder fired ended, but not in ended state");
// Discard all output streams that have finished now.
@ -4350,7 +4346,8 @@ HTMLMediaElement::UpdateReadyStateInternal()
return;
}
if (mDownloadSuspendedByCache && mDecoder && !mDecoder->IsEndedOrShutdown() &&
if (mDownloadSuspendedByCache &&
mDecoder && !mDecoder->IsEnded() &&
mFirstFrameLoaded) {
// The decoder has signaled that the download has been suspended by the
// media cache. So move readyState into HAVE_ENOUGH_DATA, in case there's
@ -4756,7 +4753,7 @@ bool HTMLMediaElement::IsPlaybackEnded() const
// the current playback position is equal to the effective end of the media resource.
// See bug 449157.
return mReadyState >= nsIDOMHTMLMediaElement::HAVE_METADATA &&
mDecoder ? mDecoder->IsEndedOrShutdown() : false;
mDecoder && mDecoder->IsEnded();
}
already_AddRefed<nsIPrincipal> HTMLMediaElement::GetCurrentPrincipal()
@ -4870,7 +4867,7 @@ void HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement, bool aSuspendE
#endif
if (mDecoder) {
mDecoder->Resume();
if (!mPaused && !mDecoder->IsEndedOrShutdown()) {
if (!mPaused && !mDecoder->IsEnded()) {
mDecoder->Play();
}
}
@ -4956,7 +4953,7 @@ void HTMLMediaElement::AddRemoveSelfReference()
bool needSelfReference = !mShuttingDown &&
ownerDoc->IsActive() &&
(mDelayingLoadEvent ||
(!mPaused && mDecoder && !mDecoder->IsEndedOrShutdown()) ||
(!mPaused && mDecoder && !mDecoder->IsEnded()) ||
(!mPaused && mSrcStream && !mSrcStream->IsFinished()) ||
(mDecoder && mDecoder->IsSeeking()) ||
CanActivateAutoplay() ||

View File

@ -1006,10 +1006,7 @@ ContentParent::RecvUngrabPointer(const uint32_t& aTime)
NS_RUNTIMEABORT("This message only makes sense on GTK platforms");
return false;
#else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
gdk_pointer_ungrab(aTime);
#pragma GCC diagnostic pop
return true;
#endif
}

View File

@ -162,7 +162,7 @@ MediaDecoderOwner*
MediaDecoder::ResourceCallback::GetMediaOwner() const
{
MOZ_ASSERT(NS_IsMainThread());
return mDecoder ? mDecoder->GetOwner() : nullptr;
return mDecoder ? mDecoder->mOwner : nullptr;
}
void
@ -571,7 +571,7 @@ MediaDecoder::MediaDecoder(MediaDecoderOwner* aOwner)
"MediaDecoder::mMediaSeekable (Canonical)")
, mMediaSeekableOnlyInBufferedRanges(AbstractThread::MainThread(), false,
"MediaDecoder::mMediaSeekableOnlyInBufferedRanges (Canonical)")
, mIsVisible(AbstractThread::MainThread(), !mOwner->IsHidden(),
, mIsVisible(AbstractThread::MainThread(), !aOwner->IsHidden(),
"MediaDecoder::mIsVisible (Canonical)")
, mTelemetryReported(false)
{
@ -616,10 +616,7 @@ void
MediaDecoder::Shutdown()
{
MOZ_ASSERT(NS_IsMainThread());
if (IsShutdown()) {
return;
}
MOZ_ASSERT(!IsShutdown());
// Unwatch all watch targets to prevent further notifications.
mWatchManager.Shutdown();
@ -640,7 +637,6 @@ MediaDecoder::Shutdown()
mMetadataLoadedListener.Disconnect();
mFirstFrameLoadedListener.Disconnect();
mOnPlaybackEvent.Disconnect();
mOnSeekingStart.Disconnect();
mOnMediaNotSeekable.Disconnect();
mDecoderStateMachine->BeginShutdown()
@ -667,6 +663,7 @@ MediaDecoder::Shutdown()
CancelDormantTimer();
ChangeState(PLAY_STATE_SHUTDOWN);
mOwner = nullptr;
}
MediaDecoder::~MediaDecoder()
@ -691,6 +688,9 @@ MediaDecoder::OnPlaybackEvent(MediaEventType aEvent)
case MediaEventType::PlaybackEnded:
PlaybackEnded();
break;
case MediaEventType::SeekStarted:
SeekingStarted();
break;
case MediaEventType::DecodeError:
DecodeError();
break;
@ -773,8 +773,6 @@ MediaDecoder::SetStateMachineParameters()
mOnPlaybackEvent = mDecoderStateMachine->OnPlaybackEvent().Connect(
AbstractThread::MainThread(), this, &MediaDecoder::OnPlaybackEvent);
mOnSeekingStart = mDecoderStateMachine->OnSeekingStart().Connect(
AbstractThread::MainThread(), this, &MediaDecoder::SeekingStarted);
mOnMediaNotSeekable = mDecoderStateMachine->OnMediaNotSeekable().Connect(
AbstractThread::MainThread(), this, &MediaDecoder::OnMediaNotSeekable);
}
@ -1074,13 +1072,6 @@ MediaDecoder::IsSeeking() const
return mLogicallySeeking;
}
bool
MediaDecoder::IsEndedOrShutdown() const
{
MOZ_ASSERT(NS_IsMainThread());
return IsEnded() || IsShutdown();
}
bool
MediaDecoder::OwnerHasError() const
{
@ -1223,9 +1214,7 @@ void
MediaDecoder::NotifyBytesDownloaded()
{
MOZ_ASSERT(NS_IsMainThread());
if (IsShutdown()) {
return;
}
MOZ_ASSERT(!IsShutdown());
UpdatePlaybackRate();
mOwner->DownloadProgressed();
}
@ -1324,13 +1313,11 @@ MediaDecoder::OnSeekRejected()
}
void
MediaDecoder::SeekingStarted(MediaDecoderEventVisibility aEventVisibility)
MediaDecoder::SeekingStarted()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!IsShutdown());
if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
mOwner->SeekStarted();
}
mOwner->SeekStarted();
}
void
@ -1364,6 +1351,9 @@ MediaDecoder::UpdateLogicalPositionInternal(MediaDecoderEventVisibility aEventVi
MOZ_ASSERT(!IsShutdown());
double currentPosition = static_cast<double>(CurrentPosition()) / static_cast<double>(USECS_PER_S);
if (mPlayState == PLAY_STATE_ENDED) {
currentPosition = std::max(currentPosition, mDuration);
}
bool logicalPositionChanged = mLogicalPosition != currentPosition;
mLogicalPosition = currentPosition;
@ -1659,12 +1649,7 @@ void MediaDecoder::AddSizeOfResources(ResourceSizes* aSizes) {
void
MediaDecoder::NotifyDataArrived() {
MOZ_ASSERT(NS_IsMainThread());
// Don't publish events since task queues might be shutting down.
if (IsShutdown()) {
return;
}
MOZ_ASSERT(!IsShutdown());
mDataArrivedEvent.Notify();
}
@ -1857,7 +1842,7 @@ MediaDecoder::GetOwner() const
{
MOZ_ASSERT(NS_IsMainThread());
// mOwner is valid until shutdown.
return !IsShutdown() ? mOwner : nullptr;
return mOwner;
}
void

View File

@ -241,10 +241,8 @@ public:
// Call on the main thread only.
bool IsSeeking() const;
// Return true if the decoder has reached the end of playback or the decoder
// has shutdown.
// Call on the main thread only.
bool IsEndedOrShutdown() const;
// Return true if the decoder has reached the end of playback.
bool IsEnded() const;
// Return true if the MediaDecoderOwner's error attribute is not null.
// Must be called before Shutdown().
@ -416,9 +414,8 @@ private:
mIgnoreProgressData = mLogicallySeeking;
}
// Seeking has started. Inform the element on the main
// thread.
void SeekingStarted(MediaDecoderEventVisibility aEventVisibility = MediaDecoderEventVisibility::Observable);
// Seeking has started. Inform the element on the main thread.
void SeekingStarted();
void UpdateLogicalPositionInternal(MediaDecoderEventVisibility aEventVisibility);
void UpdateLogicalPosition()
@ -531,9 +528,6 @@ protected:
// Cancel a timer for heuristic dormant.
void CancelDormantTimer();
// Return true if the decoder has reached the end of playback
bool IsEnded() const;
bool IsShutdown() const;
// Called by the state machine to notify the decoder that the duration
@ -673,9 +667,9 @@ protected:
void OnMetadataUpdate(TimedMetadata&& aMetadata);
// This should only ever be accessed from the main thread.
// It is set in Init and cleared in Shutdown when the element goes away.
// The decoder does not add a reference the element.
MediaDecoderOwner* const mOwner;
// It is set in the constructor and cleared in Shutdown when the element goes
// away. The decoder does not add a reference the element.
MediaDecoderOwner* mOwner;
// Counters related to decode and presentation of frames.
const RefPtr<FrameStatistics> mFrameStats;
@ -737,7 +731,6 @@ protected:
MediaEventListener mFirstFrameLoadedListener;
MediaEventListener mOnPlaybackEvent;
MediaEventListener mOnSeekingStart;
MediaEventListener mOnMediaNotSeekable;
protected:

View File

@ -1100,7 +1100,12 @@ void MediaDecoderStateMachine::RecomputeDuration()
return;
}
if (duration < mObservedDuration.Ref()) {
// Only adjust the duration when an explicit duration isn't set (MSE).
// The duration is always exactly known with MSE and there's no need to adjust
// it based on what may have been seen in the past; in particular as this data
// may no longer exist such as when the mediasource duration was reduced.
if (mExplicitDuration.Ref().isNothing() &&
duration < mObservedDuration.Ref()) {
duration = mObservedDuration;
}
@ -1410,8 +1415,6 @@ void MediaDecoderStateMachine::InitiateDecodeRecoverySeek(TrackSet aTracks)
mReader.get(), mCurrentSeek.mTarget,
mInfo, Duration(), GetMediaTime());
mOnSeekingStart.Notify(MediaDecoderEventVisibility::Suppressed);
// Reset our state machine and decoding pipeline before seeking.
if (mSeekTask->NeedToResetMDSM()) {
Reset(aTracks);
@ -1631,7 +1634,9 @@ MediaDecoderStateMachine::InitiateSeek(SeekJob aSeekJob)
// clamped value.
UpdatePlaybackPositionInternal(mSeekTask->GetSeekTarget().GetTime().ToMicroseconds());
mOnSeekingStart.Notify(aSeekJob.mTarget.mEventVisibility);
if (aSeekJob.mTarget.mEventVisibility == MediaDecoderEventVisibility::Observable) {
mOnPlaybackEvent.Notify(MediaEventType::SeekStarted);
}
// Reset our state machine and decoding pipeline before seeking.
if (mSeekTask->NeedToResetMDSM()) { Reset(); }

View File

@ -118,6 +118,7 @@ enum class MediaEventType : int8_t {
PlaybackStarted,
PlaybackStopped,
PlaybackEnded,
SeekStarted,
DecodeError,
Invalidate
};
@ -244,9 +245,6 @@ public:
MediaEventSource<MediaEventType>&
OnPlaybackEvent() { return mOnPlaybackEvent; }
MediaEventSource<MediaDecoderEventVisibility>&
OnSeekingStart() { return mOnSeekingStart; }
// Immutable after construction - may be called on any thread.
bool IsRealTime() const { return mRealTime; }
@ -949,7 +947,6 @@ private:
MediaDecoderEventVisibility> mFirstFrameLoadedEvent;
MediaEventProducer<MediaEventType> mOnPlaybackEvent;
MediaEventProducer<MediaDecoderEventVisibility> mOnSeekingStart;
// True if audio is offloading.
// Playback will not start when audio is offloading.

View File

@ -192,7 +192,12 @@ MediaSourceDecoder::Ended(bool aEnded)
{
MOZ_ASSERT(NS_IsMainThread());
static_cast<MediaSourceResource*>(GetResource())->SetEnded(aEnded);
mEnded = true;
if (aEnded) {
// We want the MediaSourceReader to refresh its buffered range as it may
// have been modified (end lined up).
NotifyDataArrived();
}
mEnded = aEnded;
}
void
@ -310,6 +315,8 @@ MediaSourceDecoder::CanPlayThrough()
TimeInterval
MediaSourceDecoder::ClampIntervalToEnd(const TimeInterval& aInterval)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mEnded) {
return aInterval;
}

View File

@ -91,7 +91,7 @@ private:
RefPtr<MediaSourceDemuxer> mDemuxer;
RefPtr<MediaFormatReader> mReader;
Atomic<bool> mEnded;
bool mEnded;
};
} // namespace mozilla

View File

@ -289,9 +289,6 @@ SourceBuffer::Ended()
MOZ_ASSERT(IsAttached());
MSE_DEBUG("Ended");
mTrackBuffersManager->Ended();
// We want the MediaSourceReader to refresh its buffered range as it may
// have been modified (end lined up).
mMediaSource->GetDecoder()->NotifyDataArrived();
}
SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)

View File

@ -370,8 +370,9 @@ status_t AudioOffloadPlayer::DoSeek()
mPositionTimeMediaUs = -1;
mStartPosUs = mSeekTarget.GetTime().ToMicroseconds();
if (!mSeekPromise.IsEmpty()) {
mOnSeekingStarted.Notify(mSeekTarget.mEventVisibility);
if (!mSeekPromise.IsEmpty() &&
mSeekTarget.mEventVisibility == MediaDecoderEventVisibility::Observable) {
mOnSeekingStarted.Notify();
}
if (mPlaying) {

View File

@ -194,7 +194,7 @@ private:
MediaEventProducer<void> mOnPositionChanged;
MediaEventProducer<void> mOnPlaybackEnded;
MediaEventProducer<void> mOnPlayerTearDown;
MediaEventProducer<MediaDecoderEventVisibility> mOnSeekingStarted;
MediaEventProducer<void> mOnSeekingStarted;
MediaEventListener mPositionChanged;
MediaEventListener mPlaybackEnded;
MediaEventListener mPlayerTearDown;

View File

@ -125,6 +125,19 @@ interface GlobalEventHandlers {
attribute EventHandler onmozpointerlockchange;
[Pref="pointer-lock-api.prefixed.enabled"]
attribute EventHandler onmozpointerlockerror;
// CSS-Animation and CSS-Transition handlers.
attribute EventHandler onanimationend;
attribute EventHandler onanimationiteration;
attribute EventHandler onanimationstart;
attribute EventHandler ontransitionend;
// CSS-Animation and CSS-Transition legacy handlers.
// This handler isn't standard.
attribute EventHandler onwebkitanimationend;
attribute EventHandler onwebkitanimationiteration;
attribute EventHandler onwebkitanimationstart;
attribute EventHandler onwebkittransitionend;
};
[NoInterfaceObject]

View File

@ -2047,6 +2047,11 @@ FrameLayerBuilder::GetDebugSingleOldPaintedLayerForFrame(nsIFrame* aFrame)
}
layer = data->mLayer;
}
if (!layer) {
return nullptr;
}
return layer->AsPaintedLayer();
}

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<svg width="200" height="200">
<defs>
<mask id="mask1" x="0" y="0" width="100" height="100" >
<circle cx="100" cy="100" r="50" style="stroke:none; fill: #ffffff"/>
</mask>
</defs>
<rect id="drawPath" x="50" y="50" width="100" height="100" fill="blue" stroke="blue" mask="url(#mask1)"/>
</svg>
</html>

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html class="reftest-wait">
<svg width="200" height="200">
<defs>
<mask id="mask1" x="0" y="0" width="100" height="100" >
<circle cx="100" cy="100" r="50" style="stroke:none; fill: #ffffff"/>
</mask>
</defs>
<rect id="drawPath" x="50" y="50" width="100" height="100" fill="blue" stroke="blue" mask="url(#mask1)"/>
</svg>
<script>
function doTest() {
window.history.pushState(null, "", "new-page");
drawPath.style.display = "none";
window.setTimeout(() => {
drawPath.style.display = "inline";
document.documentElement.removeAttribute('class');
}, 0);
}
drawPath = document.getElementById("drawPath");
window.addEventListener("MozReftestInvalidate", doTest);
</script>
</html>

View File

@ -1967,3 +1967,4 @@ HTTP == 652991-1a.html 652991-1-ref.html
HTTP == 652991-1b.html 652991-1-ref.html
HTTP == 652991-2.html 652991-2-ref.html
HTTP == 652991-3.html 652991-3-ref.html
HTTP == 652991-4.html 652991-4-ref.html

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html class="reftest-wait">
<title>background-position-x animation after finish</title>
<style>
@keyframes holdBackgroundPosition {
from,to { background-position-x: 100%; }
}
#test {
height: 100px;
width: 100px;
background-repeat: no-repeat;
background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAMAAADzN3VRAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF/wAAAAAAQaMSAwAAABJJREFUeNpiYBgFo2AwAIAAAwACigABtnCV2AAAAABJRU5ErkJggg==); /* a 25x25 px red box */
animation: holdBackgroundPosition 0.01s;
background-position-x: 50%;
}
</style>
<div id="test" class="reftest-opaque-layer"></div>
<script>
document.getElementById("test").addEventListener("animationend", () => {
requestAnimationFrame(() => {
document.documentElement.classList.remove("reftest-wait");
})
}, false);
</script>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<title>background-position-x animation overridden by important style</title>
<style>
@keyframes holdBackgroundPosition {
from,to { background-position-x: 50%; }
}
#test {
height: 100px;
width: 100px;
background-repeat: no-repeat;
background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAMAAADzN3VRAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF/wAAAAAAQaMSAwAAABJJREFUeNpiYBgFo2AwAIAAAwACigABtnCV2AAAAABJRU5ErkJggg==); /* a 25x25 px red box */
background-position-x: 50% !important;
animation: holdBackgroundPosition 100s infinite;
}
</style>
<div id="test" class="reftest-opaque-layer"></div>

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<title>background-position-x animation in delay phase</title>
<style>
@keyframes holdBackgroundPosition {
from,to { background-position-x: 100%; }
}
#test {
height: 100px;
width: 100px;
background-repeat: no-repeat;
background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAMAAADzN3VRAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF/wAAAAAAQaMSAwAAABJJREFUeNpiYBgFo2AwAIAAAwACigABtnCV2AAAAABJRU5ErkJggg==); /* a 25x25 px red box */
animation: holdBackgroundPosition 100s 100s infinite;
background-position-x: 50%;
}
</style>
<div id="test" class="reftest-opaque-layer"></div>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<title>Reference of testcases for background-position-x animations</title>
<style>
#test {
height: 100px;
width: 100px;
background-repeat: no-repeat;
background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAMAAADzN3VRAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF/wAAAAAAQaMSAwAAABJJREFUeNpiYBgFo2AwAIAAAwACigABtnCV2AAAAABJRU5ErkJggg==); /* a 25x25 px red box */
background-position-x: 50%;
}
</style>
<div id="test"></div>

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<title>background-position-x animation while running</title>
<style>
@keyframes holdBackgroundPosition {
from,to { background-position-x: 50%; }
}
#test {
height: 100px;
width: 100px;
background-repeat: no-repeat;
background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAMAAADzN3VRAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF/wAAAAAAQaMSAwAAABJJREFUeNpiYBgFo2AwAIAAAwACigABtnCV2AAAAABJRU5ErkJggg==); /* a 25x25 px red box */
background-position-x: 0px;
animation: holdBackgroundPosition 100s infinite;
}
</style>
<div id="test" class="reftest-opaque-layer"></div>

View File

@ -27,3 +27,7 @@ test-pref(layers.offmainthreadcomposition.async-animations,false) == stacking-co
== stacking-context-transform-none-with-fill-forwards.html stacking-context-animation-ref.html
fails == stacking-context-opacity-1-in-delay.html stacking-context-animation-ref.html # bug 1278136 and bug 1279403
fails == stacking-context-transform-none-in-delay.html stacking-context-animation-ref.html # bug 1278136 and bug 1279403
== background-position-in-delay.html background-position-ref.html
== background-position-after-finish.html background-position-ref.html
fails == background-position-running.html background-position-ref.html # This test fails the reftest-opaque-layer check since animating background-position currently creates an active layer, and reftest-opaque-layer only handles items assigned to PaintedLayers.
fails == background-position-important.html background-position-ref.html # This test fails the reftest-opaque-layer check since animating background-position overridden by a non-animated !important style also creates an active layer, and reftest-opaque-layer only handles items that are assigned to PaintedLayers.

View File

@ -442,3 +442,4 @@ skip-if(Android) pref(layout.css.mix-blend-mode.enabled,true) == blend-differenc
== use-localRef-filter-01.svg use-localRef-filter-01-ref.svg
== use-localRef-fill-01.svg use-localRef-fill-01-ref.svg
== use-localRef-stroke-01.svg use-localRef-stroke-01-ref.svg
== use-localRef-mask-01.svg use-localRef-mask-01-ref.svg

View File

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Reference for mask linked to local-ref URL</title>
<defs>
<mask id="circleMask1" x="0" y="0" width="100" height="100" >
<circle cx="50" cy="50" r="40" style="stroke:none; fill: #ffffff"/>
</mask>
<mask id="circleMask2" x="0" y="0" width="100" height="100" >
<circle cx="50" cy="150" r="40" style="stroke:none; fill: #ffffff"/>
</mask>
<mask id="circleMask3" x="0" y="0" width="100" height="100" >
<circle cx="50" cy="250" r="40" style="stroke:none; fill: #ffffff"/>
</mask>
</defs>
<rect id="mask1" x="10" y="10" width="80" height="80" fill="blue" mask="url(#circleMask1)"/>
<rect id="mask2" x="10" y="110" width="80" fill="blue" height="80" mask="url(#circleMask2)"/>
<rect id="mask3" x="10" y="210" width="80" fill="blue" height="80" mask="url(#circleMask3)"/>
</svg>

After

Width:  |  Height:  |  Size: 914 B

View File

@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Testcase for mask linked to local-ref URL</title>
<defs>
<mask id="circleMask1" x="0" y="0" width="100" height="100" >
<circle cx="50" cy="50" r="0" style="stroke:none; fill: #ffffff"/>
</mask>
<mask id="circleMask2" x="0" y="0" width="100" height="100" >
<circle cx="50" cy="150" r="40" style="stroke:none; fill: #ffffff"/>
</mask>
<mask id="circleMask3" x="0" y="0" width="100" height="100" >
<circle cx="50" cy="250" r="40" style="stroke:none; fill: #ffffff"/>
</mask>
</defs>
<style>
#mask3 {
mask: url(#circleMask3);
}
</style>
<use xlink:href="use-localRef-mask-resource.svg#mask1"/>
<use xlink:href="use-localRef-mask-resource.svg#mask2" mask="url(#circleMask2)"/>
<use xlink:href="use-localRef-mask-resource.svg#mask3"/>
</svg>

After

Width:  |  Height:  |  Size: 899 B

View File

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<mask id="circleMask1" x="0" y="0" width="100" height="100" >
<circle cx="50" cy="50" r="40" style="stroke:none; fill: #ffffff"/>
</mask>
<mask id="circleMask2" x="0" y="0" width="100" height="100" >
<circle cx="50" cy="150" r="0" style="stroke:none; fill: #ffffff"/>
</mask>
<mask id="circleMask3" x="0" y="0" width="100" height="100" >
<circle cx="50" cy="250" r="0" style="stroke:none; fill: #ffffff"/>
</mask>
</defs>
<rect id="mask1" x="10" y="10" width="80" height="80" fill="blue" mask="url(#circleMask1)"/>
<rect id="mask2" x="10" y="110" width="80" fill="blue" height="80"/>
<rect id="mask3" x="10" y="210" width="80" fill="blue" height="80"/>
</svg>

After

Width:  |  Height:  |  Size: 802 B

View File

@ -2145,12 +2145,10 @@ nsComputedDOMStyle::DoGetImageLayerImage(const nsStyleImageLayers& aLayers)
// Instead, we store the local URI in one place -- on Layer::mSourceURI.
// Hence, we must serialize using mSourceURI (instead of
// SetValueToStyleImage()/mImage) in this case.
bool isLocalURI = image.GetType() == eStyleImageType_Null &&
aLayers.mLayers[i].mSourceURI;
if (isLocalURI) {
if (aLayers.mLayers[i].mSourceURI.IsLocalRef()) {
// This is how we represent a 'mask-image' reference for a local URI,
// such as 'mask-image:url(#mymask)' or 'mask:url(#mymask)'
val->SetURI(aLayers.mLayers[i].mSourceURI);
val->SetURI(aLayers.mLayers[i].mSourceURI.GetSourceURL());
} else {
SetValueToStyleImage(image, val);
}
@ -6147,8 +6145,8 @@ nsComputedDOMStyle::DoGetMask()
RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
if (firstLayer.mSourceURI) {
val->SetURI(firstLayer.mSourceURI);
if (firstLayer.mSourceURI.GetSourceURL()) {
val->SetURI(firstLayer.mSourceURI.GetSourceURL());
} else {
val->SetIdent(eCSSKeyword_none);
}

View File

@ -6657,18 +6657,18 @@ struct BackgroundItemComputer<nsCSSValueList, nsStyleImage>
};
template <>
struct BackgroundItemComputer<nsCSSValueList, nsCOMPtr<nsIURI> >
struct BackgroundItemComputer<nsCSSValueList, FragmentOrURL>
{
static void ComputeValue(nsStyleContext* aStyleContext,
const nsCSSValueList* aSpecifiedValue,
nsCOMPtr<nsIURI>& aComputedValue,
FragmentOrURL& aComputedValue,
RuleNodeCacheConditions& aConditions)
{
if (eCSSUnit_Image == aSpecifiedValue->mValue.GetUnit() ||
eCSSUnit_URL == aSpecifiedValue->mValue.GetUnit()) {
aComputedValue = aSpecifiedValue->mValue.GetURLValue();
aComputedValue.SetValue(&aSpecifiedValue->mValue);
} else if (eCSSUnit_Null != aSpecifiedValue->mValue.GetUnit()) {
aComputedValue = nullptr;
aComputedValue.SetNull();
}
}
};
@ -10082,11 +10082,11 @@ nsRuleNode::ComputeSVGResetData(void* aStartStruct,
// mask: none | <url>
const nsCSSValue* maskValue = aRuleData->ValueForMask();
if (eCSSUnit_URL == maskValue->GetUnit()) {
svgReset->mMask.mLayers[0].mSourceURI = maskValue->GetURLValue();
svgReset->mMask.mLayers[0].mSourceURI.SetValue(maskValue);
} else if (eCSSUnit_None == maskValue->GetUnit() ||
eCSSUnit_Initial == maskValue->GetUnit() ||
eCSSUnit_Unset == maskValue->GetUnit()) {
svgReset->mMask.mLayers[0].mSourceURI = nullptr;
svgReset->mMask.mLayers[0].mSourceURI.SetNull();
} else if (eCSSUnit_Inherit == maskValue->GetUnit()) {
conditions.SetUncacheable();
svgReset->mMask.mLayers[0].mSourceURI =

View File

@ -118,6 +118,102 @@ StyleStructContext::HackilyFindSomeDeviceContext()
static bool AreShadowArraysEqual(nsCSSShadowArray* lhs, nsCSSShadowArray* rhs);
// --------------------
// FragmentOrURL
//
void
FragmentOrURL::SetValue(const nsCSSValue* aValue)
{
mozilla::css::URLValue *urlVal = aValue->GetURLStructValue();
MOZ_ASSERT_IF(urlVal->GetLocalURLFlag(), urlVal->GetURI());
mIsLocalRef = urlVal->GetLocalURLFlag();
mURL = urlVal->GetURI();
#ifdef DEBUG
if (mIsLocalRef) {
bool hasRef = false;
mURL->GetHasRef(&hasRef);
MOZ_ASSERT(hasRef);
}
#endif
}
void
FragmentOrURL::SetNull()
{
mURL = nullptr;
mIsLocalRef = false;
}
FragmentOrURL&
FragmentOrURL::operator=(const FragmentOrURL& aOther)
{
mIsLocalRef = aOther.mIsLocalRef;
mURL = aOther.mURL;
return *this;
}
bool
FragmentOrURL::operator==(const FragmentOrURL& aOther) const
{
if (aOther.mIsLocalRef != mIsLocalRef) {
return false;
}
return EqualURIs(aOther.mURL, mURL);
}
bool
FragmentOrURL::EqualsExceptRef(nsIURI* aURI) const
{
bool ret = false;
mURL->EqualsExceptRef(aURI, &ret);
return ret;
}
void
FragmentOrURL::GetSourceString(nsString &aRef) const
{
MOZ_ASSERT(mURL);
nsCString cref;
if (mIsLocalRef) {
mURL->GetRef(cref);
cref.Insert('#', 0);
} else {
mURL->GetSpec(cref);
}
aRef = NS_ConvertUTF8toUTF16(cref);
}
already_AddRefed<nsIURI>
FragmentOrURL::Resolve(nsIURI* aURI) const
{
nsCOMPtr<nsIURI> result;
if (mIsLocalRef) {
nsCString ref;
mURL->GetRef(ref);
aURI->Clone(getter_AddRefs(result));
result->SetRef(ref);
} else {
result = mURL;
}
return result.forget();
}
already_AddRefed<nsIURI>
FragmentOrURL::Resolve(nsIContent* aContent) const
{
nsCOMPtr<nsIURI> url = aContent->GetBaseURI();
return Resolve(url);
}
// --------------------
// nsStyleFont
//
@ -1009,103 +1105,6 @@ nsStyleBasicShape::GetShapeTypeName() const
return eCSSKeyword_UNKNOWN;
}
// --------------------
// FragmentOrURL
//
void
FragmentOrURL::SetValue(const nsCSSValue* aValue)
{
mozilla::css::URLValue *urlVal = aValue->GetURLStructValue();
MOZ_ASSERT_IF(urlVal->GetLocalURLFlag(), urlVal->GetURI());
mIsLocalRef = urlVal->GetLocalURLFlag();
mURL = urlVal->GetURI();
#ifdef DEBUG
if (mIsLocalRef) {
bool hasRef = false;
mURL->GetHasRef(&hasRef);
MOZ_ASSERT(hasRef);
}
#endif
}
void
FragmentOrURL::SetNull()
{
mURL = nullptr;
mIsLocalRef = false;
}
FragmentOrURL&
FragmentOrURL::operator=(const FragmentOrURL& aOther)
{
mIsLocalRef = aOther.mIsLocalRef;
mURL = aOther.mURL;
return *this;
}
bool
FragmentOrURL::operator==(const FragmentOrURL& aOther) const
{
if (aOther.mIsLocalRef != mIsLocalRef) {
return false;
}
return EqualURIs(aOther.mURL, mURL);
}
bool
FragmentOrURL::EqualsExceptRef(nsIURI* aURI) const
{
bool ret = false;
mURL->EqualsExceptRef(aURI, &ret);
return ret;
}
void
FragmentOrURL::GetSourceString(nsString &aRef) const
{
MOZ_ASSERT(mURL);
nsCString cref;
if (mIsLocalRef) {
mURL->GetRef(cref);
cref.Insert('#', 0);
} else {
mURL->GetSpec(cref);
}
aRef = NS_ConvertUTF8toUTF16(cref);
}
already_AddRefed<nsIURI>
FragmentOrURL::Resolve(nsIURI* aURI) const
{
nsCOMPtr<nsIURI> result;
if (mIsLocalRef) {
nsCString ref;
mURL->GetRef(ref);
aURI->Clone(getter_AddRefs(result));
result->SetRef(ref);
} else {
result = mURL;
}
return result.forget();
}
already_AddRefed<nsIURI>
FragmentOrURL::Resolve(nsIContent* aContent) const
{
nsCOMPtr<nsIURI> url = aContent->GetBaseURI();
return Resolve(url);
}
// --------------------
// nsStyleClipPath
//
@ -2608,7 +2607,7 @@ nsStyleImageLayers::HasLayerWithImage() const
// mLayers[i].mImage can be empty if mask-image prop value is a reference
// to SVG mask element.
// So we need to test both mSourceURI and mImage.
if (mLayers[i].mSourceURI || !mLayers[i].mImage.IsEmpty()) {
if (mLayers[i].mSourceURI.GetSourceURL() || !mLayers[i].mImage.IsEmpty()) {
return true;
}
}
@ -2835,7 +2834,7 @@ nsStyleImageLayers::Layer::operator==(const Layer& aOther) const
mImage == aOther.mImage &&
mMaskMode == aOther.mMaskMode &&
mComposite == aOther.mComposite &&
EqualURIs(mSourceURI, aOther.mSourceURI);
mSourceURI == aOther.mSourceURI;
}
nsChangeHint
@ -2843,7 +2842,7 @@ nsStyleImageLayers::Layer::CalcDifference(const nsStyleImageLayers::Layer& aNewL
nsChangeHint aPositionChangeHint) const
{
nsChangeHint hint = nsChangeHint(0);
if (!EqualURIs(mSourceURI, aNewLayer.mSourceURI)) {
if (mSourceURI != aNewLayer.mSourceURI) {
hint |= nsChangeHint_RepaintFrame;
// If Layer::mSourceURI links to a SVG mask, it has a fragment. Not vice
@ -2858,11 +2857,18 @@ nsStyleImageLayers::Layer::CalcDifference(const nsStyleImageLayers::Layer& aNewL
// That is, if mSourceURI has a fragment, it may link to a SVG mask; If
// not, it "must" not link to a SVG mask.
bool maybeSVGMask = false;
if (mSourceURI) {
mSourceURI->GetHasRef(&maybeSVGMask);
if (mSourceURI.IsLocalRef()) {
maybeSVGMask = true;
} else if (mSourceURI.GetSourceURL()) {
mSourceURI.GetSourceURL()->GetHasRef(&maybeSVGMask);
}
if (!maybeSVGMask && aNewLayer.mSourceURI) {
aNewLayer.mSourceURI->GetHasRef(&maybeSVGMask);
if (!maybeSVGMask) {
if (aNewLayer.mSourceURI.IsLocalRef()) {
maybeSVGMask = true;
} else if (aNewLayer.mSourceURI.GetSourceURL()) {
aNewLayer.mSourceURI.GetSourceURL()->GetHasRef(&maybeSVGMask);
}
}
// Return nsChangeHint_UpdateEffects and nsChangeHint_UpdateOverflow if

View File

@ -110,6 +110,39 @@ static_assert(NS_STYLE_INHERIT_MASK == (1 << nsStyleStructID_Length) - 1,
static_assert((NS_RULE_NODE_IS_ANIMATION_RULE & NS_STYLE_INHERIT_MASK) == 0,
"NS_RULE_NODE_IS_ANIMATION_RULE must not overlap the style struct bits.");
struct FragmentOrURL
{
FragmentOrURL() : mIsLocalRef(false) {}
FragmentOrURL(const FragmentOrURL& aSource)
: mIsLocalRef(false)
{ *this = aSource; }
void SetValue(const nsCSSValue* aValue);
void SetNull();
FragmentOrURL& operator=(const FragmentOrURL& aOther);
bool operator==(const FragmentOrURL& aOther) const;
bool operator!=(const FragmentOrURL& aOther) const {
return !(*this == aOther);
}
bool EqualsExceptRef(nsIURI* aURI) const;
nsIURI* GetSourceURL() const { return mURL; }
void GetSourceString(nsString& aRef) const;
// When matching a url with mIsLocalRef set, resolve it against aURI;
// Otherwise, ignore aURL and return mURL directly.
already_AddRefed<nsIURI> Resolve(nsIURI* aURI) const;
already_AddRefed<nsIURI> Resolve(nsIContent* aContent) const;
bool IsLocalRef() const { return mIsLocalRef; }
private:
nsCOMPtr<nsIURI> mURL;
bool mIsLocalRef;
};
// The lifetime of these objects is managed by the presshell's arena.
struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleFont
{
@ -652,7 +685,7 @@ struct nsStyleImageLayers {
friend struct Layer;
struct Layer {
nsStyleImage mImage; // [reset]
nsCOMPtr<nsIURI> mSourceURI; // [reset]
FragmentOrURL mSourceURI; // [reset]
// mask-only property
// This property is used for mask layer only.
// For a background layer, it should always
@ -3233,39 +3266,6 @@ protected:
nscoord mTwipsPerPixel;
};
struct FragmentOrURL
{
FragmentOrURL() : mIsLocalRef(false) {}
FragmentOrURL(const FragmentOrURL& aSource)
: mIsLocalRef(false)
{ *this = aSource; }
void SetValue(const nsCSSValue* aValue);
void SetNull();
FragmentOrURL& operator=(const FragmentOrURL& aOther);
bool operator==(const FragmentOrURL& aOther) const;
bool operator!=(const FragmentOrURL& aOther) const {
return !(*this == aOther);
}
bool EqualsExceptRef(nsIURI* aURI) const;
nsIURI* GetSourceURL() const { return mURL; }
void GetSourceString(nsString& aRef) const;
// When matching a url with mIsLocalRef set, resolve it against aURI;
// Otherwise, ignore aURL and return mURL directly.
already_AddRefed<nsIURI> Resolve(nsIURI* aURI) const;
already_AddRefed<nsIURI> Resolve(nsIContent* aContent) const;
bool IsLocalRef() const { return mIsLocalRef; }
private:
nsCOMPtr<nsIURI> mURL;
bool mIsLocalRef;
};
enum nsStyleSVGPaintType {
eStyleSVGPaintType_None = 1,
eStyleSVGPaintType_Color,

View File

@ -52,6 +52,7 @@ support-files = file_animations_effect_timing_enddelay.html
[test_animations_effect_timing_iterations.html]
support-files = file_animations_effect_timing_iterations.html
[test_animations_event_order.html]
[test_animations_event_handler_attribute.html]
[test_animations_iterationstart.html]
support-files = file_animations_iterationstart.html
[test_animations_omta.html]

View File

@ -0,0 +1,148 @@
<!doctype html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=911987
-->
<head>
<meta charset=utf-8>
<title>Test for CSS Animation and Transition event handler
attributes. (Bug 911987)</title>
<script type="application/javascript"
src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="animation_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style>
@keyframes anim { to { margin-left: 100px } }
</style>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=911987">Mozilla Bug
911987</a>
<div id="display"></div>
<pre id="test">
<script type="application/javascript">
'use strict';
// Create the div element with event handlers.
// We need two elements: one with the content attribute speficied and one
// with the IDL attribute specified since we can't set these independently.
function createAndRegisterTargets(eventAttributes) {
var displayElement = document.getElementById('display');
var contentAttributeElement = document.createElement("div");
var idlAttributeElement = document.createElement("div");
displayElement.appendChild(contentAttributeElement);
displayElement.appendChild(idlAttributeElement);
// Add handlers
eventAttributes.forEach(event => {
contentAttributeElement.setAttribute(event, 'handleEvent(event);');
contentAttributeElement.handlerType = 'content attribute';
idlAttributeElement[event] = handleEvent;
idlAttributeElement.handlerType = 'IDL attribute';
});
return [contentAttributeElement, idlAttributeElement];
}
function handleEvent(event) {
if (event.target.receivedEventType) {
ok(false, `Received ${event.type} event, but this element have previous `
`received event '${event.target.receivedEventType}'.`);
return;
}
event.target.receivedEventType = event.type;
}
function checkReceivedEvents(eventType, elements) {
elements.forEach(element => {
is(element.receivedEventType, eventType,
`Expected to receive '${eventType}', got
'${element.receivedEventType}', for event handler registered
using ${element.handlerType}`);
element.receivedEventType = undefined;
});
}
// Take over the refresh driver right from the start.
advance_clock(0);
// 1. Test CSS Animation event handlers.
var targets = createAndRegisterTargets([ 'onanimationstart',
'onanimationiteration',
'onanimationend' ]);
targets.forEach(div => {
div.setAttribute('style', 'animation: anim 100ms 2');
getComputedStyle(div).animationName; // flush
});
advance_clock(0);
checkReceivedEvents("animationstart", targets);
advance_clock(100);
checkReceivedEvents("animationiteration", targets);
advance_clock(200);
checkReceivedEvents("animationend", targets);
targets.forEach(div => { div.remove(); });
// 2. Test CSS Transition event handlers.
advance_clock(0);
var targets = createAndRegisterTargets([ 'ontransitionend' ]);
targets.forEach(div => {
div.style.transition = 'margin-left 100ms';
getComputedStyle(div).marginLeft; // flush
div.style.marginLeft = "200px";
getComputedStyle(div).marginLeft; // flush
});
advance_clock(100);
checkReceivedEvents("transitionend", targets);
targets.forEach(div => { div.remove(); });
// 3. Test prefixed CSS Animation event handlers.
var targets = createAndRegisterTargets([ 'onwebkitanimationstart',
'onwebkitanimationiteration',
'onwebkitanimationend' ]);
targets.forEach(div => {
div.setAttribute('style', 'animation: anim 100ms 2');
getComputedStyle(div).animationName; // flush
});
advance_clock(0);
checkReceivedEvents("webkitAnimationStart", targets);
advance_clock(100);
checkReceivedEvents("webkitAnimationIteration", targets);
advance_clock(200);
checkReceivedEvents("webkitAnimationEnd", targets);
targets.forEach(div => { div.remove(); });
// 4. Test prefixed CSS Transition event handlers.
advance_clock(0);
var targets = createAndRegisterTargets([ 'onwebkittransitionend' ]);
targets.forEach(div => {
div.style.transition = 'margin-left 100ms';
getComputedStyle(div).marginLeft; // flush
div.style.marginLeft = "200px";
getComputedStyle(div).marginLeft; // flush
});
advance_clock(100);
checkReceivedEvents("webkitTransitionEnd", targets);
targets.forEach(div => { div.remove(); });
SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
</script>
</body>
</html>

View File

@ -395,9 +395,9 @@ nsSVGMaskProperty::nsSVGMaskProperty(nsIFrame* aFrame)
const nsStyleSVGReset *svgReset = aFrame->StyleSVGReset();
for (uint32_t i = 0; i < svgReset->mMask.mImageCount; i++) {
nsSVGPaintingProperty* prop =
new nsSVGPaintingProperty(svgReset->mMask.mLayers[i].mSourceURI, aFrame,
false);
nsCOMPtr<nsIURI> maskUri = nsSVGEffects::GetMaskURI(aFrame, i);
nsSVGPaintingProperty* prop = new nsSVGPaintingProperty(maskUri, aFrame,
false);
mProperties.AppendElement(prop);
}
}
@ -964,3 +964,13 @@ nsSVGEffects::GetPaintURI(nsIFrame* aFrame,
return ResolveFragmentOrURL(aFrame, (svgStyle->*aPaint).mPaint.mPaintServer);
}
already_AddRefed<nsIURI>
nsSVGEffects::GetMaskURI(nsIFrame* aFrame, uint32_t aIndex)
{
const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset();
MOZ_ASSERT(svgReset->mMask.mLayers.Length() > aIndex);
return ResolveFragmentOrURL(aFrame,
&svgReset->mMask.mLayers[aIndex].mSourceURI);
}

View File

@ -624,6 +624,12 @@ public:
*/
static already_AddRefed<nsIURI>
GetPaintURI(nsIFrame* aFrame, nsStyleSVGPaint nsStyleSVG::* aPaint);
/**
* A helper function to resolve SVG mask URL.
*/
static already_AddRefed<nsIURI>
GetMaskURI(nsIFrame* aFrame, uint32_t aIndex);
};
#endif /*NSSVGEFFECTS_H_*/

View File

@ -5133,7 +5133,7 @@ pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.goo
// Prefs for v4.
pref("browser.safebrowsing.provider.google4.pver", "4");
pref("browser.safebrowsing.provider.google4.lists", "goog-phish-proto,googpub-phish-proto,goog-malware-proto,goog-unwanted-proto");
pref("browser.safebrowsing.provider.google4.updateURL", "https://safebrowsing.googleapis.com/v4/threatListUpdates:fetch?$req=%REQUEST_BASE64%&$ct=application/x-protobuf&key=%GOOGLE_API_KEY%");
pref("browser.safebrowsing.provider.google4.updateURL", "https://safebrowsing.googleapis.com/v4/threatListUpdates:fetch?$ct=application/x-protobuf&key=%GOOGLE_API_KEY%");
pref("browser.safebrowsing.provider.google4.gethashURL", "https://safebrowsing.googleapis.com/v4/fullHashes:find?$req=%REQUEST_BASE64%&$ct=application/x-protobuf&key=%GOOGLE_API_KEY%");
pref("browser.safebrowsing.provider.google4.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");

View File

@ -115,7 +115,7 @@ add_test(function test_safebrowsing_update() {
}
streamUpdater.downloadUpdates("test-phish-simple,test-malware-simple", "",
URL + safebrowsingUpdatePath, onSuccess, onUpdateError, onDownloadError);
true, URL + safebrowsingUpdatePath, onSuccess, onUpdateError, onDownloadError);
});
add_test(function test_non_safebrowsing_cookie() {

View File

@ -63,7 +63,7 @@ CAIRO_VERSION=1.10
PANGO_VERSION=1.22.0
GTK2_VERSION=2.18.0
GTK3_VERSION=3.4.0
GDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_4
GDK_VERSION_MAX_ALLOWED=GDK_VERSION_3_4
WINDRES_VERSION=2.14.90
W32API_VERSION=3.14
GNOMEUI_VERSION=2.2.0
@ -2725,8 +2725,9 @@ if test "$COMPILE_ENVIRONMENT"; then
MOZ_GTK3_CFLAGS="-I${_topsrcdir}/widget/gtk/compat-gtk3 $MOZ_GTK3_CFLAGS"
TK_CFLAGS=$MOZ_GTK3_CFLAGS
TK_LIBS=$MOZ_GTK3_LIBS
AC_DEFINE_UNQUOTED(GDK_VERSION_MIN_REQUIRED,$GDK_VERSION_MIN_REQUIRED)
AC_DEFINE_UNQUOTED(GDK_VERSION_MAX_ALLOWED,$GDK_VERSION_MIN_REQUIRED)
dnl GDK_VERSION_MIN_REQUIRED is not set here as GDK3 deprecated warnings
dnl are suppressed by widget/gtk/compat-gtk3/gdk/gdkversionmacros.h.
AC_DEFINE_UNQUOTED(GDK_VERSION_MAX_ALLOWED,$GDK_VERSION_MAX_ALLOWED)
GLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_32
fi
if test "$MOZ_WIDGET_TOOLKIT" = gtk2; then

View File

@ -1,4 +0,0 @@
[mediasource-play-then-seek-back.html]
type: testharness
prefs: [media.mediasource.enabled:true]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1128069

View File

@ -1,10 +0,0 @@
[mediasource-seek-beyond-duration.html]
type: testharness
prefs: [media.mediasource.enabled:true]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1093945
[Test seeking beyond updated media duration.]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1093945
[Test seeking beyond media duration.]
disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1066467

View File

@ -30,8 +30,6 @@
function finishSeekThenPlay()
{
assert_true(mediaElement.seeking, 'mediaElement is seeking');
assert_equals(mediaElement.currentTime, 0.0, 'Current time is 0.0');
test.expectEvent(mediaElement, 'seeked', 'mediaElement finished seek');
test.waitForExpectedEvents(confirmPlayThenEnd);
@ -42,6 +40,7 @@
assert_greater_than(mediaElement.currentTime, 0.0, 'Playback has started.');
test.expectEvent(mediaElement, 'seeking', 'mediaElement');
mediaElement.currentTime = 0.0;
assert_true(mediaElement.seeking, 'mediaElement is seeking');
test.waitForExpectedEvents(finishSeekThenPlay);
}

View File

@ -62,6 +62,15 @@
{
assert_equals(mediaElement.duration, segmentInfo.duration);
assert_greater_than_equal(mediaElement.duration, 2.0, 'Duration is >2.0s.');
test.expectEvent(sourceBuffer, "updateend");
sourceBuffer.remove(1.5, Infinity);
assert_true(sourceBuffer.updating, "updating");
});
test.waitForExpectedEvents(function()
{
assert_false(sourceBuffer.updating, "updating");
test.waitForCurrentTimeChange(mediaElement, function()
{
// Update duration.

View File

@ -85,8 +85,12 @@
assert_equals(eventInfo.description, expected.description, "Descriptions match for '" + event.type + "'.");
expectations.shift(1);
if (t.waitCallbacks_.length > 0)
if (t.waitCallbacks_.length > 1)
setTimeout(waitHandler, 0);
else if (t.waitCallbacks_.length == 1) {
// Immediately call the callback.
waitHandler();
}
});
object.addEventListener(eventName, eventHandler);
};

View File

@ -220,6 +220,7 @@ add_test(function test_local_list() {
streamUpdater.downloadUpdates(
"goog-downloadwhite-digest256,goog-badbinurl-shavar",
"goog-downloadwhite-digest256,goog-badbinurl-shavar;\n",
true, // isPostRequest.
"http://localhost:4444/downloads",
updateSuccess, handleError, handleError);
});

View File

@ -197,6 +197,7 @@ function waitForUpdates() {
streamUpdater.downloadUpdates(
"goog-downloadwhite-digest256",
"goog-downloadwhite-digest256;\n",
true,
"http://localhost:4444/downloads",
updateSuccess, handleError, handleError);
return deferred.promise;

View File

@ -297,6 +297,7 @@ function waitForUpdates() {
streamUpdater.downloadUpdates(
"goog-downloadwhite-digest256",
"goog-downloadwhite-digest256;\n",
true,
"http://localhost:4444/downloads",
updateSuccess, handleError, handleError);
return deferred.promise;

View File

@ -11,6 +11,7 @@
#include "prprf.h"
#include "nsUrlClassifierUtils.h"
#include "nsPrintfCString.h"
// MOZ_LOG=UrlClassifierProtocolParser:5
mozilla::LazyLogModule gUrlClassifierProtocolParserLog("UrlClassifierProtocolParser");
@ -61,8 +62,8 @@ ParseChunkRange(nsACString::const_iterator& aBegin,
}
ProtocolParser::ProtocolParser()
: mState(PROTOCOL_STATE_CONTROL)
, mUpdateStatus(NS_OK)
: mUpdateStatus(NS_OK)
, mState(PROTOCOL_STATE_CONTROL)
, mUpdateWait(0)
, mResetRequested(false)
, mTableUpdate(nullptr)
@ -114,6 +115,12 @@ ProtocolParser::AppendStream(const nsACString& aData)
return NS_OK;
}
void
ProtocolParser::End()
{
// Inbound data has already been processed in every AppendStream() call.
}
nsresult
ProtocolParser::ProcessControl(bool* aDone)
{
@ -694,5 +701,178 @@ ProtocolParser::GetTableUpdate(const nsACString& aTable)
return update;
}
///////////////////////////////////////////////////////////////////////
// ProtocolParserProtobuf
ProtocolParserProtobuf::ProtocolParserProtobuf()
{
}
ProtocolParserProtobuf::~ProtocolParserProtobuf()
{
}
nsresult
ProtocolParserProtobuf::AppendStream(const nsACString& aData)
{
// Protobuf data cannot be parsed progressively. Just save the incoming data.
mPending.Append(aData);
return NS_OK;
}
void
ProtocolParserProtobuf::End()
{
// mUpdateStatus will be updated to success as long as not all
// the responses are invalid.
mUpdateStatus = NS_ERROR_FAILURE;
FetchThreatListUpdatesResponse response;
if (!response.ParseFromArray(mPending.get(), mPending.Length())) {
NS_WARNING("ProtocolParserProtobuf failed parsing data.");
return;
}
for (int i = 0; i < response.list_update_responses_size(); i++) {
auto r = response.list_update_responses(i);
nsresult rv = ProcessOneResponse(r);
if (NS_SUCCEEDED(rv)) {
mUpdateStatus = rv;
} else {
NS_WARNING("Failed to process one response.");
}
}
}
nsresult
ProtocolParserProtobuf::ProcessOneResponse(const ListUpdateResponse& aResponse)
{
// A response must have a threat type.
if (!aResponse.has_threat_type()) {
NS_WARNING("Threat type not initialized. This seems to be an invalid response.");
return NS_ERROR_FAILURE;
}
// Convert threat type to list name.
nsCOMPtr<nsIUrlClassifierUtils> urlUtil =
do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
nsCString listName;
nsresult rv = urlUtil->ConvertThreatTypeToListName(aResponse.threat_type(),
listName);
if (NS_FAILED(rv)) {
PARSER_LOG((nsPrintfCString("Threat type to list name conversion error: %d",
aResponse.threat_type())).get());
return NS_ERROR_FAILURE;
}
// Test if this is a full update.
bool isFullUpdate = false;
if (aResponse.has_response_type()) {
isFullUpdate =
aResponse.response_type() == ListUpdateResponse::FULL_UPDATE;
} else {
NS_WARNING("Response type not initialized.");
return NS_ERROR_FAILURE;
}
// Warn if there's no new state.
if (!aResponse.has_new_client_state()) {
NS_WARNING("New state not initialized.");
return NS_ERROR_FAILURE;
}
PARSER_LOG(("==== Update for threat type '%d' ====", aResponse.threat_type()));
PARSER_LOG(("* listName: %s\n", listName.get()));
PARSER_LOG(("* newState: %s\n", aResponse.new_client_state().c_str()));
PARSER_LOG(("* isFullUpdate: %s\n", (isFullUpdate ? "yes" : "no")));
ProcessAdditionOrRemoval(aResponse.additions(), true /*aIsAddition*/);
ProcessAdditionOrRemoval(aResponse.removals(), false);
PARSER_LOG(("\n\n"));
return NS_OK;
}
nsresult
ProtocolParserProtobuf::ProcessAdditionOrRemoval(const ThreatEntrySetList& aUpdate,
bool aIsAddition)
{
nsresult ret = NS_OK;
for (int i = 0; i < aUpdate.size(); i++) {
auto update = aUpdate.Get(i);
if (!update.has_compression_type()) {
NS_WARNING(nsPrintfCString("%s with no compression type.",
aIsAddition ? "Addition" : "Removal").get());
continue;
}
switch (update.compression_type()) {
case COMPRESSION_TYPE_UNSPECIFIED:
NS_WARNING("Unspecified compression type.");
break;
case RAW:
ret = (aIsAddition ? ProcessRawAddition(update)
: ProcessRawRemoval(update));
break;
case RICE:
// Not implemented yet (see bug 1285848),
NS_WARNING("Encoded table update is not supported yet.");
break;
}
}
return ret;
}
nsresult
ProtocolParserProtobuf::ProcessRawAddition(const ThreatEntrySet& aAddition)
{
if (!aAddition.has_raw_hashes()) {
PARSER_LOG(("* No raw addition."));
return NS_OK;
}
auto rawHashes = aAddition.raw_hashes();
if (!rawHashes.has_prefix_size()) {
NS_WARNING("Raw hash has no prefix size");
return NS_OK;
}
auto prefixes = rawHashes.raw_hashes();
if (4 == rawHashes.prefix_size()) {
// Process fixed length prefixes separately.
uint32_t* fixedLengthPrefixes = (uint32_t*)prefixes.c_str();
size_t numOfFixedLengthPrefixes = prefixes.size() / 4;
PARSER_LOG(("* Raw addition (4 bytes)"));
PARSER_LOG((" - # of prefixes: %d", numOfFixedLengthPrefixes));
PARSER_LOG((" - Memory address: 0x%p", fixedLengthPrefixes));
} else {
// TODO: Process variable length prefixes including full hashes.
// See Bug 1283009.
PARSER_LOG((" Raw addition (%d bytes)", rawHashes.prefix_size()));
}
return NS_OK;
}
nsresult
ProtocolParserProtobuf::ProcessRawRemoval(const ThreatEntrySet& aRemoval)
{
if (!aRemoval.has_raw_indices()) {
NS_WARNING("A removal has no indices.");
return NS_OK;
}
// indices is an array of int32.
auto indices = aRemoval.raw_indices().indices();
PARSER_LOG(("* Raw removal"));
PARSER_LOG((" - # of removal: %d", indices.size()));
return NS_OK;
}
} // namespace safebrowsing
} // namespace mozilla

View File

@ -8,12 +8,13 @@
#include "HashStore.h"
#include "nsICryptoHMAC.h"
#include "safebrowsing.pb.h"
namespace mozilla {
namespace safebrowsing {
/**
* Some helpers for parsing the safe
* Helpers to parse the "shavar", "digest256" and "simple" list formats.
*/
class ProtocolParser {
public:
@ -23,7 +24,7 @@ public:
};
ProtocolParser();
~ProtocolParser();
virtual ~ProtocolParser();
nsresult Status() const { return mUpdateStatus; }
@ -32,7 +33,11 @@ public:
void SetCurrentTable(const nsACString& aTable);
nsresult Begin();
nsresult AppendStream(const nsACString& aData);
virtual nsresult AppendStream(const nsACString& aData);
// Notify that the inbound data is ready for parsing if progressive
// parsing is not supported, for example in V4.
virtual void End();
// Forget the table updates that were created by this pass. It
// becomes the caller's responsibility to free them. This is shitty.
@ -73,6 +78,11 @@ private:
void CleanupUpdates();
protected:
nsCString mPending;
nsresult mUpdateStatus;
private:
enum ParserState {
PROTOCOL_STATE_CONTROL,
PROTOCOL_STATE_CHUNK
@ -100,9 +110,6 @@ private:
nsCOMPtr<nsICryptoHash> mCryptoHash;
nsresult mUpdateStatus;
nsCString mPending;
uint32_t mUpdateWait;
bool mResetRequested;
@ -113,6 +120,29 @@ private:
TableUpdate *mTableUpdate;
};
// Helpers to parse the "proto" list format.
class ProtocolParserProtobuf final : public ProtocolParser {
public:
typedef FetchThreatListUpdatesResponse_ListUpdateResponse ListUpdateResponse;
typedef google::protobuf::RepeatedPtrField<ThreatEntrySet> ThreatEntrySetList;
public:
ProtocolParserProtobuf();
virtual nsresult AppendStream(const nsACString& aData) override;
virtual void End() override;
private:
virtual ~ProtocolParserProtobuf();
// For parsing update info.
nsresult ProcessOneResponse(const ListUpdateResponse& aResponse);
nsresult ProcessAdditionOrRemoval(const ThreatEntrySetList& aUpdate,
bool aIsAddition);
nsresult ProcessRawAddition(const ThreatEntrySet& aAddition);
nsresult ProcessRawRemoval(const ThreatEntrySet& aRemoval);
};
} // namespace safebrowsing
} // namespace mozilla

View File

@ -12,7 +12,7 @@ Cu.import("resource://gre/modules/Services.jsm");
//
// There is a single listmanager for the whole application.
//
// TODO more comprehensive update tests, for example add unittest check
// TODO more comprehensive update tests, for example add unittest check
// that the listmanagers tables are properly written on updates
// Lower and upper limits on the server-provided polling frequency
@ -352,8 +352,13 @@ PROT_ListManager.prototype.makeUpdateRequest_ = function(updateUrl, tableData) {
// tableNames: map of tables that need updating,
// request: list of tables and existing chunk ranges from tableData
// }
var streamerMap = { tableList: null, tableNames: {}, request: "" };
var streamerMap = { tableList: null,
tableNames: {},
requestPayload: "",
isPostRequest: true };
let useProtobuf = false;
let onceThru = false;
for (var tableName in this.tablesData) {
// Skip tables not matching this update url
if (this.tablesData[tableName].updateUrl != updateUrl) {
@ -364,11 +369,13 @@ PROT_ListManager.prototype.makeUpdateRequest_ = function(updateUrl, tableData) {
// We use the table name 'goog-*-proto' and an additional provider "google4"
// to describe the v4 settings.
let isCurTableProto = tableName.endsWith('-proto');
if (useProtobuf && !isCurTableProto) {
log('ERROR: Tables for the same updateURL should all be "proto" or none. ' +
'Check "browser.safebrowsing.provider.google4.lists"');
if (!onceThru) {
useProtobuf = isCurTableProto;
onceThru = true;
} else if (useProtobuf !== isCurTableProto) {
log('ERROR: Cannot mix "proto" tables with other types ' +
'within the same provider.');
}
useProtobuf = isCurTableProto;
if (this.needsUpdate_[this.tablesData[tableName].updateUrl][tableName]) {
streamerMap.tableNames[tableName] = true;
@ -381,8 +388,24 @@ PROT_ListManager.prototype.makeUpdateRequest_ = function(updateUrl, tableData) {
}
if (useProtobuf) {
// TODO: Bug 1275507 - XPCOM API to build v4 update request.
streamerMap.request = "";
let tableArray = streamerMap.tableList.split(',');
// The state is a byte stream which server told us from the
// last table update. The state would be used to do the partial
// update and the empty string means the table has
// never been downloaded. See Bug 1287058 for supporting
// partial update.
let stateArray = [];
tableArray.forEach(() => stateArray.push(''));
let urlUtils = Cc["@mozilla.org/url-classifier/utils;1"]
.getService(Ci.nsIUrlClassifierUtils);
let requestPayload = urlUtils.makeUpdateRequestV4(tableArray,
stateArray,
tableArray.length);
// Use a base64-encoded request.
streamerMap.requestPayload = btoa(requestPayload);
streamerMap.isPostRequest = false;
} else {
// Build the request. For each table already in the database, include the
// chunk data from the database
@ -391,23 +414,26 @@ PROT_ListManager.prototype.makeUpdateRequest_ = function(updateUrl, tableData) {
var fields = lines[i].split(";");
var name = fields[0];
if (streamerMap.tableNames[name]) {
streamerMap.request += lines[i] + "\n";
streamerMap.requestPayload += lines[i] + "\n";
delete streamerMap.tableNames[name];
}
}
// For each requested table that didn't have chunk data in the database,
// request it fresh
for (let tableName in streamerMap.tableNames) {
streamerMap.request += tableName + ";\n";
streamerMap.requestPayload += tableName + ";\n";
}
streamerMap.isPostRequest = true;
}
log("update request: " + JSON.stringify(streamerMap, undefined, 2) + "\n");
// Don't send an empty request.
if (streamerMap.request.length > 0) {
if (streamerMap.requestPayload.length > 0) {
this.makeUpdateRequestForEntry_(updateUrl, streamerMap.tableList,
streamerMap.request);
streamerMap.requestPayload,
streamerMap.isPostRequest);
} else {
// We were disabled between kicking off getTables and now.
log("Not sending empty request");
@ -416,8 +442,9 @@ PROT_ListManager.prototype.makeUpdateRequest_ = function(updateUrl, tableData) {
PROT_ListManager.prototype.makeUpdateRequestForEntry_ = function(updateUrl,
tableList,
request) {
log("makeUpdateRequestForEntry_: request " + request +
requestPayload,
isPostRequest) {
log("makeUpdateRequestForEntry_: requestPayload " + requestPayload +
" update: " + updateUrl + " tablelist: " + tableList + "\n");
var streamer = Cc["@mozilla.org/url-classifier/streamupdater;1"]
.getService(Ci.nsIUrlClassifierStreamUpdater);
@ -426,7 +453,8 @@ PROT_ListManager.prototype.makeUpdateRequestForEntry_ = function(updateUrl,
if (!streamer.downloadUpdates(
tableList,
request,
requestPayload,
isPostRequest,
updateUrl,
BindToObject(this.updateSuccess_, this, tableList, updateUrl),
BindToObject(this.updateError_, this, tableList, updateUrl),

View File

@ -20,7 +20,9 @@ interface nsIUrlClassifierStreamUpdater : nsISupports
* as well as in testing.
* @param aRequestTables Comma-separated list of tables included in this
* update.
* @param aRequestBody The body for the request.
* @param aRequestPayload The payload for the request.
* @param aIsPostRequest Whether the request should be sent by POST method.
* Should be 'true' for v2 usage.
* @param aUpdateUrl The plaintext url from which to request updates.
* @param aSuccessCallback Called after a successful update.
* @param aUpdateErrorCallback Called for problems applying the update
@ -28,7 +30,8 @@ interface nsIUrlClassifierStreamUpdater : nsISupports
* connection refused error.
*/
boolean downloadUpdates(in ACString aRequestTables,
in ACString aRequestBody,
in ACString aRequestPayload,
in boolean aIsPostRequest,
in ACString aUpdateUrl,
in nsIUrlClassifierCallback aSuccessCallback,
in nsIUrlClassifierCallback aUpdateErrorCallback,

View File

@ -441,7 +441,28 @@ nsUrlClassifierDBServiceWorker::BeginStream(const nsACString &table)
NS_ASSERTION(!mProtocolParser, "Should not have a protocol parser.");
mProtocolParser = new ProtocolParser();
// Check if we should use protobuf to parse the update.
bool useProtobuf = false;
for (size_t i = 0; i < mUpdateTables.Length(); i++) {
bool isCurProtobuf =
StringEndsWith(mUpdateTables[i], NS_LITERAL_CSTRING("-proto"));
if (0 == i) {
// Use the first table name to decice if all the subsequent tables
// should be '-proto'.
useProtobuf = isCurProtobuf;
continue;
}
if (useProtobuf != isCurProtobuf) {
NS_WARNING("Cannot mix 'proto' tables with other types "
"within the same provider.");
break;
}
}
mProtocolParser = (useProtobuf ? new ProtocolParserProtobuf()
: new ProtocolParser());
if (!mProtocolParser)
return NS_ERROR_OUT_OF_MEMORY;
@ -512,6 +533,8 @@ nsUrlClassifierDBServiceWorker::FinishStream()
mInStream = false;
mProtocolParser->End();
if (NS_SUCCEEDED(mProtocolParser->Status())) {
if (mProtocolParser->UpdateWait()) {
mUpdateWait = mProtocolParser->UpdateWait();

View File

@ -103,7 +103,8 @@ nsUrlClassifierStreamUpdater::DownloadDone()
nsresult
nsUrlClassifierStreamUpdater::FetchUpdate(nsIURI *aUpdateUrl,
const nsACString & aRequestBody,
const nsACString & aRequestPayload,
bool aIsPostRequest,
const nsACString & aStreamTable)
{
@ -111,7 +112,7 @@ nsUrlClassifierStreamUpdater::FetchUpdate(nsIURI *aUpdateUrl,
{
nsCString spec;
aUpdateUrl->GetSpec(spec);
LOG(("Fetching update %s from %s", aRequestBody.Data(), spec.get()));
LOG(("Fetching update %s from %s", aRequestPayload.Data(), spec.get()));
}
#endif
@ -134,9 +135,26 @@ nsUrlClassifierStreamUpdater::FetchUpdate(nsIURI *aUpdateUrl,
mBeganStream = false;
// If aRequestBody is empty, construct it for the test.
if (!aRequestBody.IsEmpty()) {
rv = AddRequestBody(aRequestBody);
if (!aIsPostRequest) {
// We use POST method to send our request in v2. In v4, the request
// needs to be embedded to the URL and use GET method to send.
// However, from the Chromium source code, a extended HTTP header has
// to be sent along with the request to make the request succeed.
// The following description is from Chromium source code:
//
// "The following header informs the envelope server (which sits in
// front of Google's stubby server) that the received GET request should be
// interpreted as a POST."
//
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-HTTP-Method-Override"),
NS_LITERAL_CSTRING("POST"),
false);
NS_ENSURE_SUCCESS(rv, rv);
} else if (!aRequestPayload.IsEmpty()) {
rv = AddRequestBody(aRequestPayload);
NS_ENSURE_SUCCESS(rv, rv);
}
@ -176,13 +194,19 @@ nsUrlClassifierStreamUpdater::FetchUpdate(nsIURI *aUpdateUrl,
nsresult
nsUrlClassifierStreamUpdater::FetchUpdate(const nsACString & aUpdateUrl,
const nsACString & aRequestBody,
const nsACString & aRequestPayload,
bool aIsPostRequest,
const nsACString & aStreamTable)
{
LOG(("(pre) Fetching update from %s\n", PromiseFlatCString(aUpdateUrl).get()));
nsCString updateUrl(aUpdateUrl);
if (!aIsPostRequest) {
updateUrl.AppendPrintf("&$req=%s", nsCString(aRequestPayload).get());
}
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aUpdateUrl);
nsresult rv = NS_NewURI(getter_AddRefs(uri), updateUrl);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString urlSpec;
@ -190,13 +214,14 @@ nsUrlClassifierStreamUpdater::FetchUpdate(const nsACString & aUpdateUrl,
LOG(("(post) Fetching update from %s\n", urlSpec.get()));
return FetchUpdate(uri, aRequestBody, aStreamTable);
return FetchUpdate(uri, aRequestPayload, aIsPostRequest, aStreamTable);
}
NS_IMETHODIMP
nsUrlClassifierStreamUpdater::DownloadUpdates(
const nsACString &aRequestTables,
const nsACString &aRequestBody,
const nsACString &aRequestPayload,
bool aIsPostRequest,
const nsACString &aUpdateUrl,
nsIUrlClassifierCallback *aSuccessCallback,
nsIUrlClassifierCallback *aUpdateErrorCallback,
@ -208,12 +233,13 @@ nsUrlClassifierStreamUpdater::DownloadUpdates(
NS_ENSURE_ARG(aDownloadErrorCallback);
if (mIsUpdating) {
LOG(("Already updating, queueing update %s from %s", aRequestBody.Data(),
LOG(("Already updating, queueing update %s from %s", aRequestPayload.Data(),
aUpdateUrl.Data()));
*_retval = false;
PendingRequest *request = mPendingRequests.AppendElement();
request->mTables = aRequestTables;
request->mRequest = aRequestBody;
request->mRequestPayload = aRequestPayload;
request->mIsPostRequest = aIsPostRequest;
request->mUrl = aUpdateUrl;
request->mSuccessCallback = aSuccessCallback;
request->mUpdateErrorCallback = aUpdateErrorCallback;
@ -248,11 +274,12 @@ nsUrlClassifierStreamUpdater::DownloadUpdates(
rv = mDBService->BeginUpdate(this, aRequestTables);
if (rv == NS_ERROR_NOT_AVAILABLE) {
LOG(("Service busy, already updating, queuing update %s from %s",
aRequestBody.Data(), aUpdateUrl.Data()));
aRequestPayload.Data(), aUpdateUrl.Data()));
*_retval = false;
PendingRequest *request = mPendingRequests.AppendElement();
request->mTables = aRequestTables;
request->mRequest = aRequestBody;
request->mRequestPayload = aRequestPayload;
request->mIsPostRequest = aIsPostRequest;
request->mUrl = aUpdateUrl;
request->mSuccessCallback = aSuccessCallback;
request->mUpdateErrorCallback = aUpdateErrorCallback;
@ -272,9 +299,8 @@ nsUrlClassifierStreamUpdater::DownloadUpdates(
*_retval = true;
LOG(("FetchUpdate: %s", aUpdateUrl.Data()));
//LOG(("requestBody: %s", aRequestBody.Data()));
return FetchUpdate(aUpdateUrl, aRequestBody, EmptyCString());
return FetchUpdate(aUpdateUrl, aRequestPayload, aIsPostRequest, EmptyCString());
}
///////////////////////////////////////////////////////////////////////////////
@ -318,7 +344,9 @@ nsUrlClassifierStreamUpdater::FetchNext()
PendingUpdate &update = mPendingUpdates[0];
LOG(("Fetching update url: %s\n", update.mUrl.get()));
nsresult rv = FetchUpdate(update.mUrl, EmptyCString(),
nsresult rv = FetchUpdate(update.mUrl,
EmptyCString(),
true, // This method is for v2 and v2 is always a POST.
update.mTable);
if (NS_FAILED(rv)) {
LOG(("Error fetching update url: %s\n", update.mUrl.get()));
@ -349,7 +377,8 @@ nsUrlClassifierStreamUpdater::FetchNextRequest()
bool dummy;
DownloadUpdates(
request.mTables,
request.mRequest,
request.mRequestPayload,
request.mIsPostRequest,
request.mUrl,
request.mSuccessCallback,
request.mUpdateErrorCallback,

View File

@ -54,11 +54,13 @@ private:
// Fetches an update for a single table.
nsresult FetchUpdate(nsIURI *aURI,
const nsACString &aRequestBody,
const nsACString &aRequest,
bool aIsPostRequest,
const nsACString &aTable);
// Dumb wrapper so we don't have to create URIs.
nsresult FetchUpdate(const nsACString &aURI,
const nsACString &aRequestBody,
const nsACString &aRequest,
bool aIsPostRequest,
const nsACString &aTable);
// Fetches the next table, from mPendingUpdates.
@ -78,7 +80,8 @@ private:
struct PendingRequest {
nsCString mTables;
nsCString mRequest;
nsCString mRequestPayload;
bool mIsPostRequest;
nsCString mUrl;
nsCOMPtr<nsIUrlClassifierCallback> mSuccessCallback;
nsCOMPtr<nsIUrlClassifierCallback> mUpdateErrorCallback;

View File

@ -211,6 +211,9 @@ static const struct {
{ "googpub-phish-proto", SOCIAL_ENGINEERING_PUBLIC}, // 2
{ "goog-unwanted-proto", UNWANTED_SOFTWARE}, // 3
{ "goog-phish-proto", SOCIAL_ENGINEERING}, // 5
// For testing purpose.
{ "test-phish-proto", SOCIAL_ENGINEERING_PUBLIC}, // 2
};
NS_IMETHODIMP

View File

@ -44,7 +44,7 @@ private:
public:
nsUrlClassifierUtils();
NS_DECL_ISUPPORTS
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIURLCLASSIFIERUTILS
nsresult Init();

View File

@ -200,7 +200,7 @@ function doStreamUpdate(updateText, success, failure, downloadFailure) {
downloadFailure = failure;
}
streamUpdater.downloadUpdates(allTables, "",
streamUpdater.downloadUpdates(allTables, "", true,
dataUpdate, success, failure, downloadFailure);
}

View File

@ -118,6 +118,7 @@ add_test(function test_update() {
streamUpdater.downloadUpdates(
"goog-downloadwhite-digest256",
"goog-downloadwhite-digest256;\n",
true,
"http://localhost:4444/downloads",
updateSuccess, handleError, handleError);
});

View File

@ -31,31 +31,36 @@ const TEST_TABLE_DATA_LIST = [
}
];
// This table has a different update URL.
const TEST_TABLE_DATA_ANOTHER = {
tableName: "test-listmanageranother-digest256",
providerName: "google",
updateUrl: "http://localhost:5555/safebrowsing/update",
gethashUrl: "http://localhost:5555/safebrowsing/gethash-another",
// This table has a different update URL (for v4).
const TEST_TABLE_DATA_V4 = {
tableName: "test-phish-proto",
providerName: "google4",
updateUrl: "http://localhost:5555/safebrowsing/update?",
gethashUrl: "http://localhost:5555/safebrowsing/gethash-v4",
};
const PREF_NEXTUPDATETIME = "browser.safebrowsing.provider.google.nextupdatetime";
const PREF_NEXTUPDATETIME_V4 = "browser.safebrowsing.provider.google4.nextupdatetime";
let gListManager = Cc["@mozilla.org/url-classifier/listmanager;1"]
.getService(Ci.nsIUrlListManager);
let gUrlUtils = Cc["@mozilla.org/url-classifier/utils;1"]
.getService(Ci.nsIUrlClassifierUtils);
// Global test server for serving safebrowsing updates.
let gHttpServ = null;
let gUpdateResponse = "";
let gExpectedUpdateRequest = "";
let gExpectedQueryV4 = "";
// Handles request for TEST_TABLE_DATA_ANOTHER.
let gHttpServAnother = null;
// Handles request for TEST_TABLE_DATA_V4.
let gHttpServV4 = null;
// These two variables are used to synchronize the last two racing updates
// (in terms of "update URL") in test_update_all_tables().
let gUpdatedCntForTableData = 0; // For TEST_TABLE_DATA_LIST.
let gIsAnotherUpdated = false; // For TEST_TABLE_DATA_ANOTHER.
let gIsV4Updated = false; // For TEST_TABLE_DATA_V4.
prefBranch.setBoolPref("browser.safebrowsing.debug", true);
@ -66,10 +71,11 @@ TEST_TABLE_DATA_LIST.forEach(function(t) {
t.updateUrl,
t.gethashUrl);
});
gListManager.registerTable(TEST_TABLE_DATA_ANOTHER.tableName,
TEST_TABLE_DATA_ANOTHER.providerName,
TEST_TABLE_DATA_ANOTHER.updateUrl,
TEST_TABLE_DATA_ANOTHER.gethashUrl);
gListManager.registerTable(TEST_TABLE_DATA_V4.tableName,
TEST_TABLE_DATA_V4.providerName,
TEST_TABLE_DATA_V4.updateUrl,
TEST_TABLE_DATA_V4.gethashUrl);
const SERVER_INVOLVED_TEST_CASE_LIST = [
// - Do table0 update.
@ -110,17 +116,26 @@ const SERVER_INVOLVED_TEST_CASE_LIST = [
function test_update_all_tables() {
disableAllUpdates();
// Enable all tables including TEST_TABLE_DATA_ANOTHER!
// Enable all tables including TEST_TABLE_DATA_V4!
TEST_TABLE_DATA_LIST.forEach(function(t) {
gListManager.enableUpdate(t.tableName);
});
gListManager.enableUpdate(TEST_TABLE_DATA_ANOTHER.tableName);
gListManager.enableUpdate(TEST_TABLE_DATA_V4.tableName);
// Expected results for v2.
gExpectedUpdateRequest = TEST_TABLE_DATA_LIST[0].tableName + ";a:5:s:2-12\n" +
TEST_TABLE_DATA_LIST[1].tableName + ";\n" +
TEST_TABLE_DATA_LIST[2].tableName + ";\n";
gUpdateResponse = "n:1000\n";
// We test the request against the query string since v4 request
// would be appened to the query string. The request is generated
// by protobuf API (binary) then encoded to base64 format.
let requestV4 = gUrlUtils.makeUpdateRequestV4([TEST_TABLE_DATA_V4.tableName],
[""],
1);
gExpectedQueryV4 = "&$req=" + btoa(requestV4);
forceTableUpdate();
},
@ -133,8 +148,8 @@ add_test(function test_getGethashUrl() {
TEST_TABLE_DATA_LIST.forEach(function (t) {
equal(gListManager.getGethashUrl(t.tableName), t.gethashUrl);
});
equal(gListManager.getGethashUrl(TEST_TABLE_DATA_ANOTHER.tableName),
TEST_TABLE_DATA_ANOTHER.gethashUrl);
equal(gListManager.getGethashUrl(TEST_TABLE_DATA_V4.tableName),
TEST_TABLE_DATA_V4.gethashUrl);
run_next_test();
});
@ -165,36 +180,43 @@ function run_test() {
return;
}
if (gIsAnotherUpdated) {
if (gIsV4Updated) {
run_next_test(); // All tests are done. Just finish.
return;
}
do_print("Waiting for TEST_TABLE_DATA_ANOTHER to be tested ...");
do_print("Waiting for TEST_TABLE_DATA_V4 to be tested ...");
});
gHttpServ.start(4444);
// Setup another testing server for the different update URL.
gHttpServAnother = new HttpServer();
gHttpServAnother.registerDirectory("/", do_get_cwd());
// Setup v4 testing server for the different update URL.
gHttpServV4 = new HttpServer();
gHttpServV4.registerDirectory("/", do_get_cwd());
gHttpServAnother.registerPathHandler("/safebrowsing/update", function(request, response) {
let body = NetUtil.readInputStreamToString(request.bodyInputStream,
request.bodyInputStream.available());
gHttpServV4.registerPathHandler("/safebrowsing/update", function(request, response) {
// V4 update request body should be empty.
equal(request.bodyInputStream.available(), 0);
// Verify if the request is as expected.
equal(body, TEST_TABLE_DATA_ANOTHER.tableName + ";\n");
// Not on the spec. Found in Chromium source code...
equal(request.getHeader("X-HTTP-Method-Override"), "POST");
// Respond with no chunk control.
// V4 update request uses GET.
equal(request.method, "GET");
// V4 append the base64 encoded request to the query string.
equal(request.queryString, gExpectedQueryV4);
// Respond a V2 compatible content for now. In the future we can
// send a meaningful response to test Bug 1284178 to see if the
// update is successfully stored to database.
response.setHeader("Content-Type",
"application/vnd.google.safebrowsing-update", false);
response.setStatusLine(request.httpVersion, 200, "OK");
let content = "n:1000\n";
response.bodyOutputStream.write(content, content.length);
gIsAnotherUpdated = true;
gIsV4Updated = true;
if (gUpdatedCntForTableData === SERVER_INVOLVED_TEST_CASE_LIST.length) {
// All tests are done!
@ -205,7 +227,7 @@ function run_test() {
do_print("Wait for all sever-involved tests to be done ...");
});
gHttpServAnother.start(5555);
gHttpServV4.start(5555);
run_next_test();
}
@ -214,12 +236,13 @@ function run_test() {
// call disableAllUpdates() first to clean up the updateCheckers in listmanager.
function forceTableUpdate() {
prefBranch.setCharPref(PREF_NEXTUPDATETIME, "1");
prefBranch.setCharPref(PREF_NEXTUPDATETIME_V4, "1");
gListManager.maybeToggleUpdateChecking();
}
function disableAllUpdates() {
TEST_TABLE_DATA_LIST.forEach(t => gListManager.disableUpdate(t.tableName));
gListManager.disableUpdate(TEST_TABLE_DATA_ANOTHER.tableName);
gListManager.disableUpdate(TEST_TABLE_DATA_V4.tableName);
}
// Since there's no public interface on listmanager to know the update success,
@ -243,3 +266,11 @@ function readFileToString(aFilename) {
let buf = NetUtil.readInputStreamToString(stream, stream.available());
return buf;
}
function buildUpdateRequestV4InBase64() {
let request = urlUtils.makeUpdateRequestV4([TEST_TABLE_DATA_V4.tableName],
[""],
1);
return btoa(request);
}

View File

@ -0,0 +1,31 @@
#ifndef GDKVERSIONMACROS_WRAPPER_H
#define GDKVERSIONMACROS_WRAPPER_H
/**
* Suppress all GTK3 deprecated warnings as deprecated functions are often
* used for GTK2 compatibility.
*
* GDK_VERSION_MIN_REQUIRED cannot be used to suppress warnings for functions
* deprecated in 3.0, but still needs to be set because gdkversionmacros.h
* asserts that GDK_VERSION_MAX_ALLOWED >= GDK_VERSION_MIN_REQUIRED and
* GDK_VERSION_MIN_REQUIRED >= GDK_VERSION_3_0.
*
* Setting GDK_DISABLE_DEPRECATION_WARNINGS would also disable
* GDK_UNAVAILABLE() warnings, which are useful.
*/
#define GDK_VERSION_MIN_REQUIRED GDK_VERSION_3_0
#include_next <gdk/gdkversionmacros.h>
/* GDK_AVAILABLE_IN_ALL was introduced in 3.10 */
#ifndef GDK_AVAILABLE_IN_ALL
#define GDK_AVAILABLE_IN_ALL
#endif
#undef GDK_DEPRECATED
#define GDK_DEPRECATED GDK_AVAILABLE_IN_ALL
#undef GDK_DEPRECATED_FOR
#define GDK_DEPRECATED_FOR(f) GDK_AVAILABLE_IN_ALL
#endif /* GDKVERSIONMACROS_WRAPPER_H */

View File

@ -78,9 +78,6 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk2':
'gtk2drawing.c',
]
else:
# Ignore GTK3 deprecation warnings while much of the code is still used
# with GTK2.
DEFINES['GDK_DISABLE_DEPRECATION_WARNINGS'] = True;
UNIFIED_SOURCES += [
'gtk3drawing.cpp',
'nsApplicationChooser.cpp',