Merge mozilla-central to services-central

This commit is contained in:
Philipp von Weitershausen 2011-04-08 11:29:58 -07:00
commit bbcabe833e
219 changed files with 6242 additions and 3846 deletions

View File

@ -157,11 +157,12 @@ NotificationController::ScheduleContentInsertion(nsAccessible* aContainer,
if (mTreeConstructedState == eTreeConstructionPending)
return;
nsRefPtr<ContentInsertion> insertion =
new ContentInsertion(mDocument, aContainer, aStartChildNode, aEndChildNode);
if (insertion && mContentInsertions.AppendElement(insertion))
nsRefPtr<ContentInsertion> insertion = new ContentInsertion(mDocument,
aContainer);
if (insertion && insertion->InitChildList(aStartChildNode, aEndChildNode) &&
mContentInsertions.AppendElement(insertion)) {
ScheduleProcessing();
}
}
////////////////////////////////////////////////////////////////////////////////
@ -681,15 +682,31 @@ NotificationController::TextEnumerator(nsCOMPtrHashKey<nsIContent>* aEntry,
// NotificationController: content inserted notification
NotificationController::ContentInsertion::
ContentInsertion(nsDocAccessible* aDocument, nsAccessible* aContainer,
nsIContent* aStartChildNode, nsIContent* aEndChildNode) :
ContentInsertion(nsDocAccessible* aDocument, nsAccessible* aContainer) :
mDocument(aDocument), mContainer(aContainer)
{
}
bool
NotificationController::ContentInsertion::
InitChildList(nsIContent* aStartChildNode, nsIContent* aEndChildNode)
{
bool haveToUpdate = false;
nsIContent* node = aStartChildNode;
while (node != aEndChildNode) {
mInsertedContent.AppendElement(node);
// Notification triggers for content insertion even if no content was
// actually inserted, check if the given content has a frame to discard
// this case early.
if (node->GetPrimaryFrame()) {
if (mInsertedContent.AppendElement(node))
haveToUpdate = true;
}
node = node->GetNextSibling();
}
return haveToUpdate;
}
NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController::ContentInsertion)

View File

@ -321,13 +321,13 @@ private:
class ContentInsertion
{
public:
ContentInsertion(nsDocAccessible* aDocument, nsAccessible* aContainer,
nsIContent* aStartChildNode, nsIContent* aEndChildNode);
ContentInsertion(nsDocAccessible* aDocument, nsAccessible* aContainer);
virtual ~ContentInsertion() { mDocument = nsnull; }
NS_INLINE_DECL_REFCOUNTING(ContentInsertion)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ContentInsertion)
bool InitChildList(nsIContent* aStartChildNode, nsIContent* aEndChildNode);
void Process();
private:

View File

@ -66,8 +66,8 @@ struct WalkState
nsAccTreeWalker::
nsAccTreeWalker(nsIWeakReference* aShell, nsIContent* aContent,
PRBool aWalkAnonContent) :
mWeakShell(aShell), mState(nsnull)
PRBool aWalkAnonContent, bool aWalkCache) :
mWeakShell(aShell), mState(nsnull), mWalkCache(aWalkCache)
{
NS_ASSERTION(aContent, "No node for the accessible tree walker!");
@ -114,7 +114,8 @@ nsAccTreeWalker::NextChildInternal(bool aNoWalkUp)
mState->childIdx++;
bool isSubtreeHidden = false;
nsAccessible* accessible =
nsAccessible* accessible = mWalkCache ?
GetAccService()->GetAccessibleInWeakShell(childNode, mWeakShell) :
GetAccService()->GetOrCreateAccessible(childNode, presShell, mWeakShell,
&isSubtreeHidden);

View File

@ -54,7 +54,7 @@ class nsAccTreeWalker
{
public:
nsAccTreeWalker(nsIWeakReference *aShell, nsIContent *aNode,
PRBool aWalkAnonymousContent);
PRBool aWalkAnonymousContent, bool aWalkCache = false);
virtual ~nsAccTreeWalker();
/**
@ -95,6 +95,7 @@ private:
nsCOMPtr<nsIWeakReference> mWeakShell;
PRInt32 mChildFilter;
bool mWalkCache;
WalkState* mState;
};

View File

@ -474,6 +474,9 @@ nsAccessible::GetFirstChild(nsIAccessible **aFirstChild)
NS_ENSURE_ARG_POINTER(aFirstChild);
*aFirstChild = nsnull;
if (IsDefunct())
return NS_ERROR_FAILURE;
PRInt32 childCount = GetChildCount();
NS_ENSURE_TRUE(childCount != -1, NS_ERROR_FAILURE);
@ -490,6 +493,9 @@ nsAccessible::GetLastChild(nsIAccessible **aLastChild)
NS_ENSURE_ARG_POINTER(aLastChild);
*aLastChild = nsnull;
if (IsDefunct())
return NS_ERROR_FAILURE;
PRInt32 childCount = GetChildCount();
NS_ENSURE_TRUE(childCount != -1, NS_ERROR_FAILURE);
@ -503,6 +509,9 @@ nsAccessible::GetChildAt(PRInt32 aChildIndex, nsIAccessible **aChild)
NS_ENSURE_ARG_POINTER(aChild);
*aChild = nsnull;
if (IsDefunct())
return NS_ERROR_FAILURE;
PRInt32 childCount = GetChildCount();
NS_ENSURE_TRUE(childCount != -1, NS_ERROR_FAILURE);
@ -526,6 +535,9 @@ nsAccessible::GetChildren(nsIArray **aOutChildren)
NS_ENSURE_ARG_POINTER(aOutChildren);
*aOutChildren = nsnull;
if (IsDefunct())
return NS_ERROR_FAILURE;
PRInt32 childCount = GetChildCount();
NS_ENSURE_TRUE(childCount != -1, NS_ERROR_FAILURE);
@ -555,6 +567,9 @@ nsAccessible::GetChildCount(PRInt32 *aChildCount)
{
NS_ENSURE_ARG_POINTER(aChildCount);
if (IsDefunct())
return NS_ERROR_FAILURE;
*aChildCount = GetChildCount();
return *aChildCount != -1 ? NS_OK : NS_ERROR_FAILURE;
}
@ -2812,9 +2827,6 @@ nsAccessible::RemoveChild(nsAccessible* aChild)
nsAccessible*
nsAccessible::GetChildAt(PRUint32 aIndex)
{
if (EnsureChildren())
return nsnull;
nsAccessible *child = mChildren.SafeElementAt(aIndex, nsnull);
if (!child)
return nsnull;
@ -2831,14 +2843,13 @@ nsAccessible::GetChildAt(PRUint32 aIndex)
PRInt32
nsAccessible::GetChildCount()
{
return EnsureChildren() ? -1 : mChildren.Length();
return mChildren.Length();
}
PRInt32
nsAccessible::GetIndexOf(nsAccessible* aChild)
{
return EnsureChildren() || (aChild->mParent != this) ?
-1 : aChild->GetIndexInParent();
return (aChild->mParent != this) ? -1 : aChild->GetIndexInParent();
}
PRInt32
@ -2850,9 +2861,6 @@ nsAccessible::GetIndexInParent() const
PRInt32
nsAccessible::GetEmbeddedChildCount()
{
if (EnsureChildren())
return -1;
if (IsChildrenFlag(eMixedChildren)) {
if (!mEmbeddedObjCollector)
mEmbeddedObjCollector = new EmbeddedObjCollector(this);
@ -2865,9 +2873,6 @@ nsAccessible::GetEmbeddedChildCount()
nsAccessible*
nsAccessible::GetEmbeddedChildAt(PRUint32 aIndex)
{
if (EnsureChildren())
return nsnull;
if (IsChildrenFlag(eMixedChildren)) {
if (!mEmbeddedObjCollector)
mEmbeddedObjCollector = new EmbeddedObjCollector(this);
@ -2881,9 +2886,6 @@ nsAccessible::GetEmbeddedChildAt(PRUint32 aIndex)
PRInt32
nsAccessible::GetIndexOfEmbeddedChild(nsAccessible* aChild)
{
if (EnsureChildren())
return -1;
if (IsChildrenFlag(eMixedChildren)) {
if (!mEmbeddedObjCollector)
mEmbeddedObjCollector = new EmbeddedObjCollector(this);

View File

@ -1427,7 +1427,7 @@ nsDocAccessible::ContentRemoved(nsIContent* aContainerNode,
nsAccessible* container = aContainerNode ?
GetAccessibleOrContainer(aContainerNode) : this;
UpdateTree(container, aChildNode, PR_FALSE);
UpdateTree(container, aChildNode, false);
}
void
@ -1444,9 +1444,9 @@ nsDocAccessible::RecreateAccessible(nsIContent* aContent)
nsAccessible* container = GetAccessibleOrContainer(parentContent);
// Remove and reinsert.
UpdateTree(container, aContent, PR_FALSE);
UpdateTree(container, aContent, false);
container->UpdateChildren();
UpdateTree(container, aContent, PR_TRUE);
UpdateTree(container, aContent, true);
}
}
@ -1468,15 +1468,20 @@ nsDocAccessible::NotifyOfCachingEnd(nsAccessible* aAccessible)
// invalidation list.
for (PRUint32 idx = 0; idx < mInvalidationList.Length(); idx++) {
nsIContent* content = mInvalidationList[idx];
if (!HasAccessible(content)) {
// Make sure we keep children updated. While we're inside of caching
// loop then we must exist it with cached children.
nsAccessible* accessible = GetAccessible(content);
if (!accessible) {
nsAccessible* container = GetContainerAccessible(content);
NS_ASSERTION(container,
"Got a referenced element that is not in document!");
if (container)
if (container) {
container->UpdateChildren();
accessible = GetAccessible(content);
}
}
// Make sure the subtree is created.
if (accessible)
CacheChildrenInSubtree(accessible);
}
mInvalidationList.Clear();
@ -1797,16 +1802,28 @@ nsDocAccessible::ProcessContentInserted(nsAccessible* aContainer,
nsAccessible* directContainer =
GetContainerAccessible(aInsertedContent->ElementAt(idx));
if (directContainer)
UpdateTree(directContainer, aInsertedContent->ElementAt(idx), PR_TRUE);
UpdateTree(directContainer, aInsertedContent->ElementAt(idx), true);
}
}
void
nsDocAccessible::UpdateTree(nsAccessible* aContainer, nsIContent* aChildNode,
PRBool aIsInsert)
bool aIsInsert)
{
PRUint32 updateFlags =
UpdateTreeInternal(aChildNode, aChildNode->GetNextSibling(), aIsInsert);
PRUint32 updateFlags = eNoAccessible;
// If child node is not accessible then look for its accessible children.
nsAccessible* child = GetAccessible(aChildNode);
if (child) {
updateFlags |= UpdateTreeInternal(child, aIsInsert);
} else {
nsAccTreeWalker walker(mWeakShell, aChildNode,
aContainer->GetAllowsAnonChildAccessibles(), true);
while ((child = walker.NextChild()))
updateFlags |= UpdateTreeInternal(child, aIsInsert);
}
// Content insertion/removal is not cause of accessible tree change.
if (updateFlags == eNoAccessible)
@ -1849,97 +1866,77 @@ nsDocAccessible::UpdateTree(nsAccessible* aContainer, nsIContent* aChildNode,
}
PRUint32
nsDocAccessible::UpdateTreeInternal(nsIContent* aStartNode,
nsIContent* aEndNode,
PRBool aIsInsert)
nsDocAccessible::UpdateTreeInternal(nsAccessible* aChild, bool aIsInsert)
{
PRUint32 updateFlags = eNoAccessible;
for (nsIContent* node = aStartNode; node != aEndNode;
node = node->GetNextSibling()) {
PRUint32 updateFlags = eAccessible;
// Tree update triggers for content insertion even if no content was
// inserted actually, check if the given content has a frame to discard
// this case early.
if (aIsInsert && !node->GetPrimaryFrame())
continue;
nsINode* node = aChild->GetNode();
if (aIsInsert) {
// Create accessible tree for shown accessible.
CacheChildrenInSubtree(aChild);
nsAccessible* accessible = GetAccessible(node);
} else {
// Fire menupopup end event before hide event if a menu goes away.
if (!accessible) {
updateFlags |= UpdateTreeInternal(node->GetFirstChild(), nsnull,
aIsInsert);
continue;
// XXX: We don't look into children of hidden subtree to find hiding
// menupopup (as we did prior bug 570275) because we don't do that when
// menu is showing (and that's impossible until bug 606924 is fixed).
// Nevertheless we should do this at least because layout coalesces
// the changes before our processing and we may miss some menupopup
// events. Now we just want to be consistent in content insertion/removal
// handling.
if (aChild->ARIARole() == nsIAccessibleRole::ROLE_MENUPOPUP) {
nsRefPtr<AccEvent> event =
new AccEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, aChild);
if (event)
FireDelayedAccessibleEvent(event);
}
}
// Fire show/hide event.
nsRefPtr<AccEvent> event;
if (aIsInsert)
event = new AccShowEvent(aChild, node);
else
event = new AccHideEvent(aChild, node);
if (event)
FireDelayedAccessibleEvent(event);
if (aIsInsert) {
PRUint32 ariaRole = aChild->ARIARole();
if (ariaRole == nsIAccessibleRole::ROLE_MENUPOPUP) {
// Fire EVENT_MENUPOPUP_START if ARIA menu appears.
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START,
node, AccEvent::eRemoveDupes);
} else if (ariaRole == nsIAccessibleRole::ROLE_ALERT) {
// Fire EVENT_ALERT if ARIA alert appears.
updateFlags = eAlertAccessible;
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_ALERT, node,
AccEvent::eRemoveDupes);
}
updateFlags |= eAccessible;
if (aIsInsert) {
// Create accessible tree for shown accessible.
CacheChildrenInSubtree(accessible);
} else {
// Fire menupopup end event before hide event if a menu goes away.
// XXX: We don't look into children of hidden subtree to find hiding
// menupopup (as we did prior bug 570275) because we don't do that when
// menu is showing (and that's impossible until bug 606924 is fixed).
// Nevertheless we should do this at least because layout coalesces
// the changes before our processing and we may miss some menupopup
// events. Now we just want to be consistent in content insertion/removal
// handling.
if (accessible->ARIARole() == nsIAccessibleRole::ROLE_MENUPOPUP) {
nsRefPtr<AccEvent> event =
new AccEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, accessible);
if (event)
FireDelayedAccessibleEvent(event);
}
// If focused node has been shown then it means its frame was recreated
// while it's focused. Fire focus event on new focused accessible. If
// the queue contains focus event for this node then it's suppressed by
// this one.
if (node == gLastFocusedNode) {
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_FOCUS,
node, AccEvent::eCoalesceFromSameDocument);
}
} else {
// Update the tree for content removal.
// The accessible parent may differ from container accessible if
// the parent doesn't have own DOM node like list accessible for HTML
// selects.
nsAccessible* parent = aChild->GetParent();
NS_ASSERTION(parent, "No accessible parent?!");
if (parent)
parent->RemoveChild(aChild);
// Fire show/hide event.
nsRefPtr<AccEvent> event;
if (aIsInsert)
event = new AccShowEvent(accessible, node);
else
event = new AccHideEvent(accessible, node);
if (event)
FireDelayedAccessibleEvent(event);
if (aIsInsert) {
PRUint32 ariaRole = accessible->ARIARole();
if (ariaRole == nsIAccessibleRole::ROLE_MENUPOPUP) {
// Fire EVENT_MENUPOPUP_START if ARIA menu appears.
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START,
node, AccEvent::eRemoveDupes);
} else if (ariaRole == nsIAccessibleRole::ROLE_ALERT) {
// Fire EVENT_ALERT if ARIA alert appears.
updateFlags = eAlertAccessible;
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_ALERT, node,
AccEvent::eRemoveDupes);
}
// If focused node has been shown then it means its frame was recreated
// while it's focused. Fire focus event on new focused accessible. If
// the queue contains focus event for this node then it's suppressed by
// this one.
if (node == gLastFocusedNode) {
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_FOCUS,
node, AccEvent::eCoalesceFromSameDocument);
}
} else {
// Update the tree for content removal.
// The accessible parent may differ from container accessible if
// the parent doesn't have own DOM node like list accessible for HTML
// selects.
nsAccessible* parent = accessible->GetParent();
NS_ASSERTION(parent, "No accessible parent?!");
if (parent)
parent->RemoveChild(accessible);
UncacheChildrenInSubtree(accessible);
}
UncacheChildrenInSubtree(aChild);
}
return updateFlags;

View File

@ -443,7 +443,7 @@ protected:
* Update the accessible tree for content insertion or removal.
*/
void UpdateTree(nsAccessible* aContainer, nsIContent* aChildNode,
PRBool aIsInsert);
bool aIsInsert);
/**
* Helper for UpdateTree() method. Go down to DOM subtree and updates
@ -455,9 +455,7 @@ protected:
eAlertAccessible = 2
};
PRUint32 UpdateTreeInternal(nsIContent* aStartNode,
nsIContent* aEndNode,
PRBool aIsInsert);
PRUint32 UpdateTreeInternal(nsAccessible* aChild, bool aIsInsert);
/**
* Create accessible tree.

View File

@ -2212,7 +2212,6 @@ nsHyperTextAccessible::GetChildOffset(PRUint32 aChildIndex,
PRUint32 lastOffset = mOffsets.IsEmpty() ?
0 : mOffsets[mOffsets.Length() - 1];
EnsureChildren();
while (mOffsets.Length() < aChildIndex) {
nsAccessible* child = mChildren[mOffsets.Length()];
lastOffset += nsAccUtils::TextLength(child);

View File

@ -69,6 +69,7 @@ _TEST_FILES =\
test_focusdoc.html \
test_menu.xul \
test_mutation.html \
test_mutation.xhtml \
test_scroll.xul \
test_selection.html \
test_statechange.html \

View File

@ -0,0 +1,99 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Accessible mutation events testing</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<bindings xmlns="http://www.mozilla.org/xbl" >
<binding id="button">
<content>
<button xmlns="http://www.w3.org/1999/xhtml">a button</button>
</content>
</binding>
</bindings>
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../events.js"></script>
<script type="application/javascript">
/**
* Insert a not accessible bound element containing an accessible element
* in anonymous content.
*/
function insertBinding(aContainerID)
{
this.containerNode = getNode(aContainerID);
function getButtonFromBinding(aNode)
{
try { return document.getAnonymousNodes(aNode.firstChild)[0]; }
catch (e) { return null; }
}
this.eventSeq = [
new invokerChecker(EVENT_SHOW, getButtonFromBinding, this.containerNode),
new invokerChecker(EVENT_REORDER, this.containerNode)
];
this.invoke = function insertBinding_invoke()
{
var span = document.createElement("span");
span.setAttribute("style", "-moz-binding:url(#button)");
this.containerNode.appendChild(span);
}
this.getID = function insertBinding_getID()
{
return "insert button binding";
}
}
/**
* Do tests.
*/
var gQueue = null;
//gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true;
function doTests()
{
gQueue = new eventQueue();
gQueue.push(new insertBinding("testContainer"));
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=646369"
title="UpdateTree should rely on accessible tree walker rather than DOM tree traversal">
Mozilla Bug 646369</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<div id="eventdump"></div>
<div id="testContainer"></div>
</body>
</html>

View File

@ -3,7 +3,7 @@
-moz-padding-end: 0;
padding-bottom: 10px;
-moz-padding-start: 0;
width: 600px;
width: 620px;
}
#clientBox {
@ -58,10 +58,6 @@
-moz-padding-start: 0;
}
#updateBox {
margin-bottom: 10px;
}
#updateButton,
#updateDeck > hbox > label {
-moz-margin-start: 0;
@ -99,3 +95,36 @@
color: #999999;
margin-top: 10px;
}
#currentChannel {
margin: 0;
padding: 0;
font-weight: bold;
}
#channelSelector {
margin-top: 10px;
}
#channelMenulist {
margin: 0;
}
.channel-description {
margin: 10px 0;
text-align: center;
}
#detailsBox,
#channelSelector,
.channel-description {
-moz-transition: opacity 250ms;
}
#contentDeck:not([selectedIndex="0"]) > #detailsBox,
#contentDeck:not([selectedIndex="1"]) > #channelSelector,
#channelDescriptionDeck:not([selectedIndex="0"]) > #releaseDescription,
#channelDescriptionDeck:not([selectedIndex="1"]) > #betaDescription,
#channelDescriptionDeck:not([selectedIndex="2"]) > #auroraDescription {
opacity: 0;
}

View File

@ -88,6 +88,8 @@ function init(aEvent)
gAppUpdater = new appUpdater();
#endif
gChannelSelector.init();
#ifdef XP_MACOSX
// it may not be sized at this point, and we need its width to calculate its position
window.sizeToContent();
@ -572,3 +574,68 @@ appUpdater.prototype =
}
};
#endif
var gChannelSelector = {
validChannels: { release: 1, beta: 1, aurora: 1 },
init: function() {
try {
this.channelValue = Services.prefs.getCharPref("app.update.desiredChannel");
} catch (e) {
let defaults = Services.prefs.getDefaultBranch("");
this.channelValue = defaults.getCharPref("app.update.channel");
}
// Only show channel selector UI on valid update channels.
if (this.channelValue in this.validChannels) {
document.getElementById("currentChannelText").hidden = false;
this.setChannelLabel(this.channelValue);
this.setChannelMenuitem(this.channelValue);
}
},
selectChannel: function(aSelectedItem) {
document.getElementById("channelDescriptionDeck").selectedPanel =
document.getElementById(aSelectedItem.value + "Description");
},
cancel: function() {
this.setChannelMenuitem(this.channelValue);
this.hide();
},
apply: function() {
this.channelValue = document.getElementById("channelMenulist").selectedItem.value;
this.setChannelLabel(this.channelValue);
// Change app update channel.
Services.prefs.setCharPref("app.update.desiredChannel", this.channelValue);
// App updater will look at app.update.desiredChannel for new channel value
// and will clear it when the update is complete.
gAppUpdater.isChecking = true;
gAppUpdater.checker.checkForUpdates(gAppUpdater.updateCheckListener, true);
this.hide();
},
show: function() {
document.getElementById("contentDeck").selectedPanel =
document.getElementById("channelSelector");
},
hide: function() {
document.getElementById("contentDeck").selectedPanel =
document.getElementById("detailsBox");
},
setChannelLabel: function(aValue) {
let channelLabel = document.getElementById("currentChannel");
channelLabel.value = document.getElementById(aValue + "Menuitem").label;
},
setChannelMenuitem: function(aValue) {
document.getElementById("channelMenulist").selectedItem =
document.getElementById(aValue + "Menuitem");
}
}

View File

