зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to inbound
This commit is contained in:
Коммит
a5c6d616b0
7
CLOBBER
7
CLOBBER
|
@ -18,4 +18,9 @@
|
|||
# Modifying this file will now automatically clobber the buildbot machines \o/
|
||||
#
|
||||
|
||||
Bug 887836 - webidl changes require a Windows clobber.
|
||||
# Are you updating CLOBBER because you think it's needed for your WebIDL
|
||||
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
|
||||
# don't change CLOBBER for WebIDL changes any more.
|
||||
Bug 928195 rewrote WebIDL build system integration from the ground up. This
|
||||
will hopefully be the last required clobber due to WebIDLs poorly interacting
|
||||
with the build system.
|
||||
|
|
|
@ -118,8 +118,8 @@ this.AccessFu = {
|
|||
Output.start();
|
||||
TouchAdapter.start();
|
||||
|
||||
Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
|
||||
Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', false);
|
||||
Services.obs.addObserver(this, 'remote-browser-shown', false);
|
||||
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
|
||||
Services.obs.addObserver(this, 'Accessibility:NextObject', false);
|
||||
Services.obs.addObserver(this, 'Accessibility:PreviousObject', false);
|
||||
Services.obs.addObserver(this, 'Accessibility:Focus', false);
|
||||
|
@ -162,8 +162,8 @@ this.AccessFu = {
|
|||
Utils.win.removeEventListener('TabClose', this);
|
||||
Utils.win.removeEventListener('TabSelect', this);
|
||||
|
||||
Services.obs.removeObserver(this, 'remote-browser-frame-shown');
|
||||
Services.obs.removeObserver(this, 'in-process-browser-or-app-frame-shown');
|
||||
Services.obs.removeObserver(this, 'remote-browser-shown');
|
||||
Services.obs.removeObserver(this, 'inprocess-browser-shown');
|
||||
Services.obs.removeObserver(this, 'Accessibility:NextObject');
|
||||
Services.obs.removeObserver(this, 'Accessibility:PreviousObject');
|
||||
Services.obs.removeObserver(this, 'Accessibility:Focus');
|
||||
|
@ -304,11 +304,15 @@ this.AccessFu = {
|
|||
case 'Accessibility:MoveByGranularity':
|
||||
this.Input.moveByGranularity(JSON.parse(aData));
|
||||
break;
|
||||
case 'remote-browser-frame-shown':
|
||||
case 'in-process-browser-or-app-frame-shown':
|
||||
case 'remote-browser-shown':
|
||||
case 'inprocess-browser-shown':
|
||||
{
|
||||
let mm = aSubject.QueryInterface(Ci.nsIFrameLoader).messageManager;
|
||||
this._handleMessageManager(mm);
|
||||
// Ignore notifications that aren't from a BrowserOrApp
|
||||
let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
|
||||
if (!frameLoader.ownerIsBrowserOrAppFrame) {
|
||||
return;
|
||||
}
|
||||
this._handleMessageManager(frameLoader.messageManager);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,12 +150,16 @@ let ErrorPage = {
|
|||
},
|
||||
|
||||
init: function errorPageInit() {
|
||||
Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', false);
|
||||
Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
|
||||
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
|
||||
Services.obs.addObserver(this, 'remote-browser-shown', false);
|
||||
},
|
||||
|
||||
observe: function errorPageObserve(aSubject, aTopic, aData) {
|
||||
let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
|
||||
// Ignore notifications that aren't from a BrowserOrApp
|
||||
if (!frameLoader.ownerIsBrowserOrAppFrame) {
|
||||
return;
|
||||
}
|
||||
let mm = frameLoader.messageManager;
|
||||
|
||||
// This won't happen from dom/ipc/preload.js in non-OOP builds.
|
||||
|
|
|
@ -13,6 +13,8 @@ mock.pth:python/mock-1.0.0
|
|||
mozilla.pth:build
|
||||
mozilla.pth:config
|
||||
mozilla.pth:xpcom/typelib/xpt/tools
|
||||
mozilla.pth:dom/bindings
|
||||
mozilla.pth:dom/bindings/parser
|
||||
moztreedocs.pth:tools/docs
|
||||
copy:build/buildconfig.py
|
||||
packages.txt:testing/mozbase/packages.txt
|
||||
|
|
|
@ -111,7 +111,7 @@ interface nsIContentViewManager : nsISupports
|
|||
readonly attribute nsIContentView rootContentView;
|
||||
};
|
||||
|
||||
[scriptable, builtinclass, uuid(5b9949dc-56f1-47b6-b6d2-3785bb90ed6d)]
|
||||
[scriptable, builtinclass, uuid(a723673b-a26e-4cc6-ae23-ec70df9d97c9)]
|
||||
interface nsIFrameLoader : nsISupports
|
||||
{
|
||||
/**
|
||||
|
@ -272,6 +272,11 @@ interface nsIFrameLoader : nsISupports
|
|||
* have a notion of visibility in the parent process when frames are OOP.
|
||||
*/
|
||||
[infallible] attribute boolean visible;
|
||||
|
||||
/**
|
||||
* Find out whether the owner content really is a browser or app frame
|
||||
*/
|
||||
readonly attribute boolean ownerIsBrowserOrAppFrame;
|
||||
};
|
||||
|
||||
%{C++
|
||||
|
|
|
@ -948,9 +948,9 @@ nsFrameLoader::ShowRemoteFrame(const nsIntSize& size,
|
|||
EnsureMessageManager();
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||
if (OwnerIsBrowserOrAppFrame() && os && !mRemoteBrowserInitialized) {
|
||||
if (os && !mRemoteBrowserInitialized) {
|
||||
os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
|
||||
"remote-browser-frame-shown", nullptr);
|
||||
"remote-browser-shown", nullptr);
|
||||
mRemoteBrowserInitialized = true;
|
||||
}
|
||||
} else {
|
||||
|
@ -1409,6 +1409,14 @@ nsFrameLoader::OwnerIsBrowserOrAppFrame()
|
|||
return browserFrame ? browserFrame->GetReallyIsBrowserOrApp() : false;
|
||||
}
|
||||
|
||||
// The xpcom getter version
|
||||
NS_IMETHODIMP
|
||||
nsFrameLoader::GetOwnerIsBrowserOrAppFrame(bool* aResult)
|
||||
{
|
||||
*aResult = OwnerIsBrowserOrAppFrame();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
nsFrameLoader::OwnerIsAppFrame()
|
||||
{
|
||||
|
@ -1677,18 +1685,16 @@ nsFrameLoader::MaybeCreateDocShell()
|
|||
mDocShell->SetIsBrowserInsideApp(containingAppId);
|
||||
}
|
||||
|
||||
if (OwnerIsBrowserOrAppFrame()) {
|
||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||
if (os) {
|
||||
os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
|
||||
"in-process-browser-or-app-frame-shown", nullptr);
|
||||
}
|
||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||
if (os) {
|
||||
os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
|
||||
"inprocess-browser-shown", nullptr);
|
||||
}
|
||||
|
||||
if (mMessageManager) {
|
||||
mMessageManager->LoadFrameScript(
|
||||
NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js"),
|
||||
/* allowDelayedLoad = */ true);
|
||||
}
|
||||
if (OwnerIsBrowserOrAppFrame() && mMessageManager) {
|
||||
mMessageManager->LoadFrameScript(
|
||||
NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js"),
|
||||
/* allowDelayedLoad = */ true);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
|
|
@ -164,6 +164,15 @@ public:
|
|||
return (mIsActive || mIsFrozen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the animation is active.
|
||||
*
|
||||
* @return true if the animation is active, false otherwise.
|
||||
*/
|
||||
bool IsActive() const {
|
||||
return mIsActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this animation will replace the passed in result rather than
|
||||
* adding to it. Animations that replace the underlying value may be called
|
||||
|
|
|
@ -27,7 +27,11 @@ public:
|
|||
explicit nsSMILRepeatCount(double aCount)
|
||||
: mCount(kNotSet) { SetCount(aCount); }
|
||||
|
||||
operator double() const { return mCount; }
|
||||
operator double() const {
|
||||
MOZ_ASSERT(IsDefinite(),
|
||||
"Converting indefinite or unset repeat count to double");
|
||||
return mCount;
|
||||
}
|
||||
bool IsDefinite() const {
|
||||
return mCount != kNotSet && mCount != kIndefinite;
|
||||
}
|
||||
|
|
|
@ -112,8 +112,9 @@ namespace
|
|||
//----------------------------------------------------------------------
|
||||
// Helper class: AutoIntervalUpdateBatcher
|
||||
|
||||
// RAII helper to set the mDeferIntervalUpdates flag on an nsSMILTimedElement
|
||||
// and perform the UpdateCurrentInterval when the object is destroyed.
|
||||
// Stack-based helper class to set the mDeferIntervalUpdates flag on an
|
||||
// nsSMILTimedElement and perform the UpdateCurrentInterval when the object is
|
||||
// destroyed.
|
||||
//
|
||||
// If several of these objects are allocated on the stack, the update will not
|
||||
// be performed until the last object for a given nsSMILTimedElement is
|
||||
|
@ -146,6 +147,31 @@ private:
|
|||
bool mDidSetFlag;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Helper class: AutoIntervalUpdater
|
||||
|
||||
// Stack-based helper class to call UpdateCurrentInterval when it is destroyed
|
||||
// which helps avoid bugs where we forget to call UpdateCurrentInterval in the
|
||||
// case of early returns (e.g. due to parse errors).
|
||||
//
|
||||
// This can be safely used in conjunction with AutoIntervalUpdateBatcher; any
|
||||
// calls to UpdateCurrentInterval made by this class will simply be deferred if
|
||||
// there is an AutoIntervalUpdateBatcher on the stack.
|
||||
class MOZ_STACK_CLASS nsSMILTimedElement::AutoIntervalUpdater
|
||||
{
|
||||
public:
|
||||
AutoIntervalUpdater(nsSMILTimedElement& aTimedElement)
|
||||
: mTimedElement(aTimedElement) { }
|
||||
|
||||
~AutoIntervalUpdater()
|
||||
{
|
||||
mTimedElement.UpdateCurrentInterval();
|
||||
}
|
||||
|
||||
private:
|
||||
nsSMILTimedElement& mTimedElement;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Templated helper functions
|
||||
|
||||
|
@ -667,19 +693,32 @@ nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, bool aEndOnly)
|
|||
NS_ASSERTION(aContainerTime >= beginTime,
|
||||
"Sample time should not precede current interval");
|
||||
nsSMILTime activeTime = aContainerTime - beginTime;
|
||||
SampleSimpleTime(activeTime);
|
||||
// We register our repeat times as milestones (except when we're
|
||||
// seeking) so we should get a sample at exactly the time we repeat.
|
||||
// (And even when we are seeking we want to update
|
||||
// mCurrentRepeatIteration so we do that first before testing the seek
|
||||
// state.)
|
||||
uint32_t prevRepeatIteration = mCurrentRepeatIteration;
|
||||
if (ActiveTimeToSimpleTime(activeTime, mCurrentRepeatIteration)==0 &&
|
||||
|
||||
// The 'min' attribute can cause the active interval to be longer than
|
||||
// the 'repeating interval'.
|
||||
// In that extended period we apply the fill mode.
|
||||
if (GetRepeatDuration() <= nsSMILTimeValue(activeTime)) {
|
||||
if (mClient && mClient->IsActive()) {
|
||||
mClient->Inactivate(mFillMode == FILL_FREEZE);
|
||||
}
|
||||
SampleFillValue();
|
||||
} else {
|
||||
SampleSimpleTime(activeTime);
|
||||
|
||||
// We register our repeat times as milestones (except when we're
|
||||
// seeking) so we should get a sample at exactly the time we repeat.
|
||||
// (And even when we are seeking we want to update
|
||||
// mCurrentRepeatIteration so we do that first before testing the
|
||||
// seek state.)
|
||||
uint32_t prevRepeatIteration = mCurrentRepeatIteration;
|
||||
if (
|
||||
ActiveTimeToSimpleTime(activeTime, mCurrentRepeatIteration)==0 &&
|
||||
mCurrentRepeatIteration != prevRepeatIteration &&
|
||||
mCurrentRepeatIteration &&
|
||||
mSeekState == SEEK_NOT_SEEKING) {
|
||||
FireTimeEventAsync(NS_SMIL_REPEAT,
|
||||
static_cast<int32_t>(mCurrentRepeatIteration));
|
||||
FireTimeEventAsync(NS_SMIL_REPEAT,
|
||||
static_cast<int32_t>(mCurrentRepeatIteration));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -905,8 +944,10 @@ nsSMILTimedElement::UnsetEndSpec(RemovalTestFunction aRemove)
|
|||
nsresult
|
||||
nsSMILTimedElement::SetSimpleDuration(const nsAString& aDurSpec)
|
||||
{
|
||||
nsSMILTimeValue duration;
|
||||
// Update the current interval before returning
|
||||
AutoIntervalUpdater updater(*this);
|
||||
|
||||
nsSMILTimeValue duration;
|
||||
const nsAString& dur = nsSMILParserUtils::TrimWhitespace(aDurSpec);
|
||||
|
||||
// SVG-specific: "For SVG's animation elements, if "media" is specified, the
|
||||
|
@ -926,7 +967,6 @@ nsSMILTimedElement::SetSimpleDuration(const nsAString& aDurSpec)
|
|||
"Setting unresolved simple duration");
|
||||
|
||||
mSimpleDur = duration;
|
||||
UpdateCurrentInterval();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -941,8 +981,10 @@ nsSMILTimedElement::UnsetSimpleDuration()
|
|||
nsresult
|
||||
nsSMILTimedElement::SetMin(const nsAString& aMinSpec)
|
||||
{
|
||||
nsSMILTimeValue duration;
|
||||
// Update the current interval before returning
|
||||
AutoIntervalUpdater updater(*this);
|
||||
|
||||
nsSMILTimeValue duration;
|
||||
const nsAString& min = nsSMILParserUtils::TrimWhitespace(aMinSpec);
|
||||
|
||||
if (min.EqualsLiteral("media")) {
|
||||
|
@ -957,7 +999,6 @@ nsSMILTimedElement::SetMin(const nsAString& aMinSpec)
|
|||
NS_ABORT_IF_FALSE(duration.GetMillis() >= 0L, "Invalid duration");
|
||||
|
||||
mMin = duration;
|
||||
UpdateCurrentInterval();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -972,8 +1013,10 @@ nsSMILTimedElement::UnsetMin()
|
|||
nsresult
|
||||
nsSMILTimedElement::SetMax(const nsAString& aMaxSpec)
|
||||
{
|
||||
nsSMILTimeValue duration;
|
||||
// Update the current interval before returning
|
||||
AutoIntervalUpdater updater(*this);
|
||||
|
||||
nsSMILTimeValue duration;
|
||||
const nsAString& max = nsSMILParserUtils::TrimWhitespace(aMaxSpec);
|
||||
|
||||
if (max.EqualsLiteral("media") || max.EqualsLiteral("indefinite")) {
|
||||
|
@ -988,7 +1031,6 @@ nsSMILTimedElement::SetMax(const nsAString& aMaxSpec)
|
|||
}
|
||||
|
||||
mMax = duration;
|
||||
UpdateCurrentInterval();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -1023,15 +1065,16 @@ nsSMILTimedElement::UnsetRestart()
|
|||
nsresult
|
||||
nsSMILTimedElement::SetRepeatCount(const nsAString& aRepeatCountSpec)
|
||||
{
|
||||
// Update the current interval before returning
|
||||
AutoIntervalUpdater updater(*this);
|
||||
|
||||
nsSMILRepeatCount newRepeatCount;
|
||||
|
||||
if (nsSMILParserUtils::ParseRepeatCount(aRepeatCountSpec, newRepeatCount)) {
|
||||
mRepeatCount = newRepeatCount;
|
||||
UpdateCurrentInterval();
|
||||
return NS_OK;
|
||||
}
|
||||
mRepeatCount.Unset();
|
||||
UpdateCurrentInterval();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -1045,6 +1088,9 @@ nsSMILTimedElement::UnsetRepeatCount()
|
|||
nsresult
|
||||
nsSMILTimedElement::SetRepeatDur(const nsAString& aRepeatDurSpec)
|
||||
{
|
||||
// Update the current interval before returning
|
||||
AutoIntervalUpdater updater(*this);
|
||||
|
||||
nsSMILTimeValue duration;
|
||||
|
||||
const nsAString& repeatDur =
|
||||
|
@ -1060,7 +1106,6 @@ nsSMILTimedElement::SetRepeatDur(const nsAString& aRepeatDurSpec)
|
|||
}
|
||||
|
||||
mRepeatDur = duration;
|
||||
UpdateCurrentInterval();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -1084,12 +1129,8 @@ nsSMILTimedElement::SetFillMode(const nsAString& aFillModeSpec)
|
|||
? nsSMILFillMode(temp.GetEnumValue())
|
||||
: FILL_REMOVE;
|
||||
|
||||
// Check if we're in a fill-able state: i.e. we've played at least one
|
||||
// interval and are now between intervals or at the end of all intervals
|
||||
bool isFillable = HasPlayed() &&
|
||||
(mElementState == STATE_WAITING || mElementState == STATE_POSTACTIVE);
|
||||
|
||||
if (mClient && mFillMode != previousFillMode && isFillable) {
|
||||
// Update fill mode of client
|
||||
if (mFillMode != previousFillMode && HasClientInFillRange()) {
|
||||
mClient->Inactivate(mFillMode == FILL_FREEZE);
|
||||
SampleFillValue();
|
||||
}
|
||||
|
@ -1102,9 +1143,9 @@ nsSMILTimedElement::UnsetFillMode()
|
|||
{
|
||||
uint16_t previousFillMode = mFillMode;
|
||||
mFillMode = FILL_REMOVE;
|
||||
if ((mElementState == STATE_WAITING || mElementState == STATE_POSTACTIVE) &&
|
||||
previousFillMode == FILL_FREEZE && mClient && HasPlayed())
|
||||
if (previousFillMode == FILL_FREEZE && HasClientInFillRange()) {
|
||||
mClient->Inactivate(false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1803,11 +1844,7 @@ nsSMILTimedElement::CalcActiveEnd(const nsSMILTimeValue& aBegin,
|
|||
NS_ABORT_IF_FALSE(aBegin.IsDefinite(),
|
||||
"Indefinite or unresolved begin time in CalcActiveEnd");
|
||||
|
||||
if (mRepeatDur.IsIndefinite()) {
|
||||
result.SetIndefinite();
|
||||
} else {
|
||||
result = GetRepeatDuration();
|
||||
}
|
||||
result = GetRepeatDuration();
|
||||
|
||||
if (aEnd.IsDefinite()) {
|
||||
nsSMILTime activeDur = aEnd.GetMillis() - aBegin.GetMillis();
|
||||
|
@ -1832,29 +1869,25 @@ nsSMILTimedElement::CalcActiveEnd(const nsSMILTimeValue& aBegin,
|
|||
nsSMILTimeValue
|
||||
nsSMILTimedElement::GetRepeatDuration() const
|
||||
{
|
||||
nsSMILTimeValue result;
|
||||
|
||||
if (mRepeatCount.IsDefinite() && mRepeatDur.IsDefinite()) {
|
||||
if (mSimpleDur.IsDefinite()) {
|
||||
nsSMILTime activeDur =
|
||||
nsSMILTime(mRepeatCount * double(mSimpleDur.GetMillis()));
|
||||
result.SetMillis(std::min(activeDur, mRepeatDur.GetMillis()));
|
||||
} else {
|
||||
result = mRepeatDur;
|
||||
}
|
||||
} else if (mRepeatCount.IsDefinite() && mSimpleDur.IsDefinite()) {
|
||||
nsSMILTime activeDur =
|
||||
nsSMILTime(mRepeatCount * double(mSimpleDur.GetMillis()));
|
||||
result.SetMillis(activeDur);
|
||||
} else if (mRepeatDur.IsDefinite()) {
|
||||
result = mRepeatDur;
|
||||
} else if (mRepeatCount.IsIndefinite()) {
|
||||
result.SetIndefinite();
|
||||
nsSMILTimeValue multipliedDuration;
|
||||
if (mRepeatCount.IsDefinite() && mSimpleDur.IsDefinite()) {
|
||||
multipliedDuration.SetMillis(
|
||||
nsSMILTime(mRepeatCount * double(mSimpleDur.GetMillis())));
|
||||
} else {
|
||||
result = mSimpleDur;
|
||||
multipliedDuration.SetIndefinite();
|
||||
}
|
||||
|
||||
return result;
|
||||
nsSMILTimeValue repeatDuration;
|
||||
|
||||
if (mRepeatDur.IsResolved()) {
|
||||
repeatDuration = std::min(multipliedDuration, mRepeatDur);
|
||||
} else if (mRepeatCount.IsSet()) {
|
||||
repeatDuration = multipliedDuration;
|
||||
} else {
|
||||
repeatDuration = mSimpleDur;
|
||||
}
|
||||
|
||||
return repeatDuration;
|
||||
}
|
||||
|
||||
nsSMILTimeValue
|
||||
|
@ -1873,8 +1906,7 @@ nsSMILTimedElement::ApplyMinAndMax(const nsSMILTimeValue& aDuration) const
|
|||
if (aDuration > mMax) {
|
||||
result = mMax;
|
||||
} else if (aDuration < mMin) {
|
||||
nsSMILTimeValue repeatDur = GetRepeatDuration();
|
||||
result = mMin > repeatDur ? repeatDur : mMin;
|
||||
result = mMin;
|
||||
} else {
|
||||
result = aDuration;
|
||||
}
|
||||
|
@ -2068,16 +2100,35 @@ nsSMILTimedElement::SampleFillValue()
|
|||
if (mFillMode != FILL_FREEZE || !mClient)
|
||||
return;
|
||||
|
||||
const nsSMILInterval* prevInterval = GetPreviousInterval();
|
||||
NS_ABORT_IF_FALSE(prevInterval,
|
||||
"Attempting to sample fill value but there is no previous interval");
|
||||
NS_ABORT_IF_FALSE(prevInterval->End()->Time().IsDefinite() &&
|
||||
prevInterval->End()->IsFixedTime(),
|
||||
"Attempting to sample fill value but the endpoint of the previous "
|
||||
"interval is not resolved and fixed");
|
||||
nsSMILTime activeTime;
|
||||
|
||||
nsSMILTime activeTime = prevInterval->End()->Time().GetMillis() -
|
||||
prevInterval->Begin()->Time().GetMillis();
|
||||
if (mElementState == STATE_WAITING || mElementState == STATE_POSTACTIVE) {
|
||||
const nsSMILInterval* prevInterval = GetPreviousInterval();
|
||||
NS_ABORT_IF_FALSE(prevInterval,
|
||||
"Attempting to sample fill value but there is no previous interval");
|
||||
NS_ABORT_IF_FALSE(prevInterval->End()->Time().IsDefinite() &&
|
||||
prevInterval->End()->IsFixedTime(),
|
||||
"Attempting to sample fill value but the endpoint of the previous "
|
||||
"interval is not resolved and fixed");
|
||||
|
||||
activeTime = prevInterval->End()->Time().GetMillis() -
|
||||
prevInterval->Begin()->Time().GetMillis();
|
||||
|
||||
// If the interval's repeat duration was shorter than its active duration,
|
||||
// use the end of the repeat duration to determine the frozen animation's
|
||||
// state.
|
||||
nsSMILTimeValue repeatDuration = GetRepeatDuration();
|
||||
if (repeatDuration.IsDefinite()) {
|
||||
activeTime = std::min(repeatDuration.GetMillis(), activeTime);
|
||||
}
|
||||
} else if (mElementState == STATE_ACTIVE) {
|
||||
// If we are being asked to sample the fill value while active we *must*
|
||||
// have a repeat duration shorter than the active duration so use that.
|
||||
MOZ_ASSERT(GetRepeatDuration().IsDefinite(),
|
||||
"Attempting to sample fill value of an active animation with "
|
||||
"an indefinite repeat duration");
|
||||
activeTime = GetRepeatDuration().GetMillis();
|
||||
}
|
||||
|
||||
uint32_t repeatIteration;
|
||||
nsSMILTime simpleTime =
|
||||
|
@ -2173,8 +2224,13 @@ nsSMILTimedElement::GetNextMilestone(nsSMILMilestone& aNextMilestone) const
|
|||
// Work out what comes next: the interval end or the next repeat iteration
|
||||
nsSMILTimeValue nextRepeat;
|
||||
if (mSeekState == SEEK_NOT_SEEKING && mSimpleDur.IsDefinite()) {
|
||||
nextRepeat.SetMillis(mCurrentInterval->Begin()->Time().GetMillis() +
|
||||
(mCurrentRepeatIteration + 1) * mSimpleDur.GetMillis());
|
||||
nsSMILTime nextRepeatActiveTime =
|
||||
(mCurrentRepeatIteration + 1) * mSimpleDur.GetMillis();
|
||||
// Check that the repeat fits within the repeat duration
|
||||
if (nsSMILTimeValue(nextRepeatActiveTime) < GetRepeatDuration()) {
|
||||
nextRepeat.SetMillis(mCurrentInterval->Begin()->Time().GetMillis() +
|
||||
nextRepeatActiveTime);
|
||||
}
|
||||
}
|
||||
nsSMILTimeValue nextMilestone =
|
||||
std::min(mCurrentInterval->End()->Time(), nextRepeat);
|
||||
|
@ -2283,6 +2339,15 @@ nsSMILTimedElement::GetPreviousInterval() const
|
|||
: mOldIntervals[mOldIntervals.Length()-1].get();
|
||||
}
|
||||
|
||||
bool
|
||||
nsSMILTimedElement::HasClientInFillRange() const
|
||||
{
|
||||
// Returns true if we have a client that is in the range where it will fill
|
||||
return mClient &&
|
||||
((mElementState != STATE_ACTIVE && HasPlayed()) ||
|
||||
(mElementState == STATE_ACTIVE && !mClient->IsActive()));
|
||||
}
|
||||
|
||||
bool
|
||||
nsSMILTimedElement::EndHasEventConditions() const
|
||||
{
|
||||
|
|
|
@ -512,6 +512,7 @@ protected:
|
|||
const nsSMILInstanceTime* GetEffectiveBeginInstance() const;
|
||||
const nsSMILInterval* GetPreviousInterval() const;
|
||||
bool HasPlayed() const { return !mOldIntervals.IsEmpty(); }
|
||||
bool HasClientInFillRange() const;
|
||||
bool EndHasEventConditions() const;
|
||||
bool AreEndTimesDependentOn(
|
||||
const nsSMILInstanceTime* aBase) const;
|
||||
|
@ -615,6 +616,9 @@ protected:
|
|||
bool mDoDeferredUpdate; // Set if an update to the current interval was
|
||||
// requested while mDeferIntervalUpdates was set
|
||||
|
||||
// Stack-based helper class to call UpdateCurrentInterval when it is destroyed
|
||||
class AutoIntervalUpdater;
|
||||
|
||||
// Recursion depth checking
|
||||
uint8_t mDeleteCount;
|
||||
uint8_t mUpdateIntervalRecursionDepth;
|
||||
|
|
|
@ -32,12 +32,15 @@ support-files =
|
|||
[test_smilGetSimpleDuration.xhtml]
|
||||
[test_smilGetStartTime.xhtml]
|
||||
[test_smilHyperlinking.xhtml]
|
||||
[test_smilInvalidValues.html]
|
||||
[test_smilKeySplines.xhtml]
|
||||
[test_smilKeyTimes.xhtml]
|
||||
[test_smilKeyTimesPacedMode.xhtml]
|
||||
[test_smilMappedAttrFromBy.xhtml]
|
||||
[test_smilMappedAttrFromTo.xhtml]
|
||||
[test_smilMappedAttrPaced.xhtml]
|
||||
[test_smilMinTiming.html]
|
||||
[test_smilRepeatDuration.html]
|
||||
[test_smilRepeatTiming.xhtml]
|
||||
[test_smilReset.xhtml]
|
||||
[test_smilRestart.xhtml]
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=941315
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test invalid values cause the model to be updated (bug 941315)</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=941315">Mozilla Bug 941315</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
<svg width="100%" height="1" onload="this.pauseAnimations()">
|
||||
<rect>
|
||||
<animate id="a" dur="100s"/>
|
||||
<animate id="b" dur="5s" begin="a.end"/>
|
||||
</rect>
|
||||
<circle cx="-100" cy="20" r="15" fill="blue" id="circle"/>
|
||||
</svg>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
var a = $('a'),
|
||||
b = $('b');
|
||||
|
||||
// Animation doesn't start until onload
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
window.addEventListener("load", runTests, false);
|
||||
|
||||
// Make testing getStartTime easier
|
||||
SVGAnimationElement.prototype.safeGetStartTime = function() {
|
||||
try {
|
||||
return this.getStartTime();
|
||||
} catch(e) {
|
||||
if (e.name == "InvalidStateError" &&
|
||||
e.code == DOMException.INVALID_STATE_ERR) {
|
||||
return 'none';
|
||||
} else {
|
||||
ok(false, "Unexpected exception: " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function runTests() {
|
||||
[testSimpleDuration, testMin, testMax, testRepeatDur, testRepeatCount]
|
||||
.forEach(function(test) {
|
||||
ise(b.getStartTime(), 100, "initial state before running " + test.name);
|
||||
test();
|
||||
ise(b.getStartTime(), 100, "final state after running " + test.name);
|
||||
});
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function testSimpleDuration() {
|
||||
// Verify a valid value updates as expected
|
||||
a.setAttribute("dur", "50s");
|
||||
ise(b.safeGetStartTime(), 50, "valid simple duration");
|
||||
|
||||
// Check an invalid value also causes the model to be updated
|
||||
a.setAttribute("dur", "abc"); // -> indefinite
|
||||
ise(b.safeGetStartTime(), "none", "invalid simple duration");
|
||||
|
||||
// Restore state
|
||||
a.setAttribute("dur", "100s");
|
||||
}
|
||||
|
||||
function testMin() {
|
||||
a.setAttribute("min", "200s");
|
||||
ise(b.safeGetStartTime(), 200, "valid min duration");
|
||||
|
||||
a.setAttribute("min", "abc"); // -> indefinite
|
||||
ise(b.safeGetStartTime(), 100, "invalid min duration");
|
||||
|
||||
a.removeAttribute("min");
|
||||
}
|
||||
|
||||
function testMax() {
|
||||
a.setAttribute("max", "50s");
|
||||
ise(b.safeGetStartTime(), 50, "valid max duration");
|
||||
|
||||
a.setAttribute("max", "abc"); // -> indefinite
|
||||
ise(b.safeGetStartTime(), 100, "invalid max duration");
|
||||
|
||||
a.removeAttribute("max");
|
||||
}
|
||||
|
||||
function testRepeatDur() {
|
||||
a.setAttribute("repeatDur", "200s");
|
||||
ise(b.safeGetStartTime(), 200, "valid repeatDur duration");
|
||||
|
||||
a.setAttribute("repeatDur", "abc"); // -> indefinite
|
||||
ise(b.safeGetStartTime(), 100, "invalid repeatDur duration");
|
||||
|
||||
a.removeAttribute("repeatDur");
|
||||
}
|
||||
|
||||
function testRepeatCount() {
|
||||
a.setAttribute("repeatCount", "2");
|
||||
ise(b.safeGetStartTime(), 200, "valid repeatCount duration");
|
||||
|
||||
a.setAttribute("repeatCount", "abc"); // -> indefinite
|
||||
ise(b.safeGetStartTime(), 100, "invalid repeatCount duration");
|
||||
|
||||
a.removeAttribute("repeatCount");
|
||||
}
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,93 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=948245
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 948245</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=948245">Mozilla Bug 948245</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
<svg id="svg" onload="this.pauseAnimations()">
|
||||
<rect fill="red" id="rect" x="0">
|
||||
<animate attributeName="x" to="100" id="animation" dur="100s" min="200s"/>
|
||||
</rect>
|
||||
</svg>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
// The 'min' attribute introduces a kind of additional state into the SMIL
|
||||
// model. If the 'min' attribute extends the active duration, the additional
|
||||
// time between the amount of time the animation normally runs for (called the
|
||||
// 'repeat duration') and the extended active duration is filled using the
|
||||
// fill mode.
|
||||
//
|
||||
// Below we refer to this period of time between the end of the repeat
|
||||
// duration and the end of the active duration as the 'extended period'.
|
||||
//
|
||||
// This test verifies that as we jump in and out of these states we produce
|
||||
// the correct values.
|
||||
//
|
||||
// The test animation above produces an active interval that is longer than
|
||||
// the 'repeating duration' of the animation.
|
||||
var rect = $('rect'),
|
||||
animation = $('animation');
|
||||
|
||||
// Animation doesn't start until onload
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
window.addEventListener("load", runTests, false);
|
||||
|
||||
function runTests() {
|
||||
ok($('svg').animationsPaused(), "should be paused by <svg> load handler");
|
||||
|
||||
// In the extended period (t=150s) we should not be animating or filling
|
||||
// since the default fill mode is "none".
|
||||
animation.ownerSVGElement.setCurrentTime(150);
|
||||
ise(rect.x.animVal.value, 0,
|
||||
"Shouldn't fill in extended period with fill='none'");
|
||||
|
||||
// If we set the fill mode we should start filling.
|
||||
animation.setAttribute("fill", "freeze");
|
||||
ise(rect.x.animVal.value, 100,
|
||||
"Should fill in extended period with fill='freeze'");
|
||||
|
||||
// If we unset the fill attribute we should stop filling.
|
||||
animation.removeAttribute("fill");
|
||||
ise(rect.x.animVal.value, 0, "Shouldn't fill after unsetting fill");
|
||||
|
||||
// If we jump back into the repeated interval (at t=50s) we should be
|
||||
// animating.
|
||||
animation.ownerSVGElement.setCurrentTime(50);
|
||||
ise(rect.x.animVal.value, 50, "Should be active in repeating interval");
|
||||
|
||||
// If we jump to the boundary at the start of the extended period we should
|
||||
// not be filling (since we removed the fill attribute above).
|
||||
animation.ownerSVGElement.setCurrentTime(100);
|
||||
ise(rect.x.animVal.value, 0,
|
||||
"Shouldn't fill after seeking to boundary of extended period");
|
||||
|
||||
// If we apply a fill mode at this boundary point we should do regular fill
|
||||
// behavior of using the last value in the interpolation range.
|
||||
animation.setAttribute("fill", "freeze");
|
||||
ise(rect.x.animVal.value, 100,
|
||||
"Should fill at boundary to extended period");
|
||||
|
||||
// Check that if we seek past the interval we fill with the value at the end
|
||||
// of the _repeat_duration_ not the value at the end of the
|
||||
// _active_duration_.
|
||||
animation.setAttribute("repeatCount", "1.5");
|
||||
animation.ownerSVGElement.setCurrentTime(225);
|
||||
ise(rect.x.animVal.value, 50,
|
||||
"Should fill with the end of the repeat duration value");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,139 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=948245
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for repeat duration calculation (Bug 948245)</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=948245">Mozilla Bug 948245</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
<svg id="svg" onload="this.pauseAnimations()">
|
||||
<rect>
|
||||
<animate id="a"/>
|
||||
<animate id="b" begin="a.end"/>
|
||||
</rect>
|
||||
</svg>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
// Tests the calculation of the repeat duration which is one of the steps
|
||||
// towards determining the active duration.
|
||||
//
|
||||
// The repeat duration is determined by the following three attributes:
|
||||
//
|
||||
// dur: may be definite (e.g. '2s') or 'indefinite' (the default)
|
||||
// repeatCount: may be definite (e.g. '2.5'), 'indefinite', or not set
|
||||
// repeatDur: may be definite (e.g. '5s'), 'indefinite', or not set
|
||||
//
|
||||
// That leaves 18 combinations to test.
|
||||
var testCases =
|
||||
[
|
||||
// 1. repeatDur: definite, repeatCount: definite, dur: definite
|
||||
// (Two test cases here to ensure we get the minimum)
|
||||
{ repeatDur: 15, repeatCount: 2, dur: 10, result: 15 },
|
||||
{ repeatDur: 25, repeatCount: 2, dur: 10, result: 20 },
|
||||
// 2. repeatDur: indefinite, repeatCount: definite, dur: definite
|
||||
{ repeatDur: 'indefinite', repeatCount: 2, dur: 10, result: 20 },
|
||||
// 3. repeatDur: not set, repeatCount: definite, dur: definite
|
||||
{ repeatCount: 2, dur: 10, result: 20 },
|
||||
// 4. repeatDur: definite, repeatCount: indefinite, dur: definite
|
||||
{ repeatDur: 15, repeatCount: 'indefinite', dur: 10, result: 15 },
|
||||
// 5. repeatDur: indefinite, repeatCount: indefinite, dur: definite
|
||||
{ repeatDur: 'indefinite', repeatCount: 'indefinite', dur: 10,
|
||||
result: 'indefinite' },
|
||||
// 6. repeatDur: not set, repeatCount: indefinite, dur: definite
|
||||
{ repeatCount: 'indefinite', dur: 10, result: 'indefinite' },
|
||||
// 7. repeatDur: definite, repeatCount: not set, dur: definite
|
||||
{ repeatDur: 15, dur: 10, result: 15 },
|
||||
// 8. repeatDur: indefinite, repeatCount: not set, dur: definite
|
||||
{ repeatDur: 'indefinite', dur: 10, result: 'indefinite' },
|
||||
// 9. repeatDur: not set, repeatCount: not set, dur: definite
|
||||
{ dur: 10, result: 10 },
|
||||
// 10. repeatDur: definite, repeatCount: definite, dur: indefinite
|
||||
{ repeatDur: 15, repeatCount: 2, dur: 'indefinite', result: 15 },
|
||||
// 11. repeatDur: indefinite, repeatCount: definite, dur: indefinite
|
||||
{ repeatDur: 'indefinite', repeatCount: 2, dur: 'indefinite',
|
||||
result: 'indefinite' },
|
||||
// 12. repeatDur: not set, repeatCount: definite, dur: indefinite
|
||||
{ repeatCount: 2, dur: 'indefinite', result: 'indefinite' },
|
||||
// 13. repeatDur: definite, repeatCount: indefinite, dur: indefinite
|
||||
{ repeatDur: 15, repeatCount: 'indefinite', dur: 'indefinite',
|
||||
result: 15 },
|
||||
// 14. repeatDur: indefinite, repeatCount: indefinite, dur: indefinite
|
||||
{ repeatDur: 'indefinite', repeatCount: 'indefinite', dur: 'indefinite',
|
||||
result: 'indefinite' },
|
||||
// 15. repeatDur: not set, repeatCount: indefinite, dur: indefinite
|
||||
{ repeatCount: 'indefinite', dur: 'indefinite', result: 'indefinite' },
|
||||
// 16. repeatDur: definite, repeatCount: not set, dur: indefinite
|
||||
{ repeatDur: 15, dur: 'indefinite', result: 15 },
|
||||
// 17. repeatDur: indefinite, repeatCount: not set, dur: indefinite
|
||||
{ repeatDur: 'indefinite', dur: 'indefinite', result: 'indefinite' },
|
||||
// 18. repeatDur: not set, repeatCount: not set, dur: indefinite
|
||||
{ dur: 'indefinite', result: 'indefinite' }
|
||||
];
|
||||
|
||||
// We can test the repeat duration by setting these attributes on animation
|
||||
// 'a' and checking the start time of 'b' which is defined to start when 'a'
|
||||
// finishes.
|
||||
//
|
||||
// Since 'a' has no end/min/max attributes the end of its active interval
|
||||
// should coincide with the end of its repeat duration.
|
||||
//
|
||||
// Sometimes the repeat duration is defined to be 'indefinite'. In this case
|
||||
// calling getStartTime on b will throw an exception so we need to catch that
|
||||
// exception and translate it to 'indefinite' as follows:
|
||||
function getRepeatDuration() {
|
||||
try {
|
||||
return $('b').getStartTime();
|
||||
} catch(e) {
|
||||
if (e.name == "InvalidStateError" &&
|
||||
e.code == DOMException.INVALID_STATE_ERR) {
|
||||
return 'indefinite';
|
||||
} else {
|
||||
ok(false, "Unexpected exception: " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animation doesn't start until onload
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
window.addEventListener("load", runTests, false);
|
||||
|
||||
// Run through each of the test cases
|
||||
function runTests() {
|
||||
ok($('svg').animationsPaused(), "should be paused by <svg> load handler");
|
||||
|
||||
testCases.forEach(function(test) {
|
||||
var a = $('a');
|
||||
|
||||
// Set the attributes
|
||||
var msgPieces = [];
|
||||
[ 'repeatDur', 'repeatCount', 'dur' ].forEach(function(attr) {
|
||||
if (typeof test[attr] != "undefined") {
|
||||
a.setAttribute(attr, test[attr].toString());
|
||||
msgPieces.push(attr + ': ' + test[attr].toString());
|
||||
} else {
|
||||
a.removeAttribute(attr);
|
||||
msgPieces.push(attr + ': <not set>');
|
||||
}
|
||||
});
|
||||
var msg = msgPieces.join(', ');
|
||||
|
||||
// Check the result
|
||||
ise(getRepeatDuration(), test.result, msg);
|
||||
});
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -1,98 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import cPickle
|
||||
from Configuration import Configuration
|
||||
from Codegen import CGBindingRoot, replaceFileIfChanged, CGEventRoot
|
||||
from mozbuild.makeutil import Makefile
|
||||
from mozbuild.pythonutil import iter_modules_in_path
|
||||
from buildconfig import topsrcdir
|
||||
|
||||
|
||||
def generate_binding_files(config, outputprefix, srcprefix, webidlfile,
|
||||
generatedEventsWebIDLFiles):
|
||||
"""
|
||||
|config| Is the configuration object.
|
||||
|outputprefix| is a prefix to use for the header guards and filename.
|
||||
"""
|
||||
|
||||
depsname = ".deps/" + outputprefix + ".pp"
|
||||
root = CGBindingRoot(config, outputprefix, webidlfile)
|
||||
replaceFileIfChanged(outputprefix + ".h", root.declare())
|
||||
replaceFileIfChanged(outputprefix + ".cpp", root.define())
|
||||
|
||||
if webidlfile in generatedEventsWebIDLFiles:
|
||||
eventName = webidlfile[:-len(".webidl")]
|
||||
generatedEvent = CGEventRoot(config, eventName)
|
||||
replaceFileIfChanged(eventName + ".h", generatedEvent.declare())
|
||||
replaceFileIfChanged(eventName + ".cpp", generatedEvent.define())
|
||||
|
||||
mk = Makefile()
|
||||
# NOTE: it's VERY important that we output dependencies for the FooBinding
|
||||
# file here, not for the header or generated cpp file. These dependencies
|
||||
# are used later to properly determine changedDeps and prevent rebuilding
|
||||
# too much. See the comment explaining $(binding_dependency_trackers) in
|
||||
# Makefile.in.
|
||||
rule = mk.create_rule([outputprefix])
|
||||
rule.add_dependencies(os.path.join(srcprefix, x) for x in sorted(root.deps()))
|
||||
rule.add_dependencies(iter_modules_in_path(topsrcdir))
|
||||
with open(depsname, 'w') as f:
|
||||
mk.dump(f)
|
||||
|
||||
def main():
|
||||
# Parse arguments.
|
||||
from optparse import OptionParser
|
||||
usagestring = "usage: %prog [header|cpp] configFile outputPrefix srcPrefix webIDLFile"
|
||||
o = OptionParser(usage=usagestring)
|
||||
o.add_option("--verbose-errors", action='store_true', default=False,
|
||||
help="When an error happens, display the Python traceback.")
|
||||
(options, args) = o.parse_args()
|
||||
|
||||
configFile = os.path.normpath(args[0])
|
||||
srcPrefix = os.path.normpath(args[1])
|
||||
|
||||
# Load the configuration
|
||||
f = open('ParserResults.pkl', 'rb')
|
||||
config = cPickle.load(f)
|
||||
f.close()
|
||||
|
||||
def readFile(f):
|
||||
file = open(f, 'rb')
|
||||
try:
|
||||
contents = file.read()
|
||||
finally:
|
||||
file.close()
|
||||
return contents
|
||||
allWebIDLFiles = readFile(args[2]).split()
|
||||
generatedEventsWebIDLFiles = readFile(args[3]).split()
|
||||
changedDeps = readFile(args[4]).split()
|
||||
|
||||
if all(f.endswith("Binding") or f == "ParserResults.pkl" for f in changedDeps):
|
||||
toRegenerate = filter(lambda f: f.endswith("Binding"), changedDeps)
|
||||
if len(toRegenerate) == 0 and len(changedDeps) == 1:
|
||||
# Work around build system bug 874923: if we get here that means
|
||||
# that changedDeps contained only one entry and it was
|
||||
# "ParserResults.pkl". That should never happen: if the
|
||||
# ParserResults.pkl changes then either one of the globalgen files
|
||||
# changed (in which case we wouldn't be in this "only
|
||||
# ParserResults.pkl and *Binding changed" code) or some .webidl
|
||||
# files changed (and then the corresponding *Binding files should
|
||||
# show up in changedDeps). Since clearly the build system is
|
||||
# confused, just regenerate everything to be safe.
|
||||
toRegenerate = allWebIDLFiles
|
||||
else:
|
||||
toRegenerate = map(lambda f: f[:-len("Binding")] + ".webidl",
|
||||
toRegenerate)
|
||||
else:
|
||||
toRegenerate = allWebIDLFiles
|
||||
|
||||
for webIDLFile in toRegenerate:
|
||||
assert webIDLFile.endswith(".webidl")
|
||||
outputPrefix = webIDLFile[:-len(".webidl")] + "Binding"
|
||||
generate_binding_files(config, outputPrefix, srcPrefix, webIDLFile,
|
||||
generatedEventsWebIDLFiles);
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -29,27 +29,6 @@ INSTANCE_RESERVED_SLOTS = 3
|
|||
def memberReservedSlot(member):
|
||||
return "(DOM_INSTANCE_RESERVED_SLOTS + %d)" % member.slotIndex
|
||||
|
||||
def replaceFileIfChanged(filename, newContents):
|
||||
"""
|
||||
Read a copy of the old file, so that we don't touch it if it hasn't changed.
|
||||
Returns True if the file was updated, false otherwise.
|
||||
"""
|
||||
oldFileContents = ""
|
||||
try:
|
||||
oldFile = open(filename, 'rb')
|
||||
oldFileContents = ''.join(oldFile.readlines())
|
||||
oldFile.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
if newContents == oldFileContents:
|
||||
return False
|
||||
|
||||
f = open(filename, 'wb')
|
||||
f.write(newContents)
|
||||
f.close()
|
||||
return True
|
||||
|
||||
def toStringBool(arg):
|
||||
return str(not not arg).lower()
|
||||
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import cPickle
|
||||
from Configuration import Configuration
|
||||
from Codegen import CGExampleRoot, replaceFileIfChanged
|
||||
|
||||
def generate_interface_example(config, interfaceName):
|
||||
"""
|
||||
|config| Is the configuration object.
|
||||
|interfaceName| is the name of the interface we're generating an example for.
|
||||
"""
|
||||
|
||||
root = CGExampleRoot(config, interfaceName)
|
||||
exampleHeader = interfaceName + "-example.h"
|
||||
exampleImpl = interfaceName + "-example.cpp"
|
||||
replaceFileIfChanged(exampleHeader, root.declare())
|
||||
replaceFileIfChanged(exampleImpl, root.define())
|
||||
|
||||
def main():
|
||||
|
||||
# Parse arguments.
|
||||
from optparse import OptionParser
|
||||
usagestring = "usage: %prog configFile interfaceName"
|
||||
o = OptionParser(usage=usagestring)
|
||||
o.add_option("--verbose-errors", action='store_true', default=False,
|
||||
help="When an error happens, display the Python traceback.")
|
||||
(options, args) = o.parse_args()
|
||||
|
||||
if len(args) != 2:
|
||||
o.error(usagestring)
|
||||
configFile = os.path.normpath(args[0])
|
||||
interfaceName = args[1]
|
||||
|
||||
# Load the configuration
|
||||
f = open('ParserResults.pkl', 'rb')
|
||||
config = cPickle.load(f)
|
||||
f.close()
|
||||
|
||||
# Generate the example class.
|
||||
generate_interface_example(config, interfaceName)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,81 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# We do one global pass over all the WebIDL to generate our prototype enum
|
||||
# and generate information for subsequent phases.
|
||||
|
||||
import os
|
||||
import WebIDL
|
||||
import cPickle
|
||||
from Configuration import Configuration
|
||||
from Codegen import GlobalGenRoots, replaceFileIfChanged
|
||||
|
||||
def generate_file(config, name, action):
|
||||
|
||||
root = getattr(GlobalGenRoots, name)(config)
|
||||
if action is 'declare':
|
||||
filename = name + '.h'
|
||||
code = root.declare()
|
||||
else:
|
||||
assert action is 'define'
|
||||
filename = name + '.cpp'
|
||||
code = root.define()
|
||||
|
||||
if replaceFileIfChanged(filename, code):
|
||||
print "Generating %s" % (filename)
|
||||
else:
|
||||
print "%s hasn't changed - not touching it" % (filename)
|
||||
|
||||
def main():
|
||||
# Parse arguments.
|
||||
from optparse import OptionParser
|
||||
usageString = "usage: %prog [options] webidldir [files]"
|
||||
o = OptionParser(usage=usageString)
|
||||
o.add_option("--cachedir", dest='cachedir', default=None,
|
||||
help="Directory in which to cache lex/parse tables.")
|
||||
o.add_option("--verbose-errors", action='store_true', default=False,
|
||||
help="When an error happens, display the Python traceback.")
|
||||
(options, args) = o.parse_args()
|
||||
|
||||
if len(args) < 2:
|
||||
o.error(usageString)
|
||||
|
||||
configFile = args[0]
|
||||
baseDir = args[1]
|
||||
fileList = args[2:]
|
||||
|
||||
# Parse the WebIDL.
|
||||
parser = WebIDL.Parser(options.cachedir)
|
||||
for filename in fileList:
|
||||
fullPath = os.path.normpath(os.path.join(baseDir, filename))
|
||||
f = open(fullPath, 'rb')
|
||||
lines = f.readlines()
|
||||
f.close()
|
||||
parser.parse(''.join(lines), fullPath)
|
||||
parserResults = parser.finish()
|
||||
|
||||
# Load the configuration.
|
||||
config = Configuration(configFile, parserResults)
|
||||
|
||||
# Write the configuration out to a pickle.
|
||||
resultsFile = open('ParserResults.pkl', 'wb')
|
||||
cPickle.dump(config, resultsFile, -1)
|
||||
resultsFile.close()
|
||||
|
||||
# Generate the atom list.
|
||||
generate_file(config, 'GeneratedAtomList', 'declare')
|
||||
|
||||
# Generate the prototype list.
|
||||
generate_file(config, 'PrototypeList', 'declare')
|
||||
|
||||
# Generate the common code.
|
||||
generate_file(config, 'RegisterBindings', 'declare')
|
||||
generate_file(config, 'RegisterBindings', 'define')
|
||||
|
||||
generate_file(config, 'UnionTypes', 'declare')
|
||||
generate_file(config, 'UnionTypes', 'define')
|
||||
generate_file(config, 'UnionConversions', 'declare')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,243 +1,85 @@
|
|||
# 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/.
|
||||
# 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/.
|
||||
|
||||
abs_dist := $(abspath $(DIST))
|
||||
webidl_base := $(topsrcdir)/dom/webidl
|
||||
|
||||
webidl_base = $(topsrcdir)/dom/webidl
|
||||
# Generated by moz.build
|
||||
include webidlsrcs.mk
|
||||
|
||||
binding_include_path := mozilla/dom
|
||||
webidl_files += $(generated_events_webidl_files)
|
||||
all_webidl_files = $(webidl_files) $(generated_webidl_files) $(preprocessed_webidl_files)
|
||||
|
||||
# Set exported_binding_headers before adding the test IDL to the mix
|
||||
exported_binding_headers := $(subst .webidl,Binding.h,$(all_webidl_files))
|
||||
exported_generated_events_headers := $(subst .webidl,.h,$(generated_events_webidl_files))
|
||||
|
||||
# Set linked_binding_cpp_files before adding the test IDL to the mix
|
||||
linked_binding_cpp_files := $(subst .webidl,Binding.cpp,$(all_webidl_files))
|
||||
linked_generated_events_cpp_files := $(subst .webidl,.cpp,$(generated_events_webidl_files))
|
||||
|
||||
all_webidl_files += $(test_webidl_files) $(preprocessed_test_webidl_files)
|
||||
|
||||
generated_header_files := $(subst .webidl,Binding.h,$(all_webidl_files)) $(exported_generated_events_headers)
|
||||
generated_cpp_files := $(subst .webidl,Binding.cpp,$(all_webidl_files)) $(linked_generated_events_cpp_files)
|
||||
|
||||
# We want to be able to only regenerate the .cpp and .h files that really need
|
||||
# to change when a .webidl file changes. We do this by making the
|
||||
# binding_dependency_trackers targets have dependencies on the right .webidl
|
||||
# files via generated .pp files, having a .BindingGen target that depends on the
|
||||
# binding_dependency_trackers and which has all the generated binding .h/.cpp
|
||||
# depending on it, and then in the make commands for that target being able to
|
||||
# check which exact binding_dependency_trackers changed.
|
||||
binding_dependency_trackers := $(subst .webidl,Binding,$(all_webidl_files))
|
||||
|
||||
globalgen_targets := \
|
||||
GeneratedAtomList.h \
|
||||
PrototypeList.h \
|
||||
RegisterBindings.h \
|
||||
RegisterBindings.cpp \
|
||||
UnionTypes.h \
|
||||
UnionTypes.cpp \
|
||||
UnionConversions.h \
|
||||
$(NULL)
|
||||
|
||||
# Nasty hack: when the test/Makefile.in invokes us to do codegen, it
|
||||
# uses a target of
|
||||
# "export TestExampleInterface-example TestExampleProxyInterface-example".
|
||||
# We don't actually need to load our .o.pp files in that case, so just
|
||||
# pretend like we have no CPPSRCS if that's the target. It makes the
|
||||
# test cycle much faster, which is why we're doing it.
|
||||
#
|
||||
# XXXbz We could try to cheat even more and only include our CPPSRCS
|
||||
# when $(MAKECMDGOALS) contains libs, so that we can skip loading all
|
||||
# those .o.pp when trying to make a single .cpp file too, but that
|
||||
# would break |make FooBinding.o(bj)|. Ah, well.
|
||||
ifneq (export TestExampleInterface-example TestExampleProxyInterface-example,$(MAKECMDGOALS))
|
||||
CPPSRCS = \
|
||||
$(unified_binding_cpp_files) \
|
||||
$(linked_generated_events_cpp_files) \
|
||||
$(filter %.cpp, $(globalgen_targets)) \
|
||||
$(NULL)
|
||||
ifdef GNU_CC
|
||||
OS_CXXFLAGS += -Wno-uninitialized
|
||||
endif
|
||||
|
||||
ABS_DIST := $(abspath $(DIST))
|
||||
# These come from webidlsrcs.mk.
|
||||
# TODO Write directly into backend.mk.
|
||||
CPPSRCS += $(globalgen_sources) $(unified_binding_cpp_files)
|
||||
|
||||
EXTRA_EXPORT_MDDEPEND_FILES := $(addsuffix .pp,$(binding_dependency_trackers))
|
||||
# Generated bindings reference *Binding.h, not mozilla/dom/*Binding.h. And,
|
||||
# since we generate exported bindings directly to $(DIST)/include, we need
|
||||
# to add that path to the search list.
|
||||
#
|
||||
# Ideally, binding generation uses the prefixed header file names.
|
||||
# Bug 932092 tracks.
|
||||
LOCAL_INCLUDES += -I$(DIST)/include/mozilla/dom
|
||||
|
||||
EXPORTS_GENERATED_FILES := $(exported_binding_headers) $(exported_generated_events_headers)
|
||||
EXPORTS_GENERATED_DEST := $(ABS_DIST)/include/$(binding_include_path)
|
||||
EXPORTS_GENERATED_TARGET := export
|
||||
INSTALL_TARGETS += EXPORTS_GENERATED
|
||||
|
||||
# Install auto-generated GlobalGen files. The rules for the install must
|
||||
# be in the same target/subtier as GlobalGen.py, otherwise the files will not
|
||||
# get installed into the appropriate location as they are generated.
|
||||
globalgen_headers_FILES := \
|
||||
GeneratedAtomList.h \
|
||||
PrototypeList.h \
|
||||
RegisterBindings.h \
|
||||
UnionConversions.h \
|
||||
UnionTypes.h \
|
||||
$(NULL)
|
||||
globalgen_headers_DEST = $(ABS_DIST)/include/mozilla/dom
|
||||
globalgen_headers_TARGET := export
|
||||
INSTALL_TARGETS += globalgen_headers
|
||||
PYTHON_UNIT_TESTS += $(srcdir)/mozwebidlcodegen/test/test_mozwebidlcodegen.py
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
ifdef GNU_CC
|
||||
CXXFLAGS += -Wno-uninitialized
|
||||
endif
|
||||
|
||||
# If you change bindinggen_dependencies here, change it in
|
||||
# dom/bindings/test/Makefile.in too.
|
||||
bindinggen_dependencies := \
|
||||
BindingGen.py \
|
||||
Bindings.conf \
|
||||
Configuration.py \
|
||||
Codegen.py \
|
||||
ParserResults.pkl \
|
||||
parser/WebIDL.py \
|
||||
# TODO This list should be emitted to a .pp file via
|
||||
# GenerateCSS2PropertiesWebIDL.py.
|
||||
css2properties_dependencies = \
|
||||
$(topsrcdir)/layout/style/nsCSSPropList.h \
|
||||
$(topsrcdir)/layout/style/nsCSSPropAliasList.h \
|
||||
$(webidl_base)/CSS2Properties.webidl.in \
|
||||
$(webidl_base)/CSS2PropertiesProps.h \
|
||||
$(srcdir)/GenerateCSS2PropertiesWebIDL.py \
|
||||
$(GLOBAL_DEPS) \
|
||||
$(NULL)
|
||||
|
||||
CSS2Properties.webidl: $(topsrcdir)/layout/style/nsCSSPropList.h \
|
||||
$(topsrcdir)/layout/style/nsCSSPropAliasList.h \
|
||||
$(webidl_base)/CSS2Properties.webidl.in \
|
||||
$(webidl_base)/CSS2PropertiesProps.h \
|
||||
$(srcdir)/GenerateCSS2PropertiesWebIDL.py \
|
||||
$(GLOBAL_DEPS)
|
||||
$(CPP) $(DEFINES) $(ACDEFINES) -I$(topsrcdir)/layout/style $(webidl_base)/CSS2PropertiesProps.h | \
|
||||
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) \
|
||||
$(srcdir)/GenerateCSS2PropertiesWebIDL.py $(webidl_base)/CSS2Properties.webidl.in > CSS2Properties.webidl
|
||||
CSS2Properties.webidl: $(css2properties_dependencies)
|
||||
$(CPP) $(DEFINES) $(ACDEFINES) -I$(topsrcdir)/layout/style \
|
||||
$(webidl_base)/CSS2PropertiesProps.h | \
|
||||
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) \
|
||||
$(srcdir)/GenerateCSS2PropertiesWebIDL.py \
|
||||
$(webidl_base)/CSS2Properties.webidl.in > $@
|
||||
|
||||
$(webidl_files): %: $(webidl_base)/%
|
||||
$(INSTALL) $(IFLAGS1) $(webidl_base)/$* .
|
||||
|
||||
$(test_webidl_files): %: $(srcdir)/test/%
|
||||
$(INSTALL) $(IFLAGS1) $(srcdir)/test/$* .
|
||||
|
||||
# We can't easily use PP_TARGETS here because it insists on outputting targets
|
||||
# that look like "$(CURDIR)/foo" whereas we want our target to just be "foo".
|
||||
# Make sure to include $(GLOBAL_DEPS) so we pick up changes to what symbols are
|
||||
# defined. Also make sure to remove $@ before writing to it, because otherwise
|
||||
# if a file goes from non-preprocessed to preprocessed we can end up writing to
|
||||
# a symlink, which will clobber files in the srcdir, which is bad.
|
||||
$(preprocessed_webidl_files): %: $(webidl_base)/% $(GLOBAL_DEPS)
|
||||
$(RM) $@
|
||||
$(call py_action,preprocessor, \
|
||||
$(DEFINES) $(ACDEFINES) $(XULPPFLAGS) $(webidl_base)/$* -o $@)
|
||||
|
||||
# See the comment about PP_TARGETS for $(preprocessed_webidl_files)
|
||||
$(preprocessed_test_webidl_files): %: $(srcdir)/test/% $(GLOBAL_DEPS)
|
||||
$(RM) $@
|
||||
$(call py_action,preprocessor, \
|
||||
$(DEFINES) $(ACDEFINES) $(XULPPFLAGS) $(srcdir)/test/$* -o $@)
|
||||
|
||||
# Make is dumb and can get confused between "foo" and "$(CURDIR)/foo". Make
|
||||
# sure that the latter depends on the former, since the latter gets used in .pp
|
||||
# files.
|
||||
all_webidl_files_absolute = $(addprefix $(CURDIR)/,$(all_webidl_files))
|
||||
$(all_webidl_files_absolute): $(CURDIR)/%: %
|
||||
|
||||
$(generated_header_files): .BindingGen
|
||||
|
||||
$(generated_cpp_files): .BindingGen
|
||||
|
||||
# $(binding_dependency_trackers) pick up additional dependencies via .pp files
|
||||
# The rule: just brings the tracker up to date, if it's out of date, so that
|
||||
# we'll know that we have to redo binding generation and flag this prerequisite
|
||||
# there as being newer than the bindinggen target.
|
||||
$(binding_dependency_trackers):
|
||||
@$(TOUCH) $@
|
||||
|
||||
$(globalgen_targets): ParserResults.pkl
|
||||
|
||||
%-example: .BindingGen
|
||||
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
|
||||
$(PLY_INCLUDE) -I$(srcdir)/parser \
|
||||
$(srcdir)/ExampleGen.py \
|
||||
$(srcdir)/Bindings.conf $*
|
||||
|
||||
CACHE_DIR = _cache
|
||||
|
||||
globalgen_dependencies := \
|
||||
GlobalGen.py \
|
||||
Bindings.conf \
|
||||
Configuration.py \
|
||||
Codegen.py \
|
||||
parser/WebIDL.py \
|
||||
webidlsrcs.mk \
|
||||
$(all_webidl_files) \
|
||||
$(CACHE_DIR)/.done \
|
||||
# Most of the logic for dependencies lives inside Python so it can be
|
||||
# used by multiple build backends. We simply have rules to generate
|
||||
# and include the .pp file.
|
||||
#
|
||||
# The generated .pp file contains all the important dependencies such as
|
||||
# changes to .webidl or .py files should result in code generation being
|
||||
# performed.
|
||||
codegen_dependencies := \
|
||||
$(nonstatic_webidl_files) \
|
||||
$(GLOBAL_DEPS) \
|
||||
$(NULL)
|
||||
|
||||
$(CACHE_DIR)/.done:
|
||||
$(MKDIR) -p $(CACHE_DIR)
|
||||
$(call include_deps,codegen.pp)
|
||||
|
||||
codegen.pp: $(codegen_dependencies)
|
||||
$(call py_action,webidl,$(srcdir))
|
||||
@$(TOUCH) $@
|
||||
|
||||
# Running GlobalGen.py updates ParserResults.pkl as a side-effect
|
||||
ParserResults.pkl: $(globalgen_dependencies)
|
||||
$(info Generating global WebIDL files)
|
||||
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
|
||||
$(PLY_INCLUDE) -I$(srcdir)/parser \
|
||||
$(srcdir)/GlobalGen.py $(srcdir)/Bindings.conf . \
|
||||
--cachedir=$(CACHE_DIR) \
|
||||
$(all_webidl_files)
|
||||
|
||||
$(globalgen_headers_FILES): ParserResults.pkl
|
||||
|
||||
# Make sure .deps actually exists, since we'll try to write to it from
|
||||
# BindingGen.py but we're typically running in the export phase, which is
|
||||
# before anyone has bothered creating .deps.
|
||||
# Then, pass our long lists through files to try to avoid blowing out the
|
||||
# command line.
|
||||
# Next, BindingGen.py will examine the changed dependency list to figure out
|
||||
# what it really needs to regenerate.
|
||||
# Finally, touch the .BindingGen file so that we don't have to keep redoing
|
||||
# all that until something else actually changes.
|
||||
.BindingGen: $(bindinggen_dependencies) $(binding_dependency_trackers)
|
||||
$(info Generating WebIDL bindings)
|
||||
$(MKDIR) -p .deps
|
||||
echo $(all_webidl_files) > .all-webidl-file-list
|
||||
echo $(generated_events_webidl_files) > .generated-events-webidl-files
|
||||
echo $? > .changed-dependency-list
|
||||
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
|
||||
$(PLY_INCLUDE) -I$(srcdir)/parser \
|
||||
$(srcdir)/BindingGen.py \
|
||||
$(srcdir)/Bindings.conf \
|
||||
$(CURDIR) \
|
||||
.all-webidl-file-list \
|
||||
.generated-events-webidl-files \
|
||||
.changed-dependency-list
|
||||
@$(TOUCH) $@
|
||||
.PHONY: compiletests
|
||||
compiletests:
|
||||
$(call SUBMAKE,libs,test)
|
||||
|
||||
GARBAGE += \
|
||||
webidlyacc.py \
|
||||
codegen.pp \
|
||||
codegen.json \
|
||||
parser.out \
|
||||
$(wildcard *-example.h) \
|
||||
$(wildcard *-example.cpp) \
|
||||
.BindingGen \
|
||||
.all-webidl-file-list \
|
||||
.generated-events-webidl-files \
|
||||
.changed-dependency-list \
|
||||
$(binding_dependency_trackers) \
|
||||
WebIDLGrammar.pkl \
|
||||
$(wildcard *.h) \
|
||||
$(wildcard *Binding.cpp) \
|
||||
$(wildcard *Event.cpp) \
|
||||
$(wildcard *-event.cpp) \
|
||||
$(wildcard *.webidl) \
|
||||
$(NULL)
|
||||
|
||||
# Make sure all binding header files are created during the export stage, so we
|
||||
# don't have issues with .cpp files being compiled before we've generated the
|
||||
# headers they depend on. This is really only needed for the test files, since
|
||||
# the non-test headers are all exported above anyway. Note that this means that
|
||||
# we do all of our codegen during export.
|
||||
export:: $(generated_header_files)
|
||||
|
||||
distclean::
|
||||
-$(RM) \
|
||||
$(generated_header_files) \
|
||||
$(generated_cpp_files) \
|
||||
$(all_webidl_files) \
|
||||
$(globalgen_targets) \
|
||||
ParserResults.pkl \
|
||||
$(NULL)
|
||||
DIST_GARBAGE += \
|
||||
file-lists.json \
|
||||
$(NULL)
|
||||
|
|
|
@ -18,6 +18,17 @@ from mozbuild.base import MachCommandBase
|
|||
|
||||
@CommandProvider
|
||||
class WebIDLProvider(MachCommandBase):
|
||||
@Command('webidl-example', category='misc',
|
||||
description='Generate example files for a WebIDL interface.')
|
||||
@CommandArgument('interface', nargs='+',
|
||||
help='Interface(s) whose examples to generate.')
|
||||
def webidl_example(self, interface):
|
||||
from mozwebidlcodegen import BuildSystemWebIDL
|
||||
|
||||
manager = self._spawn(BuildSystemWebIDL).manager
|
||||
for i in interface:
|
||||
manager.generate_example_files(i)
|
||||
|
||||
@Command('webidl-parser-test', category='testing',
|
||||
description='Run WebIDL tests.')
|
||||
@CommandArgument('--verbose', '-v', action='store_true',
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
# 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/.
|
||||
|
||||
TEST_DIRS += ['test']
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'ErrorResult.h',
|
||||
]
|
||||
|
|
|
@ -0,0 +1,565 @@
|
|||
# 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/.
|
||||
|
||||
# This module contains code for managing WebIDL files and bindings for
|
||||
# the build system.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import errno
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
from mach.mixin.logging import LoggingMixin
|
||||
|
||||
from mozbuild.base import MozbuildObject
|
||||
from mozbuild.makeutil import Makefile
|
||||
from mozbuild.pythonutil import iter_modules_in_path
|
||||
from mozbuild.util import FileAvoidWrite
|
||||
|
||||
import mozpack.path as mozpath
|
||||
|
||||
import WebIDL
|
||||
from Codegen import (
|
||||
CGBindingRoot,
|
||||
CGEventRoot,
|
||||
CGExampleRoot,
|
||||
GlobalGenRoots,
|
||||
)
|
||||
from Configuration import Configuration
|
||||
|
||||
|
||||
class BuildResult(object):
|
||||
"""Represents the result of processing WebIDL files.
|
||||
|
||||
This holds a summary of output file generation during code generation.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# The .webidl files that had their outputs regenerated.
|
||||
self.inputs = set()
|
||||
|
||||
# The output files that were created.
|
||||
self.created = set()
|
||||
|
||||
# The output files that changed.
|
||||
self.updated = set()
|
||||
|
||||
# The output files that didn't change.
|
||||
self.unchanged = set()
|
||||
|
||||
|
||||
class WebIDLCodegenManagerState(dict):
|
||||
"""Holds state for the WebIDL code generation manager.
|
||||
|
||||
State is currently just an extended dict. The internal implementation of
|
||||
state should be considered a black box to everyone except
|
||||
WebIDLCodegenManager. But we'll still document it.
|
||||
|
||||
Fields:
|
||||
|
||||
version
|
||||
The integer version of the format. This is to detect incompatible
|
||||
changes between state. It should be bumped whenever the format
|
||||
changes or semantics change.
|
||||
|
||||
webidls
|
||||
A dictionary holding information about every known WebIDL input.
|
||||
Keys are the basenames of input WebIDL files. Values are dicts of
|
||||
metadata. Keys in those dicts are:
|
||||
|
||||
* filename - The full path to the input filename.
|
||||
* inputs - A set of full paths to other webidl files this webidl
|
||||
depends on.
|
||||
* outputs - Set of full output paths that are created/derived from
|
||||
this file.
|
||||
* sha1 - The hexidecimal SHA-1 of the input filename from the last
|
||||
processing time.
|
||||
|
||||
global_inputs
|
||||
A dictionary defining files that influence all processing. Keys
|
||||
are full filenames. Values are hexidecimal SHA-1 from the last
|
||||
processing time.
|
||||
"""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self, fh=None):
|
||||
self['version'] = self.VERSION
|
||||
self['webidls'] = {}
|
||||
self['global_depends'] = {}
|
||||
|
||||
if not fh:
|
||||
return
|
||||
|
||||
state = json.load(fh)
|
||||
if state['version'] != self.VERSION:
|
||||
raise Exception('Unknown state version: %s' % state['version'])
|
||||
|
||||
self['version'] = state['version']
|
||||
self['global_depends'] = state['global_depends']
|
||||
|
||||
for k, v in state['webidls'].items():
|
||||
self['webidls'][k] = v
|
||||
|
||||
# Sets are converted to lists for serialization because JSON
|
||||
# doesn't support sets.
|
||||
self['webidls'][k]['inputs'] = set(v['inputs'])
|
||||
self['webidls'][k]['outputs'] = set(v['outputs'])
|
||||
|
||||
def dump(self, fh):
|
||||
"""Dump serialized state to a file handle."""
|
||||
normalized = deepcopy(self)
|
||||
|
||||
for k, v in self['webidls'].items():
|
||||
# Convert sets to lists because JSON doesn't support sets.
|
||||
normalized['webidls'][k]['outputs'] = sorted(v['outputs'])
|
||||
normalized['webidls'][k]['inputs'] = sorted(v['inputs'])
|
||||
|
||||
json.dump(normalized, fh, sort_keys=True)
|
||||
|
||||
|
||||
class WebIDLCodegenManager(LoggingMixin):
|
||||
"""Manages all code generation around WebIDL.
|
||||
|
||||
To facilitate testing, this object is meant to be generic and reusable.
|
||||
Paths, etc should be parameters and not hardcoded.
|
||||
"""
|
||||
|
||||
# Global parser derived declaration files.
|
||||
GLOBAL_DECLARE_FILES = {
|
||||
'GeneratedAtomList.h',
|
||||
'PrototypeList.h',
|
||||
'RegisterBindings.h',
|
||||
'UnionConversions.h',
|
||||
'UnionTypes.h',
|
||||
}
|
||||
|
||||
# Global parser derived definition files.
|
||||
GLOBAL_DEFINE_FILES = {
|
||||
'RegisterBindings.cpp',
|
||||
'UnionTypes.cpp',
|
||||
}
|
||||
|
||||
# Example interfaces to build along with the tree. Other example
|
||||
# interfaces will need to be generated manually.
|
||||
BUILD_EXAMPLE_INTERFACES = {
|
||||
'TestExampleInterface',
|
||||
'TestExampleProxyInterface',
|
||||
}
|
||||
|
||||
def __init__(self, config_path, inputs, exported_header_dir,
|
||||
codegen_dir, state_path, cache_dir=None, make_deps_path=None,
|
||||
make_deps_target=None):
|
||||
"""Create an instance that manages WebIDLs in the build system.
|
||||
|
||||
config_path refers to a WebIDL config file (e.g. Bindings.conf).
|
||||
inputs is a 3-tuple describing the input .webidl files and how to
|
||||
process them. Members are:
|
||||
(set(.webidl files), set(basenames of exported files),
|
||||
set(basenames of generated events files))
|
||||
|
||||
exported_header_dir and codegen_dir are directories where generated
|
||||
files will be written to.
|
||||
state_path is the path to a file that will receive JSON state from our
|
||||
actions.
|
||||
make_deps_path is the path to a make dependency file that we can
|
||||
optionally write.
|
||||
make_deps_target is the target that receives the make dependencies. It
|
||||
must be defined if using make_deps_path.
|
||||
"""
|
||||
self.populate_logger()
|
||||
|
||||
input_paths, exported_stems, generated_events_stems = inputs
|
||||
|
||||
self._config_path = config_path
|
||||
self._input_paths = set(input_paths)
|
||||
self._exported_stems = set(exported_stems)
|
||||
self._generated_events_stems = set(generated_events_stems)
|
||||
self._exported_header_dir = exported_header_dir
|
||||
self._codegen_dir = codegen_dir
|
||||
self._state_path = state_path
|
||||
self._cache_dir = cache_dir
|
||||
self._make_deps_path = make_deps_path
|
||||
self._make_deps_target = make_deps_target
|
||||
|
||||
if (make_deps_path and not make_deps_target) or (not make_deps_path and
|
||||
make_deps_target):
|
||||
raise Exception('Must define both make_deps_path and make_deps_target '
|
||||
'if one is defined.')
|
||||
|
||||
self._parser_results = None
|
||||
self._config = None
|
||||
self._state = WebIDLCodegenManagerState()
|
||||
|
||||
if os.path.exists(state_path):
|
||||
with open(state_path, 'rb') as fh:
|
||||
try:
|
||||
self._state = WebIDLCodegenManagerState(fh=fh)
|
||||
except Exception as e:
|
||||
self.log(logging.WARN, 'webidl_bad_state', {'msg': str(e)},
|
||||
'Bad WebIDL state: {msg}')
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
if not self._config:
|
||||
self._parse_webidl()
|
||||
|
||||
return self._config
|
||||
|
||||
def generate_build_files(self):
|
||||
"""Generate files required for the build.
|
||||
|
||||
This function is in charge of generating all the .h/.cpp files derived
|
||||
from input .webidl files. Please note that there are build actions
|
||||
required to produce .webidl files and these build actions are
|
||||
explicitly not captured here: this function assumes all .webidl files
|
||||
are present and up to date.
|
||||
|
||||
This routine is called as part of the build to ensure files that need
|
||||
to exist are present and up to date. This routine may not be called if
|
||||
the build dependencies (generated as a result of calling this the first
|
||||
time) say everything is up to date.
|
||||
|
||||
Because reprocessing outputs for every .webidl on every invocation
|
||||
is expensive, we only regenerate the minimal set of files on every
|
||||
invocation. The rules for deciding what needs done are roughly as
|
||||
follows:
|
||||
|
||||
1. If any .webidl changes, reparse all .webidl files and regenerate
|
||||
the global derived files. Only regenerate output files (.h/.cpp)
|
||||
impacted by the modified .webidl files.
|
||||
2. If an non-.webidl dependency (Python files, config file) changes,
|
||||
assume everything is out of date and regenerate the world. This
|
||||
is because changes in those could globally impact every output
|
||||
file.
|
||||
3. If an output file is missing, ensure it is present by performing
|
||||
necessary regeneration.
|
||||
"""
|
||||
# Despite #1 above, we assume the build system is smart enough to not
|
||||
# invoke us if nothing has changed. Therefore, any invocation means
|
||||
# something has changed. And, if anything has changed, we need to
|
||||
# parse the WebIDL.
|
||||
self._parse_webidl()
|
||||
|
||||
result = BuildResult()
|
||||
|
||||
# If we parse, we always update globals - they are cheap and it is
|
||||
# easier that way.
|
||||
created, updated, unchanged = self._write_global_derived()
|
||||
result.created |= created
|
||||
result.updated |= updated
|
||||
result.unchanged |= unchanged
|
||||
|
||||
# If any of the extra dependencies changed, regenerate the world.
|
||||
global_changed, global_hashes = self._global_dependencies_changed()
|
||||
if global_changed:
|
||||
# Make a copy because we may modify.
|
||||
changed_inputs = set(self._input_paths)
|
||||
else:
|
||||
changed_inputs = self._compute_changed_inputs()
|
||||
|
||||
self._state['global_depends'] = global_hashes
|
||||
|
||||
# Generate bindings from .webidl files.
|
||||
for filename in sorted(changed_inputs):
|
||||
basename = mozpath.basename(filename)
|
||||
result.inputs.add(filename)
|
||||
written, deps = self._generate_build_files_for_webidl(filename)
|
||||
result.created |= written[0]
|
||||
result.updated |= written[1]
|
||||
result.unchanged |= written[2]
|
||||
|
||||
self._state['webidls'][basename] = dict(
|
||||
filename=filename,
|
||||
outputs=written[0] | written[1] | written[2],
|
||||
inputs=set(deps),
|
||||
sha1=self._input_hashes[filename],
|
||||
)
|
||||
|
||||
# Process some special interfaces required for testing.
|
||||
for interface in self.BUILD_EXAMPLE_INTERFACES:
|
||||
written = self.generate_example_files(interface)
|
||||
result.created |= written[0]
|
||||
result.updated |= written[1]
|
||||
result.unchanged |= written[2]
|
||||
|
||||
# Generate a make dependency file.
|
||||
if self._make_deps_path:
|
||||
mk = Makefile()
|
||||
codegen_rule = mk.create_rule([self._make_deps_target])
|
||||
codegen_rule.add_dependencies(global_hashes.keys())
|
||||
codegen_rule.add_dependencies(self._input_paths)
|
||||
|
||||
with FileAvoidWrite(self._make_deps_path) as fh:
|
||||
mk.dump(fh)
|
||||
|
||||
self._save_state()
|
||||
|
||||
return result
|
||||
|
||||
def generate_example_files(self, interface):
|
||||
"""Generates example files for a given interface."""
|
||||
root = CGExampleRoot(self.config, interface)
|
||||
|
||||
return self._maybe_write_codegen(root, *self._example_paths(interface))
|
||||
|
||||
def _parse_webidl(self):
|
||||
self.log(logging.INFO, 'webidl_parse',
|
||||
{'count': len(self._input_paths)},
|
||||
'Parsing {count} WebIDL files.')
|
||||
|
||||
hashes = {}
|
||||
parser = WebIDL.Parser(self._cache_dir)
|
||||
|
||||
for path in sorted(self._input_paths):
|
||||
with open(path, 'rb') as fh:
|
||||
data = fh.read()
|
||||
hashes[path] = hashlib.sha1(data).hexdigest()
|
||||
parser.parse(data, path)
|
||||
|
||||
self._parser_results = parser.finish()
|
||||
self._config = Configuration(self._config_path, self._parser_results)
|
||||
self._input_hashes = hashes
|
||||
|
||||
def _write_global_derived(self):
|
||||
things = [('declare', f) for f in self.GLOBAL_DECLARE_FILES]
|
||||
things.extend(('define', f) for f in self.GLOBAL_DEFINE_FILES)
|
||||
|
||||
result = (set(), set(), set())
|
||||
|
||||
for what, filename in things:
|
||||
stem = mozpath.splitext(filename)[0]
|
||||
root = getattr(GlobalGenRoots, stem)(self._config)
|
||||
|
||||
if what == 'declare':
|
||||
code = root.declare()
|
||||
output_root = self._exported_header_dir
|
||||
elif what == 'define':
|
||||
code = root.define()
|
||||
output_root = self._codegen_dir
|
||||
else:
|
||||
raise Exception('Unknown global gen type: %s' % what)
|
||||
|
||||
output_path = mozpath.join(output_root, filename)
|
||||
self._maybe_write_file(output_path, code, result)
|
||||
|
||||
return result
|
||||
|
||||
def _compute_changed_inputs(self):
|
||||
"""Compute the set of input files that need to be regenerated."""
|
||||
changed_inputs = set()
|
||||
expected_outputs = self.expected_build_output_files()
|
||||
|
||||
# Look for missing output files.
|
||||
if any(not os.path.exists(f) for f in expected_outputs):
|
||||
# FUTURE Bug 940469 Only regenerate minimum set.
|
||||
changed_inputs |= self._input_paths
|
||||
|
||||
# That's it for examining output files. We /could/ examine SHA-1's of
|
||||
# output files from a previous run to detect modifications. But that's
|
||||
# a lot of extra work and most build systems don't do that anyway.
|
||||
|
||||
# Now we move on to the input files.
|
||||
old_hashes = {v['filename']: v['sha1']
|
||||
for v in self._state['webidls'].values()}
|
||||
|
||||
old_filenames = set(old_hashes.keys())
|
||||
new_filenames = self._input_paths
|
||||
|
||||
# If an old file has disappeared or a new file has arrived, mark
|
||||
# it.
|
||||
changed_inputs |= old_filenames ^ new_filenames
|
||||
|
||||
# For the files in common between runs, compare content. If the file
|
||||
# has changed, mark it. We don't need to perform mtime comparisons
|
||||
# because content is a stronger validator.
|
||||
for filename in old_filenames & new_filenames:
|
||||
if old_hashes[filename] != self._input_hashes[filename]:
|
||||
changed_inputs.add(filename)
|
||||
|
||||
# We've now populated the base set of inputs that have changed.
|
||||
|
||||
# Inherit dependencies from previous run. The full set of dependencies
|
||||
# is associated with each record, so we don't need to perform any fancy
|
||||
# graph traversal.
|
||||
for v in self._state['webidls'].values():
|
||||
if any(dep for dep in v['inputs'] if dep in changed_inputs):
|
||||
changed_inputs.add(v['filename'])
|
||||
|
||||
# Ensure all changed inputs actually exist (some changed inputs could
|
||||
# have been from deleted files).
|
||||
return set(f for f in changed_inputs if os.path.exists(f))
|
||||
|
||||
def _binding_info(self, p):
|
||||
"""Compute binding metadata for an input path.
|
||||
|
||||
Returns a tuple of:
|
||||
|
||||
(stem, binding_stem, is_event, output_files)
|
||||
|
||||
output_files is itself a tuple. The first two items are the binding
|
||||
header and C++ paths, respectively. The 2nd pair are the event header
|
||||
and C++ paths or None if this isn't an event binding.
|
||||
"""
|
||||
basename = mozpath.basename(p)
|
||||
stem = mozpath.splitext(basename)[0]
|
||||
binding_stem = '%sBinding' % stem
|
||||
|
||||
if stem in self._exported_stems:
|
||||
header_dir = self._exported_header_dir
|
||||
else:
|
||||
header_dir = self._codegen_dir
|
||||
|
||||
is_event = stem in self._generated_events_stems
|
||||
|
||||
files = (
|
||||
mozpath.join(header_dir, '%s.h' % binding_stem),
|
||||
mozpath.join(self._codegen_dir, '%s.cpp' % binding_stem),
|
||||
mozpath.join(header_dir, '%s.h' % stem) if is_event else None,
|
||||
mozpath.join(self._codegen_dir, '%s.cpp' % stem) if is_event else None,
|
||||
)
|
||||
|
||||
return stem, binding_stem, is_event, header_dir, files
|
||||
|
||||
def _example_paths(self, interface):
|
||||
return (
|
||||
mozpath.join(self._codegen_dir, '%s-example.h' % interface),
|
||||
mozpath.join(self._codegen_dir, '%s-example.cpp' % interface))
|
||||
|
||||
def expected_build_output_files(self):
|
||||
"""Obtain the set of files generate_build_files() should write."""
|
||||
paths = set()
|
||||
|
||||
# Account for global generation.
|
||||
for p in self.GLOBAL_DECLARE_FILES:
|
||||
paths.add(mozpath.join(self._exported_header_dir, p))
|
||||
for p in self.GLOBAL_DEFINE_FILES:
|
||||
paths.add(mozpath.join(self._codegen_dir, p))
|
||||
|
||||
for p in self._input_paths:
|
||||
stem, binding_stem, is_event, header_dir, files = self._binding_info(p)
|
||||
paths |= {f for f in files if f}
|
||||
|
||||
for interface in self.BUILD_EXAMPLE_INTERFACES:
|
||||
for p in self._example_paths(interface):
|
||||
paths.add(p)
|
||||
|
||||
return paths
|
||||
|
||||
def _generate_build_files_for_webidl(self, filename):
|
||||
self.log(logging.INFO, 'webidl_generate_build_for_input',
|
||||
{'filename': filename},
|
||||
'Generating WebIDL files derived from {filename}')
|
||||
|
||||
stem, binding_stem, is_event, header_dir, files = self._binding_info(filename)
|
||||
root = CGBindingRoot(self._config, binding_stem, filename)
|
||||
|
||||
result = self._maybe_write_codegen(root, files[0], files[1])
|
||||
|
||||
if is_event:
|
||||
generated_event = CGEventRoot(self._config, stem)
|
||||
result = self._maybe_write_codegen(generated_event, files[2],
|
||||
files[3], result)
|
||||
|
||||
return result, root.deps()
|
||||
|
||||
def _global_dependencies_changed(self):
|
||||
"""Determine whether the global dependencies have changed."""
|
||||
current_files = set(iter_modules_in_path(mozpath.dirname(__file__)))
|
||||
|
||||
# We need to catch other .py files from /dom/bindings. We assume these
|
||||
# are in the same directory as the config file.
|
||||
current_files |= set(iter_modules_in_path(mozpath.dirname(self._config_path)))
|
||||
|
||||
current_files.add(self._config_path)
|
||||
|
||||
current_hashes = {}
|
||||
for f in current_files:
|
||||
# This will fail if the file doesn't exist. If a current global
|
||||
# dependency doesn't exist, something else is wrong.
|
||||
with open(f, 'rb') as fh:
|
||||
current_hashes[f] = hashlib.sha1(fh.read()).hexdigest()
|
||||
|
||||
# The set of files has changed.
|
||||
if current_files ^ set(self._state['global_depends'].keys()):
|
||||
return True, current_hashes
|
||||
|
||||
# Compare hashes.
|
||||
for f, sha1 in current_hashes.items():
|
||||
if sha1 != self._state['global_depends'][f]:
|
||||
return True, current_hashes
|
||||
|
||||
return False, current_hashes
|
||||
|
||||
def _save_state(self):
|
||||
with open(self._state_path, 'wb') as fh:
|
||||
self._state.dump(fh)
|
||||
|
||||
def _maybe_write_codegen(self, obj, declare_path, define_path, result=None):
|
||||
assert declare_path and define_path
|
||||
if not result:
|
||||
result = (set(), set(), set())
|
||||
|
||||
self._maybe_write_file(declare_path, obj.declare(), result)
|
||||
self._maybe_write_file(define_path, obj.define(), result)
|
||||
|
||||
return result
|
||||
|
||||
def _maybe_write_file(self, path, content, result):
|
||||
fh = FileAvoidWrite(path)
|
||||
fh.write(content)
|
||||
existed, updated = fh.close()
|
||||
|
||||
if not existed:
|
||||
result[0].add(path)
|
||||
elif updated:
|
||||
result[1].add(path)
|
||||
else:
|
||||
result[2].add(path)
|
||||
|
||||
|
||||
def create_build_system_manager(topsrcdir, topobjdir, dist_dir):
|
||||
"""Create a WebIDLCodegenManager for use by the build system."""
|
||||
src_dir = os.path.join(topsrcdir, 'dom', 'bindings')
|
||||
obj_dir = os.path.join(topobjdir, 'dom', 'bindings')
|
||||
|
||||
with open(os.path.join(obj_dir, 'file-lists.json'), 'rb') as fh:
|
||||
files = json.load(fh)
|
||||
|
||||
inputs = (files['webidls'], files['exported_stems'],
|
||||
files['generated_events_stems'])
|
||||
|
||||
cache_dir = os.path.join(obj_dir, '_cache')
|
||||
try:
|
||||
os.makedirs(cache_dir)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
return WebIDLCodegenManager(
|
||||
os.path.join(src_dir, 'Bindings.conf'),
|
||||
inputs,
|
||||
os.path.join(dist_dir, 'include', 'mozilla', 'dom'),
|
||||
obj_dir,
|
||||
os.path.join(obj_dir, 'codegen.json'),
|
||||
cache_dir=cache_dir,
|
||||
# The make rules include a codegen.pp file containing dependencies.
|
||||
make_deps_path=os.path.join(obj_dir, 'codegen.pp'),
|
||||
make_deps_target='codegen.pp',
|
||||
)
|
||||
|
||||
|
||||
class BuildSystemWebIDL(MozbuildObject):
|
||||
@property
|
||||
def manager(self):
|
||||
if not hasattr(self, '_webidl_manager'):
|
||||
self._webidl_manager = create_build_system_manager(
|
||||
self.topsrcdir, self.topobjdir, self.distdir)
|
||||
|
||||
return self._webidl_manager
|
|
@ -0,0 +1,3 @@
|
|||
interface Child : Parent {
|
||||
void ChildBaz();
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
interface DummyInterface {};
|
||||
interface DummyInterfaceWorkers {};
|
|
@ -0,0 +1,3 @@
|
|||
/* These interfaces are hard-coded and need to be defined. */
|
||||
interface TestExampleInterface {};
|
||||
interface TestExampleProxyInterface {};
|
|
@ -0,0 +1,3 @@
|
|||
interface Parent {
|
||||
void MethodFoo();
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
interface EventTarget {
|
||||
void addEventListener();
|
||||
};
|
||||
|
||||
interface Event {};
|
||||
|
||||
callback EventHandlerNonNull = any (Event event);
|
||||
typedef EventHandlerNonNull? EventHandler;
|
||||
|
||||
[NoInterfaceObject]
|
||||
interface TestEvent : EventTarget {
|
||||
attribute EventHandler onfoo;
|
||||
};
|
|
@ -0,0 +1,278 @@
|
|||
# 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/.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import imp
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import mozpack.path as mozpath
|
||||
|
||||
from mozwebidlcodegen import (
|
||||
WebIDLCodegenManager,
|
||||
WebIDLCodegenManagerState,
|
||||
)
|
||||
|
||||
from mozfile import NamedTemporaryFile
|
||||
|
||||
from mozunit import (
|
||||
MockedOpen,
|
||||
main,
|
||||
)
|
||||
|
||||
|
||||
OUR_DIR = mozpath.abspath(mozpath.dirname(__file__))
|
||||
TOPSRCDIR = mozpath.normpath(mozpath.join(OUR_DIR, '..', '..', '..', '..'))
|
||||
|
||||
|
||||
class TestWebIDLCodegenManager(unittest.TestCase):
|
||||
TEST_STEMS = {
|
||||
'Child',
|
||||
'Parent',
|
||||
'ExampleBinding',
|
||||
'TestEvent',
|
||||
}
|
||||
|
||||
@property
|
||||
def _static_input_paths(self):
|
||||
s = {mozpath.join(OUR_DIR, p) for p in os.listdir(OUR_DIR)
|
||||
if p.endswith('.webidl')}
|
||||
|
||||
return s
|
||||
|
||||
@property
|
||||
def _config_path(self):
|
||||
config = mozpath.join(TOPSRCDIR, 'dom', 'bindings', 'Bindings.conf')
|
||||
self.assertTrue(os.path.exists(config))
|
||||
|
||||
return config
|
||||
|
||||
def _get_manager_args(self):
|
||||
tmp = tempfile.mkdtemp()
|
||||
self.addCleanup(shutil.rmtree, tmp)
|
||||
|
||||
cache_dir = mozpath.join(tmp, 'cache')
|
||||
os.mkdir(cache_dir)
|
||||
|
||||
ip = self._static_input_paths
|
||||
|
||||
inputs = (
|
||||
ip,
|
||||
{mozpath.splitext(mozpath.basename(p))[0] for p in ip},
|
||||
set()
|
||||
)
|
||||
|
||||
return dict(
|
||||
config_path=self._config_path,
|
||||
inputs=inputs,
|
||||
exported_header_dir=mozpath.join(tmp, 'exports'),
|
||||
codegen_dir=mozpath.join(tmp, 'codegen'),
|
||||
state_path=mozpath.join(tmp, 'state.json'),
|
||||
make_deps_path=mozpath.join(tmp, 'codegen.pp'),
|
||||
make_deps_target='codegen.pp',
|
||||
cache_dir=cache_dir,
|
||||
)
|
||||
|
||||
def _get_manager(self):
|
||||
return WebIDLCodegenManager(**self._get_manager_args())
|
||||
|
||||
def test_unknown_state_version(self):
|
||||
"""Loading a state file with a too new version resets state."""
|
||||
args = self._get_manager_args()
|
||||
|
||||
p = args['state_path']
|
||||
|
||||
with open(p, 'wb') as fh:
|
||||
json.dump({
|
||||
'version': WebIDLCodegenManagerState.VERSION + 1,
|
||||
'foobar': '1',
|
||||
}, fh)
|
||||
|
||||
manager = WebIDLCodegenManager(**args)
|
||||
|
||||
self.assertEqual(manager._state['version'],
|
||||
WebIDLCodegenManagerState.VERSION)
|
||||
self.assertNotIn('foobar', manager._state)
|
||||
|
||||
def test_generate_build_files(self):
|
||||
"""generate_build_files() does the right thing from empty."""
|
||||
manager = self._get_manager()
|
||||
result = manager.generate_build_files()
|
||||
self.assertEqual(len(result.inputs), 5)
|
||||
|
||||
output = manager.expected_build_output_files()
|
||||
self.assertEqual(result.created, output)
|
||||
self.assertEqual(len(result.updated), 0)
|
||||
self.assertEqual(len(result.unchanged), 0)
|
||||
|
||||
for f in output:
|
||||
self.assertTrue(os.path.isfile(f))
|
||||
|
||||
for f in manager.GLOBAL_DECLARE_FILES:
|
||||
self.assertIn(mozpath.join(manager._exported_header_dir, f), output)
|
||||
|
||||
for f in manager.GLOBAL_DEFINE_FILES:
|
||||
self.assertIn(mozpath.join(manager._codegen_dir, f), output)
|
||||
|
||||
for s in self.TEST_STEMS:
|
||||
self.assertTrue(os.path.isfile(mozpath.join(
|
||||
manager._exported_header_dir, '%sBinding.h' % s)))
|
||||
self.assertTrue(os.path.isfile(mozpath.join(
|
||||
manager._codegen_dir, '%sBinding.cpp' % s)))
|
||||
|
||||
self.assertTrue(os.path.isfile(manager._state_path))
|
||||
|
||||
with open(manager._state_path, 'rb') as fh:
|
||||
state = json.load(fh)
|
||||
self.assertEqual(state['version'], 1)
|
||||
self.assertIn('webidls', state)
|
||||
|
||||
child = state['webidls']['Child.webidl']
|
||||
self.assertEqual(len(child['inputs']), 2)
|
||||
self.assertEqual(len(child['outputs']), 2)
|
||||
self.assertEqual(child['sha1'], 'c41527cad3bc161fa6e7909e48fa11f9eca0468b')
|
||||
|
||||
def test_generate_build_files_load_state(self):
|
||||
"""State should be equivalent when instantiating a new instance."""
|
||||
args = self._get_manager_args()
|
||||
m1 = WebIDLCodegenManager(**args)
|
||||
self.assertEqual(len(m1._state['webidls']), 0)
|
||||
m1.generate_build_files()
|
||||
|
||||
m2 = WebIDLCodegenManager(**args)
|
||||
self.assertGreater(len(m2._state['webidls']), 2)
|
||||
self.assertEqual(m1._state, m2._state)
|
||||
|
||||
def test_no_change_no_writes(self):
|
||||
"""If nothing changes, no files should be updated."""
|
||||
args = self._get_manager_args()
|
||||
m1 = WebIDLCodegenManager(**args)
|
||||
m1.generate_build_files()
|
||||
|
||||
m2 = WebIDLCodegenManager(**args)
|
||||
result = m2.generate_build_files()
|
||||
|
||||
self.assertEqual(len(result.inputs), 0)
|
||||
self.assertEqual(len(result.created), 0)
|
||||
self.assertEqual(len(result.updated), 0)
|
||||
|
||||
def test_output_file_regenerated(self):
|
||||
"""If an output file disappears, it is regenerated."""
|
||||
args = self._get_manager_args()
|
||||
m1 = WebIDLCodegenManager(**args)
|
||||
m1.generate_build_files()
|
||||
|
||||
rm_count = 0
|
||||
for p in m1._state['webidls']['Child.webidl']['outputs']:
|
||||
rm_count += 1
|
||||
os.unlink(p)
|
||||
|
||||
for p in m1.GLOBAL_DECLARE_FILES:
|
||||
rm_count += 1
|
||||
os.unlink(mozpath.join(m1._exported_header_dir, p))
|
||||
|
||||
m2 = WebIDLCodegenManager(**args)
|
||||
result = m2.generate_build_files()
|
||||
self.assertEqual(len(result.created), rm_count)
|
||||
|
||||
def test_only_rebuild_self(self):
|
||||
"""If an input file changes, only rebuild that one file."""
|
||||
args = self._get_manager_args()
|
||||
m1 = WebIDLCodegenManager(**args)
|
||||
m1.generate_build_files()
|
||||
|
||||
child_path = None
|
||||
for p in m1._input_paths:
|
||||
if p.endswith('Child.webidl'):
|
||||
child_path = p
|
||||
break
|
||||
|
||||
self.assertIsNotNone(child_path)
|
||||
child_content = open(child_path, 'rb').read()
|
||||
|
||||
with MockedOpen({child_path: child_content + '\n/* */'}):
|
||||
m2 = WebIDLCodegenManager(**args)
|
||||
result = m2.generate_build_files()
|
||||
self.assertEqual(result.inputs, set([child_path]))
|
||||
self.assertEqual(len(result.updated), 0)
|
||||
self.assertEqual(len(result.created), 0)
|
||||
|
||||
def test_rebuild_dependencies(self):
|
||||
"""Ensure an input file used by others results in others rebuilding."""
|
||||
args = self._get_manager_args()
|
||||
m1 = WebIDLCodegenManager(**args)
|
||||
m1.generate_build_files()
|
||||
|
||||
parent_path = None
|
||||
child_path = None
|
||||
for p in m1._input_paths:
|
||||
if p.endswith('Parent.webidl'):
|
||||
parent_path = p
|
||||
elif p.endswith('Child.webidl'):
|
||||
child_path = p
|
||||
|
||||
self.assertIsNotNone(parent_path)
|
||||
parent_content = open(parent_path, 'rb').read()
|
||||
|
||||
with MockedOpen({parent_path: parent_content + '\n/* */'}):
|
||||
m2 = WebIDLCodegenManager(**args)
|
||||
result = m2.generate_build_files()
|
||||
self.assertEqual(result.inputs, {child_path, parent_path})
|
||||
self.assertEqual(len(result.updated), 0)
|
||||
self.assertEqual(len(result.created), 0)
|
||||
|
||||
def test_python_change_regenerate_everything(self):
|
||||
"""If a Python file changes, we should attempt to rebuild everything."""
|
||||
|
||||
# We don't want to mutate files in the source directory because we want
|
||||
# to be able to build from a read-only filesystem. So, we install a
|
||||
# dummy module and rewrite the metadata to say it comes from the source
|
||||
# directory.
|
||||
#
|
||||
# Hacking imp to accept a MockedFile doesn't appear possible. So for
|
||||
# the first iteration we read from a temp file. The second iteration
|
||||
# doesn't need to import, so we are fine with a mocked file.
|
||||
fake_path = mozpath.join(OUR_DIR, 'fakemodule.py')
|
||||
with NamedTemporaryFile('wt') as fh:
|
||||
fh.write('# Original content')
|
||||
fh.flush()
|
||||
mod = imp.load_source('mozwebidlcodegen.fakemodule', fh.name)
|
||||
mod.__file__ = fake_path
|
||||
|
||||
args = self._get_manager_args()
|
||||
m1 = WebIDLCodegenManager(**args)
|
||||
with MockedOpen({fake_path: '# Original content'}):
|
||||
old_exists = os.path.exists
|
||||
try:
|
||||
def exists(p):
|
||||
if p == fake_path:
|
||||
return True
|
||||
return old_exists(p)
|
||||
|
||||
os.path.exists = exists
|
||||
|
||||
result = m1.generate_build_files()
|
||||
l = len(result.inputs)
|
||||
|
||||
with open(fake_path, 'wt') as fh:
|
||||
fh.write('# Modified content')
|
||||
|
||||
m2 = WebIDLCodegenManager(**args)
|
||||
result = m2.generate_build_files()
|
||||
self.assertEqual(len(result.inputs), l)
|
||||
|
||||
result = m2.generate_build_files()
|
||||
self.assertEqual(len(result.inputs), 0)
|
||||
finally:
|
||||
os.path.exists = old_exists
|
||||
del sys.modules['mozwebidlcodegen.fakemodule']
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -2,89 +2,23 @@
|
|||
# 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/.
|
||||
|
||||
# Do NOT export this library. We don't actually want our test code
|
||||
# being added to libxul or anything.
|
||||
|
||||
# pymake can't handle descending into dom/bindings several times simultaneously
|
||||
ifdef .PYMAKE
|
||||
.NOTPARALLEL:
|
||||
endif
|
||||
|
||||
# Need this for $(test_webidl_files)
|
||||
include ../webidlsrcs.mk
|
||||
|
||||
# But the webidl actually lives in our parent dir
|
||||
test_webidl_files := $(addprefix ../,$(test_webidl_files))
|
||||
# Store the actual locations of our source preprocessed files, so we
|
||||
# can depend on them sanely.
|
||||
source_preprocessed_test_webidl_files := $(addprefix $(srcdir)/,$(preprocessed_test_webidl_files))
|
||||
preprocessed_test_webidl_files := $(addprefix ../,$(preprocessed_test_webidl_files))
|
||||
|
||||
CPPSRCS += \
|
||||
$(subst .webidl,Binding.cpp,$(test_webidl_files)) \
|
||||
$(subst .webidl,Binding.cpp,$(preprocessed_test_webidl_files)) \
|
||||
$(NULL)
|
||||
|
||||
# If you change bindinggen_dependencies here, change it in
|
||||
# dom/bindings/Makefile.in too. But note that we include ../Makefile
|
||||
# here manually, since $(GLOBAL_DEPS) won't cover it.
|
||||
bindinggen_dependencies := \
|
||||
../BindingGen.py \
|
||||
../Bindings.conf \
|
||||
../Configuration.py \
|
||||
../Codegen.py \
|
||||
../ParserResults.pkl \
|
||||
../parser/WebIDL.py \
|
||||
../Makefile \
|
||||
$(GLOBAL_DEPS) \
|
||||
$(NULL)
|
||||
# $(test_sources) comes from webidlsrcs.mk.
|
||||
# TODO Update this variable in backend.mk.
|
||||
CPPSRCS += $(addprefix ../,$(test_sources))
|
||||
|
||||
ifdef GNU_CC
|
||||
CXXFLAGS += -Wno-uninitialized
|
||||
OS_CXXFLAGS += -Wno-uninitialized
|
||||
endif
|
||||
|
||||
# Bug 932082 tracks having bindings use namespaced includes.
|
||||
LOCAL_INCLUDES += -I$(DIST)/include/mozilla/dom -I..
|
||||
|
||||
# Include rules.mk before any of our targets so our first target is coming from
|
||||
# rules.mk and running make with no target in this dir does the right thing.
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
$(CPPSRCS): .BindingGen
|
||||
|
||||
.BindingGen: $(bindinggen_dependencies) \
|
||||
$(test_webidl_files) \
|
||||
$(source_preprocessed_test_webidl_files) \
|
||||
$(NULL)
|
||||
# The export phase in dom/bindings is what actually looks at
|
||||
# dependencies and regenerates things as needed, so just go ahead and
|
||||
# make that phase here. Also make our example interface files. If the
|
||||
# target used here ever changes, change the conditional around
|
||||
# $(CPPSRCS) in dom/bindings/Makefile.in.
|
||||
$(MAKE) -C .. export TestExampleInterface-example TestExampleProxyInterface-example
|
||||
@$(TOUCH) $@
|
||||
|
||||
check::
|
||||
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
|
||||
$(PLY_INCLUDE) $(srcdir)/../parser/runtests.py
|
||||
|
||||
# Since we define MOCHITEST_FILES, config/makefiles/mochitest.mk goes ahead and
|
||||
# sets up a rule with libs:: in itm which makes our .DEFAULT_TARGET be "libs".
|
||||
# Then ruls.mk does |.DEFAULT_TARGET ?= default| which leaves it as "libs". So
|
||||
# if we make without an explicit target in this directory, we try to make
|
||||
# "libs", but with a $(MAKECMDGOALS) of empty string. And then rules.mk
|
||||
# helpfully does not include our *.o.pp files, since it includes them only if
|
||||
# filtering some stuff out from $(MAKECMDGOALS) leaves it nonempty. The upshot
|
||||
# is that if some headers change and we run make in this dir without an explicit
|
||||
# target things don't get rebuilt.
|
||||
#
|
||||
# On the other hand, if we set .DEFAULT_TARGET to "default" explicitly here,
|
||||
# then rules.mk will reinvoke make with "export" and "libs" but this time hey
|
||||
# will be passed as explicit targets, show up in $(MAKECMDGOALS), and things
|
||||
# will work. Do this at the end of our Makefile so the rest of the build system
|
||||
# does not get a chance to muck with it after we set it.
|
||||
.DEFAULT_GOAL := default
|
||||
|
||||
# Make sure to add .BindingGen to GARBAGE so we'll rebuild our example
|
||||
# files if someone goes through and deletes GARBAGE all over, which
|
||||
# will delete example files from our parent dir.
|
||||
GARBAGE += \
|
||||
.BindingGen \
|
||||
$(NULL)
|
||||
|
|
|
@ -14,9 +14,20 @@ MOCHITEST_MANIFESTS += ['mochitest.ini']
|
|||
|
||||
MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
|
||||
|
||||
TEST_WEBIDL_FILES += [
|
||||
'TestDictionary.webidl',
|
||||
'TestJSImplInheritanceGen.webidl',
|
||||
'TestTypedef.webidl',
|
||||
]
|
||||
|
||||
PREPROCESSED_TEST_WEBIDL_FILES += [
|
||||
'TestCodeGen.webidl',
|
||||
'TestExampleGen.webidl',
|
||||
'TestJSImplGen.webidl',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/dom/bindings',
|
||||
'/js/xpconnect/src',
|
||||
'/js/xpconnect/wrappers',
|
||||
]
|
||||
|
||||
|
|
|
@ -65,8 +65,8 @@ BrowserElementParentFactory.prototype = {
|
|||
// alive for as long as its frame element lives.
|
||||
this._bepMap = new WeakMap();
|
||||
|
||||
Services.obs.addObserver(this, 'remote-browser-frame-shown', /* ownsWeak = */ true);
|
||||
Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', /* ownsWeak = */ true);
|
||||
Services.obs.addObserver(this, 'remote-browser-shown', /* ownsWeak = */ true);
|
||||
Services.obs.addObserver(this, 'inprocess-browser-shown', /* ownsWeak = */ true);
|
||||
},
|
||||
|
||||
_browserFramesPrefEnabled: function() {
|
||||
|
@ -79,11 +79,19 @@ BrowserElementParentFactory.prototype = {
|
|||
},
|
||||
|
||||
_observeInProcessBrowserFrameShown: function(frameLoader) {
|
||||
// Ignore notifications that aren't from a BrowserOrApp
|
||||
if (!frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsBrowserOrAppFrame) {
|
||||
return;
|
||||
}
|
||||
debug("In-process browser frame shown " + frameLoader);
|
||||
this._createBrowserElementParent(frameLoader, /* hasRemoteFrame = */ false);
|
||||
},
|
||||
|
||||
_observeRemoteBrowserFrameShown: function(frameLoader) {
|
||||
// Ignore notifications that aren't from a BrowserOrApp
|
||||
if (!frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsBrowserOrAppFrame) {
|
||||
return;
|
||||
}
|
||||
debug("Remote browser frame shown " + frameLoader);
|
||||
this._createBrowserElementParent(frameLoader, /* hasRemoteFrame = */ true);
|
||||
},
|
||||
|
@ -103,10 +111,10 @@ BrowserElementParentFactory.prototype = {
|
|||
this._init();
|
||||
}
|
||||
break;
|
||||
case 'remote-browser-frame-shown':
|
||||
case 'remote-browser-shown':
|
||||
this._observeRemoteBrowserFrameShown(subject);
|
||||
break;
|
||||
case 'in-process-browser-or-app-frame-shown':
|
||||
case 'inprocess-browser-shown':
|
||||
this._observeInProcessBrowserFrameShown(subject);
|
||||
break;
|
||||
case 'content-document-global-created':
|
||||
|
|
|
@ -44,8 +44,8 @@ this.Keyboard = {
|
|||
},
|
||||
|
||||
init: function keyboardInit() {
|
||||
Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', false);
|
||||
Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
|
||||
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
|
||||
Services.obs.addObserver(this, 'remote-browser-shown', false);
|
||||
Services.obs.addObserver(this, 'oop-frameloader-crashed', false);
|
||||
|
||||
for (let name of this._messageNames)
|
||||
|
@ -63,6 +63,10 @@ this.Keyboard = {
|
|||
ppmm.broadcastAsyncMessage('Keyboard:FocusChange', { 'type': 'blur' });
|
||||
}
|
||||
} else {
|
||||
// Ignore notifications that aren't from a BrowserOrApp
|
||||
if (!frameLoader.ownerIsBrowserOrAppFrame) {
|
||||
return;
|
||||
}
|
||||
this.initFormsFrameScript(mm);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -602,7 +602,7 @@ ParticularProcessPriorityManager::Init()
|
|||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||
if (os) {
|
||||
os->AddObserver(this, "audio-channel-process-changed", /* ownsWeak */ true);
|
||||
os->AddObserver(this, "remote-browser-frame-shown", /* ownsWeak */ true);
|
||||
os->AddObserver(this, "remote-browser-shown", /* ownsWeak */ true);
|
||||
os->AddObserver(this, "ipc:browser-destroyed", /* ownsWeak */ true);
|
||||
os->AddObserver(this, "frameloader-visible-changed", /* ownsWeak */ true);
|
||||
}
|
||||
|
@ -674,7 +674,7 @@ ParticularProcessPriorityManager::Observe(nsISupports* aSubject,
|
|||
|
||||
if (topic.EqualsLiteral("audio-channel-process-changed")) {
|
||||
OnAudioChannelProcessChanged(aSubject);
|
||||
} else if (topic.EqualsLiteral("remote-browser-frame-shown")) {
|
||||
} else if (topic.EqualsLiteral("remote-browser-shown")) {
|
||||
OnRemoteBrowserFrameShown(aSubject);
|
||||
} else if (topic.EqualsLiteral("ipc:browser-destroyed")) {
|
||||
OnTabParentDestroyed(aSubject);
|
||||
|
@ -747,6 +747,13 @@ ParticularProcessPriorityManager::OnRemoteBrowserFrameShown(nsISupports* aSubjec
|
|||
nsCOMPtr<nsIFrameLoader> fl = do_QueryInterface(aSubject);
|
||||
NS_ENSURE_TRUE_VOID(fl);
|
||||
|
||||
// Ignore notifications that aren't from a BrowserOrApp
|
||||
bool isBrowserOrApp;
|
||||
fl->GetOwnerIsBrowserOrAppFrame(&isBrowserOrApp);
|
||||
if (!isBrowserOrApp) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsITabParent> tp;
|
||||
fl->GetTabParent(getter_AddRefs(tp));
|
||||
NS_ENSURE_TRUE_VOID(tp);
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
#include "APZCCallbackHelper.h"
|
||||
#include "nsILoadContext.h"
|
||||
#include "ipc/nsGUIEventIPC.h"
|
||||
#include "gfxPlatform.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
#include "PCOMContentPermissionRequestChild.h"
|
||||
|
@ -2281,6 +2282,10 @@ TabChild::InitRenderingState()
|
|||
NS_WARNING("failed to properly allocate layer transaction");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Track which compositor backend is in use (see bug 947038). This can
|
||||
// be removed when deprecated textures are removed.
|
||||
gfxPlatform::GetPlatform()->SetCompositorBackend(mTextureFactoryIdentifier.mParentBackend);
|
||||
} else {
|
||||
// Pushing transactions to the parent content.
|
||||
shadowManager = remoteFrame->SendPLayerTransactionConstructor();
|
||||
|
|
|
@ -107,12 +107,9 @@ if CONFIG['MOZ_NFC']:
|
|||
if CONFIG['MOZ_B2G']:
|
||||
PARALLEL_DIRS += ['downloads']
|
||||
|
||||
# bindings/test is here, because it needs to build after bindings/, and
|
||||
# we build subdirectories before ourselves.
|
||||
TEST_DIRS += [
|
||||
'tests',
|
||||
'imptests',
|
||||
'bindings/test',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'cocoa', 'windows', 'android', 'qt', 'os2'):
|
||||
|
|
|
@ -553,18 +553,6 @@ if CONFIG['MOZ_B2G_FM']:
|
|||
'FMRadio.webidl',
|
||||
]
|
||||
|
||||
if CONFIG['ENABLE_TESTS']:
|
||||
TEST_WEBIDL_FILES += [
|
||||
'TestDictionary.webidl',
|
||||
'TestJSImplInheritanceGen.webidl',
|
||||
'TestTypedef.webidl',
|
||||
]
|
||||
PREPROCESSED_TEST_WEBIDL_FILES += [
|
||||
'TestCodeGen.webidl',
|
||||
'TestExampleGen.webidl',
|
||||
'TestJSImplGen.webidl',
|
||||
]
|
||||
|
||||
GENERATED_EVENTS_WEBIDL_FILES = [
|
||||
'BlobEvent.webidl',
|
||||
'CallGroupErrorEvent.webidl',
|
||||
|
|
|
@ -63,161 +63,6 @@ public:
|
|||
RefPtr<gfx::DataSourceSurface> mSurface;
|
||||
};
|
||||
|
||||
/**
|
||||
* Texture source and host implementaion for software compositing.
|
||||
*/
|
||||
class DeprecatedTextureHostBasic : public DeprecatedTextureHost
|
||||
, public TextureSourceBasic
|
||||
{
|
||||
public:
|
||||
DeprecatedTextureHostBasic()
|
||||
: mCompositor(nullptr)
|
||||
{}
|
||||
|
||||
SurfaceFormat GetFormat() const MOZ_OVERRIDE { return mFormat; }
|
||||
|
||||
virtual IntSize GetSize() const MOZ_OVERRIDE { return mSize; }
|
||||
|
||||
virtual TextureSourceBasic* AsSourceBasic() MOZ_OVERRIDE { return this; }
|
||||
|
||||
SourceSurface *GetSurface() MOZ_OVERRIDE { return mSurface; }
|
||||
|
||||
virtual void SetCompositor(Compositor* aCompositor)
|
||||
{
|
||||
mCompositor = static_cast<BasicCompositor*>(aCompositor);
|
||||
}
|
||||
|
||||
virtual const char *Name() { return "DeprecatedTextureHostBasic"; }
|
||||
|
||||
protected:
|
||||
virtual void UpdateImpl(const SurfaceDescriptor& aImage,
|
||||
nsIntRegion *aRegion,
|
||||
nsIntPoint*) MOZ_OVERRIDE
|
||||
{
|
||||
AutoOpenSurface surf(OPEN_READ_ONLY, aImage);
|
||||
nsRefPtr<gfxASurface> surface = ShadowLayerForwarder::OpenDescriptor(OPEN_READ_ONLY, aImage);
|
||||
nsRefPtr<gfxImageSurface> image = surface->GetAsImageSurface();
|
||||
mFormat = ImageFormatToSurfaceFormat(image->Format());
|
||||
mSize = IntSize(image->Width(), image->Height());
|
||||
mSurface = Factory::CreateWrappingDataSourceSurface(image->Data(),
|
||||
image->Stride(),
|
||||
mSize,
|
||||
mFormat);
|
||||
}
|
||||
|
||||
virtual bool EnsureSurface() {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool Lock() MOZ_OVERRIDE {
|
||||
return EnsureSurface();
|
||||
}
|
||||
|
||||
virtual TemporaryRef<gfx::DataSourceSurface> GetAsSurface() MOZ_OVERRIDE {
|
||||
if (!mSurface) {
|
||||
return nullptr;
|
||||
}
|
||||
return mSurface->GetDataSurface();
|
||||
}
|
||||
|
||||
BasicCompositor *mCompositor;
|
||||
RefPtr<SourceSurface> mSurface;
|
||||
IntSize mSize;
|
||||
SurfaceFormat mFormat;
|
||||
};
|
||||
|
||||
void
|
||||
DeserializerToPlanarYCbCrImageData(YCbCrImageDataDeserializer& aDeserializer, PlanarYCbCrData& aData)
|
||||
{
|
||||
aData.mYChannel = aDeserializer.GetYData();
|
||||
aData.mYStride = aDeserializer.GetYStride();
|
||||
aData.mYSize = aDeserializer.GetYSize();
|
||||
aData.mCbChannel = aDeserializer.GetCbData();
|
||||
aData.mCrChannel = aDeserializer.GetCrData();
|
||||
aData.mCbCrStride = aDeserializer.GetCbCrStride();
|
||||
aData.mCbCrSize = aDeserializer.GetCbCrSize();
|
||||
aData.mPicSize = aDeserializer.GetYSize();
|
||||
}
|
||||
|
||||
class YCbCrDeprecatedTextureHostBasic : public DeprecatedTextureHostBasic
|
||||
{
|
||||
public:
|
||||
virtual void UpdateImpl(const SurfaceDescriptor& aImage,
|
||||
nsIntRegion *aRegion,
|
||||
nsIntPoint*) MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(aImage.type() == SurfaceDescriptor::TYCbCrImage);
|
||||
mSurface = nullptr;
|
||||
ConvertImageToRGB(aImage);
|
||||
}
|
||||
|
||||
virtual void SwapTexturesImpl(const SurfaceDescriptor& aImage,
|
||||
nsIntRegion* aRegion) MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(aImage.type() == SurfaceDescriptor::TYCbCrImage);
|
||||
mSurface = nullptr;
|
||||
}
|
||||
|
||||
virtual bool EnsureSurface() MOZ_OVERRIDE
|
||||
{
|
||||
if (mSurface) {
|
||||
return true;
|
||||
}
|
||||
if (!mBuffer) {
|
||||
return false;
|
||||
}
|
||||
return ConvertImageToRGB(*mBuffer);
|
||||
}
|
||||
|
||||
bool ConvertImageToRGB(const SurfaceDescriptor& aImage)
|
||||
{
|
||||
YCbCrImageDataDeserializer deserializer(aImage.get_YCbCrImage().data().get<uint8_t>());
|
||||
PlanarYCbCrData data;
|
||||
DeserializerToPlanarYCbCrImageData(deserializer, data);
|
||||
|
||||
gfxImageFormat format = gfxImageFormatRGB24;
|
||||
gfxIntSize size;
|
||||
gfxUtils::GetYCbCrToRGBDestFormatAndSize(data, format, size);
|
||||
if (size.width > PlanarYCbCrImage::MAX_DIMENSION ||
|
||||
size.height > PlanarYCbCrImage::MAX_DIMENSION) {
|
||||
NS_ERROR("Illegal image dest width or height");
|
||||
return false;
|
||||
}
|
||||
|
||||
mSize = ToIntSize(size);
|
||||
mFormat = (format == gfxImageFormatRGB24)
|
||||
? FORMAT_B8G8R8X8
|
||||
: FORMAT_B8G8R8A8;
|
||||
|
||||
RefPtr<DataSourceSurface> surface = Factory::CreateDataSourceSurface(mSize, mFormat);
|
||||
gfxUtils::ConvertYCbCrToRGB(data, format, size,
|
||||
surface->GetData(),
|
||||
surface->Stride());
|
||||
|
||||
mSurface = surface;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
TemporaryRef<DeprecatedTextureHost>
|
||||
CreateBasicDeprecatedTextureHost(SurfaceDescriptorType aDescriptorType,
|
||||
uint32_t aTextureHostFlags,
|
||||
uint32_t aTextureFlags)
|
||||
{
|
||||
RefPtr<DeprecatedTextureHost> result = nullptr;
|
||||
if (aDescriptorType == SurfaceDescriptor::TYCbCrImage) {
|
||||
result = new YCbCrDeprecatedTextureHostBasic();
|
||||
} else {
|
||||
MOZ_ASSERT(aDescriptorType == SurfaceDescriptor::TShmem ||
|
||||
aDescriptorType == SurfaceDescriptor::TMemoryImage,
|
||||
"We can only support Shmem currently");
|
||||
result = new DeprecatedTextureHostBasic();
|
||||
}
|
||||
|
||||
result->SetFlags(aTextureFlags);
|
||||
return result.forget();
|
||||
}
|
||||
|
||||
BasicCompositor::BasicCompositor(nsIWidget *aWidget)
|
||||
: mWidget(aWidget)
|
||||
{
|
||||
|
|
|
@ -58,6 +58,11 @@ ContentClient::CreateContentClient(CompositableForwarder* aForwarder)
|
|||
useDeprecatedTextures = gfxPlatform::GetPlatform()->UseDeprecatedTextures();
|
||||
#endif
|
||||
|
||||
// Always use new textures for the basic compositor.
|
||||
if (backend == LAYERS_BASIC) {
|
||||
useDeprecatedTextures = false;
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
if (backend == LAYERS_D3D11) {
|
||||
useDoubleBuffering = !!gfxWindowsPlatform::GetPlatform()->GetD2DDevice();
|
||||
|
|
|
@ -84,10 +84,6 @@ TextureHost::AsTextureHost(PTextureParent* actor)
|
|||
TemporaryRef<DeprecatedTextureHost> CreateDeprecatedTextureHostOGL(SurfaceDescriptorType aDescriptorType,
|
||||
uint32_t aDeprecatedTextureHostFlags,
|
||||
uint32_t aTextureFlags);
|
||||
// implemented in BasicCompositor.cpp
|
||||
TemporaryRef<DeprecatedTextureHost> CreateBasicDeprecatedTextureHost(SurfaceDescriptorType aDescriptorType,
|
||||
uint32_t aDeprecatedTextureHostFlags,
|
||||
uint32_t aTextureFlags);
|
||||
|
||||
#ifdef XP_WIN
|
||||
TemporaryRef<DeprecatedTextureHost> CreateDeprecatedTextureHostD3D9(SurfaceDescriptorType aDescriptorType,
|
||||
|
@ -127,10 +123,6 @@ DeprecatedTextureHost::CreateDeprecatedTextureHost(SurfaceDescriptorType aDescri
|
|||
aDeprecatedTextureHostFlags,
|
||||
aTextureFlags);
|
||||
#endif
|
||||
case LAYERS_BASIC:
|
||||
return CreateBasicDeprecatedTextureHost(aDescriptorType,
|
||||
aDeprecatedTextureHostFlags,
|
||||
aTextureFlags);
|
||||
default:
|
||||
MOZ_CRASH("Couldn't create texture host");
|
||||
}
|
||||
|
|
|
@ -138,6 +138,7 @@ ThebesLayerComposite::RenderLayer(const nsIntRect& aClipRect)
|
|||
|
||||
mBuffer->SetPaintWillResample(MayResample());
|
||||
|
||||
mBuffer->SetCompositor(mCompositeManager->GetCompositor());
|
||||
mBuffer->Composite(effectChain,
|
||||
GetEffectiveOpacity(),
|
||||
transform,
|
||||
|
|
|
@ -281,6 +281,7 @@ gfxPlatform::gfxPlatform()
|
|||
, mDrawLayerBorders(false)
|
||||
, mDrawTileBorders(false)
|
||||
, mDrawBigImageBorders(false)
|
||||
, mCompositorBackend(LAYERS_NONE)
|
||||
{
|
||||
mUseHarfBuzzScripts = UNINITIALIZED_VALUE;
|
||||
mAllowDownloadableFonts = UNINITIALIZED_VALUE;
|
||||
|
@ -2213,3 +2214,22 @@ gfxPlatform::ComponentAlphaEnabled()
|
|||
InitLayersAccelerationPrefs();
|
||||
return sComponentAlphaEnabled;
|
||||
}
|
||||
|
||||
void
|
||||
gfxPlatform::SetCompositorBackend(mozilla::layers::LayersBackend backend)
|
||||
{
|
||||
// The compositor backend should always be the same after one has been chosen.
|
||||
MOZ_ASSERT(mCompositorBackend == LAYERS_NONE || mCompositorBackend == backend);
|
||||
if (mCompositorBackend == LAYERS_NONE) {
|
||||
mCompositorBackend = backend;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
gfxPlatform::UseDeprecatedTextures() const
|
||||
{
|
||||
if (mCompositorBackend == LAYERS_BASIC) {
|
||||
return false;
|
||||
}
|
||||
return mLayersUseDeprecated;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "mozilla/RefPtr.h"
|
||||
#include "GfxInfoCollector.h"
|
||||
|
||||
#include "mozilla/layers/LayersTypes.h"
|
||||
#include "mozilla/layers/CompositorTypes.h"
|
||||
|
||||
#ifdef XP_OS2
|
||||
|
@ -619,7 +620,8 @@ public:
|
|||
* This method should not be called from the compositor thread.
|
||||
*/
|
||||
bool PreferMemoryOverShmem() const;
|
||||
bool UseDeprecatedTextures() const { return mLayersUseDeprecated; }
|
||||
bool UseDeprecatedTextures() const;
|
||||
void SetCompositorBackend(mozilla::layers::LayersBackend backend);
|
||||
|
||||
protected:
|
||||
gfxPlatform();
|
||||
|
@ -736,6 +738,7 @@ private:
|
|||
bool mDrawLayerBorders;
|
||||
bool mDrawTileBorders;
|
||||
bool mDrawBigImageBorders;
|
||||
mozilla::layers::LayersBackend mCompositorBackend;
|
||||
};
|
||||
|
||||
#endif /* GFX_PLATFORM_H */
|
||||
|
|
|
@ -160,15 +160,18 @@ var ignoreFunctions = {
|
|||
// FIXME!
|
||||
"NS_DebugBreak": true,
|
||||
|
||||
// Bug 940765 - fetching preferences should not GC
|
||||
"PrefHashEntry* pref_HashTableLookup(void*)": true,
|
||||
"uint8 mozilla::Preferences::InitStaticMembers()": true, // Temporary, see bug 940765
|
||||
|
||||
// These are a little overzealous -- these destructors *can* GC if they end
|
||||
// up wrapping a pending exception. See bug 898815 for the heavyweight fix.
|
||||
"void js::AutoCompartment::~AutoCompartment(int32)" : true,
|
||||
"void JSAutoCompartment::~JSAutoCompartment(int32)" : true,
|
||||
|
||||
// Bug 948646 - the only thing AutoJSContext's constructor calls
|
||||
// is an Init() routine whose entire body is covered with an
|
||||
// AutoAssertNoGC. AutoSafeJSContext is the same thing, just with
|
||||
// a different value for the 'aSafe' parameter.
|
||||
"void mozilla::AutoJSContext::AutoJSContext(mozilla::detail::GuardObjectNotifier*)" : true,
|
||||
"void mozilla::AutoSafeJSContext::~AutoSafeJSContext(int32)" : true,
|
||||
|
||||
// And these are workarounds to avoid even more analysis work,
|
||||
// which would sadly still be needed even with bug 898815.
|
||||
"void js::AutoCompartment::AutoCompartment(js::ExclusiveContext*, JSCompartment*)": true,
|
||||
|
@ -232,3 +235,17 @@ function isSuppressConstructor(name)
|
|||
|| /::AutoEnterAnalysis/.test(name)
|
||||
|| /::AutoAssertNoGC/.test(name);
|
||||
}
|
||||
|
||||
// nsISupports subclasses' methods may be scriptable (or overridden
|
||||
// via binary XPCOM), and so may GC. But some fields just aren't going
|
||||
// to get overridden with something that can GC.
|
||||
function isOverridableField(csu, field)
|
||||
{
|
||||
if (csu != 'nsISupports')
|
||||
return false;
|
||||
if (field == 'GetCurrentJSContext')
|
||||
return false;
|
||||
if (field == 'IsOnCurrentThread')
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -61,13 +61,13 @@ function findVirtualFunctions(csu, field, suppressed)
|
|||
// which should never enter the JS engine (even when calling dtors).
|
||||
while (worklist.length) {
|
||||
var csu = worklist.pop();
|
||||
if (csu == "nsISupports") {
|
||||
if (field == "AddRef" || field == "Release") {
|
||||
suppressed[0] = true;
|
||||
return [];
|
||||
}
|
||||
return null;
|
||||
if (csu == "nsISupports" && (field == "AddRef" || field == "Release")) {
|
||||
suppressed[0] = true;
|
||||
return [];
|
||||
}
|
||||
if (isOverridableField(csu, field))
|
||||
return null;
|
||||
|
||||
if (csu in superclasses) {
|
||||
for (var superclass of superclasses[csu])
|
||||
worklist.push(superclass);
|
||||
|
@ -258,6 +258,8 @@ for (var csuIndex = minStream; csuIndex <= maxStream; csuIndex++) {
|
|||
|
||||
xdb.open("src_body.xdb");
|
||||
|
||||
printErr("Finished loading data structures");
|
||||
|
||||
var minStream = xdb.min_data_stream();
|
||||
var maxStream = xdb.max_data_stream();
|
||||
|
||||
|
@ -275,8 +277,9 @@ for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
|
|||
seenCallees = {};
|
||||
seenSuppressedCallees = {};
|
||||
|
||||
var functionName = name.readString();
|
||||
for (var body of functionBodies)
|
||||
processBody(name.readString(), body);
|
||||
processBody(functionName, body);
|
||||
|
||||
xdb.free_string(name);
|
||||
xdb.free_string(data);
|
||||
|
|
|
@ -148,9 +148,7 @@ function loadCallgraph(file)
|
|||
// Any field call that has been resolved to all possible callees can be
|
||||
// trusted to not GC if all of those callees are known to not GC.
|
||||
for (var name in resolvedFunctions) {
|
||||
if (!(name in gcFunctions)) {
|
||||
if (!(name in gcFunctions))
|
||||
suppressedFunctions[name] = true;
|
||||
printErr("Adding " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -668,35 +668,6 @@ XPCJSRuntime::SuspectWrappedNative(XPCWrappedNative *wrapper,
|
|||
cb.NoteJSRoot(obj);
|
||||
}
|
||||
|
||||
bool
|
||||
CanSkipWrappedJS(nsXPCWrappedJS *wrappedJS)
|
||||
{
|
||||
JSObject *obj = wrappedJS->GetJSObjectPreserveColor();
|
||||
// If traversing wrappedJS wouldn't release it, nor
|
||||
// cause any other objects to be added to the graph, no
|
||||
// need to add it to the graph at all.
|
||||
bool isRootWrappedJS = wrappedJS->IsRootWrapper();
|
||||
if (nsCCUncollectableMarker::sGeneration &&
|
||||
(!obj || !xpc_IsGrayGCThing(obj)) &&
|
||||
!wrappedJS->IsSubjectToFinalization() &&
|
||||
(isRootWrappedJS || CanSkipWrappedJS(wrappedJS->GetRootWrapper()))) {
|
||||
if (!wrappedJS->IsAggregatedToNative() || !isRootWrappedJS) {
|
||||
return true;
|
||||
} else {
|
||||
nsISupports* agg = wrappedJS->GetAggregatedNativeObject();
|
||||
nsXPCOMCycleCollectionParticipant* cp = nullptr;
|
||||
CallQueryInterface(agg, &cp);
|
||||
nsISupports* canonical = nullptr;
|
||||
agg->QueryInterface(NS_GET_IID(nsCycleCollectionISupports),
|
||||
reinterpret_cast<void**>(&canonical));
|
||||
if (cp && canonical && cp->CanSkipInCC(canonical)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
XPCJSRuntime::TraverseAdditionalNativeRoots(nsCycleCollectionNoteRootCallback &cb)
|
||||
{
|
||||
|
@ -714,13 +685,7 @@ XPCJSRuntime::TraverseAdditionalNativeRoots(nsCycleCollectionNoteRootCallback &c
|
|||
}
|
||||
|
||||
for (XPCRootSetElem *e = mWrappedJSRoots; e ; e = e->GetNextRoot()) {
|
||||
nsXPCWrappedJS *wrappedJS = static_cast<nsXPCWrappedJS*>(e);
|
||||
if (!cb.WantAllTraces() &&
|
||||
CanSkipWrappedJS(wrappedJS)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cb.NoteXPCOMRoot(static_cast<nsIXPConnectWrappedJS *>(wrappedJS));
|
||||
cb.NoteXPCOMRoot(ToSupports(static_cast<nsXPCWrappedJS*>(e)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "xpcprivate.h"
|
||||
#include "jsprf.h"
|
||||
#include "nsCCUncollectableMarker.h"
|
||||
#include "nsCxPusher.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
@ -16,6 +17,76 @@ using namespace mozilla;
|
|||
|
||||
// NOTE: much of the fancy footwork is done in xpcstubs.cpp
|
||||
|
||||
|
||||
// nsXPCWrappedJS lifetime.
|
||||
//
|
||||
// An nsXPCWrappedJS is either rooting its JS object or is subject to finalization.
|
||||
// The subject-to-finalization state lets wrappers support
|
||||
// nsSupportsWeakReference in the case where the underlying JS object
|
||||
// is strongly owned, but the wrapper itself is only weakly owned.
|
||||
//
|
||||
// A wrapper is rooting its JS object whenever its refcount is greater than 1. In
|
||||
// this state, root wrappers are always added to the cycle collector graph. The
|
||||
// wrapper keeps around an extra refcount, added in the constructor, to support
|
||||
// the possibility of an eventual transition to the subject-to-finalization state.
|
||||
// This extra refcount is ignored by the cycle collector, which traverses the "self"
|
||||
// edge for this refcount.
|
||||
//
|
||||
// When the refcount of a rooting wrapper drops to 1, if there is no weak reference
|
||||
// to the wrapper (which can only happen for the root wrapper), it is immediately
|
||||
// Destroy()'d. Otherwise, it becomes subject to finalization.
|
||||
//
|
||||
// When a wrapper is subject to finalization, the wrapper has a refcount of 1. It is
|
||||
// now owned exclusively by its JS object. Either a weak reference will be turned into
|
||||
// a strong ref which will bring its refcount up to 2 and change the wrapper back to
|
||||
// the rooting state, or it will stay alive until the JS object dies. If the JS object
|
||||
// dies, then when XPCJSRuntime::FinalizeCallback calls FindDyingJSObjects
|
||||
// it will find the wrapper and call Release() in it, destroying the wrapper.
|
||||
// Otherwise, the wrapper will stay alive, even if it no longer has a weak reference
|
||||
// to it.
|
||||
//
|
||||
// When the wrapper is subject to finalization, it is kept alive by an implicit reference
|
||||
// from the JS object which is invisible to the cycle collector, so the cycle collector
|
||||
// does not traverse any children of wrappers that are subject to finalization. This will
|
||||
// result in a leak if a wrapper in the non-rooting state has an aggregated native that
|
||||
// keeps alive the wrapper's JS object. See bug 947049.
|
||||
|
||||
|
||||
// If traversing wrappedJS wouldn't release it, nor cause any other objects to be
|
||||
// added to the graph, there is no need to add it to the graph at all.
|
||||
bool
|
||||
nsXPCWrappedJS::CanSkip()
|
||||
{
|
||||
if (!nsCCUncollectableMarker::sGeneration)
|
||||
return false;
|
||||
|
||||
if (IsSubjectToFinalization())
|
||||
return true;
|
||||
|
||||
// If this wrapper holds a gray object, need to trace it.
|
||||
JSObject *obj = GetJSObjectPreserveColor();
|
||||
if (obj && xpc_IsGrayGCThing(obj))
|
||||
return false;
|
||||
|
||||
// For non-root wrappers, check if the root wrapper will be
|
||||
// added to the CC graph.
|
||||
if (!IsRootWrapper())
|
||||
return mRoot->CanSkip();
|
||||
|
||||
// For the root wrapper, check if there is an aggregated
|
||||
// native object that will be added to the CC graph.
|
||||
if (!IsAggregatedToNative())
|
||||
return true;
|
||||
|
||||
nsISupports* agg = GetAggregatedNativeObject();
|
||||
nsXPCOMCycleCollectionParticipant* cp = nullptr;
|
||||
CallQueryInterface(agg, &cp);
|
||||
nsISupports* canonical = nullptr;
|
||||
agg->QueryInterface(NS_GET_IID(nsCycleCollectionISupports),
|
||||
reinterpret_cast<void**>(&canonical));
|
||||
return cp && canonical && cp->CanSkipThis(canonical);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Traverse
|
||||
(void *p, nsCycleCollectionTraversalCallback &cb)
|
||||
|
@ -37,14 +108,17 @@ NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Traverse
|
|||
NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsXPCWrappedJS, refcnt)
|
||||
}
|
||||
|
||||
// nsXPCWrappedJS keeps its own refcount artificially at or above 1, see the
|
||||
// comment above nsXPCWrappedJS::AddRef.
|
||||
// A wrapper that is subject to finalization will only die when its JS object dies.
|
||||
if (tmp->IsSubjectToFinalization())
|
||||
return NS_OK;
|
||||
|
||||
// Don't let the extra reference for nsSupportsWeakReference keep a wrapper that is
|
||||
// not subject to finalization alive.
|
||||
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "self");
|
||||
cb.NoteXPCOMChild(s);
|
||||
|
||||
if (refcnt > 1) {
|
||||
// nsXPCWrappedJS roots its mJSObj when its refcount is > 1, see
|
||||
// the comment above nsXPCWrappedJS::AddRef.
|
||||
if (tmp->IsValid()) {
|
||||
MOZ_ASSERT(refcnt > 1);
|
||||
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mJSObj");
|
||||
cb.NoteJSChild(tmp->GetJSObjectPreserveColor());
|
||||
}
|
||||
|
@ -54,7 +128,7 @@ NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Traverse
|
|||
cb.NoteXPCOMChild(tmp->GetAggregatedNativeObject());
|
||||
} else {
|
||||
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "root");
|
||||
cb.NoteXPCOMChild(static_cast<nsIXPConnectWrappedJS*>(tmp->GetRootWrapper()));
|
||||
cb.NoteXPCOMChild(ToSupports(tmp->GetRootWrapper()));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
@ -66,6 +140,20 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXPCWrappedJS)
|
|||
tmp->Unlink();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
// XPCJSRuntime keeps a table of WJS, so we can remove them from
|
||||
// the purple buffer in between CCs.
|
||||
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsXPCWrappedJS)
|
||||
return true;
|
||||
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXPCWrappedJS)
|
||||
return tmp->CanSkip();
|
||||
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXPCWrappedJS)
|
||||
return tmp->CanSkip();
|
||||
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXPCWrappedJS::AggregatedQueryInterface(REFNSIID aIID, void** aInstancePtr)
|
||||
{
|
||||
|
@ -127,37 +215,22 @@ nsXPCWrappedJS::QueryInterface(REFNSIID aIID, void** aInstancePtr)
|
|||
}
|
||||
|
||||
|
||||
// Refcounting is now similar to that used in the chained (pre-flattening)
|
||||
// wrappednative system.
|
||||
//
|
||||
// We are now holding an extra refcount for nsISupportsWeakReference support.
|
||||
//
|
||||
// Non-root wrappers remove themselves from the chain in their destructors.
|
||||
// We root the JSObject as the refcount transitions from 1->2. And we unroot
|
||||
// the JSObject when the refcount transitions from 2->1.
|
||||
//
|
||||
// When the transition from 2->1 is made and no one holds a weak ref to the
|
||||
// (aggregated) object then we decrement the refcount again to 0 (and
|
||||
// destruct) . However, if a weak ref is held at the 2->1 transition, then we
|
||||
// leave the refcount at 1 to indicate that state. This leaves the JSObject
|
||||
// no longer rooted by us and (as far as we know) subject to possible
|
||||
// collection. Code in XPCJSRuntime watches for JS gc to happen and will do
|
||||
// the final release on wrappers whose JSObjects get finalized. Note that
|
||||
// even after tranistioning to this refcount-of-one state callers might do
|
||||
// an addref and cause us to re-root the JSObject and continue on more normally.
|
||||
// For a description of nsXPCWrappedJS lifetime and reference counting, see
|
||||
// the comment at the top of this file.
|
||||
|
||||
nsrefcnt
|
||||
nsXPCWrappedJS::AddRef(void)
|
||||
{
|
||||
if (!MOZ_LIKELY(NS_IsMainThread()))
|
||||
MOZ_CRASH();
|
||||
nsrefcnt cnt = ++mRefCnt;
|
||||
|
||||
MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
|
||||
nsrefcnt cnt = mRefCnt.incr();
|
||||
NS_LOG_ADDREF(this, cnt, "nsXPCWrappedJS", sizeof(*this));
|
||||
|
||||
if (2 == cnt && IsValid()) {
|
||||
GetJSObject(); // Unmark gray JSObject.
|
||||
XPCJSRuntime* rt = mClass->GetRuntime();
|
||||
rt->AddWrappedJSRoot(this);
|
||||
mClass->GetRuntime()->AddWrappedJSRoot(this);
|
||||
}
|
||||
|
||||
return cnt;
|
||||
|
@ -168,31 +241,44 @@ nsXPCWrappedJS::Release(void)
|
|||
{
|
||||
if (!MOZ_LIKELY(NS_IsMainThread()))
|
||||
MOZ_CRASH();
|
||||
NS_PRECONDITION(0 != mRefCnt, "dup release");
|
||||
MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
|
||||
NS_ASSERT_OWNINGTHREAD(nsXPCWrappedJS);
|
||||
|
||||
do_decrement:
|
||||
|
||||
nsrefcnt cnt = --mRefCnt;
|
||||
bool shouldDelete = false;
|
||||
nsISupports *base = NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Upcast(this);
|
||||
nsrefcnt cnt = mRefCnt.decr(base, &shouldDelete);
|
||||
NS_LOG_RELEASE(this, cnt, "nsXPCWrappedJS");
|
||||
|
||||
if (0 == cnt) {
|
||||
delete this; // also unlinks us from chain
|
||||
return 0;
|
||||
}
|
||||
if (1 == cnt) {
|
||||
if (MOZ_UNLIKELY(shouldDelete)) {
|
||||
mRefCnt.stabilizeForDeletion();
|
||||
DeleteCycleCollectable();
|
||||
} else {
|
||||
mRefCnt.incr();
|
||||
Destroy();
|
||||
mRefCnt.decr(base);
|
||||
}
|
||||
} else if (1 == cnt) {
|
||||
if (IsValid())
|
||||
RemoveFromRootSet();
|
||||
|
||||
// If we are not the root wrapper or if we are not being used from a
|
||||
// weak reference, then this extra ref is not needed and we can let
|
||||
// ourself be deleted.
|
||||
// Note: HasWeakReferences() could only return true for the root.
|
||||
// If we are not a root wrapper being used from a weak reference,
|
||||
// then the extra ref is not needed and we can let outselves be
|
||||
// deleted.
|
||||
if (!HasWeakReferences())
|
||||
goto do_decrement;
|
||||
return Release();
|
||||
|
||||
MOZ_ASSERT(IsRootWrapper(), "Only root wrappers should have weak references");
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(void)
|
||||
nsXPCWrappedJS::DeleteCycleCollectable(void)
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
void
|
||||
nsXPCWrappedJS::TraceJS(JSTracer* trc)
|
||||
{
|
||||
|
@ -345,9 +431,12 @@ nsXPCWrappedJS::nsXPCWrappedJS(JSContext* cx,
|
|||
{
|
||||
InitStub(GetClass()->GetIID());
|
||||
|
||||
// intentionally do double addref - see Release().
|
||||
// There is an extra AddRef to support weak references to wrappers
|
||||
// that are subject to finalization. See the top of the file for more
|
||||
// details.
|
||||
NS_ADDREF_THIS();
|
||||
NS_ADDREF_THIS();
|
||||
|
||||
NS_ADDREF(aClass);
|
||||
NS_IF_ADDREF(mOuter);
|
||||
|
||||
|
@ -358,7 +447,13 @@ nsXPCWrappedJS::nsXPCWrappedJS(JSContext* cx,
|
|||
|
||||
nsXPCWrappedJS::~nsXPCWrappedJS()
|
||||
{
|
||||
NS_PRECONDITION(0 == mRefCnt, "refcounting error");
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void
|
||||
nsXPCWrappedJS::Destroy()
|
||||
{
|
||||
MOZ_ASSERT(1 == int32_t(mRefCnt), "should be stabilized for deletion");
|
||||
|
||||
if (IsRootWrapper()) {
|
||||
XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
|
||||
|
|
|
@ -2464,14 +2464,13 @@ class nsXPCWrappedJS : protected nsAutoXPTCStub,
|
|||
public XPCRootSetElem
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_NSIXPCONNECTJSOBJECTHOLDER
|
||||
NS_DECL_NSIXPCONNECTWRAPPEDJS
|
||||
NS_DECL_NSISUPPORTSWEAKREFERENCE
|
||||
NS_DECL_NSIPROPERTYBAG
|
||||
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXPCWrappedJS, nsIXPConnectWrappedJS)
|
||||
void DeleteCycleCollectable() {}
|
||||
NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS_AMBIGUOUS(nsXPCWrappedJS, nsIXPConnectWrappedJS)
|
||||
|
||||
NS_IMETHOD CallMethod(uint16_t methodIndex,
|
||||
const XPTMethodDescriptor *info,
|
||||
|
@ -2513,12 +2512,9 @@ public:
|
|||
bool IsValid() const {return mJSObj != nullptr;}
|
||||
void SystemIsBeingShutDown();
|
||||
|
||||
// This is used by XPCJSRuntime::GCCallback to find wrappers that no
|
||||
// longer root their JSObject and are only still alive because they
|
||||
// were being used via nsSupportsWeakReference at the time when their
|
||||
// last (outside) reference was released. Wrappers that fit into that
|
||||
// category are only deleted when we see that their corresponding JSObject
|
||||
// is to be finalized.
|
||||
// These two methods are used by JSObject2WrappedJSMap::FindDyingJSObjects
|
||||
// to find non-rooting wrappers for dying JS objects. See the top of
|
||||
// XPCWrappedJS.cpp for more details.
|
||||
bool IsSubjectToFinalization() const {return IsValid() && mRefCnt == 1;}
|
||||
bool IsObjectAboutToBeFinalized() {return JS_IsAboutToBeFinalized(&mJSObj);}
|
||||
|
||||
|
@ -2537,7 +2533,9 @@ protected:
|
|||
nsXPCWrappedJS* root,
|
||||
nsISupports* aOuter);
|
||||
|
||||
void Unlink();
|
||||
bool CanSkip();
|
||||
void Destroy();
|
||||
void Unlink();
|
||||
|
||||
private:
|
||||
JS::Heap<JSObject*> mJSObj;
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
class="reftest-wait"
|
||||
onload="setTimeAndSnapshot(115, true)">
|
||||
<script xlink:href="smil-util.js" type="text/javascript"/>
|
||||
<!-- Test min behavior
|
||||
|
||||
Set up is as follows:
|
||||
1. There is a rectangle with a lime fill.
|
||||
2. A <set> animation (with default fill="none") sets the fill to
|
||||
orange at t=100s.
|
||||
It has a simple duration of 10s and a min duration of 20s.
|
||||
3. A further <set> animation sets the fill to red when the first
|
||||
animation finishes (using syncbase timing).
|
||||
|
||||
At time t=115s we should still be in the first animation's active
|
||||
interval with its fill mode of 'none' applied which should mean the
|
||||
original lime fill is used. -->
|
||||
<rect width="100%" height="100%" fill="lime">
|
||||
<set attributeName="fill" to="orange" dur="10s" min="20s" begin="100s"
|
||||
id="a"/>
|
||||
<set attributeName="fill" to="red" begin="a.end"/>
|
||||
</rect>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1017 B |
|
@ -251,6 +251,8 @@ fuzzy-if(cocoaWidget&&layersGPUAccelerated,1,2) == anim-gradient-attr-presence-0
|
|||
# interaction between xml mapped attributes and their css equivalents
|
||||
== mapped-attr-vs-css-prop-1.svg lime.svg
|
||||
|
||||
== min-1.svg lime.svg
|
||||
|
||||
== smil-transitions-interaction-1a.svg lime.svg
|
||||
== smil-transitions-interaction-1b.svg lime.svg
|
||||
== smil-transitions-interaction-2a.svg lime.svg
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# 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/.
|
||||
|
||||
import sys
|
||||
|
||||
from mozwebidlcodegen import BuildSystemWebIDL
|
||||
|
||||
|
||||
def main(argv):
|
||||
"""Perform WebIDL code generation required by the build system."""
|
||||
manager = BuildSystemWebIDL.from_environment().manager
|
||||
manager.generate_build_files()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
|
@ -10,15 +10,19 @@ import re
|
|||
|
||||
import mozpack.path as mozpath
|
||||
|
||||
from mozbuild.preprocessor import Preprocessor
|
||||
|
||||
from .base import BuildBackend
|
||||
|
||||
from ..frontend.data import (
|
||||
ConfigFileSubstitution,
|
||||
HeaderFileSubstitution,
|
||||
GeneratedEventWebIDLFile,
|
||||
GeneratedWebIDLFile,
|
||||
PreprocessedTestWebIDLFile,
|
||||
PreprocessedWebIDLFile,
|
||||
TestManifest,
|
||||
TestWebIDLFile,
|
||||
XPIDLFile,
|
||||
WebIDLFile,
|
||||
)
|
||||
|
||||
from ..util import DefaultOnReadDict
|
||||
|
@ -56,6 +60,79 @@ class XPIDLManager(object):
|
|||
self.modules.setdefault(entry['module'], set()).add(entry['root'])
|
||||
|
||||
|
||||
class WebIDLCollection(object):
|
||||
"""Collects WebIDL info referenced during the build."""
|
||||
|
||||
def __init__(self):
|
||||
self.sources = set()
|
||||
self.generated_sources = set()
|
||||
self.generated_events_sources = set()
|
||||
self.preprocessed_sources = set()
|
||||
self.test_sources = set()
|
||||
self.preprocessed_test_sources = set()
|
||||
|
||||
def all_regular_sources(self):
|
||||
return self.sources | self.generated_sources | \
|
||||
self.generated_events_sources | self.preprocessed_sources
|
||||
|
||||
def all_regular_basenames(self):
|
||||
return [os.path.basename(source) for source in self.all_regular_sources()]
|
||||
|
||||
def all_regular_stems(self):
|
||||
return [os.path.splitext(b)[0] for b in self.all_regular_basenames()]
|
||||
|
||||
def all_regular_bindinggen_stems(self):
|
||||
for stem in self.all_regular_stems():
|
||||
yield '%sBinding' % stem
|
||||
|
||||
for source in self.generated_events_sources:
|
||||
yield os.path.splitext(os.path.basename(source))[0]
|
||||
|
||||
def all_regular_cpp_basenames(self):
|
||||
for stem in self.all_regular_bindinggen_stems():
|
||||
yield '%s.cpp' % stem
|
||||
|
||||
def all_test_sources(self):
|
||||
return self.test_sources | self.preprocessed_test_sources
|
||||
|
||||
def all_test_basenames(self):
|
||||
return [os.path.basename(source) for source in self.all_test_sources()]
|
||||
|
||||
def all_test_stems(self):
|
||||
return [os.path.splitext(b)[0] for b in self.all_test_basenames()]
|
||||
|
||||
def all_test_cpp_basenames(self):
|
||||
return ['%sBinding.cpp' % s for s in self.all_test_stems()]
|
||||
|
||||
def all_static_sources(self):
|
||||
return self.sources | self.generated_events_sources | \
|
||||
self.test_sources
|
||||
|
||||
def all_non_static_sources(self):
|
||||
return self.generated_sources | self.all_preprocessed_sources()
|
||||
|
||||
def all_non_static_basenames(self):
|
||||
return [os.path.basename(s) for s in self.all_non_static_sources()]
|
||||
|
||||
def all_preprocessed_sources(self):
|
||||
return self.preprocessed_sources | self.preprocessed_test_sources
|
||||
|
||||
def all_sources(self):
|
||||
return set(self.all_regular_sources()) | set(self.all_test_sources())
|
||||
|
||||
def all_basenames(self):
|
||||
return [os.path.basename(source) for source in self.all_sources()]
|
||||
|
||||
def all_stems(self):
|
||||
return [os.path.splitext(b)[0] for b in self.all_basenames()]
|
||||
|
||||
def generated_events_basenames(self):
|
||||
return [os.path.basename(s) for s in self.generated_events_sources]
|
||||
|
||||
def generated_events_stems(self):
|
||||
return [os.path.splitext(b)[0] for b in self.generated_events_basenames()]
|
||||
|
||||
|
||||
class TestManager(object):
|
||||
"""Helps hold state related to tests."""
|
||||
|
||||
|
@ -90,6 +167,7 @@ class CommonBackend(BuildBackend):
|
|||
def _init(self):
|
||||
self._idl_manager = XPIDLManager(self.environment)
|
||||
self._test_manager = TestManager(self.environment)
|
||||
self._webidls = WebIDLCollection()
|
||||
|
||||
def consume_object(self, obj):
|
||||
if isinstance(obj, TestManifest):
|
||||
|
@ -113,6 +191,30 @@ class CommonBackend(BuildBackend):
|
|||
self._create_config_header(obj)
|
||||
self.backend_input_files.add(obj.input_path)
|
||||
|
||||
# We should consider aggregating WebIDL types in emitter.py.
|
||||
elif isinstance(obj, WebIDLFile):
|
||||
self._webidls.sources.add(mozpath.join(obj.srcdir, obj.basename))
|
||||
|
||||
elif isinstance(obj, GeneratedEventWebIDLFile):
|
||||
self._webidls.generated_events_sources.add(mozpath.join(
|
||||
obj.srcdir, obj.basename))
|
||||
|
||||
elif isinstance(obj, TestWebIDLFile):
|
||||
self._webidls.test_sources.add(mozpath.join(obj.srcdir,
|
||||
obj.basename))
|
||||
|
||||
elif isinstance(obj, PreprocessedTestWebIDLFile):
|
||||
self._webidls.preprocessed_test_sources.add(mozpath.join(
|
||||
obj.srcdir, obj.basename))
|
||||
|
||||
elif isinstance(obj, GeneratedWebIDLFile):
|
||||
self._webidls.generated_sources.add(mozpath.join(obj.srcdir,
|
||||
obj.basename))
|
||||
|
||||
elif isinstance(obj, PreprocessedWebIDLFile):
|
||||
self._webidls.preprocessed_sources.add(mozpath.join(
|
||||
obj.srcdir, obj.basename))
|
||||
|
||||
else:
|
||||
return
|
||||
|
||||
|
@ -122,6 +224,8 @@ class CommonBackend(BuildBackend):
|
|||
if len(self._idl_manager.idls):
|
||||
self._handle_idl_manager(self._idl_manager)
|
||||
|
||||
self._handle_webidl_collection(self._webidls)
|
||||
|
||||
# Write out a machine-readable file describing every test.
|
||||
path = mozpath.join(self.environment.topobjdir, 'all-tests.json')
|
||||
with self._write_file(path) as fh:
|
||||
|
|
|
@ -5,13 +5,15 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import types
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
import mozwebidlcodegen
|
||||
|
||||
import mozbuild.makeutil as mozmakeutil
|
||||
from mozpack.copier import FilePurger
|
||||
from mozpack.manifests import (
|
||||
|
@ -25,9 +27,7 @@ from ..frontend.data import (
|
|||
Defines,
|
||||
DirectoryTraversal,
|
||||
Exports,
|
||||
GeneratedEventWebIDLFile,
|
||||
GeneratedInclude,
|
||||
GeneratedWebIDLFile,
|
||||
HostProgram,
|
||||
HostSimpleProgram,
|
||||
InstallationTarget,
|
||||
|
@ -35,17 +35,13 @@ from ..frontend.data import (
|
|||
JavaJarData,
|
||||
LibraryDefinition,
|
||||
LocalInclude,
|
||||
PreprocessedTestWebIDLFile,
|
||||
PreprocessedWebIDLFile,
|
||||
Program,
|
||||
SandboxDerived,
|
||||
SandboxWrapped,
|
||||
SimpleProgram,
|
||||
TestWebIDLFile,
|
||||
TestManifest,
|
||||
VariablePassthru,
|
||||
XPIDLFile,
|
||||
TestManifest,
|
||||
WebIDLFile,
|
||||
)
|
||||
from ..util import (
|
||||
ensureParentDir,
|
||||
|
@ -270,12 +266,6 @@ class RecursiveMakeBackend(CommonBackend):
|
|||
|
||||
self._backend_files = {}
|
||||
self._ipdl_sources = set()
|
||||
self._webidl_sources = set()
|
||||
self._generated_events_webidl_sources = set()
|
||||
self._test_webidl_sources = set()
|
||||
self._preprocessed_test_webidl_sources = set()
|
||||
self._preprocessed_webidl_sources = set()
|
||||
self._generated_webidl_sources = set()
|
||||
|
||||
def detailed(summary):
|
||||
s = '{:d} total backend files. {:d} created; {:d} updated; {:d} unchanged'.format(
|
||||
|
@ -410,33 +400,6 @@ class RecursiveMakeBackend(CommonBackend):
|
|||
elif isinstance(obj, IPDLFile):
|
||||
self._ipdl_sources.add(mozpath.join(obj.srcdir, obj.basename))
|
||||
|
||||
elif isinstance(obj, WebIDLFile):
|
||||
self._webidl_sources.add(mozpath.join(obj.srcdir, obj.basename))
|
||||
self._process_webidl_basename(obj.basename)
|
||||
|
||||
elif isinstance(obj, GeneratedEventWebIDLFile):
|
||||
self._generated_events_webidl_sources.add(mozpath.join(obj.srcdir, obj.basename))
|
||||
|
||||
elif isinstance(obj, TestWebIDLFile):
|
||||
self._test_webidl_sources.add(mozpath.join(obj.srcdir,
|
||||
obj.basename))
|
||||
# Test WebIDL files are not exported.
|
||||
|
||||
elif isinstance(obj, PreprocessedTestWebIDLFile):
|
||||
self._preprocessed_test_webidl_sources.add(mozpath.join(obj.srcdir,
|
||||
obj.basename))
|
||||
# Test WebIDL files are not exported.
|
||||
|
||||
elif isinstance(obj, GeneratedWebIDLFile):
|
||||
self._generated_webidl_sources.add(mozpath.join(obj.srcdir,
|
||||
obj.basename))
|
||||
self._process_webidl_basename(obj.basename)
|
||||
|
||||
elif isinstance(obj, PreprocessedWebIDLFile):
|
||||
self._preprocessed_webidl_sources.add(mozpath.join(obj.srcdir,
|
||||
obj.basename))
|
||||
self._process_webidl_basename(obj.basename)
|
||||
|
||||
elif isinstance(obj, Program):
|
||||
self._process_program(obj.program, backend_file)
|
||||
|
||||
|
@ -607,6 +570,9 @@ class RecursiveMakeBackend(CommonBackend):
|
|||
poison_windows_h=False,
|
||||
files_per_unified_file=16):
|
||||
|
||||
# In case it's a generator.
|
||||
files = sorted(files)
|
||||
|
||||
explanation = "\n" \
|
||||
"# We build files in 'unified' mode by including several files\n" \
|
||||
"# together into a single source file. This cuts down on\n" \
|
||||
|
@ -632,7 +598,7 @@ class RecursiveMakeBackend(CommonBackend):
|
|||
return itertools.izip_longest(fillvalue=dummy_fill_value, *args)
|
||||
|
||||
for i, unified_group in enumerate(grouper(files_per_unified_file,
|
||||
sorted(files))):
|
||||
files)):
|
||||
just_the_filenames = list(filter_out_dummy(unified_group))
|
||||
yield '%s%d.%s' % (unified_prefix, i, unified_suffix), just_the_filenames
|
||||
|
||||
|
@ -750,42 +716,12 @@ class RecursiveMakeBackend(CommonBackend):
|
|||
with self._write_file(mozpath.join(ipdl_dir, 'ipdlsrcs.mk')) as ipdls:
|
||||
mk.dump(ipdls, removal_guard=False)
|
||||
|
||||
self._may_skip['compile'] -= set(['ipc/ipdl'])
|
||||
|
||||
# Write out master lists of WebIDL source files.
|
||||
bindings_dir = mozpath.join(self.environment.topobjdir, 'dom', 'bindings')
|
||||
|
||||
mk = mozmakeutil.Makefile()
|
||||
|
||||
def write_var(variable, sources):
|
||||
files = [mozpath.basename(f) for f in sorted(sources)]
|
||||
mk.add_statement('%s += %s' % (variable, ' '.join(files)))
|
||||
write_var('webidl_files', self._webidl_sources)
|
||||
write_var('generated_events_webidl_files', self._generated_events_webidl_sources)
|
||||
write_var('test_webidl_files', self._test_webidl_sources)
|
||||
write_var('preprocessed_test_webidl_files', self._preprocessed_test_webidl_sources)
|
||||
write_var('generated_webidl_files', self._generated_webidl_sources)
|
||||
write_var('preprocessed_webidl_files', self._preprocessed_webidl_sources)
|
||||
|
||||
all_webidl_files = itertools.chain(iter(self._webidl_sources),
|
||||
iter(self._generated_events_webidl_sources),
|
||||
iter(self._generated_webidl_sources),
|
||||
iter(self._preprocessed_webidl_sources))
|
||||
all_webidl_files = [mozpath.basename(x) for x in all_webidl_files]
|
||||
all_webidl_sources = [re.sub(r'\.webidl$', 'Binding.cpp', x) for x in all_webidl_files]
|
||||
|
||||
self._add_unified_build_rules(mk, all_webidl_sources,
|
||||
bindings_dir,
|
||||
unified_prefix='UnifiedBindings',
|
||||
unified_files_makefile_variable='unified_binding_cpp_files',
|
||||
poison_windows_h=True)
|
||||
|
||||
# Assume that Somebody Else has responsibility for correctly
|
||||
# specifying removal dependencies for |all_webidl_sources|.
|
||||
with self._write_file(mozpath.join(bindings_dir, 'webidlsrcs.mk')) as webidls:
|
||||
mk.dump(webidls, removal_guard=False)
|
||||
|
||||
self._may_skip['compile'] -= set(['dom/bindings', 'dom/bindings/test'])
|
||||
# These contain autogenerated sources that the build config doesn't
|
||||
# yet know about.
|
||||
# TODO Emit GENERATED_SOURCES so these special cases are dealt with
|
||||
# the proper way.
|
||||
self._may_skip['compile'] -= {'ipc/ipdl'}
|
||||
self._may_skip['compile'] -= {'dom/bindings', 'dom/bindings/test'}
|
||||
|
||||
self._fill_root_mk()
|
||||
|
||||
|
@ -1026,10 +962,6 @@ class RecursiveMakeBackend(CommonBackend):
|
|||
def _process_host_simple_program(self, program, backend_file):
|
||||
backend_file.write('HOST_SIMPLE_PROGRAMS += %s\n' % program)
|
||||
|
||||
def _process_webidl_basename(self, basename):
|
||||
header = 'mozilla/dom/%sBinding.h' % mozpath.splitext(basename)[0]
|
||||
self._install_manifests['dist_include'].add_optional_exists(header)
|
||||
|
||||
def _process_test_manifest(self, obj, backend_file):
|
||||
# Much of the logic in this function could be moved to CommonBackend.
|
||||
self.backend_input_files.add(mozpath.join(obj.topsrcdir,
|
||||
|
@ -1171,3 +1103,86 @@ class RecursiveMakeBackend(CommonBackend):
|
|||
# Makefile.in files, the old file will get replaced with
|
||||
# the autogenerated one automatically.
|
||||
self.backend_input_files.add(obj.input_path)
|
||||
|
||||
def _handle_webidl_collection(self, webidls):
|
||||
if not webidls.all_stems():
|
||||
return
|
||||
|
||||
bindings_dir = mozpath.join(self.environment.topobjdir, 'dom',
|
||||
'bindings')
|
||||
|
||||
all_inputs = set(webidls.all_static_sources())
|
||||
for s in webidls.all_non_static_basenames():
|
||||
all_inputs.add(mozpath.join(bindings_dir, s))
|
||||
|
||||
generated_events_stems = webidls.generated_events_stems()
|
||||
exported_stems = webidls.all_regular_stems()
|
||||
|
||||
# The WebIDL manager reads configuration from a JSON file. So, we
|
||||
# need to write this file early.
|
||||
o = dict(
|
||||
webidls=sorted(all_inputs),
|
||||
generated_events_stems=sorted(generated_events_stems),
|
||||
exported_stems=sorted(exported_stems),
|
||||
)
|
||||
|
||||
file_lists = mozpath.join(bindings_dir, 'file-lists.json')
|
||||
with self._write_file(file_lists) as fh:
|
||||
json.dump(o, fh, sort_keys=True, indent=2)
|
||||
|
||||
manager = mozwebidlcodegen.create_build_system_manager(
|
||||
self.environment.topsrcdir,
|
||||
self.environment.topobjdir,
|
||||
mozpath.join(self.environment.topobjdir, 'dist')
|
||||
)
|
||||
|
||||
# The manager is the source of truth on what files are generated.
|
||||
# Consult it for install manifests.
|
||||
include_dir = mozpath.join(self.environment.topobjdir, 'dist',
|
||||
'include')
|
||||
for f in manager.expected_build_output_files():
|
||||
if f.startswith(include_dir):
|
||||
self._install_manifests['dist_include'].add_optional_exists(
|
||||
mozpath.relpath(f, include_dir))
|
||||
|
||||
# We pass WebIDL info to make via a completely generated make file.
|
||||
mk = Makefile()
|
||||
mk.add_statement('nonstatic_webidl_files := %s' % ' '.join(
|
||||
sorted(webidls.all_non_static_basenames())))
|
||||
mk.add_statement('globalgen_sources := %s' % ' '.join(
|
||||
sorted(manager.GLOBAL_DEFINE_FILES)))
|
||||
mk.add_statement('test_sources := %s' % ' '.join(
|
||||
sorted('%sBinding.cpp' % s for s in webidls.all_test_stems())))
|
||||
|
||||
# Add rules to preprocess bindings.
|
||||
# This should ideally be using PP_TARGETS. However, since the input
|
||||
# filenames match the output filenames, the existing PP_TARGETS rules
|
||||
# result in circular dependencies and other make weirdness. One
|
||||
# solution is to rename the input or output files repsectively. See
|
||||
# bug 928195 comment 129.
|
||||
for source in sorted(webidls.all_preprocessed_sources()):
|
||||
basename = os.path.basename(source)
|
||||
rule = mk.create_rule([basename])
|
||||
rule.add_dependencies([source, '$(GLOBAL_DEPS)'])
|
||||
rule.add_commands([
|
||||
# Remove the file before writing so bindings that go from
|
||||
# static to preprocessed don't end up writing to a symlink,
|
||||
# which would modify content in the source directory.
|
||||
'$(RM) $@',
|
||||
'$(call py_action,preprocessor,$(DEFINES) $(ACDEFINES) '
|
||||
'$(XULPPFLAGS) $< -o $@)'
|
||||
])
|
||||
|
||||
# Bindings are compiled in unified mode to speed up compilation and
|
||||
# to reduce linker memory size. Note that test bindings are separated
|
||||
# from regular ones so tests bindings aren't shipped.
|
||||
self._add_unified_build_rules(mk,
|
||||
webidls.all_regular_cpp_basenames(),
|
||||
bindings_dir,
|
||||
unified_prefix='UnifiedBindings',
|
||||
unified_files_makefile_variable='unified_binding_cpp_files',
|
||||
poison_windows_h=True)
|
||||
|
||||
webidls_mk = mozpath.join(bindings_dir, 'webidlsrcs.mk')
|
||||
with self._write_file(webidls_mk) as fh:
|
||||
mk.dump(fh, removal_guard=False)
|
||||
|
|
|
@ -273,6 +273,10 @@ class MozbuildObject(ProcessExecutionMixin):
|
|||
def bindir(self):
|
||||
return os.path.join(self.topobjdir, 'dist', 'bin')
|
||||
|
||||
@property
|
||||
def includedir(self):
|
||||
return os.path.join(self.topobjdir, 'dist', 'include')
|
||||
|
||||
@property
|
||||
def statedir(self):
|
||||
return os.path.join(self.topobjdir, '.mozbuild')
|
||||
|
|
|
@ -988,6 +988,8 @@ void nsBaseWidget::CreateCompositor(int aWidth, int aHeight)
|
|||
ImageBridgeChild::IdentifyCompositorTextureHost(textureFactoryIdentifier);
|
||||
WindowUsesOMTC();
|
||||
|
||||
gfxPlatform::GetPlatform()->SetCompositorBackend(textureFactoryIdentifier.mParentBackend);
|
||||
|
||||
mLayerManager = lm;
|
||||
return;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче