author | David Anderson <danderson@mozilla.com> |
Mon, 19 Jul 2010 20:49:26 -0700 | |
changeset 53125 | 1d68b3042bf58e0b4cd561ba61d2d399a3a269a9 |
parent 53124 | da41e5a6de821ac221edcf7101846662cf813471 (current diff) |
parent 48506 | 684ff9ad214e9e0bb33794c276598083c59f2bff (diff) |
child 53126 | d49db5482db6447a775c48ca9c1b8f15e1fd763d |
push id | unknown |
push user | unknown |
push date | unknown |
milestone | 2.0b2pre |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/accessible/public/nsIAccessibleTable.idl +++ b/accessible/public/nsIAccessibleTable.idl @@ -40,17 +40,17 @@ * * ***** END LICENSE BLOCK ***** */ #include "nsISupports.idl" interface nsIAccessible; interface nsIArray; -[scriptable, uuid(035c0c0e-41e3-4985-8ad9-d9f14cdc667a)] +[scriptable, uuid(cb0bf7b9-117e-40e2-9e46-189c3d43ce4a)] interface nsIAccessibleTable : nsISupports { /** * Return the caption accessible for the table. For example, html:caption * element of html:table element. */ readonly attribute nsIAccessible caption; @@ -100,16 +100,27 @@ interface nsIAccessibleTable : nsISuppor /** * Translate the given cell index into the corresponding row index. * * @param cellIndex [in] index of the table cell to return row index for */ long getRowIndexAt(in long cellIndex); /** + * Translate the given cell index into the corresponding row and column + * indices. + * + * @param cellIndex [in] cell index to return row and column indices for + * @param rowIndex [out] row index at the given cell index + * @param columnIndex [out] column index at the given cell index + */ + void getRowAndColumnIndicesAt(in long cellIndex, + out long rowIndex, out long columnIndex); + + /** * Return the number of columns occupied by the accessible cell at * the specified row and column in the table. The result differs from 1 if * the specified cell spans multiple columns. * * @param row [in] row index of the cell to return the column extent for * @param column [in] column index of the cell to return the column extent * for */
new file mode 100644 --- /dev/null +++ b/accessible/src/base/AccGroupInfo.cpp @@ -0,0 +1,169 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Alexander Surkov <surkov.alexander@gmail.com> (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "AccGroupInfo.h" + +AccGroupInfo::AccGroupInfo(nsAccessible* aItem, PRUint32 aRole) : + mPosInSet(0), mSetSize(0), mParent(nsnull) +{ + nsAccessible* parent = aItem->GetParent(); + if (!parent) + return; + + PRInt32 indexInParent = aItem->GetIndexInParent(); + PRInt32 level = nsAccUtils::GetARIAOrDefaultLevel(aItem); + + // Compute position in set. + mPosInSet = 1; + for (PRInt32 idx = indexInParent - 1; idx >=0 ; idx--) { + nsAccessible* sibling = parent->GetChildAt(idx); + PRUint32 siblingRole = nsAccUtils::Role(sibling); + + // If the sibling is separator then the group is ended. + if (siblingRole == nsIAccessibleRole::ROLE_SEPARATOR) + break; + + // If sibling is not visible and hasn't the same base role. + if (BaseRole(siblingRole) != aRole || + nsAccUtils::State(sibling) & nsIAccessibleStates::STATE_INVISIBLE) + continue; + + // Check if it's hierarchical flatten structure, i.e. if the sibling + // level is lesser than this one then group is ended, if the sibling level + // is greater than this one then the group is split by some child elements + // (group will be continued). + PRInt32 siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling); + if (siblingLevel < level) { + mParent = sibling; + break; + } + + // Skip subset. + if (siblingLevel > level) + continue; + + // If the previous item in the group has calculated group information then + // build group information for this item based on found one. + if (sibling->mGroupInfo) { + mPosInSet += sibling->mGroupInfo->mPosInSet; + mParent = sibling->mGroupInfo->mParent; + mSetSize = sibling->mGroupInfo->mSetSize; + return; + } + + mPosInSet++; + } + + // Compute set size. + mSetSize = mPosInSet; + + PRInt32 siblingCount = parent->GetChildCount(); + for (PRInt32 idx = indexInParent + 1; idx < siblingCount; idx++) { + nsAccessible* sibling = parent->GetChildAt(idx); + + PRUint32 siblingRole = nsAccUtils::Role(sibling); + + // If the sibling is separator then the group is ended. + if (siblingRole == nsIAccessibleRole::ROLE_SEPARATOR) + break; + + // If sibling is visible and has the same base role + if (BaseRole(siblingRole) != aRole || + nsAccUtils::State(sibling) & nsIAccessibleStates::STATE_INVISIBLE) + continue; + + // and check if it's hierarchical flatten structure. + PRInt32 siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling); + if (siblingLevel < level) + break; + + // Skip subset. + if (siblingLevel > level) + continue; + + // If the next item in the group has calculated group information then + // build group information for this item based on found one. + if (sibling->mGroupInfo) { + mParent = sibling->mGroupInfo->mParent; + mSetSize = sibling->mGroupInfo->mSetSize; + return; + } + + mSetSize++; + } + + if (mParent) + return; + + // Compute parent. + PRUint32 parentRole = nsAccUtils::Role(parent); + + // In the case of ARIA row in treegrid, return treegrid since ARIA + // groups aren't used to organize levels in ARIA treegrids. + if (aRole == nsIAccessibleRole::ROLE_ROW && + parentRole == nsIAccessibleRole::ROLE_TREE_TABLE) { + mParent = parent; + return; + } + + // In the case of ARIA tree, a tree can be arranged by using ARIA groups + // to organize levels. In this case the parent of the tree item will be + // a group and the previous treeitem of that should be the tree item + // parent. Or, if the parent is something other than a tree we will + // return that. + + if (parentRole != nsIAccessibleRole::ROLE_GROUPING) { + mParent = parent; + return; + } + + nsAccessible* parentPrevSibling = parent->GetSiblingAtOffset(-1); + PRUint32 parentPrevSiblingRole = nsAccUtils::Role(parentPrevSibling); + if (parentPrevSiblingRole == nsIAccessibleRole::ROLE_TEXT_LEAF) { + // XXX Sometimes an empty text accessible is in the hierarchy here, + // although the text does not appear to be rendered, GetRenderedText() + // says that it is so we need to skip past it to find the true + // previous sibling. + parentPrevSibling = parentPrevSibling->GetSiblingAtOffset(-1); + parentPrevSiblingRole = nsAccUtils::Role(parentPrevSibling); + } + + // Previous sibling of parent group is a tree item, this is the + // conceptual tree item parent. + if (parentPrevSiblingRole == nsIAccessibleRole::ROLE_OUTLINEITEM) + mParent = parentPrevSibling; +}
new file mode 100644 --- /dev/null +++ b/accessible/src/base/AccGroupInfo.h @@ -0,0 +1,96 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Alexander Surkov <surkov.alexander@gmail.com> (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef AccGroupInfo_h_ +#define AccGroupInfo_h_ + +#include "nsAccessible.h" +#include "nsAccUtils.h" + +/** + * Calculate and store group information. + */ +class AccGroupInfo +{ +public: + AccGroupInfo(nsAccessible* aItem, PRUint32 aRole); + ~AccGroupInfo() { } + + PRInt32 PosInSet() const { return mPosInSet; } + PRUint32 SetSize() const { return mSetSize; } + nsAccessible* GetConceptualParent() const { return mParent; } + + /** + * Create group info. + */ + static AccGroupInfo* CreateGroupInfo(nsAccessible* aAccessible) + { + PRUint32 role = nsAccUtils::Role(aAccessible); + if (role != nsIAccessibleRole::ROLE_ROW && + role != nsIAccessibleRole::ROLE_GRID_CELL && + role != nsIAccessibleRole::ROLE_OUTLINEITEM && + role != nsIAccessibleRole::ROLE_OPTION && + role != nsIAccessibleRole::ROLE_LISTITEM && + role != nsIAccessibleRole::ROLE_MENUITEM && + role != nsIAccessibleRole::ROLE_CHECK_MENU_ITEM && + role != nsIAccessibleRole::ROLE_RADIO_MENU_ITEM && + role != nsIAccessibleRole::ROLE_RADIOBUTTON && + role != nsIAccessibleRole::ROLE_PAGETAB) + return nsnull; + + AccGroupInfo* info = new AccGroupInfo(aAccessible, BaseRole(role)); + return info; + } + +private: + AccGroupInfo(const AccGroupInfo&); + AccGroupInfo& operator =(const AccGroupInfo&); + + static PRUint32 BaseRole(PRUint32 aRole) + { + if (aRole == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM || + aRole == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM) + return nsIAccessibleRole::ROLE_MENUITEM; + return aRole; + } + + PRUint32 mPosInSet; + PRUint32 mSetSize; + nsAccessible* mParent; +}; + +#endif
--- a/accessible/src/base/Makefile.in +++ b/accessible/src/base/Makefile.in @@ -44,16 +44,17 @@ include $(DEPTH)/config/autoconf.mk MODULE = accessibility LIBRARY_NAME = accessibility_base_s LIBXUL_LIBRARY = 1 CPPSRCS = \ AccCollector.cpp \ + AccGroupInfo.cpp \ AccIterator.cpp \ filters.cpp \ nsAccDocManager.cpp \ nsAccessNode.cpp \ nsAccEvent.cpp \ nsARIAGridAccessible.cpp \ nsARIAMap.cpp \ nsDocAccessible.cpp \
--- a/accessible/src/base/nsARIAGridAccessible.cpp +++ b/accessible/src/base/nsARIAGridAccessible.cpp @@ -217,16 +217,44 @@ nsARIAGridAccessible::GetRowIndexAt(PRIn NS_ENSURE_ARG(aCellIndex < rowCount * colsCount); *aRowIndex = aCellIndex / colsCount; return NS_OK; } NS_IMETHODIMP +nsARIAGridAccessible::GetRowAndColumnIndicesAt(PRInt32 aCellIndex, + PRInt32* aRowIndex, + PRInt32* aColumnIndex) +{ + NS_ENSURE_ARG_POINTER(aRowIndex); + *aRowIndex = -1; + NS_ENSURE_ARG_POINTER(aColumnIndex); + *aColumnIndex = -1; + + if (IsDefunct()) + return NS_ERROR_FAILURE; + + NS_ENSURE_ARG(aCellIndex >= 0); + + PRInt32 rowCount = 0; + GetRowCount(&rowCount); + + PRInt32 colsCount = 0; + GetColumnCount(&colsCount); + + NS_ENSURE_ARG(aCellIndex < rowCount * colsCount); + + *aColumnIndex = aCellIndex % colsCount; + *aRowIndex = aCellIndex / colsCount; + return NS_OK; +} + +NS_IMETHODIMP nsARIAGridAccessible::GetColumnExtentAt(PRInt32 aRow, PRInt32 aColumn, PRInt32 *aExtentCount) { NS_ENSURE_ARG_POINTER(aExtentCount); *aExtentCount = 0; if (IsDefunct()) return NS_ERROR_FAILURE;
--- a/accessible/src/base/nsAccCache.h +++ b/accessible/src/base/nsAccCache.h @@ -34,21 +34,20 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifndef _nsAccCache_H_ #define _nsAccCache_H_ +#include "nsIAccessible.h" #include "nsRefPtrHashtable.h" #include "nsCycleCollectionParticipant.h" -class nsIAccessible; - //////////////////////////////////////////////////////////////////////////////// // Accessible cache utils //////////////////////////////////////////////////////////////////////////////// /** * Shutdown and removes the accessible from cache. */ template <class T>
--- a/accessible/src/base/nsAccUtils.cpp +++ b/accessible/src/base/nsAccUtils.cpp @@ -362,116 +362,16 @@ nsAccUtils::GetAncestorWithRole(nsAccess return parent; if (parent == document) break; } return nsnull; } -void -nsAccUtils::GetARIATreeItemParent(nsIAccessible *aStartTreeItem, - nsIContent *aStartContent, - nsIAccessible **aTreeItemParentResult) -{ - *aTreeItemParentResult = nsnull; - - nsCOMPtr<nsIAccessible> parentAccessible; - aStartTreeItem->GetParent(getter_AddRefs(parentAccessible)); - if (!parentAccessible) - return; - - PRUint32 startTreeItemRole = nsAccUtils::Role(aStartTreeItem); - - // Calculate tree grid row parent only if the row inside of ARIA treegrid. - if (startTreeItemRole == nsIAccessibleRole::ROLE_ROW) { - PRUint32 role = nsAccUtils::Role(parentAccessible); - if (role != nsIAccessibleRole::ROLE_TREE_TABLE) - return; - } - - // This is a tree or treegrid that uses aria-level to define levels, so find - // the first previous sibling accessible where level is defined to be less - // than the current level. - nsAutoString levelStr; - if (nsAccUtils::HasDefinedARIAToken(aStartContent, nsAccessibilityAtoms::aria_level) && - aStartContent->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_level, levelStr)) { - - PRInt32 success; - PRInt32 level = levelStr.ToInteger(&success); - if (level > 1 && NS_SUCCEEDED(success)) { - nsCOMPtr<nsIAccessible> currentAccessible = aStartTreeItem, prevAccessible; - while (PR_TRUE) { - currentAccessible->GetPreviousSibling(getter_AddRefs(prevAccessible)); - currentAccessible.swap(prevAccessible); - nsCOMPtr<nsIAccessNode> accessNode = do_QueryInterface(currentAccessible); - if (!accessNode) { - break; // Reached top of tree, no higher level found - } - PRUint32 role = nsAccUtils::Role(currentAccessible); - if (role != startTreeItemRole) - continue; - - nsCOMPtr<nsIDOMNode> treeItemNode; - accessNode->GetDOMNode(getter_AddRefs(treeItemNode)); - nsCOMPtr<nsIContent> treeItemContent = do_QueryInterface(treeItemNode); - if (treeItemContent && - nsAccUtils::HasDefinedARIAToken(treeItemContent, - nsAccessibilityAtoms::aria_level) && - treeItemContent->GetAttr(kNameSpaceID_None, - nsAccessibilityAtoms::aria_level, levelStr)) { - if (levelStr.ToInteger(&success) < level && NS_SUCCEEDED(success)) { - NS_ADDREF(*aTreeItemParentResult = currentAccessible); - return; - } - } - } - } - } - - // In the case of ARIA treegrid, return its parent since ARIA group isn't - // used to organize levels in ARIA treegrids. - - if (startTreeItemRole == nsIAccessibleRole::ROLE_ROW) { - NS_ADDREF(*aTreeItemParentResult = parentAccessible); - return; // The container for the tree grid rows - } - - // In the case of ARIA tree, a tree can be arranged by using role="group" to - // organize levels. In this case the parent of the tree item will be a group - // and the previous sibling of that should be the tree item parent. Or, if - // the parent is something other than a tree we will return that. - - PRUint32 role = nsAccUtils::Role(parentAccessible); - if (role != nsIAccessibleRole::ROLE_GROUPING) { - NS_ADDREF(*aTreeItemParentResult = parentAccessible); - return; // The container for the tree items - } - - nsCOMPtr<nsIAccessible> prevAccessible; - parentAccessible->GetPreviousSibling(getter_AddRefs(prevAccessible)); - if (!prevAccessible) - return; - role = nsAccUtils::Role(prevAccessible); - if (role == nsIAccessibleRole::ROLE_TEXT_LEAF) { - // XXX Sometimes an empty text accessible is in the hierarchy here, - // although the text does not appear to be rendered, GetRenderedText() says that it is - // so we need to skip past it to find the true previous sibling - nsCOMPtr<nsIAccessible> tempAccessible = prevAccessible; - tempAccessible->GetPreviousSibling(getter_AddRefs(prevAccessible)); - if (!prevAccessible) - return; - role = nsAccUtils::Role(prevAccessible); - } - if (role == nsIAccessibleRole::ROLE_OUTLINEITEM) { - // Previous sibling of parent group is a tree item -- this is the conceptual tree item parent - NS_ADDREF(*aTreeItemParentResult = prevAccessible); - } -} - nsAccessible * nsAccUtils::GetSelectableContainer(nsAccessible *aAccessible, PRUint32 aState) { if (!aAccessible) return nsnull; if (!(aState & nsIAccessibleStates::STATE_SELECTABLE)) return nsnull;
--- a/accessible/src/base/nsAccUtils.h +++ b/accessible/src/base/nsAccUtils.h @@ -193,29 +193,16 @@ public: * @param aDescendant [in] descendant to start search with * @param aRole [in] role to find matching ancestor for * @return the ancestor accessible with the given role, or * nsnull if no match is found */ static nsAccessible * GetAncestorWithRole(nsAccessible *aDescendant, PRUint32 aRole); - /** - * For an ARIA tree item , get the accessible that represents its conceptual parent. - * This method will use the correct method for the given way the tree is constructed. - * The conceptual parent is what the user sees as the parent, not the DOM or accessible parent. - * @param aStartTreeItem The tree item to get the parent for - * @param aStartTreeItemContent The content node for the tree item - * @param The tree item's parent, or null if none - */ - static void - GetARIATreeItemParent(nsIAccessible *aStartTreeItem, - nsIContent *aStartTreeItemContent, - nsIAccessible **aTreeItemParent); - /** * Return single or multi selectable container for the given item. * * @param aAccessible [in] the item accessible * @param aState [in] the state of the item accessible */ static nsAccessible *GetSelectableContainer(nsAccessible *aAccessible, PRUint32 aState);
--- a/accessible/src/base/nsAccessNode.cpp +++ b/accessible/src/base/nsAccessNode.cpp @@ -126,17 +126,17 @@ nsAccessNode::~nsAccessNode() void nsAccessNode::LastRelease() { // First cleanup if needed... if (mWeakShell) { Shutdown(); NS_ASSERTION(!mWeakShell, "A Shutdown() impl forgot to call its parent's Shutdown?"); } // ... then die. - NS_DELETEXPCOM(this); + delete this; } //////////////////////////////////////////////////////////////////////////////// // nsAccessNode public PRBool nsAccessNode::Init() {
--- a/accessible/src/base/nsAccessible.cpp +++ b/accessible/src/base/nsAccessible.cpp @@ -36,16 +36,17 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsAccessible.h" #include "nsIXBLAccessible.h" +#include "AccGroupInfo.h" #include "AccIterator.h" #include "nsAccUtils.h" #include "nsARIAMap.h" #include "nsDocAccessible.h" #include "nsEventShell.h" #include "nsAccessibilityService.h" #include "nsAccTreeWalker.h" @@ -2120,21 +2121,22 @@ nsAccessible::GetRelationByType(PRUint32 return NS_OK; // XXX bug 381599, avoid performance problems // This is an ARIA tree or treegrid that doesn't use owns, so we need to // get the parent the hard way. if (mRoleMapEntry && (mRoleMapEntry->role == nsIAccessibleRole::ROLE_OUTLINEITEM || mRoleMapEntry->role == nsIAccessibleRole::ROLE_ROW)) { - nsCOMPtr<nsIAccessible> accTarget; - nsAccUtils::GetARIATreeItemParent(this, mContent, - getter_AddRefs(accTarget)); - - return nsRelUtils::AddTarget(aRelationType, aRelation, accTarget); + AccGroupInfo* groupInfo = GetGroupInfo(); + if (!groupInfo) + return NS_OK_NO_RELATION_TARGET; + + return nsRelUtils::AddTarget(aRelationType, aRelation, + groupInfo->GetConceptualParent()); } // If accessible is in its own Window, or is the root of a document, // then we should provide NODE_CHILD_OF relation so that MSAA clients // can easily get to true parent instead of getting to oleacc's // ROLE_WINDOW accessible which will prevent us from going up further // (because it is system generated and has no idea about the hierarchy // above it). @@ -3086,108 +3088,34 @@ nsAccessible::GetActionRule(PRUint32 aSt // Get an action based on ARIA attribute. if (nsAccUtils::HasDefinedARIAToken(mContent, nsAccessibilityAtoms::aria_expanded)) return eExpandAction; return eNoAction; } +AccGroupInfo* +nsAccessible::GetGroupInfo() +{ + if (mGroupInfo) + return mGroupInfo; + + mGroupInfo = AccGroupInfo::CreateGroupInfo(this); + return mGroupInfo; +} + void nsAccessible::GetPositionAndSizeInternal(PRInt32 *aPosInSet, PRInt32 *aSetSize) { - PRUint32 role = nsAccUtils::Role(this); - if (role != nsIAccessibleRole::ROLE_LISTITEM && - role != nsIAccessibleRole::ROLE_MENUITEM && - role != nsIAccessibleRole::ROLE_CHECK_MENU_ITEM && - role != nsIAccessibleRole::ROLE_RADIO_MENU_ITEM && - role != nsIAccessibleRole::ROLE_RADIOBUTTON && - role != nsIAccessibleRole::ROLE_PAGETAB && - role != nsIAccessibleRole::ROLE_OPTION && - role != nsIAccessibleRole::ROLE_OUTLINEITEM && - role != nsIAccessibleRole::ROLE_ROW && - role != nsIAccessibleRole::ROLE_GRID_CELL) - return; - - PRUint32 baseRole = role; - if (role == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM || - role == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM) - baseRole = nsIAccessibleRole::ROLE_MENUITEM; - - nsAccessible* parent = GetParent(); - NS_ENSURE_TRUE(parent,); - - PRInt32 level = nsAccUtils::GetARIAOrDefaultLevel(this); - - // Compute 'posinset'. - PRInt32 positionInGroup = 1; - for (PRInt32 idx = mIndexInParent - 1; idx >= 0; idx--) { - nsAccessible* sibling = parent->GetChildAt(idx); - - PRUint32 siblingRole = nsAccUtils::Role(sibling); - - // If the sibling is separator then the group is ended. - if (siblingRole == nsIAccessibleRole::ROLE_SEPARATOR) - break; - - PRUint32 siblingBaseRole = siblingRole; - if (siblingRole == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM || - siblingRole == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM) - siblingBaseRole = nsIAccessibleRole::ROLE_MENUITEM; - - // If sibling is visible and has the same base role - if (siblingBaseRole == baseRole && - !(nsAccUtils::State(sibling) & nsIAccessibleStates::STATE_INVISIBLE)) { - - // and check if it's hierarchical flatten structure, i.e. if the sibling - // level is lesser than this one then group is ended, if the sibling level - // is greater than this one then the group is splited by some child - // elements (group will be continued). - PRInt32 siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling); - if (siblingLevel < level) - break; - else if (level == siblingLevel) - ++ positionInGroup; - } + AccGroupInfo* groupInfo = GetGroupInfo(); + if (groupInfo) { + *aPosInSet = groupInfo->PosInSet(); + *aSetSize = groupInfo->SetSize(); } - - // Compute 'setsize'. - PRInt32 setSize = positionInGroup; - - PRInt32 siblingCount = parent->GetChildCount(); - for (PRInt32 idx = mIndexInParent + 1; idx < siblingCount; idx++) { - nsAccessible* sibling = parent->GetChildAt(idx); - NS_ENSURE_TRUE(sibling,); - - PRUint32 siblingRole = nsAccUtils::Role(sibling); - - // If the sibling is separator then the group is ended. - if (siblingRole == nsIAccessibleRole::ROLE_SEPARATOR) - break; - - PRUint32 siblingBaseRole = siblingRole; - if (siblingRole == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM || - siblingRole == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM) - siblingBaseRole = nsIAccessibleRole::ROLE_MENUITEM; - - // If sibling is visible and has the same base role - if (siblingBaseRole == baseRole && - !(nsAccUtils::State(sibling) & nsIAccessibleStates::STATE_INVISIBLE)) { - - // and check if it's hierarchical flatten structure. - PRInt32 siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling); - if (siblingLevel < level) - break; - else if (level == siblingLevel) - ++ setSize; - } - } - - *aPosInSet = positionInGroup; - *aSetSize = setSize; } PRInt32 nsAccessible::GetLevelInternal() { PRInt32 level = nsAccUtils::GetDefaultLevel(this); PRUint32 role = nsAccUtils::Role(this);
--- a/accessible/src/base/nsAccessible.h +++ b/accessible/src/base/nsAccessible.h @@ -47,16 +47,17 @@ #include "nsIAccessibleValue.h" #include "nsIAccessibleRole.h" #include "nsIAccessibleStates.h" #include "nsStringGlue.h" #include "nsTArray.h" #include "nsRefPtrHashtable.h" +class AccGroupInfo; class nsAccessible; class nsAccEvent; struct nsRoleMapEntry; struct nsRect; class nsIContent; class nsIFrame; class nsIAtom; @@ -319,17 +320,22 @@ protected: * Cache accessible children. */ virtual void CacheChildren(); /** * Set accessible parent and index in parent. */ void BindToParent(nsAccessible* aParent, PRUint32 aIndexInParent); - void UnbindFromParent() { mParent = nsnull; mIndexInParent = -1; } + void UnbindFromParent() + { + mParent = nsnull; + mIndexInParent = -1; + mGroupInfo = nsnull; + } /** * Return sibling accessible at the given offset. */ virtual nsAccessible* GetSiblingAtOffset(PRInt32 aOffset, nsresult *aError = nsnull); ////////////////////////////////////////////////////////////////////////////// @@ -420,31 +426,39 @@ protected: * Return the action rule based on ARIA enum constants EActionRule * (see nsARIAMap.h). Used by GetNumActions() and GetActionName(). * * @param aStates [in] states of the accessible */ PRUint32 GetActionRule(PRUint32 aStates); /** + * Return group info. + */ + AccGroupInfo* GetGroupInfo(); + + /** * Fires platform accessible event. It's notification method only. It does * change nothing on Gecko side. Don't use it until you're sure what you do * (see example in XUL tree accessible), use nsEventShell::FireEvent() * instead. MUST be overridden in wrap classes. * * @param aEvent the accessible event to fire. */ virtual nsresult FirePlatformEvent(nsAccEvent *aEvent) = 0; // Data Members nsRefPtr<nsAccessible> mParent; nsTArray<nsRefPtr<nsAccessible> > mChildren; PRBool mAreChildrenInitialized; PRInt32 mIndexInParent; + nsAutoPtr<AccGroupInfo> mGroupInfo; + friend class AccGroupInfo; + nsRoleMapEntry *mRoleMapEntry; // Non-null indicates author-supplied role; possibly state & value as well }; NS_DEFINE_STATIC_IID_ACCESSOR(nsAccessible, NS_ACCESSIBLE_IMPL_IID) #endif
--- a/accessible/src/html/nsHTMLTableAccessible.cpp +++ b/accessible/src/html/nsHTMLTableAccessible.cpp @@ -949,16 +949,36 @@ nsHTMLTableAccessible::GetRowIndexAt(PRI PRInt32 column; nsresult rv = tableLayout->GetRowAndColumnByIndex(aIndex, aRow, &column); NS_ENSURE_SUCCESS(rv, rv); return (*aRow == -1 || column == -1) ? NS_ERROR_INVALID_ARG : NS_OK; } NS_IMETHODIMP +nsHTMLTableAccessible::GetRowAndColumnIndicesAt(PRInt32 aIndex, + PRInt32* aRowIdx, + PRInt32* aColumnIdx) +{ + NS_ENSURE_ARG_POINTER(aRowIdx); + *aRowIdx = -1; + NS_ENSURE_ARG_POINTER(aColumnIdx); + *aColumnIdx = -1; + + if (IsDefunct()) + return NS_ERROR_FAILURE; + + nsITableLayout* tableLayout = GetTableLayout(); + if (tableLayout) + tableLayout->GetRowAndColumnByIndex(aIndex, aRowIdx, aColumnIdx); + + return (*aRowIdx == -1 || *aColumnIdx == -1) ? NS_ERROR_INVALID_ARG : NS_OK; +} + +NS_IMETHODIMP nsHTMLTableAccessible::GetColumnExtentAt(PRInt32 aRowIndex, PRInt32 aColumnIndex, PRInt32 *aExtentCount) { nsITableLayout *tableLayout = GetTableLayout(); NS_ENSURE_STATE(tableLayout); nsCOMPtr<nsIDOMElement> domElement;
--- a/accessible/src/msaa/CAccessibleTable.cpp +++ b/accessible/src/msaa/CAccessibleTable.cpp @@ -661,43 +661,38 @@ CAccessibleTable::get_rowColumnExtentsAt *aColumnExtents = 0; *aIsSelected = false; nsCOMPtr<nsIAccessibleTable> tableAcc(do_QueryObject(this)); NS_ASSERTION(tableAcc, CANT_QUERY_ASSERTION_MSG); if (!tableAcc) return E_FAIL; - PRInt32 row = -1; - nsresult rv = tableAcc->GetRowIndexAt(aIndex, &row); - if (NS_FAILED(rv)) - return GetHRESULT(rv); - - PRInt32 column = -1; - rv = tableAcc->GetColumnIndexAt(aIndex, &column); + PRInt32 rowIdx = -1, columnIdx = -1; + nsresult rv = tableAcc->GetRowAndColumnIndicesAt(aIndex, &rowIdx, &columnIdx); if (NS_FAILED(rv)) return GetHRESULT(rv); PRInt32 rowExtents = 0; - rv = tableAcc->GetRowExtentAt(row, column, &rowExtents); + rv = tableAcc->GetRowExtentAt(rowIdx, columnIdx, &rowExtents); if (NS_FAILED(rv)) return GetHRESULT(rv); PRInt32 columnExtents = 0; - rv = tableAcc->GetColumnExtentAt(row, column, &columnExtents); + rv = tableAcc->GetColumnExtentAt(rowIdx, columnIdx, &columnExtents); if (NS_FAILED(rv)) return GetHRESULT(rv); PRBool isSelected = PR_FALSE; - rv = tableAcc->IsCellSelected(row, column, &isSelected); + rv = tableAcc->IsCellSelected(rowIdx, columnIdx, &isSelected); if (NS_FAILED(rv)) return GetHRESULT(rv); - *aRow = row; - *aColumn = column; + *aRow = rowIdx; + *aColumn = columnIdx; *aRowExtents = rowExtents; *aColumnExtents = columnExtents; *aIsSelected = isSelected; return S_OK; } __except(nsAccessNodeWrap::FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } return E_FAIL; }
--- a/accessible/src/xul/nsXULListboxAccessible.cpp +++ b/accessible/src/xul/nsXULListboxAccessible.cpp @@ -408,16 +408,38 @@ nsXULListboxAccessible::GetRowIndexAt(PR nsresult rv = GetColumnCount(&columnCount); NS_ENSURE_SUCCESS(rv, rv); *aRow = aIndex / columnCount; return NS_OK; } NS_IMETHODIMP +nsXULListboxAccessible::GetRowAndColumnIndicesAt(PRInt32 aCellIndex, + PRInt32* aRowIndex, + PRInt32* aColumnIndex) +{ + NS_ENSURE_ARG_POINTER(aRowIndex); + *aRowIndex = -1; + NS_ENSURE_ARG_POINTER(aColumnIndex); + *aColumnIndex = -1; + + if (IsDefunct()) + return NS_ERROR_FAILURE; + + PRInt32 columnCount = 0; + nsresult rv = GetColumnCount(&columnCount); + NS_ENSURE_SUCCESS(rv, rv); + + *aColumnIndex = aCellIndex % columnCount; + *aRowIndex = aCellIndex / columnCount; + return NS_OK; +} + +NS_IMETHODIMP nsXULListboxAccessible::GetColumnExtentAt(PRInt32 aRow, PRInt32 aColumn, PRInt32 *aCellSpans) { NS_ENSURE_ARG_POINTER(aCellSpans); *aCellSpans = 1; return NS_OK; }
--- a/accessible/src/xul/nsXULTreeGridAccessible.cpp +++ b/accessible/src/xul/nsXULTreeGridAccessible.cpp @@ -407,16 +407,38 @@ nsXULTreeGridAccessible::GetRowIndexAt(P nsresult rv = GetColumnCount(&columnCount); NS_ENSURE_SUCCESS(rv, rv); *aRowIndex = aCellIndex / columnCount; return NS_OK; } NS_IMETHODIMP +nsXULTreeGridAccessible::GetRowAndColumnIndicesAt(PRInt32 aCellIndex, + PRInt32* aRowIndex, + PRInt32* aColumnIndex) +{ + NS_ENSURE_ARG_POINTER(aRowIndex); + *aRowIndex = -1; + NS_ENSURE_ARG_POINTER(aColumnIndex); + *aColumnIndex = -1; + + if (IsDefunct()) + return NS_ERROR_FAILURE; + + PRInt32 columnCount = 0; + nsresult rv = GetColumnCount(&columnCount); + NS_ENSURE_SUCCESS(rv, rv); + + *aColumnIndex = aCellIndex % columnCount; + *aRowIndex = aCellIndex / columnCount; + return NS_OK; +} + +NS_IMETHODIMP nsXULTreeGridAccessible::GetColumnExtentAt(PRInt32 aRowIndex, PRInt32 aColumnIndex, PRInt32 *aExtentCount) { NS_ENSURE_ARG_POINTER(aExtentCount); *aExtentCount = 1; return NS_OK;
--- a/accessible/tests/mochitest/table.js +++ b/accessible/tests/mochitest/table.js @@ -219,45 +219,59 @@ function testTableIndexes(aIdentifier, a cellAcc = tableAcc.getCellAt(rowIdx, colIdx); } catch (e) { } ok(idx != -1 && cellAcc || idx == -1 && !cellAcc, id + ": Can't get cell accessible at row = " + rowIdx + ", column = " + colIdx); if (idx != - 1) { - // getRowAtIndex + // getRowIndexAt var origRowIdx = rowIdx; while (origRowIdx > 0 && aIdxes[rowIdx][colIdx] == aIdxes[origRowIdx - 1][colIdx]) origRowIdx--; try { obtainedRowIdx = tableAcc.getRowIndexAt(idx); } catch (e) { ok(false, id + ": can't get row index for cell index " + idx + "," + e); } is(obtainedRowIdx, origRowIdx, - id + ": row for index " + idx +" is not correct"); + id + ": row for index " + idx + " is not correct (getRowIndexAt)"); - // getColumnAtIndex + // getColumnIndexAt var origColIdx = colIdx; while (origColIdx > 0 && aIdxes[rowIdx][colIdx] == aIdxes[rowIdx][origColIdx - 1]) origColIdx--; try { obtainedColIdx = tableAcc.getColumnIndexAt(idx); } catch (e) { ok(false, id + ": can't get column index for cell index " + idx + "," + e); } is(obtainedColIdx, origColIdx, - id + ": column for index " + idx +" is not correct"); + id + ": column for index " + idx + " is not correct (getColumnIndexAt)"); + + // getRowAndColumnIndicesAt + var obtainedRowIdxObj = { }, obtainedColIdxObj = { }; + try { + tableAcc.getRowAndColumnIndicesAt(idx, obtainedRowIdxObj, + obtainedColIdxObj); + } catch (e) { + ok(false, id + ": can't get row and column indices for cell index " + idx + "," + e); + } + + is(obtainedRowIdxObj.value, origRowIdx, + id + ": row for index " + idx + " is not correct (getRowAndColumnIndicesAt)"); + is(obtainedColIdxObj.value, origColIdx, + id + ": column for index " + idx + " is not correct (getRowAndColumnIndicesAt)"); if (cellAcc) { var cellId = prettyName(cellAcc); cellAcc = getAccessible(cellAcc, [nsIAccessibleTableCell]); // cell: 'table-cell-index' attribute var attrs = cellAcc.attributes;
--- a/browser/app/macbuild/Contents/Info.plist.in +++ b/browser/app/macbuild/Contents/Info.plist.in @@ -129,10 +129,12 @@ </dict> </array> <key>CFBundleVersion</key> <string>%APP_VERSION%</string> <key>NSAppleScriptEnabled</key> <true/> <key>CGDisableCoalescedUpdates</key> <true/> + <key>LSMinimumSystemVersion</key> + <string>10.5</string> </dict> </plist>
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -315,18 +315,16 @@ pref("browser.tabs.closeWindowWithLastTa pref("browser.tabs.insertRelatedAfterCurrent", true); pref("browser.tabs.warnOnClose", true); pref("browser.tabs.warnOnOpen", true); pref("browser.tabs.maxOpenBeforeWarn", 15); pref("browser.tabs.loadInBackground", true); pref("browser.tabs.opentabfor.middleclick", true); pref("browser.tabs.loadDivertedInBackground", false); pref("browser.tabs.loadBookmarksInBackground", false); -pref("browser.tabs.tabMinWidth", 100); -pref("browser.tabs.tabMaxWidth", 250); pref("browser.tabs.tabClipWidth", 140); // Where to show tab close buttons: // 0 on active tab only // 1 on all tabs until tabClipWidth is reached, then active tab only // 2 no close buttons at all // 3 at the end of the tabstrip pref("browser.tabs.closeButtons", 1);
--- a/browser/base/content/browser-fullZoom.js +++ b/browser/base/content/browser-fullZoom.js @@ -125,17 +125,17 @@ var FullZoom = { // Retrieve the initial status of the Private Browsing mode. this._inPrivateBrowsing = Cc["@mozilla.org/privatebrowsing;1"]. getService(Ci.nsIPrivateBrowsingService). privateBrowsingEnabled; this._siteSpecificPref = gPrefService.getBoolPref("browser.zoom.siteSpecific"); - this.updateBackgroundTabs = + this.updateBackgroundTabs = gPrefService.getBoolPref("browser.zoom.updateBackgroundTabs"); // Listen for changes to the browser.zoom branch so we can enable/disable // updating background tabs and per-site saving and restoring of zoom levels. gPrefService.addObserver("browser.zoom.", this, true); }, destroy: function FullZoom_destroy() { let os = Cc["@mozilla.org/observer-service;1"].
--- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -17,22 +17,28 @@ tabbrowser { #tabbrowser-tabs[overflow="true"] > .tabbrowser-arrowscrollbox > .tabs-newtab-button, #TabsToolbar[currentset]:not([currentset*="tabbrowser-tabs,new-tab-button"]) > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button, #navigator-toolbox[customizing="true"] > #TabsToolbar > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button { visibility: collapse; } .tabbrowser-tab { -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tab"); +} + +.tabbrowser-tab:not([pinned]) { -moz-box-flex: 100; + max-width: 250px; + min-width: 100px; + width: 0; } .tabbrowser-tab:not([fadein]) { - max-width: 1px !important; - min-width: 1px !important; + max-width: 1px; + min-width: 1px; } .tabbrowser-tab[fadein]:not([pinned]) { -moz-transition: min-width .2s ease-out, max-width .25s ease-out; } .tabbrowser-tab:not([fadein]) > .tab-text, .tabbrowser-tab:not([fadein]) > .tab-icon-image, @@ -43,19 +49,16 @@ tabbrowser { .tabbrowser-tab[fadein] > .tab-text, .tabbrowser-tab[fadein] > .tab-icon-image, .tabbrowser-tab[fadein] > .tab-close-button { -moz-transition: opacity .25s; } .tabbrowser-tab[pinned] { position: fixed; - -moz-box-flex: 0; - min-width: 0 !important; - max-width: none !important; } .tabbrowser-tab[pinned] > .tab-text { display: none; } #alltabs-popup { -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup"); @@ -76,16 +79,22 @@ toolbar[printpreview="true"] { #TabsToolbar { -moz-box-ordinal-group: 100; } #navigator-toolbox[tabsontop="true"] > #TabsToolbar { -moz-box-ordinal-group: 10; } +%ifdef MENUBAR_CAN_AUTOHIDE +#main-window[inFullscreen] > #appmenu-button-container { + display: none; +} +%endif + toolbarpaletteitem[place="palette"] > toolbaritem > hbox[type="places"] { display: none; } toolbar[mode="icons"] > #reload-button:not([displaystop]) + #stop-button, toolbar[mode="icons"] > #reload-button[displaystop] { visibility: collapse; }
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -4009,17 +4009,17 @@ var XULBrowserWindow = { // check the current value so we don't trigger an attribute change // and cause needless (slow!) UI updates if (this.statusText != text) { this.statusTextField.label = text; this.statusText = text; } }, - onLinkIconAvailable: function (aBrowser, aIconURL) { + onLinkIconAvailable: function (aIconURL) { if (gProxyFavIcon && gBrowser.userTypedValue === null) PageProxySetIcon(aIconURL); // update the favicon in the URL bar }, onProgressChange: function (aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) { // Check this._busyUI to be safe, because we don't want to update @@ -4485,41 +4485,33 @@ var CombinedStopReload = { if (this._timer) { clearTimeout(this._timer); this._timer = 0; } } }; var TabsProgressListener = { - onProgressChange: function (aBrowser, aWebProgress, aRequest, - aCurSelfProgress, aMaxSelfProgress, - aCurTotalProgress, aMaxTotalProgress) { - }, - +#ifdef MOZ_CRASHREPORTER onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { -#ifdef MOZ_CRASHREPORTER if (aRequest instanceof Ci.nsIChannel && aStateFlags & Ci.nsIWebProgressListener.STATE_START && aStateFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT && gCrashReporter.enabled) { gCrashReporter.annotateCrashReport("URL", aRequest.URI.spec); } + }, #endif - }, onLocationChange: function (aBrowser, aWebProgress, aRequest, aLocationURI) { // Filter out any sub-frame loads if (aBrowser.contentWindow == aWebProgress.DOMWindow) FullZoom.onLocationChange(aLocationURI, false, aBrowser); }, - onStatusChange: function (aBrowser, aWebProgress, aRequest, aStatus, aMessage) { - }, - onRefreshAttempted: function (aBrowser, aWebProgress, aURI, aDelay, aSameURI) { if (gPrefService.getBoolPref("accessibility.blockautorefresh")) { let brandBundle = document.getElementById("bundle_brand"); let brandShortName = brandBundle.getString("brandShortName"); let refreshButtonText = gNavigatorBundle.getString("refreshBlocked.goButton"); let refreshButtonAccesskey = gNavigatorBundle.getString("refreshBlocked.goButton.accesskey"); @@ -4556,19 +4548,16 @@ var TabsProgressListener = { buttons); notification.refreshURI = aURI; notification.delay = aDelay; notification.docShell = docShell; } return false; } return true; - }, - - onSecurityChange: function (aBrowser, aWebProgress, aRequest, aState) { } } function nsBrowserAccess() { } nsBrowserAccess.prototype = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]), @@ -4722,22 +4711,22 @@ var TabsOnTop = { this.syncCommand(); return val; } } #ifdef MENUBAR_CAN_AUTOHIDE function updateAppButtonDisplay() { - var menubarHidden = + var displayAppButton = window.menubar.visible && document.getElementById("toolbar-menubar").getAttribute("autohide") == "true"; - document.getElementById("appmenu-button-container").hidden = !menubarHidden; - - if (menubarHidden) + document.getElementById("appmenu-button-container").hidden = !displayAppButton; + + if (displayAppButton) document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1"); else document.documentElement.removeAttribute("chromemargin"); } #endif function displaySecurityInfo() { @@ -7767,16 +7756,20 @@ var TabContextMenu = { // Session store // XXXzeniko should't we just disable this item as we disable // the tabbrowser-multiple items above - for consistency? document.getElementById("context_undoCloseTab").hidden = Cc["@mozilla.org/browser/sessionstore;1"]. getService(Ci.nsISessionStore). getClosedTabCount(window) == 0; + + // Only one of pin/unpin should be visible + document.getElementById("context_pinTab").hidden = this.contextTab.pinned; + document.getElementById("context_unpinTab").hidden = !this.contextTab.pinned; } }; XPCOMUtils.defineLazyGetter(this, "HUDConsoleUI", function () { Cu.import("resource://gre/modules/HUDService.jsm"); try { return HUDService.consoleUI; }
--- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -121,16 +121,22 @@ <menuitem id="context_closeOtherTabs" label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;" tbattr="tabbrowser-multiple" oncommand="gBrowser.removeAllTabsBut(TabContextMenu.contextTab);"/> <menuseparator/> <menuitem id="context_openTabInWindow" label="&openTabInNewWindow.label;" accesskey="&openTabInNewWindow.accesskey;" tbattr="tabbrowser-multiple" oncommand="gBrowser.replaceTabWithWindow(TabContextMenu.contextTab);"/> + <menuitem id="context_pinTab" label="&pinTab.label;" + accesskey="&pinTab.accesskey;" + oncommand="gBrowser.pinTab(TabContextMenu.contextTab);"/> + <menuitem id="context_unpinTab" label="&unpinTab.label;" hidden="true" + accesskey="&unpinTab.accesskey;" + oncommand="gBrowser.unpinTab(TabContextMenu.contextTab);"/> <menuseparator/> <menuitem id="context_bookmarkTab" label="&bookmarkThisTab.label;" accesskey="&bookmarkThisTab.accesskey;" oncommand="BookmarkThisTab(TabContextMenu.contextTab);"/> <menuitem id="context_bookmarkAllTabs" label="&bookmarkAllTabs.label;" accesskey="&bookmarkAllTabs.accesskey;"
--- a/browser/base/content/sanitize.js +++ b/browser/base/content/sanitize.js @@ -117,16 +117,22 @@ Sanitizer.prototype = { const Ci = Components.interfaces; var cacheService = Cc["@mozilla.org/network/cache-service;1"]. getService(Ci.nsICacheService); try { // Cache doesn't consult timespan, nor does it have the // facility for timespan-based eviction. Wipe it. cacheService.evictEntries(Ci.nsICache.STORE_ANYWHERE); } catch(er) {} + + var imageCache = Cc["@mozilla.org/image/cache;1"]. + getService(Ci.imgICache); + try { + imageCache.clearCache(false); // true=chrome, false=content + } catch(er) {} }, get canClear() { return true; } },
--- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -258,16 +258,63 @@ <parameter name="aBrowser"/> <body> <![CDATA[ return (aBrowser || this.mCurrentBrowser).parentNode; ]]> </body> </method> + <method name="_callProgressListeners"> + <parameter name="aBrowser"/> + <parameter name="aMethod"/> + <parameter name="aArguments"/> + <parameter name="aCallGlobalListeners"/> + <parameter name="aCallTabsListeners"/> + <body><![CDATA[ + var rv = true; + + if (!aBrowser) + aBrowser = this.mCurrentBrowser; + + if (aCallGlobalListeners != false && + aBrowser == this.mCurrentBrowser) { + this.mProgressListeners.forEach(function (p) { + if (aMethod in p) { + try { + if (!p[aMethod].apply(p, aArguments)) + rv = false; + } catch (e) { + // don't inhibit other listeners + Components.utils.reportError(e); + } + } + }); + } + + if (aCallTabsListeners != false) { + aArguments.unshift(aBrowser); + + this.mTabsProgressListeners.forEach(function (p) { + if (aMethod in p) { + try { + if (!p[aMethod].apply(p, aArguments)) + rv = false; + } catch (e) { + // don't inhibit other listeners + Components.utils.reportError(e); + } + } + }); + } + + return rv; + ]]></body> + </method> + <!-- A web progress listener object definition for a given tab. --> <method name="mTabProgressListener"> <parameter name="aTab"/> <parameter name="aBrowser"/> <parameter name="aStartsBlank"/> <body> <![CDATA[ return ({ @@ -288,75 +335,54 @@ destroy: function () { this._cancelStalledTimer(); this.mTab.removeAttribute("stalled"); delete this.mTab; delete this.mBrowser; delete this.mTabBrowser; }, - onProgressChange : function (aWebProgress, aRequest, - aCurSelfProgress, aMaxSelfProgress, - aCurTotalProgress, aMaxTotalProgress) - { + _callProgressListeners: function () { + Array.unshift(arguments, this.mBrowser); + return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments); + }, + + onProgressChange: function (aWebProgress, aRequest, + aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress) { this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0; if (this.mBlank) return; if (this.mTotalProgress) { const STATES = 8; let state = Math.ceil(STATES * this.mTotalProgress); if (state != this.mTab.getAttribute("progress")) { this.mTab.setAttribute("progress", state); this.mTab.removeAttribute("stalled"); this._startStalledTimer(); } } - if (this.mTabBrowser.mCurrentTab == this.mTab) { - for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) { - let p = this.mTabBrowser.mProgressListeners[i]; - if (p) - try { - p.onProgressChange(aWebProgress, aRequest, - aCurSelfProgress, aMaxSelfProgress, - aCurTotalProgress, aMaxTotalProgress); - } catch (e) { - // don't inhibit other listeners - Components.utils.reportError(e); - } - } - } - - for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) { - let p = this.mTabBrowser.mTabsProgressListeners[i]; - if (p) - try { - p.onProgressChange(this.mBrowser, aWebProgress, aRequest, - aCurSelfProgress, aMaxSelfProgress, - aCurTotalProgress, aMaxTotalProgress); - } catch (e) { - // don't inhibit other listeners - Components.utils.reportError(e); - } - } + this._callProgressListeners("onProgressChange", + [aWebProgress, aRequest, + aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress]); }, - onProgressChange64 : function (aWebProgress, aRequest, - aCurSelfProgress, aMaxSelfProgress, - aCurTotalProgress, aMaxTotalProgress) - { + onProgressChange64: function (aWebProgress, aRequest, + aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress) { return this.onProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); }, - onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus) - { + onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) { if (!aRequest) return; var oldBlank = this.mBlank; const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener; const nsIChannel = Components.interfaces.nsIChannel; @@ -431,56 +457,41 @@ if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.loading")) this.mTabBrowser.setTabTitle(this.mTab); if (this.mTabBrowser.mCurrentTab == this.mTab) this.mTabBrowser.mIsBusy = false; } - if (this.mTabBrowser.mCurrentTab == this.mTab) { - for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) { - let p = this.mTabBrowser.mProgressListeners[i]; - if (p) - try { - if (!oldBlank) - p.onStateChange(aWebProgress, aRequest, aStateFlags, aStatus); - // make sure that the visible status of new blank tabs is correctly set - else if ("onUpdateCurrentBrowser" in p) - p.onUpdateCurrentBrowser(aStateFlags, aStatus, "", 0); - } catch (e) { - // don't inhibit other listeners - Components.utils.reportError(e); - } - } + if (oldBlank) { + this._callProgressListeners("onUpdateCurrentBrowser", + [aStateFlags, aStatus, "", 0], + true, false); + } else { + this._callProgressListeners("onStateChange", + [aWebProgress, aRequest, aStateFlags, aStatus], + true, false); } - for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) { - let p = this.mTabBrowser.mTabsProgressListeners[i]; - if (p) - try { - p.onStateChange(this.mBrowser, aWebProgress, aRequest, aStateFlags, aStatus); - } catch (e) { - // don't inhibit other listeners - Components.utils.reportError(e); - } - } + this._callProgressListeners("onStateChange", + [aWebProgress, aRequest, aStateFlags, aStatus], + false); if (aStateFlags & (nsIWebProgressListener.STATE_START | nsIWebProgressListener.STATE_STOP)) { // reset cached temporary values at beginning and end this.mMessage = ""; this.mTotalProgress = 0; } this.mStateFlags = aStateFlags; this.mStatus = aStatus; }, - onLocationChange : function(aWebProgress, aRequest, aLocation) - { + onLocationChange: function (aWebProgress, aRequest, aLocation) { // The document loaded correctly, clear the value if we should if (this.mBrowser.userTypedClear > 0) this.mBrowser.userTypedValue = null; // Don't clear the favicon if this onLocationChange was triggered // by a pushState or a replaceState. See bug 550565. if (aWebProgress.DOMWindow == this.mBrowser.contentWindow && aWebProgress.isLoadingDocument && @@ -492,139 +503,45 @@ this.mBrowser.missingPlugins = null; var browserHistory = this.mTabBrowser.mBrowserHistory; if ("lastURI" in this.mBrowser && this.mBrowser.lastURI) browserHistory.unregisterOpenPage(this.mBrowser.lastURI); browserHistory.registerOpenPage(aLocation); if (!this.mBlank) { - if (this.mTabBrowser.mCurrentTab == this.mTab) { - for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) { - let p = this.mTabBrowser.mProgressListeners[i]; - if (p) - try { - p.onLocationChange(aWebProgress, aRequest, aLocation); - } catch (e) { - // don't inhibit other listeners - Components.utils.reportError(e); - } - } - } - - for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) { - let p = this.mTabBrowser.mTabsProgressListeners[i]; - if (p) - try { - p.onLocationChange(this.mBrowser, aWebProgress, aRequest, aLocation); - } catch (e) { - // don't inhibit other listeners - Components.utils.reportError(e); - } - } + this._callProgressListeners("onLocationChange", + [aWebProgress, aRequest, aLocation]); } this.mBrowser.lastURI = aLocation; }, - onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage) - { + onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) { if (this.mBlank) return; - if (this.mTabBrowser.mCurrentTab == this.mTab) { - for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) { - let p = this.mTabBrowser.mProgressListeners[i]; - if (p) - try { - p.onStatusChange(aWebProgress, aRequest, aStatus, aMessage); - } catch (e) { - // don't inhibit other listeners - Components.utils.reportError(e); - } - } - } - - for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) { - let p = this.mTabBrowser.mTabsProgressListeners[i]; - if (p) - try { - p.onStatusChange(this.mBrowser, aWebProgress, aRequest, aStatus, aMessage); - } catch (e) { - // don't inhibit other listeners - Components.utils.reportError(e); - } - } + this._callProgressListeners("onStatusChange", + [aWebProgress, aRequest, aStatus, aMessage]); this.mMessage = aMessage; }, - onSecurityChange : function(aWebProgress, aRequest, aState) - { - if (this.mTabBrowser.mCurrentTab == this.mTab) { - for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) { - let p = this.mTabBrowser.mProgressListeners[i]; - if (p) - try { - p.onSecurityChange(aWebProgress, aRequest, aState); - } catch (e) { - // don't inhibit other listeners - Components.utils.reportError(e); - } - } - } - - for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) { - let p = this.mTabBrowser.mTabsProgressListeners[i]; - if (p) - try { - p.onSecurityChange(this.mBrowser, aWebProgress, aRequest, aState); - } catch (e) { - // don't inhibit other listeners - Components.utils.reportError(e); - } - } + onSecurityChange: function (aWebProgress, aRequest, aState) { + this._callProgressListeners("onSecurityChange", + [aWebProgress, aRequest, aState]); }, - onRefreshAttempted : function(aWebProgress, aURI, aDelay, aSameURI) - { - var allowRefresh = true; - if (this.mTabBrowser.mCurrentTab == this.mTab) { - for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) { - let p = this.mTabBrowser.mProgressListeners[i]; - if (p && "onRefreshAttempted" in p) { - try { - if (!p.onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI)) - allowRefresh = false; - } catch (e) { - // don't inhibit other listeners - Components.utils.reportError(e); - } - } - } - } - - for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) { - let p = this.mTabBrowser.mTabsProgressListeners[i]; - if (p && "onRefreshAttempted" in p) { - try { - if (!p.onRefreshAttempted(this.mBrowser, aWebProgress, aURI, aDelay, aSameURI)) - allowRefresh = false; - } catch (e) { - // don't inhibit other listeners - Components.utils.reportError(e); - } - } - } - return allowRefresh; + onRefreshAttempted: function (aWebProgress, aURI, aDelay, aSameURI) { + return this._callProgressListeners("onRefreshAttempted", + [aWebProgress, aURI, aDelay, aSameURI]); }, - QueryInterface : function(aIID) - { + QueryInterface: function (aIID) { if (aIID.equals(Components.interfaces.nsIWebProgressListener) || aIID.equals(Components.interfaces.nsIWebProgressListener2) || aIID.equals(Components.interfaces.nsISupportsWeakReference) || aIID.equals(Components.interfaces.nsISupports)) return this; throw Components.results.NS_NOINTERFACE; }, @@ -658,39 +575,17 @@ if (!(aURI instanceof Ci.nsIURI)) aURI = makeURI(aURI); this.mFaviconService.setAndLoadFaviconForPage(browser.currentURI, aURI, false); } this.updateIcon(aTab); - if (browser == this.mCurrentBrowser) { - for (let i = 0; i < this.mProgressListeners.length; i++) { - let p = this.mProgressListeners[i]; - if ('onLinkIconAvailable' in p) - try { - p.onLinkIconAvailable(browser, browser.mIconURL); - } catch (e) { - // don't inhibit other listeners - Components.utils.reportError(e); - } - } - } - - for (let i = 0; i < this.mTabsProgressListeners.length; i++) { - let p = this.mTabsProgressListeners[i]; - if ('onLinkIconAvailable' in p) - try { - p.onLinkIconAvailable(browser, browser.mIconURL); - } catch (e) { - // don't inhibit other listeners - Components.utils.reportError(e); - } - } + this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]); ]]> </body> </method> <method name="getIcon"> <parameter name="aTab"/> <body> <![CDATA[ @@ -859,76 +754,60 @@ this.mCurrentBrowser.updatePageReport(); // Update the URL bar. var loc = this.mCurrentBrowser.currentURI; var webProgress = this.mCurrentBrowser.webProgress; var securityUI = this.mCurrentBrowser.securityUI; - var i, p; - for (i = 0; i < this.mProgressListeners.length; i++) { - p = this.mProgressListeners[i]; - if (p) - try { - p.onLocationChange(webProgress, null, loc); - if (securityUI) - p.onSecurityChange(webProgress, null, securityUI.state); - - // make sure that all status indicators are properly updated - if ("onUpdateCurrentBrowser" in p) { - let listener = this.mTabListeners[this.tabContainer.selectedIndex] || null; - if (listener && listener.mStateFlags) - p.onUpdateCurrentBrowser(listener.mStateFlags, listener.mStatus, - listener.mMessage, listener.mTotalProgress); - } - } catch (e) { - // don't inhibit other listeners or following code - Components.utils.reportError(e); - } + this._callProgressListeners(null, "onLocationChange", + [webProgress, null, loc], true, false); + + if (securityUI) { + this._callProgressListeners(null, "onSecurityChange", + [webProgress, null, securityUI.state], true, false); + } + + var listener = this.mTabListeners[this.tabContainer.selectedIndex] || null; + if (listener && listener.mStateFlags) { + this._callProgressListeners(null, "onUpdateCurrentBrowser", + [listener.mStateFlags, listener.mStatus, + listener.mMessage, listener.mTotalProgress], + true, false); } // Don't switch the fast find or update the titlebar (bug 540248) - this tab switch is temporary if (!this._previewMode) { this._fastFind.setDocShell(this.mCurrentBrowser.docShell); this.updateTitlebar(); } // If the new tab is busy, and our current state is not busy, then // we need to fire a start to all progress listeners. const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener; if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) { this.mIsBusy = true; - for (i = 0; i < this.mProgressListeners.length; i++) { - p = this.mProgressListeners[i]; - if (p) - try { - p.onStateChange(webProgress, null, nsIWebProgressListener.STATE_START | nsIWebProgressListener.STATE_IS_NETWORK, 0); - } catch (e) { - // don't inhibit other listeners or following code - Components.utils.reportError(e); - } - } + this._callProgressListeners(null, "onStateChange", + [webProgress, null, + nsIWebProgressListener.STATE_START | + nsIWebProgressListener.STATE_IS_NETWORK, 0], + true, false); } // If the new tab is not busy, and our current state is busy, then // we need to fire a stop to all progress listeners. if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) { this.mIsBusy = false; - for (i = 0; i < this.mProgressListeners.length; i++) { - p = this.mProgressListeners[i]; - if (p) - try { - p.onStateChange(webProgress, null, nsIWebProgressListener.STATE_STOP | nsIWebProgressListener.STATE_IS_NETWORK, 0); - } catch (e) { - // don't inhibit other listeners or following code - Components.utils.reportError(e); - } - } + this._callProgressListeners(null, "onStateChange", + [webProgress, null, + nsIWebProgressListener.STATE_STOP | + nsIWebProgressListener.STATE_IS_NETWORK, 0], + true, false); } // TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code // that might rely upon the other changes suppressed. // Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window if (!this._previewMode) { // We've selected the new tab, so go ahead and notify listeners. var event = document.createEvent("Events"); @@ -1049,21 +928,17 @@ // create a filter and hook it up to our first browser filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"] .createInstance(Components.interfaces.nsIWebProgress); this.mTabFilters[0] = filter; this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL); } // Remove all our progress listeners from the active browser's filter. - for (var i = 0; i < this.mProgressListeners.length; i++) { - var p = this.mProgressListeners[i]; - if (p) - filter.removeProgressListener(p); - } + this.mProgressListeners.forEach(filter.removeProgressListener, filter); // Wire up a progress listener to our filter. const listener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, false); filter.addProgressListener(listener, Components.interfaces.nsIWebProgress.NOTIFY_ALL); this.mTabListeners[0] = listener; ]]> @@ -1205,19 +1080,16 @@ var blank = !aURI || (aURI == "about:blank"); if (blank) t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle")); else t.setAttribute("label", aURI); t.setAttribute("crop", "end"); - t.style.maxWidth = this.tabContainer.mTabMaxWidth + "px"; - t.style.minWidth = this.tabContainer.mTabMinWidth + "px"; - t.width = 0; t.setAttribute("validate", "never"); t.setAttribute("onerror", "this.removeAttribute('image');"); t.className = "tabbrowser-tab"; // When overflowing, new tabs are scrolled into view smoothly, which // doesn't go well together with the width transition. So we skip the // transition in that case. if (aSkipAnimation || @@ -1761,22 +1633,18 @@ ]]> </body> </method> <method name="removeProgressListener"> <parameter name="aListener"/> <body> <![CDATA[ - for (var i = 0; i < this.mProgressListeners.length; i++) { - if (this.mProgressListeners[i] == aListener) { - this.mProgressListeners.splice(i, 1); - break; - } - } + this.mProgressListeners = + this.mProgressListeners.filter(function (l) l != aListener); if (!this.mTabbedMode) // Don't forget to remove it from the filter we hooked it up to this.mTabFilters[0].removeProgressListener(aListener); ]]> </body> </method> @@ -1787,19 +1655,18 @@ this.mTabsProgressListeners.push(aListener); </body> </method> <method name="removeTabsProgressListener"> <parameter name="aListener"/> <body> <![CDATA[ - var pos = this.mTabsProgressListeners.indexOf(aListener); - if (pos >= 0) - this.mTabsProgressListeners.splice(pos, 1); + this.mTabsProgressListeners = + this.mTabsProgressListeners.filter(function (l) l != aListener); ]]> </body> </method> <method name="getBrowserForTab"> <parameter name="aTab"/> <body> <![CDATA[ @@ -2500,28 +2367,23 @@ onclick="checkForMiddleClick(this, event);" tooltiptext="&newTabButton.tooltip;"/> </xul:arrowscrollbox> </content> <implementation implements="nsIDOMEventListener"> <constructor> <![CDATA[ - this.mTabMinWidth = Services.prefs.getIntPref("browser.tabs.tabMinWidth"); - this.mTabMaxWidth = Services.prefs.getIntPref("browser.tabs.tabMaxWidth"); this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth"); this.mCloseButtons = Services.prefs.getIntPref("browser.tabs.closeButtons"); this._closeWindowWithLastTab = Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab"); var tab = this.firstChild; tab.setAttribute("label", this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle")); - tab.style.minWidth = this.mTabMinWidth + "px"; - tab.style.maxWidth = this.mTabMaxWidth + "px"; - tab.width = 0; tab.setAttribute("crop", "end"); tab.setAttribute("validate", "never"); tab.setAttribute("onerror", "this.removeAttribute('image');"); this.adjustTabstrip(); Services.prefs.addObserver("browser.tabs.closeButtons", this._prefObserver, false); Services.prefs.addObserver("browser.tabs.autoHide", this._prefObserver, false); Services.prefs.addObserver("browser.tabs.closeWindowWithLastTab", this._prefObserver, false);
--- a/browser/base/content/test/Makefile.in +++ b/browser/base/content/test/Makefile.in @@ -120,16 +120,17 @@ endif browser_bug477014.js \ browser_bug479408.js \ browser_bug479408_sample.html \ browser_bug481560.js \ browser_bug484315.js \ browser_bug491431.js \ browser_bug495058.js \ browser_bug517902.js \ + browser_bug519216.js \ browser_bug520538.js \ browser_bug521216.js \ browser_bug537474.js \ browser_bug550565.js \ browser_bug553455.js \ browser_bug555224.js \ browser_bug555767.js \ browser_bug556061.js \
--- a/browser/base/content/test/browser_alltabslistener.js +++ b/browser/base/content/test/browser_alltabslistener.js @@ -33,21 +33,16 @@ var gFrontProgressListener = { info("FrontProgress: " + state + " 0x" + aState.toString(16)); ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener"); is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener"); gFrontNotificationsPos++; } } var gAllProgressListener = { - onProgressChange: function (aBrowser, aWebProgress, aRequest, - aCurSelfProgress, aMaxSelfProgress, - aCurTotalProgress, aMaxTotalProgress) { - }, - onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { var state = "onStateChange"; info("AllProgress: " + state + " 0x" + aStateFlags.toString(16)); ok(aBrowser == gTestBrowser, state + " notification came from the correct browser"); ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener"); is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener"); gAllNotificationsPos++;
--- a/browser/base/content/test/browser_bug356571.js +++ b/browser/base/content/test/browser_bug356571.js @@ -52,22 +52,17 @@ var gProgressListener = { if (++this._runCount != kURIs.length) return; // Check we failed on unknown protocol (received an alert from docShell) ok(didFail, "Correctly failed on unknown protocol"); // Check we opened all tabs ok(gBrowser.tabs.length == kURIs.length, "Correctly opened all expected tabs"); finishTest(); } - }, - - onProgressChange: function () {}, - onLocationChange: function () {}, - onStatusChange: function () {}, - onSecurityChange: function () {} + } } function test() { todo(false, "temp. disabled"); return; /* FIXME */ waitForExplicitFinish(); // Wait for all tabs to finish loading gBrowser.addTabsProgressListener(gProgressListener);
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/browser_bug519216.js @@ -0,0 +1,48 @@ +function test() { + waitForExplicitFinish(); + gBrowser.stop(); + gBrowser.addProgressListener(progressListener1); + gBrowser.addProgressListener(progressListener2); + gBrowser.addProgressListener(progressListener3); + gBrowser.loadURI("data:text/plain,bug519216"); +} + +var calledListener1 = false; +var progressListener1 = { + onLocationChange: function onLocationChange() { + calledListener1 = true; + gBrowser.removeProgressListener(this); + } +}; + +var calledListener2 = false; +var progressListener2 = { + onLocationChange: function onLocationChange() { + ok(calledListener1, "called progressListener1 before progressListener2"); + calledListener2 = true; + gBrowser.removeProgressListener(this); + } +}; + +var progressListener3 = { + onLocationChange: function onLocationChange() { + ok(calledListener2, "called progressListener2 before progressListener3"); + gBrowser.removeProgressListener(this); + gBrowser.addProgressListener(progressListener4); + executeSoon(function () { + expectListener4 = true; + gBrowser.reload(); + }); + } +}; + +var expectListener4 = false; +var progressListener4 = { + onLocationChange: function onLocationChange() { + ok(expectListener4, "didn't call progressListener4 for the first location change"); + gBrowser.removeProgressListener(this); + gBrowser.addTab(); + gBrowser.removeCurrentTab(); + finish(); + } +};
--- a/browser/base/content/test/browser_bug521216.js +++ b/browser/base/content/test/browser_bug521216.js @@ -30,20 +30,17 @@ function TabOpen(aEvent) { record(arguments.callee.name); } var progressListener = { onLocationChange: function onLocationChange(aBrowser) { if (aBrowser == tab.linkedBrowser) record(arguments.callee.name); }, - onProgressChange: function () {}, - onSecurityChange: function () {}, onStateChange: function onStateChange(aBrowser) { if (aBrowser == tab.linkedBrowser) record(arguments.callee.name); }, - onStatusChange: function () {}, onLinkIconAvailable: function onLinkIconAvailable(aBrowser) { if (aBrowser == tab.linkedBrowser) record(arguments.callee.name); } };
--- a/browser/base/content/test/browser_overflowScroll.js +++ b/browser/base/content/test/browser_overflowScroll.js @@ -20,17 +20,17 @@ function test() { // there may be some pending animations. That can cause a failure of // this tests, so, we should test this in another stack. setTimeout(doTest, 0); } function doTest() { tabstrip.smoothScroll = false; - var tabMinWidth = gPrefService.getIntPref("browser.tabs.tabMinWidth"); + var tabMinWidth = parseInt(getComputedStyle(gBrowser.selectedTab, null).minWidth); var tabCountForOverflow = Math.ceil(width(tabstrip) / tabMinWidth * 3); while (tabContainer.childNodes.length < tabCountForOverflow) gBrowser.addTab("about:blank", {skipAnimation: true}); tabstrip.addEventListener("overflow", runOverflowTests, false); } function runOverflowTests(aEvent) {
--- a/browser/components/feeds/content/subscribe.js +++ b/browser/components/feeds/content/subscribe.js @@ -50,12 +50,12 @@ var SubscribeHandler = { writeContent: function SH_writeContent() { this._feedWriter.writeContent(); }, uninit: function SH_uninit() { this._feedWriter.close(); }, - subscribe: function FH_subscribe() { + subscribe: function SH_subscribe() { this._feedWriter.subscribe(); } };
--- a/browser/components/places/tests/browser/browser_library_middleclick.js +++ b/browser/components/places/tests/browser/browser_library_middleclick.js @@ -90,31 +90,16 @@ var gTabsListener = { // Close all tabs. while (gBrowser.tabs.length > 1) gBrowser.removeCurrentTab(); this._openTabsCount = 0; // Test finished. This will move to the next one. waitForFocus(gCurrentTest.finish, gBrowser.ownerDocument.defaultView); } - }, - - onProgressChange: function(aBrowser, aWebProgress, aRequest, - aCurSelfProgress, aMaxSelfProgress, - aCurTotalProgress, aMaxTotalProgress) { - }, - onStateChange: function(aBrowser, aWebProgress, aRequest, - aStateFlags, aStatus) { - }, - onStatusChange: function(aBrowser, aWebProgress, aRequest, - aStatus, aMessage) { - }, - onSecurityChange: function(aBrowser, aWebProgress, aRequest, aState) { - }, - noLinkIconAvailable: function(aBrowser) { } } //------------------------------------------------------------------------------ // Open bookmark in a new tab. gTests.push({ desc: "Open bookmark in a new tab.",
--- a/browser/components/preferences/cookies.xul +++ b/browser/components/preferences/cookies.xul @@ -136,11 +136,11 @@ label="&button.removeallcookies.label;" accesskey="&button.removeallcookies.accesskey;" oncommand="gCookiesWindow.deleteAllCookies();"/> <spacer flex="1"/> #ifndef XP_MACOSX <button oncommand="close();" icon="close" label="&button.close.label;" accesskey="&button.close.accesskey;"/> #endif </hbox> - <resizer dir="bottomend"/> + <resizer type="window" dir="bottomend"/> </hbox> </window>
--- a/browser/components/preferences/permissions.xul +++ b/browser/components/preferences/permissions.xul @@ -106,11 +106,11 @@ accesskey="&removeallpermissions.accesskey;" oncommand="gPermissionManager.onAllPermissionsDeleted();"/> <spacer flex="1"/> #ifndef XP_MACOSX <button oncommand="close();" icon="close" label="&button.close.label;" accesskey="&button.close.accesskey;"/> #endif </hbox> - <resizer dir="bottomend"/> + <resizer type="window" dir="bottomend"/> </hbox> </window>
--- a/browser/components/privatebrowsing/src/nsPrivateBrowsingService.js +++ b/browser/components/privatebrowsing/src/nsPrivateBrowsingService.js @@ -566,16 +566,27 @@ PrivateBrowsingService.prototype = { try { cs.evictEntries(Ci.nsICache.STORE_ANYWHERE); } catch (ex) { Cu.reportError("Exception thrown while clearing the cache: " + ex.toString()); } } + // Image Cache + let (imageCache = Cc["@mozilla.org/image/cache;1"]. + getService(Ci.imgICache)) { + try { + imageCache.clearCache(false); // true=chrome, false=content + } catch (ex) { + Cu.reportError("Exception thrown while clearing the image cache: " + + ex.toString()); + } + } + // Cookies let (cm = Cc["@mozilla.org/cookiemanager;1"]. getService(Ci.nsICookieManager2)) { let enumerator = cm.getCookiesFromHost(aDomain); while (enumerator.hasMoreElements()) { let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie); cm.remove(cookie.host, cookie.name, cookie.path, false); }
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_viewsource.js +++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_viewsource.js @@ -87,21 +87,16 @@ function test() { step2(); } else if (aTopic == "domwindowopened") ok(false, "Entering the private browsing mode should not open any view source window"); } Services.ww.registerNotification(observer); gBrowser.addTabsProgressListener({ - onLocationChange: function() {}, - onProgressChange: function() {}, - onSecurityChange: function() {}, - onStatusChange: function() {}, - onRefreshAttempted: function() {}, onStateChange: function(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { if (aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP | Ci.nsIWebProgressListener.STATE_IS_WINDOW)) { gBrowser.removeTabsProgressListener(this); step3(); } }
--- a/browser/components/sessionstore/test/browser/browser_480148.js +++ b/browser/components/sessionstore/test/browser/browser_480148.js @@ -78,18 +78,17 @@ function test() { } return expected; } // the number of tests we're running let numTests = 4; let completedTests = 0; - // access the pref service just once - let tabMinWidth = gPrefService.getIntPref("browser.tabs.tabMinWidth"); + let tabMinWidth = parseInt(getComputedStyle(gBrowser.selectedTab, null).minWidth); function runTest(testNum, totalTabs, selectedTab, shownTabs, order) { let test = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener, Ci.nsISupportsWeakReference]), state: buildTestState(totalTabs, selectedTab), numTabsToShow: shownTabs,
--- a/browser/components/sessionstore/test/browser/browser_522545.js +++ b/browser/components/sessionstore/test/browser/browser_522545.js @@ -57,21 +57,17 @@ function test() { function waitForBrowserState(aState, aSetStateCallback) { var locationChanges = 0; gBrowser.addTabsProgressListener({ onLocationChange: function (aBrowser) { if (++locationChanges == aState.windows[0].tabs.length) { gBrowser.removeTabsProgressListener(this); executeSoon(aSetStateCallback); } - }, - onProgressChange: function () {}, - onSecurityChange: function () {}, - onStateChange: function () {}, - onStatusChange: function () {} + } }); ss.setBrowserState(JSON.stringify(aState)); } // This tests the following use case: // User opens a new tab which gets focus. The user types something into the // address bar, then crashes or quits. function test_newTabFocused() { @@ -206,21 +202,17 @@ function test() { // be in a non-userTypedValue case, while others should still have // userTypedValue and userTypedClear set. gBrowser.addTabsProgressListener({ onLocationChange: function (aBrowser) { if (uris.indexOf(aBrowser.currentURI.spec) > -1) { gBrowser.removeTabsProgressListener(this); firstLocationChange(); } - }, - onProgressChange: function () {}, - onSecurityChange: function () {}, - onStateChange: function () {}, - onStatusChange: function () {} + } }); function firstLocationChange() { let state = JSON.parse(ss.getBrowserState()); let hasUTV = state.windows[0].tabs.some(function(aTab) { return aTab.userTypedValue && aTab.userTypedClear && !aTab.entries.length; });
--- a/browser/components/wintaskbar/WindowsPreviewPerTab.jsm +++ b/browser/components/wintaskbar/WindowsPreviewPerTab.jsm @@ -525,26 +525,16 @@ TabWindow.prototype = { this.previews.splice(oldPos, 1); this.previews.splice(newPos, 0, preview); this.updateTabOrdering(); break; } }, //// Browser progress listener - onLocationChange: function () { - }, - onProgressChange: function () { - }, - onSecurityChange: function () { - }, - onStateChange: function () { - }, - onStatusChange: function () { - }, onLinkIconAvailable: function (aBrowser, aIconURL) { let self = this; getFaviconAsImage(aIconURL, function (img) { let index = self.tabbrowser.browsers.indexOf(aBrowser); // Only add it if we've found the index. The tab could have closed! if (index != -1) self.previews[index].icon = img; });
--- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -17,16 +17,20 @@ <!ENTITY reloadTab.label "Reload Tab"> <!ENTITY reloadTab.accesskey "R"> <!ENTITY reloadAllTabs.label "Reload All Tabs"> <!ENTITY reloadAllTabs.accesskey "A"> <!ENTITY closeOtherTabs.label "Close Other Tabs"> <!ENTITY closeOtherTabs.accesskey "o"> <!ENTITY openTabInNewWindow.label "Open in a New Window"> <!ENTITY openTabInNewWindow.accesskey "W"> +<!ENTITY pinTab.label "Make into App Tab"> +<!ENTITY pinTab.accesskey "p"> +<!ENTITY unpinTab.label "Make into Normal Tab"> +<!ENTITY unpinTab.accesskey "k"> <!ENTITY bookmarkThisTab.label "Bookmark This Tab"> <!ENTITY bookmarkThisTab.accesskey "B"> <!ENTITY bookmarkAllTabs.label "Bookmark All Tabs…"> <!ENTITY bookmarkAllTabs.accesskey "T"> <!ENTITY undoCloseTab.label "Undo Close Tab"> <!ENTITY undoCloseTab.accesskey "U"> <!ENTITY closeTab.label "Close Tab"> <!ENTITY closeTab.accesskey "c">
--- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -47,17 +47,17 @@ addonInstallManage=Open Add-ons Manager addonInstallManage.accesskey=O # LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4, addonErrorIncompatible, addonErrorBlocklisted): # #1 is the add-on name, #2 is the host name, #3 is the application name # #4 is the application version addonError-1=The add-on could not be downloaded because of a connection failure on #2. addonError-2=The add-on from #2 could not be installed because it does not match the add-on #3 expected. addonError-3=The add-on downloaded from #2 could not be installed because it appears to be corrupt. -addonError-4=#1 could not be installed because Firefox cannot modify the needed file. +addonError-4=#1 could not be installed because #3 cannot modify the needed file. addonErrorIncompatible=#1 could not be installed because it is not compatible with #3 #4. addonErrorBlocklisted=#1 could not be installed because it has a high risk of causing stability or security problems. # LOCALIZATION NOTE (lwthemeInstallRequest.message): %S will be replaced with # the host name of the site. lwthemeInstallRequest.message=This site (%S) attempted to install a theme. lwthemeInstallRequest.allowButton=Allow lwthemeInstallRequest.allowButton.accesskey=a
--- a/browser/makefiles.sh +++ b/browser/makefiles.sh @@ -46,17 +46,16 @@ browser/components/certerror/Makefile browser/components/dirprovider/Makefile browser/components/feeds/Makefile browser/components/feeds/public/Makefile browser/components/feeds/src/Makefile browser/components/migration/Makefile browser/components/migration/public/Makefile browser/components/migration/src/Makefile browser/components/places/Makefile -browser/components/places/public/Makefile browser/components/places/src/Makefile browser/components/preferences/Makefile browser/components/privatebrowsing/Makefile browser/components/privatebrowsing/src/Makefile browser/components/safebrowsing/Makefile browser/components/safebrowsing/src/Makefile browser/components/search/Makefile browser/components/sessionstore/Makefile
--- a/browser/themes/winstripe/browser/browser.css +++ b/browser/themes/winstripe/browser/browser.css @@ -176,17 +176,17 @@ statusbarpanel#statusbar-display { #main-window[tabsontop="true"] > #appmenu-button-container > #appmenu-button { position: relative !important; margin-bottom: -1.6em !important; } #navigator-toolbox[tabsontop="true"] > #toolbar-menubar[autohide="true"] { position: relative !important; background-color: -moz-dialog !important; } - #navigator-toolbox[tabsontop="true"] > #toolbar-menubar[autohide="true"] ~ #TabsToolbar { + #navigator-toolbox[tabsontop="true"] > #toolbar-menubar[autohide="true"] ~ #TabsToolbar:not([inFullscreen]) { -moz-padding-start: 10em !important; } %ifdef WINSTRIPE_AERO } %endif /* ::::: bookmark buttons ::::: */
--- a/browser/themes/winstripe/browser/pageInfo.css +++ b/browser/themes/winstripe/browser/pageInfo.css @@ -35,22 +35,38 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ @import "chrome://global/skin/"; /* View buttons */ +#viewGroup { + -moz-padding-start: 10px; +} + #viewGroup > radio { list-style-image: url("chrome://browser/skin/pageInfo.png"); -moz-box-orient: vertical; -moz-box-align: center; -moz-appearance: none; padding: 5px 3px 1px 3px; + margin: 0 1px; + min-width: 4.5em; +} + +#viewGroup > radio:hover { + background-color: #E0E8F6; + color: black; +} + +#viewGroup > radio[selected="true"] { + background-color: #C1D2EE; + color: black; } #topBar { border-bottom: 2px groove ThreeDFace; -moz-padding-start: 10px; background-color: -moz-Field; color: -moz-FieldText; }
--- a/build/pymake/.hg_archival.txt +++ b/build/pymake/.hg_archival.txt @@ -1,2 +1,2 @@ repo: f5ab154deef2ffa97f1b2139589ae4a1962090a4 -node: 3476582628db128ad061c30cab1a74a0c5d14b9b +node: 7ae0b4af32617677698f9de3ab76bcb154bbf085
--- a/build/pymake/pymake/data.py +++ b/build/pymake/pymake/data.py @@ -1397,17 +1397,17 @@ class Makefile(object): Variables.FLAVOR_SIMPLE, Variables.SOURCE_IMPLICIT, val) def foundtarget(self, t): """ Inform the makefile of a target which is a candidate for being the default target, if there isn't already a default target. """ - if self.defaulttarget is None: + if self.defaulttarget is None and t != '.PHONY': self.defaulttarget = t def getpatternvariables(self, pattern): assert isinstance(pattern, Pattern) for p, v in self._patternvariables: if p == pattern: return v
--- a/build/pymake/pymake/parser.py +++ b/build/pymake/pymake/parser.py @@ -279,17 +279,17 @@ def ifeq(d, offset): if token not in _eqargstokenlist: raise SyntaxError("No arguments after conditional", d.getloc(offset)) offset += 1 if token == '(': arg1, t, offset = parsemakesyntax(d, offset, (',',), itermakefilechars) if t is None: - raise SyntaxError("Expected two arguments in conditional", d.getloc(offset)) + raise SyntaxError("Expected two arguments in conditional", d.getloc(d.lend)) arg1.rstrip() offset = d.skipwhitespace(offset) arg2, t, offset = parsemakesyntax(d, offset, (')',), itermakefilechars) if t is None: raise SyntaxError("Unexpected text in conditional", d.getloc(offset)) @@ -599,16 +599,19 @@ class ParseStackFrame(object): self.parent = parent self.expansion = expansion self.tokenlist = tokenlist self.openbrace = openbrace self.closebrace = closebrace self.function = function self.loc = loc + def __str__(self): + return "<state=%i expansion=%s tokenlist=%s openbrace=%s closebrace=%s>" % (self.parsestate, self.expansion, self.tokenlist, self.openbrace, self.closebrace) + _matchingbrace = { '(': ')', '{': '}', } def parsemakesyntax(d, offset, stopon, iterfunc): """ Given Data, parse it into a data.Expansion. @@ -684,17 +687,17 @@ def parsemakesyntax(d, offset, stopon, i e = data.Expansion.fromstring(c, loc) stacktop.expansion.appendfunc(functions.VariableRef(loc, e)) elif token in ('(', '{'): assert token == stacktop.openbrace stacktop.expansion.appendstr(token) stacktop = ParseStackFrame(_PARSESTATE_PARENMATCH, stacktop, stacktop.expansion, - (token, stacktop.closebrace), + (token, stacktop.closebrace, '$'), openbrace=token, closebrace=stacktop.closebrace, loc=d.getloc(tokenoffset)) elif parsestate == _PARSESTATE_PARENMATCH: assert token == stacktop.closebrace stacktop.expansion.appendstr(token) stacktop = stacktop.parent elif parsestate == _PARSESTATE_TOPLEVEL: assert stacktop.parent is None return stacktop.expansion.finish(), token, offset
--- a/build/pymake/tests/default-target.mk +++ b/build/pymake/tests/default-target.mk @@ -1,12 +1,14 @@ test: VAR = value %.do: @echo TEST-FAIL: ran target "$@", should have run "all" +.PHONY: test + all: @echo TEST-PASS: the default target is all test: @echo TEST-FAIL: ran target "$@", should have run "all" test.do:
new file mode 100644 --- /dev/null +++ b/build/pymake/tests/if-syntaxerr.mk @@ -0,0 +1,6 @@ +#T returncode: 2 + +ifeq ($(FOO,VAR)) +all: + @echo TEST_FAIL +endif
--- a/build/pymake/tests/include-dynamic.mk +++ b/build/pymake/tests/include-dynamic.mk @@ -1,10 +1,10 @@ $(shell \ -if test ! -f include-dynamic.inc; then \ +if ! test -f include-dynamic.inc; then \ echo "TESTVAR = oldval" > include-dynamic.inc; \ sleep 2; \ echo "TESTVAR = newval" > include-dynamic.inc.in; \ fi \ ) # before running the 'all' rule, we should be rebuilding include-dynamic.inc, # because there is a rule to do so
new file mode 100644 --- /dev/null +++ b/build/pymake/tests/path-length.mk @@ -0,0 +1,9 @@ +#T gmake skip + +$(shell \ +mkdir foo; \ +touch tfile; \ +) + +all: foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../tfile + @echo TEST-PASS
--- a/caps/src/nsNullPrincipal.cpp +++ b/caps/src/nsNullPrincipal.cpp @@ -73,17 +73,17 @@ nsNullPrincipal::AddRef() NS_IMETHODIMP_(nsrefcnt) nsNullPrincipal::Release() { NS_PRECONDITION(0 != mJSPrincipals.refcount, "dup release"); nsrefcnt count = PR_AtomicDecrement((PRInt32 *)&mJSPrincipals.refcount); NS_LOG_RELEASE(this, count, "nsNullPrincipal"); if (count == 0) { - NS_DELETEXPCOM(this); + delete this; } return count; } nsNullPrincipal::nsNullPrincipal() { }
--- a/caps/src/nsPrincipal.cpp +++ b/caps/src/nsPrincipal.cpp @@ -161,17 +161,17 @@ nsPrincipal::AddRef() NS_IMETHODIMP_(nsrefcnt) nsPrincipal::Release() { NS_PRECONDITION(0 != mJSPrincipals.refcount, "dup release"); nsrefcnt count = PR_AtomicDecrement((PRInt32 *)&mJSPrincipals.refcount); NS_LOG_RELEASE(this, count, "nsPrincipal"); if (count == 0) { - NS_DELETEXPCOM(this); + delete this; } return count; } nsPrincipal::nsPrincipal() : mCapabilities(nsnull), mSecurityPolicy(nsnull),
--- a/caps/src/nsSystemPrincipal.cpp +++ b/caps/src/nsSystemPrincipal.cpp @@ -70,17 +70,17 @@ nsSystemPrincipal::AddRef() NS_IMETHODIMP_(nsrefcnt) nsSystemPrincipal::Release() { NS_PRECONDITION(0 != mJSPrincipals.refcount, "dup release"); nsrefcnt count = PR_AtomicDecrement((PRInt32 *)&mJSPrincipals.refcount); NS_LOG_RELEASE(this, count, "nsSystemPrincipal"); if (count == 0) { - NS_DELETEXPCOM(this); + delete this; } return count; } /////////////////////////////////////// // Methods implementing nsIPrincipal //
--- a/chrome/src/Makefile.in +++ b/chrome/src/Makefile.in @@ -41,16 +41,17 @@ srcdir = @srcdir@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk MODULE = chrome LIBRARY_NAME = chrome_s LIBXUL_LIBRARY = 1 FORCE_STATIC_LIB = 1 +FORCE_USE_PIC = 1 EXPORTS_NAMESPACES = mozilla/chrome EXPORTS_mozilla/chrome = \ RegistryMessageUtils.h \ $(NULL) CPPSRCS = \
--- a/chrome/test/unit/test_resolve_uris.js +++ b/chrome/test/unit/test_resolve_uris.js @@ -46,20 +46,22 @@ let manifests = [ ]; registerManifests(manifests); function do_run_test() { let cr = Cc["@mozilla.org/chrome/chrome-registry;1"]. getService(Ci.nsIChromeRegistry); - var runtime = Components.classes["@mozilla.org/xre/app-info;1"] - .getService(Components.interfaces.nsIXULRuntime); - if (runtime.processType == - Components.interfaces.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { + // If we don't have libxul or e10s then we don't have process separation, so + // we don't need to worry about checking for new chrome. + var appInfo = Cc["@mozilla.org/xre/app-info;1"]; + if (!appInfo || + (appInfo.getService(Ci.nsIXULRuntime).processType == + Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)) { cr.checkForNewChrome(); } // See if our various things were able to register let registrationTypes = [ "content", "locale", "skin",
--- a/config/nsStaticComponents.cpp.in +++ b/config/nsStaticComponents.cpp.in @@ -1,8 +1,9 @@ +#line 2 "nsStaticComponents.cpp.in" /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ @@ -35,42 +36,38 @@ * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #define XPCOM_TRANSLATE_NSGM_ENTRY_POINT 1 -#include "nsIGenericFactory.h" +#include "mozilla/ModuleUtils.h" #include "nsXPCOM.h" #include "nsMemory.h" #include "nsStaticComponents.h" /** - * Construct a unique NSGetModule entry point for a generic module. - */ -#define NSGETMODULE(_name) _name##_NSGetModule - -/** * Declare an NSGetModule() routine for a generic module. */ #define MODULE(_name) \ -NSGETMODULE_ENTRY_POINT(_name) (nsIComponentManager*, nsIFile*, nsIModule**); + NSMODULE_DECL(_name); %MODULE_LIST% #line 57 "nsStaticComponents.cpp.in" #undef MODULE -#define MODULE(_name) { #_name, NSGETMODULE(_name) }, +#define MODULE(_name) \ + NSMODULE_NAME(_name), /** * The nsStaticModuleInfo */ -static nsStaticModuleInfo const gStaticModuleInfo[] = { +static const mozilla::Module *const kStaticModules[] = { %MODULE_LIST% -#line 69 "nsStaticComponents.cpp.in" +#line 70 "nsStaticComponents.cpp.in" + NULL }; -nsStaticModuleInfo const *const kPStaticModules = gStaticModuleInfo; -PRUint32 const kStaticModuleCount = NS_ARRAY_LENGTH(gStaticModuleInfo); +mozilla::Module const *const *const kPStaticModules = kStaticModules;
--- a/config/outofdate.pl +++ b/config/outofdate.pl @@ -42,17 +42,17 @@ # otherwise assumes .class files in same directory as .java files) #Returns: list of input arguments which are newer than corresponding class #files (nonexistent class files are considered to be real old :-) # $found = 1; # GLOBALS -$SEP = 0; # the paltform independent path separator +$SEP = 0; # the platform independent path separator $CFG = 0; # the value of the -cfg flag # determine the path separator $_ = $ENV{"PATH"}; if (m|/|) { $SEP = "/"; } else {
--- a/config/rules.mk +++ b/config/rules.mk @@ -163,16 +163,17 @@ testxpcsrcdir = $(topsrcdir)/testing/xpc # Execute all tests in the $(XPCSHELL_TESTS) directories. # See also testsuite-targets.mk 'xpcshell-tests' target for global execution. xpcshell-tests: $(PYTHON) -u $(topsrcdir)/config/pythonpath.py \ -I$(topsrcdir)/build \ $(testxpcsrcdir)/runxpcshelltests.py \ --symbols-path=$(DIST)/crashreporter-symbols \ + $(EXTRA_TEST_ARGS) \ $(DIST)/bin/xpcshell \ $(foreach dir,$(XPCSHELL_TESTS),$(testxpcobjdir)/$(MODULE)/$(dir)) # Execute a single test, specified in $(SOLO_FILE), but don't automatically # start the test. Instead, present the xpcshell prompt so the user can # attach a debugger and then start the test. check-interactive: $(PYTHON) -u $(topsrcdir)/config/pythonpath.py \ @@ -188,16 +189,18 @@ check-interactive: # Execute a single test, specified in $(SOLO_FILE) check-one: $(PYTHON) -u $(topsrcdir)/config/pythonpath.py \ -I$(topsrcdir)/build \ $(testxpcsrcdir)/runxpcshelltests.py \ --symbols-path=$(DIST)/crashreporter-symbols \ --test-path=$(SOLO_FILE) \ --profile-name=$(MOZ_APP_NAME) \ + --verbose \ + $(EXTRA_TEST_ARGS) \ $(DIST)/bin/xpcshell \ $(foreach dir,$(XPCSHELL_TESTS),$(testxpcobjdir)/$(MODULE)/$(dir)) endif # XPCSHELL_TESTS ifdef CPP_UNIT_TESTS # Compile the tests to $(DIST)/bin. Make lots of niceties available by default @@ -2286,8 +2289,11 @@ FREEZE_VARIABLES = \ $(foreach var,$(FREEZE_VARIABLES),$(eval $(var)_FROZEN := '$($(var))')) CHECK_FROZEN_VARIABLES = $(foreach var,$(FREEZE_VARIABLES), \ $(if $(subst $($(var)_FROZEN),,'$($(var))'),$(error Makefile variable '$(var)' changed value after including rules.mk. Was $($(var)_FROZEN), now $($(var)).))) libs export libs:: $(CHECK_FROZEN_VARIABLES) + +default:: + if test -d $(DIST)/bin ; then touch $(DIST)/bin/.purgecaches ; fi
--- a/configure.in +++ b/configure.in @@ -1533,17 +1533,17 @@ hppa* | parisc) sun4u | sparc*) CPU_ARCH=sparc ;; x86_64 | ia64) CPU_ARCH="$OS_TEST" ;; -arm) +arm*) CPU_ARCH=arm ;; esac if test -z "$OS_TARGET"; then OS_TARGET=$OS_ARCH fi OS_CONFIG="${OS_TARGET}${OS_RELEASE}" @@ -1562,18 +1562,18 @@ if test "$GNU_CC"; then DSO_LDOPTS="$DSO_LDOPTS -Wl,-z,defs" fi WARNINGS_AS_ERRORS='-Werror' DSO_CFLAGS='' DSO_PIC_CFLAGS='-fPIC' ASFLAGS="$ASFLAGS -fPIC" _MOZ_RTTI_FLAGS_ON=${_COMPILER_PREFIX}-frtti _MOZ_RTTI_FLAGS_OFF=${_COMPILER_PREFIX}-fno-rtti - _MOZ_EXCEPTIONS_FLAGS_ON='-fhandle-exceptions' - _MOZ_EXCEPTIONS_FLAGS_OFF='-fno-handle-exceptions' + _MOZ_EXCEPTIONS_FLAGS_ON='-fexceptions' + _MOZ_EXCEPTIONS_FLAGS_OFF='-fno-exceptions' # Turn on GNU specific features # -Wall - turn on all warnings # -pedantic - make compiler warn about non-ANSI stuff, and # be a little bit stricter # Warnings slamm took out for now (these were giving more noise than help): # -Wbad-function-cast - warns when casting a function to a new return type # -Wshadow - removed because it generates more noise than help --pete @@ -2534,18 +2534,18 @@ ia64*-hpux*) AC_DEFINE(_i386) OS_TARGET=NTO WARNINGS_AS_ERRORS='' MOZ_OPTIMIZE_FLAGS="-O" MOZ_DEBUG_FLAGS="-gstabs" USE_PTHREADS=1 _PEDANTIC= LIBS="$LIBS -lsocket -lstdc++" - _DEFINES_CFLAGS='-Wp,-include -Wp,$(DEPTH)/mozilla-config.h -DMOZILLA_CLIENT -D_POSIX_C_SOURCE=199506' - _DEFINES_CXXFLAGS='-DMOZILLA_CLIENT -Wp,-include -Wp,$(DEPTH)/mozilla-config.h -D_POSIX_C_SOURCE=199506' + _DEFINES_CFLAGS='-include $(DEPTH)/mozilla-config.h -DMOZILLA_CLIENT -D_POSIX_C_SOURCE=199506' + _DEFINES_CXXFLAGS='-DMOZILLA_CLIENT -include $(DEPTH)/mozilla-config.h -D_POSIX_C_SOURCE=199506' if test "$with_x" != "yes" then _PLATFORM_DEFAULT_TOOLKIT="photon" TK_CFLAGS='-I/usr/include/photon' TK_LIBS='-lph' fi case "${target_cpu}" in ppc*) @@ -4020,42 +4020,16 @@ EOF cd ${_curdir} rm -rf conftest* _conftest dnl =================================================================== ;; esac dnl =================================================================== dnl ======================================================== -dnl By default, turn rtti and exceptions off on g++/egcs -dnl ======================================================== -if test "$GNU_CXX"; then - - AC_MSG_CHECKING(for C++ exceptions flag) - - dnl They changed -f[no-]handle-exceptions to -f[no-]exceptions in g++ 2.8 - AC_CACHE_VAL(ac_cv_cxx_exceptions_flags, - [echo "int main() { return 0; }" | cat > conftest.C - - ${CXX-g++} ${CXXFLAGS} -c -fno-handle-exceptions conftest.C > conftest.out 2>&1 - - if egrep "warning.*renamed" conftest.out >/dev/null; then - ac_cv_cxx_exceptions_flags=${_COMPILER_PREFIX}-fno-exceptions - else - ac_cv_cxx_exceptions_flags=${_COMPILER_PREFIX}-fno-handle-exceptions - fi - - rm -f conftest*]) - - AC_MSG_RESULT($ac_cv_cxx_exceptions_flags) - _MOZ_EXCEPTIONS_FLAGS_OFF=$ac_cv_cxx_exceptions_flags - _MOZ_EXCEPTIONS_FLAGS_ON=`echo $ac_cv_cxx_exceptions_flags | sed 's|no-||'` -fi - -dnl ======================================================== dnl Put your C++ language/feature checks below dnl ======================================================== AC_LANG_CPLUSPLUS ARM_ABI_PREFIX= HAVE_GCC3_ABI= if test "$GNU_CC"; then if test "$CPU_ARCH" = "arm" ; then @@ -6000,17 +5974,17 @@ dnl = Check alsa availability on Linux i dnl ======================================================== dnl If using sydneyaudio with Linux, ensure that the alsa library is available if test -n "$MOZ_SYDNEYAUDIO"; then case "$target_os" in linux*) PKG_CHECK_MODULES(MOZ_ALSA, alsa, , [echo "$MOZ_ALSA_PKG_ERRORS" - AC_MSG_ERROR([Need alsa for Ogg or Wave decoding on Linux. Disable with --disable-ogg --disable-wave. (On Ubuntu, you might try installing the package libasound2-dev.)])]) + AC_MSG_ERROR([Need alsa for Ogg, Wave or WebM decoding on Linux. Disable with --disable-ogg --disable-wave --disable-webm. (On Ubuntu, you might try installing the package libasound2-dev.)])]) ;; esac fi dnl ======================================================== dnl Splashscreen dnl ======================================================== AC_ARG_ENABLE(splashscreen, @@ -7972,17 +7946,17 @@ MOZ_ARG_DISABLE_BOOL(md, if test "$SOLARIS_SUNPRO_CC"; then _cpp_md_flag=1 fi]) if test "$_cpp_md_flag"; then COMPILER_DEPEND=1 if test "$OS_ARCH" = "OpenVMS"; then _DEPEND_CFLAGS='$(subst =, ,$(filter-out %/.pp,-MM=-MD=-MF=$(MDDEPDIR)/$(basename $(@F)).pp))' else - _DEPEND_CFLAGS='$(filter-out %/.pp,-Wp,-MD,$(MDDEPDIR)/$(basename $(@F)).pp)' + _DEPEND_CFLAGS='$(filter-out %/.pp,-MD -MF $(MDDEPDIR)/$(basename $(@F)).pp)' fi dnl Sun Studio on Solaris use -xM instead of -MD, see config/rules.mk if test "$SOLARIS_SUNPRO_CC"; then _DEPEND_CFLAGS= fi else COMPILER_DEPEND= dnl Don't override this for MSVC
--- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -125,16 +125,17 @@ class nsIXTFService; #endif #ifdef IBMBIDI class nsIBidiKeyboard; #endif class nsIMIMEHeaderParam; class nsIObserver; class nsPresContext; class nsIChannel; +struct nsIntMargin; #ifndef have_PrefChangedFunc_typedef typedef int (*PR_CALLBACK PrefChangedFunc)(const char *, void *); #define have_PrefChangedFunc_typedef #endif namespace mozilla { class IHistory;
--- a/content/base/public/nsCopySupport.h +++ b/content/base/public/nsCopySupport.h @@ -34,25 +34,25 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifndef nsCopySupport_h__ #define nsCopySupport_h__ #include "nscore.h" +#include "nsINode.h" class nsISelection; class nsIDocument; class nsIImageLoadingContent; class nsIContent; class nsITransferable; class nsACString; class nsAString; -class nsIDOMNode; class nsIPresShell; class nsCopySupport { // class of static helper functions for copy support public: static nsresult HTMLCopy(nsISelection *aSel, nsIDocument *aDoc, PRInt16 aClipboardID); static nsresult DoHooks(nsIDocument *aDoc, nsITransferable *aTrans, @@ -64,20 +64,24 @@ class nsCopySupport // doc. static nsresult GetContents(const nsACString& aMimeType, PRUint32 aFlags, nsISelection *aSel, nsIDocument *aDoc, nsAString& outdata); static nsresult ImageCopy(nsIImageLoadingContent* aImageElement, PRInt32 aCopyFlags); // Get the selection as a transferable. Similar to HTMLCopy except does // not deal with the clipboard. - static nsresult GetTransferableForSelection(nsISelection * aSelection, - nsIDocument * aDocument, - nsITransferable ** aTransferable); + static nsresult GetTransferableForSelection(nsISelection* aSelection, + nsIDocument* aDocument, + nsITransferable** aTransferable); + // Same as GetTransferableForSelection, but doesn't skip invisible content. + static nsresult GetTransferableForNode(nsINode* aNode, + nsIDocument* aDoc, + nsITransferable** aTransferable); /** * Retrieve the selection for the given document. If the current focus * within the document has its own selection, aSelection will be set to it * and this focused content node returned. Otherwise, aSelection will be * set to the document's selection and null will be returned. */ static nsIContent* GetSelectionForCopy(nsIDocument* aDocument, nsISelection** aSelection);
--- a/content/base/src/nsCSPService.cpp +++ b/content/base/src/nsCSPService.cpp @@ -50,25 +50,25 @@ #include "nsIChannelPolicy.h" #include "nsIChannelEventSink.h" #include "nsIPropertyBag2.h" #include "nsIWritablePropertyBag2.h" #include "nsNetError.h" #include "nsChannelProperties.h" /* Keeps track of whether or not CSP is enabled */ -static PRBool gCSPEnabled = PR_TRUE; +PRBool CSPService::sCSPEnabled = PR_TRUE; #ifdef PR_LOGGING static PRLogModuleInfo* gCspPRLog; #endif CSPService::CSPService() { - nsContentUtils::AddBoolPrefVarCache("security.csp.enable", &gCSPEnabled); + nsContentUtils::AddBoolPrefVarCache("security.csp.enable", &sCSPEnabled); #ifdef PR_LOGGING if (!gCspPRLog) gCspPRLog = PR_NewLogModule("CSP"); #endif } CSPService::~CSPService() @@ -97,17 +97,17 @@ CSPService::ShouldLoad(PRUint32 aContent PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("CSPService::ShouldLoad called for %s", location.get())); } #endif // default decision, CSP can revise it if there's a policy to enforce *aDecision = nsIContentPolicy::ACCEPT; // No need to continue processing if CSP is disabled - if (!gCSPEnabled) + if (!sCSPEnabled) return NS_OK; // find the principal of the document that initiated this request and see // if it has a CSP policy object nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext)); nsCOMPtr<nsIPrincipal> principal; nsCOMPtr<nsIContentSecurityPolicy> csp; if (node) { @@ -155,17 +155,17 @@ CSPService::ShouldProcess(PRUint32 { if (!aContentLocation) return NS_ERROR_FAILURE; // default decision is to accept the item *aDecision = nsIContentPolicy::ACCEPT; // No need to continue processing if CSP is disabled - if (!gCSPEnabled) + if (!sCSPEnabled) return NS_OK; // find the nsDocument that initiated this request and see if it has a // CSP policy object nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext)); nsCOMPtr<nsIPrincipal> principal; nsCOMPtr<nsIContentSecurityPolicy> csp; if (node) {
--- a/content/base/src/nsCSPService.h +++ b/content/base/src/nsCSPService.h @@ -47,12 +47,13 @@ { 0x97, 0xd9, 0x3f, 0x7d, 0xca, 0x2c, 0xb4, 0x60 } } class CSPService : public nsIContentPolicy, public nsIChannelEventSink { public: NS_DECL_ISUPPORTS NS_DECL_NSICONTENTPOLICY NS_DECL_NSICHANNELEVENTSINK - + CSPService(); virtual ~CSPService(); + static PRBool sCSPEnabled; };
--- a/content/base/src/nsChannelPolicy.cpp +++ b/content/base/src/nsChannelPolicy.cpp @@ -32,16 +32,17 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsChannelPolicy.h" nsChannelPolicy::nsChannelPolicy() + : mLoadType(0) { } nsChannelPolicy::~nsChannelPolicy() { } NS_IMPL_ISUPPORTS1(nsChannelPolicy, nsIChannelPolicy)
--- a/content/base/src/nsContentAreaDragDrop.cpp +++ b/content/base/src/nsContentAreaDragDrop.cpp @@ -74,64 +74,51 @@ #include "nsIContent.h" #include "nsIImageLoadingContent.h" #include "nsUnicharUtils.h" #include "nsIURL.h" #include "nsIDocument.h" #include "nsIScriptSecurityManager.h" #include "nsIPrincipal.h" #include "nsIDocShellTreeItem.h" -#include "nsRange.h" #include "nsIWebBrowserPersist.h" #include "nsEscape.h" #include "nsContentUtils.h" #include "nsIMIMEService.h" #include "imgIContainer.h" #include "imgIRequest.h" #include "nsDOMDataTransfer.h" // private clipboard data flavors for html copy, used by editor when pasting #define kHTMLContext "text/_moz_htmlcontext" #define kHTMLInfo "text/_moz_htmlinfo" -nsresult NS_NewDomSelection(nsISelection **aDomSelection); - -// if inNode is null, use the selection from the window +// if aNode is null, use the selection from the window static nsresult GetTransferableForNodeOrSelection(nsIDOMWindow* aWindow, nsIContent* aNode, nsITransferable** aTransferable) { NS_ENSURE_ARG_POINTER(aWindow); nsCOMPtr<nsIDOMDocument> domDoc; aWindow->GetDocument(getter_AddRefs(domDoc)); NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE); nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); nsresult rv; - nsCOMPtr<nsISelection> selection; - nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aNode); - if (node) { - // Make a temporary selection with this node in a single range. - rv = NS_NewDomSelection(getter_AddRefs(selection)); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr<nsIDOMRange> range; - rv = NS_NewRange(getter_AddRefs(range)); - NS_ENSURE_SUCCESS(rv, rv); - rv = range->SelectNode(node); - NS_ENSURE_SUCCESS(rv, rv); - rv = selection->AddRange(range); - NS_ENSURE_SUCCESS(rv, rv); + if (aNode) { + rv = nsCopySupport::GetTransferableForNode(aNode, doc, aTransferable); } else { + nsCOMPtr<nsISelection> selection; aWindow->GetSelection(getter_AddRefs(selection)); + rv = nsCopySupport::GetTransferableForSelection(selection, doc, + aTransferable); } - rv = nsCopySupport::GetTransferableForSelection(selection, doc, - aTransferable); NS_ENSURE_SUCCESS(rv, rv); return rv; } class NS_STACK_CLASS DragDataProducer { public: DragDataProducer(nsIDOMWindow* aWindow, @@ -419,17 +406,16 @@ DragDataProducer::GetNodeString(nsIConte docRange->CreateRange(getter_AddRefs(range)); if (range) { range->SelectNode(node); range->ToString(outNodeString); } } } - nsresult DragDataProducer::Produce(nsDOMDataTransfer* aDataTransfer, PRBool* aCanDrag, PRBool* aDragSelection, nsIContent** aDragNode) { NS_PRECONDITION(aCanDrag && aDragSelection && aDataTransfer && aDragNode, "null pointer passed to Produce");
--- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -2410,17 +2410,18 @@ nsContentUtils::LoadImage(nsIURI* aURI, imgIDecoderObserver* aObserver, PRInt32 aLoadFlags, imgIRequest** aRequest) { NS_PRECONDITION(aURI, "Must have a URI"); NS_PRECONDITION(aLoadingDocument, "Must have a document"); NS_PRECONDITION(aLoadingPrincipal, "Must have a principal"); NS_PRECONDITION(aRequest, "Null out param"); - if (!sImgLoader) { + imgILoader* imgLoader = GetImgLoader(); + if (!imgLoader) { // nothing we can do here return NS_OK; } nsCOMPtr<nsILoadGroup> loadGroup = aLoadingDocument->GetDocumentLoadGroup(); NS_ASSERTION(loadGroup, "Could not get loadgroup; onload may fire too early"); nsIURI *documentURI = aLoadingDocument->GetDocumentURI(); @@ -2439,27 +2440,27 @@ nsContentUtils::LoadImage(nsIURI* aURI, } } // Make the URI immutable so people won't change it under us NS_TryToSetImmutable(aURI); // XXXbz using "documentURI" for the initialDocumentURI is not quite // right, but the best we can do here... - return sImgLoader->LoadImage(aURI, /* uri to load */ - documentURI, /* initialDocumentURI */ - aReferrer, /* referrer */ - loadGroup, /* loadgroup */ - aObserver, /* imgIDecoderObserver */ - aLoadingDocument, /* uniquification key */ - aLoadFlags, /* load flags */ - nsnull, /* cache key */ - nsnull, /* existing request*/ - channelPolicy, /* CSP info */ - aRequest); + return imgLoader->LoadImage(aURI, /* uri to load */ + documentURI, /* initialDocumentURI */ + aReferrer, /* referrer */ + loadGroup, /* loadgroup */ + aObserver, /* imgIDecoderObserver */ + aLoadingDocument, /* uniquification key */ + aLoadFlags, /* load flags */ + nsnull, /* cache key */ + nsnull, /* existing request*/ + channelPolicy, /* CSP info */ + aRequest); } // static already_AddRefed<imgIContainer> nsContentUtils::GetImageFromContent(nsIImageLoadingContent* aContent, imgIRequest **aRequest) { if (aRequest) {
--- a/content/base/src/nsCopySupport.cpp +++ b/content/base/src/nsCopySupport.cpp @@ -45,16 +45,17 @@ #include "nsIComponentManager.h" #include "nsIServiceManager.h" #include "nsIClipboard.h" #include "nsISelection.h" #include "nsWidgetsCID.h" #include "nsXPCOM.h" #include "nsISupportsPrimitives.h" #include "nsIDOMRange.h" +#include "nsRange.h" #include "imgIContainer.h" #include "nsIPresShell.h" #include "nsFocusManager.h" #include "nsEventDispatcher.h" #include "nsIDocShell.h" #include "nsIContentViewerEdit.h" #include "nsIClipboardDragDropHooks.h" @@ -73,16 +74,18 @@ #include "nsIFrame.h" // image copy stuff #include "nsIImageLoadingContent.h" #include "nsIInterfaceRequestorUtils.h" #include "nsContentUtils.h" #include "nsContentCID.h" +nsresult NS_NewDomSelection(nsISelection **aDomSelection); + static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID); static NS_DEFINE_CID(kCTransferableCID, NS_TRANSFERABLE_CID); static NS_DEFINE_CID(kHTMLConverterCID, NS_HTMLFORMATCONVERTER_CID); // private clipboard data flavors for html copy, used by editor when pasting #define kHTMLContext "text/_moz_htmlcontext" #define kHTMLInfo "text/_moz_htmlinfo" @@ -95,17 +98,17 @@ static nsresult AppendString(nsITransfer static nsresult AppendDOMNode(nsITransferable *aTransferable, nsIDOMNode *aDOMNode); // Helper used for HTMLCopy and GetTransferableForSelection since both routines // share common code. static nsresult SelectionCopyHelper(nsISelection *aSel, nsIDocument *aDoc, PRBool doPutOnClipboard, PRInt16 aClipboardID, - nsITransferable ** aTransferable) + PRUint32 aFlags, nsITransferable ** aTransferable) { // Clear the output parameter for the transferable, if provided. if (aTransferable) { *aTransferable = nsnull; } nsresult rv = NS_OK; @@ -130,19 +133,18 @@ SelectionCopyHelper(nsISelection *aSel, // is. if it is a selection into input/textarea element or in a html content // with pre-wrap style : text/plain. Otherwise text/html. // see nsHTMLCopyEncoder::SetSelection mimeType.AssignLiteral(kUnicodeMime); // we want preformatted for the case where the selection is inside input/textarea // and we don't want pretty printing for others cases, to not have additionnal // line breaks which are then converted into spaces by the htmlConverter (see bug #524975) - PRUint32 flags = nsIDocumentEncoder::OutputPreformatted - | nsIDocumentEncoder::OutputRaw - | nsIDocumentEncoder::SkipInvisibleContent; + PRUint32 flags = aFlags | nsIDocumentEncoder::OutputPreformatted + | nsIDocumentEncoder::OutputRaw; nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aDoc); NS_ASSERTION(domDoc, "Need a document"); rv = docEncoder->Init(domDoc, mimeType, flags); if (NS_FAILED(rv)) return rv; @@ -173,17 +175,17 @@ SelectionCopyHelper(nsISelection *aSel, PRUint32 ConvertedLen; rv = htmlConverter->Convert(kHTMLMime, plainHTML, textBuffer.Length() * 2, kUnicodeMime, getter_AddRefs(ConvertedData), &ConvertedLen); NS_ENSURE_SUCCESS(rv, rv); ConvertedData->GetData(plaintextBuffer); mimeType.AssignLiteral(kHTMLMime); - flags = nsIDocumentEncoder::SkipInvisibleContent; + flags = aFlags; rv = docEncoder->Init(domDoc, mimeType, flags); NS_ENSURE_SUCCESS(rv, rv); rv = docEncoder->SetSelection(aSel); NS_ENSURE_SUCCESS(rv, rv); // encode the selection as html with contextual info @@ -274,27 +276,57 @@ SelectionCopyHelper(nsISelection *aSel, if (aTransferable != nsnull) { trans.swap(*aTransferable); } } } return rv; } -nsresult nsCopySupport::HTMLCopy(nsISelection *aSel, nsIDocument *aDoc, PRInt16 aClipboardID) +nsresult +nsCopySupport::HTMLCopy(nsISelection* aSel, nsIDocument* aDoc, + PRInt16 aClipboardID) { - return SelectionCopyHelper(aSel, aDoc, PR_TRUE, aClipboardID, nsnull); + return SelectionCopyHelper(aSel, aDoc, PR_TRUE, aClipboardID, + nsIDocumentEncoder::SkipInvisibleContent, + nsnull); +} + +nsresult +nsCopySupport::GetTransferableForSelection(nsISelection* aSel, + nsIDocument* aDoc, + nsITransferable** aTransferable) +{ + return SelectionCopyHelper(aSel, aDoc, PR_FALSE, 0, + nsIDocumentEncoder::SkipInvisibleContent, + aTransferable); } nsresult -nsCopySupport::GetTransferableForSelection(nsISelection * aSel, - nsIDocument * aDoc, - nsITransferable ** aTransferable) +nsCopySupport::GetTransferableForNode(nsINode* aNode, + nsIDocument* aDoc, + nsITransferable** aTransferable) { - return SelectionCopyHelper(aSel, aDoc, PR_FALSE, 0, aTransferable); + nsCOMPtr<nsISelection> selection; + // Make a temporary selection with aNode in a single range. + nsresult rv = NS_NewDomSelection(getter_AddRefs(selection)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIDOMRange> range; + rv = NS_NewRange(getter_AddRefs(range)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aNode); + NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); + rv = range->SelectNode(node); + NS_ENSURE_SUCCESS(rv, rv); + rv = selection->AddRange(range); + NS_ENSURE_SUCCESS(rv, rv); + // It's not the primary selection - so don't skip invisible content. + PRUint32 flags = 0; + return SelectionCopyHelper(selection, aDoc, PR_FALSE, 0, flags, + aTransferable); } nsresult nsCopySupport::DoHooks(nsIDocument *aDoc, nsITransferable *aTrans, PRBool *aDoPutOnClipboard) { NS_ENSURE_ARG(aDoc); *aDoPutOnClipboard = PR_TRUE;
--- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -190,26 +190,24 @@ static NS_DEFINE_CID(kDOMEventGroupCID, #ifdef MOZ_SMIL #include "nsSMILAnimationController.h" #include "imgIContainer.h" #include "nsSVGUtils.h" #endif // MOZ_SMIL // FOR CSP (autogenerated by xpidl) #include "nsIContentSecurityPolicy.h" +#include "nsCSPService.h" #include "nsHTMLStyleSheet.h" #include "nsHTMLCSSStyleSheet.h" #include "mozilla/dom/Link.h" using namespace mozilla::dom; -/* Keeps track of whether or not CSP is enabled */ -static PRBool gCSPEnabled = PR_TRUE; - #ifdef PR_LOGGING static PRLogModuleInfo* gDocumentLeakPRLog; static PRLogModuleInfo* gCspPRLog; #endif #define NAME_NOT_VALID ((nsBaseContentList*)1) nsIdentifierMapEntry::~nsIdentifierMapEntry() @@ -1404,18 +1402,16 @@ nsDocument::nsDocument(const char* aCont if (gDocumentLeakPRLog) PR_LOG(gDocumentLeakPRLog, PR_LOG_DEBUG, ("DOCUMENT %p created", this)); if (!gCspPRLog) gCspPRLog = PR_NewLogModule("CSP"); #endif - nsContentUtils::AddBoolPrefVarCache("security.csp.enable", &gCSPEnabled); - // Start out mLastStyleSheetSet as null, per spec SetDOMStringToNull(mLastStyleSheetSet); } static PLDHashOperator ClearAllBoxObjects(const void* aKey, nsPIBoxObject* aBoxObject, void* aUserArg) { if (aBoxObject) { @@ -2170,17 +2166,17 @@ nsDocument::StartDocumentLoad(const char NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult nsDocument::InitCSP() { - if (gCSPEnabled) { + if (CSPService::sCSPEnabled) { nsAutoString cspHeaderValue; nsAutoString cspROHeaderValue; this->GetHeaderData(nsGkAtoms::headerCSP, cspHeaderValue); this->GetHeaderData(nsGkAtoms::headerCSPReportOnly, cspROHeaderValue); PRBool system = PR_FALSE; nsIScriptSecurityManager *ssm = nsContentUtils::GetSecurityManager();
--- a/content/base/src/nsInProcessTabChildGlobal.cpp +++ b/content/base/src/nsInProcessTabChildGlobal.cpp @@ -56,17 +56,17 @@ bool SendSyncMessageToParent(void* aCall nsTArray<nsString>* aJSONRetVal) { nsInProcessTabChildGlobal* tabChild = static_cast<nsInProcessTabChildGlobal*>(aCallbackData); nsCOMPtr<nsIContent> owner = tabChild->mOwner; nsTArray<nsCOMPtr<nsIRunnable> > asyncMessages; asyncMessages.SwapElements(tabChild->mASyncMessages); PRUint32 len = asyncMessages.Length(); - for (PRInt32 i = 0; i < len; ++i) { + for (PRUint32 i = 0; i < len; ++i) { nsCOMPtr<nsIRunnable> async = asyncMessages[i]; async->Run(); } if (tabChild->mChromeMessageManager) { tabChild->mChromeMessageManager->ReceiveMessage(owner, aMessage, PR_TRUE, aJSON, nsnull, aJSONRetVal); } return true; @@ -281,18 +281,19 @@ nsInProcessTabChildGlobal::InitTabChildG nsIXPConnect::FLAG_SYSTEM_GLOBAL_OBJECT; nsISupports* scopeSupports = NS_ISUPPORTS_CAST(nsPIDOMEventTarget*, this); JS_SetContextPrivate(cx, scopeSupports); nsresult rv = xpc->InitClassesWithNewWrappedGlobal(cx, scopeSupports, - NS_GET_IID(nsISupports), flags, - getter_AddRefs(mGlobal)); + NS_GET_IID(nsISupports), + GetPrincipal(), EmptyCString(), + flags, getter_AddRefs(mGlobal)); NS_ENSURE_SUCCESS(rv, false); JSObject* global = nsnull; rv = mGlobal->GetJSObject(&global); NS_ENSURE_SUCCESS(rv, false); JS_SetGlobalObject(cx, global);
--- a/content/base/src/nsXMLHttpRequest.cpp +++ b/content/base/src/nsXMLHttpRequest.cpp @@ -1355,18 +1355,17 @@ nsXMLHttpRequest::GetAllResponseHeaders( if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) { return NS_OK; } nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel(); if (httpChannel) { - nsHeaderVisitor *visitor = nsnull; - NS_NEWXPCOM(visitor, nsHeaderVisitor); + nsHeaderVisitor *visitor = new nsHeaderVisitor(); if (!visitor) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(visitor); nsresult rv = httpChannel->VisitResponseHeaders(visitor); if (NS_SUCCEEDED(rv)) *_retval = ToNewCString(visitor->Headers());
--- a/content/base/test/Makefile.in +++ b/content/base/test/Makefile.in @@ -397,16 +397,17 @@ include $(topsrcdir)/config/rules.mk test_bug300992.html \ test_websocket_hello.html \ file_websocket_hello_wsh.py \ test_ws_basic_tests.html \ file_ws_basic_tests_wsh.py \ test_websocket.html \ file_websocket_wsh.py \ file_websocket_http_resource.txt \ + test_bug574596.html \ $(NULL) # This test fails on the Mac for some reason ifneq (,$(filter gtk2 windows,$(MOZ_WIDGET_TOOLKIT))) _TEST_FILES2 += test_copyimage.html \ $(NULL) endif
new file mode 100644 --- /dev/null +++ b/content/base/test/test_bug574596.html @@ -0,0 +1,86 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=574596 +--> +<head> + <title>Test for Bug 574596</title> + <script type="application/javascript" src="/MochiKit/packed.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=574596">Mozilla Bug 574596</a> +<style type="text/css"> +#link1 a { -moz-user-select:none; } +</style> +<div id="link1"><a href="http://www.mozilla.org/">link1</a></div> +<div id="link2"><a href="http://www.mozilla.org/">link2</a></div> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 574596 **/ + +function ignoreFunc(actualData, expectedData) { + return true; +} + +var dragLinkText = [[ + { type:"text/x-moz-url", data:"", eqTest:ignoreFunc }, + { type:"text/x-moz-url-data", data:"http://www.mozilla.org/" }, + { type:"text/x-moz-url-desc", data:"link1" }, + { type:"text/uri-list", data:"http://www.mozilla.org/" }, + { type:"text/_moz_htmlcontext", data:"", eqTest:ignoreFunc }, + { type:"text/_moz_htmlinfo", data:"", eqTest:ignoreFunc }, + { type:"text/html", data:'<div id="link1"><a href="http://www.mozilla.org/">link1</a></div>' }, + { type:"text/plain", data:"http://www.mozilla.org/" } +]]; + + +function dumpTransfer(dataTransfer,expect) { + dtData = dataTransfer.mozItemCount + "items:\n"; + for (var i = 0; i < dataTransfer.mozItemCount; i++) { + var dtTypes = dataTransfer.mozTypesAt(i); + for (var j = 0; j < dtTypes.length; j++) { + var actualData = dataTransfer.mozGetDataAt(dtTypes[j],i) + if (expect && expect[i] && expect[i][j]) { + if (expect[i][j].eqTest) + dtData += expect[i][j].eqTest(actualData,expect[i][j].data) ? "ok" : "fail"; + else + dtData += (actualData == expect[i][j].data) ? "ok" : "fail"; + } + dtData += "["+i+"]" + "["+j+"]: " + '"' + dtTypes[j] + '" "' + actualData + '"\n'; + } + } + alert(dtData); +} + +function runTest() { + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + + var result = synthesizeDragStart($('link1'), dragLinkText, window); + is(result, null, "Drag -moz-user-select:none link (#link1)"); + // if (result) dumpTransfer(result,dragLinkText); + + dragLinkText[0][2].data = "link2"; + dragLinkText[0][6].data = '<div id="link2"><a href="http://www.mozilla.org/">link2</a></div>' + var result = synthesizeDragStart($('link2'), dragLinkText, window); + is(result, null, "Drag link (#link2)"); + // if (result) dumpTransfer(result,dragLinkText); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(runTest); + + +</script> +</pre> +</body> +</html>
--- a/content/base/test/test_fileapi.html +++ b/content/base/test/test_fileapi.html @@ -257,17 +257,17 @@ expectedTestCount++; // Test reading from nonexistent files r = new FileReader(); var didThrow = false; try { r.readAsDataURL(nonExistingFile); } catch(ex) { didThrow = true; } -// Once this test passes, we shoud test that onerror gets called and +// Once this test passes, we should test that onerror gets called and // that the FileReader object is in the right state during that call. todo(!didThrow, "shouldn't throw when opening nonexistent file, should fire error instead"); function getLoadHandler(expectedResult, expectedLength, testName) { return function (event) { is(event.target.readyState, FileReader.DONE, "readyState in test " + testName);
--- a/content/base/test/test_websocket.html +++ b/content/base/test/test_websocket.html @@ -58,17 +58,31 @@ function shouldNotOpen(e) { var ws = e.target; ok(false, "onopen shouldn't be called on test " + ws._testNumber + "!"); } function shouldNotReceiveCloseEvent(e) { var ws = e.target; - ok(false, "onclose shouldn't be called on test " + ws._testNumber + "!"); + var extendedErrorInfo = ""; + if (!ws._testNumber) { + extendedErrorInfo += "\nws members:\n"; + for (var i in ws) { + extendedErrorInfo += (i + ": " + ws[i] + "\n"); + } + + extendedErrorInfo += "\ne members:\n"; + for (var i in e) { + extendedErrorInfo += (i + ": " + e[i] + "\n"); + } + } + + // FIXME: see bug 578276. This should be a test failure, but it's too flaky on the tbox. + ok(true, "onclose shouldn't be called on test " + ws._testNumber + "!" + extendedErrorInfo); } function shouldCloseCleanly(e) { var ws = e.target; ok(e.wasClean, "the ws connection in test " + ws._testNumber + " should be closed cleanly"); } @@ -84,22 +98,25 @@ function CreateTestWS(ws_location, ws_pr try { if (ws_protocol == undefined) { ws = new WebSocket(ws_location); } else { ws = new WebSocket(ws_location, ws_protocol); } + + ws._testNumber = current_test; + ws._receivedCloseEvent = false; + ok(true, "added testNumber: " + ws._testNumber +"\n"); + ws.onerror = function(e) { ok(false, "onerror called on test " + e.target._testNumber + "!"); }; - ws._testNumber = current_test; - ws._receivedCloseEvent = false; ws.addEventListener("close", function(e) { ws._receivedCloseEvent = true; }, false); } catch (e) { throw e; } @@ -414,16 +431,17 @@ function test16() ws.onclose = shouldCloseCleanly; } var status_test17 = "not started"; window._test17 = function() { var local_ws = new WebSocket("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 17"); + local_ws._testNumber = "local17"; current_test++; status_test17 = "started"; local_ws.onopen = function(e) { status_test17 = "opened"; e.target.send("client data"); @@ -486,16 +504,17 @@ function test19() shouldCloseNotCleanly(e); doTest(20); }; } window._test20 = function() { var local_ws = new WebSocket("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 20"); + local_ws._testNumber = "local20"; current_test++; local_ws.onerror = function() { ok(false, "onerror called on test " + e.target._testNumber + "!"); }; local_ws.onclose = shouldNotReceiveCloseEvent;
--- a/content/canvas/src/WebGLContext.cpp +++ b/content/canvas/src/WebGLContext.cpp @@ -133,18 +133,18 @@ WebGLContext::SetCanvasElement(nsHTMLCan } NS_IMETHODIMP WebGLContext::SetDimensions(PRInt32 width, PRInt32 height) { // If incrementing the generation would cause overflow, // don't allow it. Allowing this would allow us to use // resource handles created from older context generations. - if (mGeneration + 1 == 0) - return NS_ERROR_FAILURE; + if (!(mGeneration+1).valid()) + return NS_ERROR_FAILURE; // exit without changing the value of mGeneration if (mWidth == width && mHeight == height) return NS_OK; if (gl) { // hey we already have something if (gl->Resize(gfxIntSize(width, height))) { @@ -176,17 +176,17 @@ WebGLContext::SetDimensions(PRInt32 widt LogMessage("WebGL: Using software rendering via OSMesa"); } } mWidth = width; mHeight = height; // increment the generation number - mGeneration++; + ++mGeneration; MakeContextCurrent(); // Make sure that we clear this out, otherwise // we'll end up displaying random memory gl->fViewport(0, 0, mWidth, mHeight); gl->fClearColor(0, 0, 0, 0); gl->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT);
--- a/content/canvas/src/WebGLContext.h +++ b/content/canvas/src/WebGLContext.h @@ -55,16 +55,18 @@ #include "nsHTMLCanvasElement.h" #include "nsWeakReference.h" #include "nsIDOMHTMLElement.h" #include "nsIJSNativeInitializer.h" #include "GLContext.h" #include "Layers.h" +#include "CheckedInt.h" + #define UNPACK_FLIP_Y_WEBGL 0x9240 #define UNPACK_PREMULTIPLY_ALPHA_WEBGL 0x9241 #define CONTEXT_LOST_WEBGL 0x9242 class nsIDocShell; namespace mozilla { @@ -302,27 +304,27 @@ public: return ErrorInvalidEnum("%s: invalid enum value", info); } already_AddRefed<CanvasLayer> GetCanvasLayer(LayerManager *manager); void MarkContextClean() { } // a number that increments every time we have an event that causes // all context resources to be lost. - PRUint32 Generation() { return mGeneration; } + PRUint32 Generation() { return mGeneration.value(); } protected: nsCOMPtr<nsIDOMHTMLCanvasElement> mCanvasElement; nsHTMLCanvasElement *HTMLCanvasElement() { return static_cast<nsHTMLCanvasElement*>(mCanvasElement.get()); } nsRefPtr<gl::GLContext> gl; PRInt32 mWidth, mHeight; - PRUint32 mGeneration; + CheckedUint32 mGeneration; PRBool mInvalidated; WebGLuint mActiveTexture; WebGLenum mSynthesizedGLError; PRBool SafeToCreateCanvas3DContext(nsHTMLCanvasElement *canvasElement); PRBool InitAndValidateGL(); @@ -330,16 +332,17 @@ protected: PRBool ValidateCapabilityEnum(WebGLenum cap, const char *info); PRBool ValidateBlendEquationEnum(WebGLuint cap, const char *info); PRBool ValidateBlendFuncDstEnum(WebGLuint mode, const char *info); PRBool ValidateBlendFuncSrcEnum(WebGLuint mode, const char *info); PRBool ValidateTextureTargetEnum(WebGLenum target, const char *info); PRBool ValidateComparisonEnum(WebGLenum target, const char *info); PRBool ValidateStencilOpEnum(WebGLenum action, const char *info); PRBool ValidateFaceEnum(WebGLenum target, const char *info); + PRBool ValidateBufferUsageEnum(WebGLenum target, const char *info); PRBool ValidateTexFormatAndType(WebGLenum format, WebGLenum type, PRUint32 *texelSize, const char *info); void Invalidate(); void MakeContextCurrent() { gl->MakeCurrent(); } // helpers @@ -700,17 +703,17 @@ public: ZeroOwners(); mDeleted = PR_TRUE; } PRBool Deleted() { return mDeleted; } WebGLuint GLName() { return mName; } const nsTArray<WebGLShader*>& AttachedShaders() const { return mAttachedShaders; } PRBool LinkStatus() { return mLinkStatus; } - GLuint Generation() const { return mGeneration; } + PRUint32 Generation() const { return mGeneration.value(); } void SetLinkStatus(PRBool val) { mLinkStatus = val; } PRBool ContainsShader(WebGLShader *shader) { return mAttachedShaders.Contains(shader); } // return true if the shader wasn't already attached PRBool AttachShader(WebGLShader *shader) { @@ -737,20 +740,19 @@ public: return PR_TRUE; } return PR_FALSE; } PRBool NextGeneration() { - GLuint nextGeneration = mGeneration + 1; - if (nextGeneration == 0) + if (!(mGeneration+1).valid()) return PR_FALSE; // must exit without changing mGeneration - mGeneration = nextGeneration; + ++mGeneration; mMapUniformLocations.Clear(); return PR_TRUE; } already_AddRefed<WebGLUniformLocation> GetUniformLocationObject(GLint glLocation); /* Called only after LinkProgram */ PRBool UpdateInfo(gl::GLContext *gl); @@ -765,17 +767,17 @@ public: NS_DECL_ISUPPORTS NS_DECL_NSIWEBGLPROGRAM protected: WebGLuint mName; PRPackedBool mDeleted; PRPackedBool mLinkStatus; nsTArray<WebGLShader*> mAttachedShaders; nsRefPtrHashtable<nsUint32HashKey, WebGLUniformLocation> mMapUniformLocations; - GLuint mGeneration; + CheckedUint32 mGeneration; GLint mUniformMaxNameLength; GLint mAttribMaxNameLength; GLint mUniformCount; GLint mAttribCount; std::vector<bool> mAttribsInUse; }; NS_DEFINE_STATIC_IID_ACCESSOR(WebGLProgram, WEBGLPROGRAM_PRIVATE_IID) @@ -859,26 +861,26 @@ public: NS_DECLARE_STATIC_IID_ACCESSOR(WEBGLUNIFORMLOCATION_PRIVATE_IID) WebGLUniformLocation(WebGLContext *context, WebGLProgram *program, GLint location) : WebGLContextBoundObject(context), mProgram(program), mProgramGeneration(program->Generation()), mLocation(location) { } WebGLProgram *Program() const { return mProgram; } GLint Location() const { return mLocation; } - GLuint ProgramGeneration() const { return mProgramGeneration; } + PRUint32 ProgramGeneration() const { return mProgramGeneration; } // needed for our generic helpers to check nsIxxx parameters, see GetConcreteObject. PRBool Deleted() { return PR_FALSE; } NS_DECL_ISUPPORTS NS_DECL_NSIWEBGLUNIFORMLOCATION protected: WebGLObjectRefPtr<WebGLProgram> mProgram; - GLuint mProgramGeneration; + PRUint32 mProgramGeneration; GLint mLocation; }; NS_DEFINE_STATIC_IID_ACCESSOR(WebGLUniformLocation, WEBGLUNIFORMLOCATION_PRIVATE_IID) /** ** Template implementations **/
--- a/content/canvas/src/WebGLContextGL.cpp +++ b/content/canvas/src/WebGLContextGL.cpp @@ -120,31 +120,16 @@ already_AddRefed<WebGLUniformLocation> W /* void present (); */ NS_IMETHODIMP WebGLContext::Present() { return NS_ERROR_NOT_IMPLEMENTED; } -/* long sizeInBytes (in GLenum type); */ -NS_IMETHODIMP -WebGLContext::SizeInBytes(WebGLenum type, PRInt32 *retval) -{ - if (type == LOCAL_GL_FLOAT) *retval = sizeof(float); - if (type == LOCAL_GL_SHORT) *retval = sizeof(short); - if (type == LOCAL_GL_UNSIGNED_SHORT) *retval = sizeof(unsigned short); - if (type == LOCAL_GL_BYTE) *retval = 1; - if (type == LOCAL_GL_UNSIGNED_BYTE) *retval = 1; - if (type == LOCAL_GL_INT) *retval = sizeof(int); - if (type == LOCAL_GL_UNSIGNED_INT) *retval = sizeof(unsigned int); - if (type == LOCAL_GL_DOUBLE) *retval = sizeof(double); - return NS_OK; -} - /* void GlActiveTexture (in GLenum texture); */ NS_IMETHODIMP WebGLContext::ActiveTexture(WebGLenum texture) { if (texture < LOCAL_GL_TEXTURE0 || texture >= LOCAL_GL_TEXTURE0+mBound2DTextures.Length()) return ErrorInvalidEnum("ActiveTexture: texture unit %d out of range (0..%d)", texture, mBound2DTextures.Length()-1); @@ -287,17 +272,17 @@ WebGLContext::BindTexture(WebGLenum targ MakeContextCurrent(); gl->fBindTexture(target, texturename); return NS_OK; } -GL_SAME_METHOD_4(BlendColor, BlendColor, float, float, float, float) +GL_SAME_METHOD_4(BlendColor, BlendColor, WebGLfloat, WebGLfloat, WebGLfloat, WebGLfloat) NS_IMETHODIMP WebGLContext::BlendEquation(WebGLenum mode) { if (!ValidateBlendEquationEnum(mode, "blendEquation: mode")) return NS_OK; MakeContextCurrent(); gl->fBlendEquation(mode); @@ -357,16 +342,22 @@ WebGLContext::BufferData_size(WebGLenum if (target == LOCAL_GL_ARRAY_BUFFER) { boundBuffer = mBoundArrayBuffer; } else if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) { boundBuffer = mBoundElementArrayBuffer; } else { return ErrorInvalidEnum("BufferData: invalid target"); } + if (size < 0) + return ErrorInvalidValue("bufferData: negative size"); + + if (!ValidateBufferUsageEnum(usage, "bufferData: usage")) + return NS_OK; + if (!boundBuffer) return ErrorInvalidOperation("BufferData: no buffer bound!"); MakeContextCurrent(); boundBuffer->SetByteLength(size); boundBuffer->ZeroDataIfElementArray(); @@ -383,16 +374,19 @@ WebGLContext::BufferData_buf(WebGLenum t if (target == LOCAL_GL_ARRAY_BUFFER) { boundBuffer = mBoundArrayBuffer; } else if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) { boundBuffer = mBoundElementArrayBuffer; } else { return ErrorInvalidEnum("BufferData: invalid target"); } + if (!ValidateBufferUsageEnum(usage, "bufferData: usage")) + return NS_OK; + if (!boundBuffer) return ErrorInvalidOperation("BufferData: no buffer bound!"); MakeContextCurrent(); boundBuffer->SetByteLength(wb->byteLength); boundBuffer->CopyDataIfElementArray(wb->data); @@ -409,16 +403,19 @@ WebGLContext::BufferData_array(WebGLenum if (target == LOCAL_GL_ARRAY_BUFFER) { boundBuffer = mBoundArrayBuffer; } else if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) { boundBuffer = mBoundElementArrayBuffer; } else { return ErrorInvalidEnum("BufferData: invalid target"); } + if (!ValidateBufferUsageEnum(usage, "bufferData: usage")) + return NS_OK; + if (!boundBuffer) return ErrorInvalidOperation("BufferData: no buffer bound!"); MakeContextCurrent(); boundBuffer->SetByteLength(wa->byteLength); boundBuffer->CopyDataIfElementArray(wa->data); @@ -444,18 +441,21 @@ WebGLContext::BufferSubData_buf(GLenum t boundBuffer = mBoundElementArrayBuffer; } else { return ErrorInvalidEnum("BufferSubData: invalid target"); } if (!boundBuffer) return ErrorInvalidOperation("BufferData: no buffer bound!"); - // XXX check for overflow - if (byteOffset + wb->byteLength > boundBuffer->ByteLength()) + CheckedUint32 checked_neededByteLength = CheckedUint32(byteOffset) + wb->byteLength; + if (!checked_neededByteLength.valid()) + return ErrorInvalidOperation("bufferSubData: integer overflow computing the needed byte length"); + + if (checked_neededByteLength.value() > boundBuffer->ByteLength()) return ErrorInvalidOperation("BufferSubData: not enough data - operation requires %d bytes, but buffer only has %d bytes", byteOffset, wb->byteLength, boundBuffer->ByteLength()); MakeContextCurrent(); boundBuffer->CopySubDataIfElementArray(byteOffset, wb->byteLength, wb->data); gl->fBufferSubData(target, byteOffset, wb->byteLength, wb->data); @@ -474,18 +474,21 @@ WebGLContext::BufferSubData_array(WebGLe boundBuffer = mBoundElementArrayBuffer; } else { return ErrorInvalidEnum("BufferSubData: invalid target"); } if (!boundBuffer) return ErrorInvalidOperation("BufferData: no buffer bound!"); - // XXX check for overflow - if (byteOffset + wa->byteLength > boundBuffer->ByteLength()) + CheckedUint32 checked_neededByteLength = CheckedUint32(byteOffset) + wa->byteLength; + if (!checked_neededByteLength.valid()) + return ErrorInvalidOperation("bufferSubData: integer overflow computing the needed byte length"); + + if (checked_neededByteLength.value() > boundBuffer->ByteLength()) return ErrorInvalidOperation("BufferSubData: not enough data -- operation requires %d bytes, but buffer only has %d bytes", byteOffset, wa->byteLength, boundBuffer->ByteLength()); MakeContextCurrent(); boundBuffer->CopySubDataIfElementArray(byteOffset, wa->byteLength, wa->data); gl->fBufferSubData(target, byteOffset, wa->byteLength, wa->data); @@ -507,25 +510,25 @@ WebGLContext::Clear(PRUint32 mask) { MakeContextCurrent(); gl->fClear(mask); Invalidate(); return NS_OK; } -GL_SAME_METHOD_4(ClearColor, ClearColor, float, float, float, float) +GL_SAME_METHOD_4(ClearColor, ClearColor, WebGLfloat, WebGLfloat, WebGLfloat, WebGLfloat) #ifdef USE_GLES2 -GL_SAME_METHOD_1(ClearDepthf, ClearDepth, float) +GL_SAME_METHOD_1(ClearDepthf, ClearDepth, WebGLfloat) #else -GL_SAME_METHOD_1(ClearDepth, ClearDepth, float) +GL_SAME_METHOD_1(ClearDepth, ClearDepth, WebGLfloat) #endif -GL_SAME_METHOD_1(ClearStencil, ClearStencil, PRInt32) +GL_SAME_METHOD_1(ClearStencil, ClearStencil, WebGLint) GL_SAME_METHOD_4(ColorMask, ColorMask, WebGLboolean, WebGLboolean, WebGLboolean, WebGLboolean) NS_IMETHODIMP WebGLContext::CopyTexImage2D(WebGLenum target, WebGLint level, WebGLenum internalformat, WebGLint x, @@ -635,17 +638,26 @@ WebGLContext::CreateShader(WebGLenum typ WebGLShader *shader = new WebGLShader(this, name, type); NS_ADDREF(*retval = shader); mMapShaders.Put(name, shader); return NS_OK; } -GL_SAME_METHOD_1(CullFace, CullFace, WebGLenum) +NS_IMETHODIMP +WebGLContext::CullFace(WebGLenum face) +{ + if (!ValidateFaceEnum(face, "cullFace")) + return NS_OK; + + MakeContextCurrent(); + gl->fCullFace(face); + return NS_OK; +} NS_IMETHODIMP WebGLContext::DeleteBuffer(nsIWebGLBuffer *bobj) { WebGLuint bufname; WebGLBuffer *buf; PRBool isNull, isDeleted; if (!GetConcreteObjectAndGLName(bobj, &buf, &bufname, &isNull, &isDeleted)) @@ -811,19 +823,19 @@ WebGLContext::DepthFunc(WebGLenum func) MakeContextCurrent(); gl->fDepthFunc(func); return NS_OK; } GL_SAME_METHOD_1(DepthMask, DepthMask, WebGLboolean) #ifdef USE_GLES2 -GL_SAME_METHOD_2(DepthRangef, DepthRange, float, float) +GL_SAME_METHOD_2(DepthRangef, DepthRange, WebGLfloat, WebGLfloat) #else -GL_SAME_METHOD_2(DepthRange, DepthRange, float, float) +GL_SAME_METHOD_2(DepthRange, DepthRange, WebGLfloat, WebGLfloat) #endif NS_IMETHODIMP WebGLContext::DisableVertexAttribArray(WebGLuint index) { if (index > mAttribBuffers.Length()) return ErrorInvalidValue("DisableVertexAttribArray: index out of range"); @@ -858,20 +870,22 @@ WebGLContext::DrawArrays(GLenum mode, We if (count == 0) return NS_OK; // If there is no current program, this is silently ignored. // Any checks below this depend on a program being available. if (!mCurrentProgram) return NS_OK; - if (first+count < first || first+count < count) - return ErrorInvalidOperation("DrawArrays: overflow in first+count"); - - if (!ValidateBuffers(first+count)) + CheckedInt32 checked_firstPlusCount = CheckedInt32(first) + count; + + if (!checked_firstPlusCount.valid()) + return ErrorInvalidOperation("drawArrays: overflow in first+count"); + + if (!ValidateBuffers(checked_firstPlusCount.value())) return ErrorInvalidOperation("DrawArrays: bound vertex attribute buffers do not have sufficient data for given first and count"); MakeContextCurrent(); gl->fDrawArrays(mode, first, count); Invalidate(); @@ -892,57 +906,65 @@ WebGLContext::DrawElements(WebGLenum mod break; default: return ErrorInvalidEnum("DrawElements: invalid mode"); } if (count < 0 || byteOffset < 0) return ErrorInvalidValue("DrawElements: negative count or offset"); - WebGLuint byteCount; + CheckedUint32 checked_byteCount; + if (type == LOCAL_GL_UNSIGNED_SHORT) { - byteCount = WebGLuint(count) << 1; - if (byteCount >> 1 != WebGLuint(count)) - return ErrorInvalidValue("DrawElements: overflow in byteCount"); - + checked_byteCount = 2 * CheckedUint32(count); if (byteOffset % 2 != 0) return ErrorInvalidValue("DrawElements: invalid byteOffset for UNSIGNED_SHORT (must be a multiple of 2)"); } else if (type == LOCAL_GL_UNSIGNED_BYTE) { - byteCount = count; + checked_byteCount = count; } else { return ErrorInvalidEnum("DrawElements: type must be UNSIGNED_SHORT or UNSIGNED_BYTE"); } + if (!checked_byteCount.valid()) + return ErrorInvalidValue("DrawElements: overflow in byteCount"); + // If count is 0, there's nothing to do. if (count == 0) return NS_OK; // If there is no current program, this is silently ignored. // Any checks below this depend on a program being available. if (!mCurrentProgram) return NS_OK; if (!mBoundElementArrayBuffer) return ErrorInvalidOperation("DrawElements: must have element array buffer binding"); - if (byteOffset+byteCount < WebGLuint(byteOffset) || byteOffset+byteCount < byteCount) + CheckedUint32 checked_neededByteCount = checked_byteCount + byteOffset; + + if (!checked_neededByteCount.valid()) return ErrorInvalidOperation("DrawElements: overflow in byteOffset+byteCount"); - if (byteOffset + byteCount > mBoundElementArrayBuffer->ByteLength()) + if (checked_neededByteCount.value() > mBoundElementArrayBuffer->ByteLength()) return ErrorInvalidOperation("DrawElements: bound element array buffer is too small for given count and offset"); WebGLuint maxIndex = 0; if (type == LOCAL_GL_UNSIGNED_SHORT) { maxIndex = mBoundElementArrayBuffer->FindMaximum<GLushort>(count, byteOffset); } else if (type == LOCAL_GL_UNSIGNED_BYTE) { maxIndex = mBoundElementArrayBuffer->FindMaximum<GLubyte>(count, byteOffset); } - // maxIndex+1 because ValidateBuffers expects the number of elements needed - if (!ValidateBuffers(maxIndex+1)) { + // maxIndex+1 because ValidateBuffers expects the number of elements needed. + // it is very important here to check tha maxIndex+1 doesn't overflow, otherwise the buffer validation is bypassed !!! + // maxIndex is a WebGLuint, ValidateBuffers takes a PRUint32, we validate maxIndex+1 as a PRUint32. + CheckedUint32 checked_neededCount = CheckedUint32(maxIndex) + 1; + if (!checked_neededCount.valid()) + return ErrorInvalidOperation("drawElements: overflow in maxIndex+1"); + if (!ValidateBuffers(checked_neededCount.value())) { return ErrorInvalidOperation("DrawElements: bound vertex attribute buffers do not have sufficient " "data for given indices from the bound element array"); } MakeContextCurrent(); gl->fDrawElements(mode, count, type, (GLvoid*) (byteOffset)); @@ -1301,24 +1323,17 @@ WebGLContext::GetParameter(PRUint32 pnam { GLint i = 0; gl->fGetIntegerv(pname, &i); wrval->SetAsInt32(i); } break; #define LOCAL_GL_MAX_VARYING_VECTORS 0x8dfc // not present in desktop OpenGL - // temporarily add those defs here, as they're missing from - // gfx/thebes/public/GLDefs.h - // and from - // gfx/layers/opengl/glDefs.h - // and I don't know in which of these 2 files they should go (probably we're going to - // kill one of them soon?) - #define LOCAL_GL_MAX_FRAGMENT_INPUT_COMPONENTS 0x9125 - #define LOCAL_GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122 + case LOCAL_GL_MAX_VARYING_VECTORS: { #ifdef USE_GLES2 GLint i = 0; gl->fGetIntegerv(pname, &i); wrval->SetAsInt32(i); #else // since this pname is absent from desktop OpenGL, we have to implement it by hand. @@ -2112,17 +2127,17 @@ WebGLContext::IsEnabled(WebGLenum cap, W return NS_OK; } MakeContextCurrent(); *retval = gl->fIsEnabled(cap); return NS_OK; } -GL_SAME_METHOD_1(LineWidth, LineWidth, float) +GL_SAME_METHOD_1(LineWidth, LineWidth, WebGLfloat) NS_IMETHODIMP WebGLContext::LinkProgram(nsIWebGLProgram *pobj) { GLuint progname; WebGLProgram *program; if (!GetConcreteObjectAndGLName(pobj, &program, &progname)) return ErrorInvalidOperation("LinkProgram: invalid program"); @@ -2174,17 +2189,17 @@ WebGLContext::PixelStorei(WebGLenum pnam default: return ErrorInvalidEnum("PixelStorei: invalid parameter"); } return NS_OK; } -GL_SAME_METHOD_2(PolygonOffset, PolygonOffset, float, float) +GL_SAME_METHOD_2(PolygonOffset, PolygonOffset, WebGLfloat, WebGLfloat) NS_IMETHODIMP WebGLContext::ReadPixels(PRInt32 dummy) { return NS_ERROR_FAILURE; } nsresult @@ -2227,26 +2242,29 @@ WebGLContext::ReadPixels_base(WebGLint x return ErrorInvalidEnum("ReadPixels: unsupported pixel type"); } MakeContextCurrent(); PRUint32 packAlignment; gl->fGetIntegerv(LOCAL_GL_PACK_ALIGNMENT, (GLint*) &packAlignment); - PRUint32 plainRowSize = width*size; - - // alignedRowSize = row size rounded up to next multiple of - // packAlignment which is a power of 2 - PRUint32 alignedRowSize = (plainRowSize + packAlignment-1) & - ~PRUint32(packAlignment-1); - - PRUint32 neededByteLength = (height-1)*alignedRowSize + plainRowSize; - - if(neededByteLength > byteLength) + CheckedUint32 checked_plainRowSize = CheckedUint32(width) * size; + + // alignedRowSize = row size rounded up to next multiple of packAlignment + CheckedUint32 checked_alignedRowSize + = ((checked_plainRowSize + packAlignment-1) / packAlignment) * packAlignment; + + CheckedUint32 checked_neededByteLength + = (height-1) * checked_alignedRowSize + checked_plainRowSize; + + if (!checked_neededByteLength.valid()) + return ErrorInvalidOperation("ReadPixels: integer overflow computing the needed buffer size"); + + if (checked_neededByteLength.value() > byteLength) return ErrorInvalidOperation("ReadPixels: buffer too small"); if (CanvasUtils::CheckSaneSubrectSize(x, y, width, height, boundWidth, boundHeight)) { // the easy case: we're not reading out-of-range pixels gl->fReadPixels(x, y, width, height, format, type, data); } else { // the rectangle doesn't fit entirely in the bound buffer. We then have to set to zero the part // of the buffer that correspond to out-of-range pixels. We don't want to rely on system OpenGL @@ -2272,30 +2290,39 @@ WebGLContext::ReadPixels_base(WebGLint x GLint subrect_x = PR_MAX(x, 0); GLint subrect_end_x = PR_MIN(x+width, boundWidth); GLsizei subrect_width = subrect_end_x - subrect_x; GLint subrect_y = PR_MAX(y, 0); GLint subrect_end_y = PR_MIN(y+height, boundHeight); GLsizei subrect_height = subrect_end_y - subrect_y; + if (subrect_width < 0 || subrect_height < 0 || + subrect_width > width || subrect_height) + return ErrorInvalidOperation("ReadPixels: integer overflow computing clipped rect size"); + + // now we know that subrect_width is in the [0..width] interval, and same for heights. + // now, same computation as above to find the size of the intermediate buffer to allocate for the subrect + // no need to check again for integer overflow here, since we already know the sizes aren't greater than before PRUint32 subrect_plainRowSize = subrect_width * size; PRUint32 subrect_alignedRowSize = (subrect_plainRowSize + packAlignment-1) & ~PRUint32(packAlignment-1); PRUint32 subrect_byteLength = (subrect_height-1)*subrect_alignedRowSize + subrect_plainRowSize; // create subrect buffer, call glReadPixels, copy pixels into destination buffer, delete subrect buffer GLubyte *subrect_data = new GLubyte[subrect_byteLength]; gl->fReadPixels(subrect_x, subrect_y, subrect_width, subrect_height, format, type, subrect_data); + + // notice that this for loop terminates because we already checked that subrect_height is at most height for (GLint y_inside_subrect = 0; y_inside_subrect < subrect_height; ++y_inside_subrect) { GLint subrect_x_in_dest_buffer = subrect_x - x; GLint subrect_y_in_dest_buffer = subrect_y - y; memcpy(static_cast<GLubyte*>(data) - + alignedRowSize * (subrect_y_in_dest_buffer + y_inside_subrect) + + checked_alignedRowSize.value() * (subrect_y_in_dest_buffer + y_inside_subrect) + size * subrect_x_in_dest_buffer, // destination subrect_data + subrect_alignedRowSize * y_inside_subrect, // source subrect_plainRowSize); // size } delete [] subrect_data; } return NS_OK; } @@ -2348,24 +2375,29 @@ WebGLContext::ReadPixels_byteLength_old_ case LOCAL_GL_UNSIGNED_BYTE: break; default: return ErrorInvalidEnum("ReadPixels: unsupported pixel type"); } PRUint32 packAlignment; gl->fGetIntegerv(LOCAL_GL_PACK_ALIGNMENT, (GLint*) &packAlignment); - PRUint32 plainRowSize = width*size; + CheckedUint32 checked_plainRowSize = CheckedUint32(width) * size; // alignedRowSize = row size rounded up to next multiple of // packAlignment which is a power of 2 - PRUint32 alignedRowSize = (plainRowSize + packAlignment-1) & - ~PRUint32(packAlignment-1); - - *retval = (height-1)*alignedRowSize + plainRowSize; + CheckedUint32 checked_alignedRowSize + = ((checked_plainRowSize + packAlignment-1) / packAlignment) * packAlignment; + + CheckedUint32 checked_neededByteLength = (height-1)*checked_alignedRowSize + checked_plainRowSize; + + if (!checked_neededByteLength.valid()) + return ErrorInvalidOperation("ReadPixels: integer overflow computing the needed buffer size"); + + *retval = checked_neededByteLength.value(); return NS_OK; } NS_IMETHODIMP WebGLContext::RenderbufferStorage(WebGLenum target, WebGLenum internalformat, WebGLsizei width, WebGLsizei height) { if (target != LOCAL_GL_RENDERBUFFER) @@ -2390,17 +2422,17 @@ WebGLContext::RenderbufferStorage(WebGLe mBoundRenderbuffer->setDimensions(width, height); MakeContextCurrent(); gl->fRenderbufferStorage(target, internalformat, width, height); return NS_OK; } -GL_SAME_METHOD_2(SampleCoverage, SampleCoverage, float, WebGLboolean) +GL_SAME_METHOD_2(SampleCoverage, SampleCoverage, WebGLfloat, WebGLboolean) NS_IMETHODIMP WebGLContext::Scissor(WebGLint x, WebGLint y, WebGLsizei width, WebGLsizei height) { if (width < 0 || height < 0) return ErrorInvalidValue("Scissor: negative size"); MakeContextCurrent(); @@ -2938,19 +2970,16 @@ WebGLContext::VertexAttribPointer(WebGLu } if (index >= mAttribBuffers.Length()) return ErrorInvalidValue("VertexAttribPointer: index out of range - %d >= %d", index, mAttribBuffers.Length()); if (size < 1 || size > 4) return ErrorInvalidValue("VertexAttribPointer: invalid element size"); - if (stride < 0) - return ErrorInvalidValue("VertexAttribPointer: stride cannot be negative"); - /* XXX make work with bufferSubData & heterogeneous types if (type != mBoundArrayBuffer->GLType()) return ErrorInvalidOperation("VertexAttribPointer: type must match bound VBO type: %d != %d", type, mBoundArrayBuffer->GLType()); */ // XXX 0 stride? //if (stride < (GLuint) size) // return ErrorInvalidOperation("VertexAttribPointer: stride must be >= size!"); @@ -3016,21 +3045,25 @@ WebGLContext::TexImage2D_base(WebGLenum if (border != 0) return ErrorInvalidValue("TexImage2D: border must be 0"); PRUint32 texelSize = 0; if (!ValidateTexFormatAndType(format, type, &texelSize, "texImage2D")) return NS_OK; - // XXX overflow! - uint32 bytesNeeded = width * height * texelSize; - + CheckedUint32 checked_bytesNeeded = CheckedUint32(width) * height * texelSize; + + if (!checked_bytesNeeded.valid()) + return ErrorInvalidOperation("texImage2D: integer overflow computing the needed buffer size"); + + PRUint32 bytesNeeded = checked_bytesNeeded.value(); + if (byteLength && byteLength < bytesNeeded) - return ErrorInvalidValue("TexImage2D: not enough data for operation (need %d, have %d)", + return ErrorInvalidOperation("TexImage2D: not enough data for operation (need %d, have %d)", bytesNeeded, byteLength); MakeContextCurrent(); if (byteLength) { gl->fTexImage2D(target, level, internalformat, width, height, border, format, type, data); } else { // We need some zero pages, because GL doesn't guarantee the @@ -3149,18 +3182,23 @@ WebGLContext::TexSubImage2D_base(WebGLen PRUint32 texelSize = 0; if (!ValidateTexFormatAndType(format, type, &texelSize, "texSubImage2D")) return NS_OK; if (width == 0 || height == 0) return NS_OK; // ES 2.0 says it has no effect, we better return right now - // XXX overflow! - uint32 bytesNeeded = width * height * texelSize; + CheckedUint32 checked_bytesNeeded = CheckedUint32(width) * height * texelSize; + + if (!checked_bytesNeeded.valid()) + return ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size"); + + PRUint32 bytesNeeded = checked_bytesNeeded.value(); + if (byteLength < bytesNeeded) return ErrorInvalidValue("TexSubImage2D: not enough data for operation (need %d, have %d)", bytesNeeded, byteLength); MakeContextCurrent(); gl->fTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels); return NS_OK;
--- a/content/canvas/src/WebGLContextValidate.cpp +++ b/content/canvas/src/WebGLContextValidate.cpp @@ -34,16 +34,18 @@ * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "WebGLContext.h" +#include "CheckedInt.h" + using namespace mozilla; /* * Pull all the data out of the program that will be used by validate later on */ PRBool WebGLProgram::UpdateInfo(gl::GLContext *gl) { @@ -107,22 +109,28 @@ WebGLContext::ValidateBuffers(PRUint32 c } // If the attrib is not in use, then we don't have to validate // it, just need to make sure that the binding is non-null. if (!mCurrentProgram->IsAttribInUse(i)) continue; // compute the number of bytes we actually need - WebGLuint needed = vd.byteOffset + // the base offset - vd.actualStride() * (count-1) + // to stride to the start of the last element group - vd.componentSize() * vd.size; // and the number of bytes needed for these components + CheckedUint32 checked_needed = CheckedUint32(vd.byteOffset) + // the base offset + CheckedUint32(vd.actualStride()) * (count-1) + // to stride to the start of the last element group + CheckedUint32(vd.componentSize()) * vd.size; // and the number of bytes needed for these components - if (vd.buf->ByteLength() < needed) { - LogMessage("VBO too small for bound attrib index %d: need at least %d bytes, but have only %d", i, needed, vd.buf->ByteLength()); + if (!checked_needed.valid()) { + LogMessage("Integer overflow computing the size of bound vertex attrib buffer at index %d", i); + return PR_FALSE; + } + + if (vd.buf->ByteLength() < checked_needed.value()) { + LogMessage("VBO too small for bound attrib index %d: need at least %d bytes, but have only %d", + i, checked_needed.value(), vd.buf->ByteLength()); return PR_FALSE; } } return PR_TRUE; } PRBool WebGLContext::ValidateCapabilityEnum(WebGLenum cap, const char *info) @@ -245,16 +253,29 @@ PRBool WebGLContext::ValidateFaceEnum(We case LOCAL_GL_FRONT_AND_BACK: return PR_TRUE; default: ErrorInvalidEnumInfo(info); return PR_FALSE; } } +PRBool WebGLContext::ValidateBufferUsageEnum(WebGLenum target, const char *info) +{ + switch (target) { + case LOCAL_GL_STREAM_DRAW: + case LOCAL_GL_STATIC_DRAW: + case LOCAL_GL_DYNAMIC_DRAW: + return PR_TRUE; + default: + ErrorInvalidEnumInfo(info); + return PR_FALSE; + } +} + PRBool WebGLContext::ValidateTexFormatAndType(WebGLenum format, WebGLenum type, PRUint32 *texelSize, const char *info) { if (type == LOCAL_GL_UNSIGNED_BYTE) { switch (format) { case LOCAL_GL_RED: case LOCAL_GL_GREEN:
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp +++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp @@ -3106,17 +3106,17 @@ nsCanvasRenderingContext2D::SetLineCap(c if (capstyle.EqualsLiteral("butt")) cap = gfxContext::LINE_CAP_BUTT; else if (capstyle.EqualsLiteral("round")) cap = gfxContext::LINE_CAP_ROUND; else if (capstyle.EqualsLiteral("square")) cap = gfxContext::LINE_CAP_SQUARE; else // XXX ERRMSG we need to report an error to developers here! (bug 329026) - return NS_ERROR_NOT_IMPLEMENTED; + return NS_OK; mThebes->SetLineCap(cap); return NS_OK; } NS_IMETHODIMP nsCanvasRenderingContext2D::GetLineCap(nsAString& capstyle) { @@ -3142,17 +3142,17 @@ nsCanvasRenderingContext2D::SetLineJoin( if (joinstyle.EqualsLiteral("round")) j = gfxContext::LINE_JOIN_ROUND; else if (joinstyle.EqualsLiteral("bevel")) j = gfxContext::LINE_JOIN_BEVEL; else if (joinstyle.EqualsLiteral("miter")) j = gfxContext::LINE_JOIN_MITER; else // XXX ERRMSG we need to report an error to developers here! (bug 329026) - return NS_ERROR_NOT_IMPLEMENTED; + return NS_OK; mThebes->SetLineJoin(j); return NS_OK; } NS_IMETHODIMP nsCanvasRenderingContext2D::GetLineJoin(nsAString& joinstyle) { @@ -3527,17 +3527,18 @@ nsCanvasRenderingContext2D::SetGlobalCom else CANVAS_OP_TO_THEBES_OP("lighter", ADD) else CANVAS_OP_TO_THEBES_OP("source-atop", ATOP) else CANVAS_OP_TO_THEBES_OP("source-in", IN) else CANVAS_OP_TO_THEBES_OP("source-out", OUT) else CANVAS_OP_TO_THEBES_OP("source-over", OVER) else CANVAS_OP_TO_THEBES_OP("xor", XOR) // not part of spec, kept here for compat else CANVAS_OP_TO_THEBES_OP("over", OVER) - else return NS_ERROR_NOT_IMPLEMENTED; + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + else return NS_OK; #undef CANVAS_OP_TO_THEBES_OP mThebes->SetOperator(thebes_op); return NS_OK; } NS_IMETHODIMP
--- a/content/canvas/test/test_canvas.html +++ b/content/canvas/test/test_canvas.html @@ -1458,17 +1458,17 @@ try { ctx.globalCompositeOperation = 'xor'; ctx.globalCompositeOperation = 'Source-over'; ok(ctx.globalCompositeOperation == 'xor', "ctx.globalCompositeOperation == 'xor'"); } catch (e) { _thrown_outer = true; } -todo(!_thrown_outer, 'should not throw exception'); +ok(!_thrown_outer, 'should not throw exception'); } </script> <!-- [[[ test_2d.composite.operation.clear.html ]]] --> <p>Canvas test: 2d.composite.operation.clear</p> @@ -1564,17 +1564,17 @@ try { ctx.globalCompositeOperation = 'xor'; ctx.globalCompositeOperation = 'highlight'; ok(ctx.globalCompositeOperation == 'xor', "ctx.globalCompositeOperation == 'xor'"); } catch (e) { _thrown_outer = true; } -todo(!_thrown_outer, 'should not throw exception'); +ok(!_thrown_outer, 'should not throw exception'); } </script> <!-- [[[ test_2d.composite.operation.nullsuffix.html ]]] --> <p>Canvas test: 2d.composite.operation.nullsuffix - bug 401788</p> @@ -1591,17 +1591,17 @@ try { ctx.globalCompositeOperation = 'xor'; ctx.globalCompositeOperation = 'source-over\0'; ok(ctx.globalCompositeOperation == 'xor', "ctx.globalCompositeOperation == 'xor'"); } catch (e) { _thrown_outer = true; } -todo(!_thrown_outer, 'should not throw exception'); +ok(!_thrown_outer, 'should not throw exception'); } </script> <!-- [[[ test_2d.composite.operation.over.html ]]] --> <p>Canvas test: 2d.composite.operation.over</p> @@ -1637,17 +1637,17 @@ try { ctx.globalCompositeOperation = 'xor'; ctx.globalCompositeOperation = 'nonexistent'; ok(ctx.globalCompositeOperation == 'xor', "ctx.globalCompositeOperation == 'xor'"); } catch (e) { _thrown_outer = true; } -todo(!_thrown_outer, 'should not throw exception'); +ok(!_thrown_outer, 'should not throw exception'); } </script> <!-- [[[ test_2d.composite.solid.copy.html ]]] --> <p>Canvas test: 2d.composite.solid.copy</p> @@ -9126,17 +9126,17 @@ ok(ctx.lineCap === 'butt', "ctx.lineCap ctx.lineCap = 'butt'; ctx.lineCap = 'bevel'; ok(ctx.lineCap === 'butt', "ctx.lineCap === 'butt'"); } catch (e) { _thrown_outer = true; } -todo(!_thrown_outer, 'should not throw exception'); +ok(!_thrown_outer, 'should not throw exception'); } </script> <!-- [[[ test_2d.line.cap.open.html ]]] --> <p>Canvas test: 2d.line.cap.open</p> @@ -9500,17 +9500,17 @@ ok(ctx.lineJoin === 'bevel', "ctx.lineJo ctx.lineJoin = 'bevel'; ctx.lineJoin = 'butt'; ok(ctx.lineJoin === 'bevel', "ctx.lineJoin === 'bevel'"); } catch (e) { _thrown_outer = true; } -todo(!_thrown_outer, 'should not throw exception'); +ok(!_thrown_outer, 'should not throw exception'); } </script> <!-- [[[ test_2d.line.join.miter.html ]]] --> <p>Canvas test: 2d.line.join.miter</p> @@ -19311,17 +19311,17 @@ var ctx = canvas.getContext('2d'); var _thrown_outer = false; try { ok(canvas.getContext('2D') === null, "canvas.getContext('2D') === null"); } catch (e) { _thrown_outer = true; } -todo(!_thrown_outer, 'should not throw exception'); +ok(!_thrown_outer, 'should not throw exception'); } </script> <!-- [[[ test_context.emptystring.html ]]] --> <p>Canvas test: context.emptystring - bug 401788</p> @@ -19337,17 +19337,17 @@ var ctx = canvas.getContext('2d'); var _thrown_outer = false; try { ok(canvas.getContext("") === null, "canvas.getContext(\"\") === null"); } catch (e) { _thrown_outer = true; } -todo(!_thrown_outer, 'should not throw exception'); +ok(!_thrown_outer, 'should not throw exception'); } </script> <!-- [[[ test_context.unrecognised.badname.html ]]] --> <p>Canvas test: context.unrecognised.badname - bug 401788</p> @@ -19363,17 +19363,17 @@ var ctx = canvas.getContext('2d'); var _thrown_outer = false; try { ok(canvas.getContext('This is not an implemented context in any real browser') === null, "canvas.getContext('This is not an implemented context in any real browser') === null"); } catch (e) { _thrown_outer = true; } -todo(!_thrown_outer, 'should not throw exception'); +ok(!_thrown_outer, 'should not throw exception'); } </script> <!-- [[[ test_context.unrecognised.badsuffix.html ]]] --> <p>Canvas test: context.unrecognised.badsuffix - bug 401788</p> @@ -19389,17 +19389,17 @@ var ctx = canvas.getContext('2d'); var _thrown_outer = false; try { ok(canvas.getContext("2d#") === null, "canvas.getContext(\"2d#\") === null"); } catch (e) { _thrown_outer = true; } -todo(!_thrown_outer, 'should not throw exception'); +ok(!_thrown_outer, 'should not throw exception'); } </script> <!-- [[[ test_context.unrecognised.nullsuffix.html ]]] --> <p>Canvas test: context.unrecognised.nullsuffix - bug 401788</p> @@ -19415,17 +19415,17 @@ var ctx = canvas.getContext('2d'); var _thrown_outer = false; try { ok(canvas.getContext("2d\0") === null, "canvas.getContext(\"2d\\0\") === null"); } catch (e) { _thrown_outer = true; } -todo(!_thrown_outer, 'should not throw exception'); +ok(!_thrown_outer, 'should not throw exception'); } </script> <!-- [[[ test_context.unrecognised.unicode.html ]]] --> <p>Canvas test: context.unrecognised.unicode - bug 401788</p> @@ -19441,17 +19441,17 @@ var ctx = canvas.getContext('2d'); var _thrown_outer = false; try { ok(canvas.getContext("2\uFF44") === null, "canvas.getContext(\"2\\uFF44\") === null"); // Fullwidth Latin Small Letter D } catch (e) { _thrown_outer = true; } -todo(!_thrown_outer, 'should not throw exception'); +ok(!_thrown_outer, 'should not throw exception'); } </script> <!-- [[[ test_fallback.basic.html ]]] --> <p>Canvas test: fallback.basic</p> @@ -20602,17 +20602,17 @@ var _thrown_outer = false; try { var data = canvas.toDataURL('ImAgE/PnG'); ok(/^data:image\/png[;,]/.test(data), "data =~ /^data:image\\/png[;,]/"); } catch (e) { _thrown_outer = true; } -todo(!_thrown_outer, 'should not throw exception'); +ok(!_thrown_outer, 'should not throw exception'); } </script> <!-- [[[ test_toDataURL.nocontext.html ]]] --> <p>Canvas test: toDataURL.nocontext</p> @@ -20710,17 +20710,17 @@ var _thrown_outer = false; try { var data = canvas.toDataURL('image/example'); ok(/^data:image\/png[;,]/.test(data), "data =~ /^data:image\\/png[;,]/"); } catch (e) { _thrown_outer = true; } -todo(!_thrown_outer, 'should not throw exception'); +ok(!_thrown_outer, 'should not throw exception'); } </script> <!-- [[[ test_toDataURL.zerosize.html ]]] --> <p>Canvas test: toDataURL.zerosize</p>
--- a/content/canvas/test/webgl/conformance/program-test.html +++ b/content/canvas/test/webgl/conformance/program-test.html @@ -135,21 +135,21 @@ function go() { if(gl.getError() != gl.NO_ERROR) assertMsg(false, "unexpected error in detachShader()"); assertMsg(areArraysEqual(gl.getAttachedShaders(prog), expected_shaders), errmsg); } checkGetAttachedShaders([], [], [], "getAttachedShaders should return an empty list by default"); checkGetAttachedShaders([fs], [], [fs], "attaching a single shader should give the expected list"); checkGetAttachedShaders([fs, vs, fs2, vs2], [], [fs, vs, fs2, vs2], "attaching some shaders should give the expected list"); - checkGetAttachedShaders([fs], [fs], [], "attaching a shader and detaching it shoud leave an empty list"); + checkGetAttachedShaders([fs], [fs], [], "attaching a shader and detaching it should leave an empty list"); checkGetAttachedShaders([fs, vs, fs2, vs2], [fs, vs, fs2, vs2], [], - "attaching some shaders and detaching them in same order shoud leave an empty list"); + "attaching some shaders and detaching them in same order should leave an empty list"); checkGetAttachedShaders([fs, vs, fs2, vs2], [fs, vs2, vs, fs2], [], - "attaching some shaders and detaching them in random order shoud leave an empty list"); + "attaching some shaders and detaching them in random order should leave an empty list"); checkGetAttachedShaders([fs, vs, fs2, vs2], [vs], [fs, fs2, vs2], "attaching and detaching some shaders should leave the difference list"); checkGetAttachedShaders([fs, vs, fs2, vs2], [fs, vs2], [vs, fs2], "attaching and detaching some shaders should leave the difference list"); checkGetAttachedShaders([fsBad], [], [fsBad], "attaching a shader that failed to compile should still show it in the list"); checkGetAttachedShaders([fs, vsBad, fs2], [], [fs, vsBad, fs2], "attaching shaders, including one that failed to compile, should still show the it in the list");
--- a/content/events/src/nsEventListenerService.cpp +++ b/content/events/src/nsEventListenerService.cpp @@ -134,20 +134,23 @@ nsEventListenerInfo::ToSource(nsAString& if (GetJSVal(&v)) { nsCOMPtr<nsIThreadJSContextStack> stack = nsContentUtils::ThreadJSContextStack(); if (stack) { JSContext* cx = nsnull; stack->GetSafeJSContext(&cx); if (cx && NS_SUCCEEDED(stack->Push(cx))) { - JSAutoRequest ar(cx); - JSString* str = JS_ValueToSource(cx, v); - if (str) { - aResult.Assign(nsDependentJSString(str)); + { + // Extra block to finish the auto request before calling pop + JSAutoRequest ar(cx); + JSString* str = JS_ValueToSource(cx, v); + if (str) { + aResult.Assign(nsDependentJSString(str)); + } } stack->Pop(&cx); } } } return NS_OK; }
--- a/content/html/content/src/nsGenericHTMLElement.cpp +++ b/content/html/content/src/nsGenericHTMLElement.cpp @@ -1576,19 +1576,19 @@ nsGenericHTMLElement::ParseTableCellHAli PRBool nsGenericHTMLElement::ParseTableVAlignValue(const nsAString& aString, nsAttrValue& aResult) { return aResult.ParseEnumValue(aString, kTableVAlignTable, PR_FALSE); } -PRBool +PRBool nsGenericHTMLElement::ParseDivAlignValue(const nsAString& aString, - nsAttrValue& aResult) const + nsAttrValue& aResult) { return aResult.ParseEnumValue(aString, kDivAlignTable, PR_FALSE); } PRBool nsGenericHTMLElement::ParseImageAttribute(nsIAtom* aAttribute, const nsAString& aString, nsAttrValue& aResult)
--- a/content/html/content/src/nsGenericHTMLElement.h +++ b/content/html/content/src/nsGenericHTMLElement.h @@ -248,18 +248,18 @@ public: /** * Parse a div align string to value (left/right/center/middle/justify) * * @param aString the string to parse * @param aResult the resulting HTMLValue * @return whether the value was parsed */ - PRBool ParseDivAlignValue(const nsAString& aString, - nsAttrValue& aResult) const; + static PRBool ParseDivAlignValue(const nsAString& aString, + nsAttrValue& aResult); /** * Convert a table halign string to value (left/right/center/char/justify) * * @param aString the string to parse * @param aResult the resulting HTMLValue * @return whether the value was parsed */
--- a/content/html/content/src/nsHTMLCanvasElement.cpp +++ b/content/html/content/src/nsHTMLCanvasElement.cpp @@ -197,23 +197,17 @@ nsHTMLCanvasElement::ToDataURL(const nsA { // do a trust check if this is a write-only canvas // or if we're trying to use the 2-arg form if ((mWriteOnly || optional_argc >= 2) && !nsContentUtils::IsCallerTrustedForRead()) { return NS_ERROR_DOM_SECURITY_ERR; } - nsAutoString type(aType); - - if (type.IsEmpty()) { - type.AssignLiteral("image/png"); - } - - return ToDataURLImpl(type, aParams, aDataURL); + return ToDataURLImpl(aType, aParams, aDataURL); } // nsHTMLCanvasElement::toDataURLAs // // Native-callers only NS_IMETHODIMP @@ -224,33 +218,40 @@ nsHTMLCanvasElement::ToDataURLAs(const n return ToDataURLImpl(aMimeType, aEncoderOptions, aDataURL); } nsresult nsHTMLCanvasElement::ToDataURLImpl(const nsAString& aMimeType, const nsAString& aEncoderOptions, nsAString& aDataURL) { - nsresult rv; + bool fallbackToPNG = false; // We get an input stream from the context. If more than one context type // is supported in the future, this will have to be changed to do the right // thing. For now, just assume that the 2D context has all the goods. nsCOMPtr<nsICanvasRenderingContextInternal> context; - rv = GetContext(NS_LITERAL_STRING("2d"), getter_AddRefs(context)); + nsresult rv = GetContext(NS_LITERAL_STRING("2d"), getter_AddRefs(context)); NS_ENSURE_SUCCESS(rv, rv); // get image bytes nsCOMPtr<nsIInputStream> imgStream; NS_ConvertUTF16toUTF8 aMimeType8(aMimeType); rv = context->GetInputStream(nsPromiseFlatCString(aMimeType8).get(), nsPromiseFlatString(aEncoderOptions).get(), getter_AddRefs(imgStream)); - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - NS_ENSURE_SUCCESS(rv, rv); + if (NS_FAILED(rv)) { + // Use image/png instead. + // XXX ERRMSG we need to report an error to developers here! (bug 329026) + fallbackToPNG = true; + rv = context->GetInputStream("image/png", + nsPromiseFlatString(aEncoderOptions).get(), + getter_AddRefs(imgStream)); + NS_ENSURE_SUCCESS(rv, rv); + } // Generally, there will be only one chunk of data, and it will be available // for us to read right away, so optimize this case. PRUint32 bufSize; rv = imgStream->Available(&bufSize); NS_ENSURE_SUCCESS(rv, rv); // ...leave a little extra room so we can call read again and make sure we @@ -278,18 +279,22 @@ nsHTMLCanvasElement::ToDataURLImpl(const // base 64, result will be NULL terminated char* encodedImg = PL_Base64Encode(imgData, imgSize, nsnull); PR_Free(imgData); if (!encodedImg) // not sure why this would fail return NS_ERROR_OUT_OF_MEMORY; // build data URL string - aDataURL = NS_LITERAL_STRING("data:") + aMimeType + - NS_LITERAL_STRING(";base64,") + NS_ConvertUTF8toUTF16(encodedImg); + if (fallbackToPNG) + aDataURL = NS_LITERAL_STRING("data:image/png;base64,") + + NS_ConvertUTF8toUTF16(encodedImg); + else + aDataURL = NS_LITERAL_STRING("data:") + aMimeType + + NS_LITERAL_STRING(";base64,") + NS_ConvertUTF8toUTF16(encodedImg); PR_Free(encodedImg); return NS_OK; } nsresult nsHTMLCanvasElement::GetContextHelper(const nsAString& aContextId, @@ -304,34 +309,34 @@ nsHTMLCanvasElement::GetContextHelper(co for (PRUint32 i = 0; i < ctxId.Length(); i++) { if ((ctxId[i] < 'A' || ctxId[i] > 'Z') && (ctxId[i] < 'a' || ctxId[i] > 'z') && (ctxId[i] < '0' || ctxId[i] > '9') && (ctxId[i] != '-') && (ctxId[i] != '_')) { // XXX ERRMSG we need to report an error to developers here! (bug 329026) - return NS_ERROR_INVALID_ARG; + return NS_OK; } } nsCString ctxString("@mozilla.org/content/canvas-rendering-context;1?id="); ctxString.Append(ctxId); nsresult rv; nsCOMPtr<nsICanvasRenderingContextInternal> ctx = do_CreateInstance(nsPromiseFlatCString(ctxString).get(), &rv); if (rv == NS_ERROR_OUT_OF_MEMORY) { *aContext = nsnull; return NS_ERROR_OUT_OF_MEMORY; } if (NS_FAILED(rv)) { *aContext = nsnull; // XXX ERRMSG we need to report an error to developers here! (bug 329026) - return NS_ERROR_INVALID_ARG; + return NS_OK; } rv = ctx->SetCanvasElement(this); if (NS_FAILED(rv)) { *aContext = nsnull; return rv; } @@ -371,17 +376,17 @@ nsHTMLCanvasElement::GetContext(const ns if (NS_FAILED(rv)) { mCurrentContext = nsnull; return rv; } mCurrentContextId.Assign(aContextId); } else if (!mCurrentContextId.Equals(aContextId)) { //XXX eventually allow for more than one active context on a given canvas - return NS_ERROR_INVALID_ARG; + return NS_OK; } NS_ADDREF (*aContext = mCurrentContext); return NS_OK; } NS_IMETHODIMP nsHTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId,
--- a/content/html/content/test/test_bug346485.html +++ b/content/html/content/test/test_bug346485.html @@ -111,17 +111,17 @@ function checkFormIDLAttribute(element) function checkHtmlForIDLAttribute(element) { is(element.htmlFor, 'a b', "htmlFor IDL attribute should reflect the for content attribute"); // DOMSettableTokenList is tested in another bug so we just test assignation element.htmlFor.value = 'a b c'; - is(element.htmlFor, 'a b c', "htmlFor shoud have changed"); + is(element.htmlFor, 'a b c', "htmlFor should have changed"); } function submitForm() { // Setting the values for the submit. document.getElementById('o').value = 'foo'; document.getElementById('a').value = 'afield'; document.getElementById('b').value = 'bfield';
--- a/content/html/document/src/nsHTMLContentSink.cpp +++ b/content/html/document/src/nsHTMLContentSink.cpp @@ -143,21 +143,23 @@ typedef nsGenericHTMLElement* (*contentC nsGenericHTMLElement* NS_NewHTMLNOTUSEDElement(nsINodeInfo *aNodeInfo, PRUint32 aFromParser) { NS_NOTREACHED("The element ctor should never be called"); return nsnull; } #define HTML_TAG(_tag, _classname) NS_NewHTML##_classname##Element, +#define HTML_HTMLELEMENT_TAG(_tag) NS_NewHTMLElement, #define HTML_OTHER(_tag) NS_NewHTMLNOTUSEDElement, static const contentCreatorCallback sContentCreatorCallbacks[] = { NS_NewHTMLUnknownElement, #include "nsHTMLTagList.h" #undef HTML_TAG +#undef HTML_HTMLELEMENT_TAG #undef HTML_OTHER NS_NewHTMLUnknownElement }; class SinkContext; class HTMLContentSink; static void MaybeSetForm(nsGenericHTMLElement*, nsHTMLTag, HTMLContentSink*);
--- a/content/html/document/src/nsHTMLDocument.cpp +++ b/content/html/document/src/nsHTMLDocument.cpp @@ -1249,31 +1249,21 @@ nsHTMLDocument::SetCompatibilityMode(nsC // // nsIDOMDocument interface implementation // NS_IMETHODIMP nsHTMLDocument::CreateElement(const nsAString& aTagName, nsIDOMElement** aReturn) { *aReturn = nsnull; - nsresult rv; + nsresult rv = nsContentUtils::CheckQName(aTagName, PR_FALSE); + if (NS_FAILED(rv)) + return rv; nsAutoString tagName(aTagName); - - // if we are in quirks, allow surrounding '<' '>' for IE compat - if (mCompatMode == eCompatibility_NavQuirks && - tagName.Length() > 2 && - tagName.First() == '<' && - tagName.Last() == '>') { - tagName = Substring(tagName, 1, tagName.Length() - 2); - } - - rv = nsContentUtils::CheckQName(tagName, PR_FALSE); - NS_ENSURE_SUCCESS(rv, rv); - if (IsHTML()) { ToLowerCase(tagName); } nsCOMPtr<nsIAtom> name = do_GetAtom(tagName); nsCOMPtr<nsIContent> content; rv = CreateElem(name, nsnull, kNameSpaceID_XHTML, PR_TRUE,
--- a/content/html/document/src/nsHTMLFragmentContentSink.cpp +++ b/content/html/document/src/nsHTMLFragmentContentSink.cpp @@ -1001,17 +1001,20 @@ nsHTMLParanoidFragmentSink::AddAttribute } // Get value and remove mandatory quotes static const char* kWhitespace = "\n\r\t\b"; const nsAString& v = nsContentUtils::TrimCharsInSet(kWhitespace, aNode.GetValueAt(i)); // check the attributes we allow that contain URIs - if (IsAttrURI(keyAtom)) { + // special case src attributes for img tags, because they can't + // run any dangerous code. + if (IsAttrURI(keyAtom) && + !(nodeType == eHTMLTag_img && keyAtom == nsGkAtoms::src)) { if (!baseURI) { baseURI = aContent->GetBaseURI(); } nsCOMPtr<nsIURI> attrURI; rv = NS_NewURI(getter_AddRefs(attrURI), v, nsnull, baseURI); if (NS_SUCCEEDED(rv)) { rv = secMan-> CheckLoadURIWithPrincipal(mTargetDocument->NodePrincipal(),
--- a/content/html/document/test/Makefile.in +++ b/content/html/document/test/Makefile.in @@ -56,16 +56,17 @@ include $(topsrcdir)/config/rules.mk bug199692-scrolled.html \ test_bug172261.html \ test_bug255820.html \ test_bug259332.html \ test_bug311681.html \ test_bug311681.xhtml \ test_bug324378.html \ test_bug332848.xhtml \ + test_bug340017.xhtml \ test_bug359657.html \ test_bug369370.html \ bug369370-popup.png \ test_bug380383.html \ test_bug391777.html \ test_bug402680.html \ test_bug403868.html \ test_bug403868.xhtml \ @@ -90,20 +91,20 @@ include $(topsrcdir)/config/rules.mk bug448564-iframe-3.html \ bug448564-echo.sjs \ bug448564-submit.js \ test_bug478251.html \ test_bug481440.html \ test_bug481647.html \ test_bug482659.html \ test_bug486741.html \ + test_bug489532.html \ test_bug497242.xhtml \ - test_bug512367.html \ - test_bug570375.html \ - test_bug340017.xhtml \ test_bug499092.html \ bug499092.xml \ bug499092.html \ + test_bug512367.html \ + test_bug570376.html \ test_bug571981.html \ $(NULL) libs:: $(_TEST_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644 --- /dev/null +++ b/content/html/document/test/test_bug489532.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=489532 +--> +<head> + <title>Test for Bug 489532</title> + <script src="/MochiKit/packed.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=489532">Mozilla Bug 489532</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script> +/** Test for Bug 489532 **/ +try { + document.createElement("<div>"); + ok(false, "Should throw.") +} catch (e) { + ok(e instanceof DOMException, "Expected DOMException."); + is(e.code, DOMException.INVALID_CHARACTER_ERR, + "Expected INVALID_CHARACTER_ERR."); +} +</script> +</pre> +</body> +</html>
rename from content/html/document/test/test_bug570375.html rename to content/html/document/test/test_bug570376.html --- a/content/html/document/test/test_bug570375.html +++ b/content/html/document/test/test_bug570376.html @@ -1,30 +1,30 @@ <!DOCTYPE HTML> <html> <!-- -https://bugzilla.mozilla.org/show_bug.cgi?id=570375 +https://bugzilla.mozilla.org/show_bug.cgi?id=570376 --> <head> - <title>Test for Bug 570375</title> + <title>Test for Bug 570376</title> <script type="application/javascript" src="/MochiKit/packed.js"></script> <script type="application/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=570375">Mozilla Bug 570375</a> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=570376">Mozilla Bug 570376</a> <p id="display"> <iframe id="testiframe"></iframe> </p> <div id="content" style="display: none"> </div> <pre id="test"> <script type="application/javascript"> -/** Test for Bug 570375 +/** Test for Bug 570376 Don't crash loading <form><legend> with the html5 parser preffed off. **/ netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); var prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); var gOriginalHtml5Pref = prefs.getBoolPref("html5.enable"); prefs.setBoolPref("html5.enable", false);
--- a/content/media/nsBuiltinDecoderReader.cpp +++ b/content/media/nsBuiltinDecoderReader.cpp @@ -121,19 +121,19 @@ VideoData* VideoData::Create(nsVideoInfo NS_WARNING("Invalid plane size"); return nsnull; } // Ensure the picture size specified in the headers can be extracted out of // the frame we've been supplied without indexing out of bounds. PRUint32 picXLimit; PRUint32 picYLimit; if (!AddOverflow32(aInfo.mPicture.x, aInfo.mPicture.width, picXLimit) || - picXLimit > PRUint32(aBuffer.mPlanes[0].mStride) || + picXLimit > aBuffer.mPlanes[0].mStride || !AddOverflow32(aInfo.mPicture.y, aInfo.mPicture.height, picYLimit) || - picYLimit > PRUint32(aBuffer.mPlanes[0].mHeight)) + picYLimit > aBuffer.mPlanes[0].mHeight) { // The specified picture dimensions can't be contained inside the video // frame, we'll stomp memory if we try to copy it. Fail. NS_WARNING("Overflowing picture rect"); return nsnull; } nsAutoPtr<VideoData> v(new VideoData(aOffset, aTime, aEndTime, aKeyframe, aTimecode));
--- a/content/media/ogg/nsOggReader.cpp +++ b/content/media/ogg/nsOggReader.cpp @@ -899,20 +899,21 @@ PRInt64 nsOggReader::FindEndTime(PRInt64 nsOggCodecState* codecState = nsnull; mCodecStates.Get(serial, &codecState); if (!codecState) { // This page is from a bitstream which we haven't encountered yet. // It's probably from a new "link" in a "chained" ogg. Don't // bother even trying to find a duration... + endTime = -1; break; } - PRInt64 t = codecState ? codecState->Time(granulepos) : -1; + PRInt64 t = codecState->Time(granulepos); if (t != -1) { endTime = t; } } ogg_sync_reset(&mOggState); NS_ASSERTION(mDataOffset > 0,
--- a/content/smil/nsSMILAnimationController.cpp +++ b/content/smil/nsSMILAnimationController.cpp @@ -320,16 +320,19 @@ nsSMILAnimationController::DoSample() void nsSMILAnimationController::DoSample(PRBool aSkipUnchangedContainers) { // Reset resample flag mResampleNeeded = PR_FALSE; // STEP 1: Bring model up to date + // (i) Rewind elements where necessary + // (ii) Run milestone samples + RewindElements(); DoMilestoneSamples(); // STEP 2: Sample the child time containers // // When we sample the child time containers they will simply record the sample // time in document time. TimeContainerHashtable activeContainers; activeContainers.Init(mChildContainerTable.Count()); @@ -398,16 +401,66 @@ nsSMILAnimationController::DoSample(PRBo // Update last compositor table mLastCompositorTable = currentCompositorTable.forget(); NS_ASSERTION(!mResampleNeeded, "Resample dirty flag set during sample!"); } void +nsSMILAnimationController::RewindElements() +{ + PRBool rewindNeeded = PR_FALSE; + mChildContainerTable.EnumerateEntries(RewindNeeded, &rewindNeeded); + if (!rewindNeeded) + return; + + mAnimationElementTable.EnumerateEntries(RewindAnimation, nsnull); + mChildContainerTable.EnumerateEntries(ClearRewindNeeded, nsnull); +} + +/*static*/ PR_CALLBACK PLDHashOperator +nsSMILAnimationController::RewindNeeded(TimeContainerPtrKey* aKey, + void* aData) +{ + NS_ABORT_IF_FALSE(aData, + "Null data pointer during time container enumeration"); + PRBool* rewindNeeded = static_cast<PRBool*>(aData); + + nsSMILTimeContainer* container = aKey->GetKey(); + if (container->NeedsRewind()) { + *rewindNeeded = PR_TRUE; + return PL_DHASH_STOP; + } + + return PL_DHASH_NEXT; +} + +/*static*/ PR_CALLBACK PLDHashOperator +nsSMILAnimationController::RewindAnimation(AnimationElementPtrKey* aKey, + void* aData) +{ + nsISMILAnimationElement* animElem = aKey->GetKey(); + nsSMILTimeContainer* timeContainer = animElem->GetTimeContainer(); + if (timeContainer && timeContainer->NeedsRewind()) { + animElem->TimedElement().Rewind(); + } + + return PL_DHASH_NEXT; +} + +/*static*/ PR_CALLBACK PLDHashOperator +nsSMILAnimationController::ClearRewindNeeded(TimeContainerPtrKey* aKey, + void* aData) +{ + aKey->GetKey()->ClearNeedsRewind(); + return PL_DHASH_NEXT; +} + +void nsSMILAnimationController::DoMilestoneSamples() { // We need to sample the timing model but because SMIL operates independently // of the frame-rate, we can get one sample at t=0s and the next at t=10min. // // In between those two sample times a whole string of significant events // might be expected to take place: events firing, new interdependencies // between animations resolved and dissolved, etc. @@ -537,16 +590,17 @@ nsSMILAnimationController::SampleTimeCon SampleTimeContainerParams* params = static_cast<SampleTimeContainerParams*>(aData); nsSMILTimeContainer* container = aKey->GetKey(); if (!container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN) && (container->NeedsSample() || !params->mSkipUnchangedContainers)) { container->ClearMilestones(); container->Sample(); + container->MarkSeekFinished(); params->mActiveContainers->PutEntry(container); } return PL_DHASH_NEXT; } /*static*/ PR_CALLBACK PLDHashOperator nsSMILAnimationController::SampleAnimation(AnimationElementPtrKey* aKey, @@ -582,16 +636,18 @@ nsSMILAnimationController::SampleTimedEl // Instead we build up a hashmap of active time containers during the previous // step (SampleTimeContainer) and then test here if the container for this // timed element is in the list. if (!aActiveContainers->GetEntry(timeContainer)) return; nsSMILTime containerTime = timeContainer->GetCurrentTime(); + NS_ABORT_IF_FALSE(!timeContainer->IsSeeking(), + "Doing a regular sample but the time container is still seeking"); aElement->TimedElement().SampleAt(containerTime); } /*static*/ void nsSMILAnimationController::AddAnimationToCompositorTable( nsISMILAnimationElement* aElement, nsSMILCompositorTable* aCompositorTable) { // Add a compositor to the hash table if there's not already one there
--- a/content/smil/nsSMILAnimationController.h +++ b/content/smil/nsSMILAnimationController.h @@ -147,21 +147,31 @@ protected: // Cycle-collection implementation helpers PR_STATIC_CALLBACK(PLDHashOperator) CompositorTableEntryTraverse( nsSMILCompositor* aCompositor, void* aArg); // Sample-related callbacks and implementation helpers virtual void DoSample(); void DoSample(PRBool aSkipUnchangedContainers); + + void RewindElements(); + PR_STATIC_CALLBACK(PLDHashOperator) RewindNeeded( + TimeContainerPtrKey* aKey, void* aData); + PR_STATIC_CALLBACK(PLDHashOperator) RewindAnimation( + AnimationElementPtrKey* aKey, void* aData); + PR_STATIC_CALLBACK(PLDHashOperator) ClearRewindNeeded( + TimeContainerPtrKey* aKey, void* aData); + void DoMilestoneSamples(); PR_STATIC_CALLBACK(PLDHashOperator) GetNextMilestone( TimeContainerPtrKey* aKey, void* aData); PR_STATIC_CALLBACK(PLDHashOperator) GetMilestoneElements( TimeContainerPtrKey* aKey, void* aData); + PR_STATIC_CALLBACK(PLDHashOperator) SampleTimeContainer( TimeContainerPtrKey* aKey, void* aData); PR_STATIC_CALLBACK(PLDHashOperator) SampleAnimation( AnimationElementPtrKey* aKey, void* aData); static void SampleTimedElement(nsISMILAnimationElement* aElement, TimeContainerHashtable* aActiveContainers); static void AddAnimationToCompositorTable( nsISMILAnimationElement* aElement, nsSMILCompositorTable* aCompositorTable);
--- a/content/smil/nsSMILInstanceTime.cpp +++ b/content/smil/nsSMILInstanceTime.cpp @@ -69,48 +69,51 @@ namespace // Implementation nsSMILInstanceTime::nsSMILInstanceTime(const nsSMILTimeValue& aTime, nsSMILInstanceTimeSource aSource, nsSMILTimeValueSpec* aCreator, nsSMILInterval* aBaseInterval) : mTime(aTime), mFlags(0), + mVisited(PR_FALSE), + mFixedEndpointRefCnt(0), mSerial(0), - mVisited(PR_FALSE), - mChainEnd(PR_FALSE), mCreator(aCreator), mBaseInterval(nsnull) // This will get set to aBaseInterval in a call to // SetBaseInterval() at end of constructor { switch (aSource) { case SOURCE_NONE: // No special flags break; case SOURCE_DOM: - mFlags = kClearOnReset | kFromDOM; + mFlags = kDynamic | kFromDOM; break; case SOURCE_SYNCBASE: mFlags = kMayUpdate; break; case SOURCE_EVENT: - mFlags = kClearOnReset; + mFlags = kDynamic; break; } SetBaseInterval(aBaseInterval); } nsSMILInstanceTime::~nsSMILInstanceTime() { NS_ABORT_IF_FALSE(!mBaseInterval && !mCreator, "Destroying instance time without first calling Unlink()"); + NS_ABORT_IF_FALSE(mFixedEndpointRefCnt == 0, + "Destroying instance time that is still used as the fixed endpoint of an " + "interval"); } void nsSMILInstanceTime::Unlink() { nsRefPtr<nsSMILInstanceTime> deathGrip(this); if (mBaseInterval) { mBaseInterval->RemoveDependentTime(*this); @@ -124,22 +127,19 @@ nsSMILInstanceTime::HandleChangedInterva const nsSMILTimeContainer* aSrcContainer, PRBool aBeginObjectChanged, PRBool aEndObjectChanged) { NS_ABORT_IF_FALSE(mBaseInterval, "Got call to HandleChangedInterval on an independent instance time."); NS_ABORT_IF_FALSE(mCreator, "Base interval is set but creator is not."); - if (mVisited || mChainEnd) { - // We're breaking the cycle here but we need to ensure that if we later - // receive a change notice in a different context (e.g. due to a time - // container change) that we don't end up following the chain further and so - // we set a flag to that effect. - mChainEnd = PR_TRUE; + if (mVisited) { + // Break the cycle here + Unlink(); return; } PRBool objectChanged = mCreator->DependsOnBegin() ? aBeginObjectChanged : aEndObjectChanged; AutoBoolSetter setVisited(mVisited); @@ -147,42 +147,85 @@ nsSMILInstanceTime::HandleChangedInterva mCreator->HandleChangedInstanceTime(*GetBaseTime(), aSrcContainer, *this, objectChanged); } void nsSMILInstanceTime::HandleDeletedInterval() { NS_ABORT_IF_FALSE(mBaseInterval, - "Got call to HandleDeletedInterval on an independent instance time."); - NS_ABORT_IF_FALSE(mCreator, "Base interval is set but creator is not."); + "Got call to HandleDeletedInterval on an independent instance time"); + NS_ABORT_IF_FALSE(mCreator, "Base interval is set but creator is not"); mBaseInterval = nsnull; + mFlags &= ~kMayUpdate; // Can't update without a base interval nsRefPtr<nsSMILInstanceTime> deathGrip(this); mCreator->HandleDeletedInstanceTime(*this); mCreator = nsnull; } +void +nsSMILInstanceTime::HandleFilteredInterval() +{ + NS_ABORT_IF_FALSE(mBaseInterval, + "Got call to HandleFilteredInterval on an independent instance time"); + + mBaseInterval = nsnull; + mFlags &= ~kMayUpdate; // Can't update without a base interval + mCreator = nsnull; +} + PRBool -nsSMILInstanceTime::IsDependent(const nsSMILInstanceTime& aOther) const +nsSMILInstanceTime::ShouldPreserve() const +{ + return mFixedEndpointRefCnt > 0 || (mFlags & kWasDynamicEndpoint); +} + +void +nsSMILInstanceTime::UnmarkShouldPreserve() +{ + mFlags &= ~kWasDynamicEndpoint; +} + +void +nsSMILInstanceTime::AddRefFixedEndpoint() { - if (mVisited || mChainEnd) + NS_ABORT_IF_FALSE(mFixedEndpointRefCnt < PR_UINT16_MAX, + "Fixed endpoint reference count upper limit reached"); + ++mFixedEndpointRefCnt; + mFlags &= ~kMayUpdate; // Once fixed, always fixed +} + +void +nsSMILInstanceTime::ReleaseFixedEndpoint() +{ + NS_ABORT_IF_FALSE(mFixedEndpointRefCnt > 0, "Duplicate release"); + --mFixedEndpointRefCnt; + if (mFixedEndpointRefCnt == 0 && IsDynamic()) { + mFlags |= kWasDynamicEndpoint; + } +} + +PRBool +nsSMILInstanceTime::IsDependentOn(const nsSMILInstanceTime& aOther) const +{ + if (mVisited) return PR_FALSE; const nsSMILInstanceTime* myBaseTime = GetBaseTime(); if (!myBaseTime) return PR_FALSE; if (myBaseTime == &aOther) return PR_TRUE; // mVisited is mutable AutoBoolSetter setVisited(const_cast<nsSMILInstanceTime*>(this)->mVisited); - return myBaseTime->IsDependent(aOther); + return myBaseTime->IsDependentOn(aOther); } void nsSMILInstanceTime::SetBaseInterval(nsSMILInterval* aBaseInterval) { NS_ABORT_IF_FALSE(!mBaseInterval, "Attempting to reassociate an instance time with a different interval.");
--- a/content/smil/nsSMILInstanceTime.h +++ b/content/smil/nsSMILInstanceTime.h @@ -87,34 +87,41 @@ public: nsSMILTimeValueSpec* aCreator = nsnull, nsSMILInterval* aBaseInterval = nsnull); ~nsSMILInstanceTime(); void Unlink(); void HandleChangedInterval(const nsSMILTimeContainer* aSrcContainer, PRBool aBeginObjectChanged, PRBool aEndObjectChanged); void HandleDeletedInterval(); + void HandleFilteredInterval(); const nsSMILTimeValue& Time() const { return mTime; } const nsSMILTimeValueSpec* GetCreator() const { return mCreator; } - PRBool ClearOnReset() const { return !!(mFlags & kClearOnReset); } - PRBool MayUpdate() const { return !!(mFlags & kMayUpdate); } + PRBool IsDynamic() const { return !!(mFlags & kDynamic); } + PRBool IsFixedTime() const { return !(mFlags & kMayUpdate); } PRBool FromDOM() const { return !!(mFlags & kFromDOM); } - void MarkNoLongerUpdating() { mFlags &= ~kMayUpdate; } + PRBool ShouldPreserve() const; + void UnmarkShouldPreserve(); + + void AddRefFixedEndpoint(); + void ReleaseFixedEndpoint(); void DependentUpdate(const nsSMILTimeValue& aNewTime) { - NS_ABORT_IF_FALSE(MayUpdate(), + NS_ABORT_IF_FALSE(!IsFixedTime(), "Updating an instance time that is not expected to be updated"); mTime = aNewTime; } - PRBool IsDependent(const nsSMILInstanceTime& aOther) const; + PRBool IsDependent() const { return !!mBaseInterval; } + PRBool IsDependentOn(const nsSMILInstanceTime& aOther) const; + const nsSMILInterval* GetBaseInterval() const { return mBaseInterval; } PRBool SameTimeAndBase(const nsSMILInstanceTime& aOther) const { return mTime == aOther.mTime && GetBaseTime() == aOther.GetBaseTime(); } // Get and set a serial number which may be used by a containing class to // control the sort order of otherwise similar instance times. @@ -126,43 +133,60 @@ public: protected: void SetBaseInterval(nsSMILInterval* aBaseInterval); const nsSMILInstanceTime* GetBaseTime() const; nsSMILTimeValue mTime; // Internal flags used to represent the behaviour of different instance times enum { - // Indicates if this instance time should be removed when the owning timed - // element is reset. True for events and DOM calls. - kClearOnReset = 1, + // Indicates that this instance time was generated by an event or a DOM + // call. Such instance times require special handling when (i) the owning + // element is reset, and (ii) when a backwards seek is performed and the + // timing model is reconstructed. + kDynamic = 1, // Indicates that this instance time is referred to by an // nsSMILTimeValueSpec and as such may be updated. Such instance time should // not be filtered out by the nsSMILTimedElement even if they appear to be - // in the past as they may be updated to a future time. Initially set for - // syncbase-generated times until they are frozen. + // in the past as they may be updated to a future time. kMayUpdate = 2, // Indicates that this instance time was generated from the DOM as opposed // to an nsSMILTimeValueSpec. When a 'begin' or 'end' attribute is set or // reset we should clear all the instance times that have been generated by // that attribute (and hence an nsSMILTimeValueSpec), but not those from the // DOM. - kFromDOM = 4 + kFromDOM = 4, + + // Indicates that this instance time was used as the endpoint of an interval + // that has been filtered or removed. However, since it is a dynamic time it + // should be preserved and not filtered. + kWasDynamicEndpoint = 8 }; - PRUint8 mFlags; // Combination of kClearOnReset, kMayUpdate, etc. + PRUint8 mFlags; // Combination of kDynamic, kMayUpdate, etc. + PRPackedBool mVisited; // (mutable) Cycle tracking + + // Additional reference count to determine if this instance time is currently + // used as a fixed endpoint in any intervals. Instance times that are used in + // this way should not be removed when the owning nsSMILTimedElement removes + // instance times in response to a restart or in an attempt to free up memory + // by filtering out old instance times. + // + // Instance times are only shared in a few cases, namely: + // a) early ends, + // b) zero-duration intervals, and + // c) momentarily whilst establishing new intervals and updating the current + // interval + // Hence the limited range of a PRUint16 should be more than adequate. + PRUint16 mFixedEndpointRefCnt; + PRUint32 mSerial; // A serial number used by the containing class to // specify the sort order for instance times with the // same mTime. - PRPackedBool mVisited; // (mutable) Cycle tracking - PRPackedBool mChainEnd; // Flag to indicate that this instance time is part - // of some cyclic dependency and that in order to - // avoid infinite recursion the cycle should not be - // followed any further than this point. nsSMILTimeValueSpec* mCreator; // The nsSMILTimeValueSpec object that created // us. (currently only needed for syncbase // instance times.) nsSMILInterval* mBaseInterval; // Interval from which this time is derived // (only used for syncbase instance times) };
--- a/content/smil/nsSMILInterval.cpp +++ b/content/smil/nsSMILInterval.cpp @@ -34,59 +34,82 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsSMILInterval.h" nsSMILInterval::nsSMILInterval() : + mBeginFixed(PR_FALSE), + mEndFixed(PR_FALSE), mBeginObjectChanged(PR_FALSE), mEndObjectChanged(PR_FALSE) { } nsSMILInterval::nsSMILInterval(const nsSMILInterval& aOther) : mBegin(aOther.mBegin), mEnd(aOther.mEnd), + mBeginFixed(PR_FALSE), + mEndFixed(PR_FALSE), mBeginObjectChanged(PR_FALSE), mEndObjectChanged(PR_FALSE) { NS_ABORT_IF_FALSE(aOther.mDependentTimes.IsEmpty(), "Attempting to copy-construct an interval with dependent times, " "this will lead to instance times being shared between intervals."); + + // For the time being we don't allow intervals with fixed endpoints to be + // copied since we only ever copy-construct to establish a new current + // interval. If we ever need to copy historical intervals we may need to move + // the ReleaseFixedEndpoint calls from Unlink to the dtor. + NS_ABORT_IF_FALSE(!aOther.mBeginFixed && !aOther.mEndFixed, + "Attempting to copy-construct an interval with fixed endpoints"); } nsSMILInterval::~nsSMILInterval() { NS_ABORT_IF_FALSE(mDependentTimes.IsEmpty(), "Destroying interval without disassociating dependent instance times. " - "NotifyDeleting was not called."); + "Unlink was not called"); } void nsSMILInterval::NotifyChanged(const nsSMILTimeContainer* aContainer) { for (PRInt32 i = mDependentTimes.Length() - 1; i >= 0; --i) { mDependentTimes[i]->HandleChangedInterval(aContainer, mBeginObjectChanged, mEndObjectChanged); } mBeginObjectChanged = PR_FALSE; mEndObjectChanged = PR_FALSE; } void -nsSMILInterval::NotifyDeleting() +nsSMILInterval::Unlink(PRBool aFiltered) { for (PRInt32 i = mDependentTimes.Length() - 1; i >= 0; --i) { - mDependentTimes[i]->HandleDeletedInterval(); + if (aFiltered) { + mDependentTimes[i]->HandleFilteredInterval(); + } else { + mDependentTimes[i]->HandleDeletedInterval(); + } } mDependentTimes.Clear(); + if (mBegin && mBeginFixed) { + mBegin->ReleaseFixedEndpoint(); + } + mBegin = nsnull; + if (mEnd && mEndFixed) { + mEnd->ReleaseFixedEndpoint(); + } + mEnd = nsnull; } nsSMILInstanceTime* nsSMILInterval::Begin() { NS_ABORT_IF_FALSE(mBegin && mEnd, "Requesting Begin() on un-initialized interval."); return mBegin; @@ -99,36 +122,63 @@ nsSMILInterval::End() "Requesting End() on un-initialized interval."); return mEnd; } void nsSMILInterval::SetBegin(nsSMILInstanceTime& aBegin) { NS_ABORT_IF_FALSE(aBegin.Time().IsResolved(), - "Attempting to set unresolved begin time on interval."); + "Attempting to set unresolved begin time on interval"); + NS_ABORT_IF_FALSE(!mBeginFixed, + "Attempting to set begin time but the begin point is fixed"); if (mBegin == &aBegin) return; mBegin = &aBegin; mBeginObjectChanged = PR_TRUE; } void nsSMILInterval::SetEnd(nsSMILInstanceTime& aEnd) { + NS_ABORT_IF_FALSE(!mEndFixed, + "Attempting to set end time but the end point is fixed"); + if (mEnd == &aEnd) return; mEnd = &aEnd; mEndObjectChanged = PR_TRUE; } void +nsSMILInterval::FixBegin() +{ + NS_ABORT_IF_FALSE(mBegin && mEnd, + "Fixing begin point on un-initialized interval"); + NS_ABORT_IF_FALSE(!mBeginFixed, "Duplicate calls to FixBegin()"); + mBeginFixed = PR_TRUE; + mBegin->AddRefFixedEndpoint(); +} + +void +nsSMILInterval::FixEnd() +{ + NS_ABORT_IF_FALSE(mBegin && mEnd, + "Fixing end point on un-initialized interval"); + NS_ABORT_IF_FALSE(mBeginFixed, + "Fixing the end of an interval without a fixed begin"); + NS_ABORT_IF_FALSE(!mEndFixed, "Duplicate calls to FixEnd()"); + mEndFixed = PR_TRUE; + mEnd->AddRefFixedEndpoint(); +} + +void nsSMILInterval::AddDependentTime(nsSMILInstanceTime& aTime) { nsRefPtr<nsSMILInstanceTime>* inserted = mDependentTimes.InsertElementSorted(&aTime); if (!inserted) { NS_WARNING("Insufficient memory to insert instance time."); } } @@ -137,8 +187,24 @@ void nsSMILInterval::RemoveDependentTime(const nsSMILInstanceTime& aTime) { #ifdef DEBUG PRBool found = #endif mDependentTimes.RemoveElementSorted(&aTime); NS_ABORT_IF_FALSE(found, "Couldn't find instance time to delete."); } + +PRBool +nsSMILInterval::IsDependencyChainLink() const +{ + if (!mBegin || !mEnd) + return PR_FALSE; // Not yet initialised so it can't be part of a chain + + if (mDependentTimes.IsEmpty()) + return PR_FALSE; // No dependents, chain end + + // So we have dependents, but we're still only a link in the chain (as opposed + // to the end of the chain) if one of our endpoints is dependent on an + // interval other than ourselves. + return (mBegin->IsDependent() && mBegin->GetBaseInterval() != this) || + (mEnd->IsDependent() && mEnd->GetBaseInterval() != this); +}
--- a/content/smil/nsSMILInterval.h +++ b/content/smil/nsSMILInterval.h @@ -52,17 +52,17 @@ class nsSMILInterval { public: nsSMILInterval(); nsSMILInterval(const nsSMILInterval& aOther); ~nsSMILInterval(); void NotifyChanged(const nsSMILTimeContainer* aContainer); - void NotifyDeleting(); + void Unlink(PRBool aFiltered = PR_FALSE); const nsSMILInstanceTime* Begin() const { NS_ABORT_IF_FALSE(mBegin && mEnd, "Requesting Begin() on un-initialized instance time"); return mBegin; } nsSMILInstanceTime* Begin(); @@ -78,56 +78,46 @@ public: void SetBegin(nsSMILInstanceTime& aBegin); void SetEnd(nsSMILInstanceTime& aEnd); void Set(nsSMILInstanceTime& aBegin, nsSMILInstanceTime& aEnd) { SetBegin(aBegin); SetEnd(aEnd); } - void FreezeBegin() - { - NS_ABORT_IF_FALSE(mBegin && mEnd, - "Freezing Begin() on un-initialized instance time"); - mBegin->MarkNoLongerUpdating(); - } - - void FreezeEnd() - { - NS_ABORT_IF_FALSE(mBegin && mEnd, - "Freezing End() on un-initialized instance time"); - NS_ABORT_IF_FALSE(!mBegin->MayUpdate(), - "Freezing the end of an interval without a fixed begin"); - mEnd->MarkNoLongerUpdating(); - } - - // XXX Backwards seeking support (bug 492458) - void Unfreeze() - { - // XXX - UnfreezeEnd(); - } - - void UnfreezeEnd() - { - // XXX - } + void FixBegin(); + void FixEnd(); void AddDependentTime(nsSMILInstanceTime& aTime); void RemoveDependentTime(const nsSMILInstanceTime& aTime); + // Cue for assessing if this interval can be filtered + PRBool IsDependencyChainLink() const; + private: nsRefPtr<nsSMILInstanceTime> mBegin; nsRefPtr<nsSMILInstanceTime> mEnd; typedef nsTArray<nsRefPtr<nsSMILInstanceTime> > InstanceTimeList; // nsSMILInstanceTimes to notify when this interval is changed or deleted. InstanceTimeList mDependentTimes; + // Indicates if the end points of the interval are fixed or not. + // + // Note that this is not the same as having an end point whose TIME is fixed + // (i.e. nsSMILInstanceTime::IsFixed() returns PR_TRUE). This is because it is + // possible to have an end point with a fixed TIME and yet still update the + // end point to refer to a different nsSMILInstanceTime object. + // + // However, if mBegin/EndFixed is PR_TRUE, then BOTH the nsSMILInstanceTime + // OBJECT returned for that end point and its TIME value will not change. + PRPackedBool mBeginFixed; + PRPackedBool mEndFixed; + // When change notifications are passed around the timing model we try to // filter out all changes where there is no observable difference to an // instance time. Changes that may produce an observable difference are: // // * Changes to the time of an interval endpoint // * Changes in the relative times of different time containers // * Changes to the dependency chain (which may affect the animation sandwich) //
--- a/content/smil/nsSMILTimeContainer.cpp +++ b/content/smil/nsSMILTimeContainer.cpp @@ -41,16 +41,18 @@ nsSMILTimeContainer::nsSMILTimeContainer() : mParent(nsnull), mCurrentTime(0L), mParentOffset(0L), mPauseStart(0L), mNeedsPauseSample(PR_FALSE), + mNeedsRewind(PR_FALSE), + mIsSeeking(PR_FALSE), mPauseState(PAUSE_BEGIN) { } nsSMILTimeContainer::~nsSMILTimeContainer() { if (mParent) { mParent->RemoveChild(*this); @@ -140,29 +142,40 @@ nsSMILTimeContainer::GetCurrentTime() co return 0L; return mCurrentTime; } void nsSMILTimeContainer::SetCurrentTime(nsSMILTime aSeekTo) { + // SVG 1.1 doesn't specify what to do for negative times so we adopt SVGT1.2's + // behaviour of clamping negative times to 0. + aSeekTo = PR_MAX(0, aSeekTo); + // The following behaviour is consistent with: // http://www.w3.org/2003/01/REC-SVG11-20030114-errata // #getCurrentTime_setCurrentTime_undefined_before_document_timeline_begin // which says that if SetCurrentTime is called before the document timeline // has begun we should still adjust the offset. nsSMILTime parentTime = GetParentTime(); mParentOffset = parentTime - aSeekTo; + mIsSeeking = PR_TRUE; if (IsPaused()) { mNeedsPauseSample = PR_TRUE; mPauseStart = parentTime; } + if (aSeekTo < mCurrentTime) { + // Backwards seek + mNeedsRewind = PR_TRUE; + ClearMilestones(); + } + // Force an update to the current time in case we get a call to GetCurrentTime // before another call to Sample(). UpdateCurrentTime(); NotifyTimeChange(); } nsSMILTime
--- a/content/smil/nsSMILTimeContainer.h +++ b/content/smil/nsSMILTimeContainer.h @@ -166,16 +166,31 @@ public: * Return if this time container should be sampled or can be skipped. * * This is most useful as an optimisation for skipping time containers that * don't require a sample. */ PRBool NeedsSample() const { return !mPauseState || mNeedsPauseSample; } /* + * Indicates if the elements of this time container need to be rewound. + * This occurs during a backwards seek. + */ + PRBool NeedsRewind() const { return mNeedsRewind; } + void ClearNeedsRewind() { mNeedsRewind = PR_FALSE; } + + /* + * Indicates the time container is currently processing a SetCurrentTime + * request and appropriate seek behaviour should be applied by child elements + * (e.g. not firing time events). + */ + PRBool IsSeeking() const { return mIsSeeking; } + void MarkSeekFinished() { mIsSeeking = PR_FALSE; } + + /* * Sets the parent time container. * * The callee still retains ownership of the time container. */ nsresult SetParent(nsSMILTimeContainer* aParent); /* * Registers an element for a sample at the given time. @@ -274,16 +289,19 @@ protected: nsSMILTime mParentOffset; // The timestamp in parent time when the container was paused nsSMILTime mPauseStart; // Whether or not a pause sample is required PRPackedBool mNeedsPauseSample; + PRPackedBool mNeedsRewind; // Backwards seek performed + PRPackedBool mIsSeeking; // Currently in the middle of a seek operation + // A bitfield of the pause state for all pause requests PRUint32 mPauseState; struct MilestoneEntry { MilestoneEntry(nsSMILMilestone aMilestone, nsISMILAnimationElement& aElement) : mMilestone(aMilestone), mTimebase(&aElement)
--- a/content/smil/nsSMILTimeValueSpec.cpp +++ b/content/smil/nsSMILTimeValueSpec.cpp @@ -155,18 +155,18 @@ nsSMILTimeValueSpec::HandleNewInterval(n void nsSMILTimeValueSpec::HandleChangedInstanceTime( const nsSMILInstanceTime& aBaseTime, const nsSMILTimeContainer* aSrcContainer, nsSMILInstanceTime& aInstanceTimeToUpdate, PRBool aObjectChanged) { // If the instance time is fixed (e.g. because it's being used as the begin - // time of an active interval) we just ignore the change. - if (!aInstanceTimeToUpdate.MayUpdate()) + // time of an active or postactive interval) we just ignore the change. + if (aInstanceTimeToUpdate.IsFixedTime()) return; nsSMILTimeValue updatedTime = ConvertBetweenTimeContainers(aBaseTime.Time(), aSrcContainer); // Apply offset if (updatedTime.IsResolved()) { updatedTime.SetMillis(updatedTime.GetMillis() +
--- a/content/smil/nsSMILTimedElement.cpp +++ b/content/smil/nsSMILTimedElement.cpp @@ -86,16 +86,39 @@ nsSMILTimedElement::InstanceTimeComparat NS_ABORT_IF_FALSE(aElem1->Serial() && aElem2->Serial(), "Instance times have not been assigned serial numbers"); PRInt8 cmp = aElem1->Time().CompareTo(aElem2->Time()); return cmp == 0 ? aElem1->Serial() < aElem2->Serial() : cmp < 0; } //---------------------------------------------------------------------- +// Templated helper functions + +// Selectively remove elements from an array of type +// nsTArray<nsRefPtr<nsSMILInstanceTime> > with O(n) performance. +template <class TestFunctor> +void +nsSMILTimedElement::RemoveInstanceTimes(InstanceTimeList& aArray, + TestFunctor& aTest) +{ + InstanceTimeList newArray; + for (PRUint32 i = 0; i < aArray.Length(); ++i) { + nsSMILInstanceTime* item = aArray[i].get(); + if (aTest(item, i)) { + item->Unlink(); + } else { + newArray.AppendElement(item); + } + } + aArray.Clear(); + aArray.SwapElements(newArray); +} + +//---------------------------------------------------------------------- // Static members nsAttrValue::EnumTable nsSMILTimedElement::sFillModeTable[] = { {"remove", FILL_REMOVE}, {"freeze", FILL_FREEZE}, {nsnull, 0} }; @@ -103,31 +126,38 @@ nsAttrValue::EnumTable nsSMILTimedElemen {"always", RESTART_ALWAYS}, {"whenNotActive", RESTART_WHENNOTACTIVE}, {"never", RESTART_NEVER}, {nsnull, 0} }; const nsSMILMilestone nsSMILTimedElement::sMaxMilestone(LL_MAXINT, PR_FALSE); +// The thresholds at which point we start filtering intervals and instance times +// indiscriminately. +// See FilterIntervals and FilterInstanceTimes. +const PRUint8 nsSMILTimedElement::sMaxNumIntervals = 20; +const PRUint8 nsSMILTimedElement::sMaxNumInstanceTimes = 100; + //---------------------------------------------------------------------- // Ctor, dtor nsSMILTimedElement::nsSMILTimedElement() : mAnimationElement(nsnull), mFillMode(FILL_REMOVE), mRestartMode(RESTART_ALWAYS), mBeginSpecSet(PR_FALSE), mEndHasEventConditions(PR_FALSE), mInstanceSerialIndex(0), mClient(nsnull), mCurrentInterval(nsnull), mPrevRegisteredMilestone(sMaxMilestone), - mElementState(STATE_STARTUP) + mElementState(STATE_STARTUP), + mSeekState(SEEK_NOT_SEEKING) { mSimpleDur.SetIndefinite(); mMin.SetMillis(0L); mMax.SetIndefinite(); mTimeDependents.Init(); } nsSMILTimedElement::~nsSMILTimedElement() @@ -144,22 +174,22 @@ nsSMILTimedElement::~nsSMILTimedElement( mEndInstances[i]->Unlink(); } mEndInstances.Clear(); // Notify anyone listening to our intervals that they're gone // (We shouldn't get any callbacks from this because all our instance times // are now disassociated with any intervals) if (mCurrentInterval) { - mCurrentInterval->NotifyDeleting(); + mCurrentInterval->Unlink(); mCurrentInterval = nsnull; } for (PRInt32 i = mOldIntervals.Length() - 1; i >= 0; --i) { - mOldIntervals[i]->NotifyDeleting(); + mOldIntervals[i]->Unlink(); } mOldIntervals.Clear(); } void nsSMILTimedElement::SetAnimationElement(nsISMILAnimationElement* aElement) { NS_ABORT_IF_FALSE(aElement, "NULL owner element"); @@ -327,32 +357,43 @@ nsSMILTimedElement::RemoveInstanceTime(n PRBool found = #endif instanceList.RemoveElementSorted(aInstanceTime, InstanceTimeComparator()); NS_ABORT_IF_FALSE(found, "Couldn't find instance time to delete"); UpdateCurrentInterval(); } +namespace +{ + class RemoveByCreator + { + public: + RemoveByCreator(const nsSMILTimeValueSpec* aCreator) : mCreator(aCreator) + { } + + PRBool operator()(nsSMILInstanceTime* aInstanceTime, PRUint32 /*aIndex*/) + { + return aInstanceTime->GetCreator() == mCreator; + } + + private: + const nsSMILTimeValueSpec* mCreator; + }; +} + void nsSMILTimedElement::RemoveInstanceTimesForCreator( const nsSMILTimeValueSpec* aCreator, PRBool aIsBegin) { NS_ABORT_IF_FALSE(aCreator, "Creator not set"); + InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances; - - PRInt32 count = instances.Length(); - for (PRInt32 i = count - 1; i >= 0; --i) { - nsSMILInstanceTime* instance = instances[i].get(); - NS_ABORT_IF_FALSE(instance, "NULL instance in instances array"); - if (instance->GetCreator() == aCreator) { - instance->Unlink(); - instances.RemoveElementAt(i); - } - } + RemoveByCreator removeByCreator(aCreator); + RemoveInstanceTimes(instances, removeByCreator); UpdateCurrentInterval(); } void nsSMILTimedElement::SetTimeClient(nsSMILAnimationFunction* aClient) { // @@ -411,16 +452,26 @@ nsSMILTimedElement::DoSampleAt(nsSMILTim // the already active container. if (GetTimeContainer()->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN)) return; NS_ABORT_IF_FALSE(mElementState != STATE_STARTUP || aEndOnly, "Got a regular sample during startup state, expected an end sample" " instead"); + PRBool finishedSeek = PR_FALSE; + if (GetTimeContainer()->IsSeeking() && mSeekState == SEEK_NOT_SEEKING) { + mSeekState = mElementState == STATE_ACTIVE ? + SEEK_FORWARD_FROM_ACTIVE : + SEEK_FORWARD_FROM_INACTIVE; + } else if (mSeekState != SEEK_NOT_SEEKING && + !GetTimeContainer()->IsSeeking()) { + finishedSeek = PR_TRUE; + } + PRBool stateChanged; nsSMILTimeValue sampleTime(aContainerTime); do { #ifdef DEBUG // Check invariant if (mElementState == STATE_STARTUP || mElementState == STATE_POSTACTIVE) { NS_ABORT_IF_FALSE(!mCurrentInterval, @@ -440,31 +491,26 @@ nsSMILTimedElement::DoSampleAt(nsSMILTim nsSMILInterval firstInterval; mElementState = NS_SUCCEEDED(GetNextInterval(nsnull, nsnull, firstInterval)) ? STATE_WAITING : STATE_POSTACTIVE; stateChanged = PR_TRUE; if (mElementState == STATE_WAITING) { mCurrentInterval = new nsSMILInterval(firstInterval); - if (!mCurrentInterval) { - NS_WARNING("Failed to allocate memory for new interval"); - mElementState = STATE_POSTACTIVE; - } else { - NotifyNewInterval(); - } + NotifyNewInterval(); } } break; case STATE_WAITING: { if (mCurrentInterval->Begin()->Time() <= sampleTime) { mElementState = STATE_ACTIVE; - mCurrentInterval->FreezeBegin(); + mCurrentInterval->FixBegin(); if (HasPlayed()) { Reset(); // Apply restart behaviour } if (mClient) { mClient->Activate(mCurrentInterval->Begin()->Time().GetMillis()); } if (HasPlayed()) { // The call to Reset() may mean that the end point of our current @@ -478,50 +524,41 @@ nsSMILTimedElement::DoSampleAt(nsSMILTim } stateChanged = PR_TRUE; } } break; case STATE_ACTIVE: { - // Only apply an early end if we're not already ending. - if (mCurrentInterval->End()->Time() > sampleTime) { - nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(sampleTime); - if (earlyEnd) { - mCurrentInterval->SetEnd(*earlyEnd); - NotifyChangedInterval(); - } - } + ApplyEarlyEnd(sampleTime); if (mCurrentInterval->End()->Time() <= sampleTime) { nsSMILInterval newInterval; mElementState = NS_SUCCEEDED(GetNextInterval(mCurrentInterval, nsnull, newInterval)) ? STATE_WAITING : STATE_POSTACTIVE; if (mClient) { mClient->Inactivate(mFillMode == FILL_FREEZE); } - mCurrentInterval->FreezeEnd(); + mCurrentInterval->FixEnd(); mOldIntervals.AppendElement(mCurrentInterval.forget()); // We must update mOldIntervals before calling SampleFillValue SampleFillValue(); if (mElementState == STATE_WAITING) { mCurrentInterval = new nsSMILInterval(newInterval); - if (!mCurrentInterval) { - NS_WARNING("Failed to allocate memory for new interval"); - mElementState = STATE_POSTACTIVE; - } else { - NotifyNewInterval(); - } + NotifyNewInterval(); } + FilterHistory(); stateChanged = PR_TRUE; } else { nsSMILTime beginTime = mCurrentInterval->Begin()->Time().GetMillis(); + NS_ASSERTION(aContainerTime >= beginTime, + "Sample time should not precede current interval"); nsSMILTime activeTime = aContainerTime - beginTime; SampleSimpleTime(activeTime); } } break; case STATE_POSTACTIVE: break; @@ -531,16 +568,19 @@ nsSMILTimedElement::DoSampleAt(nsSMILTim // state. However, for end samples we only drive the state machine as far as // the waiting or postactive state because we don't want to commit to any new // interval (by transitioning to the active state) until all the end samples // have finished and we then have complete information about the available // instance times upon which to base our next interval. } while (stateChanged && (!aEndOnly || (mElementState != STATE_WAITING && mElementState != STATE_POSTACTIVE))); + if (finishedSeek) { + DoPostSeek(); + } RegisterMilestone(); } void nsSMILTimedElement::HandleContainerTimeChange() { // In future we could possibly introduce a separate change notice for time // container changes and only notify those dependents who live in other time @@ -548,44 +588,56 @@ nsSMILTimedElement::HandleContainerTimeC // the nsSMILTimeValueSpec we'll check if anything has changed and if not, we // won't go any further. if (mElementState == STATE_WAITING || mElementState == STATE_ACTIVE) { NotifyChangedInterval(); } } void -nsSMILTimedElement::Reset() +nsSMILTimedElement::Rewind() { - // SMIL 3.0 section 5.4.3, 'Resetting element state': - // Any instance times associated with past Event-values, Repeat-values, - // Accesskey-values or added via DOM method calls are removed from the - // dependent begin and end instance times lists. In effect, all events and - // DOM methods calls in the past are cleared. This does not apply to an - // instance time that defines the begin of the current interval. - PRInt32 count = mBeginInstances.Length(); - for (PRInt32 i = count - 1; i >= 0; --i) { - nsSMILInstanceTime* instance = mBeginInstances[i].get(); - NS_ABORT_IF_FALSE(instance, "NULL instance in begin instances array"); - if (instance->ClearOnReset() && - (!mCurrentInterval || instance != mCurrentInterval->Begin())) { - instance->Unlink(); - mBeginInstances.RemoveElementAt(i); - } + NS_ABORT_IF_FALSE(mAnimationElement, + "Got rewind request before being attached to an animation element"); + NS_ABORT_IF_FALSE(mSeekState == SEEK_NOT_SEEKING, + "Got rewind request whilst already seeking"); + + mSeekState = mElementState == STATE_ACTIVE ? + SEEK_BACKWARD_FROM_ACTIVE : + SEEK_BACKWARD_FROM_INACTIVE; + + // Set the STARTUP state first so that if we get any callbacks we won't waste + // time recalculating the current interval + mElementState = STATE_STARTUP; + + // Clear the intervals and instance times except those instance times we can't + // regenerate (DOM calls etc.) + RewindTiming(); + + UnsetBeginSpec(); + UnsetEndSpec(); + + if (mClient) { + mClient->Inactivate(PR_FALSE); } - count = mEndInstances.Length(); - for (PRInt32 j = count - 1; j >= 0; --j) { - nsSMILInstanceTime* instance = mEndInstances[j].get(); - NS_ABORT_IF_FALSE(instance, "NULL instance in end instances array"); - if (instance->ClearOnReset()) { - instance->Unlink(); - mEndInstances.RemoveElementAt(j); - } + if (mAnimationElement->HasAnimAttr(nsGkAtoms::begin)) { + nsAutoString attValue; + mAnimationElement->GetAnimAttr(nsGkAtoms::begin, attValue); + SetBeginSpec(attValue, &mAnimationElement->Content()); } + + if (mAnimationElement->HasAnimAttr(nsGkAtoms::end)) { + nsAutoString attValue; + mAnimationElement->GetAnimAttr(nsGkAtoms::end, attValue); + SetEndSpec(attValue, &mAnimationElement->Content()); + } + + mPrevRegisteredMilestone = sMaxMilestone; + RegisterMilestone(); } PRBool nsSMILTimedElement::SetAttr(nsIAtom* aAttribute, const nsAString& aValue, nsAttrValue& aResult, nsIContent* aContextNode, nsresult* aParseResult) { PRBool foundMatch = PR_TRUE; @@ -917,26 +969,25 @@ void nsSMILTimedElement::AddDependent(nsSMILTimeValueSpec& aDependent) { // There's probably no harm in attempting to register a dependent // nsSMILTimeValueSpec twice, but we're not expecting it to happen. NS_ABORT_IF_FALSE(!mTimeDependents.GetEntry(&aDependent), "nsSMILTimeValueSpec is already registered as a dependency"); mTimeDependents.PutEntry(&aDependent); - // Add old and current intervals + // Add current interval. We could add historical intervals too but that would + // cause unpredictable results since some intervals may have been filtered. + // SMIL doesn't say what to do here so for simplicity and consistency we + // simply add the current interval if there is one. // // It's not necessary to call SyncPauseTime since we're dealing with // historical instance times not newly added ones. - nsSMILTimeContainer* container = GetTimeContainer(); - for (PRUint32 i = 0; i < mOldIntervals.Length(); ++i) { - aDependent.HandleNewInterval(*mOldIntervals[i], container); - } if (mCurrentInterval) { - aDependent.HandleNewInterval(*mCurrentInterval, container); + aDependent.HandleNewInterval(*mCurrentInterval, GetTimeContainer()); } } void nsSMILTimedElement::RemoveDependent(nsSMILTimeValueSpec& aDependent) { mTimeDependents.RemoveEntry(&aDependent); } @@ -945,17 +996,17 @@ PRBool nsSMILTimedElement::IsTimeDependent(const nsSMILTimedElement& aOther) const { const nsSMILInstanceTime* thisBegin = GetEffectiveBeginInstance(); const nsSMILInstanceTime* otherBegin = aOther.GetEffectiveBeginInstance(); if (!thisBegin || !otherBegin) return PR_FALSE; - return thisBegin->IsDependent(*otherBegin); + return thisBegin->IsDependentOn(*otherBegin); } void nsSMILTimedElement::BindToTree(nsIContent* aContextNode) { // Resolve references to other parts of the tree PRUint32 count = mBeginSpecs.Length(); for (PRUint32 i = 0; i < count; ++i) { @@ -1049,33 +1100,307 @@ nsSMILTimedElement::SetBeginOrEndSpec(co ClearBeginOrEndSpecs(aIsBegin); } UpdateCurrentInterval(); return rv; } +namespace +{ + class RemoveNonDOM + { + public: + PRBool operator()(nsSMILInstanceTime* aInstanceTime, PRUint32 /*aIndex*/) + { + return !aInstanceTime->FromDOM(); + } + }; +} + void nsSMILTimedElement::ClearBeginOrEndSpecs(PRBool aIsBegin) { TimeValueSpecList& specs = aIsBegin ? mBeginSpecs : mEndSpecs; specs.Clear(); // Remove only those instance times generated by the attribute, not those from // DOM calls. InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances; - PRInt32 count = instances.Length(); - for (PRInt32 i = count - 1; i >= 0; --i) { - nsSMILInstanceTime* instance = instances[i].get(); - NS_ABORT_IF_FALSE(instance, "NULL instance in instances array"); - if (!instance->FromDOM()) { - instance->Unlink(); - instances.RemoveElementAt(i); + RemoveNonDOM removeNonDOM; + RemoveInstanceTimes(instances, removeNonDOM); +} + +void +nsSMILTimedElement::RewindTiming() +{ + RewindInstanceTimes(mBeginInstances); + RewindInstanceTimes(mEndInstances); + + if (mCurrentInterval) { + mCurrentInterval->Unlink(); + mCurrentInterval = nsnull; + } + + for (PRInt32 i = mOldIntervals.Length() - 1; i >= 0; --i) { + mOldIntervals[i]->Unlink(); + } + mOldIntervals.Clear(); +} + +namespace +{ + class RemoveNonDynamic + { + public: + PRBool operator()(nsSMILInstanceTime* aInstanceTime, PRUint32 /*aIndex*/) + { + // Generally dynamically-generated instance times (DOM calls, event-based + // times) are not associated with their creator nsSMILTimeValueSpec. + // If that ever changes though we'll need to make sure to disassociate + // them here otherwise they'll get removed when we clear the set of + // nsSMILTimeValueSpecs later on. + NS_ABORT_IF_FALSE(!aInstanceTime->IsDynamic() || + !aInstanceTime->GetCreator(), + "Instance time retained during rewind needs to be unlinked"); + return !aInstanceTime->IsDynamic(); + } + }; +} + +void +nsSMILTimedElement::RewindInstanceTimes(InstanceTimeList& aList) +{ + RemoveNonDynamic removeNonDynamic; + RemoveInstanceTimes(aList, removeNonDynamic); +} + +void +nsSMILTimedElement::ApplyEarlyEnd(const nsSMILTimeValue& aSampleTime) +{ + // This should only be called within DoSampleAt as a helper function + NS_ABORT_IF_FALSE(mElementState == STATE_ACTIVE, + "Unexpected state to try to apply an early end"); + + // Only apply an early end if we're not already ending. + if (mCurrentInterval->End()->Time() > aSampleTime) { + nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(aSampleTime); + if (earlyEnd) { + if (earlyEnd->IsDependent()) { + // Generate a new instance time for the early end since the + // existing instance time is part of some dependency chain that we + // don't want to participate in. + nsRefPtr<nsSMILInstanceTime> newEarlyEnd = + new nsSMILInstanceTime(earlyEnd->Time()); + mCurrentInterval->SetEnd(*newEarlyEnd); + } else { + mCurrentInterval->SetEnd(*earlyEnd); + } + NotifyChangedInterval(); + } + } +} + +namespace +{ + class RemoveReset + { + public: + RemoveReset(const nsSMILInstanceTime* aCurrentIntervalBegin) + : mCurrentIntervalBegin(aCurrentIntervalBegin) { } + PRBool operator()(nsSMILInstanceTime* aInstanceTime, PRUint32 /*aIndex*/) + { + // SMIL 3.0 section 5.4.3, 'Resetting element state': + // Any instance times associated with past Event-values, Repeat-values, + // Accesskey-values or added via DOM method calls are removed from the + // dependent begin and end instance times lists. In effect, all events + // and DOM methods calls in the past are cleared. This does not apply to + // an instance time that defines the begin of the current interval. + return aInstanceTime->IsDynamic() && + !aInstanceTime->ShouldPreserve() && + (!mCurrentIntervalBegin || aInstanceTime != mCurrentIntervalBegin); } + + private: + const nsSMILInstanceTime* mCurrentIntervalBegin; + }; +} + +void +nsSMILTimedElement::Reset() +{ + RemoveReset resetBegin(mCurrentInterval ? mCurrentInterval->Begin() : nsnull); + RemoveInstanceTimes(mBeginInstances, resetBegin); + + RemoveReset resetEnd(nsnull); + RemoveInstanceTimes(mEndInstances, resetEnd); +} + +void +nsSMILTimedElement::DoPostSeek() +{ + // XXX When implementing TimeEvents we'll need to compare mElementState with + // mSeekState and dispatch events as follows: + // ACTIVE->INACTIVE: End event + // INACTIVE->ACTIVE: Begin event + // ACTIVE->ACTIVE: Nothing (even if they're different intervals) + // INACTIVE->INACTIVE: Nothing (even if we've skipped intervals) + + // Finish backwards seek + if (mSeekState == SEEK_BACKWARD_FROM_INACTIVE || + mSeekState == SEEK_BACKWARD_FROM_ACTIVE) { + // Previously some dynamic instance times may have been marked to be + // preserved because they were endpoints of an historic interval (which may + // or may not have been filtered). Now that we've finished a seek we should + // clear that flag for those instance times whose intervals are no longer + // historic. + UnpreserveInstanceTimes(mBeginInstances); + UnpreserveInstanceTimes(mEndInstances); + + // Now that the times have been unmarked perform a reset. This might seem + // counter-intuitive when we're only doing a seek within an interval but + // SMIL seems to require this. SMIL 3.0, 'Hyperlinks and timing': + // Resolved end times associated with events, Repeat-values, + // Accesskey-values or added via DOM method calls are cleared when seeking + // to time earlier than the resolved end time. + Reset(); + UpdateCurrentInterval(); + } + + mSeekState = SEEK_NOT_SEEKING; +} + +void +nsSMILTimedElement::UnpreserveInstanceTimes(InstanceTimeList& aList) +{ + const nsSMILInterval* prevInterval = GetPreviousInterval(); + const nsSMILInstanceTime* cutoff = mCurrentInterval ? + mCurrentInterval->Begin() : + prevInterval ? prevInterval->Begin() : nsnull; + InstanceTimeComparator cmp; + PRUint32 count = aList.Length(); + for (PRUint32 i = 0; i < count; ++i) { + nsSMILInstanceTime* instance = aList[i].get(); + if (!cutoff || cmp.LessThan(cutoff, instance)) { + instance->UnmarkShouldPreserve(); + } + } +} + +void +nsSMILTimedElement::FilterHistory() +{ + // We should filter the intervals first, since instance times still used in an + // interval won't be filtered. + FilterIntervals(); + FilterInstanceTimes(mBeginInstances); + FilterInstanceTimes(mEndInstances); +} + +void +nsSMILTimedElement::FilterIntervals() +{ + // We can filter old intervals that: + // + // a) are not the previous interval; AND + // b) are not in the middle of a dependency chain + // + // Condition (a) is necessary since the previous interval is used for applying + // fill effects and updating the current interval. + // + // Condition (b) is necessary since even if this interval itself is not + // active, it may be part of a dependency chain that includes active + // intervals. Such chains are used to establish priorities within the + // animation sandwich. + // + // Although the above conditions allow us to safely filter intervals for most + // scenarios they do not cover all cases and there will still be scenarios + // that generate intervals indefinitely. In such a case we simply set + // a maximum number of intervals and drop any intervals beyond that threshold. + + PRUint32 threshold = mOldIntervals.Length() > sMaxNumIntervals ? + mOldIntervals.Length() - sMaxNumIntervals : + 0; + IntervalList filteredList; + for (PRUint32 i = 0; i < mOldIntervals.Length(); ++i) + { + nsSMILInterval* interval = mOldIntervals[i].get(); + if (i + 1 < mOldIntervals.Length() /*skip previous interval*/ && + (i < threshold || !interval->IsDependencyChainLink())) { + interval->Unlink(PR_TRUE /*filtered, not deleted*/); + } else { + filteredList.AppendElement(mOldIntervals[i].forget()); + } + } + mOldIntervals.Clear(); + mOldIntervals.SwapElements(filteredList); +} + +namespace +{ + class RemoveFiltered + { + public: + RemoveFiltered(nsSMILTimeValue aCutoff) : mCutoff(aCutoff) { } + PRBool operator()(nsSMILInstanceTime* aInstanceTime, PRUint32 /*aIndex*/) + { + // We can filter instance times that: + // a) Precede the end point of the previous interval; AND + // b) Are NOT syncbase times that might be updated to a time after the end + // point of the previous interval; AND + // c) Are NOT fixed end points in any remaining interval. + return aInstanceTime->Time() < mCutoff && + aInstanceTime->IsFixedTime() && + !aInstanceTime->ShouldPreserve(); + } + + private: + nsSMILTimeValue mCutoff; + }; + + class RemoveBelowThreshold + { + public: + RemoveBelowThreshold(PRUint32 aThreshold, + const nsSMILInstanceTime* aCurrentIntervalBegin) + : mThreshold(aThreshold), + mCurrentIntervalBegin(aCurrentIntervalBegin) { } + PRBool operator()(nsSMILInstanceTime* aInstanceTime, PRUint32 aIndex) + { + return aInstanceTime != mCurrentIntervalBegin && aIndex < mThreshold; + } + + private: + PRUint32 mThreshold; + const nsSMILInstanceTime* mCurrentIntervalBegin; + }; +} + +void +nsSMILTimedElement::FilterInstanceTimes(InstanceTimeList& aList) +{ + if (GetPreviousInterval()) { + RemoveFiltered removeFiltered(GetPreviousInterval()->End()->Time()); + RemoveInstanceTimes(aList, removeFiltered); + } + + // As with intervals it is possible to create a document that, even despite + // our most aggressive filtering, will generate instance times indefinitely + // (e.g. cyclic dependencies with TimeEvents---we can't filter such times as + // they're unpredictable due to the possibility of seeking the document which + // may prevent some events from being generated). Therefore we introduce + // a hard cutoff at which point we just drop the oldest instance times. + if (aList.Length() > sMaxNumInstanceTimes) { + PRUint32 threshold = aList.Length() - sMaxNumInstanceTimes; + // We should still preserve the current interval begin time however + const nsSMILInstanceTime* currentIntervalBegin = mCurrentInterval ? + mCurrentInterval->Begin() : nsnull; + RemoveBelowThreshold removeBelowThreshold(threshold, currentIntervalBegin); + RemoveInstanceTimes(aList, removeBelowThreshold); } } // // This method is based on the pseudocode given in the SMILANIM spec. // // See: // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#Timing-BeginEnd-LC-Start @@ -1114,18 +1439,16 @@ nsSMILTimedElement::GetNextInterval(cons // Calculate begin time if (aFixedBeginTime) { if (aFixedBeginTime->Time() < beginAfter) return NS_ERROR_FAILURE; // our ref-counting is not const-correct tempBegin = const_cast<nsSMILInstanceTime*>(aFixedBeginTime); } else if (!mBeginSpecSet && beginAfter <= zeroTime) { tempBegin = new nsSMILInstanceTime(nsSMILTimeValue(0)); - if (!tempBegin) - return NS_ERROR_OUT_OF_MEMORY; } else { PRInt32 beginPos = 0; tempBegin = GetNextGreaterOrEqual(mBeginInstances, beginAfter, beginPos); if (!tempBegin || !tempBegin->Time().IsResolved()) return NS_ERROR_FAILURE; } NS_ABORT_IF_FALSE(tempBegin && tempBegin->Time().IsResolved() && tempBegin->Time() >= beginAfter, @@ -1161,18 +1484,16 @@ nsSMILTimedElement::GetNextInterval(cons nsSMILTimeValue intervalEnd = tempEnd ? tempEnd->Time() : nsSMILTimeValue(); nsSMILTimeValue activeEnd = CalcActiveEnd(tempBegin->Time(), intervalEnd); if (!tempEnd || intervalEnd != activeEnd) { tempEnd = new nsSMILInstanceTime(activeEnd); } - if (!tempEnd) - return NS_ERROR_OUT_OF_MEMORY; } NS_ABORT_IF_FALSE(tempEnd, "Failed to get end point for next interval"); // If we get two zero-length intervals in a row we will potentially have an // infinite loop so we break it here by searching for the next begin time // greater than tempEnd on the next time around. if (tempEnd->Time().IsResolved() && tempBegin->Time() == tempEnd->Time()) { if (prevIntervalWasZeroDur) { @@ -1328,16 +1649,19 @@ nsSMILTimedElement::ApplyMinAndMax(const nsSMILTime nsSMILTimedElement::ActiveTimeToSimpleTime(nsSMILTime aActiveTime, PRUint32& aRepeatIteration) { nsSMILTime result; NS_ASSERTION(mSimpleDur.IsResolved() || mSimpleDur.IsIndefinite(), "Unresolved simple duration in ActiveTimeToSimpleTime"); + NS_ASSERTION(aActiveTime >= 0, "Expecting non-negative active time"); + // Note that a negative aActiveTime will give us a negative value for + // aRepeatIteration, which is bad because aRepeatIteration is unsigned if (mSimpleDur.IsIndefinite() || mSimpleDur.GetMillis() == 0L) { aRepeatIteration = 0; result = aActiveTime; } else { result = aActiveTime % mSimpleDur.GetMillis(); aRepeatIteration = (PRUint32)(aActiveTime / mSimpleDur.GetMillis()); } @@ -1404,20 +1728,16 @@ nsSMILTimedElement::UpdateCurrentInterva if (NS_SUCCEEDED(rv)) { if (mElementState == STATE_POSTACTIVE) { NS_ABORT_IF_FALSE(!mCurrentInterval, "In postactive state but the interval has been set"); mCurrentInterval = new nsSMILInterval(updatedInterval); - if (!mCurrentInterval) { - NS_WARNING("Failed to allocate memory for new interval."); - return; - } mElementState = STATE_WAITING; NotifyNewInterval(); } else { PRBool changed = PR_FALSE; if (mElementState != STATE_ACTIVE && @@ -1445,17 +1765,17 @@ nsSMILTimedElement::UpdateCurrentInterva // Only apply a fill if it was already being applied before the (now // deleted) interval was created PRBool applyFill = HasPlayed() && mFillMode == FILL_FREEZE; mClient->Inactivate(applyFill); } if (mElementState == STATE_ACTIVE || mElementState == STATE_WAITING) { mElementState = STATE_POSTACTIVE; - mCurrentInterval->NotifyDeleting(); + mCurrentInterval->Unlink(); mCurrentInterval = nsnull; } } } void nsSMILTimedElement::SampleSimpleTime(nsSMILTime aActiveTime) { @@ -1472,19 +1792,19 @@ nsSMILTimedElement::SampleFillValue() { if (mFillMode != FILL_FREEZE || !mClient) return; const nsSMILInterval* prevInterval = GetPreviousInterval(); NS_ABORT_IF_FALSE(prevInterval, "Attempting to sample fill value but there is no previous interval"); NS_ABORT_IF_FALSE(prevInterval->End()->Time().IsResolved() && - !prevInterval->End()->MayUpdate(), + prevInterval->End()->IsFixedTime(), "Attempting to sample fill value but the endpoint of the previous " - "interval is not resolved and frozen"); + "interval is not resolved and fixed"); nsSMILTime activeTime = prevInterval->End()->Time().GetMillis() - prevInterval->Begin()->Time().GetMillis(); PRUint32 repeatIteration; nsSMILTime simpleTime = ActiveTimeToSimpleTime(activeTime, repeatIteration); @@ -1503,20 +1823,16 @@ nsSMILTimedElement::AddInstanceTimeFromC nsSMILTime timeWithOffset = aCurrentTime + PRInt64(NS_round(offset)); nsSMILTimeValue timeVal(timeWithOffset); // XXX If we re-use this method for event-based timing we'll need to change it // so we don't end up setting SOURCE_DOM for event-based times. nsRefPtr<nsSMILInstanceTime> instanceTime = new nsSMILInstanceTime(timeVal, nsSMILInstanceTime::SOURCE_DOM); - if (!instanceTime) { - NS_WARNING("Insufficient memory to create instance time"); - return; - } AddInstanceTime(instanceTime, aIsBegin); } void nsSMILTimedElement::RegisterMilestone() { nsSMILTimeContainer* container = GetTimeContainer();
--- a/content/smil/nsSMILTimedElement.h +++ b/content/smil/nsSMILTimedElement.h @@ -224,20 +224,24 @@ public: * Informs the timed element that its time container has changed time * relative to document time. The timed element therefore needs to update its * dependent elements (which may belong to a different time container) so they * can re-resolve their times. */ void HandleContainerTimeChange(); /** - * Reset the element's internal state. As described in SMILANIM 3.3.7, all - * instance times associated with DOM calls, events, etc. are cleared. + * Resets this timed element's accumulated times and intervals back to start + * up state. + * + * This is used for backwards seeking where rather than accumulating + * historical timing state and winding it back, we reset the element and seek + * forwards. */ - void Reset(); + void Rewind(); /** * Attempts to set an attribute on this timed element. * * @param aAttribute The name of the attribute to set. The namespace of this * attribute is not specified as it is checked by the host * element. Only attributes in the namespace defined for * SMIL attributes in the host language are passed to the @@ -340,16 +344,20 @@ protected: const nsSMILInstanceTime* aElem2) const; }; struct NotifyTimeDependentsParams { nsSMILInterval* mCurrentInterval; nsSMILTimeContainer* mTimeContainer; }; + // Templated helper functions + template <class TestFunctor> + void RemoveInstanceTimes(InstanceTimeList& aArray, TestFunctor& aTest); + // // Implementation helpers // nsresult SetBeginSpec(const nsAString& aBeginSpec, nsIContent* aContextNode); nsresult SetEndSpec(const nsAString& aEndSpec, nsIContent* aContextNode); @@ -370,19 +378,69 @@ protected: void UnsetRepeatCount(); void UnsetRepeatDur(); void UnsetFillMode(); nsresult SetBeginOrEndSpec(const nsAString& aSpec, nsIContent* aContextNode, PRBool aIsBegin); void ClearBeginOrEndSpecs(PRBool aIsBegin); + void RewindTiming(); + void RewindInstanceTimes(InstanceTimeList& aList); void DoSampleAt(nsSMILTime aContainerTime, PRBool aEndOnly); /** + * Helper function to check for an early end and, if necessary, update the + * current interval accordingly. + * + * See SMIL 3.0, section 5.4.5, Element life cycle, "Active Time - Playing an + * interval" for a description of ending early. + * + * @param aSampleTime The current sample time. Early ends should only be + * applied at the last possible moment (i.e. if they are at + * or before the current sample time) and only if the + * current interval is not already ending. + */ + void ApplyEarlyEnd(const nsSMILTimeValue& aSampleTime); + + /** + * Clears certain state in response to the element restarting. + * + * This state is described in SMIL 3.0, section 5.4.3, Resetting element state + */ + void Reset(); + + /** + * Completes a seek operation by sending appropriate events and, in the case + * of a backwards seek, updating the state of timing information that was + * previously considered historical. + */ + void DoPostSeek(); + + /** + * Unmarks instance times that were previously preserved because they were + * considered important historical milestones but are no longer such because + * a backwards seek has been performed. + */ + void UnpreserveInstanceTimes(InstanceTimeList& aList); + + /** + * Helper function to iterate through this element's accumulated timing + * information (specifically old nsSMILIntervals and nsSMILTimeInstanceTimes) + * and discard items that are no longer needed or exceed some threshold of + * accumulated state. + */ + void FilterHistory(); + + // Helper functions for FilterHistory to clear old nsSMILIntervals and + // nsSMILInstanceTimes respectively. + void FilterIntervals(); + void FilterInstanceTimes(InstanceTimeList& aList); + + /** * Calculates the next acceptable interval for this element after the * specified interval, or, if no previous interval is specified, it will be * the first interval with an end time after t=0. * * @see SMILANIM 3.6.8 * * @param aPrevInterval The previous interval used. If supplied, the first * interval that begins after aPrevInterval will be @@ -478,16 +536,18 @@ protected: InstanceTimeList mEndInstances; PRUint32 mInstanceSerialIndex; nsSMILAnimationFunction* mClient; nsAutoPtr<nsSMILInterval> mCurrentInterval; IntervalList mOldIntervals; nsSMILMilestone mPrevRegisteredMilestone; static const nsSMILMilestone sMaxMilestone; + static const PRUint8 sMaxNumIntervals; + static const PRUint8 sMaxNumInstanceTimes; // Set of dependent time value specs to be notified when establishing a new // current interval. Change notifications and delete notifications are handled // by the interval. // // [weak] The nsSMILTimeValueSpec objects register themselves and unregister // on destruction. Likewise, we notify them when we are destroyed. TimeValueSpecHashSet mTimeDependents; @@ -499,11 +559,21 @@ protected: enum nsSMILElementState { STATE_STARTUP, STATE_WAITING, STATE_ACTIVE, STATE_POSTACTIVE }; nsSMILElementState mElementState; + + enum nsSMILSeekState + { + SEEK_NOT_SEEKING, + SEEK_FORWARD_FROM_ACTIVE, + SEEK_FORWARD_FROM_INACTIVE, + SEEK_BACKWARD_FROM_ACTIVE, + SEEK_BACKWARD_FROM_INACTIVE + }; + nsSMILSeekState mSeekState; }; #endif // NS_SMILTIMEDELEMENT_H_
--- a/content/smil/test/Makefile.in +++ b/content/smil/test/Makefile.in @@ -52,16 +52,17 @@ include $(topsrcdir)/config/rules.mk db_smilCSSPropertyList.js \ db_smilMappedAttrList.js \ smilAnimateMotionValueLists.js \ smilTestUtils.js \ smilXHR_helper.svg \ test_smilAnimateMotion.xhtml \ test_smilAnimateMotionInvalidValues.xhtml \ test_smilAnimateMotionOverrideRules.xhtml \ + test_smilBackwardsSeeking.xhtml \ test_smilChangeAfterFrozen.xhtml \ test_smilContainerBinding.xhtml \ test_smilCrossContainer.xhtml \ test_smilCSSFontStretchRelative.xhtml \ test_smilCSSFromBy.xhtml \ test_smilCSSFromTo.xhtml \ test_smilCSSInherit.xhtml \ test_smilCSSInvalidValues.xhtml \
new file mode 100644 --- /dev/null +++ b/content/smil/test/test_smilBackwardsSeeking.xhtml @@ -0,0 +1,191 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for backwards seeking behavior </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> +<p id="display"></p> +<div id="content" style="display: none"> +<svg id="svg" xmlns="http://www.w3.org/2000/svg" /> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for backwards seeking behavior **/ + +var gSvg = document.getElementById("svg"); + +SimpleTest.waitForExplicitFinish(); + +function main() +{ + // Pause our document, so that the setCurrentTime calls are the only + // thing affecting document time + gSvg.pauseAnimations(); + + // We define a series of scenarios, sample times, and expected return values + // from getStartTime. + // + // Each scenario is basically a variation on the following arrangement: + // + // <svg> + // <set ... dur="1s" begin="<A-BEGIN>"/> + // <set ... dur="1s" begin="<B-BEGIN>"/> + // </svg> + // + // Each test then consists of the following: + // animA: attributes to be applied to a + // animB: attributes to be applied to b + // times: a series of triples which consist of: + // <sample time, a's expected start time, b's expected start time> + // * The sample time is the time passed to setCurrentTime and so is + // in seconds. + // * The expected start times are compared with the return value of + // getStartTime. To check for an unresolved start time where + // getStartTime would normally throw an exception use + // 'unresolved'. + // * We also allow the special notation to indicate a call to + // beginElement + // <'beginElementAt', id of animation element, offset> + // + // In the diagrams below '^' means the time before the seek and '*' is the + // seek time. + var testCases = Array(); + + // 0: Simple case + // + // A: +------- + // B: +------- begin: a.begin + // * ^ + testCases[0] = { + 'animA': {'begin':'1s', 'id':'a'}, + 'animB': {'begin':'a.begin'}, + 'times': [ [0, 1, 1], + [1, 1, 1], + [2, 'unresolved', 'unresolved'], + [0, 1, 1], + [1.5, 1, 1], + [1, 1, 1], + [2, 'unresolved', 'unresolved'] ] + }; + + // 1: Restored times should be live + // + // When we restore times they should be live. So we have the following + // scenario. + // + // A: +------- + // B: +------- begin: a.begin + // * ^ + // + // Then we call beginElement at an earlier time which should give us the + // following. + // + // A: +------- + // B: +------- + // * ^ + // + // If the times are not live however we'll end up with this + // + // A: +------- + // B: +-+------- + // * ^ + testCases[1] = { + 'animA': {'begin':'1s', 'id':'a', 'restart':'whenNotActive'}, + 'animB': {'begin':'a.begin', 'restart':'always'}, + 'times': [ [0, 1, 1], + [2, 'unresolved', 'unresolved'], + [0.25, 1, 1], + ['beginElementAt', 'a', 0.25], // = start time of 0.5 + [0.25, 0.5, 0.5], + [1, 0.5, 0.5], + [1.5, 'unresolved', 'unresolved'] ] + }; + + // 2: Multiple intervals A + // + // A: +- +- + // B: +- +- begin: a.begin+4s + // * ^ + testCases[2] = { + 'animA': {'begin':'1s; 3s', 'id':'a'}, + 'animB': {'begin':'a.begin+4s'}, + 'times': [ [0, 1, 5], + [3, 3, 5], + [6.5, 'unresolved', 7], + [4, 'unresolved', 5], + [6, 'unresolved', 7], + [2, 3, 5], + ['beginElementAt', 'a', 0], + [2, 2, 5], + [5, 'unresolved', 5], + [6, 'unresolved', 6], + [7, 'unresolved', 7], + [8, 'unresolved', 'unresolved'] ] + }; + + for (var i = 0; i < testCases.length; i++) { + gSvg.setCurrentTime(0); + var test = testCases[i]; + + // Create animation elements + var animA = createAnim(test.animA); + var animB = createAnim(test.animB); + + // Run samples + for (var j = 0; j < test.times.length; j++) { + var times = test.times[j]; + if (times[0] == 'beginElementAt') { + var anim = getElement(times[1]); + anim.beginElementAt(times[2]); + } else { + gSvg.setCurrentTime(times[0]); + checkStartTime(animA, times[1], times[0], i, 'a'); + checkStartTime(animB, times[2], times[0], i, 'b'); + } + } + + // Tidy up + removeElement(animA); + removeElement(animB); + } + + SimpleTest.finish(); +} + +function createAnim(attr) +{ + const svgns = "http://www.w3.org/2000/svg"; + var anim = document.createElementNS(svgns, 'set'); + anim.setAttribute('attributeName','x'); + anim.setAttribute('to','10'); + anim.setAttribute('dur','1s'); + for (name in attr) { + anim.setAttribute(name, attr[name]); + } + return gSvg.appendChild(anim); +} + +function checkStartTime(anim, expectedStartTime, sampleTime, caseNum, id) +{ + var startTime = 'unresolved'; + try { + startTime = anim.getStartTime(); + } catch (e) { + if (e.code != DOMException.INVALID_STATE_ERR) + throw e; + } + + var msg = "Test case " + caseNum + ", t=" + sampleTime + " animation '" + + id + "': Unexpected getStartTime:"; + is(startTime, expectedStartTime, msg); +} + +window.addEventListener("load", main, false); +]]> +</script> +</pre> +</body> +</html>
--- a/content/smil/test/test_smilContainerBinding.xhtml +++ b/content/smil/test/test_smilContainerBinding.xhtml @@ -55,21 +55,21 @@ function main() { svg.setCurrentTime(1); is(anim.getStartTime(), 2); // Unbind removeElement(anim); is(anim.getStartTime(), 6); // Rebind - // At this point all the old intervals should be re-added to anim. If they're - // not and only the current interval is added to anim we'll get a start time - // of 4s instead of 2s. + // At this point only the current interval will be re-added to anim (this is + // for consistency since old intervals may or may not have been filtered). + // Therefore the start time should be 4s instead of 2s. circle.appendChild(anim); - is(anim.getStartTime(), 2); + is(anim.getStartTime(), 4); SimpleTest.finish(); } function createAnim() { const svgns="http://www.w3.org/2000/svg"; var anim = document.createElementNS(svgns,'set'); anim.setAttribute('attributeName','cx');
--- a/content/smil/test/test_smilSetCurrentTime.xhtml +++ b/content/smil/test/test_smilSetCurrentTime.xhtml @@ -26,17 +26,18 @@ SimpleTest.waitForExplicitFinish(); function main() { ok(gSvg.animationsPaused(), "should be paused by <svg> load handler"); is(gSvg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler"); // Test that seeking takes effect immediately for (var i = 0; i < gTimes.length; i++) { gSvg.setCurrentTime(gTimes[i]); - assertFloatsEqual(gSvg.getCurrentTime(), gTimes[i]); + // We adopt the SVGT1.2 behavior of clamping negative times to 0 + assertFloatsEqual(gSvg.getCurrentTime(), Math.max(gTimes[i], 0.0)); } // Test that seeking isn't messed up by timeouts // (using tail recursion to set up the chain of timeout function calls) var func = function() { checkTimesAfterIndex(0); } setTimeout(func, gWaitTime); @@ -51,17 +52,17 @@ function checkTimesAfterIndex(index) { if (index == gTimes.length) { // base case -- we're done! SimpleTest.finish(); return; } gSvg.setCurrentTime(gTimes[index]); var func = function() { - assertFloatsEqual(gSvg.getCurrentTime(), gTimes[index]); + assertFloatsEqual(gSvg.getCurrentTime(), Math.max(gTimes[index], 0.0)); checkTimesAfterIndex(index + 1); } setTimeout(func, gWaitTime); } function assertFloatsEqual(aVal, aExpected) { ok(Math.abs(aVal - aExpected) <= PRECISION_LEVEL, "getCurrentTime returned " + aVal + " after seeking to " + aExpected)
--- a/content/svg/content/src/nsSVGElement.cpp +++ b/content/svg/content/src/nsSVGElement.cpp @@ -1948,23 +1948,27 @@ nsSVGElement::GetAnimatedAttr(nsIAtom* a if (!transformable) return nsnull; nsresult rv = transformable->GetTransform(getter_AddRefs(transformList)); NS_ENSURE_SUCCESS(rv, nsnull); } if (aName == nsGkAtoms::gradientTransform) { nsCOMPtr<nsIDOMSVGGradientElement> gradientElement( do_QueryInterface(static_cast<nsIContent*>(this))); + if (!gradientElement) + return nsnull; nsresult rv = gradientElement->GetGradientTransform(getter_AddRefs(transformList)); NS_ENSURE_SUCCESS(rv, nsnull); } if (aName == nsGkAtoms::patternTransform) { nsCOMPtr<nsIDOMSVGPatternElement> patternElement( do_QueryInterface(static_cast<nsIContent*>(this))); + if (!patternElement) + return nsnull; nsresult rv = patternElement->GetPatternTransform(getter_AddRefs(transformList)); NS_ENSURE_SUCCESS(rv, nsnull); } if (transformList) { nsSVGAnimatedTransformList* list = static_cast<nsSVGAnimatedTransformList*>(transformList.get()); NS_ENSURE_TRUE(list, nsnull);
--- a/content/svg/content/src/nsSVGPointList.cpp +++ b/content/svg/content/src/nsSVGPointList.cpp @@ -164,49 +164,59 @@ NS_INTERFACE_MAP_END NS_IMETHODIMP nsSVGPointList::SetValueString(const nsAString& aValue) { nsCharSeparatedTokenizer tokenizer(aValue, ',', nsCharSeparatedTokenizer::SEPARATOR_OPTIONAL); nsCOMArray<nsIDOMSVGPoint> points; + PRBool parseError = PR_FALSE; + while (tokenizer.hasMoreTokens()) { // Parse 2 tokens NS_ConvertUTF16toUTF8 utf8String1(tokenizer.nextToken()); const char *token1 = utf8String1.get(); if (!tokenizer.hasMoreTokens() || // No 2nd token. *token1 == '\0') { // 1st token is empty string. - return NS_ERROR_DOM_SYNTAX_ERR; + parseError = PR_TRUE; + break; } NS_ConvertUTF16toUTF8 utf8String2(tokenizer.nextToken()); const char *token2 = utf8String2.get(); if (*token2 == '\0') { // 2nd token is empty string. - return NS_ERROR_DOM_SYNTAX_ERR; + parseError = PR_TRUE; + break; } // Convert parsed tokens to float values. char *end; float x = float(PR_strtod(token1, &end)); if (*end != '\0' || !NS_FloatIsFinite(x)) { - return NS_ERROR_DOM_SYNTAX_ERR; + parseError = PR_TRUE; + break; } float y = float(PR_strtod(token2, &end)); if (*end != '\0' || !NS_FloatIsFinite(y)) { - return NS_ERROR_DOM_SYNTAX_ERR; + parseError = PR_TRUE; + break; } // Build a point from our parsed float values. nsCOMPtr<nsIDOMSVGPoint> point; NS_NewSVGPoint(getter_AddRefs(point), x, y); // uses infallible 'new'. points.AppendObject(point); } if (tokenizer.lastTokenEndedWithSeparator()) { // Reject trailing comma - return NS_ERROR_DOM_SYNTAX_ERR; + parseError = PR_TRUE; + } + + if (parseError) { + // XXX nsSVGUtils::ReportToConsole() } WillModify(); ReleasePoints(); PRInt32 count = points.Count(); for (PRInt32 i = 0; i < count; ++i) { AppendElement(points.ObjectAt(i)); }
--- a/content/xbl/src/nsBindingManager.cpp +++ b/content/xbl/src/nsBindingManager.cpp @@ -1,9 +1,10 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=79: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * @@ -622,16 +623,19 @@ nsBindingManager::SetWrappedJS(nsIConten } void nsBindingManager::RemovedFromDocumentInternal(nsIContent* aContent, nsIDocument* aOldDocument) { NS_PRECONDITION(aOldDocument != nsnull, "no old document"); + if (mDestroyed) + return; + // Hold a ref to the binding so it won't die when we remove it from our // table. nsRefPtr<nsXBLBinding> binding = GetBinding(aContent); if (aContent->HasFlag(NODE_IS_INSERTION_PARENT)) { nsRefPtr<nsXBLBinding> parentBinding = GetBinding(aContent->GetBindingParent()); if (parentBinding) { parentBinding->RemoveInsertionParent(aContent); // Clear insertion parent only if we don't have a binding which @@ -1645,16 +1649,18 @@ nsBindingManager::ContentRemoved(nsIDocu } } } } void nsBindingManager::DropDocumentReference() { + mDestroyed = PR_TRUE; + // Make sure to not run any more XBL constructors mProcessingAttachedStack = PR_TRUE; if (mProcessAttachedQueueEvent) { mProcessAttachedQueueEvent->Revoke(); } if (mContentListTable.ops) PL_DHashTableFinish(&(mContentListTable)); @@ -1663,16 +1669,19 @@ nsBindingManager::DropDocumentReference( if (mAnonymousNodesTable.ops) PL_DHashTableFinish(&(mAnonymousNodesTable)); mAnonymousNodesTable.ops = nsnull; if (mInsertionParentTable.ops) PL_DHashTableFinish(&(mInsertionParentTable)); mInsertionParentTable.ops = nsnull; + if (mBindingTable.IsInitialized()) + mBindingTable.Clear(); + mDocument = nsnull; } void nsBindingManager::Traverse(nsIContent *aContent, nsCycleCollectionTraversalCallback &cb) { if (!aContent->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
--- a/content/xbl/src/nsXBLContentSink.cpp +++ b/content/xbl/src/nsXBLContentSink.cpp @@ -66,18 +66,17 @@ using namespace mozilla::dom; nsresult NS_NewXBLContentSink(nsIXMLContentSink** aResult, nsIDocument* aDoc, nsIURI* aURI, nsISupports* aContainer) { NS_ENSURE_ARG_POINTER(aResult); - nsXBLContentSink* it; - NS_NEWXPCOM(it, nsXBLContentSink); + nsXBLContentSink* it = new nsXBLContentSink(); NS_ENSURE_TRUE(it, NS_ERROR_OUT_OF_MEMORY); nsCOMPtr<nsIXMLContentSink> kungFuDeathGrip = it; nsresult rv = it->Init(aDoc, aURI, aContainer); NS_ENSURE_SUCCESS(rv, rv); return CallQueryInterface(it, aResult); }
--- a/content/xbl/src/nsXBLDocumentInfo.cpp +++ b/content/xbl/src/nsXBLDocumentInfo.cpp @@ -258,17 +258,17 @@ nsXBLDocGlobalObject::SetContext(nsIScri NS_ASSERTION(aScriptContext->GetScriptTypeID() == nsIProgrammingLanguage::JAVASCRIPT, "xbl is not multi-language"); aScriptContext->WillInitializeContext(); // NOTE: We init this context with a NULL global, so we automatically // hook up to the existing nsIScriptGlobalObject global setup by // nsGlobalWindow. nsresult rv; - rv = aScriptContext->InitContext(nsnull); + rv = aScriptContext->InitContext(); NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Script Language's InitContext failed"); aScriptContext->SetGCOnDestruction(PR_FALSE); aScriptContext->DidInitializeContext(); // and we set up our global manually mScriptContext = aScriptContext; } nsresult
--- a/content/xml/document/src/nsXMLContentSink.cpp +++ b/content/xml/document/src/nsXMLContentSink.cpp @@ -119,18 +119,17 @@ NS_NewXMLContentSink(nsIXMLContentSink** nsIURI* aURI, nsISupports* aContainer, nsIChannel* aChannel) { NS_PRECONDITION(nsnull != aResult, "null ptr"); if (nsnull == aResult) { return NS_ERROR_NULL_POINTER; } - nsXMLContentSink* it; - NS_NEWXPCOM(it, nsXMLContentSink); + nsXMLContentSink* it = new nsXMLContentSink(); if (nsnull == it) { return NS_ERROR_OUT_OF_MEMORY; } nsCOMPtr<nsIXMLContentSink> kungFuDeathGrip = it; nsresult rv = it->Init(aDoc, aURI, aContainer, aChannel); NS_ENSURE_SUCCESS(rv, rv);
--- a/content/xul/document/src/nsXULPrototypeDocument.cpp +++ b/content/xul/document/src/nsXULPrototypeDocument.cpp @@ -679,17 +679,17 @@ nsXULPDGlobalObject::SetScriptContext(PR if (!aScriptContext) NS_WARNING("Possibly early removal of script object, see bug #41608"); else { // should probably assert the context is clean??? aScriptContext->WillInitializeContext(); // NOTE: We init this context with a NULL global - this is subtly // different than nsGlobalWindow which passes 'this' - rv = aScriptContext->InitContext(nsnull); + rv = aScriptContext->InitContext(); NS_ENSURE_SUCCESS(rv, rv); } NS_ASSERTION(!aScriptContext || !mScriptContexts[lang_ndx], "Bad call to SetContext()!"); void *script_glob = nsnull;
--- a/db/mork/build/nsMorkFactory.cpp +++ b/db/mork/build/nsMorkFactory.cpp @@ -31,20 +31,18 @@ * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -#include "nsIServiceManager.h" +#include "mozilla/ModuleUtils.h" #include "nsCOMPtr.h" -#include "nsIModule.h" -#include "nsIGenericFactory.h" #include "nsMorkCID.h" #include "nsIMdbFactoryFactory.h" #include "mdb.h" class nsMorkFactoryService : public nsIMdbFactoryService { public: nsMorkFactoryService() {}; @@ -54,26 +52,35 @@ public: NS_IMETHOD GetMdbFactory(nsIMdbFactory **aFactory); protected: nsCOMPtr<nsIMdbFactory> mMdbFactory; }; NS_GENERIC_FACTORY_CONSTRUCTOR(nsMorkFactoryService) -static const nsModuleComponentInfo components[] = -{ - { "Mork Factory Service", - NS_MORK_CID, - NS_MORK_CONTRACTID, - nsMorkFactoryServiceConstructor - } +NS_DEFINE_NAMED_CID(NS_MORK_CID); + +const mozilla::Module::CIDEntry kMorkCIDs[] = { + { &kNS_MORK_CID, false, NULL, nsMorkFactoryServiceConstructor }, + { NULL } }; -NS_IMPL_NSGETMODULE(nsMorkModule, components) +const mozilla::Module::ContractIDEntry kMorkContracts[] = { + { NS_MORK_CONTRACTID, &kNS_MORK_CID }, + { NULL } +}; + +static const mozilla::Module kMorkModule = { + mozilla::Module::kVersion, + kMorkCIDs, + kMorkContracts +}; + +NSMODULE_DEFN(nsMorkModule) = &kMorkModule; NS_IMPL_ISUPPORTS1(nsMorkFactoryService, nsIMdbFactoryService) NS_IMETHODIMP nsMorkFactoryService::GetMdbFactory(nsIMdbFactory **aFactory) { if (!mMdbFactory) mMdbFactory = MakeMdbFactory(); NS_IF_ADDREF(*aFactory = mMdbFactory);
--- a/docshell/base/IHistory.h +++ b/docshell/base/IHistory.h @@ -38,26 +38,25 @@ * ***** END LICENSE BLOCK ***** */ #ifndef mozilla_IHistory_h_ #define mozilla_IHistory_h_ #include "nsISupports.h" class nsIURI; -class nsString; namespace mozilla { namespace dom { class Link; } #define IHISTORY_IID \ - {0x6f736049, 0x6370, 0x4376, {0xb7, 0x17, 0xfa, 0xfc, 0x0b, 0x4f, 0xd0, 0xf1}} + {0xaf27265d, 0x5672, 0x4d23, {0xa0, 0x75, 0x34, 0x8e, 0xb9, 0x73, 0x5a, 0x9a}} class IHistory : public nsISupports { public: NS_DECLARE_STATIC_IID_ACCESSOR(IHISTORY_IID) /** * Registers the Link for notifications about the visited-ness of aURI. @@ -92,69 +91,21 @@ public: * * @param aURI * The URI that aLink was registered for. * @param aLink * The link object to unregister for aURI. */ NS_IMETHOD UnregisterVisitedCallback(nsIURI *aURI, dom::Link *aLink) = 0; - enum VisitFlags { - /** - * Indicates whether the URI was loaded in a top-level window. - */ - TOP_LEVEL = 1 << 0, - /** - * Indicates whether the URI was loaded as part of a permanent redirect. - */ - REDIRECT_PERMANENT = 1 << 1, - /** - * Indicates whether the URI was loaded as part of a temporary redirect. - */ - REDIRECT_TEMPORARY = 1 << 2 - }; - - /** - * Adds a history visit for the URI. - * - * @pre aURI must not be null. - * - * @param aURI - * The URI of the page being visited. - * @param aLastVisitedURI - * The URI of the last visit in the chain. - * @param aFlags - * The VisitFlags describing this visit. - */ - NS_IMETHOD VisitURI( - nsIURI *aURI, - nsIURI *aLastVisitedURI, - PRUint32 aFlags - ) = 0; - - /** - * Set the title of the URI. - * - * @pre aURI must not be null. - * - * @param aURI - * The URI to set the title for. - * @param aTitle - * The title string. - */ - NS_IMETHOD SetURITitle(nsIURI* aURI, const nsAString& aTitle) = 0; }; NS_DEFINE_STATIC_IID_ACCESSOR(IHistory, IHISTORY_IID) #define NS_DECL_IHISTORY \ NS_IMETHOD RegisterVisitedCallback(nsIURI *aURI, \ mozilla::dom::Link *aContent); \ NS_IMETHOD UnregisterVisitedCallback(nsIURI *aURI, \ - mozilla::dom::Link *aContent); \ - NS_IMETHOD VisitURI(nsIURI *aURI, \ - nsIURI *aLastVisitedURI, \ - PRUint32 aFlags); \ - NS_IMETHOD SetURITitle(nsIURI* aURI, const nsAString& aTitle); + mozilla::dom::Link *aContent); } // namespace mozilla #endif // mozilla_IHistory_h_
--- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -107,18 +107,16 @@ #include "nsDOMJSUtils.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIView.h" #include "nsIViewManager.h" #include "nsIScriptChannel.h" #include "nsIOfflineCacheUpdate.h" #include "nsCPrefetchService.h" #include "nsJSON.h" -#include "IHistory.h" -#include "mozilla/Services.h" // we want to explore making the document own the load group // so we can associate the document URI with the load group. // until this point, we have an evil hack: #include "nsIHttpChannelInternal.h" // Local Includes @@ -4699,25 +4697,20 @@ nsDocShell::SetTitle(const PRUnichar * a // tree owner. if (!parent) { nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_QueryInterface(mTreeOwner)); if (treeOwnerAsWin) treeOwnerAsWin->SetTitle(aTitle); } - if (mCurrentURI && mLoadType != LOAD_ERROR_PAGE) { - nsCOMPtr<IHistory> history = services::GetHistoryService(); - if (history) { - history->SetURITitle(mCurrentURI, mTitle); - } - else if (mGlobalHistory) { - mGlobalHistory->SetPageTitle(mCurrentURI, nsString(mTitle)); - } - } + if (mGlobalHistory && mCurrentURI && mLoadType != LOAD_ERROR_PAGE) { + mGlobalHistory->SetPageTitle(mCurrentURI, nsString(mTitle)); + } + // Update SessionHistory with the document's title. if (mOSHE && mLoadType != LOAD_BYPASS_HISTORY && mLoadType != LOAD_ERROR_PAGE) { mOSHE->SetTitle(mTitle); } @@ -5674,63 +5667,42 @@ nsDocShell::OnRedirectStateChange(nsICha PRUint32 aRedirectFlags, PRUint32 aStateFlags) { NS_ASSERTION(aStateFlags & STATE_REDIRECTING, "Calling OnRedirectStateChange when there is no redirect"); if (!(aStateFlags & STATE_IS_DOCUMENT)) return; // not a toplevel document - nsCOMPtr<nsIURI> oldURI, newURI; - aOldChannel->GetURI(getter_AddRefs(oldURI)); - aNewChannel->GetURI(getter_AddRefs(newURI)); - if (!oldURI || !newURI) { - return; - } - - // Below a URI visit is saved (see AddURIVisit method doc). - // The visit chain looks something like: - // ... - // Site N - 1 - // => Site N - // (redirect to =>) Site N + 1 (we are here!) - - // Get N - 1 and transition type - nsCOMPtr<nsIURI> previousURI; - PRUint32 previousFlags = 0; - ExtractLastVisit(aOldChannel, getter_AddRefs(previousURI), &previousFlags); - - if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL || - ChannelIsPost(aOldChannel)) { - // 1. Internal redirects are ignored because they are specific to the - // channel implementation. - // 2. POSTs are not saved by global history. - // - // Regardless, we need to propagate the previous visit to the new - // channel. - SaveLastVisit(aNewChannel, previousURI, previousFlags); - } - else { - nsCOMPtr<nsIURI> referrer; - // Treat referrer as null if there is an error getting it. - (void)NS_GetReferrerFromChannel(aOldChannel, - getter_AddRefs(referrer)); - - // Add visit N -1 => N - AddURIVisit(oldURI, referrer, previousURI, previousFlags); - - // Since N + 1 could be the final destination, we will not save N => N + 1 - // here. OnNewURI will do that, so we will cache it. - SaveLastVisit(aNewChannel, oldURI, aRedirectFlags); + nsCOMPtr<nsIGlobalHistory3> history3(do_QueryInterface(mGlobalHistory)); + nsresult result = NS_ERROR_NOT_IMPLEMENTED; + if (history3) { + // notify global history of this redirect + result = history3->AddDocumentRedirect(aOldChannel, aNewChannel, + aRedirectFlags, !IsFrame()); + } + + if (result == NS_ERROR_NOT_IMPLEMENTED) { + // when there is no GlobalHistory3, or it doesn't implement + // AddToplevelRedirect, we fall back to GlobalHistory2. Just notify + // that the redirecting page was a rePdirect so it will be link colored + // but not visible. + nsCOMPtr<nsIURI> oldURI; + aOldChannel->GetURI(getter_AddRefs(oldURI)); + if (! oldURI) + return; // nothing to tell anybody about + AddToGlobalHistory(oldURI, PR_TRUE, aOldChannel); } // check if the new load should go through the application cache. nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel = do_QueryInterface(aNewChannel); if (appCacheChannel) { + nsCOMPtr<nsIURI> newURI; + aNewChannel->GetURI(getter_AddRefs(newURI)); appCacheChannel->SetChooseApplicationCache(ShouldCheckAppCache(newURI)); } if (!(aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) && mLoadType & (LOAD_CMD_RELOAD | LOAD_CMD_HISTORY)) { mLoadType = LOAD_NORMAL_REPLACE; SetHistoryEntry(&mLSHE, nsnull); } @@ -8960,17 +8932,17 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIC PRBool updateHistory = PR_TRUE; PRBool equalUri = PR_FALSE; PRBool shAvailable = PR_TRUE; // Get the post data from the channel nsCOMPtr<nsIInputStream> inputStream; if (aChannel) { nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); - + // Check if the HTTPChannel is hiding under a multiPartChannel if (!httpChannel) { GetHttpChannel(aChannel, getter_AddRefs(httpChannel)); } if (httpChannel) { nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel)); if (uploadChannel) { @@ -9050,18 +9022,18 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIC (aLoadType == LOAD_RELOAD_BYPASS_CACHE || aLoadType == LOAD_RELOAD_BYPASS_PROXY || aLoadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE)) { NS_ASSERTION(!updateHistory, "We shouldn't be updating history for forced reloads!"); nsCOMPtr<nsICachingChannel> cacheChannel(do_QueryInterface(aChannel)); nsCOMPtr<nsISupports> cacheKey; - // Get the Cache Key and store it in SH. - if (cacheChannel) + // Get the Cache Key and store it in SH. + if (cacheChannel) cacheChannel->GetCacheKey(getter_AddRefs(cacheKey)); // If we already have a loading history entry, store the new cache key // in it. Otherwise, since we're doing a reload and won't be updating // our history entry, store the cache key in our current history entry. if (mLSHE) mLSHE->SetCacheKey(cacheKey); else if (mOSHE) mOSHE->SetCacheKey(cacheKey); @@ -9073,32 +9045,20 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIC /* This is a fresh page getting loaded for the first time *.Create a Entry for it and add it to SH, if this is the * rootDocShell */ (void) AddToSessionHistory(aURI, aChannel, aOwner, getter_AddRefs(mLSHE)); } + // Update Global history if (aAddToGlobalHistory) { - // If this is a POST request, we do not want to include this in global - // history. - if (!ChannelIsPost(aChannel)) { - nsCOMPtr<nsIURI> previousURI; - PRUint32 previousFlags = 0; - ExtractLastVisit(aChannel, getter_AddRefs(previousURI), - &previousFlags); - - nsCOMPtr<nsIURI> referrer; - // Treat referrer as null if there is an error getting it. - (void)NS_GetReferrerFromChannel(aChannel, - getter_AddRefs(referrer)); - - AddURIVisit(aURI, referrer, previousURI, previousFlags); - } + // Get the referrer uri from the channel + AddToGlobalHistory(aURI, PR_FALSE, aChannel); } } // If this was a history load, update the index in // SH. if (rootSH && (mLoadType & LOAD_CMD_HISTORY)) { nsCOMPtr<nsISHistoryInternal> shInternal(do_QueryInterface(rootSH)); if (shInternal) { @@ -9407,17 +9367,17 @@ nsDocShell::AddState(nsIVariant *aData, // We need to call FireOnLocationChange so that the browser's address bar // gets updated and the back button is enabled, but we only need to // explicitly call FireOnLocationChange if we're not calling SetCurrentURI, // since SetCurrentURI will call FireOnLocationChange for us. if (!equalURIs) { SetCurrentURI(newURI, nsnull, PR_TRUE); document->SetDocumentURI(newURI); - AddURIVisit(newURI, oldURI, oldURI, 0); + AddToGlobalHistory(newURI, PR_FALSE, oldURI); } else { FireOnLocationChange(this, nsnull, mCurrentURI); } // Try to set the title of the current history element if (mOSHE) mOSHE->SetTitle(aTitle); @@ -10103,119 +10063,63 @@ NS_IMETHODIMP nsDocShell::GetHasEditingS NS_IMETHODIMP nsDocShell::MakeEditable(PRBool inWaitForUriLoad) { nsresult rv = EnsureEditorData(); if (NS_FAILED(rv)) return rv; return mEditorData->MakeEditable(inWaitForUriLoad); } -bool -nsDocShell::ChannelIsPost(nsIChannel* aChannel) -{ - nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); - if (!httpChannel) { - return false; - } - - nsCAutoString method; - httpChannel->GetRequestMethod(method); - return method.Equals("POST"); -} - -void -nsDocShell::ExtractLastVisit(nsIChannel* aChannel, - nsIURI** aURI, - PRUint32* aChannelRedirectFlags) -{ - nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel)); - if (!props) { - return; - } - - nsresult rv = props->GetPropertyAsInterface( - NS_LITERAL_STRING("docshell.previousURI"), - NS_GET_IID(nsIURI), - reinterpret_cast<void**>(aURI) - ); - - if (NS_FAILED(rv)) { - // There is no last visit for this channel, so this must be the first - // link. Link the visit to the referrer of this request, if any. - // Treat referrer as null if there is an error getting it. - (void)NS_GetReferrerFromChannel(aChannel, aURI); - } - else { - rv = props->GetPropertyAsUint32( - NS_LITERAL_STRING("docshell.previousFlags"), - aChannelRedirectFlags - ); - - NS_WARN_IF_FALSE( - NS_FAILED(rv), - "Could not fetch previous flags, URI will be treated like referrer" - ); - } -} - -void -nsDocShell::SaveLastVisit(nsIChannel* aChannel, - nsIURI* aURI, - PRUint32 aChannelRedirectFlags) -{ - nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel)); - if (!props || !aURI) { - return; - } - - props->SetPropertyAsInterface(NS_LITERAL_STRING("docshell.previousURI"), - aURI); - props->SetPropertyAsUint32(NS_LITERAL_STRING("docshell.previousFlags"), - aChannelRedirectFlags); -} - -void -nsDocShell::AddURIVisit(nsIURI* aURI, - nsIURI* aReferrerURI, - nsIURI* aPreviousURI, - PRUint32 aChannelRedirectFlags) -{ - NS_ASSERTION(aURI, "Visited URI is null!"); - - // Only content-type docshells save URI visits. - if (mItemType != typeContent) { - return; - } - - nsCOMPtr<IHistory> history = services::GetHistoryService(); - - if (history) { - PRUint32 visitURIFlags = 0; - - if (!IsFrame()) { - visitURIFlags |= IHistory::TOP_LEVEL; - } - - if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_TEMPORARY) { - visitURIFlags |= IHistory::REDIRECT_TEMPORARY; - } - else if (aChannelRedirectFlags & - nsIChannelEventSink::REDIRECT_PERMANENT) { - visitURIFlags |= IHistory::REDIRECT_PERMANENT; - } - - (void)history->VisitURI(aURI, aPreviousURI, visitURIFlags); - } - else if (mGlobalHistory) { - // Falls back to sync global history interface. - (void)mGlobalHistory->AddURI(aURI, - !!aChannelRedirectFlags, - !IsFrame(), - aReferrerURI); - } +nsresult +nsDocShell::AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect, + nsIChannel * aChannel) +{ + // If this is a POST request, we do not want to include this in global + // history, so return early. + nsCOMPtr<nsIHttpChannel> hchan(do_QueryInterface(aChannel)); + if (hchan) { + nsCAutoString type; + nsresult rv = hchan->GetRequestMethod(type); + if (NS_SUCCEEDED(rv) && type.EqualsLiteral("POST")) + return NS_OK; + } + + nsCOMPtr<nsIURI> referrer; + if (aChannel) + NS_GetReferrerFromChannel(aChannel, getter_AddRefs(referrer)); + + return AddToGlobalHistory(aURI, aRedirect, referrer); +} + +nsresult +nsDocShell::AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect, + nsIURI * aReferrer) +{ + if (mItemType != typeContent || !mGlobalHistory) + return NS_OK; + + PRBool visited; + nsresult rv = mGlobalHistory->IsVisited(aURI, &visited); + if (NS_FAILED(rv))