@ -77,44 +77,81 @@
#expand <label id="version" value="__MOZ_APP_VERSION__"/>
<label id="distribution" class="text-blurb"/>
<label id="distributionId" class="text-blurb"/>
<vbox id="updateBox">
<!-- Make sure the selectedIndex attribute is always set so that the CSS
selectors for transitions work -->
<deck id="contentDeck" selectedIndex="0">
<vbox id="detailsBox">
<vbox id="updateBox">
#ifdef MOZ_UPDATER
<deck id="updateDeck" orient="vertical">
<hbox id="updateButtonBox" align="center">
<button id="updateButton" align="start"
oncommand="gAppUpdater.buttonOnCommand();"/>
<spacer flex="1"/>
</hbox>
<hbox id="checkingForUpdates" align="center">
<image class="update-throbber"/><label>&update.checkingForUpdates;</label>
</hbox>
<hbox id="checkingAddonCompat" align="center">
<image class="update-throbber"/><label>&update.checkingAddonCompat;</label>
</hbox>
<hbox id="downloading" align="center">
<image class="update-throbber"/><label>&update.downloading.start;</label><label id="downloadStatus"/><label>&update.downloading.end;</label>
</hbox>
<hbox id="downloadFailed" align="center">
<label>&update.failed.start;</label><label id="failedLink" class="text-link">&update.failed.linkText;</label><label>&update.failed.end;</label>
</hbox>
<hbox id="adminDisabled" align="center">
<label>&update.adminDisabled;</label>
</hbox>
<hbox id="noUpdatesFound" align="center">
<label>&update.noUpdatesFound;</label>
</hbox>
<hbox id="manualUpdate" align="center">
<label>&update.manual.start;</label><label id="manualLink" class="text-link"/><label>&update.manual.end;</label>
</hbox>
</deck>
<deck id="updateDeck" orient="vertical">
<hbox id="updateButtonBox" align="center">
<button id="updateButton" align="start"
oncommand="gAppUpdater.buttonOnCommand();"/>
<spacer flex="1"/>
</hbox>
<hbox id="checkingForUpdates" align="center">
<image class="update-throbber"/><label>&update.checkingForUpdates;</label>
</hbox>
<hbox id="checkingAddonCompat" align="center">
<image class="update-throbber"/><label>&update.checkingAddonCompat;</label>
</hbox>
<hbox id="downloading" align="center">
<image class="update-throbber"/><label>&update.downloading.start;</label><label id="downloadStatus"/><label>&update.downloading.end;</label>
</hbox>
<hbox id="downloadFailed" align="center">
<label>&update.failed.start;</label><label id="failedLink" class="text-link">&update.failed.linkText;</label><label>&update.failed.end;</label>
</hbox>
<hbox id="adminDisabled" align="center">
<label>&update.adminDisabled;</label>
</hbox>
<hbox id="noUpdatesFound" align="center">
<label>&update.noUpdatesFound;</label>
</hbox>
<hbox id="manualUpdate" align="center">
<label>&update.manual.start;</label><label id="manualLink" class="text-link"/><label>&update.manual.end;</label>
</hbox>
</deck>
#endif
</vbox>
<description class="text-blurb">
&community.start2;<label class="text-link" href="http://www.mozilla.org/">&community.mozillaLink;</label>&community.middle2;<label class="text-link" href="about:credits">&community.creditsLink;</label>&community.end2;
</description>
<description class="text-blurb">
&contribute.start;<label class="text-link" href="http://www.mozilla.org/contribute/">&contribute.getInvolvedLink;</label>&contribute.end;
</description>
</vbox>
<description class="text-blurb" id="currentChannelText" hidden="true">
&channel.description.start;<label id="currentChannel"/>&channel.description.end;<label id="channelChangeLink" class="text-link" onclick="gChannelSelector.show();">&channel.change;</label>
</description>
<description class="text-blurb">
&community.start2;<label class="text-link" href="http://www.mozilla.org/">&community.mozillaLink;</label>&community.middle2;<label class="text-link" href="about:credits">&community.creditsLink;</label>&community.end2;
</description>
<description class="text-blurb">
&contribute.start;<label class="text-link" href="http://www.mozilla.org/contribute/">&contribute.getInvolvedLink;</label>&contribute.end;
</description>
</vbox>
<vbox id="channelSelector">
<hbox pack="center" align="center">
<label>&channel.selector.start;</label>
<menulist id="channelMenulist" onselect="gChannelSelector.selectChannel(this.selectedItem);">
<menupopup>
<menuitem id="releaseMenuitem" label="Release" value="release"/>
<menuitem id="betaMenuitem" label="Beta" value="beta"/>
<menuseparator/>
<menuitem id="auroraMenuitem" label="Aurora" value="aurora"/>
</menupopup>
</menulist>
<label>&channel.selector.end;</label>
</hbox>
<deck id="channelDescriptionDeck" selectedIndex="0">
<description id="releaseDescription" class="channel-description">&channel.release.description;</description>
<description id="betaDescription" class="channel-description">&channel.beta.description;</description>
<description id="auroraDescription" class="channel-description">&channel.aurora.description;</description>
</deck>
<hbox id="channelSelectorButtons" pack="center">
<button oncommand="gChannelSelector.apply();" label="&channel.selector.applyButton;"/>
<button oncommand="gChannelSelector.cancel();" label="&channel.selector.cancelButton;"/>
</hbox>
</vbox>
</deck>
</vbox>
</hbox>
<vbox id="bottomBox">

View File

@ -320,8 +320,10 @@ let TabView = {
// ----------
// On move to group pop showing.
moveToGroupPopupShowing: function TabView_moveToGroupPopupShowing(event) {
// there are hidden tabs so initialize the iframe and update the context menu
if ((gBrowser.tabs.length - gBrowser.visibleTabs.length) > 0)
// Update the context menu only if Panorama was already initialized or if
// there are hidden tabs.
let numHiddenTabs = gBrowser.tabs.length - gBrowser.visibleTabs.length;
if (this._window || numHiddenTabs > 0)
this.updateContextMenu(TabContextMenu.contextTab, event.target);
},

View File

@ -691,6 +691,7 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
});
this.droppable(false);
this.removeTrenches();
this._createUndoButton();
} else
this.close();
@ -740,6 +741,7 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
this.$undoContainer.remove();
this.$undoContainer = null;
this.droppable(true);
this.setTrenches(this.bounds);
GroupItems.setActiveGroupItem(this);
if (this._activeTab)
@ -1237,8 +1239,8 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
count: count || this._children.length,
hideTitle: false
};
let arrObj = Items.arrange(null, bb, options);
let arrObj = Items.arrange(this._children, bb, options);
let shouldStack = arrObj.childWidth < TabItems.minTabWidth * 1.35;
this._columns = shouldStack ? null : arrObj.columns;

View File

@ -914,7 +914,6 @@ let Items = {
// width of children and the columns.
// count - overrides the item count for layout purposes;
// default: the actual item count
// padding - pixels between each item
// columns - (int) a preset number of columns to use
// dropPos - a <Point> which should have a one-tab space left open, used
// when a tab is dragged over.

View File

@ -229,16 +229,6 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
this.tabCanvas.paint();
},
// ----------
// Function: _getFontSizeFromWidth
// Private method that returns the fontsize to use given the tab's width
_getFontSizeFromWidth: function TabItem__getFontSizeFromWidth(width) {
let widthRange = new Range(0,TabItems.tabWidth);
let proportion = widthRange.proportion(width-TabItems.tabItemPadding.x, true);
// proportion is in [0,1]
return TabItems.fontSizeRange.scale(proportion);
},
// ----------
// Function: unforceCanvasSize
// Stops holding the thumbnail resolution; allows it to shift to the
@ -373,6 +363,19 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
(!GroupItems.getActiveGroupItem() && !self.tab.hidden))
GroupItems.setActiveGroupItem(self.parent);
}
} else {
// When duplicating a non-blank orphaned tab, create a group including both of them.
// This prevents overlaid tabs in Tab View (only one tab appears to be there).
// In addition, as only one active orphaned tab is shown when Tab View is hidden
// and there are two tabs shown after the duplication, it also prevents
// the inactive tab to suddenly disappear when toggling Tab View twice.
//
// Fixes:
// Bug 645653 - Middle-click on reload button to duplicate orphan tabs does not create a group
// Bug 643119 - Ctrl+Drag to duplicate does not work for orphaned tabs
// ... (and any other way of duplicating a non-blank orphaned tab).
if (GroupItems.getActiveGroupItem() == null)
GroupItems.newTab(self, {immediately: true});
}
} else {
// create tab by double click is handled in UI_init().
@ -440,16 +443,14 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
if (rect.width != this.bounds.width || options.force) {
css.width = rect.width - TabItems.tabItemPadding.x;
css.fontSize = this._getFontSizeFromWidth(rect.width);
css.fontSize = TabItems.getFontSizeFromWidth(rect.width);
css.fontSize += 'px';
}
if (rect.height != this.bounds.height || options.force) {
css.height = rect.height - TabItems.tabItemPadding.y;
if (!this.isStacked)
css.height = rect.height - TabItems.tabItemPadding.y -
TabItems.fontSizeRange.max;
else
css.height = rect.height - TabItems.tabItemPadding.y;
css.height -= TabItems.fontSizeRange.max;
}
if (Utils.isEmptyObject(css))
@ -1280,57 +1281,59 @@ let TabItems = {
return sane;
},
// ----------
// Function: getFontSizeFromWidth
// Private method that returns the fontsize to use given the tab's width
getFontSizeFromWidth: function TabItem_getFontSizeFromWidth(width) {
let widthRange = new Range(0, TabItems.tabWidth);
let proportion = widthRange.proportion(width - TabItems.tabItemPadding.x, true);
// proportion is in [0,1]
return TabItems.fontSizeRange.scale(proportion);
},
// ----------
// Function: _getWidthForHeight
// Private method that returns the tabitem width given a height.
// Set options.hideTitle=true to measure without a title.
// Default is to measure with a title.
_getWidthForHeight: function TabItems__getWidthForHeight(height, options) {
let titleSize = (options !== undefined && options.hideTitle === true) ?
0 : TabItems.fontSizeRange.max;
return Math.max(0, Math.max(TabItems.minTabHeight, height - titleSize)) *
TabItems.invTabAspect;
_getWidthForHeight: function TabItems__getWidthForHeight(height) {
return height * TabItems.invTabAspect;
},
// ----------
// Function: _getHeightForWidth
// Private method that returns the tabitem height given a width.
// Set options.hideTitle=false to measure without a title.
// Default is to measure with a title.
_getHeightForWidth: function TabItems__getHeightForWidth(width, options) {
let titleSize = (options !== undefined && options.hideTitle === true) ?
0 : TabItems.fontSizeRange.max;
return Math.max(0, Math.max(TabItems.minTabWidth,width)) *
TabItems.tabAspect + titleSize;
_getHeightForWidth: function TabItems__getHeightForWidth(width) {
return width * TabItems.tabAspect;
},
// ----------
// Function: calcValidSize
// Pass in a desired size, and receive a size based on proper title
// size and aspect ratio.
calcValidSize: function TabItems_calcValidSize(size, options) {
Utils.assert(Utils.isPoint(size), 'input is a Point');
let retSize = new Point(0,0);
if (size.x==-1) {
retSize.x = this._getWidthForHeight(size.y, options);
retSize.y = size.y;
} else if (size.y==-1) {
retSize.x = size.x;
retSize.y = this._getHeightForWidth(size.x, options);
} else {
let fitHeight = this._getHeightForWidth(size.x, options);
let fitWidth = this._getWidthForHeight(size.y, options);
// Go with the smallest final dimension.
if (fitWidth < size.x) {
retSize.x = fitWidth;
retSize.y = size.y;
} else {
retSize.x = size.x;
retSize.y = fitHeight;
}
let width = Math.max(TabItems.minTabWidth, size.x);
let showTitle = !options || !options.hideTitle;
let titleSize = showTitle ? TabItems.fontSizeRange.max : 0;
let height = Math.max(TabItems.minTabHeight, size.y - titleSize);
let retSize = new Point(width, height);
if (size.x > -1)
retSize.y = this._getHeightForWidth(width);
if (size.y > -1)
retSize.x = this._getWidthForHeight(height);
if (size.x > -1 && size.y > -1) {
if (retSize.x < size.x)
retSize.y = this._getHeightForWidth(retSize.x);
else
retSize.x = this._getWidthForHeight(retSize.y);
}
if (showTitle)
retSize.y += titleSize;
return retSize;
}
};

View File

@ -15,7 +15,7 @@
*
* The Original Code is sanitize dialog test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*

View File

@ -15,7 +15,7 @@
*
* The Original Code is sanitize dialog test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*

View File

@ -73,6 +73,7 @@ _BROWSER_FILES = \
browser_tabview_bug597360.js \
browser_tabview_bug597399.js \
browser_tabview_bug597980.js \
browser_tabview_bug598375.js \
browser_tabview_bug598600.js \
browser_tabview_bug599626.js \
browser_tabview_bug600645.js \
@ -105,6 +106,7 @@ _BROWSER_FILES = \
browser_tabview_bug624953.js \
browser_tabview_bug625269.js \
browser_tabview_bug625424.js \
browser_tabview_bug625666.js \
browser_tabview_bug626368.js \
browser_tabview_bug626525.js \
browser_tabview_bug626791.js \
@ -122,7 +124,10 @@ _BROWSER_FILES = \
browser_tabview_bug634077.js \
browser_tabview_bug634085.js \
browser_tabview_bug634158.js \
browser_tabview_bug634672.js \
browser_tabview_bug635696.js \
browser_tabview_bug641802.js \
browser_tabview_bug645653.js \
browser_tabview_dragdrop.js \
browser_tabview_exit_button.js \
browser_tabview_expander.js \

View File

@ -0,0 +1,19 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
waitForExplicitFinish();
newWindowWithTabView(function (win) {
registerCleanupFunction(function () win.close());
let cw = win.TabView.getContentWindow();
let groupItem = cw.GroupItems.groupItems[0];
groupItem.setBounds(new cw.Rect(cw.innerWidth - 200, 0, 200, 200));
whenTabViewIsHidden(finish, win);
let button = cw.document.getElementById("exit-button");
EventUtils.synthesizeMouseAtCenter(button, {}, cw);
});
}

View File

@ -125,18 +125,20 @@ function test() {
// make sure we don't freeze item size when removing an item from a stack
let testRemoveWhileStacked = function () {
let oldBounds = groupItem.getBounds();
groupItem.setSize(150, 200, true);
groupItem.setSize(250, 250, true);
groupItem.setUserSize();
let originalBounds = groupItem.getChild(0).getBounds();
ok(!groupItem._isStacked, 'testRemoveWhileStacked: group is not stacked');
ok(!groupItem.isStacked(), 'testRemoveWhileStacked: group is not stacked');
// add a new tab to let the group stack
win.gBrowser.loadOneTab('about:blank', {inBackground: true});
ok(groupItem._isStacked, 'testRemoveWhileStacked: group is now stacked');
// add new tabs to let the group stack
while (!groupItem.isStacked())
win.gBrowser.loadOneTab('about:blank', {inBackground: true});
afterAllTabsLoaded(function () {
groupItem.getChild(0).close();
ok(!groupItem.isStacked(), 'testRemoveWhileStacked: group is not stacked');
let bounds = groupItem.getChild(0).getBounds();
ok(originalBounds.equals(bounds), 'testRemoveWhileStacked: tabs did not change their size');
@ -156,7 +158,7 @@ function test() {
groupItem.setSize(100, 100, true);
groupItem.setUserSize();
ok(groupItem._isStacked, 'testExpandedMode: group is stacked');
ok(groupItem.isStacked(), 'testExpandedMode: group is stacked');
groupItem.addSubscriber(groupItem, 'expanded', function () {
groupItem.removeSubscriber(groupItem, 'expanded');
@ -173,7 +175,7 @@ function test() {
let tabItem = groupItem.getChild(1);
let bounds = tabItem.getBounds();
for (let i=0; i<3; i++)
while (groupItem.getChildren().length > 2)
groupItem.getChild(1).close();
ok(originalBounds.equals(groupItem.getChild(0).getBounds()), 'testExpandedMode: tabs did not change their size');

View File

@ -3,24 +3,11 @@
function test() {
let cw;
let createGroupItem = function () {
let bounds = new cw.Rect(20, 20, 400, 200);
let groupItem = new cw.GroupItem([], {bounds: bounds, immediately: true});
let groupItemId = groupItem.id;
registerCleanupFunction(function() {
let groupItem = cw.GroupItems.groupItem(groupItemId);
if (groupItem)
groupItem.close();
});
return groupItem;
}
let win;
let testFocusTitle = function () {
let title = 'title';
let groupItem = createGroupItem();
let groupItem = cw.GroupItems.groupItems[0];
groupItem.setTitle(title);
let target = groupItem.$titleShield[0];
@ -34,14 +21,21 @@ function test() {
is(input.selectionStart, title.length, 'caret is at the rightmost position and no text is selected');
is(input.selectionEnd, title.length, 'caret is at the rightmost position and no text is selected');
groupItem.close();
hideTabView(finish);
win.close();
finish();
}
waitForExplicitFinish();
showTabView(function () {
cw = TabView.getContentWindow();
testFocusTitle();
newWindowWithTabView(function (tvwin) {
win = tvwin;
registerCleanupFunction(function () {
if (!win.closed)
win.close();
});
cw = win.TabView.getContentWindow();
SimpleTest.waitForFocus(testFocusTitle, cw);
});
}

View File

@ -4,36 +4,20 @@
function test() {
waitForExplicitFinish();
let finishTest = function (groupItem) {
groupItem.addSubscriber(groupItem, 'groupHidden', function () {
groupItem.removeSubscriber(groupItem, 'groupHidden');
groupItem.closeHidden();
hideTabView(finish);
});
groupItem.closeAll();
}
showTabView(function () {
let cw = TabView.getContentWindow();
let bounds = new cw.Rect(20, 20, 150, 200);
let groupItem = new cw.GroupItem([], {bounds: bounds, immediately: true});
cw.GroupItems.setActiveGroupItem(groupItem);
for (let i=0; i<4; i++)
gBrowser.loadOneTab('about:blank', {inBackground: true});
ok(!groupItem._isStacked, 'groupItem is not stacked');
let groupItem = createGroupItemWithBlankTabs(window, 200, 240, 20, 4)
ok(!groupItem.isStacked(), 'groupItem is not stacked');
cw.GroupItems.pauseArrange();
groupItem.setSize(150, 150);
groupItem.setSize(100, 100, true);
groupItem.setUserSize();
ok(!groupItem._isStacked, 'groupItem is still not stacked');
ok(!groupItem.isStacked(), 'groupItem is still not stacked');
cw.GroupItems.resumeArrange();
ok(groupItem._isStacked, 'groupItem is now stacked');
ok(groupItem.isStacked(), 'groupItem is now stacked');
finishTest(groupItem);
closeGroupItem(groupItem, finish);
});
}

View File

@ -0,0 +1,49 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
let cw;
let getTabItemAspect = function (tabItem) {
let bounds = cw.iQ('.thumb', tabItem.container).bounds();
let padding = cw.TabItems.tabItemPadding;
return (bounds.height + padding.y) / (bounds.width + padding.x);
}
let getAspectRange = function () {
let aspect = cw.TabItems.tabAspect;
let variance = aspect / 100 * 1.5;
return new cw.Range(aspect - variance, aspect + variance);
}
waitForExplicitFinish();
newWindowWithTabView(function (win) {
registerCleanupFunction(function () win.close());
cw = win.TabView.getContentWindow();
// prepare orphan tab
let tabItem = win.gBrowser.tabs[0]._tabViewTabItem;
tabItem.parent.remove(tabItem, {immediately: true});
tabItem.setBounds(new cw.Rect(20, 20, 200, 165), true);
let bounds = tabItem.getBounds();
// prepare group item
let box = new cw.Rect(20, 300, 400, 200);
let groupItem = new cw.GroupItem([], {bounds: box, immediately: true});
groupItem.setBounds(new cw.Rect(20, 100, 400, 200));
groupItem.pushAway(true);
let newBounds = tabItem.getBounds();
ok(newBounds.width < bounds.width, "The new width of item is smaller than the old one.");
ok(newBounds.height < bounds.height, "The new height of item is smaller than the old one.");
let aspectRange = getAspectRange();
let aspect = getTabItemAspect(tabItem);
ok(aspectRange.contains(aspect), "orphaned tabItem's aspect is correct");
finish();
});
}

View File

@ -0,0 +1,52 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
let cw;
let win;
waitForExplicitFinish();
newWindowWithTabView(function (tvwin) {
win = tvwin;
cw = win.TabView.getContentWindow();
registerCleanupFunction(function () {
if (win && !win.closed)
win.close();
});
// fill the group item with some tabs
for (let i = 0; i < 5; i++)
win.gBrowser.loadOneTab("about:blank");
let groupItem = cw.GroupItems.groupItems[0];
groupItem.setSize(400, 400, true);
let range = new cw.Range(1, 400);
// determine the groupItem's largest possible stacked size
while (range.extent > 1) {
let pivot = Math.floor(range.extent / 2);
groupItem.setSize(range.min + pivot, range.min + pivot, true);
if (groupItem.isStacked())
range.min += pivot;
else
range.max -= pivot;
}
// stack the group
groupItem.setSize(range.min, range.min, true);
ok(groupItem.isStacked(), "groupItem is stacked");
// one step back to un-stack the groupItem
groupItem.setSize(range.max, range.max, true);
ok(!groupItem.isStacked(), "groupItem is no longer stacked");
// check that close buttons are visible
let tabItem = groupItem.getChild(0);
isnot(tabItem.$close.css("display"), "none", "close button is visible");
finish();
});
}

View File

@ -0,0 +1,64 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
let openMoveToGroupPopup = function () {
let tab = gBrowser.selectedTab;
document.popupNode = tab;
contextMenu.openPopup(tab, "end_after", 0, 0, true, false);
tvMenuPopup.openPopup(tvMenu, "end_after", 0, 0, true, false);
}
let hideMoveToGroupPopup = function () {
tvMenuPopup.hidePopup();
contextMenu.hidePopup();
}
let assertValidPrerequisites = function (visible) {
let cw = TabView.getContentWindow();
is(cw.GroupItems.groupItems.length, 1, "there is one groupItem");
is(gBrowser.tabs.length, 1, "there is one tab");
is(TabView.isVisible(), visible, "tabview is visible");
}
let tvMenu = document.getElementById("context_tabViewMenu");
let contextMenu = document.getElementById("tabContextMenu");
let tvMenuPopup = document.getElementById("context_tabViewMenuPopup");
waitForExplicitFinish();
registerCleanupFunction(function () {
hideMoveToGroupPopup();
hideTabView(function () {});
let groupItems = TabView.getContentWindow().GroupItems.groupItems;
if (groupItems.length > 1)
closeGroupItem(groupItems[0], function () {});
});
showTabView(function () {
assertValidPrerequisites(true);
hideTabView(function () {
let groupItem = createGroupItemWithBlankTabs(window, 200, 200, 10, 1);
groupItem.setTitle("group2");
gBrowser.selectedTab = gBrowser.tabs[0];
executeSoon(function () {
openMoveToGroupPopup();
is(tvMenuPopup.firstChild.getAttribute("label"), "group2", "menuItem is present");
hideMoveToGroupPopup();
closeGroupItem(groupItem, function () {
openMoveToGroupPopup();
is(tvMenuPopup.firstChild.tagName, "menuseparator", "menuItem is not present");
hideMoveToGroupPopup();
assertValidPrerequisites(false);
finish();
});
});
});
});
}

View File

@ -0,0 +1,83 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* Orphans a non-blank tab, duplicates it and checks whether a new group is created with two tabs.
* The original one should be the first tab of the new group.
*
* This prevents overlaid tabs in Tab View (only one tab appears to be there).
* In addition, as only one active orphaned tab is shown when Tab View is hidden
* and there are two tabs shown after the duplication, it also prevents
* the inactive tab to suddenly disappear when toggling Tab View twice.
*
* Covers:
* Bug 645653 - Middle-click on reload button to duplicate orphan tabs does not create a group
* Bug 643119 - Ctrl+Drag to duplicate does not work for orphaned tabs
* ... (and any other way of duplicating a non-blank orphaned tab).
*
* See tabitems.js::_reconnect() for the fix.
*
* Miguel Ojeda <miguel.ojeda.sandonis@gmail.com>
*/
function loadedAboutMozilla(tab) {
return tab.linkedBrowser.contentDocument.getElementById('moztext');
}
function test() {
waitForExplicitFinish();
showTabView(function() {
ok(TabView.isVisible(), "Tab View is visible");
let contentWindow = TabView.getContentWindow();
is(contentWindow.GroupItems.groupItems.length, 1, "There is one group item on startup.");
let originalGroupItem = contentWindow.GroupItems.groupItems[0];
is(originalGroupItem.getChildren().length, 1, "There is one tab item in that group.");
let originalTabItem = originalGroupItem.getChild(0);
ok(originalTabItem, "The tabitem has been found.");
// close the group => orphan the tab
originalGroupItem.close();
contentWindow.GroupItems.setActiveGroupItem(originalGroupItem);
is(contentWindow.GroupItems.groupItems.length, 0, "There are not any groups now.");
ok(TabView.isVisible(), "Tab View is still shown.");
hideTabView(function() {
ok(!TabView.isVisible(), "Tab View is not shown anymore.");
// load a non-blank page
loadURI('about:mozilla');
afterAllTabsLoaded(function() {
ok(loadedAboutMozilla(originalTabItem.tab), "The original tab loaded about:mozilla.");
// duplicate it
duplicateTabIn(originalTabItem.tab, "tabshift");
afterAllTabsLoaded(function() {
// check
is(gBrowser.selectedTab, originalTabItem.tab, "The selected tab is the original one.");
is(contentWindow.GroupItems.groupItems.length, 1, "There is one group item again.");
let groupItem = contentWindow.GroupItems.groupItems[0];
is(groupItem.getChildren().length, 2, "There are two tab items in that group.");
is(originalTabItem, groupItem.getChild(0), "The first tab item in the group is the original one.");
let otherTab = groupItem.getChild(1);
ok(loadedAboutMozilla(otherTab.tab), "The other tab loaded about:mozilla.");
// clean up
gBrowser.removeTab(otherTab.tab);
is(contentWindow.GroupItems.groupItems.length, 1, "There is one group item after closing the second tab.");
is(groupItem.getChildren().length, 1, "There is only one tab item after closing the second tab.");
is(originalTabItem, groupItem.getChild(0), "The first tab item in the group is still the original one.");
loadURI("about:blank");
afterAllTabsLoaded(function() {
finish();
});
});
});
});
});
}

View File

@ -17,7 +17,7 @@ function onTabViewWindowLoaded(win) {
// let's create a group small enough to get stacked
let group = new contentWindow.GroupItem([], {
immediately: true,
bounds: {left: 20, top: 300, width: 300, height: 300}
bounds: {left: 20, top: 300, width: 400, height: 400}
});
let expander = contentWindow.iQ(group.container).find(".stackExpander");

View File

@ -106,7 +106,8 @@ function afterAllTabsLoaded(callback, win) {
for (let a = 0; a < win.gBrowser.tabs.length; a++) {
let browser = win.gBrowser.tabs[a].linkedBrowser;
if (browser.contentDocument.readyState != "complete") {
if (browser.contentDocument.readyState != "complete" ||
browser.webProgress.isLoadingDocument) {
stillToLoad++;
browser.addEventListener("load", onLoad, true);
}

View File

@ -13,7 +13,7 @@
#
# The Original Code is Places test code.
#
# The Initial Developer of the Original Code is Mozilla Corp.
# The Initial Developer of the Original Code is the Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2008
# the Initial Developer. All Rights Reserved.
#

View File

@ -15,7 +15,7 @@
*
* The Original Code is Places test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*

View File

@ -14,7 +14,7 @@
*
* The Original Code is Places test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*

View File

@ -14,7 +14,7 @@
*
* The Original Code is Places test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*

View File

@ -14,7 +14,7 @@
*
* The Original Code is Places test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*

View File

@ -13,7 +13,7 @@
*
* The Original Code is Places test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*

View File

@ -15,7 +15,7 @@
*
* The Original Code is Places test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*

View File

@ -15,7 +15,7 @@
*
* The Original Code is Places test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*

View File

@ -14,7 +14,7 @@
*
* The Original Code is Places test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*

View File

@ -15,7 +15,7 @@
*
* The Original Code is Places test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*

View File

@ -15,7 +15,7 @@
*
* The Original Code is Places test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*

View File

@ -15,7 +15,7 @@
*
* The Original Code is Places test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*

View File

@ -15,7 +15,7 @@
*
* The Original Code is Places test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*

View File

@ -13,7 +13,7 @@
#
# The Original Code is Places test code.
#
# The Initial Developer of the Original Code is Mozilla Corp.
# The Initial Developer of the Original Code is the Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2009
# the Initial Developer. All Rights Reserved.
#

View File

@ -15,7 +15,7 @@
-
- The Original Code is the Places test code.
-
- The Initial Developer of the Original Code is Mozilla Corp.
- The Initial Developer of the Original Code is the Mozilla Foundation.
- Portions created by the Initial Developer are Copyright (C) 2009
- the Initial Developer. All Rights Reserved.
-

View File

@ -15,7 +15,7 @@
-
- The Original Code is the Places test code.
-
- The Initial Developer of the Original Code is Mozilla Corp.
- The Initial Developer of the Original Code is the Mozilla Foundation.
- Portions created by the Initial Developer are Copyright (C) 2009
- the Initial Developer. All Rights Reserved.
-

View File

@ -15,7 +15,7 @@
-
- The Original Code is the Places test code.
-
- The Initial Developer of the Original Code is Mozilla Corp.
- The Initial Developer of the Original Code is the Mozilla Foundation.
- Portions created by the Initial Developer are Copyright (C) 2009
- the Initial Developer. All Rights Reserved.
-

View File

@ -15,7 +15,7 @@
-
- The Original Code is the Places test code.
-
- The Initial Developer of the Original Code is Mozilla Corp.
- The Initial Developer of the Original Code is the Mozilla Foundation.
- Portions created by the Initial Developer are Copyright (C) 2009
- the Initial Developer. All Rights Reserved.
-

View File

@ -15,7 +15,7 @@
*
* The Original Code is Bug 384370 code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*

View File

@ -15,7 +15,7 @@
*
* The Original Code is mozilla.com code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*

View File

@ -15,7 +15,7 @@
*
* The Original Code is Places Unit Test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*

View File

@ -15,7 +15,7 @@
*
* The Original Code is Bug 457441 code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*

View File

@ -15,7 +15,7 @@
*
* The Original Code is Places test code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*

View File

@ -15,7 +15,7 @@
*
* The Original Code is mozilla.com code.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*

View File

@ -13,7 +13,7 @@
*
* The Original Code is Privacy PrefPane Test.
*
* The Initial Developer of the Original Code is Mozilla Corp.
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*

View File

@ -54,3 +54,27 @@
example: Downloading update 111 KB of 13 MB -->
<!ENTITY update.downloading.start "Downloading update — ">
<!ENTITY update.downloading.end "">
<!-- LOCALIZATION NOTE (channel.description.start,channel.description.end): channel.description.start and
channel.description.end create one sentence, with the current channel label inserted in between.
example: You are currently on the _Stable_ update channel. -->
<!ENTITY channel.description.start "You are currently on the ">
<!ENTITY channel.description.end " update channel. ">
<!ENTITY channel.change "Change">
<!ENTITY channel.release.description "Enjoy the tried and tested final release being used by hundreds of millions around the world. Stay in control of your online experience with super speed, easy customization and the latest Web technologies.">
<!ENTITY channel.beta.description "Experience cutting edge features with more stability. Provide feedback to help refine and polish what will be in the final release.">
<!ENTITY channel.aurora.description "Experience the newest innovations in an unstable environment that's not for the faint of heart. Provide feedback on features and performance to help determine what makes the final release.">
<!-- LOCALIZATION NOTE (channel.selector.start,channel.selector.end): channel.selector.start and
channel.selector.end create one sentence, with a channel selection menulist instered in between.
This is all in one line, so try to make the localized text short.
example: Switch to the [Stable] update channel. -->
<!ENTITY channel.selector.start "Switch to the">
<!ENTITY channel.selector.end "update channel.">
<!-- LOCALIZATION NOTE (channel.selector.applyButton): This button applies the user's choice to switch
to a new update channel and starts the application update process. -->
<!ENTITY channel.selector.applyButton "Apply and Update">
<!ENTITY channel.selector.cancelButton "Cancel">

View File

@ -76,8 +76,8 @@ enum nsLinkState {
// IID for the nsIContent interface
#define NS_ICONTENT_IID \
{ 0x5788c9eb, 0x646a, 0x4285, \
{ 0xa2, 0x8c, 0xde, 0x0d, 0x43, 0x6b, 0x47, 0x72 } }
{ 0x32b94ba0, 0x1ebc, 0x4dfc, \
{ 0xba, 0x8c, 0x5f, 0x24, 0x2b, 0xcb, 0xaf, 0xce } }
/**
* A node of content in a document's content model. This interface
@ -800,7 +800,7 @@ public:
* GetIDAttributeName(). This may be null if there is no ID.
*/
nsIAtom* GetID() const {
if (HasFlag(NODE_HAS_ID)) {
if (HasID()) {
return DoGetID();
}
return nsnull;
@ -960,7 +960,7 @@ public:
protected:
/**
* Hook for implementing GetID. This is guaranteed to only be
* called if the NODE_HAS_ID flag is set.
* called if HasID() is true.
*/
virtual nsIAtom* DoGetID() const = 0;

View File

@ -166,7 +166,7 @@ public:
mIsBeingUsedAsImage(PR_FALSE),
mPartID(0)
{
mParentPtrBits |= PARENT_BIT_INDOCUMENT;
SetInDocument();
}
#endif

View File

@ -78,22 +78,19 @@ class Element;
} // namespace mozilla
enum {
// This bit will be set if the node doesn't have nsSlots
NODE_DOESNT_HAVE_SLOTS = 0x00000001U,
// This bit will be set if the node has a listener manager in the listener
// manager hash
NODE_HAS_LISTENERMANAGER = 0x00000002U,
NODE_HAS_LISTENERMANAGER = 0x00000001U,
// Whether this node has had any properties set on it
NODE_HAS_PROPERTIES = 0x00000004U,
NODE_HAS_PROPERTIES = 0x00000002U,
// Whether this node is the root of an anonymous subtree. Note that this
// need not be a native anonymous subtree. Any anonymous subtree, including
// XBL-generated ones, will do. This flag is set-once: once a node has it,
// it must not be removed.
// NOTE: Should only be used on nsIContent nodes
NODE_IS_ANONYMOUS = 0x00000008U,
NODE_IS_ANONYMOUS = 0x00000004U,
// Whether the node has some ancestor, possibly itself, that is native
// anonymous. This includes ancestors crossing XBL scopes, in cases when an
@ -101,44 +98,39 @@ enum {
// ancestor. This flag is set-once: once a node has it, it must not be
// removed.
// NOTE: Should only be used on nsIContent nodes
NODE_IS_IN_ANONYMOUS_SUBTREE = 0x00000010U,
NODE_IS_IN_ANONYMOUS_SUBTREE = 0x00000008U,
// Whether this node is the root of a native anonymous (from the perspective
// of its parent) subtree. This flag is set-once: once a node has it, it
// must not be removed.
// NOTE: Should only be used on nsIContent nodes
NODE_IS_NATIVE_ANONYMOUS_ROOT = 0x00000020U,
NODE_IS_NATIVE_ANONYMOUS_ROOT = 0x00000010U,
// Forces the XBL code to treat this node as if it were
// in the document and therefore should get bindings attached.
NODE_FORCE_XBL_BINDINGS = 0x00000040U,
NODE_FORCE_XBL_BINDINGS = 0x00000020U,
// Whether a binding manager may have a pointer to this
NODE_MAY_BE_IN_BINDING_MNGR = 0x00000080U,
NODE_MAY_BE_IN_BINDING_MNGR = 0x00000040U,
NODE_IS_EDITABLE = 0x00000100U,
NODE_IS_EDITABLE = 0x00000080U,
// Set to true if the element has a non-empty id attribute. This can in rare
// cases lie for nsXMLElement, such as when the node has been moved between
// documents with different id mappings.
NODE_HAS_ID = 0x00000200U,
// For all Element nodes, NODE_MAY_HAVE_CLASS is guaranteed to be set if the
// node in fact has a class, but may be set even if it doesn't.
NODE_MAY_HAVE_CLASS = 0x00000400U,
NODE_MAY_HAVE_STYLE = 0x00000800U,
NODE_MAY_HAVE_CLASS = 0x00000100U,
NODE_IS_INSERTION_PARENT = 0x00001000U,
NODE_IS_INSERTION_PARENT = 0x00000200U,
// Node has an :empty or :-moz-only-whitespace selector
NODE_HAS_EMPTY_SELECTOR = 0x00002000U,
NODE_HAS_EMPTY_SELECTOR = 0x00000400U,
// A child of the node has a selector such that any insertion,
// removal, or appending of children requires restyling the parent.
NODE_HAS_SLOW_SELECTOR = 0x00004000U,
NODE_HAS_SLOW_SELECTOR = 0x00000800U,
// A child of the node has a :first-child, :-moz-first-node,
// :only-child, :last-child or :-moz-last-node selector.
NODE_HAS_EDGE_CHILD_SELECTOR = 0x00008000U,
NODE_HAS_EDGE_CHILD_SELECTOR = 0x00001000U,
// A child of the node has a selector such that any insertion or
// removal of children requires restyling later siblings of that
@ -148,42 +140,33 @@ enum {
// matching :empty due to a grandchild insertion or removal), the
// child's later siblings must also be restyled.
NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS
= 0x00010000U,
= 0x00002000U,
NODE_ALL_SELECTOR_FLAGS = NODE_HAS_EMPTY_SELECTOR |
NODE_HAS_SLOW_SELECTOR |
NODE_HAS_EDGE_CHILD_SELECTOR |
NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS,
NODE_MAY_HAVE_CONTENT_EDITABLE_ATTR
= 0x00020000U,
NODE_ATTACH_BINDING_ON_POSTCREATE
= 0x00040000U,
= 0x00004000U,
// This node needs to go through frame construction to get a frame (or
// undisplayed entry).
NODE_NEEDS_FRAME = 0x00080000U,
NODE_NEEDS_FRAME = 0x00008000U,
// At least one descendant in the flattened tree has NODE_NEEDS_FRAME set.
// This should be set on every node on the flattened tree path between the
// node(s) with NODE_NEEDS_FRAME and the root content.
NODE_DESCENDANTS_NEED_FRAMES = 0x00100000U,
// Set if the node is an element.
NODE_IS_ELEMENT = 0x00200000U,
// Set if the node has the accesskey attribute set.
NODE_HAS_ACCESSKEY = 0x00400000U,
NODE_DESCENDANTS_NEED_FRAMES = 0x00010000U,
// Set if the node has the accesskey attribute set.
NODE_HAS_NAME = 0x00800000U,
NODE_HAS_ACCESSKEY = 0x00020000U,
// Two bits for the script-type ID. Not enough to represent all
// nsIProgrammingLanguage values, but we don't care. In practice,
// we can represent the ones we want, and we can fail the others at
// runtime.
NODE_SCRIPT_TYPE_OFFSET = 24,
NODE_SCRIPT_TYPE_OFFSET = 18,
NODE_SCRIPT_TYPE_SIZE = 2,
@ -294,8 +277,8 @@ private:
// IID for the nsINode interface
#define NS_INODE_IID \
{ 0x2a8dc794, 0x9178, 0x400e, \
{ 0x81, 0xff, 0x55, 0x30, 0x30, 0xb6, 0x74, 0x3b } }
{ 0x4776aa9a, 0xa886, 0x40c9, \
{ 0xae, 0x4c, 0x4d, 0x92, 0xe2, 0xf0, 0xd9, 0x61 } }
/**
* An internal interface that abstracts some DOMNode-related parts that both
@ -316,12 +299,13 @@ public:
#ifdef MOZILLA_INTERNAL_API
nsINode(already_AddRefed<nsINodeInfo> aNodeInfo)
: mNodeInfo(aNodeInfo),
mParentPtrBits(0),
mFlagsOrSlots(NODE_DOESNT_HAVE_SLOTS),
mParent(nsnull),
mFlags(0),
mBoolFlags(0),
mNextSibling(nsnull),
mPreviousSibling(nsnull),
mFirstChild(nsnull),
mNodeHasRenderingObservers(false)
mSlots(nsnull)
{
}
@ -373,8 +357,8 @@ public:
/**
* Return whether the node is an Element node
*/
PRBool IsElement() const {
return HasFlag(NODE_IS_ELEMENT);
bool IsElement() const {
return GetBoolFlag(NodeIsElement);
}
/**
@ -433,9 +417,9 @@ public:
*
* @return whether this content is in a document tree
*/
PRBool IsInDoc() const
bool IsInDoc() const
{
return mParentPtrBits & PARENT_BIT_INDOCUMENT;
return GetBoolFlag(IsInDocument);
}
/**
@ -705,12 +689,9 @@ public:
* Get the parent nsIContent for this node.
* @return the parent, or null if no parent or the parent is not an nsIContent
*/
nsIContent* GetParent() const
{
return NS_LIKELY(mParentPtrBits & PARENT_BIT_PARENT_IS_CONTENT) ?
reinterpret_cast<nsIContent*>
(mParentPtrBits & ~kParentBitMask) :
nsnull;
nsIContent* GetParent() const {
return NS_LIKELY(GetBoolFlag(ParentIsContent)) ?
reinterpret_cast<nsIContent*>(mParent) : nsnull;
}
/**
@ -720,7 +701,7 @@ public:
*/
nsINode* GetNodeParent() const
{
return reinterpret_cast<nsINode*>(mParentPtrBits & ~kParentBitMask);
return mParent;
}
/**
@ -800,9 +781,8 @@ public:
class nsSlots
{
public:
nsSlots(PtrBits aFlags)
: mFlags(aFlags),
mChildNodes(nsnull),
nsSlots()
: mChildNodes(nsnull),
mWeakReference(nsnull)
{
}
@ -811,13 +791,6 @@ public:
// putting a DestroySlots function on nsINode
virtual ~nsSlots();
/**
* Storage for flags for this node. These are the same flags as the
* mFlagsOrSlots member, but these are used when the slots class
* is allocated.
*/
PtrBits mFlags;
/**
* A list of mutation observers
*/
@ -853,12 +826,12 @@ public:
return !!(GetFlags() & aFlag);
}
PtrBits GetFlags() const
PRUint32 GetFlags() const
{
return NS_UNLIKELY(HasSlots()) ? FlagsAsSlots()->mFlags : mFlagsOrSlots;
return mFlags;
}
void SetFlags(PtrBits aFlagsToSet)
void SetFlags(PRUint32 aFlagsToSet)
{
NS_ASSERTION(!(aFlagsToSet & (NODE_IS_ANONYMOUS |
NODE_IS_NATIVE_ANONYMOUS_ROOT |
@ -868,21 +841,17 @@ public:
NODE_NEEDS_FRAME)) ||
IsNodeOfType(eCONTENT),
"Flag only permitted on nsIContent nodes");
PtrBits* flags = HasSlots() ? &FlagsAsSlots()->mFlags :
&mFlagsOrSlots;
*flags |= aFlagsToSet;
mFlags |= aFlagsToSet;
}
void UnsetFlags(PtrBits aFlagsToUnset)
void UnsetFlags(PRUint32 aFlagsToUnset)
{
NS_ASSERTION(!(aFlagsToUnset &
(NODE_IS_ANONYMOUS |
NODE_IS_IN_ANONYMOUS_SUBTREE |
NODE_IS_NATIVE_ANONYMOUS_ROOT)),
"Trying to unset write-only flags");
PtrBits* flags = HasSlots() ? &FlagsAsSlots()->mFlags :
&mFlagsOrSlots;
*flags &= ~aFlagsToUnset;
mFlags &= ~aFlagsToUnset;
}
void SetEditableFlag(PRBool aEditable)
@ -1148,10 +1117,80 @@ public:
NS_NOTREACHED("How did we get here?");
}
bool HasRenderingObservers() { return mNodeHasRenderingObservers; }
void SetHasRenderingObservers(bool aValue)
{ mNodeHasRenderingObservers = aValue; }
/**
* Boolean flags
*/
private:
enum BooleanFlag {
// Set if we're being used from -moz-element
NodeHasRenderingObservers,
// Set if our parent chain (including this node itself) terminates
// in a document
IsInDocument,
// Set if mParent is an nsIContent
ParentIsContent,
// Set if this node is an Element
NodeIsElement,
// Set if the element has a non-empty id attribute. This can in rare
// cases lie for nsXMLElement, such as when the node has been moved between
// documents with different id mappings.
ElementHasID,
// Set if the element might have inline style.
ElementMayHaveStyle,
// Set if the element has a name attribute set.
ElementHasName,
// Set if the element might have a contenteditable attribute set.
ElementMayHaveContentEditableAttr,
// Guard value
BooleanFlagCount
};
void SetBoolFlag(BooleanFlag name, bool value) {
PR_STATIC_ASSERT(BooleanFlagCount <= 8*sizeof(mBoolFlags));
mBoolFlags = (mBoolFlags & ~(1 << name)) | (value << name);
}
void SetBoolFlag(BooleanFlag name) {
PR_STATIC_ASSERT(BooleanFlagCount <= 8*sizeof(mBoolFlags));
mBoolFlags |= (1 << name);
}
void ClearBoolFlag(BooleanFlag name) {
PR_STATIC_ASSERT(BooleanFlagCount <= 8*sizeof(mBoolFlags));
mBoolFlags &= ~(1 << name);
}
bool GetBoolFlag(BooleanFlag name) const {
PR_STATIC_ASSERT(BooleanFlagCount <= 8*sizeof(mBoolFlags));
return mBoolFlags & (1 << name);
}
public:
bool HasRenderingObservers() const
{ return GetBoolFlag(NodeHasRenderingObservers); }
void SetHasRenderingObservers(bool aValue)
{ SetBoolFlag(NodeHasRenderingObservers, aValue); }
bool HasID() const { return GetBoolFlag(ElementHasID); }
bool MayHaveStyle() const { return GetBoolFlag(ElementMayHaveStyle); }
bool HasName() const { return GetBoolFlag(ElementHasName); }
bool MayHaveContentEditableAttr() const
{ return GetBoolFlag(ElementMayHaveContentEditableAttr); }
protected:
void SetParentIsContent(bool aValue) { SetBoolFlag(ParentIsContent, aValue); }
void SetInDocument() { SetBoolFlag(IsInDocument); }
void ClearInDocument() { ClearBoolFlag(IsInDocument); }
void SetIsElement() { SetBoolFlag(NodeIsElement); }
void ClearIsElement() { ClearBoolFlag(NodeIsElement); }
void SetHasID() { SetBoolFlag(ElementHasID); }
void ClearHasID() { ClearBoolFlag(ElementHasID); }
void SetMayHaveStyle() { SetBoolFlag(ElementMayHaveStyle); }
void SetHasName() { SetBoolFlag(ElementHasName); }
void ClearHasName() { ClearBoolFlag(ElementHasName); }
void SetMayHaveContentEditableAttr()
{ SetBoolFlag(ElementMayHaveContentEditableAttr); }
public:
// Optimized way to get classinfo.
virtual nsXPCClassInfo* GetClassInfo() = 0;
protected:
@ -1161,37 +1200,25 @@ protected:
PRBool HasSlots() const
{
return !(mFlagsOrSlots & NODE_DOESNT_HAVE_SLOTS);
}
nsSlots* FlagsAsSlots() const
{
NS_ASSERTION(HasSlots(), "check HasSlots first");
return reinterpret_cast<nsSlots*>(mFlagsOrSlots);
return mSlots != nsnull;
}
nsSlots* GetExistingSlots() const
{
return HasSlots() ? FlagsAsSlots() : nsnull;
return mSlots;
}
nsSlots* GetSlots()
{
if (HasSlots()) {
return FlagsAsSlots();
if (!HasSlots()) {
mSlots = CreateSlots();
}
nsSlots* newSlots = CreateSlots();
if (newSlots) {
mFlagsOrSlots = reinterpret_cast<PtrBits>(newSlots);
}
return newSlots;
return GetExistingSlots();
}
nsTObserverArray<nsIMutationObserver*> *GetMutationObservers()
{
return HasSlots() ? &FlagsAsSlots()->mMutationObservers : nsnull;
return HasSlots() ? &GetExistingSlots()->mMutationObservers : nsnull;
}
PRBool IsEditableInternal() const;
@ -1268,25 +1295,21 @@ protected:
nsCOMPtr<nsINodeInfo> mNodeInfo;
enum { PARENT_BIT_INDOCUMENT = 1 << 0, PARENT_BIT_PARENT_IS_CONTENT = 1 << 1 };
enum { kParentBitMask = 0x3 };
nsINode* mParent;
PtrBits mParentPtrBits;
PRUint32 mFlags;
/**
* Used for either storing flags for this node or a pointer to
* this contents nsContentSlots. See the definition of the
* NODE_* macros for the layout of the bits in this
* member.
*/
PtrBits mFlagsOrSlots;
private:
// Boolean flags.
PRUint32 mBoolFlags;
protected:
nsIContent* mNextSibling;
nsIContent* mPreviousSibling;
nsIContent* mFirstChild;
// More flags
bool mNodeHasRenderingObservers : 1;
// Storage for more members that are usually not needed; allocated lazily.
nsSlots* mSlots;
};

View File

@ -187,7 +187,7 @@ NS_NewDocumentFragment(nsIDOMDocumentFragment** aInstancePtrResult,
nsDocumentFragment::nsDocumentFragment(already_AddRefed<nsINodeInfo> aNodeInfo)
: nsGenericElement(aNodeInfo)
{
UnsetFlags(NODE_IS_ELEMENT);
ClearIsElement();
}
nsDocumentFragment::~nsDocumentFragment()

View File

@ -523,19 +523,19 @@ nsGenericDOMDataNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
// Set parent
if (aParent) {
mParentPtrBits =
reinterpret_cast<PtrBits>(aParent) | PARENT_BIT_PARENT_IS_CONTENT;
mParent = aParent;
}
else {
mParentPtrBits = reinterpret_cast<PtrBits>(aDocument);
mParent = aDocument;
}
SetParentIsContent(aParent);
// XXXbz sXBL/XBL2 issue!
// Set document
if (aDocument) {
// XXX See the comment in nsGenericElement::BindToTree
mParentPtrBits |= PARENT_BIT_INDOCUMENT;
SetInDocument();
if (mText.IsBidi()) {
aDocument->SetBidiEnabled();
}
@ -570,7 +570,11 @@ nsGenericDOMDataNode::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
document->BindingManager()->RemovedFromDocument(this, document);
}
mParentPtrBits = aNullParent ? 0 : mParentPtrBits & ~PARENT_BIT_INDOCUMENT;
if (aNullParent) {
mParent = nsnull;
SetParentIsContent(false);
}
ClearInDocument();
nsDataSlots *slots = GetExistingDataSlots();
if (slots) {
@ -783,7 +787,7 @@ nsGenericDOMDataNode::IsLink(nsIURI** aURI) const
nsINode::nsSlots*
nsGenericDOMDataNode::CreateSlots()
{
return new nsDataSlots(mFlagsOrSlots);
return new nsDataSlots();
}
//----------------------------------------------------------------------

View File

@ -301,8 +301,8 @@ protected:
class nsDataSlots : public nsINode::nsSlots
{
public:
nsDataSlots(PtrBits aFlags)
: nsINode::nsSlots(aFlags),
nsDataSlots()
: nsINode::nsSlots(),
mBindingParent(nsnull)
{
}

View File

@ -273,7 +273,7 @@ nsGenericElement::GetSystemEventGroup(nsIDOMEventGroup** aGroup)
nsINode::nsSlots*
nsINode::CreateSlots()
{
return new nsSlots(mFlagsOrSlots);
return new nsSlots();
}
PRBool
@ -2138,8 +2138,8 @@ nsNodeSelectorTearoff::QuerySelectorAll(const nsAString& aSelector,
}
//----------------------------------------------------------------------
nsGenericElement::nsDOMSlots::nsDOMSlots(PtrBits aFlags)
: nsINode::nsSlots(aFlags),
nsGenericElement::nsDOMSlots::nsDOMSlots()
: nsINode::nsSlots(),
mBindingParent(nsnull)
{
}
@ -2160,8 +2160,8 @@ nsGenericElement::nsGenericElement(already_AddRefed<nsINodeInfo> aNodeInfo)
{
// Set the default scriptID to JS - but skip SetScriptTypeID as it
// does extra work we know isn't necessary here...
SetFlags(NODE_IS_ELEMENT |
(nsIProgrammingLanguage::JAVASCRIPT << NODE_SCRIPT_TYPE_OFFSET));
SetFlags((nsIProgrammingLanguage::JAVASCRIPT << NODE_SCRIPT_TYPE_OFFSET));
SetIsElement();
}
nsGenericElement::~nsGenericElement()
@ -2943,15 +2943,16 @@ nsGenericElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
// Now set the parent and set the "Force attach xbl" flag if needed.
if (aParent) {
mParentPtrBits = reinterpret_cast<PtrBits>(aParent) | PARENT_BIT_PARENT_IS_CONTENT;
mParent = aParent;
if (aParent->HasFlag(NODE_FORCE_XBL_BINDINGS)) {
SetFlags(NODE_FORCE_XBL_BINDINGS);
}
}
else {
mParentPtrBits = reinterpret_cast<PtrBits>(aDocument);
mParent = aDocument;
}
SetParentIsContent(aParent);
// XXXbz sXBL/XBL2 issue!
@ -2967,7 +2968,7 @@ nsGenericElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
// aDocument);
// Being added to a document.
mParentPtrBits |= PARENT_BIT_INDOCUMENT;
SetInDocument();
// Unset this flag since we now really are in a document.
UnsetFlags(NODE_FORCE_XBL_BINDINGS |
@ -3049,7 +3050,11 @@ nsGenericElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
nsIDocument *document =
HasFlag(NODE_FORCE_XBL_BINDINGS) ? GetOwnerDoc() : GetCurrentDoc();
mParentPtrBits = aNullParent ? 0 : mParentPtrBits & ~PARENT_BIT_INDOCUMENT;
if (aNullParent) {
mParent = nsnull;
SetParentIsContent(false);
}
ClearInDocument();
if (document) {
// Notify XBL- & nsIAnonymousContentCreator-generated
@ -5287,7 +5292,7 @@ nsGenericElement::IndexOf(nsINode* aPossibleChild) const
nsINode::nsSlots*
nsGenericElement::CreateSlots()
{
return new nsDOMSlots(mFlagsOrSlots);
return new nsDOMSlots();
}
PRBool

View File

@ -943,7 +943,7 @@ public:
class nsDOMSlots : public nsINode::nsSlots
{
public:
nsDOMSlots(PtrBits aFlags);
nsDOMSlots();
virtual ~nsDOMSlots();
/**
@ -1024,14 +1024,14 @@ protected:
* Add/remove this element to the documents id cache
*/
void AddToIdTable(nsIAtom* aId) {
NS_ASSERTION(HasFlag(NODE_HAS_ID), "Node lacking NODE_HAS_ID flag");
NS_ASSERTION(HasID(), "Node doesn't have an ID?");
nsIDocument* doc = GetCurrentDoc();
if (doc && (!IsInAnonymousSubtree() || doc->IsXUL())) {
doc->AddToIdTable(this, aId);
}
}
void RemoveFromIdTable() {
if (HasFlag(NODE_HAS_ID)) {
if (HasID()) {
nsIDocument* doc = GetCurrentDoc();
if (doc) {
nsIAtom* id = DoGetID();

View File

@ -252,9 +252,8 @@ nsNodeUtils::LastRelease(nsINode* aNode)
NodeWillBeDestroyed, (aNode));
}
PtrBits flags = slots->mFlags | NODE_DOESNT_HAVE_SLOTS;
delete slots;
aNode->mFlagsOrSlots = flags;
aNode->mSlots = nsnull;
}
// Kill properties first since that may run external code, so we want to

View File

@ -63,25 +63,25 @@ namespace css = mozilla::css;
// nsIContent methods
nsIAtom*
nsStyledElement::GetClassAttributeName() const
nsStyledElementNotElementCSSInlineStyle::GetClassAttributeName() const
{
return nsGkAtoms::_class;
}
nsIAtom*
nsStyledElement::GetIDAttributeName() const
nsStyledElementNotElementCSSInlineStyle::GetIDAttributeName() const
{
return nsGkAtoms::id;
}
nsIAtom*
nsStyledElement::DoGetID() const
nsStyledElementNotElementCSSInlineStyle::DoGetID() const
{
NS_ASSERTION(HasFlag(NODE_HAS_ID), "Unexpected call");
NS_ASSERTION(HasID(), "Unexpected call");
// The nullcheck here is needed because nsGenericElement::UnsetAttr calls
// out to various code between removing the attribute and we get a chance to
// clear the NODE_HAS_ID flag.
// ClearHasID().
const nsAttrValue* attr = mAttrsAndChildren.GetAttr(nsGkAtoms::id);
@ -89,19 +89,21 @@ nsStyledElement::DoGetID() const
}
const nsAttrValue*
nsStyledElement::DoGetClasses() const
nsStyledElementNotElementCSSInlineStyle::DoGetClasses() const
{
NS_ASSERTION(HasFlag(NODE_MAY_HAVE_CLASS), "Unexpected call");
return mAttrsAndChildren.GetAttr(nsGkAtoms::_class);
}
PRBool
nsStyledElement::ParseAttribute(PRInt32 aNamespaceID, nsIAtom* aAttribute,
const nsAString& aValue, nsAttrValue& aResult)
nsStyledElementNotElementCSSInlineStyle::ParseAttribute(PRInt32 aNamespaceID,
nsIAtom* aAttribute,
const nsAString& aValue,
nsAttrValue& aResult)
{
if (aNamespaceID == kNameSpaceID_None) {
if (aAttribute == nsGkAtoms::style) {
SetFlags(NODE_MAY_HAVE_STYLE);
SetMayHaveStyle();
ParseStyleAttribute(aValue, aResult, PR_FALSE);
return PR_TRUE;
}
@ -115,11 +117,11 @@ nsStyledElement::ParseAttribute(PRInt32 aNamespaceID, nsIAtom* aAttribute,
// not that it has an emptystring as the id.
RemoveFromIdTable();
if (aValue.IsEmpty()) {
UnsetFlags(NODE_HAS_ID);
ClearHasID();
return PR_FALSE;
}
aResult.ParseAtom(aValue);
SetFlags(NODE_HAS_ID);
SetHasID();
AddToIdTable(aResult.GetAtomValue());
return PR_TRUE;
}
@ -130,8 +132,9 @@ nsStyledElement::ParseAttribute(PRInt32 aNamespaceID, nsIAtom* aAttribute,
}
nsresult
nsStyledElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
PRBool aNotify)
nsStyledElementNotElementCSSInlineStyle::UnsetAttr(PRInt32 aNameSpaceID,
nsIAtom* aAttribute,
PRBool aNotify)
{
nsAutoRemovableScriptBlocker scriptBlocker;
if (aAttribute == nsGkAtoms::id && aNameSpaceID == kNameSpaceID_None) {
@ -143,15 +146,17 @@ nsStyledElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
}
nsresult
nsStyledElement::AfterSetAttr(PRInt32 aNamespaceID, nsIAtom* aAttribute,
const nsAString* aValue, PRBool aNotify)
nsStyledElementNotElementCSSInlineStyle::AfterSetAttr(PRInt32 aNamespaceID,
nsIAtom* aAttribute,
const nsAString* aValue,
PRBool aNotify)
{
if (aNamespaceID == kNameSpaceID_None && !aValue &&
aAttribute == nsGkAtoms::id) {
// The id has been removed when calling UnsetAttr but we kept it because
// the id is used for some layout stuff between UnsetAttr and AfterSetAttr.
// Now. the id is really removed so it would not be safe to keep this flag.
UnsetFlags(NODE_HAS_ID);
ClearHasID();
}
return nsGenericElement::AfterSetAttr(aNamespaceID, aAttribute, aValue,
@ -159,9 +164,10 @@ nsStyledElement::AfterSetAttr(PRInt32 aNamespaceID, nsIAtom* aAttribute,
}
NS_IMETHODIMP
nsStyledElement::SetInlineStyleRule(css::StyleRule* aStyleRule, PRBool aNotify)
nsStyledElementNotElementCSSInlineStyle::SetInlineStyleRule(css::StyleRule* aStyleRule,
PRBool aNotify)
{
SetFlags(NODE_MAY_HAVE_STYLE);
SetMayHaveStyle();
PRBool modification = PR_FALSE;
nsAutoString oldValueStr;
@ -198,9 +204,9 @@ nsStyledElement::SetInlineStyleRule(css::StyleRule* aStyleRule, PRBool aNotify)
}
css::StyleRule*
nsStyledElement::GetInlineStyleRule()
nsStyledElementNotElementCSSInlineStyle::GetInlineStyleRule()
{
if (!HasFlag(NODE_MAY_HAVE_STYLE)) {
if (!MayHaveStyle()) {
return nsnull;
}
const nsAttrValue* attrVal = mAttrsAndChildren.GetAttr(nsGkAtoms::style);
@ -213,16 +219,17 @@ nsStyledElement::GetInlineStyleRule()
}
nsresult
nsStyledElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent,
PRBool aCompileEventHandlers)
nsStyledElementNotElementCSSInlineStyle::BindToTree(nsIDocument* aDocument,
nsIContent* aParent,
nsIContent* aBindingParent,
PRBool aCompileEventHandlers)
{
nsresult rv = nsStyledElementBase::BindToTree(aDocument, aParent,
aBindingParent,
aCompileEventHandlers);
NS_ENSURE_SUCCESS(rv, rv);
if (aDocument && HasFlag(NODE_HAS_ID) && !GetBindingParent()) {
if (aDocument && HasID() && !GetBindingParent()) {
aDocument->AddToIdTable(this, DoGetID());
}
@ -236,7 +243,8 @@ nsStyledElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
}
void
nsStyledElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
nsStyledElementNotElementCSSInlineStyle::UnbindFromTree(PRBool aDeep,
PRBool aNullParent)
{
RemoveFromIdTable();
@ -248,7 +256,7 @@ nsStyledElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
// Others and helpers
nsIDOMCSSStyleDeclaration*
nsStyledElement::GetStyle(nsresult* retval)
nsStyledElementNotElementCSSInlineStyle::GetStyle(nsresult* retval)
{
nsXULElement* xulElement = nsXULElement::FromContent(this);
if (xulElement) {
@ -270,7 +278,7 @@ nsStyledElement::GetStyle(nsresult* retval)
, PR_FALSE
#endif // MOZ_SMIL
);
SetFlags(NODE_MAY_HAVE_STYLE);
SetMayHaveStyle();
}
*retval = NS_OK;
@ -278,9 +286,9 @@ nsStyledElement::GetStyle(nsresult* retval)
}
nsresult
nsStyledElement::ReparseStyleAttribute(PRBool aForceInDataDoc)
nsStyledElementNotElementCSSInlineStyle::ReparseStyleAttribute(PRBool aForceInDataDoc)
{
if (!HasFlag(NODE_MAY_HAVE_STYLE)) {
if (!MayHaveStyle()) {
return NS_OK;
}
const nsAttrValue* oldVal = mAttrsAndChildren.GetAttr(nsGkAtoms::style);
@ -300,9 +308,9 @@ nsStyledElement::ReparseStyleAttribute(PRBool aForceInDataDoc)
}
void
nsStyledElement::ParseStyleAttribute(const nsAString& aValue,
nsAttrValue& aResult,
PRBool aForceInDataDoc)
nsStyledElementNotElementCSSInlineStyle::ParseStyleAttribute(const nsAString& aValue,
nsAttrValue& aResult,
PRBool aForceInDataDoc)
{
nsIDocument* doc = GetOwnerDoc();

View File

@ -57,12 +57,12 @@ class StyleRule;
typedef nsGenericElement nsStyledElementBase;
class nsStyledElement : public nsStyledElementBase
class nsStyledElementNotElementCSSInlineStyle : public nsStyledElementBase
{
protected:
inline nsStyledElement(already_AddRefed<nsINodeInfo> aNodeInfo)
inline nsStyledElementNotElementCSSInlineStyle(already_AddRefed<nsINodeInfo> aNodeInfo)
: nsStyledElementBase(aNodeInfo)
{}
@ -114,4 +114,11 @@ protected:
nsresult ReparseStyleAttribute(PRBool aForceInDataDoc);
};
class nsStyledElement : public nsStyledElementNotElementCSSInlineStyle {
protected:
inline nsStyledElement(already_AddRefed<nsINodeInfo> aNodeInfo)
: nsStyledElementNotElementCSSInlineStyle(aNodeInfo)
{}
};
#endif // __NS_STYLEDELEMENT_H_

View File

@ -215,7 +215,9 @@ nsTextNode::BindToAttribute(nsIAttribute* aAttr)
NS_ASSERTION(!GetNodeParent(), "Unbind before binding!");
NS_ASSERTION(HasSameOwnerDoc(aAttr), "Wrong owner document!");
mParentPtrBits = reinterpret_cast<PtrBits>(aAttr);
mParent = aAttr;
SetParentIsContent(false);
ClearInDocument();
return NS_OK;
}
@ -226,7 +228,7 @@ nsTextNode::UnbindFromAttribute()
NS_ASSERTION(GetNodeParent() &&
GetNodeParent()->IsNodeOfType(nsINode::eATTRIBUTE),
"Use this method only to unbind from an attribute!");
mParentPtrBits = 0;
mParent = nsnull;
return NS_OK;
}

View File

@ -1119,6 +1119,7 @@ nsCanvasRenderingContext2D::SetDimensions(PRInt32 width, PRInt32 height)
}
gCanvasMemoryUsed += width * height * 4;
JS_updateMallocCounter(nsContentUtils::GetCurrentJSContext(), width * height * 4);
}
return InitializeWithSurface(NULL, surface, width, height);
@ -3844,32 +3845,51 @@ nsCanvasRenderingContext2D::GetImageData_explicit(PRInt32 x, PRInt32 y, PRUint32
return NS_ERROR_DOM_SECURITY_ERR;
}
if (w == 0 || h == 0)
if (w == 0 || h == 0 || aDataLen != w * h * 4)
return NS_ERROR_DOM_SYNTAX_ERR;
if (!CanvasUtils::CheckSaneSubrectSize (x, y, w, h, mWidth, mHeight))
return NS_ERROR_DOM_SYNTAX_ERR;
CheckedInt32 rightMost = CheckedInt32(x) + w;
CheckedInt32 bottomMost = CheckedInt32(y) + h;
PRUint32 len = w * h * 4;
if (aDataLen != len)
if (!rightMost.valid() || !bottomMost.valid())
return NS_ERROR_DOM_SYNTAX_ERR;
/* Copy the surface contents to the buffer */
nsRefPtr<gfxImageSurface> tmpsurf = new gfxImageSurface(aData,
gfxIntSize(w, h),
w * 4,
gfxASurface::ImageFormatARGB32);
if (!tmpsurf || tmpsurf->CairoStatus())
nsRefPtr<gfxImageSurface> tmpsurf =
new gfxImageSurface(aData,
gfxIntSize(w, h),
w * 4,
gfxASurface::ImageFormatARGB32);
if (tmpsurf->CairoStatus())
return NS_ERROR_FAILURE;
nsRefPtr<gfxContext> tmpctx = new gfxContext(tmpsurf);
if (!tmpctx || tmpctx->HasError())
if (tmpctx->HasError())
return NS_ERROR_FAILURE;
tmpctx->SetOperator(gfxContext::OPERATOR_SOURCE);
tmpctx->SetSource(mSurface, gfxPoint(-(int)x, -(int)y));
tmpctx->Paint();
gfxRect srcRect(0, 0, mWidth, mHeight);
gfxRect destRect(x, y, w, h);
bool finishedPainting = false;
// In the common case, we want to avoid the Rectangle call.
if (!srcRect.Contains(destRect)) {
// If the requested area is entirely outside the canvas, we're done.
gfxRect tmp = srcRect.Intersect(destRect);
finishedPainting = tmp.IsEmpty();
// Set clipping region if necessary.
if (!finishedPainting) {
tmpctx->Rectangle(tmp);
}
}
if (!finishedPainting) {
tmpctx->SetOperator(gfxContext::OPERATOR_SOURCE);
tmpctx->SetSource(mSurface, gfxPoint(-x, -y));
tmpctx->Paint();
}
// make sure sUnpremultiplyTable has been created
EnsureUnpremultiplyTable();

View File

@ -7957,19 +7957,15 @@ ctx.fillRect(0, 0, 100, 50);
ctx.fillStyle = '#fff';
ctx.fillRect(20, 10, 60, 10);
try {
var imgdata1 = ctx.getImageData(85, 25, -10, -10);
ok(imgdata1.data[0] === 255, "imgdata1.data[\""+(0)+"\"] === 255");
ok(imgdata1.data[1] === 255, "imgdata1.data[\""+(1)+"\"] === 255");
ok(imgdata1.data[2] === 255, "imgdata1.data[\""+(2)+"\"] === 255");
ok(imgdata1.data[3] === 255, "imgdata1.data[\""+(3)+"\"] === 255");
ok(imgdata1.data[imgdata1.data.length-4+0] === 0, "imgdata1.data[imgdata1.data.length-4+0] === 0");
ok(imgdata1.data[imgdata1.data.length-4+1] === 0, "imgdata1.data[imgdata1.data.length-4+1] === 0");
ok(imgdata1.data[imgdata1.data.length-4+2] === 0, "imgdata1.data[imgdata1.data.length-4+2] === 0");
ok(imgdata1.data[imgdata1.data.length-4+3] === 255, "imgdata1.data[imgdata1.data.length-4+3] === 255");
} catch (e) {
ok(false, "Unexpected exception in first part of test_2d_imageData_get_source_negative()", e);
}
var imgdata1 = ctx.getImageData(85, 25, -10, -10);
ok(imgdata1.data[0] === 255, "imgdata1.data[\""+(0)+"\"] === 255");
ok(imgdata1.data[1] === 255, "imgdata1.data[\""+(1)+"\"] === 255");
ok(imgdata1.data[2] === 255, "imgdata1.data[\""+(2)+"\"] === 255");
ok(imgdata1.data[3] === 255, "imgdata1.data[\""+(3)+"\"] === 255");
ok(imgdata1.data[imgdata1.data.length-4+0] === 0, "imgdata1.data[imgdata1.data.length-4+0] === 0");
ok(imgdata1.data[imgdata1.data.length-4+1] === 0, "imgdata1.data[imgdata1.data.length-4+1] === 0");
ok(imgdata1.data[imgdata1.data.length-4+2] === 0, "imgdata1.data[imgdata1.data.length-4+2] === 0");
ok(imgdata1.data[imgdata1.data.length-4+3] === 255, "imgdata1.data[imgdata1.data.length-4+3] === 255");
var imgdata2 = ctx.getImageData(0, 0, -1, -1);
ok(imgdata2.data[0] === 0, "imgdata2.data[\""+(0)+"\"] === 0");
@ -7980,7 +7976,7 @@ ok(imgdata2.data[3] === 0, "imgdata2.data[\""+(3)+"\"] === 0");
} catch (e) {
_thrown_outer = true;
}
todo(!_thrown_outer, 'should not throw exception');
ok(!_thrown_outer, 'should not throw exception');
}
@ -8028,10 +8024,44 @@ ok(imgdata4.data[1] === 0, "imgdata4.data[\""+(1)+"\"] === 0");
ok(imgdata4.data[2] === 0, "imgdata4.data[\""+(2)+"\"] === 0");
ok(imgdata4.data[3] === 0, "imgdata4.data[\""+(3)+"\"] === 0");
var imgdata5 = ctx.getImageData(100, 10, 1, 1);
ok(imgdata5.data[0] === 0, "imgdata5.data[\""+(0)+"\"] === 0");
ok(imgdata5.data[1] === 0, "imgdata5.data[\""+(1)+"\"] === 0");
ok(imgdata5.data[2] === 0, "imgdata5.data[\""+(2)+"\"] === 0");
ok(imgdata5.data[3] === 0, "imgdata5.data[\""+(3)+"\"] === 0");
var imgdata6 = ctx.getImageData(0, 10, 1, 1);
ok(imgdata6.data[0] === 0, "imgdata6.data[\""+(0)+"\"] === 0");
ok(imgdata6.data[1] === 136, "imgdata6.data[\""+(1)+"\"] === 136");
ok(imgdata6.data[2] === 255, "imgdata6.data[\""+(2)+"\"] === 255");
ok(imgdata6.data[3] === 255, "imgdata6.data[\""+(3)+"\"] === 255");
var imgdata7 = ctx.getImageData(-10, 10, 20, 20);
ok(imgdata7.data[ 0*4+0] === 0, "imgdata7.data[ 0*4+0] === 0");
ok(imgdata7.data[ 0*4+1] === 0, "imgdata7.data[ 0*4+1] === 0");
ok(imgdata7.data[ 0*4+2] === 0, "imgdata7.data[ 0*4+2] === 0");
ok(imgdata7.data[ 0*4+3] === 0, "imgdata7.data[ 0*4+3] === 0");
ok(imgdata7.data[ 9*4+0] === 0, "imgdata7.data[ 9*4+0] === 0");
ok(imgdata7.data[ 9*4+1] === 0, "imgdata7.data[ 9*4+1] === 0");
ok(imgdata7.data[ 9*4+2] === 0, "imgdata7.data[ 9*4+2] === 0");
ok(imgdata7.data[ 9*4+3] === 0, "imgdata7.data[ 9*4+3] === 0");
ok(imgdata7.data[10*4+0] === 0, "imgdata7.data[10*4+0] === 0");
ok(imgdata7.data[10*4+1] === 136, "imgdata7.data[10*4+1] === 136");
ok(imgdata7.data[10*4+2] === 255, "imgdata7.data[10*4+2] === 255");
ok(imgdata7.data[10*4+3] === 255, "imgdata7.data[10*4+3] === 255");
ok(imgdata7.data[19*4+0] === 0, "imgdata7.data[19*4+0] === 0");
ok(imgdata7.data[19*4+1] === 136, "imgdata7.data[19*4+1] === 136");
ok(imgdata7.data[19*4+2] === 255, "imgdata7.data[19*4+2] === 255");
ok(imgdata7.data[19*4+3] === 255, "imgdata7.data[19*4+3] === 255");
ok(imgdata7.data[20*4+0] === 0, "imgdata7.data[20*4+0] === 0");
ok(imgdata7.data[20*4+1] === 0, "imgdata7.data[20*4+1] === 0");
ok(imgdata7.data[20*4+2] === 0, "imgdata7.data[20*4+2] === 0");
ok(imgdata7.data[20*4+3] === 0, "imgdata7.data[20*4+3] === 0");
} catch (e) {
_thrown_outer = true;
}
todo(!_thrown_outer, 'should not throw exception');
ok(!_thrown_outer, 'should not throw exception');
}

View File

@ -948,7 +948,7 @@ nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
NS_ENSURE_SUCCESS(rv, rv);
if (aDocument) {
if (HasFlag(NODE_HAS_NAME)) {
if (HasName()) {
aDocument->
AddToNameTable(this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue());
}
@ -1188,7 +1188,7 @@ nsGenericHTMLElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
PRInt32 change;
if (contentEditable) {
change = GetContentEditableValue() == eTrue ? -1 : 0;
SetFlags(NODE_MAY_HAVE_CONTENT_EDITABLE_ATTR);
SetMayHaveContentEditableAttr();
}
nsresult rv = nsStyledElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
@ -1218,7 +1218,7 @@ nsGenericHTMLElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
if (aAttribute == nsGkAtoms::name) {
// Have to do this before clearing flag. See RemoveFromNameTable
RemoveFromNameTable();
UnsetFlags(NODE_HAS_NAME);
ClearHasName();
}
else if (aAttribute == nsGkAtoms::contenteditable) {
contentEditable = PR_TRUE;
@ -1287,14 +1287,14 @@ nsGenericHTMLElement::ParseAttribute(PRInt32 aNamespaceID,
// not that it has an emptystring as the name.
RemoveFromNameTable();
if (aValue.IsEmpty()) {
UnsetFlags(NODE_HAS_NAME);
ClearHasName();
return PR_FALSE;
}
aResult.ParseAtom(aValue);
if (CanHaveName(Tag())) {
SetFlags(NODE_HAS_NAME);
SetHasName();
AddToNameTable(aResult.GetAtomValue());
}

View File

@ -527,14 +527,14 @@ protected:
* Add/remove this element to the documents name cache
*/
void AddToNameTable(nsIAtom* aName) {
NS_ASSERTION(HasFlag(NODE_HAS_NAME), "Node lacking NODE_HAS_NAME flag");
NS_ASSERTION(HasName(), "Node doesn't have name?");
nsIDocument* doc = GetCurrentDoc();
if (doc && !IsInAnonymousSubtree()) {
doc->AddToNameTable(this, aName);
}
}
void RemoveFromNameTable() {
if (HasFlag(NODE_HAS_NAME)) {
if (HasName()) {
nsIDocument* doc = GetCurrentDoc();
if (doc) {
doc->RemoveFromNameTable(this, GetParsedAttr(nsGkAtoms::name)->
@ -791,7 +791,7 @@ protected:
static const nsIContent::AttrValuesArray values[] =
{ &nsGkAtoms::_false, &nsGkAtoms::_true, &nsGkAtoms::_empty, nsnull };
if (!HasFlag(NODE_MAY_HAVE_CONTENT_EDITABLE_ATTR))
if (!MayHaveContentEditableAttr())
return eInherit;
PRInt32 value = FindAttrValueIn(kNameSpaceID_None,

View File

@ -2613,7 +2613,7 @@ FindNamedItems(nsIAtom* aName, nsIContent *aContent,
NS_ASSERTION(!aEntry->IsInvalidName(),
"Entry that should never have a list passed to FindNamedItems()!");
if (aContent->HasFlag(NODE_HAS_NAME)) {
if (aContent->HasName()) {
NS_ASSERTION(nsGenericHTMLElement::FromContent(aContent),
"Only HTML Elements should have a name");

View File

@ -70,7 +70,8 @@ nsMediaChannelStream::nsMediaChannelStream(nsMediaDecoder* aDecoder,
mReopenOnError(PR_FALSE), mIgnoreClose(PR_FALSE),
mCacheStream(this),
mLock("nsMediaChannelStream.mLock"),
mCacheSuspendCount(0)
mCacheSuspendCount(0),
mIgnoreResume(PR_FALSE)
{
}
@ -276,7 +277,10 @@ nsMediaChannelStream::OnStartRequest(nsIRequest* aRequest)
mIgnoreClose = PR_FALSE;
if (mSuspendCount > 0) {
// Re-suspend the channel if it needs to be suspended
// No need to call PossiblySuspend here since the channel is
// definitely in the right state for us in OneStartRequest.
mChannel->Suspend();
mIgnoreResume = PR_FALSE;
}
// Fires an initial progress event and sets up the stall counter so stall events
@ -556,7 +560,7 @@ void nsMediaChannelStream::CloseChannel()
if (mChannel) {
if (mSuspendCount > 0) {
// Resume the channel before we cancel it
mChannel->Resume();
PossiblyResume();
}
// The status we use here won't be passed to the decoder, since
// we've already revoked the listener. It can however be passed
@ -626,7 +630,7 @@ void nsMediaChannelStream::Suspend(PRBool aCloseImmediately)
MutexAutoLock lock(mLock);
mChannelStatistics.Stop(TimeStamp::Now());
}
mChannel->Suspend();
PossiblySuspend();
element->DownloadSuspended();
}
}
@ -657,7 +661,7 @@ void nsMediaChannelStream::Resume()
// if an error occurs after Resume, assume it's because the server
// timed out the connection and we should reopen it.
mReopenOnError = PR_TRUE;
mChannel->Resume();
PossiblyResume();
element->DownloadResumed();
} else {
PRInt64 totalLength = mCacheStream.GetLength();
@ -867,6 +871,29 @@ nsMediaChannelStream::GetLength()
return mCacheStream.GetLength();
}
void
nsMediaChannelStream::PossiblySuspend()
{
PRBool isPending = PR_FALSE;
nsresult rv = mChannel->IsPending(&isPending);
if (NS_SUCCEEDED(rv) && isPending) {
mChannel->Suspend();
mIgnoreResume = PR_FALSE;
} else {
mIgnoreResume = PR_TRUE;
}
}
void
nsMediaChannelStream::PossiblyResume()
{
if (!mIgnoreResume) {
mChannel->Resume();
} else {
mIgnoreResume = PR_FALSE;
}
}
class nsMediaFileStream : public nsMediaStream
{
public:

View File

@ -452,6 +452,14 @@ protected:
PRUint32 aCount,
PRUint32 *aWriteCount);
// Suspend the channel only if the channels is currently downloading data.
// If it isn't we set a flag, mIgnoreResume, so that PossiblyResume knows
// whether to acutually resume or not.
void PossiblySuspend();
// Resume from a suspend if we actually suspended (See PossiblySuspend).
void PossiblyResume();
// Main thread access only
PRInt64 mOffset;
nsRefPtr<Listener> mListener;
@ -473,6 +481,11 @@ protected:
Mutex mLock;
nsChannelStatistics mChannelStatistics;
PRUint32 mCacheSuspendCount;
// PR_TRUE if we couldn't suspend the stream and we therefore don't want
// to resume later. This is usually due to the channel not being in the
// isPending state at the time of the suspend request.
PRPackedBool mIgnoreResume;
};
#endif

View File

@ -92,6 +92,21 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGLengthList)
NS_INTERFACE_MAP_END
nsIDOMSVGLength*
DOMSVGLengthList::GetItemWithoutAddRef(PRUint32 aIndex)
{
#ifdef MOZ_SMIL
if (IsAnimValList()) {
Element()->FlushAnimations();
}
#endif
if (aIndex < Length()) {
EnsureItemAt(aIndex);
return mItems[aIndex];
}
return nsnull;
}
void
DOMSVGLengthList::InternalListLengthWillChange(PRUint32 aNewLength)
{
@ -210,18 +225,12 @@ NS_IMETHODIMP
DOMSVGLengthList::GetItem(PRUint32 index,
nsIDOMSVGLength **_retval)
{
#ifdef MOZ_SMIL
if (IsAnimValList()) {
Element()->FlushAnimations();
*_retval = GetItemWithoutAddRef(index);
if (!*_retval) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
#endif
if (index < Length()) {
EnsureItemAt(index);
NS_ADDREF(*_retval = mItems[index]);
return NS_OK;
}
*_retval = nsnull;
return NS_ERROR_DOM_INDEX_SIZE_ERR;
NS_ADDREF(*_retval);
return NS_OK;
}
NS_IMETHODIMP
@ -367,6 +376,12 @@ DOMSVGLengthList::AppendItem(nsIDOMSVGLength *newItem,
return InsertItemBefore(newItem, Length(), _retval);
}
NS_IMETHODIMP
DOMSVGLengthList::GetLength(PRUint32 *aNumberOfItems)
{
return GetNumberOfItems(aNumberOfItems);
}
void
DOMSVGLengthList::EnsureItemAt(PRUint32 aIndex)
{

View File

@ -109,6 +109,8 @@ public:
return mItems.Length();
}
nsIDOMSVGLength* GetItemWithoutAddRef(PRUint32 aIndex);
/// Called to notify us to syncronize our length and detach excess items.
void InternalListLengthWillChange(PRUint32 aNewLength);

View File

@ -92,6 +92,21 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGNumberList)
NS_INTERFACE_MAP_END
nsIDOMSVGNumber*
DOMSVGNumberList::GetItemWithoutAddRef(PRUint32 aIndex)
{
#ifdef MOZ_SMIL
if (IsAnimValList()) {
Element()->FlushAnimations();
}
#endif
if (aIndex < Length()) {
EnsureItemAt(aIndex);
return mItems[aIndex];
}
return nsnull;
}
void
DOMSVGNumberList::InternalListLengthWillChange(PRUint32 aNewLength)
{
@ -210,18 +225,12 @@ NS_IMETHODIMP
DOMSVGNumberList::GetItem(PRUint32 index,
nsIDOMSVGNumber **_retval)
{
#ifdef MOZ_SMIL
if (IsAnimValList()) {
Element()->FlushAnimations();
*_retval = GetItemWithoutAddRef(index);
if (!*_retval) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
#endif
if (index < Length()) {
EnsureItemAt(index);
NS_ADDREF(*_retval = mItems[index]);
return NS_OK;
}
*_retval = nsnull;
return NS_ERROR_DOM_INDEX_SIZE_ERR;
NS_ADDREF(*_retval);
return NS_OK;
}
NS_IMETHODIMP
@ -367,6 +376,12 @@ DOMSVGNumberList::AppendItem(nsIDOMSVGNumber *newItem,
return InsertItemBefore(newItem, Length(), _retval);
}
NS_IMETHODIMP
DOMSVGNumberList::GetLength(PRUint32 *aNumberOfItems)
{
return GetNumberOfItems(aNumberOfItems);
}
void
DOMSVGNumberList::EnsureItemAt(PRUint32 aIndex)
{

View File

@ -108,6 +108,8 @@ public:
return mItems.Length();
}
nsIDOMSVGNumber* GetItemWithoutAddRef(PRUint32 aIndex);
/// Called to notify us to syncronize our length and detach excess items.
void InternalListLengthWillChange(PRUint32 aNewLength);

View File

@ -96,6 +96,21 @@ DOMSVGPathSegList::~DOMSVGPathSegList()
sSVGPathSegListTearoffTable.RemoveTearoff(key);
}
nsIDOMSVGPathSeg*
DOMSVGPathSegList::GetItemWithoutAddRef(PRUint32 aIndex)
{
#ifdef MOZ_SMIL
if (IsAnimValList()) {
Element()->FlushAnimations();
}
#endif
if (aIndex < Length()) {
EnsureItemAt(aIndex);
return ItemAt(aIndex);
}
return nsnull;
}
void
DOMSVGPathSegList::InternalListWillChangeTo(const SVGPathData& aNewValue)
{
@ -305,18 +320,12 @@ NS_IMETHODIMP
DOMSVGPathSegList::GetItem(PRUint32 aIndex,
nsIDOMSVGPathSeg **_retval)
{
#ifdef MOZ_SMIL
if (IsAnimValList()) {
Element()->FlushAnimations();
*_retval = GetItemWithoutAddRef(aIndex);
if (!*_retval) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
#endif
if (aIndex < Length()) {
EnsureItemAt(aIndex);
NS_ADDREF(*_retval = ItemAt(aIndex));
return NS_OK;
}
*_retval = nsnull;
return NS_ERROR_DOM_INDEX_SIZE_ERR;
NS_ADDREF(*_retval);
return NS_OK;
}
NS_IMETHODIMP
@ -498,6 +507,12 @@ DOMSVGPathSegList::AppendItem(nsIDOMSVGPathSeg *aNewItem,
return InsertItemBefore(aNewItem, Length(), _retval);
}
NS_IMETHODIMP
DOMSVGPathSegList::GetLength(PRUint32 *aNumberOfItems)
{
return GetNumberOfItems(aNumberOfItems);
}
void
DOMSVGPathSegList::EnsureItemAt(PRUint32 aIndex)
{

View File

@ -126,6 +126,8 @@ public:
return mItems.Length();
}
nsIDOMSVGPathSeg* GetItemWithoutAddRef(PRUint32 aIndex);
/**
* WATCH OUT! If you add code to call this on a baseVal wrapper, then you
* must also call it on the animVal wrapper too if necessary!! See other

View File

@ -116,6 +116,21 @@ DOMSVGPointList::~DOMSVGPointList()
sSVGPointListTearoffTable.RemoveTearoff(key);
}
nsIDOMSVGPoint*
DOMSVGPointList::GetItemWithoutAddRef(PRUint32 aIndex)
{
#ifdef MOZ_SMIL
if (IsAnimValList()) {
Element()->FlushAnimations();
}
#endif
if (aIndex < Length()) {
EnsureItemAt(aIndex);
return mItems[aIndex];
}
return nsnull;
}
void
DOMSVGPointList::InternalListWillChangeTo(const SVGPointList& aNewValue)
{
@ -261,18 +276,12 @@ NS_IMETHODIMP
DOMSVGPointList::GetItem(PRUint32 aIndex,
nsIDOMSVGPoint **_retval)
{
#ifdef MOZ_SMIL
if (IsAnimValList()) {
Element()->FlushAnimations();
*_retval = GetItemWithoutAddRef(aIndex);
if (!*_retval) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
#endif
if (aIndex < Length()) {
EnsureItemAt(aIndex);
NS_ADDREF(*_retval = mItems[aIndex]);
return NS_OK;
}
*_retval = nsnull;
return NS_ERROR_DOM_INDEX_SIZE_ERR;
NS_ADDREF(*_retval);
return NS_OK;
}
NS_IMETHODIMP
@ -418,6 +427,12 @@ DOMSVGPointList::AppendItem(nsIDOMSVGPoint *aNewItem,
return InsertItemBefore(aNewItem, Length(), _retval);
}
NS_IMETHODIMP
DOMSVGPointList::GetLength(PRUint32 *aNumberOfItems)
{
return GetNumberOfItems(aNumberOfItems);
}
void
DOMSVGPointList::EnsureItemAt(PRUint32 aIndex)
{

View File

@ -126,6 +126,8 @@ public:
return mItems.Length();
}
nsIDOMSVGPoint* GetItemWithoutAddRef(PRUint32 aIndex);
/**
* WATCH OUT! If you add code to call this on a baseVal wrapper, then you
* must also call it on the animVal wrapper too if necessary!! See other

View File

@ -124,18 +124,17 @@ SVGPathData::AppendSeg(PRUint32 aType, ...)
float
SVGPathData::GetPathLength() const
{
float length = 0.0;
SVGPathTraversalState state;
PRUint32 i = 0;
while (i < mData.Length()) {
length += SVGPathSegUtils::GetLength(&mData[i], state);
SVGPathSegUtils::TraversePathSegment(&mData[i], state);
i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
}
NS_ABORT_IF_FALSE(i == mData.Length(), "Very, very bad - mData corrupt");
return length;
return state.length;
}
#ifdef DEBUG
@ -163,7 +162,9 @@ SVGPathData::GetSegmentLengths(nsTArray<double> *aLengths) const
PRUint32 i = 0;
while (i < mData.Length()) {
if (!aLengths->AppendElement(SVGPathSegUtils::GetLength(&mData[i], state))) {
state.length = 0.0;
SVGPathSegUtils::TraversePathSegment(&mData[i], state);
if (!aLengths->AppendElement(state.length)) {
aLengths->Clear();
return PR_FALSE;
}
@ -178,7 +179,6 @@ SVGPathData::GetSegmentLengths(nsTArray<double> *aLengths) const
PRBool
SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(nsTArray<double> *aOutput) const
{
double distRunningTotal = 0.0;
SVGPathTraversalState state;
aOutput->Clear();
@ -186,6 +186,7 @@ SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(nsTArray<double> *aOu
PRUint32 i = 0;
while (i < mData.Length()) {
PRUint32 segType = SVGPathSegUtils::DecodeType(mData[i]);
SVGPathSegUtils::TraversePathSegment(&mData[i], state);
// We skip all moveto commands except an initial moveto. See the text 'A
// "move to" command does not count as an additional point when dividing up
@ -199,8 +200,7 @@ SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(nsTArray<double> *aOu
if (i == 0 || (segType != nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS &&
segType != nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL)) {
distRunningTotal += SVGPathSegUtils::GetLength(&mData[i], state);
if (!aOutput->AppendElement(distRunningTotal)) {
if (!aOutput->AppendElement(state.length)) {
return PR_FALSE;
}
}
@ -220,13 +220,12 @@ SVGPathData::GetPathSegAtLength(float aDistance) const
// Return -1? Throwing would better help authors avoid tricky bugs (DOM
// could do that if we return -1).
double distRunningTotal = 0.0;
PRUint32 i = 0, segIndex = 0;
SVGPathTraversalState state;
while (i < mData.Length()) {
distRunningTotal += SVGPathSegUtils::GetLength(&mData[i], state);
if (distRunningTotal >= aDistance) {
SVGPathSegUtils::TraversePathSegment(&mData[i], state);
if (state.length >= aDistance) {
return segIndex;
}
i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);

View File

@ -105,6 +105,7 @@ class SVGPathData
// are responsible for that!
public:
typedef const float* const_iterator;
SVGPathData(){}
~SVGPathData(){}
@ -121,7 +122,7 @@ public:
#ifdef DEBUG
/**
* This method iterates over the encoded segment data and countes the number
* This method iterates over the encoded segment data and counts the number
* of segments we currently have.
*/
PRUint32 CountItems() const;
@ -178,6 +179,9 @@ public:
void ConstructPath(gfxContext *aCtx) const;
const_iterator begin() const { return mData.Elements(); }
const_iterator end() const { return mData.Elements() + mData.Length(); }
// Access to methods that can modify objects of this type is deliberately
// limited. This is to reduce the chances of someone modifying objects of
// this type without taking the necessary steps to keep DOM wrappers in sync.
@ -186,6 +190,7 @@ public:
// can take care of keeping DOM wrappers in sync.
protected:
typedef float* iterator;
/**
* This may fail on OOM if the internal capacity needs to be increased, in
@ -221,6 +226,9 @@ protected:
nsresult AppendSeg(PRUint32 aType, ...); // variable number of float args
iterator begin() { return mData.Elements(); }
iterator end() { return mData.Elements() + mData.Length(); }
nsTArray<float> mData;
};
@ -236,7 +244,6 @@ protected:
class SVGPathDataAndOwner : public SVGPathData
{
public:
SVGPathDataAndOwner(nsSVGElement *aElement = nsnull)
: mElement(aElement)
{}
@ -259,18 +266,14 @@ public:
* SVGPathDataAndOwner objects. Note that callers should also call
* SetElement() when using this method!
*/
nsresult CopyFrom(const SVGPathData& rhs) {
return SVGPathData::CopyFrom(rhs);
}
const float& operator[](PRUint32 aIndex) const {
return SVGPathData::operator[](aIndex);
}
float& operator[](PRUint32 aIndex) {
return SVGPathData::operator[](aIndex);
}
PRBool SetLength(PRUint32 aNumberOfItems) {
return SVGPathData::SetLength(aNumberOfItems);
}
using SVGPathData::CopyFrom;
// Exposed since SVGPathData objects can be modified.
using SVGPathData::iterator;
using SVGPathData::operator[];
using SVGPathData::SetLength;
using SVGPathData::begin;
using SVGPathData::end;
private:
// We must keep a strong reference to our element because we may belong to a

View File

@ -89,6 +89,240 @@ SVGPathSegListSMILType::IsEqual(const nsSMILValue& aLeft,
*static_cast<const SVGPathDataAndOwner*>(aRight.mU.mPtr);
}
static PRBool
ArcFlagsDiffer(SVGPathDataAndOwner::const_iterator aPathData1,
SVGPathDataAndOwner::const_iterator aPathData2)
{
NS_ABORT_IF_FALSE
(SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData1[0])),
"ArcFlagsDiffer called with non-arc segment");
NS_ABORT_IF_FALSE
(SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData2[0])),
"ArcFlagsDiffer called with non-arc segment");
return aPathData1[4] != aPathData2[4] || // large arc flag
aPathData1[5] != aPathData2[5]; // sweep flag
}
enum PathInterpolationResult {
eCannotInterpolate,
eRequiresConversion,
eCanInterpolate
};
static PathInterpolationResult
CanInterpolate(const SVGPathDataAndOwner& aStart,
const SVGPathDataAndOwner& aEnd)
{
if (aStart.IsEmpty()) {
return eCanInterpolate;
}
if (aStart.Length() != aEnd.Length()) {
return eCannotInterpolate;
}
PathInterpolationResult result = eCanInterpolate;
SVGPathDataAndOwner::const_iterator pStart = aStart.begin();
SVGPathDataAndOwner::const_iterator pEnd = aEnd.begin();
SVGPathDataAndOwner::const_iterator pStartDataEnd = aStart.end();
SVGPathDataAndOwner::const_iterator pEndDataEnd = aEnd.end();
while (pStart < pStartDataEnd && pEnd < pEndDataEnd) {
PRUint32 startType = SVGPathSegUtils::DecodeType(*pStart);
PRUint32 endType = SVGPathSegUtils::DecodeType(*pEnd);
if (SVGPathSegUtils::IsArcType(startType) &&
SVGPathSegUtils::IsArcType(endType) &&
ArcFlagsDiffer(pStart, pEnd)) {
return eCannotInterpolate;
}
if (startType != endType) {
if (!SVGPathSegUtils::SameTypeModuloRelativeness(startType, endType)) {
return eCannotInterpolate;
}
result = eRequiresConversion;
}
pStart += 1 + SVGPathSegUtils::ArgCountForType(startType);
pEnd += 1 + SVGPathSegUtils::ArgCountForType(endType);
}
NS_ABORT_IF_FALSE(pStart <= pStartDataEnd && pEnd <= pEndDataEnd,
"Iterated past end of buffer! (Corrupt path data?)");
if (pStart != pStartDataEnd || pEnd != pEndDataEnd) {
return eCannotInterpolate;
}
return result;
}
static void
InterpolatePathSegmentData(SVGPathDataAndOwner::const_iterator& aStart,
SVGPathDataAndOwner::const_iterator& aEnd,
SVGPathDataAndOwner::iterator& aResult,
float aUnitDistance)
{
PRUint32 startType = SVGPathSegUtils::DecodeType(*aStart);
PRUint32 endType = SVGPathSegUtils::DecodeType(*aEnd);
NS_ABORT_IF_FALSE
(startType == endType,
"InterpolatePathSegmentData expects segment types to be the same!");
NS_ABORT_IF_FALSE
(!(SVGPathSegUtils::IsArcType(startType) && ArcFlagsDiffer(aStart, aEnd)),
"InterpolatePathSegmentData cannot interpolate arc segments with different flag values!");
PRUint32 argCount = SVGPathSegUtils::ArgCountForType(startType);
// Copy over segment type.
*aResult++ = *aStart++;
++aEnd;
// Interpolate the arguments.
SVGPathDataAndOwner::const_iterator startSegEnd = aStart + argCount;
while (aStart != startSegEnd) {
*aResult = *aStart + (*aEnd - *aStart) * aUnitDistance;
++aStart;
++aEnd;
++aResult;
}
}
enum RelativenessAdjustmentType {
eAbsoluteToRelative,
eRelativeToAbsolute
};
static inline void
AdjustSegmentForRelativeness(RelativenessAdjustmentType aAdjustmentType,
const SVGPathDataAndOwner::iterator& aSegmentToAdjust,
const SVGPathTraversalState& aState)
{
if (aAdjustmentType == eAbsoluteToRelative) {
aSegmentToAdjust[0] -= aState.pos.x;
aSegmentToAdjust[1] -= aState.pos.y;
} else {
aSegmentToAdjust[0] += aState.pos.x;
aSegmentToAdjust[1] += aState.pos.y;
}
}
static void
ConvertPathSegmentData(SVGPathDataAndOwner::const_iterator& aStart,
SVGPathDataAndOwner::const_iterator& aEnd,
SVGPathDataAndOwner::iterator& aResult,
SVGPathTraversalState& aState)
{
PRUint32 startType = SVGPathSegUtils::DecodeType(*aStart);
PRUint32 endType = SVGPathSegUtils::DecodeType(*aEnd);
PRUint32 segmentLengthIncludingType =
1 + SVGPathSegUtils::ArgCountForType(startType);
SVGPathDataAndOwner::const_iterator pResultSegmentBegin = aResult;
if (startType == endType) {
// No conversion need, just directly copy aStart.
aEnd += segmentLengthIncludingType;
while (segmentLengthIncludingType) {
*aResult++ = *aStart++;
--segmentLengthIncludingType;
}
SVGPathSegUtils::TraversePathSegment(pResultSegmentBegin, aState);
return;
}
NS_ABORT_IF_FALSE
(SVGPathSegUtils::SameTypeModuloRelativeness(startType, endType),
"Incompatible path segment types passed to ConvertPathSegmentData!");
RelativenessAdjustmentType adjustmentType =
SVGPathSegUtils::IsRelativeType(startType) ? eRelativeToAbsolute
: eAbsoluteToRelative;
NS_ABORT_IF_FALSE
(segmentLengthIncludingType ==
1 + SVGPathSegUtils::ArgCountForType(endType),
"Compatible path segment types for interpolation had different lengths!");
aResult[0] = aEnd[0];
switch (endType) {
case nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_ABS:
case nsIDOMSVGPathSeg::PATHSEG_LINETO_HORIZONTAL_REL:
aResult[1] = aStart[1] +
(adjustmentType == eRelativeToAbsolute ? 1 : -1) * aState.pos.x;
break;
case nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_ABS:
case nsIDOMSVGPathSeg::PATHSEG_LINETO_VERTICAL_REL:
aResult[1] = aStart[1] +
(adjustmentType == eRelativeToAbsolute ? 1 : -1) * aState.pos.y;
break;
case nsIDOMSVGPathSeg::PATHSEG_ARC_ABS:
case nsIDOMSVGPathSeg::PATHSEG_ARC_REL:
aResult[1] = aStart[1];
aResult[2] = aStart[2];
aResult[3] = aStart[3];
aResult[4] = aStart[4];
aResult[5] = aStart[5];
aResult[6] = aStart[6];
aResult[7] = aStart[7];
AdjustSegmentForRelativeness(adjustmentType, aResult + 6, aState);
break;
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_ABS:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_REL:
aResult[5] = aStart[5];
aResult[6] = aStart[6];
AdjustSegmentForRelativeness(adjustmentType, aResult + 5, aState);
// fall through
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_REL:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
aResult[3] = aStart[3];
aResult[4] = aStart[4];
AdjustSegmentForRelativeness(adjustmentType, aResult + 3, aState);
// fall through
case nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS:
case nsIDOMSVGPathSeg::PATHSEG_MOVETO_REL:
case nsIDOMSVGPathSeg::PATHSEG_LINETO_ABS:
case nsIDOMSVGPathSeg::PATHSEG_LINETO_REL:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
case nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
aResult[1] = aStart[1];
aResult[2] = aStart[2];
AdjustSegmentForRelativeness(adjustmentType, aResult + 1, aState);
break;
}
SVGPathSegUtils::TraversePathSegment(pResultSegmentBegin, aState);
aStart += segmentLengthIncludingType;
aEnd += segmentLengthIncludingType;
aResult += segmentLengthIncludingType;
}
static void
ConvertAllPathSegmentData(SVGPathDataAndOwner::const_iterator aStart,
SVGPathDataAndOwner::const_iterator aStartDataEnd,
SVGPathDataAndOwner::const_iterator aEnd,
SVGPathDataAndOwner::const_iterator aEndDataEnd,
SVGPathDataAndOwner::iterator aResult)
{
SVGPathTraversalState state;
state.mode = SVGPathTraversalState::eUpdateOnlyStartAndCurrentPos;
while (aStart < aStartDataEnd && aEnd < aEndDataEnd) {
ConvertPathSegmentData(aStart, aEnd, aResult, state);
}
NS_ABORT_IF_FALSE(aStart == aStartDataEnd && aEnd == aEndDataEnd,
"Failed to convert all path segment data! (Corrupt?)");
}
nsresult
SVGPathSegListSMILType::Add(nsSMILValue& aDest,
const nsSMILValue& aValueToAdd,
@ -102,36 +336,33 @@ SVGPathSegListSMILType::Add(nsSMILValue& aDest,
const SVGPathDataAndOwner& valueToAdd =
*static_cast<const SVGPathDataAndOwner*>(aValueToAdd.mU.mPtr);
if (dest.Length() != valueToAdd.Length()) {
// Allow addition to empty dest:
if (dest.Length() == 0) {
return dest.CopyFrom(valueToAdd);
}
// For now we only support animation to a list with the same number of
// items (and with the same segment types).
// nsSVGUtils::ReportToConsole
// Allow addition to empty dest.
if (dest.IsEmpty()) {
return dest.CopyFrom(valueToAdd);
}
PathInterpolationResult check = CanInterpolate(dest, valueToAdd);
if (check == eCannotInterpolate) {
// nsSVGUtils::ReportToConsole - can't add path segment lists with different
// numbers of segments, with arcs with different flag values, or with
// incompatible segment types.
return NS_ERROR_FAILURE;
}
if (check == eRequiresConversion) {
ConvertAllPathSegmentData(dest.begin(), dest.end(),
valueToAdd.begin(), valueToAdd.end(),
dest.begin());
}
PRUint32 i = 0;
while (i < dest.Length()) {
PRUint32 type = SVGPathSegUtils::DecodeType(dest[i]);
if (type != SVGPathSegUtils::DecodeType(valueToAdd[i])) {
// nsSVGUtils::ReportToConsole - can't yet animate between different
// types, although it would make sense to allow animation between
// some.
return NS_ERROR_FAILURE;
}
i++;
if ((type == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS ||
type == nsIDOMSVGPathSeg::PATHSEG_ARC_REL) &&
(dest[i+3] != valueToAdd[i+3] || dest[i+4] != valueToAdd[i+4])) {
// boolean args largeArcFlag and sweepFlag must be the same
return NS_ERROR_FAILURE;
}
PRUint32 segEnd = i + SVGPathSegUtils::ArgCountForType(type);
for (; i < segEnd; ++i) {
dest[i] += valueToAdd[i];
dest[i] += valueToAdd[i] * aCount;
}
}
@ -175,10 +406,12 @@ SVGPathSegListSMILType::Interpolate(const nsSMILValue& aStartVal,
SVGPathDataAndOwner& result =
*static_cast<SVGPathDataAndOwner*>(aResult.mU.mPtr);
if (start.Length() != end.Length() && start.Length() != 0) {
// For now we only support animation to a list with the same number of
// items (and with the same segment types).
// nsSVGUtils::ReportToConsole
PathInterpolationResult check = CanInterpolate(start, end);
if (check == eCannotInterpolate) {
// nsSVGUtils::ReportToConsole - can't interpolate path segment lists with
// different numbers of segments, with arcs with different flag values, or
// with incompatible segment types.
return NS_ERROR_FAILURE;
}
@ -186,16 +419,14 @@ SVGPathSegListSMILType::Interpolate(const nsSMILValue& aStartVal,
return NS_ERROR_OUT_OF_MEMORY;
}
PRUint32 i = 0;
if (start.Length() == 0) { // identity path
if (start.IsEmpty()) { // identity path
PRUint32 i = 0;
while (i < end.Length()) {
PRUint32 type = SVGPathSegUtils::DecodeType(end[i]);
result[i] = end[i];
i++;
PRUint32 segEnd = i + SVGPathSegUtils::ArgCountForType(type);
if ((type == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS ||
type == nsIDOMSVGPathSeg::PATHSEG_ARC_REL)) {
if (SVGPathSegUtils::IsArcType(type)) {
result[i] = end[i] * aUnitDistance;
result[i+1] = end[i+1] * aUnitDistance;
result[i+2] = end[i+2] * aUnitDistance;
@ -211,32 +442,30 @@ SVGPathSegListSMILType::Interpolate(const nsSMILValue& aStartVal,
}
}
}
NS_ABORT_IF_FALSE(i == end.Length() && i == result.Length(),
"Very, very bad - path data corrupt");
} else {
while (i < end.Length()) {
PRUint32 type = SVGPathSegUtils::DecodeType(end[i]);
if (type != SVGPathSegUtils::DecodeType(start[i])) {
// nsSVGUtils::ReportToConsole - can't yet interpolate between different
// types, although it would make sense to allow interpolation between
// some.
return NS_ERROR_FAILURE;
}
result[i] = end[i];
i++;
if ((type == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS ||
type == nsIDOMSVGPathSeg::PATHSEG_ARC_REL) &&
(start[i+3] != end[i+3] || start[i+4] != end[i+4])) {
// boolean args largeArcFlag and sweepFlag must be the same
return NS_ERROR_FAILURE;
}
PRUint32 segEnd = i + SVGPathSegUtils::ArgCountForType(type);
for (; i < segEnd; ++i) {
result[i] = start[i] + (end[i] - start[i]) * aUnitDistance;
}
}
}
SVGPathDataAndOwner::const_iterator pStart = start.begin();
SVGPathDataAndOwner::const_iterator pStartDataEnd = start.end();
SVGPathDataAndOwner::const_iterator pEnd = end.begin();
SVGPathDataAndOwner::const_iterator pEndDataEnd = end.end();
SVGPathDataAndOwner::iterator pResult = result.begin();
NS_ABORT_IF_FALSE(i == end.Length(), "Very, very bad - path data corrupt");
if (check == eRequiresConversion) {
ConvertAllPathSegmentData(pStart, pStartDataEnd, pEnd, pEndDataEnd,
pResult);
pStart = pResult;
pStartDataEnd = result.end();
}
while (pStart != pStartDataEnd && pEnd != pEndDataEnd) {
InterpolatePathSegmentData(pStart, pEnd, pResult, aUnitDistance);
}
NS_ABORT_IF_FALSE(pStart == pStartDataEnd && pEnd == pEndDataEnd &&
pResult == result.end(),
"Very, very bad - path data corrupt");
}
return NS_OK;
}

View File

@ -54,17 +54,18 @@ static const PRUint32 MAX_RECURSION = 10;
/* static */ void
SVGPathSegUtils::GetValueAsString(const float *aSeg, nsAString& aValue)
SVGPathSegUtils::GetValueAsString(const float* aSeg, nsAString& aValue)
{
// Adding new seg type? Is the formatting below acceptable for the new types?
PR_STATIC_ASSERT(NS_SVG_PATH_SEG_LAST_VALID_TYPE ==
nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL);
PR_STATIC_ASSERT(NS_SVG_PATH_SEG_MAX_ARGS == 7);
PRUint32 type = DecodeType(aSeg[0]);
PRUnichar typeAsChar = GetPathSegTypeAsLetter(type);
// Special case arcs:
if (type == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS ||
type == nsIDOMSVGPathSeg::PATHSEG_ARC_REL) {
if (IsArcType(type)) {
PRBool largeArcFlag = aSeg[4] != 0.0f;
PRBool sweepFlag = aSeg[5] != 0.0f;
nsTextFormatter::ssprintf(aValue,
@ -119,343 +120,366 @@ SVGPathSegUtils::GetValueAsString(const float *aSeg, nsAString& aValue)
static float
CalcDistanceBetweenPoints(const gfxPoint &p1, const gfxPoint &p2)
CalcDistanceBetweenPoints(const gfxPoint& aP1, const gfxPoint& aP2)
{
return NS_hypot(p2.x - p1.x, p2.y - p1.y);
return NS_hypot(aP2.x - aP1.x, aP2.y - aP1.y);
}
static void SplitQuadraticBezier(const gfxPoint *curve,
gfxPoint *left,
gfxPoint *right)
static void
SplitQuadraticBezier(const gfxPoint* aCurve, gfxPoint* aLeft, gfxPoint* aRight)
{
left[0].x = curve[0].x;
left[0].y = curve[0].y;
right[2].x = curve[2].x;
right[2].y = curve[2].y;
left[1].x = (curve[0].x + curve[1].x) / 2;
left[1].y = (curve[0].y + curve[1].y) / 2;
right[1].x = (curve[1].x + curve[2].x) / 2;
right[1].y = (curve[1].y + curve[2].y) / 2;
left[2].x = right[0].x = (left[1].x + right[1].x) / 2;
left[2].y = right[0].y = (left[1].y + right[1].y) / 2;
aLeft[0].x = aCurve[0].x;
aLeft[0].y = aCurve[0].y;
aRight[2].x = aCurve[2].x;
aRight[2].y = aCurve[2].y;
aLeft[1].x = (aCurve[0].x + aCurve[1].x) / 2;
aLeft[1].y = (aCurve[0].y + aCurve[1].y) / 2;
aRight[1].x = (aCurve[1].x + aCurve[2].x) / 2;
aRight[1].y = (aCurve[1].y + aCurve[2].y) / 2;
aLeft[2].x = aRight[0].x = (aLeft[1].x + aRight[1].x) / 2;
aLeft[2].y = aRight[0].y = (aLeft[1].y + aRight[1].y) / 2;
}
static void SplitCubicBezier(const gfxPoint *curve,
gfxPoint *left,
gfxPoint *right)
static void
SplitCubicBezier(const gfxPoint* aCurve, gfxPoint* aLeft, gfxPoint* aRight)
{
gfxPoint tmp;
tmp.x = (curve[1].x + curve[2].x) / 4;
tmp.y = (curve[1].y + curve[2].y) / 4;
left[0].x = curve[0].x;
left[0].y = curve[0].y;
right[3].x = curve[3].x;
right[3].y = curve[3].y;
left[1].x = (curve[0].x + curve[1].x) / 2;
left[1].y = (curve[0].y + curve[1].y) / 2;
right[2].x = (curve[2].x + curve[3].x) / 2;
right[2].y = (curve[2].y + curve[3].y) / 2;
left[2].x = left[1].x / 2 + tmp.x;
left[2].y = left[1].y / 2 + tmp.y;
right[1].x = right[2].x / 2 + tmp.x;
right[1].y = right[2].y / 2 + tmp.y;
left[3].x = right[0].x = (left[2].x + right[1].x) / 2;
left[3].y = right[0].y = (left[2].y + right[1].y) / 2;
tmp.x = (aCurve[1].x + aCurve[2].x) / 4;
tmp.y = (aCurve[1].y + aCurve[2].y) / 4;
aLeft[0].x = aCurve[0].x;
aLeft[0].y = aCurve[0].y;
aRight[3].x = aCurve[3].x;
aRight[3].y = aCurve[3].y;
aLeft[1].x = (aCurve[0].x + aCurve[1].x) / 2;
aLeft[1].y = (aCurve[0].y + aCurve[1].y) / 2;
aRight[2].x = (aCurve[2].x + aCurve[3].x) / 2;
aRight[2].y = (aCurve[2].y + aCurve[3].y) / 2;
aLeft[2].x = aLeft[1].x / 2 + tmp.x;
aLeft[2].y = aLeft[1].y / 2 + tmp.y;
aRight[1].x = aRight[2].x / 2 + tmp.x;
aRight[1].y = aRight[2].y / 2 + tmp.y;
aLeft[3].x = aRight[0].x = (aLeft[2].x + aRight[1].x) / 2;
aLeft[3].y = aRight[0].y = (aLeft[2].y + aRight[1].y) / 2;
}
static gfxFloat CalcBezLengthHelper(gfxPoint *curve, PRUint32 numPts,
PRUint32 recursion_count,
void (*split)(const gfxPoint*, gfxPoint*, gfxPoint*))
static gfxFloat
CalcBezLengthHelper(gfxPoint* aCurve, PRUint32 aNumPts,
PRUint32 aRecursionCount,
void (*aSplit)(const gfxPoint*, gfxPoint*, gfxPoint*))
{
gfxPoint left[4];
gfxPoint right[4];
gfxFloat length = 0, dist;
for (PRUint32 i = 0; i < numPts - 1; i++) {
length += CalcDistanceBetweenPoints(curve[i], curve[i+1]);
for (PRUint32 i = 0; i < aNumPts - 1; i++) {
length += CalcDistanceBetweenPoints(aCurve[i], aCurve[i+1]);
}
dist = CalcDistanceBetweenPoints(curve[0], curve[numPts - 1]);
if (length - dist > PATH_SEG_LENGTH_TOLERANCE && recursion_count < MAX_RECURSION) {
split(curve, left, right);
++recursion_count;
return CalcBezLengthHelper(left, numPts, recursion_count, split) +
CalcBezLengthHelper(right, numPts, recursion_count, split);
dist = CalcDistanceBetweenPoints(aCurve[0], aCurve[aNumPts - 1]);
if (length - dist > PATH_SEG_LENGTH_TOLERANCE &&
aRecursionCount < MAX_RECURSION) {
aSplit(aCurve, left, right);
++aRecursionCount;
return CalcBezLengthHelper(left, aNumPts, aRecursionCount, aSplit) +
CalcBezLengthHelper(right, aNumPts, aRecursionCount, aSplit);
}
return length;
}
static inline gfxFloat
CalcLengthOfCubicBezier(const gfxPoint &pos, const gfxPoint &cp1,
const gfxPoint &cp2, const gfxPoint &to)
CalcLengthOfCubicBezier(const gfxPoint& aPos, const gfxPoint &aCP1,
const gfxPoint& aCP2, const gfxPoint &aTo)
{
gfxPoint curve[4] = { pos, cp1, cp2, to };
gfxPoint curve[4] = { aPos, aCP1, aCP2, aTo };
return CalcBezLengthHelper(curve, 4, 0, SplitCubicBezier);
}
static inline gfxFloat
CalcLengthOfQuadraticBezier(const gfxPoint &pos, const gfxPoint &cp,
const gfxPoint &to)
CalcLengthOfQuadraticBezier(const gfxPoint& aPos, const gfxPoint& aCP,
const gfxPoint& aTo)
{
gfxPoint curve[3] = { pos, cp, to };
gfxPoint curve[3] = { aPos, aCP, aTo };
return CalcBezLengthHelper(curve, 3, 0, SplitQuadraticBezier);
}
static float GetLengthOfClosePath(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseClosePath(const float* aArgs, SVGPathTraversalState& aState)
{
float dist = CalcDistanceBetweenPoints(aState.pos, aState.start);
aState.pos = aState.cp1 = aState.cp2 = aState.start;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += CalcDistanceBetweenPoints(aState.pos, aState.start);
aState.cp1 = aState.cp2 = aState.start;
}
aState.pos = aState.start;
}
static float GetLengthOfMovetoAbs(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseMovetoAbs(const float* aArgs, SVGPathTraversalState& aState)
{
aState.start = aState.pos = aState.cp1 = aState.cp2 = gfxPoint(aArgs[0], aArgs[1]);
return 0.0;
aState.start = aState.pos = gfxPoint(aArgs[0], aArgs[1]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
// aState.length is unchanged, since move commands don't affect path length.
aState.cp1 = aState.cp2 = aState.start;
}
}
static float GetLengthOfMovetoRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseMovetoRel(const float* aArgs, SVGPathTraversalState& aState)
{
// aState.pos must be second from right due to +=
aState.start = aState.cp1 = aState.cp2 = aState.pos += gfxPoint(aArgs[0], aArgs[1]);
return 0.0;
aState.start = aState.pos += gfxPoint(aArgs[0], aArgs[1]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
// aState.length is unchanged, since move commands don't affect path length.
aState.cp1 = aState.cp2 = aState.start;
}
}
static float GetLengthOfLinetoAbs(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseLinetoAbs(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint to(aArgs[0], aArgs[1]);
float dist = CalcDistanceBetweenPoints(aState.pos, to);
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += CalcDistanceBetweenPoints(aState.pos, to);
aState.cp1 = aState.cp2 = to;
}
aState.pos = to;
}
static float GetLengthOfLinetoRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseLinetoRel(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint to = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
float dist = CalcDistanceBetweenPoints(aState.pos, to);
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += CalcDistanceBetweenPoints(aState.pos, to);
aState.cp1 = aState.cp2 = to;
}
aState.pos = to;
}
static float
GetLengthOfLinetoHorizontalAbs(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseLinetoHorizontalAbs(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint to(aArgs[0], aState.pos.y);
float dist = fabs(to.x - aState.pos.x);
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += fabs(to.x - aState.pos.x);
aState.cp1 = aState.cp2 = to;
}
aState.pos = to;
}
static float
GetLengthOfLinetoHorizontalRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseLinetoHorizontalRel(const float* aArgs, SVGPathTraversalState& aState)
{
aState.cp1 = aState.cp2 = aState.pos += gfxPoint(aArgs[0], 0.0);
return fabs(aArgs[0]);
aState.pos.x += aArgs[0];
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += fabs(aArgs[0]);
aState.cp1 = aState.cp2 = aState.pos;
}
}
static float
GetLengthOfLinetoVerticalAbs(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseLinetoVerticalAbs(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint to(aState.pos.x, aArgs[0]);
float dist = fabs(to.y - aState.pos.y);
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += fabs(to.y - aState.pos.y);
aState.cp1 = aState.cp2 = to;
}
aState.pos = to;
}
static float
GetLengthOfLinetoVerticalRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseLinetoVerticalRel(const float* aArgs, SVGPathTraversalState& aState)
{
aState.cp1 = aState.cp2 = aState.pos += gfxPoint(0.0, aArgs[0]);
return fabs(aArgs[0]);
aState.pos.y += aArgs[0];
if (aState.ShouldUpdateLengthAndControlPoints()) {
aState.length += fabs(aArgs[0]);
aState.cp1 = aState.cp2 = aState.pos;
}
}
static float GetLengthOfCurvetoCubicAbs(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseCurvetoCubicAbs(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint cp1(aArgs[0], aArgs[1]);
gfxPoint cp2(aArgs[2], aArgs[3]);
gfxPoint to(aArgs[4], aArgs[5]);
float dist = (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.pos = aState.cp1 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp1(aArgs[0], aArgs[1]);
gfxPoint cp2(aArgs[2], aArgs[3]);
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.cp1 = to;
}
aState.pos = to;
}
static float
GetLengthOfCurvetoCubicSmoothAbs(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseCurvetoCubicSmoothAbs(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint cp1 = aState.pos - (aState.cp2 - aState.pos);
gfxPoint cp2(aArgs[0], aArgs[1]);
gfxPoint to(aArgs[2], aArgs[3]);
float dist = (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.pos = aState.cp1 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp1 = aState.pos - (aState.cp2 - aState.pos);
gfxPoint cp2(aArgs[0], aArgs[1]);
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.cp1 = to;
}
aState.pos = to;
}
static float
GetLengthOfCurvetoCubicRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseCurvetoCubicRel(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint cp1 = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
gfxPoint cp2 = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
gfxPoint to = aState.pos + gfxPoint(aArgs[4], aArgs[5]);
float dist = (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.pos = aState.cp1 = to;
return dist;
gfxPoint to = aState.pos + gfxPoint(aArgs[4], aArgs[5]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp1 = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
gfxPoint cp2 = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.cp1 = to;
}
aState.pos = to;
}
static float
GetLengthOfCurvetoCubicSmoothRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseCurvetoCubicSmoothRel(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint cp1 = aState.pos - (aState.cp2 - aState.pos);
gfxPoint cp2 = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
gfxPoint to = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
float dist = (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.pos = aState.cp1 = to;
return dist;
}
static float
GetLengthOfCurvetoQuadraticAbs(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint cp(aArgs[0], aArgs[1]);
gfxPoint to(aArgs[2], aArgs[3]);
float dist = (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.pos = aState.cp2 = to;
return dist;
}
static float
GetLengthOfCurvetoQuadraticSmoothAbs(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint cp = aState.pos - (aState.cp1 - aState.pos);
gfxPoint to(aArgs[0], aArgs[1]);
float dist = (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.pos = aState.cp2 = to;
return dist;
}
static float
GetLengthOfCurvetoQuadraticRel(const float *aArgs, SVGPathTraversalState &aState)
{
gfxPoint cp = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
gfxPoint to = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
float dist = (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.pos = aState.cp2 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp1 = aState.pos - (aState.cp2 - aState.pos);
gfxPoint cp2 = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
aState.cp2 = cp2;
aState.cp1 = to;
}
aState.pos = to;
}
static float
GetLengthOfCurvetoQuadraticSmoothRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseCurvetoQuadraticAbs(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint to(aArgs[2], aArgs[3]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp(aArgs[0], aArgs[1]);
aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.cp2 = to;
}
aState.pos = to;
}
static void
TraverseCurvetoQuadraticSmoothAbs(const float* aArgs,
SVGPathTraversalState& aState)
{
gfxPoint to(aArgs[0], aArgs[1]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp = aState.pos - (aState.cp1 - aState.pos);
aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.cp2 = to;
}
aState.pos = to;
}
static void
TraverseCurvetoQuadraticRel(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint to = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.cp2 = to;
}
aState.pos = to;
}
static void
TraverseCurvetoQuadraticSmoothRel(const float* aArgs,
SVGPathTraversalState& aState)
{
gfxPoint cp = aState.pos - (aState.cp1 - aState.pos);
gfxPoint to = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
float dist = (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.pos = aState.cp2 = to;
return dist;
if (aState.ShouldUpdateLengthAndControlPoints()) {
gfxPoint cp = aState.pos - (aState.cp1 - aState.pos);
aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
aState.cp1 = cp;
aState.cp2 = to;
}
aState.pos = to;
}
static float
GetLengthOfArcAbs(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseArcAbs(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint radii(aArgs[0], aArgs[1]);
gfxPoint to(aArgs[5], aArgs[6]);
gfxPoint bez[4] = { aState.pos, gfxPoint(0,0), gfxPoint(0,0), gfxPoint(0,0) };
nsSVGArcConverter converter(aState.pos, to, radii, aArgs[2],
aArgs[3] != 0, aArgs[4] != 0);
float dist = 0;
while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3]))
{
dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
bez[0] = bez[3];
if (aState.ShouldUpdateLengthAndControlPoints()) {
float dist = 0;
gfxPoint radii(aArgs[0], aArgs[1]);
gfxPoint bez[4] = { aState.pos, gfxPoint(0, 0),
gfxPoint(0, 0), gfxPoint(0, 0) };
nsSVGArcConverter converter(aState.pos, to, radii, aArgs[2],
aArgs[3] != 0, aArgs[4] != 0);
while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) {
dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
bez[0] = bez[3];
}
aState.length += dist;
aState.cp1 = aState.cp2 = to;
}
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
aState.pos = to;
}
static float
GetLengthOfArcRel(const float *aArgs, SVGPathTraversalState &aState)
static void
TraverseArcRel(const float* aArgs, SVGPathTraversalState& aState)
{
gfxPoint radii(aArgs[0], aArgs[1]);
gfxPoint to = aState.pos + gfxPoint(aArgs[5], aArgs[6]);
gfxPoint bez[4] = { aState.pos, gfxPoint(0,0), gfxPoint(0,0), gfxPoint(0,0) };
nsSVGArcConverter converter(aState.pos, to, radii, aArgs[2],
aArgs[3] != 0, aArgs[4] != 0);
float dist = 0;
while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3]))
{
dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
bez[0] = bez[3];
if (aState.ShouldUpdateLengthAndControlPoints()) {
float dist = 0;
gfxPoint radii(aArgs[0], aArgs[1]);
gfxPoint bez[4] = { aState.pos, gfxPoint(0, 0),
gfxPoint(0, 0), gfxPoint(0, 0) };
nsSVGArcConverter converter(aState.pos, to, radii, aArgs[2],
aArgs[3] != 0, aArgs[4] != 0);
while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) {
dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
bez[0] = bez[3];
}
aState.length += dist;
aState.cp1 = aState.cp2 = to;
}
aState.pos = aState.cp1 = aState.cp2 = to;
return dist;
aState.pos = to;
}
typedef float (*getLengthFunc)(const float*, SVGPathTraversalState&);
typedef void (*TraverseFunc)(const float*, SVGPathTraversalState&);
/* static */ float
SVGPathSegUtils::GetLength(const float *seg, SVGPathTraversalState &aState)
static TraverseFunc gTraverseFuncTable[NS_SVG_PATH_SEG_TYPE_COUNT] = {
nsnull, // 0 == PATHSEG_UNKNOWN
TraverseClosePath,
TraverseMovetoAbs,
TraverseMovetoRel,
TraverseLinetoAbs,
TraverseLinetoRel,
TraverseCurvetoCubicAbs,
TraverseCurvetoCubicRel,
TraverseCurvetoQuadraticAbs,
TraverseCurvetoQuadraticRel,
TraverseArcAbs,
TraverseArcRel,
TraverseLinetoHorizontalAbs,
TraverseLinetoHorizontalRel,
TraverseLinetoVerticalAbs,
TraverseLinetoVerticalRel,
TraverseCurvetoCubicSmoothAbs,
TraverseCurvetoCubicSmoothRel,
TraverseCurvetoQuadraticSmoothAbs,
TraverseCurvetoQuadraticSmoothRel
};
/* static */ void
SVGPathSegUtils::TraversePathSegment(const float* aData,
SVGPathTraversalState& aState)
{
PRUint32 type = DecodeType(seg[0]);
static getLengthFunc lengthFuncTable[20] = {
nsnull, // 0 == PATHSEG_UNKNOWN
GetLengthOfClosePath,
GetLengthOfMovetoAbs,
GetLengthOfMovetoRel,
GetLengthOfLinetoAbs,
GetLengthOfLinetoRel,
GetLengthOfCurvetoCubicAbs,
GetLengthOfCurvetoCubicRel,
GetLengthOfCurvetoQuadraticAbs,
GetLengthOfCurvetoQuadraticRel,
GetLengthOfArcAbs,
GetLengthOfArcRel,
GetLengthOfLinetoHorizontalAbs,
GetLengthOfLinetoHorizontalRel,
GetLengthOfLinetoVerticalAbs,
GetLengthOfLinetoVerticalRel,
GetLengthOfCurvetoCubicSmoothAbs,
GetLengthOfCurvetoCubicSmoothRel,
GetLengthOfCurvetoQuadraticSmoothAbs,
GetLengthOfCurvetoQuadraticSmoothRel
};
NS_ABORT_IF_FALSE(IsValidType(type), "Seg type not recognized");
NS_ABORT_IF_FALSE(type > 0 && type < NS_ARRAY_LENGTH(lengthFuncTable),
"Seg type not recognized");
return lengthFuncTable[type](seg + 1, aState);
PR_STATIC_ASSERT(NS_ARRAY_LENGTH(gTraverseFuncTable) ==
NS_SVG_PATH_SEG_TYPE_COUNT);
PRUint32 type = DecodeType(aData[0]);
gTraverseFuncTable[type](aData + 1, aState);
}

View File

@ -43,7 +43,10 @@
#include "nsContentUtils.h"
#include "gfxPoint.h"
#define NS_SVG_PATH_SEG_MAX_ARGS 7
#define NS_SVG_PATH_SEG_MAX_ARGS 7
#define NS_SVG_PATH_SEG_FIRST_VALID_TYPE nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH
#define NS_SVG_PATH_SEG_LAST_VALID_TYPE nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
#define NS_SVG_PATH_SEG_TYPE_COUNT (NS_SVG_PATH_SEG_LAST_VALID_TYPE + 1)
namespace mozilla {
@ -54,13 +57,22 @@ namespace mozilla {
*/
struct SVGPathTraversalState
{
enum TraversalMode {
eUpdateAll,
eUpdateOnlyStartAndCurrentPos
};
SVGPathTraversalState()
: start(0.0, 0.0)
, pos(0.0, 0.0)
, cp1(0.0, 0.0)
, cp2(0.0, 0.0)
, length(0.0)
, mode(eUpdateAll)
{}
PRBool ShouldUpdateLengthAndControlPoints() { return mode == eUpdateAll; }
gfxPoint start; // start point of current sub path (reset each moveto)
gfxPoint pos; // current position (end point of previous segment)
@ -72,6 +84,10 @@ struct SVGPathTraversalState
gfxPoint cp2; // cubic control point - if the previous segment was a cubic
// bezier curve then this is set to the absolute position of
// its second control point, otherwise it's set to pos
float length; // accumulated path length
TraversalMode mode; // indicates what to track while traversing a path
};
@ -144,6 +160,7 @@ public:
PRUnichar('T'), // 18 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
PRUnichar('t') // 19 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
};
PR_STATIC_ASSERT(NS_ARRAY_LENGTH(table) == NS_SVG_PATH_SEG_TYPE_COUNT);
return table[aType];
}
@ -173,6 +190,7 @@ public:
2, // 18 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
2 // 19 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
};
PR_STATIC_ASSERT(NS_ARRAY_LENGTH(table) == NS_SVG_PATH_SEG_TYPE_COUNT);
return table[aType];
}
@ -185,31 +203,81 @@ public:
return ArgCountForType(DecodeType(aType));
}
static inline PRBool IsValidType(PRUint32 aType) {
return aType >= nsIDOMSVGPathSeg::PATHSEG_CLOSEPATH &&
aType <= nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL;
static PRBool IsValidType(PRUint32 aType) {
return aType >= NS_SVG_PATH_SEG_FIRST_VALID_TYPE &&
aType <= NS_SVG_PATH_SEG_LAST_VALID_TYPE;
}
static inline PRBool IsCubicType(PRUint32 aType) {
static PRBool IsCubicType(PRUint32 aType) {
return aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_REL ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_ABS ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_REL ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS;
}
static inline PRBool IsQuadraticType(PRUint32 aType) {
static PRBool IsQuadraticType(PRUint32 aType) {
return aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_REL ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_ABS ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL ||
aType == nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS;
}
/**
* Returns the user unit length of tracing along the path segment.
*/
static float GetLength(const float *aSeg, SVGPathTraversalState &aState);
static PRBool IsArcType(PRUint32 aType) {
return aType == nsIDOMSVGPathSeg::PATHSEG_ARC_ABS ||
aType == nsIDOMSVGPathSeg::PATHSEG_ARC_REL;
}
static void ToString(const float *aSeg, nsAString& aValue);
static PRBool IsRelativeOrAbsoluteType(PRUint32 aType) {
NS_ABORT_IF_FALSE(IsValidType(aType), "Seg type not recognized");
// When adding a new path segment type, ensure that the returned condition
// below is still correct.
PR_STATIC_ASSERT(NS_SVG_PATH_SEG_LAST_VALID_TYPE ==
nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL);
return aType >= nsIDOMSVGPathSeg::PATHSEG_MOVETO_ABS;
}
static PRBool IsRelativeType(PRUint32 aType) {
NS_ABORT_IF_FALSE
(IsRelativeOrAbsoluteType(aType),
"IsRelativeType called with segment type that does not come in relative and absolute forms");
// When adding a new path segment type, ensure that the returned condition
// below is still correct.
PR_STATIC_ASSERT(NS_SVG_PATH_SEG_LAST_VALID_TYPE ==
nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL);
return aType & 1;
}
static PRUint32 RelativeVersionOfType(PRUint32 aType) {
NS_ABORT_IF_FALSE
(IsRelativeOrAbsoluteType(aType),
"RelativeVersionOfType called with segment type that does not come in relative and absolute forms");
// When adding a new path segment type, ensure that the returned condition
// below is still correct.
PR_STATIC_ASSERT(NS_SVG_PATH_SEG_LAST_VALID_TYPE ==
nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL);
return aType | 1;
}
static PRUint32 SameTypeModuloRelativeness(PRUint32 aType1, PRUint32 aType2) {
if (!IsRelativeOrAbsoluteType(aType1)) {
return aType1 == aType2;
}
return RelativeVersionOfType(aType1) == RelativeVersionOfType(aType2);
}
/**
* Traverse the given path segment and update the SVGPathTraversalState
* object.
*/
static void TraversePathSegment(const float* aData,
SVGPathTraversalState& aState);
};
} // namespace mozilla

View File

@ -147,7 +147,8 @@ nsSVGAnimationElement::GetTargetElementContent()
"if we don't have an xlink:href attribute");
// No "xlink:href" attribute --> I should target my parent.
return nsSVGUtils::GetParentElement(this);
nsIContent* parent = GetFlattenedTreeParent();
return parent && parent->IsElement() ? parent->AsElement() : nsnull;
}
PRBool

View File

@ -239,7 +239,7 @@ nsSVGElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
aCompileEventHandlers);
NS_ENSURE_SUCCESS(rv, rv);
if (!HasFlag(NODE_MAY_HAVE_STYLE)) {
if (!MayHaveStyle()) {
return NS_OK;
}
const nsAttrValue* oldVal = mAttrsAndChildren.GetAttr(nsGkAtoms::style);
@ -288,7 +288,7 @@ nsSVGElement::AfterSetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
aName == nsGkAtoms::requiredExtensions ||
aName == nsGkAtoms::systemLanguage)) {
nsIContent* parent = nsSVGUtils::GetParentElement(this);
nsIContent* parent = GetFlattenedTreeParent();
if (parent &&
parent->NodeInfo()->Equals(nsGkAtoms::svgSwitch, kNameSpaceID_SVG)) {
@ -1411,7 +1411,7 @@ nsIAtom* nsSVGElement::GetEventNameForAttr(nsIAtom* aAttr)
nsSVGSVGElement *
nsSVGElement::GetCtx()
{
dom::Element* ancestor = nsSVGUtils::GetParentElement(this);
nsIContent* ancestor = GetFlattenedTreeParent();
while (ancestor && ancestor->GetNameSpaceID() == kNameSpaceID_SVG) {
nsIAtom* tag = ancestor->Tag();
@ -1421,7 +1421,7 @@ nsSVGElement::GetCtx()
if (tag == nsGkAtoms::svg) {
return static_cast<nsSVGSVGElement*>(ancestor);
}
ancestor = nsSVGUtils::GetParentElement(ancestor);
ancestor = ancestor->GetFlattenedTreeParent();
}
// we don't have an ancestor <svg> element...

View File

@ -80,7 +80,7 @@ class SVGAnimatedPathSegList;
class SVGAnimatedPreserveAspectRatio;
}
typedef nsStyledElement nsSVGElementBase;
typedef nsStyledElementNotElementCSSInlineStyle nsSVGElementBase;
class nsSVGElement : public nsSVGElementBase, // nsIContent
public nsISVGValueObserver // :nsISupportsWeakReference

View File

@ -319,9 +319,9 @@ inline static void DidAnimateAttr(Element *aFilterPrimitive)
inline static void DidAnimateAttrViaParent(Element *aFilterPrimitive) {
// No frame, use parent's
NS_ASSERTION(!aFilterPrimitive->GetPrimaryFrame(), "Not expecting a frame");
Element *parent = nsSVGUtils::GetParentElement(aFilterPrimitive);
if (parent) {
DidAnimateAttr(parent);
nsIContent *parent = aFilterPrimitive->GetFlattenedTreeParent();
if (parent && parent->IsElement()) {
DidAnimateAttr(parent->AsElement());
}
}

View File

@ -272,7 +272,7 @@ protected:
* another non-foreignObject SVG element.
*/
PRBool IsInner() {
const mozilla::dom::Element *parent = nsSVGUtils::GetParentElement(this);
const nsIContent *parent = GetFlattenedTreeParent();
return parent && parent->GetNameSpaceID() == kNameSpaceID_SVG &&
parent->Tag() != nsGkAtoms::foreignObject;
}

View File

@ -78,6 +78,7 @@ public:
// other methods:
nsIDOMSVGTransform* GetItemWithoutAddRef(PRInt32 index);
nsIDOMSVGTransform* ElementAt(PRInt32 index);
PRBool AppendElement(nsIDOMSVGTransform* aElement);
static already_AddRefed<nsIDOMSVGMatrix>

View File

@ -49,6 +49,12 @@ include $(topsrcdir)/config/rules.mk
# test_length.xhtml \
_TEST_FILES = \
test_a_href_01.xhtml \
test_a_href_02.xhtml \
a_href_destination.svg \
a_href_helper_01.svg \
a_href_helper_02_03.svg \
a_href_helper_04.svg \
test_animLengthObjectIdentity.xhtml \
test_animLengthReadonly.xhtml \
test_animLengthRelativeUnits.xhtml \
@ -64,6 +70,7 @@ _TEST_FILES = \
getSubStringLength-helper.svg \
test_isSupported.xhtml \
test_nonAnimStrings.xhtml \
test_pathAnimInterpolation.xhtml \
test_pathSeg.xhtml \
test_pointer-events.xhtml \
test_pointer-events-2.xhtml \
@ -77,6 +84,7 @@ _TEST_FILES = \
test_SVGPathSegList.xhtml \
test_SVGStyleElement.xhtml \
test_SVGxxxList.xhtml \
test_SVGxxxListIndexing.xhtml \
test_switch.xhtml \
switch-helper.svg \
test_text.html \
@ -88,12 +96,6 @@ _TEST_FILES = \
test_viewport.html \
zoom-helper.svg \
test_zoom.xhtml \
test_a_href_01.xhtml \
test_a_href_02.xhtml \
a_href_destination.svg \
a_href_helper_01.svg \
a_href_helper_02_03.svg \
a_href_helper_04.svg \
$(NULL)
libs:: $(_TEST_FILES)

View File

@ -0,0 +1,83 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=631437
-->
<head>
<title>Tests the array indexing and .length on SVGXXXList objects</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<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=631437">Mozilla Bug 631437</a>
<svg xmlns="http://www.w3.org/2000/svg" id="svg">
<text id="text" x="10 20 30" rotate="40 50 60">abcde</text>
<path id="path" d="M0,0 L100,100"/>
<polygon id="poly" points="50,50 70,70 90,50"/>
</svg>
<script type="text/javascript;version=1.8"><![CDATA[
var text = document.getElementById("text"),
path = document.getElementById("path"),
poly = document.getElementById("poly");
function CheckList(aListObject, aExpectedListLength, aListDescription)
{
is(aListObject.numberOfItems, aExpectedListLength, aListDescription + ".numberOfItems");
is(aListObject.numberOfItems, aExpectedListLength, aListDescription + ".length");
for (let i = 0; i < aListObject.length; i++) {
let item = aListObject.getItem(i);
ok(aListObject[i] === item, aListDescription + "[" + i + "]");
}
ok(aListObject[aListObject.length] === void 0, aListDescription + "[outOfBounds]");
}
var tests = [
{ element: text,
attribute: "x",
listProperty: "x.baseVal",
type: "SVGLengthList",
subtests: [ { values: null, length: 3 },
{ values: "40", length: 1 },
{ values: "1em 2em 3em 4em 5em", length: 5 } ] },
{ element: text,
attribute: "rotate",
listProperty: "rotate.baseVal",
type: "SVGNumberList",
subtests: [ { values: null, length: 3 },
{ values: "10", length: 1 },
{ values: "1 2 3 4 5", length: 5 } ] },
{ element: path,
attribute: "d",
listProperty: "pathSegList",
type: "SVGPathSegList",
subtests: [ { values: null, length: 2 },
{ values: "M50,50", length: 1 },
{ values: "M0,0 h10 v20 h30 v40", length: 5 } ] },
{ element: poly,
attribute: "points",
listProperty: "animatedPoints",
type: "SVGPointList",
subtests: [ { values: null, length: 3 },
{ values: "100,100", length: 1 },
{ values: "0,0 10,10 20,0 30,10 40,0", length: 5 } ] }
];
for each (let test in tests) {
let list = test.element;
for each (let property in test.listProperty.split(".")) {
list = list[property];
}
for each (let subtest in test.subtests) {
if (subtest.values) {
test.element.setAttribute(test.attribute, subtest.values);
}
CheckList(list, subtest.length,
test.type + ": " + test.element.localName + "." +
test.listProperty);
}
}
]]></script>
</body>
</html>

View File

@ -0,0 +1,345 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=619498
-->
<head>
<title>Test interpolation between different path segment types</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<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=619498">Mozilla Bug 619498</a>
<svg xmlns="http://www.w3.org/2000/svg" id="svg" visibility="hidden"
onload="this.pauseAnimations()"/>
<script type="application/javascript;version=1.8"><![CDATA[
SimpleTest.waitForExplicitFinish();
var gSVG = document.getElementById("svg");
// Array of all subtests to run. This is populated by addTest.
var gTests = [];
// Array of all path segment types.
var gTypes = "zMmLlCcQqAaHhVvSsTt".split("");
// Property names on the SVGPathSeg objects for the given segment type, in the
// order that they would appear in a path data string.
var gArgumentNames = {
Z: [],
M: ['x', 'y'],
L: ['x', 'y'],
C: ['x1', 'y1', 'x2', 'y2', 'x', 'y'],
Q: ['x1', 'y1', 'x', 'y'],
A: ['r1', 'r2', 'angle', 'largeArcFlag', 'sweepFlag', 'x', 'y'],
H: ['x'],
V: ['y'],
S: ['x2', 'y2', 'x', 'y'],
T: ['x', 'y']
};
// All of these prefixes leave the current point at 100,100. Some of them
// affect the implied control point if followed by a smooth quadratic or
// cubic segment, but no valid interpolations depend on those control points.
var gPrefixes = [
[1, "M100,100"],
[2, "M50,50 M100,100"],
[2, "M50,50 m50,50"],
[2, "M50,50 L100,100"],
[2, "M50,50 l50,50"],
[3, "M50,50 H100 V100"],
[3, "M50,50 h50 V100"],
[3, "M50,50 H100 v50"],
[2, "M50,50 A10,10,10,0,0,100,100"],
[2, "M50,50 a10,10,10,0,0,50,50"],
[4, "M50,50 l50,50 z m50,50"],
// These leave the quadratic implied control point at 125,125.
[2, "M50,50 Q75,75,100,100"],
[2, "M50,50 q25,25,50,50"],
[2, "M75,75 T100,100"],
[2, "M75,75 t25,25"],
[3, "M50,50 T62.5,62.5 t37.5,37.5"],
[3, "M50,50 T62.5,62.5 T100,100"],
[3, "M50,50 t12.5,12.5 t37.5,37.5"],
[3, "M50,50 t12.5,12.5 T100,100"],
[3, "M50,50 Q50,50,62.5,62.5 t37.5,37.5"],
[3, "M50,50 Q50,50,62.5,62.5 T100,100"],
[3, "M50,50 q0,0,12.5,12.5 t37.5,37.5"],
[3, "M50,50 q0,0,12.5,12.5 T100,100"],
// These leave the cubic implied control point at 125,125.
[2, "M50,50 C10,10,75,75,100,100"],
[2, "M50,50 c10,10,25,25,50,50"],
[2, "M50,50 S75,75,100,100"],
[2, "M50,50 s25,25,50,50"],
[3, "M50,50 S10,10,75,75 S75,75,100,100"],
[3, "M50,50 S10,10,75,75 s0,0,25,25"],
[3, "M50,50 s10,10,25,25 S75,75,100,100"],
[3, "M50,50 s10,10,25,25 s0,0,25,25"],
[3, "M50,50 C10,10,20,20,75,75 S75,75,100,100"],
[3, "M50,50 C10,10,20,20,75,75 s0,0,25,25"],
[3, "M50,50 c10,10,20,20,25,25 S75,75,100,100"],
[3, "M50,50 c10,10,20,20,25,25 s0,0,25,25"]
];
// These are all of the suffixes whose result is not dependent on whether the
// preceding segment types are quadratic or cubic types. Each entry is:
//
// "<fromType><toType>": [fromArguments,
// toArguments,
// expectedArguments,
// expectedArgumentsAdditive]
//
// As an example:
//
// "Mm": [[10, 20], [30, 40], [-30, -20], [-120, -100]]
//
// This will testing interpolating between "M10,20" and "m30,40". All of the
// these tests assume that the current point is left at 100,100. So the above
// entry represents two kinds of tests, one where additive and one not:
//
// <path d="... M10,20">
// <animate attributeName="d" from="... M10,20" to="... m30,40"/>
// </path>
//
// and
//
// <path d="... M10,20">
// <animate attributeName="d" from="... M10,20" to="... m30,40"
// additive="sum"/>
// </path>
//
// where the "..." is some prefix that leaves the current point at 100,100.
// Each of the suffixes here in gSuffixes will be paired with each of the
// prefixes in gPrefixes, all of which leave the current point at 100,100.
// (Thus the above two tests for interpolating between "M" and "m" will be
// performed many times, with different preceding commands.)
//
// The expected result of the non-additive test is "m-30,-20". Since the
// animation is from an absolute moveto to a relative moveto, we first
// convert the "M10,20" into its relative form, which is "m-90,-80" due to the
// current point being 100,100. Half way through the animation between
// "m-90,-80" and "m30,40" is thus "m-30,-20".
//
// The expected result of the additive test is "m-120,-100". We take the
// halfway value of the animation, "m-30,-20" and add it on to the underlying
// value. Since the underlying value "M10,20" is an absolute moveto, we first
// convert it to relative, "m-90,-80", and then add the "m-30,-20" to it,
// giving us the result "m-120,-100".
var gSuffixes = {
// Same path segment type, no conversion required.
MM: [[10, 20], [30, 40], [20, 30], [30, 50]],
mm: [[10, 20], [30, 40], [20, 30], [30, 50]],
LL: [[10, 20], [30, 40], [20, 30], [30, 50]],
ll: [[10, 20], [30, 40], [20, 30], [30, 50]],
CC: [[10, 20, 30, 40, 50, 60], [70, 80, 90, 100, 110, 120],
[40, 50, 60, 70, 80, 90], [50, 70, 90, 110, 130, 150]],
cc: [[10, 20, 30, 40, 50, 60], [70, 80, 90, 100, 110, 120],
[40, 50, 60, 70, 80, 90], [50, 70, 90, 110, 130, 150]],
QQ: [[10, 20, 30, 40], [50, 60, 70, 80], [30, 40, 50, 60], [40, 60, 80, 100]],
qq: [[10, 20, 30, 40], [50, 60, 70, 80], [30, 40, 50, 60], [40, 60, 80, 100]],
AA: [[10, 20, 30, 0, 0, 40, 50], [60, 70, 80, 0, 0, 90, 100],
[35, 45, 55, 0, 0, 65, 75], [45, 65, 85, 0, 0, 105, 125]],
aa: [[10, 20, 30, 0, 0, 40, 50], [60, 70, 80, 0, 0, 90, 100],
[35, 45, 55, 0, 0, 65, 75], [45, 65, 85, 0, 0, 105, 125]],
HH: [[10], [20], [15], [25]],
hh: [[10], [20], [15], [25]],
VV: [[10], [20], [15], [25]],
vv: [[10], [20], [15], [25]],
SS: [[10, 20, 30, 40], [50, 60, 70, 80], [30, 40, 50, 60], [40, 60, 80, 100]],
ss: [[10, 20, 30, 40], [50, 60, 70, 80], [30, 40, 50, 60], [40, 60, 80, 100]],
TT: [[10, 20], [30, 40], [20, 30], [30, 50]],
tt: [[10, 20], [30, 40], [20, 30], [30, 50]],
// Relative <-> absolute conversion.
Mm: [[10, 20], [30, 40], [-30, -20], [-120, -100]],
mM: [[10, 20], [30, 40], [70, 80], [180, 200]],
Ll: [[10, 20], [30, 40], [-30, -20], [-120, -100]],
lL: [[10, 20], [30, 40], [70, 80], [180, 200]],
Cc: [[10, 20, 30, 40, 50, 60], [70, 80, 90, 100, 110, 120],
[-10, 0, 10, 20, 30, 40], [-100, -80, -60, -40, -20, 0]],
cC: [[10, 20, 30, 40, 50, 60], [70, 80, 90, 100, 110, 120],
[90, 100, 110, 120, 130, 140], [200, 220, 240, 260, 280, 300]],
Qq: [[10, 20, 30, 40], [50, 60, 70, 80],
[-20, -10, 0, 10], [-110, -90, -70, -50]],
qQ: [[10, 20, 30, 40], [50, 60, 70, 80],
[80, 90, 100, 110], [190, 210, 230, 250]],
Aa: [[10, 20, 30, 0, 0, 40, 50], [60, 70, 80, 0, 0, 90, 100],
[35, 45, 55, 0, 0, 15, 25], [45, 65, 85, 0, 0, -45, -25]],
aA: [[10, 20, 30, 0, 0, 40, 50], [60, 70, 80, 0, 0, 90, 100],
[35, 45, 55, 0, 0, 115, 125], [45, 65, 85, 0, 0, 255, 275]],
Hh: [[10], [20], [-35], [-125]],
hH: [[10], [20], [65], [175]],
Vv: [[10], [20], [-35], [-125]],
vV: [[10], [20], [65], [175]],
Tt: [[10, 20], [30, 40], [-30,-20], [-120, -100]],
tT: [[10, 20], [30, 40], [70, 80], [180, 200]],
Ss: [[10, 20, 30, 40], [50, 60, 70, 80],
[-20, -10, 0, 10], [-110, -90, -70, -50]],
sS: [[10, 20, 30, 40], [50, 60, 70, 80],
[80, 90, 100, 110], [190, 210, 230, 250]]
};
// Returns an array of property names that exist on an SVGPathSeg object
// corresponding to the given segment type, in the order that they would
// be present in a path data string.
function argumentNames(aType)
{
return gArgumentNames[aType.toUpperCase()];
}
// Creates and returns a new element and sets some attributes on it.
function newElement(aNamespaceURI, aLocalName, aAttributes)
{
var e = document.createElementNS(aNamespaceURI, aLocalName);
if (aAttributes) {
for (let [name, value] in Iterator(aAttributes)) {
e.setAttribute(name, value);
}
}
return e;
}
// Creates and returns a new SVG element and sets some attributes on it.
function newSVGElement(aLocalName, aAttributes)
{
return newElement("http://www.w3.org/2000/svg", aLocalName, aAttributes);
}
// Creates a subtest and adds it to the document.
//
// * aPrefixLength/aPrefix the prefix to use
// * aFromType/aFromArguments the segment to interpolate from
// * aToType/aToArguments the segment to interpolate to
// * aExpectedType/aExpectedArguments the expected result of the interpolated
// segment half way through the animation
// duration
// * aAdditive whether the subtest is for an additive
// animation
function addTest(aPrefixLength, aPrefix, aFromType, aFromArguments,
aToType, aToArguments, aExpectedType, aExpectedArguments,
aAdditive)
{
var fromPath = aPrefix + aFromType + aFromArguments,
toPath = aPrefix + aToType + aToArguments;
var path = newSVGElement("path", { d: fromPath });
var animate =
newSVGElement("animate", { attributeName: "d",
from: fromPath,
to: toPath,
dur: "8s",
additive: aAdditive ? "sum" : "replace" });
path.appendChild(animate);
gSVG.appendChild(path);
gTests.push({ element: path,
prefixLength: aPrefixLength,
from: fromPath,
to: toPath,
toType: aToType,
expectedType: aExpectedType,
expected: aExpectedArguments,
usesAddition: aAdditive });
}
// Generates an array of path segment arguments for the given type. aOffset
// is a number to add on to all non-Boolean segment arguments.
function generatePathSegmentArguments(aType, aOffset)
{
var args = new Array(argumentNames(aType).length);
for (let i = 0; i < args.length; i++) {
args[i] = i * 10 + aOffset;
}
if (aType == "A" || aType == "a") {
args[3] = 0;
args[4] = 0;
}
return args;
}
// Returns whether interpolating between the two given types is valid.
function isValidInterpolation(aFromType, aToType)
{
return aFromType.toUpperCase() == aToType.toUpperCase();
}
// Runs the test.
function run()
{
for each (let additive in [false, true]) {
let indexOfExpectedArguments = additive ? 3 : 2;
// Add subtests for each combination of prefix and suffix, and additive
// or not.
for (let [typePair, suffixEntry] in Iterator(gSuffixes)) {
let fromType = typePair[0],
toType = typePair[1],
fromArguments = suffixEntry[0],
toArguments = suffixEntry[1],
expectedArguments = suffixEntry[indexOfExpectedArguments];
for each (let prefixEntry in gPrefixes) {
let [prefixLength, prefix] = prefixEntry;
addTest(prefixLength, prefix, fromType, fromArguments,
toType, toArguments, toType, expectedArguments, additive);
}
}
// Test that differences in arc flag parameters cause the
// interpolation/addition not to occur.
addTest(1, "M100,100",
"A", [10, 20, 30, 0, 0, 40, 50],
"a", [60, 70, 80, 0, 1, 90, 100],
"a", [60, 70, 80, 0, 1, 90, 100], additive);
addTest(1, "M100,100",
"A", [10, 20, 30, 0, 0, 40, 50],
"a", [60, 70, 80, 1, 0, 90, 100],
"a", [60, 70, 80, 1, 0, 90, 100], additive);
// Test all pairs of segment types that cannot be interpolated between.
for each (let fromType in gTypes) {
let fromArguments = generatePathSegmentArguments(fromType, 0);
for each (let toType in gTypes) {
if (!isValidInterpolation(fromType, toType)) {
let toArguments = generatePathSegmentArguments(toType, 1000);
addTest(1, "M100,100", fromType, fromArguments,
toType, toArguments, toType, toArguments, additive);
}
}
}
}
// Move the document time to half way through the animations.
gSVG.setCurrentTime(4);
// Inspect the results of each subtest.
for each (let test in gTests) {
let list = test.element.animatedPathSegList;
is(list.numberOfItems, test.prefixLength + 1,
"Length of animatedPathSegList for interpolation " +
(test.usesAddition ? "with addition " : "") +
" from " + test.from + " to " + test.to);
let seg = list.getItem(list.numberOfItems - 1);
let propertyNames = argumentNames(test.expectedType);
let actual = [];
for (let i = 0; i < test.expected.length; i++) {
actual.push(+seg[propertyNames[i]]);
}
is(seg.pathSegTypeAsLetter + actual, test.expectedType + test.expected,
"Path segment for interpolation " +
(test.usesAddition ? "with addition " : "") +
" from " + test.from + " to " + test.to);
}
SimpleTest.finish();
}
window.addEventListener("load", run, false);
]]></script>
</body>
</html>

View File

@ -89,7 +89,7 @@ nsXMLElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
(!guard.Mutated(0) ||
!mNodeInfo->GetIDAttributeAtom() ||
!HasAttr(kNameSpaceID_None, GetIDAttributeName()))) {
UnsetFlags(NODE_HAS_ID);
ClearHasID();
}
return rv;
@ -104,7 +104,7 @@ nsXMLElement::GetIDAttributeName() const
nsIAtom*
nsXMLElement::DoGetID() const
{
NS_ASSERTION(HasFlag(NODE_HAS_ID), "Unexpected call");
NS_ASSERTION(HasID(), "Unexpected call");
const nsAttrValue* attrVal = mAttrsAndChildren.GetAttr(GetIDAttributeName());
return attrVal ? attrVal->GetAtomValue() : nsnull;
@ -118,7 +118,7 @@ nsXMLElement::NodeInfoChanged(nsINodeInfo* aOldNodeInfo)
"Can only change document if we're not inside one");
nsIDocument* doc = GetCurrentDoc();
if (HasFlag(NODE_HAS_ID) && doc) {
if (HasID() && doc) {
const nsAttrValue* attrVal =
mAttrsAndChildren.GetAttr(aOldNodeInfo->GetIDAttributeAtom());
if (attrVal) {
@ -126,13 +126,13 @@ nsXMLElement::NodeInfoChanged(nsINodeInfo* aOldNodeInfo)
}
}
UnsetFlags(NODE_HAS_ID);
ClearHasID();
nsIAtom* IDName = GetIDAttributeName();
if (IDName) {
const nsAttrValue* attrVal = mAttrsAndChildren.GetAttr(IDName);
if (attrVal) {
SetFlags(NODE_HAS_ID);
SetHasID();
if (attrVal->Type() == nsAttrValue::eString) {
nsString idVal(attrVal->GetStringValue());
@ -160,11 +160,11 @@ nsXMLElement::ParseAttribute(PRInt32 aNamespaceID,
// not that it has an emptystring as the id.
RemoveFromIdTable();
if (aValue.IsEmpty()) {
UnsetFlags(NODE_HAS_ID);
ClearHasID();
return PR_FALSE;
}
aResult.ParseAtom(aValue);
SetFlags(NODE_HAS_ID);
SetHasID();
AddToIdTable(aResult.GetAtomValue());
return PR_TRUE;
}
@ -182,7 +182,7 @@ nsXMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
aCompileEventHandlers);
NS_ENSURE_SUCCESS(rv, rv);
if (aDocument && HasFlag(NODE_HAS_ID) && !GetBindingParent()) {
if (aDocument && HasID() && !GetBindingParent()) {
aDocument->AddToIdTable(this, DoGetID());
}

View File

@ -248,8 +248,8 @@ nsXULElement::nsXULElement(already_AddRefed<nsINodeInfo> aNodeInfo)
XUL_PROTOTYPE_ATTRIBUTE_METER(gNumElements);
}
nsXULElement::nsXULSlots::nsXULSlots(PtrBits aFlags)
: nsXULElement::nsDOMSlots(aFlags)
nsXULElement::nsXULSlots::nsXULSlots()
: nsXULElement::nsDOMSlots()
{
}
@ -264,7 +264,7 @@ nsXULElement::nsXULSlots::~nsXULSlots()
nsINode::nsSlots*
nsXULElement::CreateSlots()
{
return new nsXULSlots(mFlagsOrSlots);
return new nsXULSlots();
}
/* static */
@ -279,13 +279,13 @@ nsXULElement::Create(nsXULPrototypeElement* aPrototype, nsINodeInfo *aNodeInfo,
element->mPrototype = aPrototype;
if (aPrototype->mHasIdAttribute) {
element->SetFlags(NODE_HAS_ID);
element->SetHasID();
}
if (aPrototype->mHasClassAttribute) {
element->SetFlags(NODE_MAY_HAVE_CLASS);
}
if (aPrototype->mHasStyleAttribute) {
element->SetFlags(NODE_MAY_HAVE_STYLE);
element->SetMayHaveStyle();
}
NS_ASSERTION(aPrototype->mScriptTypeID != nsIProgrammingLanguage::UNKNOWN,
@ -1408,7 +1408,7 @@ nsXULElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, PRBool aNotify)
// XXX Know how to remove POPUP event listeners when an attribute is unset?
if (isId) {
UnsetFlags(NODE_HAS_ID);
ClearHasID();
}
if (aNameSpaceID == kNameSpaceID_None) {
@ -1778,15 +1778,15 @@ nsXULElement::GetBuilder(nsIXULTemplateBuilder** aBuilder)
nsIAtom*
nsXULElement::DoGetID() const
{
NS_ASSERTION(HasFlag(NODE_HAS_ID), "Unexpected call");
NS_ASSERTION(HasID(), "Unexpected call");
const nsAttrValue* attr =
FindLocalOrProtoAttr(kNameSpaceID_None, nsGkAtoms::id);
// We need the nullcheck here because during unlink the prototype looses
// We need the nullcheck here because during unlink the prototype loses
// all of its attributes. We might want to change that.
// The nullcheck would also be needed if we make UnsetAttr use
// nsGenericElement::UnsetAttr as that calls out to various code between
// removing the attribute and clearing the NODE_HAS_ID flag.
// removing the attribute and calling ClearHasID().
return attr ? attr->GetAtomValue() : nsnull;
}
@ -1807,7 +1807,7 @@ nsXULElement::WalkContentStyleRules(nsRuleWalker* aRuleWalker)
css::StyleRule*
nsXULElement::GetInlineStyleRule()
{
if (!HasFlag(NODE_MAY_HAVE_STYLE)) {
if (!MayHaveStyle()) {
return nsnull;
}
// Fetch the cached style rule from the attributes.

View File

@ -621,7 +621,7 @@ protected:
class nsXULSlots : public nsGenericElement::nsDOMSlots
{
public:
nsXULSlots(PtrBits aFlags);
nsXULSlots();
virtual ~nsXULSlots();
nsRefPtr<nsFrameLoader> mFrameLoader;

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