diff --git a/accessible/public/nsIAccessibleEditableText.idl b/accessible/public/nsIAccessibleEditableText.idl index 146691590257..36b72a880bfa 100644 --- a/accessible/public/nsIAccessibleEditableText.idl +++ b/accessible/public/nsIAccessibleEditableText.idl @@ -43,7 +43,7 @@ interface nsIEditor; -[scriptable, uuid(52837507-202d-4e72-a482-5f068a1fd720)] +[scriptable, uuid(e242d495-5cde-4b1c-8c84-2525b14939f5)] interface nsIAccessibleEditableText : nsISupports { /** @@ -103,19 +103,4 @@ interface nsIAccessibleEditableText : nsISupports * clipboard into the text represented by this object. */ void pasteText (in long position); - - /** - * Returns an editor associated with the accessible. - */ - [noscript] readonly attribute nsIEditor associatedEditor; }; - -/* - Assumptions: - - selectAttributes method takes an nsISupports parameter. - 'set' methods throw exception on failure. - 'wstring' inputs are potentially multibyte (UTF-16 for - instance); 'string' and UTF-8 may be a better choice. - -*/ diff --git a/accessible/src/atk/nsApplicationAccessibleWrap.cpp b/accessible/src/atk/nsApplicationAccessibleWrap.cpp index bca6caa54a4f..9b58acfa6ec5 100644 --- a/accessible/src/atk/nsApplicationAccessibleWrap.cpp +++ b/accessible/src/atk/nsApplicationAccessibleWrap.cpp @@ -696,6 +696,16 @@ nsApplicationAccessibleWrap::Unload() // } } +NS_IMETHODIMP +nsApplicationAccessibleWrap::GetName(nsAString& aName) +{ + // ATK doesn't provide a way to obtain an application name (for example, + // Firefox or Thunderbird) like IA2 does. Thus let's return an application + // name as accessible name that was used to get a branding name (for example, + // Minefield aka nightly Firefox or Daily aka nightly Thunderbird). + return GetAppName(aName); +} + NS_IMETHODIMP nsApplicationAccessibleWrap::GetNativeInterface(void **aOutAccessible) { diff --git a/accessible/src/atk/nsApplicationAccessibleWrap.h b/accessible/src/atk/nsApplicationAccessibleWrap.h index ba17c0c31a8f..924eeefba398 100644 --- a/accessible/src/atk/nsApplicationAccessibleWrap.h +++ b/accessible/src/atk/nsApplicationAccessibleWrap.h @@ -57,6 +57,8 @@ public: virtual bool Init(); // nsAccessible + NS_IMETHOD GetName(nsAString &aName); + virtual bool AppendChild(nsAccessible* aChild); virtual bool RemoveChild(nsAccessible* aChild); diff --git a/accessible/src/base/NotificationController.cpp b/accessible/src/base/NotificationController.cpp index 5a1db68a8d28..f0c99cb5cb88 100644 --- a/accessible/src/base/NotificationController.cpp +++ b/accessible/src/base/NotificationController.cpp @@ -680,8 +680,7 @@ NotificationController::CreateTextChangeEventFor(AccMutationEvent* aEvent) // Don't fire event for the first html:br in an editor. if (aEvent->mAccessible->Role() == roles::WHITESPACE) { - nsCOMPtr editor; - textAccessible->GetAssociatedEditor(getter_AddRefs(editor)); + nsCOMPtr editor = textAccessible->GetEditor(); if (editor) { bool isEmpty = false; editor->GetDocumentIsEmpty(&isEmpty); diff --git a/accessible/src/base/nsAccessible.cpp b/accessible/src/base/nsAccessible.cpp index 712966f696a5..022f2ce957c0 100644 --- a/accessible/src/base/nsAccessible.cpp +++ b/accessible/src/base/nsAccessible.cpp @@ -969,10 +969,8 @@ void nsAccessible::GetBoundsRect(nsRect& aTotalBounds, nsIFrame** aBoundingFrame *aBoundingFrame = ancestorFrame; // If any other frame type, we only need to deal with the primary frame // Otherwise, there may be more frames attached to the same content node - if (!nsCoreUtils::IsCorrectFrameType(ancestorFrame, - nsGkAtoms::inlineFrame) && - !nsCoreUtils::IsCorrectFrameType(ancestorFrame, - nsGkAtoms::textFrame)) + if (ancestorFrame->GetType() != nsGkAtoms::inlineFrame && + ancestorFrame->GetType() != nsGkAtoms::textFrame) break; ancestorFrame = ancestorFrame->GetParent(); } @@ -996,8 +994,7 @@ void nsAccessible::GetBoundsRect(nsRect& aTotalBounds, nsIFrame** aBoundingFrame nsIFrame *iterNextFrame = nsnull; - if (nsCoreUtils::IsCorrectFrameType(iterFrame, - nsGkAtoms::inlineFrame)) { + if (iterFrame->GetType() == nsGkAtoms::inlineFrame) { // Only do deeper bounds search if we're on an inline frame // Inline frames can contain larger frames inside of them iterNextFrame = iterFrame->GetFirstPrincipalChild(); diff --git a/accessible/src/base/nsAccessible.h b/accessible/src/base/nsAccessible.h index 5b638e4623df..5a40ed68e830 100644 --- a/accessible/src/base/nsAccessible.h +++ b/accessible/src/base/nsAccessible.h @@ -214,6 +214,11 @@ public: */ virtual PRUint64 NativeState(); + /** + * Return bit set of invisible and offscreen states. + */ + PRUint64 VisibilityState(); + /** * Returns attributes for accessible without explicitly setted ARIA * attributes. @@ -702,8 +707,6 @@ protected: virtual nsIFrame* GetBoundsFrame(); virtual void GetBoundsRect(nsRect& aRect, nsIFrame** aRelativeFrame); - PRUint64 VisibilityState(); - ////////////////////////////////////////////////////////////////////////////// // Name helpers diff --git a/accessible/src/base/nsCoreUtils.cpp b/accessible/src/base/nsCoreUtils.cpp index 6d855acce804..fc2a4fbd22fd 100644 --- a/accessible/src/base/nsCoreUtils.cpp +++ b/accessible/src/base/nsCoreUtils.cpp @@ -503,17 +503,6 @@ nsCoreUtils::IsErrorPage(nsIDocument *aDocument) return StringBeginsWith(path, neterror) || StringBeginsWith(path, certerror); } -bool -nsCoreUtils::IsCorrectFrameType(nsIFrame *aFrame, nsIAtom *aAtom) -{ - NS_ASSERTION(aFrame != nsnull, - "aFrame is null in call to IsCorrectFrameType!"); - NS_ASSERTION(aAtom != nsnull, - "aAtom is null in call to IsCorrectFrameType!"); - - return aFrame->GetType() == aAtom; -} - already_AddRefed nsCoreUtils::GetDOMNodeForContainer(nsIDocShellTreeItem *aContainer) { diff --git a/accessible/src/base/nsCoreUtils.h b/accessible/src/base/nsCoreUtils.h index 5a42e6119bf3..db5ad1b83651 100644 --- a/accessible/src/base/nsCoreUtils.h +++ b/accessible/src/base/nsCoreUtils.h @@ -241,14 +241,6 @@ public: */ static bool IsErrorPage(nsIDocument *aDocument); - /** - * Retrun true if the type of given frame equals to the given frame type. - * - * @param aFrame the frame - * @param aAtom the frame type - */ - static bool IsCorrectFrameType(nsIFrame* aFrame, nsIAtom* aAtom); - /** * Return presShell for the document containing the given DOM node. */ diff --git a/accessible/src/base/nsDocAccessible.cpp b/accessible/src/base/nsDocAccessible.cpp index 483f642c457d..8eeb28aa06c2 100644 --- a/accessible/src/base/nsDocAccessible.cpp +++ b/accessible/src/base/nsDocAccessible.cpp @@ -333,8 +333,7 @@ nsDocAccessible::NativeState() state |= states::INVISIBLE | states::OFFSCREEN; } - nsCOMPtr editor; - GetAssociatedEditor(getter_AddRefs(editor)); + nsCOMPtr editor = GetEditor(); state |= editor ? states::EDITABLE : states::READONLY; return state; @@ -553,37 +552,32 @@ nsDocAccessible::GetVirtualCursor(nsIAccessiblePivot** aVirtualCursor) return NS_OK; } -// nsIAccessibleHyperText method -NS_IMETHODIMP nsDocAccessible::GetAssociatedEditor(nsIEditor **aEditor) +// nsHyperTextAccessible method +already_AddRefed +nsDocAccessible::GetEditor() const { - NS_ENSURE_ARG_POINTER(aEditor); - *aEditor = nsnull; - - if (IsDefunct()) - return NS_ERROR_FAILURE; - // Check if document is editable (designMode="on" case). Otherwise check if // the html:body (for HTML document case) or document element is editable. if (!mDocument->HasFlag(NODE_IS_EDITABLE) && !mContent->HasFlag(NODE_IS_EDITABLE)) - return NS_OK; + return nsnull; nsCOMPtr container = mDocument->GetContainer(); nsCOMPtr editingSession(do_GetInterface(container)); if (!editingSession) - return NS_OK; // No editing session interface + return nsnull; // No editing session interface nsCOMPtr editor; editingSession->GetEditorForWindow(mDocument->GetWindow(), getter_AddRefs(editor)); - if (!editor) { - return NS_OK; - } - bool isEditable; + if (!editor) + return nsnull; + + bool isEditable = false; editor->GetIsDocumentEditable(&isEditable); - if (isEditable) { - NS_ADDREF(*aEditor = editor); - } - return NS_OK; + if (isEditable) + return editor.forget(); + + return nsnull; } // nsDocAccessible public method diff --git a/accessible/src/base/nsDocAccessible.h b/accessible/src/base/nsDocAccessible.h index 47cc3e1da3f0..5368be297b62 100644 --- a/accessible/src/base/nsDocAccessible.h +++ b/accessible/src/base/nsDocAccessible.h @@ -133,8 +133,8 @@ public: virtual nsresult HandleAccEvent(AccEvent* aAccEvent); #endif - // nsIAccessibleText - NS_IMETHOD GetAssociatedEditor(nsIEditor **aEditor); + // nsHyperTextAccessible + virtual already_AddRefed GetEditor() const; // nsDocAccessible diff --git a/accessible/src/html/nsHTMLFormControlAccessible.cpp b/accessible/src/html/nsHTMLFormControlAccessible.cpp index 98c8771c4e06..defd4c5208f1 100644 --- a/accessible/src/html/nsHTMLFormControlAccessible.cpp +++ b/accessible/src/html/nsHTMLFormControlAccessible.cpp @@ -544,11 +544,12 @@ NS_IMETHODIMP nsHTMLTextFieldAccessible::DoAction(PRUint8 index) return NS_ERROR_INVALID_ARG; } -NS_IMETHODIMP nsHTMLTextFieldAccessible::GetAssociatedEditor(nsIEditor **aEditor) +already_AddRefed +nsHTMLTextFieldAccessible::GetEditor() const { - *aEditor = nsnull; nsCOMPtr editableElt(do_QueryInterface(mContent)); - NS_ENSURE_TRUE(editableElt, NS_ERROR_FAILURE); + if (!editableElt) + return nsnull; // nsGenericHTMLElement::GetEditor has a security check. // Make sure we're not restricted by the permissions of @@ -558,7 +559,7 @@ NS_IMETHODIMP nsHTMLTextFieldAccessible::GetAssociatedEditor(nsIEditor **aEditor bool pushed = stack && NS_SUCCEEDED(stack->Push(nsnull)); nsCOMPtr editor; - nsresult rv = editableElt->GetEditor(aEditor); + editableElt->GetEditor(getter_AddRefs(editor)); if (pushed) { JSContext* cx; @@ -566,7 +567,7 @@ NS_IMETHODIMP nsHTMLTextFieldAccessible::GetAssociatedEditor(nsIEditor **aEditor NS_ASSERTION(!cx, "context should be null"); } - return rv; + return editor.forget(); } //////////////////////////////////////////////////////////////////////////////// diff --git a/accessible/src/html/nsHTMLFormControlAccessible.h b/accessible/src/html/nsHTMLFormControlAccessible.h index 0dd3a0fb3215..8bc5ba492384 100644 --- a/accessible/src/html/nsHTMLFormControlAccessible.h +++ b/accessible/src/html/nsHTMLFormControlAccessible.h @@ -138,8 +138,8 @@ public: NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName); NS_IMETHOD DoAction(PRUint8 index); - // nsIAccessibleEditableText - NS_IMETHOD GetAssociatedEditor(nsIEditor **aEditor); + // nsHyperTextAccessible + virtual already_AddRefed GetEditor() const; // nsAccessible virtual void ApplyARIAState(PRUint64* aState); diff --git a/accessible/src/html/nsHyperTextAccessible.cpp b/accessible/src/html/nsHyperTextAccessible.cpp index c1060c317002..b51741676bab 100644 --- a/accessible/src/html/nsHyperTextAccessible.cpp +++ b/accessible/src/html/nsHyperTextAccessible.cpp @@ -47,6 +47,7 @@ #include "States.h" #include "nsIClipboard.h" +#include "nsContentUtils.h" #include "nsFocusManager.h" #include "nsIDOMCharacterData.h" #include "nsIDOMDocument.h" @@ -165,8 +166,7 @@ nsHyperTextAccessible::NativeState() { PRUint64 states = nsAccessibleWrap::NativeState(); - nsCOMPtr editor; - GetAssociatedEditor(getter_AddRefs(editor)); + nsCOMPtr editor = GetEditor(); if (editor) { PRUint32 flags; editor->GetFlags(&flags); @@ -710,8 +710,7 @@ nsHyperTextAccessible::HypertextOffsetsToDOMRange(PRInt32 aStartHTOffset, // If the given offsets are 0 and associated editor is empty then return // collapsed range with editor root element as range container. if (aStartHTOffset == 0 && aEndHTOffset == 0) { - nsCOMPtr editor; - GetAssociatedEditor(getter_AddRefs(editor)); + nsCOMPtr editor = GetEditor(); if (editor) { bool isEmpty = false; editor->GetDocumentIsEmpty(&isEmpty); @@ -1153,15 +1152,14 @@ nsHyperTextAccessible::GetTextAttributes(bool aIncludeDefAttrs, // Compute spelling attributes on text accessible only. nsIFrame *offsetFrame = accAtOffset->GetFrame(); if (offsetFrame && offsetFrame->GetType() == nsGkAtoms::textFrame) { - nsCOMPtr node = accAtOffset->DOMNode(); - PRInt32 nodeOffset = 0; nsresult rv = RenderedToContentOffset(offsetFrame, offsetInAcc, &nodeOffset); NS_ENSURE_SUCCESS(rv, rv); // Set 'misspelled' text attribute. - rv = GetSpellTextAttribute(node, nodeOffset, &startOffset, &endOffset, + rv = GetSpellTextAttribute(accAtOffset->GetNode(), nodeOffset, + &startOffset, &endOffset, aAttributes ? *aAttributes : nsnull); NS_ENSURE_SUCCESS(rv, rv); } @@ -1455,8 +1453,10 @@ NS_IMETHODIMP nsHyperTextAccessible::SetTextContents(const nsAString &aText) NS_IMETHODIMP nsHyperTextAccessible::InsertText(const nsAString &aText, PRInt32 aPosition) { - nsCOMPtr editor; - GetAssociatedEditor(getter_AddRefs(editor)); + if (IsDefunct()) + return NS_ERROR_FAILURE; + + nsCOMPtr editor = GetEditor(); nsCOMPtr peditor(do_QueryInterface(editor)); NS_ENSURE_STATE(peditor); @@ -1470,8 +1470,10 @@ nsHyperTextAccessible::InsertText(const nsAString &aText, PRInt32 aPosition) NS_IMETHODIMP nsHyperTextAccessible::CopyText(PRInt32 aStartPos, PRInt32 aEndPos) { - nsCOMPtr editor; - GetAssociatedEditor(getter_AddRefs(editor)); + if (IsDefunct()) + return NS_ERROR_FAILURE; + + nsCOMPtr editor = GetEditor(); NS_ENSURE_STATE(editor); nsresult rv = SetSelectionRange(aStartPos, aEndPos); @@ -1483,8 +1485,10 @@ nsHyperTextAccessible::CopyText(PRInt32 aStartPos, PRInt32 aEndPos) NS_IMETHODIMP nsHyperTextAccessible::CutText(PRInt32 aStartPos, PRInt32 aEndPos) { - nsCOMPtr editor; - GetAssociatedEditor(getter_AddRefs(editor)); + if (IsDefunct()) + return NS_ERROR_FAILURE; + + nsCOMPtr editor = GetEditor(); NS_ENSURE_STATE(editor); nsresult rv = SetSelectionRange(aStartPos, aEndPos); @@ -1496,8 +1500,10 @@ nsHyperTextAccessible::CutText(PRInt32 aStartPos, PRInt32 aEndPos) NS_IMETHODIMP nsHyperTextAccessible::DeleteText(PRInt32 aStartPos, PRInt32 aEndPos) { - nsCOMPtr editor; - GetAssociatedEditor(getter_AddRefs(editor)); + if (IsDefunct()) + return NS_ERROR_FAILURE; + + nsCOMPtr editor = GetEditor(); NS_ENSURE_STATE(editor); nsresult rv = SetSelectionRange(aStartPos, aEndPos); @@ -1509,8 +1515,10 @@ nsHyperTextAccessible::DeleteText(PRInt32 aStartPos, PRInt32 aEndPos) NS_IMETHODIMP nsHyperTextAccessible::PasteText(PRInt32 aPosition) { - nsCOMPtr editor; - GetAssociatedEditor(getter_AddRefs(editor)); + if (IsDefunct()) + return NS_ERROR_FAILURE; + + nsCOMPtr editor = GetEditor(); NS_ENSURE_STATE(editor); nsresult rv = SetSelectionRange(aPosition, aPosition); @@ -1519,44 +1527,37 @@ nsHyperTextAccessible::PasteText(PRInt32 aPosition) return editor->Paste(nsIClipboard::kGlobalClipboard); } -NS_IMETHODIMP -nsHyperTextAccessible::GetAssociatedEditor(nsIEditor **aEditor) +already_AddRefed +nsHyperTextAccessible::GetEditor() const { - NS_ENSURE_ARG_POINTER(aEditor); - *aEditor = nsnull; - - if (IsDefunct()) - return NS_ERROR_FAILURE; - if (!mContent->HasFlag(NODE_IS_EDITABLE)) { // If we're inside an editable container, then return that container's editor - nsCOMPtr ancestor, current = this; - while (NS_SUCCEEDED(current->GetParent(getter_AddRefs(ancestor))) && ancestor) { - nsRefPtr ancestorTextAccessible; - ancestor->QueryInterface(NS_GET_IID(nsHyperTextAccessible), - getter_AddRefs(ancestorTextAccessible)); - if (ancestorTextAccessible) { + nsAccessible* ancestor = Parent(); + while (ancestor) { + nsHyperTextAccessible* hyperText = ancestor->AsHyperText(); + if (hyperText) { // Recursion will stop at container doc because it has its own impl - // of GetAssociatedEditor() - return ancestorTextAccessible->GetAssociatedEditor(aEditor); + // of GetEditor() + return hyperText->GetEditor(); } - current = ancestor; + + ancestor = ancestor->Parent(); } - return NS_OK; + + return nsnull; } nsCOMPtr docShellTreeItem = nsCoreUtils::GetDocShellTreeItemFor(mContent); nsCOMPtr editingSession(do_GetInterface(docShellTreeItem)); if (!editingSession) - return NS_OK; // No editing session interface - - NS_ENSURE_TRUE(mDoc, NS_ERROR_FAILURE); - nsIDocument* docNode = mDoc->GetDocumentNode(); - NS_ENSURE_TRUE(docNode, NS_ERROR_FAILURE); + return nsnull; // No editing session interface nsCOMPtr editor; - return editingSession->GetEditorForWindow(docNode->GetWindow(), aEditor); + nsIDocument* docNode = mDoc->GetDocumentNode(); + editingSession->GetEditorForWindow(docNode->GetWindow(), + getter_AddRefs(editor)); + return editor.forget(); } /** @@ -1770,8 +1771,7 @@ nsHyperTextAccessible::GetSelectionDOMRanges(PRInt16 aType, nsCOMPtr startNode = GetNode(); - nsCOMPtr editor; - GetAssociatedEditor(getter_AddRefs(editor)); + nsCOMPtr editor = GetEditor(); if (editor) { nsCOMPtr editorRoot; editor->GetRootElement(getter_AddRefs(editorRoot)); @@ -1789,13 +1789,11 @@ nsHyperTextAccessible::GetSelectionDOMRanges(PRInt16 aType, // Remove collapsed ranges PRUint32 numRanges = aRanges->Length(); - for (PRUint32 count = 0; count < numRanges; count ++) { - bool isCollapsed = false; - (*aRanges)[count]->GetCollapsed(&isCollapsed); - if (isCollapsed) { - aRanges->RemoveElementAt(count); + for (PRUint32 idx = 0; idx < numRanges; idx ++) { + if ((*aRanges)[idx]->Collapsed()) { + aRanges->RemoveElementAt(idx); --numRanges; - --count; + --idx; } } } @@ -1837,29 +1835,19 @@ nsHyperTextAccessible::GetSelectionBounds(PRInt32 aSelectionNum, nsRange* range = ranges[aSelectionNum]; - // Get start point - nsCOMPtr startDOMNode; - range->GetStartContainer(getter_AddRefs(startDOMNode)); - nsCOMPtr startNode(do_QueryInterface(startDOMNode)); - PRInt32 startOffset = 0; - range->GetStartOffset(&startOffset); + // Get start and end points. + nsINode* startNode = range->GetStartParent(); + nsINode* endNode = range->GetEndParent(); + PRInt32 startOffset = range->StartOffset(), endOffset = range->EndOffset(); - // Get end point - nsCOMPtr endDOMNode; - range->GetEndContainer(getter_AddRefs(endDOMNode)); - nsCOMPtr endNode(do_QueryInterface(endDOMNode)); - PRInt32 endOffset = 0; - range->GetEndOffset(&endOffset); - - PRInt16 rangeCompareResult = 0; - nsresult rv = range->CompareBoundaryPoints(nsIDOMRange::START_TO_END, range, - &rangeCompareResult); - NS_ENSURE_SUCCESS(rv, rv); - - if (rangeCompareResult < 0) { - // Make sure start is before end, by swapping offsets - // This occurs when the user selects backwards in the text - startNode.swap(endNode); + // Make sure start is before end, by swapping DOM points. This occurs when + // the user selects backwards in the text. + PRInt32 rangeCompare = nsContentUtils::ComparePoints(endNode, endOffset, + startNode, startOffset); + if (rangeCompare < 0) { + nsINode* tempNode = startNode; + startNode = endNode; + endNode = tempNode; PRInt32 tempOffset = startOffset; startOffset = endOffset; endOffset = tempOffset; @@ -2324,25 +2312,17 @@ nsHyperTextAccessible::RangeBoundToHypertextOffset(nsRange *aRange, bool aIsStartHTOffset, PRInt32 *aHTOffset) { - nsCOMPtr DOMNode; + nsINode* node = nsnull; PRInt32 nodeOffset = 0; - nsresult rv; if (aIsStartBound) { - rv = aRange->GetStartContainer(getter_AddRefs(DOMNode)); - NS_ENSURE_SUCCESS(rv, rv); - - rv = aRange->GetStartOffset(&nodeOffset); - NS_ENSURE_SUCCESS(rv, rv); + node = aRange->GetStartParent(); + nodeOffset = aRange->StartOffset(); } else { - rv = aRange->GetEndContainer(getter_AddRefs(DOMNode)); - NS_ENSURE_SUCCESS(rv, rv); - - rv = aRange->GetEndOffset(&nodeOffset); - NS_ENSURE_SUCCESS(rv, rv); + node = aRange->GetEndParent(); + nodeOffset = aRange->EndOffset(); } - nsCOMPtr node(do_QueryInterface(DOMNode)); nsAccessible *startAcc = DOMPointToHypertextOffset(node, nodeOffset, aHTOffset); @@ -2354,7 +2334,7 @@ nsHyperTextAccessible::RangeBoundToHypertextOffset(nsRange *aRange, // nsHyperTextAccessible nsresult -nsHyperTextAccessible::GetSpellTextAttribute(nsIDOMNode *aNode, +nsHyperTextAccessible::GetSpellTextAttribute(nsINode* aNode, PRInt32 aNodeOffset, PRInt32 *aHTStartOffset, PRInt32 *aHTEndOffset, @@ -2367,25 +2347,19 @@ nsHyperTextAccessible::GetSpellTextAttribute(nsIDOMNode *aNode, if (!rangeCount) return NS_OK; + nsCOMPtr DOMNode = do_QueryInterface(aNode); for (PRUint32 index = 0; index < rangeCount; index++) { nsRange* range = ranges[index]; PRInt16 result; - nsresult rv = range->ComparePoint(aNode, aNodeOffset, &result); + nsresult rv = range->ComparePoint(DOMNode, aNodeOffset, &result); NS_ENSURE_SUCCESS(rv, rv); // ComparePoint checks boundary points, but we need to check that // text at aNodeOffset is inside the range. // See also bug 460690. if (result == 0) { - nsCOMPtr end; - rv = range->GetEndContainer(getter_AddRefs(end)); - NS_ENSURE_SUCCESS(rv, rv); - PRInt32 endOffset; - rv = range->GetEndOffset(&endOffset); - NS_ENSURE_SUCCESS(rv, rv); - if (aNode == end && aNodeOffset == endOffset) { + if (aNode == range->GetEndParent() && aNodeOffset == range->EndOffset()) result = 1; - } } if (result == 1) { // range is before point diff --git a/accessible/src/html/nsHyperTextAccessible.h b/accessible/src/html/nsHyperTextAccessible.h index 44e93ca4cfe8..8c42d12d7a60 100644 --- a/accessible/src/html/nsHyperTextAccessible.h +++ b/accessible/src/html/nsHyperTextAccessible.h @@ -264,6 +264,14 @@ public: return GetChildAt(GetChildIndexAtOffset(aOffset)); } + ////////////////////////////////////////////////////////////////////////////// + // EditableTextAccessible + + /** + * Return the editor associated with the accessible. + */ + virtual already_AddRefed GetEditor() const; + protected: // nsHyperTextAccessible @@ -409,7 +417,7 @@ protected: * @param aEndOffset [in, out] the end offset * @param aAttributes [out, optional] result attributes */ - nsresult GetSpellTextAttribute(nsIDOMNode *aNode, PRInt32 aNodeOffset, + nsresult GetSpellTextAttribute(nsINode* aNode, PRInt32 aNodeOffset, PRInt32 *aStartOffset, PRInt32 *aEndOffset, nsIPersistentProperties *aAttributes); diff --git a/accessible/src/xforms/nsXFormsAccessible.cpp b/accessible/src/xforms/nsXFormsAccessible.cpp index fd65c81a64aa..2cc0c1e9e94f 100644 --- a/accessible/src/xforms/nsXFormsAccessible.cpp +++ b/accessible/src/xforms/nsXFormsAccessible.cpp @@ -273,8 +273,7 @@ nsXFormsEditableAccessible::NativeState() } } - nsCOMPtr editor; - GetAssociatedEditor(getter_AddRefs(editor)); + nsCOMPtr editor = GetEditor(); NS_ENSURE_TRUE(editor, state); PRUint32 flags; editor->GetFlags(&flags); @@ -286,11 +285,14 @@ nsXFormsEditableAccessible::NativeState() return state; } -NS_IMETHODIMP -nsXFormsEditableAccessible::GetAssociatedEditor(nsIEditor **aEditor) +already_AddRefed +nsXFormsEditableAccessible::GetEditor() const { nsCOMPtr DOMNode(do_QueryInterface(mContent)); - return sXFormsService->GetEditor(DOMNode, aEditor); + + nsCOMPtr editor; + sXFormsService->GetEditor(DOMNode, getter_AddRefs(editor)); + return editor.forget(); } //////////////////////////////////////////////////////////////////////////////// diff --git a/accessible/src/xforms/nsXFormsAccessible.h b/accessible/src/xforms/nsXFormsAccessible.h index 512ea7fefac1..76350d639b8c 100644 --- a/accessible/src/xforms/nsXFormsAccessible.h +++ b/accessible/src/xforms/nsXFormsAccessible.h @@ -144,8 +144,8 @@ class nsXFormsEditableAccessible : public nsXFormsAccessible public: nsXFormsEditableAccessible(nsIContent* aContent, nsDocAccessible* aDoc); - // nsIAccessibleEditableText - NS_IMETHOD GetAssociatedEditor(nsIEditor **aEditor); + // nsHyperTextAccessible + virtual already_AddRefed GetEditor() const; // nsAccessible virtual PRUint64 NativeState(); diff --git a/accessible/src/xul/nsXULFormControlAccessible.cpp b/accessible/src/xul/nsXULFormControlAccessible.cpp index 8ff7c46d2faf..671a515dda74 100644 --- a/accessible/src/xul/nsXULFormControlAccessible.cpp +++ b/accessible/src/xul/nsXULFormControlAccessible.cpp @@ -850,14 +850,17 @@ nsXULTextFieldAccessible::CanHaveAnonChildren() return false; } -NS_IMETHODIMP nsXULTextFieldAccessible::GetAssociatedEditor(nsIEditor **aEditor) +already_AddRefed +nsXULTextFieldAccessible::GetEditor() const { - *aEditor = nsnull; - nsCOMPtr inputField = GetInputField(); nsCOMPtr editableElt(do_QueryInterface(inputField)); - NS_ENSURE_TRUE(editableElt, NS_ERROR_FAILURE); - return editableElt->GetEditor(aEditor); + if (!editableElt) + return nsnull; + + nsCOMPtr editor; + editableElt->GetEditor(getter_AddRefs(editor)); + return editor.forget(); } //////////////////////////////////////////////////////////////////////////////// diff --git a/accessible/src/xul/nsXULFormControlAccessible.h b/accessible/src/xul/nsXULFormControlAccessible.h index 965119dd9670..a7bb22aa47ee 100644 --- a/accessible/src/xul/nsXULFormControlAccessible.h +++ b/accessible/src/xul/nsXULFormControlAccessible.h @@ -260,8 +260,8 @@ public: NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName); NS_IMETHOD DoAction(PRUint8 index); - // nsIAccessibleEditableText - NS_IMETHOD GetAssociatedEditor(nsIEditor **aEditor); + // nsHyperTextAccessible + virtual already_AddRefed GetEditor() const; // nsAccessible virtual void ApplyARIAState(PRUint64* aState); diff --git a/accessible/tests/mochitest/common.js b/accessible/tests/mochitest/common.js index d17b8c1721d2..a80193e6fad5 100644 --- a/accessible/tests/mochitest/common.js +++ b/accessible/tests/mochitest/common.js @@ -613,9 +613,9 @@ function getNodePrettyName(aNode) function getObjAddress(aObj) { var exp = /native\s*@\s*(0x[a-f0-9]+)/g; - var match = exp.exec(aObj.valueOf()); + var match = exp.exec(aObj.toString()); if (match) return match[1]; - return aObj.valueOf(); + return aObj.toString(); } diff --git a/accessible/tests/mochitest/elm/test_nsApplicationAcc.html b/accessible/tests/mochitest/elm/test_nsApplicationAcc.html index dece93c690b2..58763e437233 100644 --- a/accessible/tests/mochitest/elm/test_nsApplicationAcc.html +++ b/accessible/tests/mochitest/elm/test_nsApplicationAcc.html @@ -15,41 +15,46 @@ diff --git a/accessible/tests/mochitest/events.js b/accessible/tests/mochitest/events.js index 8a2dd91c37dd..f64bf4d779ba 100644 --- a/accessible/tests/mochitest/events.js +++ b/accessible/tests/mochitest/events.js @@ -304,12 +304,20 @@ function eventQueue(aEventType) // Start processing of next invoker. invoker = this.getNextInvoker(); + this.setEventHandler(invoker); + if (gLogger.isEnabled()) { gLogger.logToConsole("Event queue: \n invoke: " + invoker.getID()); gLogger.logToDOM("EQ: invoke: " + invoker.getID(), true); } - this.setEventHandler(invoker); + var infoText = "Invoke the '" + invoker.getID() + "' test { "; + for (var idx = 0; idx < this.mEventSeq.length; idx++) { + infoText += this.isEventUnexpected(idx) ? "un" : ""; + infoText += "expected '" + this.getEventTypeAsString(idx) + "' event; "; + } + infoText += " }"; + info(infoText); if (invoker.invoke() == INVOKER_ACTION_FAILED) { // Invoker failed to prepare action, fail and finish tests. diff --git a/accessible/tests/mochitest/events/test_focus_autocomplete.xul b/accessible/tests/mochitest/events/test_focus_autocomplete.xul index 280893e76f76..56af36b40043 100644 --- a/accessible/tests/mochitest/events/test_focus_autocomplete.xul +++ b/accessible/tests/mochitest/events/test_focus_autocomplete.xul @@ -384,14 +384,11 @@ { if (SEAMONKEY) { todo(false, "Skipping this test on SeaMonkey ftb. (Bug 718237)"); + shutdownAutoComplete(); SimpleTest.finish(); return; } - // register 'test-a11y-search' autocomplete search - initAutoComplete([ "hello", "hi" ], - [ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]); - gInitQueue = new eventQueue(); gInitQueue.push(new loadFormAutoComplete("iframe")); gInitQueue.push(new initFormAutoCompleteBy("iframe", "hello")); @@ -461,6 +458,12 @@ } SimpleTest.waitForExplicitFinish(); + + // Register 'test-a11y-search' autocomplete search. + // XPFE AutoComplete needs to register early. + initAutoComplete([ "hello", "hi" ], + [ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]); + addA11yLoadEvent(initTests); ]]> diff --git a/accessible/tests/mochitest/states/test_expandable.xul b/accessible/tests/mochitest/states/test_expandable.xul index 95c52b5c25b6..def41732b90f 100644 --- a/accessible/tests/mochitest/states/test_expandable.xul +++ b/accessible/tests/mochitest/states/test_expandable.xul @@ -35,10 +35,6 @@ var gQueue = null; function doTest() { - // register 'test-a11y-search' autocomplete search - initAutoComplete([ "hello", "hi" ], - [ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]); - gQueue = new eventQueue(); gQueue.push(new openCombobox("menulist")); @@ -73,6 +69,12 @@ } SimpleTest.waitForExplicitFinish(); + + // Register 'test-a11y-search' autocomplete search. + // XPFE AutoComplete needs to register early. + initAutoComplete([ "hello", "hi" ], + [ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]); + addA11yLoadEvent(doTest); ]]> diff --git a/b2g/chrome/content/webapi.js b/b2g/chrome/content/webapi.js index a672be1e337c..2ae078e095fb 100644 --- a/b2g/chrome/content/webapi.js +++ b/b2g/chrome/content/webapi.js @@ -181,7 +181,11 @@ const ContentPanning = { case 'click': evt.stopPropagation(); evt.preventDefault(); - evt.target.removeEventListener('click', this, true); + + let target = evt.target; + let view = target.ownerDocument ? target.ownerDocument.defaultView + : target; + view.removeEventListener('click', this, true, true); break; } }, @@ -190,16 +194,24 @@ const ContentPanning = { onTouchStart: function cp_onTouchStart(evt) { this.dragging = true; + this.panning = false; + + let oldTarget = this.target; + [this.target, this.scrollCallback] = this.getPannable(evt.target); // If there is a pan animation running (from a previous pan gesture) and // the user touch back the screen, stop this animation immediatly and - // prevent the possible click action. + // prevent the possible click action if the touch happens on the same + // target. + this.preventNextClick = false; if (KineticPanning.active) { KineticPanning.stop(); - this.preventNextClick = true; + + if (oldTarget && oldTarget == this.target) + this.preventNextClick = true; } - this.scrollCallback = this.getPannable(evt.originalTarget); + this.position.set(evt.screenX, evt.screenY); KineticPanning.record(new Point(0, 0), evt.timeStamp); }, @@ -211,14 +223,15 @@ const ContentPanning = { this.onTouchMove(evt); - let pan = KineticPanning.isPan(); let click = evt.detail; - if (click && (pan || this.preventNextClick)) - evt.target.addEventListener('click', this, true); + if (this.target && click && (this.panning || this.preventNextClick)) { + let target = this.target; + let view = target.ownerDocument ? target.ownerDocument.defaultView + : target; + view.addEventListener('click', this, true, true); + } - this.preventNextClick = false; - - if (pan) + if (this.panning) KineticPanning.start(this); }, @@ -232,6 +245,13 @@ const ContentPanning = { KineticPanning.record(delta, evt.timeStamp); this.scrollCallback(delta.scale(-1)); + + // If a pan action happens, cancel the active state of the + // current target. + if (!this.panning && KineticPanning.isPan()) { + this.panning = true; + this._resetActive(); + } }, @@ -249,7 +269,7 @@ const ContentPanning = { getPannable: function cp_getPannable(node) { if (!(node instanceof Ci.nsIDOMHTMLElement) || node.tagName == 'HTML') - return null; + return [null, null]; let content = node.ownerDocument.defaultView; while (!(node instanceof Ci.nsIDOMHTMLBodyElement)) { @@ -266,12 +286,12 @@ const ContentPanning = { let isScroll = (overflow.indexOf('scroll') != -1); if (isScroll || isAuto) - return this._generateCallback(node); + return [node, this._generateCallback(node)]; node = node.parentNode; } - return this._generateCallback(content); + return [content, this._generateCallback(content)]; }, _generateCallback: function cp_generateCallback(content) { @@ -290,6 +310,19 @@ const ContentPanning = { } } return scroll; + }, + + get _domUtils() { + delete this._domUtils; + return this._domUtils = Cc['@mozilla.org/inspector/dom-utils;1'] + .getService(Ci.inIDOMUtils); + }, + + _resetActive: function cp_resetActive() { + let root = this.target.ownerDocument || this.target.document; + + const kStateActive = 0x00000001; + this._domUtils.setContentState(root.documentElement, kStateActive); } }; @@ -370,6 +403,7 @@ const KineticPanning = { return; this.momentums = []; + this.distance.set(0, 0); this.target.onKineticEnd(); this.target = null; @@ -378,23 +412,24 @@ const KineticPanning = { momentums: [], record: function kp_record(delta, timestamp) { this.momentums.push({ 'time': timestamp, 'dx' : delta.x, 'dy' : delta.y }); + this.distance.add(delta.x, delta.y); }, - isPan: function cp_isPan() { + get threshold() { let dpi = content.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils) .displayDPI; let threshold = Services.prefs.getIntPref('ui.dragThresholdX') / 240 * dpi; - let deltaX = 0; - let deltaY = 0; - let start = this.momentums[0].time; - return this.momentums.slice(1).some(function(momentum) { - deltaX += momentum.dx; - deltaY += momentum.dy; - return (Math.abs(deltaX) > threshold) || (Math.abs(deltaY) > threshold); - }); + delete this.threshold; + return this.threshold = threshold; + }, + + distance: new Point(0, 0), + isPan: function cp_isPan() { + return (Math.abs(this.distance.x) > this.threshold || + Math.abs(this.distance.y) > this.threshold); }, _startAnimation: function kp_startAnimation() { diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 9d302b991961..d837e723ac1d 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -1563,49 +1563,6 @@ function delayedStartup(isLoadingBlank, mustLoadSidebar) { gHomeButton.updateTooltip(homeButton); gHomeButton.updatePersonalToolbarStyle(homeButton); -#ifdef HAVE_SHELL_SERVICE - // Perform default browser checking (after window opens). - var shell = getShellService(); - if (shell) { -#ifdef DEBUG - var shouldCheck = false; -#else - var shouldCheck = shell.shouldCheckDefaultBrowser; -#endif - var willRecoverSession = false; - try { - var ss = Cc["@mozilla.org/browser/sessionstartup;1"]. - getService(Ci.nsISessionStartup); - willRecoverSession = - (ss.sessionType == Ci.nsISessionStartup.RECOVER_SESSION); - } - catch (ex) { /* never mind; suppose SessionStore is broken */ } - if (shouldCheck && !shell.isDefaultBrowser(true) && !willRecoverSession) { - // Delay the set-default-browser prompt so it doesn't block - // initialisation of the session store service. - setTimeout(function () { - var brandBundle = document.getElementById("bundle_brand"); - var shellBundle = document.getElementById("bundle_shell"); - - var brandShortName = brandBundle.getString("brandShortName"); - var promptTitle = shellBundle.getString("setDefaultBrowserTitle"); - var promptMessage = shellBundle.getFormattedString("setDefaultBrowserMessage", - [brandShortName]); - var checkboxLabel = shellBundle.getFormattedString("setDefaultBrowserDontAsk", - [brandShortName]); - var checkEveryTime = { value: shouldCheck }; - var ps = Services.prompt; - var rv = ps.confirmEx(window, promptTitle, promptMessage, - ps.STD_YES_NO_BUTTONS, - null, null, null, checkboxLabel, checkEveryTime); - if (rv == 0) - shell.setDefaultBrowser(true, false); - shell.shouldCheckDefaultBrowser = checkEveryTime.value; - }, 0); - } - } -#endif - // BiDi UI gBidiUI = isBidiEnabled(); if (gBidiUI) { @@ -3989,10 +3946,22 @@ var FullScreen = { } }, - exitDomFullScreen : function(e) { + exitDomFullScreen : function() { document.mozCancelFullScreen(); }, + handleEvent: function (event) { + switch (event.type) { + case "deactivate": + // We must call exitDomFullScreen asynchronously, since "deactivate" is + // dispatched in the middle of the focus manager's window lowering code, + // and the focus manager gets confused if we exit fullscreen mode in the + // middle of window lowering. See bug 729872. + setTimeout(this.exitDomFullScreen.bind(this), 0); + break; + } + }, + enterDomFullScreen : function(event) { if (!document.mozFullScreen) { return; @@ -4039,7 +4008,7 @@ var FullScreen = { // Exit DOM full-screen mode when the browser window loses focus (ALT+TAB, etc). if (gPrefService.getBoolPref("full-screen-api.exit-on-deactivate")) { - window.addEventListener("deactivate", this.exitDomFullScreen, true); + window.addEventListener("deactivate", this); } // Cancel any "hide the toolbar" animation which is in progress, and make @@ -4074,7 +4043,7 @@ var FullScreen = { gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen); gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen); gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen); - window.removeEventListener("deactivate", this.exitDomFullScreen, true); + window.removeEventListener("deactivate", this); } }, @@ -5362,6 +5331,7 @@ function setToolbarVisibility(toolbar, isVisible) { var TabsOnTop = { init: function TabsOnTop_init() { + this._initialized = true; this.syncUI(); Services.prefs.addObserver(this._prefName, this, false); }, @@ -5375,6 +5345,9 @@ var TabsOnTop = { }, syncUI: function () { + if (!this._initialized) + return; + let userEnabled = Services.prefs.getBoolPref(this._prefName); let enabled = userEnabled && gBrowser.tabContainer.visible; @@ -6121,103 +6094,114 @@ function charsetLoadListener(event) { } } -/* Begin Page Style Functions */ -function getAllStyleSheets(frameset) { - var styleSheetsArray = Array.slice(frameset.document.styleSheets); - for (let i = 0; i < frameset.frames.length; i++) { - let frameSheets = getAllStyleSheets(frameset.frames[i]); - styleSheetsArray = styleSheetsArray.concat(frameSheets); - } - return styleSheetsArray; -} -function stylesheetFillPopup(menuPopup) { - var noStyle = menuPopup.firstChild; - var persistentOnly = noStyle.nextSibling; - var sep = persistentOnly.nextSibling; - while (sep.nextSibling) - menuPopup.removeChild(sep.nextSibling); +var gPageStyleMenu = { - var styleSheets = getAllStyleSheets(window.content); - var currentStyleSheets = {}; - var styleDisabled = getMarkupDocumentViewer().authorStyleDisabled; - var haveAltSheets = false; - var altStyleSelected = false; + getAllStyleSheets: function (frameset) { + var styleSheetsArray = Array.slice(frameset.document.styleSheets); + for (let i = 0; i < frameset.frames.length; i++) { + let frameSheets = this.getAllStyleSheets(frameset.frames[i]); + styleSheetsArray = styleSheetsArray.concat(frameSheets); + } + return styleSheetsArray; + }, - for (let i = 0; i < styleSheets.length; ++i) { - let currentStyleSheet = styleSheets[i]; + stylesheetFillPopup: function (menuPopup) { + var noStyle = menuPopup.firstChild; + var persistentOnly = noStyle.nextSibling; + var sep = persistentOnly.nextSibling; + while (sep.nextSibling) + menuPopup.removeChild(sep.nextSibling); - if (!currentStyleSheet.title) - continue; + var styleSheets = this.getAllStyleSheets(window.content); + var currentStyleSheets = {}; + var styleDisabled = getMarkupDocumentViewer().authorStyleDisabled; + var haveAltSheets = false; + var altStyleSelected = false; - // Skip any stylesheets whose media attribute doesn't match. - if (currentStyleSheet.media.length > 0) { - let mediaQueryList = currentStyleSheet.media.mediaText; - if (!window.content.matchMedia(mediaQueryList).matches) + for (let i = 0; i < styleSheets.length; ++i) { + let currentStyleSheet = styleSheets[i]; + + if (!currentStyleSheet.title) continue; + + // Skip any stylesheets whose media attribute doesn't match. + if (currentStyleSheet.media.length > 0) { + let mediaQueryList = currentStyleSheet.media.mediaText; + if (!window.content.matchMedia(mediaQueryList).matches) + continue; + } + + if (!currentStyleSheet.disabled) + altStyleSelected = true; + + haveAltSheets = true; + + let lastWithSameTitle = null; + if (currentStyleSheet.title in currentStyleSheets) + lastWithSameTitle = currentStyleSheets[currentStyleSheet.title]; + + if (!lastWithSameTitle) { + let menuItem = document.createElement("menuitem"); + menuItem.setAttribute("type", "radio"); + menuItem.setAttribute("label", currentStyleSheet.title); + menuItem.setAttribute("data", currentStyleSheet.title); + menuItem.setAttribute("checked", !currentStyleSheet.disabled && !styleDisabled); + menuPopup.appendChild(menuItem); + currentStyleSheets[currentStyleSheet.title] = menuItem; + } else if (currentStyleSheet.disabled) { + lastWithSameTitle.removeAttribute("checked"); + } } - if (!currentStyleSheet.disabled) - altStyleSelected = true; + noStyle.setAttribute("checked", styleDisabled); + persistentOnly.setAttribute("checked", !altStyleSelected && !styleDisabled); + persistentOnly.hidden = (window.content.document.preferredStyleSheetSet) ? haveAltSheets : false; + sep.hidden = (noStyle.hidden && persistentOnly.hidden) || !haveAltSheets; + return true; + }, - haveAltSheets = true; + stylesheetInFrame: function (frame, title) { + return Array.some(frame.document.styleSheets, + function (stylesheet) stylesheet.title == title); + }, - let lastWithSameTitle = null; - if (currentStyleSheet.title in currentStyleSheets) - lastWithSameTitle = currentStyleSheets[currentStyleSheet.title]; + stylesheetSwitchFrame: function (frame, title) { + var docStyleSheets = frame.document.styleSheets; - if (!lastWithSameTitle) { - let menuItem = document.createElement("menuitem"); - menuItem.setAttribute("type", "radio"); - menuItem.setAttribute("label", currentStyleSheet.title); - menuItem.setAttribute("data", currentStyleSheet.title); - menuItem.setAttribute("checked", !currentStyleSheet.disabled && !styleDisabled); - menuPopup.appendChild(menuItem); - currentStyleSheets[currentStyleSheet.title] = menuItem; - } else if (currentStyleSheet.disabled) { - lastWithSameTitle.removeAttribute("checked"); + for (let i = 0; i < docStyleSheets.length; ++i) { + let docStyleSheet = docStyleSheets[i]; + + if (title == "_nostyle") + docStyleSheet.disabled = true; + else if (docStyleSheet.title) + docStyleSheet.disabled = (docStyleSheet.title != title); + else if (docStyleSheet.disabled) + docStyleSheet.disabled = false; } - } + }, - noStyle.setAttribute("checked", styleDisabled); - persistentOnly.setAttribute("checked", !altStyleSelected && !styleDisabled); - persistentOnly.hidden = (window.content.document.preferredStyleSheetSet) ? haveAltSheets : false; - sep.hidden = (noStyle.hidden && persistentOnly.hidden) || !haveAltSheets; - return true; -} + stylesheetSwitchAll: function (frameset, title) { + if (!title || title == "_nostyle" || this.stylesheetInFrame(frameset, title)) + this.stylesheetSwitchFrame(frameset, title); -function stylesheetInFrame(frame, title) { - return Array.some(frame.document.styleSheets, - function (stylesheet) stylesheet.title == title); -} + for (let i = 0; i < frameset.frames.length; i++) + this.stylesheetSwitchAll(frameset.frames[i], title); + }, -function stylesheetSwitchFrame(frame, title) { - var docStyleSheets = frame.document.styleSheets; + setStyleDisabled: function (disabled) { + getMarkupDocumentViewer().authorStyleDisabled = disabled; + }, +}; - for (let i = 0; i < docStyleSheets.length; ++i) { - let docStyleSheet = docStyleSheets[i]; +/* Legacy global page-style functions */ +var getAllStyleSheets = gPageStyleMenu.getAllStyleSheets; +var stylesheetFillPopup = gPageStyleMenu.stylesheetFillPopup; +var stylesheetInFrame = gPageStyleMenu.stylesheetInFrame; +var stylesheetSwitchFrame = gPageStyleMenu.stylesheetSwitchFrame; +var stylesheetSwitchAll = gPageStyleMenu.stylesheetSwitchAll; +var setStyleDisabled = gPageStyleMenu.setStyleDisabled; - if (title == "_nostyle") - docStyleSheet.disabled = true; - else if (docStyleSheet.title) - docStyleSheet.disabled = (docStyleSheet.title != title); - else if (docStyleSheet.disabled) - docStyleSheet.disabled = false; - } -} - -function stylesheetSwitchAll(frameset, title) { - if (!title || title == "_nostyle" || stylesheetInFrame(frameset, title)) - stylesheetSwitchFrame(frameset, title); - - for (let i = 0; i < frameset.frames.length; i++) - stylesheetSwitchAll(frameset.frames[i], title); -} - -function setStyleDisabled(disabled) { - getMarkupDocumentViewer().authorStyleDisabled = disabled; -} -/* End of the Page Style functions */ var BrowserOffline = { _inited: false, diff --git a/browser/base/content/test/browser_aboutSyncProgress.js b/browser/base/content/test/browser_aboutSyncProgress.js index 10a33ac45093..0f912bed5699 100644 --- a/browser/base/content/test/browser_aboutSyncProgress.js +++ b/browser/base/content/test/browser_aboutSyncProgress.js @@ -3,7 +3,6 @@ */ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; -Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://services-sync/main.js"); let gTests = [ { diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index 2967db789db6..e029167ad4e7 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -428,6 +428,49 @@ BrowserGlue.prototype = { let keywordURLUserSet = Services.prefs.prefHasUserValue("keyword.URL"); Services.telemetry.getHistogramById("FX_KEYWORD_URL_USERSET").add(keywordURLUserSet); + + // Perform default browser checking. + var shell; + try { + shell = Components.classes["@mozilla.org/browser/shell-service;1"] + .getService(Components.interfaces.nsIShellService); + } catch (e) { } + if (shell) { +#ifdef DEBUG + var shouldCheck = false; +#else + var shouldCheck = shell.shouldCheckDefaultBrowser; +#endif + var willRecoverSession = false; + try { + var ss = Cc["@mozilla.org/browser/sessionstartup;1"]. + getService(Ci.nsISessionStartup); + willRecoverSession = + (ss.sessionType == Ci.nsISessionStartup.RECOVER_SESSION); + } + catch (ex) { /* never mind; suppose SessionStore is broken */ } + if (shouldCheck && !shell.isDefaultBrowser(true) && !willRecoverSession) { + Services.tm.mainThread.dispatch(function() { + var brandBundle = win.document.getElementById("bundle_brand"); + var shellBundle = win.document.getElementById("bundle_shell"); + + var brandShortName = brandBundle.getString("brandShortName"); + var promptTitle = shellBundle.getString("setDefaultBrowserTitle"); + var promptMessage = shellBundle.getFormattedString("setDefaultBrowserMessage", + [brandShortName]); + var checkboxLabel = shellBundle.getFormattedString("setDefaultBrowserDontAsk", + [brandShortName]); + var checkEveryTime = { value: shouldCheck }; + var ps = Services.prompt; + var rv = ps.confirmEx(win, promptTitle, promptMessage, + ps.STD_YES_NO_BUTTONS, + null, null, null, checkboxLabel, checkEveryTime); + if (rv == 0) + shell.setDefaultBrowser(true, false); + shell.shouldCheckDefaultBrowser = checkEveryTime.value; + }, Ci.nsIThread.DISPATCH_NORMAL); + } + } }, _onQuitRequest: function BG__onQuitRequest(aCancelQuit, aQuitType) { diff --git a/browser/components/places/content/browserPlacesViews.js b/browser/components/places/content/browserPlacesViews.js index b9bc5475e0b1..0cf3c274c0f1 100644 --- a/browser/components/places/content/browserPlacesViews.js +++ b/browser/components/places/content/browserPlacesViews.js @@ -167,73 +167,33 @@ PlacesViewBase.prototype = { }, _cleanPopup: function PVB_cleanPopup(aPopup) { - // Remove places popup children and update markers to keep track of - // their indices. - let start = aPopup._startMarker != -1 ? aPopup._startMarker + 1 : 0; - let end = aPopup._endMarker != -1 ? aPopup._endMarker : - aPopup.childNodes.length; - let items = []; - - // Automatically adjust the start and the end markers. - let firstNonStaticNodeFound = false; - for (let i = start; i < end; ++i) { - let item = aPopup.childNodes[i]; - if (item.getAttribute("builder") == "end") { - // we need to do this for menus that have static content at the end but - // are initially empty, eg. the history menu, we need to know where to - // start inserting new items. - aPopup._endMarker = i; - break; - } - - if (item._placesNode) { - items.push(item); - firstNonStaticNodeFound = true; - } - else { - // This is static content. - if (!firstNonStaticNodeFound) { - // We are at the beginning of the popup, in static content. - // The markers are initialized in menu.xml, in the base binding. - aPopup._startMarker++; - } - else { - // We are at the end of the popup, after places nodes - aPopup._endMarker = i; - break; - } - } - } - - for (let i = 0; i < items.length; ++i) { - aPopup.removeChild(items[i]); - if (aPopup._endMarker != -1) - aPopup._endMarker--; + // Remove Places nodes from the popup. + let child = aPopup._startMarker; + while (child.nextSibling != aPopup._endMarker) { + if (child.nextSibling._placesNode) + aPopup.removeChild(child.nextSibling); + else + child = child.nextSibling; } }, _rebuildPopup: function PVB__rebuildPopup(aPopup) { - this._cleanPopup(aPopup); - let resultNode = aPopup._placesNode; if (!resultNode.containerOpen) return; if (resultNode._feedURI) { - aPopup.removeAttribute("emptyplacesresult"); - if (aPopup._emptyMenuItem) { - aPopup._emptyMenuItem.hidden = true; - } + this._setEmptyPopupStatus(aPopup, false); aPopup._built = true; this._populateLivemarkPopup(aPopup); return; } + this._cleanPopup(aPopup); + let cc = resultNode.childCount; if (cc > 0) { - aPopup.removeAttribute("emptyplacesresult"); - if (aPopup._emptyMenuItem) - aPopup._emptyMenuItem.hidden = true; + this._setEmptyPopupStatus(aPopup, false); for (let i = 0; i < cc; ++i) { let child = resultNode.getChild(i); @@ -241,11 +201,7 @@ PlacesViewBase.prototype = { } } else { - aPopup.setAttribute("emptyplacesresult", "true"); - // This menu is empty. If there is no static content, add - // an element to show it is empty. - if (aPopup._startMarker == -1 && aPopup._endMarker == -1) - this._showEmptyMenuItem(aPopup); + this._setEmptyPopupStatus(aPopup, true); } aPopup._built = true; }, @@ -260,17 +216,28 @@ PlacesViewBase.prototype = { aChild.parentNode.removeChild(aChild); }, - _showEmptyMenuItem: function PVB__showEmptyMenuItem(aPopup) { - if (aPopup._emptyMenuItem) { - aPopup._emptyMenuItem.hidden = false; - return; + _setEmptyPopupStatus: + function PVB__setEmptyPopupStatus(aPopup, aEmpty) { + if (!aPopup._emptyMenuitem) { + let label = PlacesUIUtils.getString("bookmarksMenuEmptyFolder"); + aPopup._emptyMenuitem = document.createElement("menuitem"); + aPopup._emptyMenuitem.setAttribute("label", label); + aPopup._emptyMenuitem.setAttribute("disabled", true); } - let label = PlacesUIUtils.getString("bookmarksMenuEmptyFolder"); - aPopup._emptyMenuItem = document.createElement("menuitem"); - aPopup._emptyMenuItem.setAttribute("label", label); - aPopup._emptyMenuItem.setAttribute("disabled", true); - aPopup.appendChild(aPopup._emptyMenuItem); + if (aEmpty) { + aPopup.setAttribute("emptyplacesresult", "true"); + // Don't add the menuitem if there is static content. + if (!aPopup._startMarker.previousSibling && + !aPopup._endMarker.nextSibling) + aPopup.insertBefore(aPopup._emptyMenuitem, aPopup._endMarker); + } + else { + aPopup.removeAttribute("emptyplacesresult"); + try { + aPopup.removeChild(aPopup._emptyMenuitem); + } catch (ex) {} + } }, _createMenuItemForPlacesNode: @@ -308,6 +275,12 @@ PlacesViewBase.prototype = { function (aStatus, aLivemark) { if (Components.isSuccessCode(aStatus)) { element.setAttribute("livemark", "true"); +#ifdef XP_MACOSX + // OS X native menubar doesn't track list-style-images since + // it doesn't have a frame (bug 733415). Thus enforce updating. + element.setAttribute("image", ""); + element.removeAttribute("image"); +#endif // Set an expando on the node, controller will use it to build // its metadata. aPlacesNode._feedURI = aLivemark.feedURI; @@ -319,12 +292,11 @@ PlacesViewBase.prototype = { let popup = document.createElement("menupopup"); popup._placesNode = PlacesUtils.asContainer(aPlacesNode); - if (this._nativeView) { - popup._startMarker = -1; - popup._endMarker = -1; - } - else + + if (!this._nativeView) { popup.setAttribute("placespopup", "true"); + } + #ifdef XP_MACOSX // No context menu on mac. popup.setAttribute("context", "placesContext"); @@ -354,26 +326,8 @@ PlacesViewBase.prototype = { _insertNewItemToPopup: function PVB__insertNewItemToPopup(aNewChild, aPopup, aBefore) { let element = this._createMenuItemForPlacesNode(aNewChild); - - if (aBefore) { - aPopup.insertBefore(element, aBefore); - } - else { - // Add the new element to the menu. If there is static content at - // the end of the menu, add the element before that. Otherwise, - // just add to the end. - if (aPopup._endMarker != -1) { - let lastElt = aPopup.childNodes[aPopup._endMarker]; - aPopup.insertBefore(element, lastElt); - } - else { - aPopup.appendChild(element); - } - } - - if (aPopup._endMarker != -1) - aPopup._endMarker++; - + let before = aBefore || aPopup._endMarker; + aPopup.insertBefore(element, before); return element; }, @@ -384,10 +338,8 @@ PlacesViewBase.prototype = { if (!siteUrl && aPopup._siteURIMenuitem) { aPopup.removeChild(aPopup._siteURIMenuitem); aPopup._siteURIMenuitem = null; - aPopup._startMarker--; aPopup.removeChild(aPopup._siteURIMenuseparator); aPopup._siteURIMenuseparator = null; - aPopup._startMarker--; } else if (siteUrl && !aPopup._siteURIMenuitem) { // Add "Open (Feed Name)" menuitem. @@ -407,14 +359,10 @@ PlacesViewBase.prototype = { PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label", [aPopup.parentNode.getAttribute("label")]) aPopup._siteURIMenuitem.setAttribute("label", label); - aPopup.insertBefore(aPopup._siteURIMenuitem, - aPopup.childNodes.item(aPopup._startMarker + 1)); - aPopup._startMarker++; + aPopup.insertBefore(aPopup._siteURIMenuitem, aPopup._startMarker); aPopup._siteURIMenuseparator = document.createElement("menuseparator"); - aPopup.insertBefore(aPopup._siteURIMenuseparator, - aPopup.childNodes.item(aPopup._startMarker + 1)); - aPopup._startMarker++; + aPopup.insertBefore(aPopup._siteURIMenuseparator, aPopup._startMarker); } }, @@ -427,7 +375,6 @@ PlacesViewBase.prototype = { */ _setLivemarkStatusMenuItem: function PVB_setLivemarkStatusMenuItem(aPopup, aStatus) { - let itemId = aPopup._placesNode.itemId; let statusMenuitem = aPopup._statusMenuitem; let stringId = ""; if (aStatus == Ci.mozILivemark.STATUS_LOADING) @@ -439,12 +386,11 @@ PlacesViewBase.prototype = { // Create the status menuitem and cache it in the popup object. statusMenuitem = document.createElement("menuitem"); statusMenuitem.setAttribute("livemarkStatus", stringId); + statusMenuitem.className = "livemarkstatus-menuitem"; statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId)); statusMenuitem.setAttribute("disabled", true); - aPopup.insertBefore(statusMenuitem, - aPopup.childNodes.item(aPopup._startMarker + 1)); + aPopup.insertBefore(statusMenuitem, aPopup._startMarker.nextSibling); aPopup._statusMenuitem = statusMenuitem; - aPopup._startMarker++; } else if (stringId && statusMenuitem.getAttribute("livemarkStatus") != stringId) { @@ -455,7 +401,6 @@ PlacesViewBase.prototype = { // The livemark has finished loading. aPopup.removeChild(aPopup._statusMenuitem); aPopup._statusMenuitem = null; - aPopup._startMarker--; } }, @@ -517,6 +462,12 @@ PlacesViewBase.prototype = { let menu = elt.parentNode; if (!menu.hasAttribute("livemark")) { menu.setAttribute("livemark", "true"); +#ifdef XP_MACOSX + // OS X native menubar doesn't track list-style-images since + // it doesn't have a frame (bug 733415). Thus enforce updating. + menu.setAttribute("image", ""); + menu.removeAttribute("image"); +#endif } PlacesUtils.livemarks.getLivemark( @@ -580,13 +531,8 @@ PlacesViewBase.prototype = { // Figure out if we need to show the "" menu-item. // TODO Bug 517701: This doesn't seem to handle the case of an empty // root. - if (!parentElt.hasChildNodes() || - (parentElt.childNodes.length == 1 && - parentElt.firstChild == parentElt._emptyMenuItem)) - this._showEmptyMenuItem(parentElt); - - if (parentElt._endMarker != -1) - parentElt._endMarker--; + if (parentElt._startMarker.nextSibling == parentElt._endMarker) + this._setEmptyPopupStatus(parentElt, true); } }, @@ -620,8 +566,9 @@ PlacesViewBase.prototype = { if (aPlacesNode.parent && aPlacesNode.parent._feedURI) { // Find the node in the parent. let popup = aPlacesNode.parent._DOMElement; - for (let i = popup._startMarker; i < popup.childNodes.length; i++) { - let child = popup.childNodes[i]; + for (let child = popup._startMarker.nextSibling; + child != popup._endMarker; + child = child.nextSibling) { if (child._placesNode && child._placesNode.uri == aPlacesNode.uri) { if (aCount) child.setAttribute("visited", "true"); @@ -649,11 +596,11 @@ PlacesViewBase.prototype = { if (!parentElt._built) return; - let index = parentElt._startMarker + 1 + aIndex; + let index = Array.indexOf(parentElt.childNodes, parentElt._startMarker) + + aIndex + 1; this._insertNewItemToPopup(aPlacesNode, parentElt, parentElt.childNodes[index]); - if (parentElt._emptyMenuItem) - parentElt._emptyMenuItem.hidden = true; + this._setEmptyPopupStatus(parentElt, false); }, nodeMoved: @@ -684,7 +631,8 @@ PlacesViewBase.prototype = { if (parentElt._built) { // Move the node. parentElt.removeChild(elt); - let index = parentElt._startMarker + 1 + aNewIndex; + let index = Array.indexOf(parentElt.childNodes, parentElt._startMarker) + + aNewIndex + 1; parentElt.insertBefore(elt, parentElt.childNodes[index]); } }, @@ -821,11 +769,9 @@ PlacesViewBase.prototype = { if (aPopup._endOptOpenAllInTabs) { aPopup.removeChild(aPopup._endOptOpenAllInTabs); aPopup._endOptOpenAllInTabs = null; - aPopup._endMarker--; aPopup.removeChild(aPopup._endOptSeparator); aPopup._endOptSeparator = null; - aPopup._endMarker--; } } else if (!aPopup._endOptOpenAllInTabs) { @@ -833,7 +779,6 @@ PlacesViewBase.prototype = { aPopup._endOptSeparator = document.createElement("menuseparator"); aPopup._endOptSeparator.className = "bookmarks-actions-menuseparator"; aPopup.appendChild(aPopup._endOptSeparator); - aPopup._endMarker++; // Add the "Open All in Tabs" menuitem. aPopup._endOptOpenAllInTabs = document.createElement("menuitem"); @@ -846,13 +791,51 @@ PlacesViewBase.prototype = { aPopup._endOptOpenAllInTabs.setAttribute("label", gNavigatorBundle.getString("menuOpenAllInTabs.label")); aPopup.appendChild(aPopup._endOptOpenAllInTabs); - aPopup._endMarker++; + } + }, + + _ensureMarkers: function PVB__ensureMarkers(aPopup) { + if (aPopup._startMarker) + return; + + // _startMarker is an hidden menuseparator that lives before places nodes. + aPopup._startMarker = document.createElement("menuseparator"); + aPopup._startMarker.hidden = true; + aPopup.insertBefore(aPopup._startMarker, aPopup.firstChild); + + // _endMarker is an hidden menuseparator that lives after places nodes. + aPopup._endMarker = document.createElement("menuseparator"); + aPopup._endMarker.hidden = true; + aPopup.appendChild(aPopup._endMarker); + + // Move the markers to the right position. + let firstNonStaticNodeFound = false; + for (let i = 0; i < aPopup.childNodes.length; i++) { + let child = aPopup.childNodes[i]; + // Menus that have static content at the end, but are initially empty, + // use a special "builder" attribute to figure out where to start + // inserting places nodes. + if (child.getAttribute("builder") == "end") { + aPopup.insertBefore(aPopup._endMarker, child); + break; + } + + if (child._placesNode && !firstNonStaticNodeFound) { + firstNonStaticNodeFound = true; + aPopup.insertBefore(aPopup._startMarker, child); + } + } + if (!firstNonStaticNodeFound) { + aPopup.insertBefore(aPopup._startMarker, aPopup._endMarker); } }, _onPopupShowing: function PVB__onPopupShowing(aEvent) { // Avoid handling popupshowing of inner views. let popup = aEvent.originalTarget; + + this._ensureMarkers(popup); + if (popup._placesNode && PlacesUIUtils.getViewForNode(popup) == this) { if (!popup._placesNode.containerOpen) popup._placesNode.containerOpen = true; @@ -1808,8 +1791,6 @@ function PlacesMenu(aPopupShowingEvent, aPlace) { #ifdef XP_MACOSX if (this._viewElt.parentNode.localName == "menubar") { this._nativeView = true; - this._rootElt._startMarker = -1; - this._rootElt._endMarker = -1; } #endif @@ -1829,8 +1810,6 @@ PlacesMenu.prototype = { _removeChild: function PM_removeChild(aChild) { PlacesViewBase.prototype._removeChild.apply(this, arguments); - if (this._endMarker != -1) - this._endMarker--; }, uninit: function PM_uninit() { diff --git a/browser/components/places/content/menu.xml b/browser/components/places/content/menu.xml index 52d31524a8ca..f11dc3a1766f 100644 --- a/browser/components/places/content/menu.xml +++ b/browser/components/places/content/menu.xml @@ -72,10 +72,6 @@ "popup-internal-box"); - - -1 - -1 - PlacesUIUtils.getViewForNode(this); @@ -83,17 +79,16 @@ = this.childNodes[this._endMarker].boxObject.y) - betweenMarkers = false; + // Don't draw the drop indicator outside of markers. + // The markers are hidden, since otherwise sometimes popups acquire + // scrollboxes on OS X, so we can't use them directly. + let firstChildTop = this._startMarker.nextSibling.boxObject.y; + let lastChildBottom = this._endMarker.previousSibling.boxObject.y + + this._endMarker.previousSibling.boxObject.height; + let betweenMarkers = target.boxObject.y >= firstChildTop || + target.boxObject.y <= lastChildBottom; // Hide the dropmarker if current node is not a Places node. return !(target && target._placesNode && betweenMarkers); diff --git a/browser/components/places/tests/unit/test_bookmarks_html.js b/browser/components/places/tests/unit/test_bookmarks_html.js index b32430d8def4..8d709d80065f 100644 --- a/browser/components/places/tests/unit/test_bookmarks_html.js +++ b/browser/components/places/tests/unit/test_bookmarks_html.js @@ -42,7 +42,7 @@ let test_bookmarks = { menu: [ { title: "Mozilla Firefox", children: [ - { title: "Help and Tutorials", + { title: "Help and Tutorials", url: "http://en-us.www.mozilla.com/en-US/firefox/help/", icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" }, @@ -57,8 +57,8 @@ let test_bookmarks = { { title: "About Us", url: "http://en-us.www.mozilla.com/en-US/about/", icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" - }, - ], + } + ] }, { title: "test", description: "folder test comment", @@ -72,10 +72,10 @@ let test_bookmarks = { keyword: "test", sidebar: true, postData: "hidden1%3Dbar&text1%3D%25s", - charset: "ISO-8859-1", - }, + charset: "ISO-8859-1" + } ] - }, + } ], toolbar: [ { title: "Getting Started", @@ -84,14 +84,14 @@ let test_bookmarks = { }, { title: "Latest Headlines", url: "http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/", - feedUrl: "http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml", + feedUrl: "http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml" } ], unfiled: [ { title: "Example.tld", - url: "http://example.tld/", - }, - ], + url: "http://example.tld/" + } + ] }; // Pre-Places bookmarks.html file pointer. @@ -303,6 +303,8 @@ add_test(function test_import_ontop() function testImportedBookmarks() { for (let group in test_bookmarks) { + do_print("[testImportedBookmarks()] Checking group '" + group + "'"); + let root; switch (group) { case "menu": @@ -335,6 +337,7 @@ function testImportedBookmarksToFolder(aFolder) for (let i = 0; i < root.childCount; i++) { let child = root.getChild(i); + // This check depends on all "menu" bookmarks being listed first in the imported file :-| if (i < rootFolderCount) { checkItem(test_bookmarks.menu[i], child); } @@ -343,9 +346,8 @@ function testImportedBookmarksToFolder(aFolder) let group = /Toolbar/.test(container.title) ? test_bookmarks.toolbar : test_bookmarks.unfiled; container.containerOpen = true; - print(container.title); + do_print("[testImportedBookmarksToFolder()] Checking container '" + container.title + "'"); for (let t = 0; t < container.childCount; t++) { - print(group[t].title + " " + container.getChild(t).title); checkItem(group[t], container.getChild(t)); } container.containerOpen = false; diff --git a/browser/themes/gnomestripe/browser.css b/browser/themes/gnomestripe/browser.css index 5b91352180ad..49d6d673f925 100644 --- a/browser/themes/gnomestripe/browser.css +++ b/browser/themes/gnomestripe/browser.css @@ -199,7 +199,7 @@ menuitem.bookmark-item { } /* Bookmark items */ -.bookmark-item:not([container]) { +.bookmark-item { list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png"); } diff --git a/browser/themes/pinstripe/browser.css b/browser/themes/pinstripe/browser.css index 43b51b2786da..f5ba4f3e2c32 100644 --- a/browser/themes/pinstripe/browser.css +++ b/browser/themes/pinstripe/browser.css @@ -229,15 +229,20 @@ toolbarbutton.bookmark-item > menupopup { list-style-image: url("chrome://global/skin/tree/folder.png"); } -.query-item[container] { - list-style-image: url("chrome://browser/skin/places/history.png"); -} - -.bookmark-item[livemark] { +.bookmark-item[container][livemark] { list-style-image: url("chrome://browser/skin/page-livemarks.png"); } -.bookmark-item[query] { +.bookmark-item[container][livemark] .bookmark-item { + list-style-image: url("chrome://browser/skin/places/livemark-item.png"); + -moz-image-region: rect(0px, 16px, 16px, 0px); +} + +.bookmark-item[container][livemark] .bookmark-item[visited] { + -moz-image-region: rect(0px, 32px, 16px, 16px); +} + +.bookmark-item[container][query] { list-style-image: url("chrome://browser/skin/places/query.png"); } @@ -257,20 +262,23 @@ toolbarbutton.bookmark-item > menupopup { list-style-image: url("chrome://global/skin/tree/folder.png"); } -.bookmark-item[livemark] .menuitem-iconic { - list-style-image: url("chrome://browser/skin/places/livemark-item.png"); - -moz-image-region: rect(0px, 16px, 16px, 0px); -} - -.bookmark-item[livemark] .menuitem-iconic[visited] { - -moz-image-region: rect(0px, 32px, 16px, 16px); -} - -.bookmark-item menuitem[openInTabs], -.bookmark-item menuitem[siteURI] { +/* Workaround for native menubar inheritance */ +.openintabs-menuitem, +.openlivemarksite-menuitem, +.livemarkstatus-menuitem { list-style-image: none; } +.bookmark-item[cutting] > .toolbarbutton-icon, +.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-icon { + opacity: 0.5; +} + +.bookmark-item[cutting] > .toolbarbutton-text, +.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-text { + opacity: 0.7; +} + #wrapper-personal-bookmarks[place="palette"] > .toolbarpaletteitem-box { background: url("chrome://browser/skin/places/bookmarksToolbar.png") no-repeat center; } @@ -287,16 +295,6 @@ toolbarbutton.bookmark-item > menupopup { height: 16px; } -.bookmark-item[cutting] > .toolbarbutton-icon, -.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-icon { - opacity: 0.5; -} - -.bookmark-item[cutting] > .toolbarbutton-text, -.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-text { - opacity: 0.7; -} - #bookmarksToolbarFolderMenu, #BMB_bookmarksToolbar { list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png"); diff --git a/browser/themes/winstripe/browser.css b/browser/themes/winstripe/browser.css index 1e733b00ec15..4ef9d529b775 100644 --- a/browser/themes/winstripe/browser.css +++ b/browser/themes/winstripe/browser.css @@ -1224,7 +1224,7 @@ toolbar[mode="full"] .toolbarbutton-1 > .toolbarbutton-menubutton-button { padding: 0; background-clip: padding-box; border: 1px solid ThreeDShadow; - border-radius: 2.5px; + border-radius: 2px; } #urlbar { diff --git a/browser/themes/winstripe/places/organizer-aero.css b/browser/themes/winstripe/places/organizer-aero.css index 70f455f74ee7..f936284de493 100644 --- a/browser/themes/winstripe/places/organizer-aero.css +++ b/browser/themes/winstripe/places/organizer-aero.css @@ -74,6 +74,6 @@ -moz-padding-start: 4px; background-clip: padding-box; border: 1px solid rgba(0,0,0,.32); - border-radius: 2.5px; + border-radius: 2px; } } diff --git a/build/automation.py.in b/build/automation.py.in index a28a09d084f0..5b4a5272aed9 100644 --- a/build/automation.py.in +++ b/build/automation.py.in @@ -738,8 +738,11 @@ user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless t def killAndGetStack(self, proc, utilityPath, debuggerInfo): """Kill the process, preferrably in a way that gets us a stack trace.""" - if not debuggerInfo and not self.haveDumpedScreen: - self.dumpScreen(utilityPath) + if not debuggerInfo: + if self.haveDumpedScreen: + self.log.info("Not taking screenshot here: see the one that was previously logged") + else: + self.dumpScreen(utilityPath) if self.CRASHREPORTER and not debuggerInfo: if self.UNIXISH: @@ -796,8 +799,11 @@ user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless t if stackFixerFunction: line = stackFixerFunction(line) self.log.info(line.rstrip().decode("UTF-8", "ignore")) - if not debuggerInfo and not self.haveDumpedScreen and "TEST-UNEXPECTED-FAIL" in line and "Test timed out" in line: - self.dumpScreen(utilityPath) + if not debuggerInfo and "TEST-UNEXPECTED-FAIL" in line and "Test timed out" in line: + if self.haveDumpedScreen: + self.log.info("Not taking screenshot here: see the one that was previously logged") + else: + self.dumpScreen(utilityPath) (line, didTimeout) = self.readWithTimeout(logsource, timeout) if not hitMaxTime and maxTime and datetime.now() - startTime > timedelta(seconds = maxTime): diff --git a/build/automationutils.py b/build/automationutils.py index adaedc7a58be..c8067bd03471 100644 --- a/build/automationutils.py +++ b/build/automationutils.py @@ -459,7 +459,7 @@ class ShutdownLeakLogger(object): DOM windows (that are still around after test suite shutdown, despite running the GC) to the tests that created them and prints leak statistics. """ - MAX_LEAK_COUNT = 130 + MAX_LEAK_COUNT = 123 def __init__(self, logger): self.logger = logger diff --git a/build/mobile/robocop/Driver.java.in b/build/mobile/robocop/Driver.java.in index e358ec89a239..b05dedcda381 100644 --- a/build/mobile/robocop/Driver.java.in +++ b/build/mobile/robocop/Driver.java.in @@ -77,5 +77,5 @@ public interface Driver { * @return A 2-D array of pixels (indexed by y, then x). The pixels * are in ARGB-8888 format. */ - int[][] getPaintedSurface(); + PaintedSurface getPaintedSurface(); } diff --git a/build/mobile/robocop/FennecNativeDriver.java.in b/build/mobile/robocop/FennecNativeDriver.java.in index eb6d048dcb54..93e65b095cb4 100644 --- a/build/mobile/robocop/FennecNativeDriver.java.in +++ b/build/mobile/robocop/FennecNativeDriver.java.in @@ -48,6 +48,8 @@ import java.io.IOException; import java.nio.IntBuffer; import java.util.HashMap; import java.util.List; +import java.io.FileOutputStream; +import java.io.DataOutputStream; import java.lang.Class; import java.lang.reflect.InvocationTargetException; @@ -291,7 +293,7 @@ public class FennecNativeDriver implements Driver { return null; } - public int[][] getPaintedSurface() { + public PaintedSurface getPaintedSurface() { GLSurfaceView view = getSurfaceView(); if (view == null) { return null; @@ -309,14 +311,34 @@ public class FennecNativeDriver implements Driver { int w = view.getWidth(); int h = view.getHeight(); pixelBuffer.position(0); - int[][] pixels = new int[h][w]; - for (int y = h - 1; y >= 0; y--) { - for (int x = 0; x < w; x++) { - int agbr = pixelBuffer.get(); - pixels[y][x] = (agbr & 0xFF00FF00) | ((agbr >> 16) & 0x000000FF) | ((agbr << 16) & 0x00FF0000); + String mapFile = "/mnt/sdcard/pixels.map"; + + FileOutputStream fos = null; + DataOutputStream dos = null; + try { + fos = new FileOutputStream(mapFile); + dos = new DataOutputStream(fos); + + for (int y = h - 1; y >= 0; y--) { + for (int x = 0; x < w; x++) { + int agbr = pixelBuffer.get(); + dos.writeInt((agbr & 0xFF00FF00) | ((agbr >> 16) & 0x000000FF) | ((agbr << 16) & 0x00FF0000)); + } + } + return new PaintedSurface(mapFile, w, h); + } catch (IOException e) { + throw new RoboCopException("exception with pixel writer on file: " + mapFile); + } finally { + try { + if (dos != null && fos != null) { + dos.flush(); + dos.close(); + fos.close(); + } + } catch (IOException e) { + throw new RoboCopException("exception closing pixel writer on file: " + mapFile); } } - return pixels; } public int mHeight=0; diff --git a/build/mobile/robocop/Makefile.in b/build/mobile/robocop/Makefile.in index 65ab91fe300f..6488befce410 100644 --- a/build/mobile/robocop/Makefile.in +++ b/build/mobile/robocop/Makefile.in @@ -61,11 +61,15 @@ _JAVA_HARNESS = \ FennecNativeDriver.java \ FennecNativeElement.java \ RoboCopException.java \ + PaintedSurface.java \ $(NULL) _JAVA_TESTS = $(patsubst $(TESTPATH)/%.in,%,$(wildcard $(TESTPATH)/*.java.in)) -_TEST_FILES = $(wildcard $(TESTPATH)/*.html) +_TEST_FILES = \ + $(wildcard $(TESTPATH)/*.html) \ + $(wildcard $(TESTPATH)/*.sjs) \ + $(NULL) _ROBOCOP_TOOLS = \ $(TESTPATH)/robocop.ini \ diff --git a/build/mobile/robocop/PaintedSurface.java.in b/build/mobile/robocop/PaintedSurface.java.in new file mode 100644 index 000000000000..98bc0d58bef4 --- /dev/null +++ b/build/mobile/robocop/PaintedSurface.java.in @@ -0,0 +1,62 @@ +#filter substitution +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package @ANDROID_PACKAGE_NAME@; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; + +public class PaintedSurface { + private String mFileName = null; + private int mWidth = -1; + private int mHeight = -1; + private MappedByteBuffer mPixelBuffer = null; + + public PaintedSurface(String filename, int width, int height) { + mFileName = filename; + mWidth = width; + mHeight = height; + + try { + File f = new File(filename); + int pixelSize = (int)f.length(); + + FileInputStream pixelFile = new FileInputStream(filename); + mPixelBuffer = pixelFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, pixelSize); + } catch (java.io.FileNotFoundException e) { + e.printStackTrace(); + } catch (java.io.IOException e) { + e.printStackTrace(); + } + } + + public final int getPixelAt(int x, int y) { + if (mPixelBuffer == null) { + throw new RoboCopException("Trying to access PaintedSurface with no active PixelBuffer"); + } + + if (x >= mWidth || x < 0) { + throw new RoboCopException("Trying to access PaintedSurface with invalid x value"); + } + + if (y >= mHeight || y < 0) { + throw new RoboCopException("Trying to access PaintedSurface with invalid y value"); + } + + // The rows are reversed so row 0 is at the end and we start with the last row. + // This is why we do mHeight-y; + int index = (x + ((mHeight - y - 1) * mWidth)) * 4; + int b1 = mPixelBuffer.get(index) & 0xFF; + int b2 = mPixelBuffer.get(index + 1) & 0xFF; + int b3 = mPixelBuffer.get(index + 2) & 0xFF; + int b4 = mPixelBuffer.get(index + 3) & 0xFF; + int value = (b1 << 24) + (b2 << 16) + (b3 << 8) + (b4 << 0); + return value; + } +} + diff --git a/caps/src/nsNullPrincipalURI.cpp b/caps/src/nsNullPrincipalURI.cpp index 5fbfde23b0ce..7e1a4e707085 100644 --- a/caps/src/nsNullPrincipalURI.cpp +++ b/caps/src/nsNullPrincipalURI.cpp @@ -67,11 +67,12 @@ NS_IMPL_THREADSAFE_ADDREF(nsNullPrincipalURI) NS_IMPL_THREADSAFE_RELEASE(nsNullPrincipalURI) NS_INTERFACE_MAP_BEGIN(nsNullPrincipalURI) - NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURI) if (aIID.Equals(kNullPrincipalURIImplementationCID)) foundInterface = static_cast(this); else NS_INTERFACE_MAP_ENTRY(nsIURI) + NS_INTERFACE_MAP_ENTRY(nsISizeOf) NS_INTERFACE_MAP_END //////////////////////////////////////////////////////////////////////////////// @@ -299,3 +300,19 @@ nsNullPrincipalURI::SchemeIs(const char *aScheme, bool *_schemeIs) *_schemeIs = (0 == nsCRT::strcasecmp(mScheme.get(), aScheme)); return NS_OK; } + +//////////////////////////////////////////////////////////////////////////////// +//// nsISizeOf + +size_t +nsNullPrincipalURI::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const +{ + return mScheme.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +size_t +nsNullPrincipalURI::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + diff --git a/caps/src/nsNullPrincipalURI.h b/caps/src/nsNullPrincipalURI.h index 3a5dc7e8c135..4cf5b28d6ccc 100644 --- a/caps/src/nsNullPrincipalURI.h +++ b/caps/src/nsNullPrincipalURI.h @@ -45,6 +45,7 @@ #define __nsNullPrincipalURI_h__ #include "nsIURI.h" +#include "nsISizeOf.h" #include "nsAutoPtr.h" #include "nsString.h" @@ -54,11 +55,16 @@ {0xb9, 0x1b, 0x6b, 0x54, 0x10, 0x22, 0x36, 0xe6} } class nsNullPrincipalURI : public nsIURI + , public nsISizeOf { public: NS_DECL_ISUPPORTS NS_DECL_NSIURI + // nsISizeOf + virtual size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const; + virtual size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const; + nsNullPrincipalURI(const nsCString &aSpec); private: diff --git a/config/autoconf.mk.in b/config/autoconf.mk.in index d0eeb0c78974..84233fbb2c9c 100644 --- a/config/autoconf.mk.in +++ b/config/autoconf.mk.in @@ -301,6 +301,10 @@ MOZ_NATIVE_NSS = @MOZ_NATIVE_NSS@ MOZ_B2G_RIL = @MOZ_B2G_RIL@ MOZ_B2G_BT = @MOZ_B2G_BT@ +MOZ_ASAN = @MOZ_ASAN@ +MOZ_CFLAGS_NSS = @MOZ_CFLAGS_NSS@ +MOZ_NO_WLZDEFS = @MOZ_NO_WLZDEFS@ + BUILD_CTYPES = @BUILD_CTYPES@ COMPILE_ENVIRONMENT = @COMPILE_ENVIRONMENT@ diff --git a/config/rules.mk b/config/rules.mk index 6c4f7ecdace6..5e299925f5dd 100644 --- a/config/rules.mk +++ b/config/rules.mk @@ -148,6 +148,9 @@ xpcshell-tests: $(testxpcsrcdir)/runxpcshelltests.py \ --symbols-path=$(DIST)/crashreporter-symbols \ --build-info-json=$(DEPTH)/mozinfo.json \ + --tests-root-dir=$(testxpcobjdir) \ + --xunit-file=$(testxpcobjdir)/$(relativesrcdir)/results.xml \ + --xunit-suite-name=xpcshell \ $(EXTRA_TEST_ARGS) \ $(LIBXUL_DIST)/bin/xpcshell \ $(foreach dir,$(XPCSHELL_TESTS),$(testxpcobjdir)/$(relativesrcdir)/$(dir)) diff --git a/configure.in b/configure.in index d2d2200e6fbd..d8e39ca8953d 100644 --- a/configure.in +++ b/configure.in @@ -628,9 +628,10 @@ if test "$GXX" = "yes"; then GNU_CXX=1 CXX_VERSION=`$CXX -v 2>&1 | grep 'gcc version'` fi -if test "`echo | $AS -v 2>&1 | grep -c GNU`" != "0"; then +if test "`echo | $AS -o conftest.out -v 2>&1 | grep -c GNU`" != "0"; then GNU_AS=1 fi +rm -f conftest.out if test "`echo | $LD -v 2>&1 | grep -c GNU`" != "0"; then GNU_LD=1 fi @@ -1836,6 +1837,33 @@ if test -n "$CLANG_CXX"; then _WARNINGS_CXXFLAGS="-Qunused-arguments ${_WARNINGS_CXXFLAGS}" fi +dnl ======================================================== +dnl = Use Address Sanitizer +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(address-sanitizer, +[ --enable-address-sanitizer Enable Address Sanitizer (default=no)], + MOZ_ASAN=1, + MOZ_ASAN= ) +if test -n "$MOZ_ASAN"; then + MOZ_LLVM_HACKS=1 + AC_DEFINE(MOZ_ASAN) +fi +AC_SUBST(MOZ_ASAN) + +dnl ======================================================== +dnl = Enable hacks required for LLVM instrumentations +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(llvm-hacks, +[ --enable-llvm-hacks Enable workarounds required for several LLVM instrumentations (default=no)], + MOZ_LLVM_HACKS=1, + MOZ_LLVM_HACKS= ) +if test -n "$MOZ_LLVM_HACKS"; then + MOZ_NO_WLZDEFS=1 + MOZ_CFLAGS_NSS=1 +fi +AC_SUBST(MOZ_NO_WLZDEFS) +AC_SUBST(MOZ_CFLAGS_NSS) + dnl ======================================================== dnl GNU specific defaults dnl ======================================================== @@ -1846,8 +1874,13 @@ if test "$GNU_CC"; then MKCSHLIB='$(CC) $(CFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -Wl,-h,$@ -o $@' DSO_LDOPTS='-shared' if test "$GCC_USE_GNU_LD"; then - # Don't allow undefined symbols in libraries - DSO_LDOPTS="$DSO_LDOPTS -Wl,-z,defs" + # Some tools like ASan use a runtime library that is only + # linked against executables, so we must allow undefined + # symbols for shared objects in some cases. + if test -z "$MOZ_NO_WLZDEFS"; then + # Don't allow undefined symbols in libraries + DSO_LDOPTS="$DSO_LDOPTS -Wl,-z,defs" + fi fi WARNINGS_AS_ERRORS='-Werror -Wno-error=uninitialized' DSO_CFLAGS='' @@ -9212,7 +9245,7 @@ if test -z "$MOZ_NATIVE_NSPR"; then fi if test "$MOZ_OPTIMIZE" = "1"; then ac_configure_args="$ac_configure_args --enable-optimize" - else + elif test -z "$MOZ_OPTIMIZE"; then ac_configure_args="$ac_configure_args --disable-optimize" fi if test -n "$HAVE_64BIT_OS"; then diff --git a/content/base/src/Link.cpp b/content/base/src/Link.cpp index 199b1df3e85a..2486d391278f 100644 --- a/content/base/src/Link.cpp +++ b/content/base/src/Link.cpp @@ -41,6 +41,7 @@ #include "nsEventStates.h" #include "nsIURL.h" +#include "nsISizeOf.h" #include "nsContentUtils.h" #include "nsEscape.h" @@ -531,5 +532,24 @@ Link::SetHrefAttribute(nsIURI *aURI) NS_ConvertUTF8toUTF16(href), true); } +size_t +Link::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const +{ + size_t n = 0; + + if (mCachedURI) { + nsCOMPtr iface = do_QueryInterface(mCachedURI); + if (iface) { + n += iface->SizeOfIncludingThis(aMallocSizeOf); + } + } + + // The following members don't need to be measured: + // - mElement, because it is a pointer-to-self used to avoid QIs + // - mHistory, because it is non-owning + + return n; +} + } // namespace dom } // namespace mozilla diff --git a/content/base/src/Link.h b/content/base/src/Link.h index c193f2c88752..1b43bd80ab81 100644 --- a/content/base/src/Link.h +++ b/content/base/src/Link.h @@ -131,6 +131,9 @@ public: */ virtual bool HasDeferredDNSPrefetchRequest() { return true; } + virtual size_t + SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const; + protected: virtual ~Link(); diff --git a/content/base/src/nsCrossSiteListenerProxy.cpp b/content/base/src/nsCrossSiteListenerProxy.cpp index b1011c01addb..ba16353dd2f4 100644 --- a/content/base/src/nsCrossSiteListenerProxy.cpp +++ b/content/base/src/nsCrossSiteListenerProxy.cpp @@ -44,7 +44,6 @@ #include "nsNetUtil.h" #include "nsIParser.h" #include "nsParserCIID.h" -#include "nsICharsetAlias.h" #include "nsMimeTypes.h" #include "nsIStreamConverterService.h" #include "nsStringStream.h" diff --git a/content/base/src/nsDOMFile.cpp b/content/base/src/nsDOMFile.cpp index 1df7d3863e0e..a5a8786d9c9f 100644 --- a/content/base/src/nsDOMFile.cpp +++ b/content/base/src/nsDOMFile.cpp @@ -43,7 +43,6 @@ #include "nsContentUtils.h" #include "nsDOMClassInfoID.h" #include "nsDOMError.h" -#include "nsICharsetAlias.h" #include "nsICharsetDetector.h" #include "nsICharsetConverterManager.h" #include "nsIConverterInputStream.h" diff --git a/content/base/src/nsDOMFileReader.cpp b/content/base/src/nsDOMFileReader.cpp index 1172c4c5e3ca..48f42da41bd2 100644 --- a/content/base/src/nsDOMFileReader.cpp +++ b/content/base/src/nsDOMFileReader.cpp @@ -42,7 +42,7 @@ #include "nsDOMClassInfoID.h" #include "nsDOMFile.h" #include "nsDOMError.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "nsICharsetDetector.h" #include "nsICharsetConverterManager.h" #include "nsIConverterInputStream.h" @@ -495,10 +495,7 @@ nsDOMFileReader::GetAsText(const nsACString &aCharset, } nsCAutoString charset; - nsCOMPtr alias = do_GetService(NS_CHARSETALIAS_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - rv = alias->GetPreferred(charsetGuess, charset); + rv = nsCharsetAlias::GetPreferred(charsetGuess, charset); NS_ENSURE_SUCCESS(rv, rv); rv = ConvertStream(aFileData, aDataLen, charset.get(), aResult); diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 451cfe23c116..9cbbd96a346c 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -147,7 +147,7 @@ #include "nsILink.h" #include "nsBlobProtocolHandler.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "nsIParser.h" #include "nsIContentSink.h" @@ -3012,13 +3012,10 @@ nsDocument::SetDocumentCharacterSet(const nsACString& aCharSetID) mCharacterSet = aCharSetID; #ifdef DEBUG - nsCOMPtr calias(do_GetService(NS_CHARSETALIAS_CONTRACTID)); - if (calias) { - nsCAutoString canonicalName; - calias->GetPreferred(aCharSetID, canonicalName); - NS_ASSERTION(canonicalName.Equals(aCharSetID), - "charset name must be canonical"); - } + nsCAutoString canonicalName; + nsCharsetAlias::GetPreferred(aCharSetID, canonicalName); + NS_ASSERTION(canonicalName.Equals(aCharSetID), + "charset name must be canonical"); #endif PRInt32 n = mCharSetObservers.Length(); @@ -3172,16 +3169,10 @@ nsDocument::TryChannelCharset(nsIChannel *aChannel, nsCAutoString charsetVal; nsresult rv = aChannel->GetContentCharset(charsetVal); if (NS_SUCCEEDED(rv)) { - nsCOMPtr calias(do_GetService(NS_CHARSETALIAS_CONTRACTID)); - if (calias) { - nsCAutoString preferred; - rv = calias->GetPreferred(charsetVal, - preferred); - if(NS_SUCCEEDED(rv)) { - aCharset = preferred; - aCharsetSource = kCharsetFromChannel; - return true; - } + rv = nsCharsetAlias::GetPreferred(charsetVal, aCharset); + if(NS_SUCCEEDED(rv)) { + aCharsetSource = kCharsetFromChannel; + return true; } } } diff --git a/content/base/src/nsFrameMessageManager.cpp b/content/base/src/nsFrameMessageManager.cpp index 62fb3f2b7df8..179d9af871e5 100644 --- a/content/base/src/nsFrameMessageManager.cpp +++ b/content/base/src/nsFrameMessageManager.cpp @@ -882,9 +882,7 @@ nsFrameScriptExecutor::InitTabChildGlobalInternal(nsISupports* aScope) JS_SetContextPrivate(cx, aScope); nsresult rv = - xpc->InitClassesWithNewWrappedGlobal(cx, aScope, - NS_GET_IID(nsISupports), - mPrincipal, nsnull, + xpc->InitClassesWithNewWrappedGlobal(cx, aScope, mPrincipal, flags, getter_AddRefs(mGlobal)); NS_ENSURE_SUCCESS(rv, false); diff --git a/content/base/src/nsNodeUtils.cpp b/content/base/src/nsNodeUtils.cpp index f4a75a6c81a7..14e7f429d381 100644 --- a/content/base/src/nsNodeUtils.cpp +++ b/content/base/src/nsNodeUtils.cpp @@ -597,29 +597,10 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, if (aCx && wrapper) { nsIXPConnect *xpc = nsContentUtils::XPConnect(); if (xpc) { - JSObject *preservedWrapper = nsnull; - - // If reparenting moves us to a new compartment, preserving causes - // problems. In that case, we release ourselves and re-preserve after - // reparenting so we're sure to have the right JS object preserved. - // We use a JSObject stack copy of the wrapper to protect it from GC - // under ReparentWrappedNativeIfFound. - if (aNode->PreservingWrapper()) { - preservedWrapper = wrapper; - nsContentUtils::ReleaseWrapper(aNode, aNode); - NS_ASSERTION(aNode->GetWrapper(), - "ReleaseWrapper cleared our wrapper, this code needs to " - "be changed to deal with that!"); - } - nsCOMPtr oldWrapper; rv = xpc->ReparentWrappedNativeIfFound(aCx, wrapper, aNewScope, aNode, getter_AddRefs(oldWrapper)); - if (preservedWrapper) { - nsContentUtils::PreserveWrapper(aNode, aNode); - } - if (NS_FAILED(rv)) { aNode->mNodeInfo.swap(nodeInfo); diff --git a/content/base/src/nsWebSocket.cpp b/content/base/src/nsWebSocket.cpp index d435033e5895..bf38a42ccac7 100644 --- a/content/base/src/nsWebSocket.cpp +++ b/content/base/src/nsWebSocket.cpp @@ -602,7 +602,7 @@ nsWebSocket::Initialize(nsISupports* aOwner, if (JSVAL_IS_OBJECT(aArgv[1]) && (jsobj = JSVAL_TO_OBJECT(aArgv[1])) && JS_IsArrayObject(aContext, jsobj)) { - jsuint len; + uint32_t len; JS_GetArrayLength(aContext, jsobj, &len); for (PRUint32 index = 0; index < len; ++index) { diff --git a/content/base/src/nsXMLHttpRequest.cpp b/content/base/src/nsXMLHttpRequest.cpp index f02ca2e09d47..1b5294158438 100644 --- a/content/base/src/nsXMLHttpRequest.cpp +++ b/content/base/src/nsXMLHttpRequest.cpp @@ -61,7 +61,7 @@ #include "nsIJSContextStack.h" #include "nsIScriptSecurityManager.h" #include "nsWeakPtr.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "nsIScriptGlobalObject.h" #include "nsDOMClassInfoID.h" #include "nsIDOMElement.h" @@ -812,11 +812,7 @@ nsXMLHttpRequest::DetectCharset() nsresult rv = channel ? channel->GetContentCharset(charsetVal) : NS_ERROR_FAILURE; if (NS_SUCCEEDED(rv)) { - nsCOMPtr calias = - do_GetService(NS_CHARSETALIAS_CONTRACTID, &rv); - if (NS_SUCCEEDED(rv) && calias) { - rv = calias->GetPreferred(charsetVal, mResponseCharset); - } + rv = nsCharsetAlias::GetPreferred(charsetVal, mResponseCharset); } if (NS_FAILED(rv) || mResponseCharset.IsEmpty()) { diff --git a/content/canvas/src/CanvasUtils.cpp b/content/canvas/src/CanvasUtils.cpp index 4f8dfae17819..1e4baf8acf62 100644 --- a/content/canvas/src/CanvasUtils.cpp +++ b/content/canvas/src/CanvasUtils.cpp @@ -122,7 +122,7 @@ JSValToMatrixElts(JSContext* cx, const jsval& val, double* (&elts)[N], nsresult* rv) { JSObject* obj; - jsuint length; + uint32_t length; if (JSVAL_IS_PRIMITIVE(val) || !(obj = JSVAL_TO_OBJECT(val)) || diff --git a/content/canvas/src/CanvasUtils.h b/content/canvas/src/CanvasUtils.h index e88363978f06..ec3a9c581759 100644 --- a/content/canvas/src/CanvasUtils.h +++ b/content/canvas/src/CanvasUtils.h @@ -146,11 +146,11 @@ JSValToDashArray(JSContext* cx, const jsval& patternArray, { // The cap is pretty arbitrary. 16k should be enough for // anybody... - static const jsuint MAX_NUM_DASHES = 1 << 14; + static const uint32_t MAX_NUM_DASHES = 1 << 14; if (!JSVAL_IS_PRIMITIVE(patternArray)) { JSObject* obj = JSVAL_TO_OBJECT(patternArray); - jsuint length; + uint32_t length; if (!JS_GetArrayLength(cx, obj, &length)) { // Not an array-like thing return NS_ERROR_INVALID_ARG; diff --git a/content/canvas/src/WebGLContext.h b/content/canvas/src/WebGLContext.h index 6ee014477a6e..ffc1154f159d 100644 --- a/content/canvas/src/WebGLContext.h +++ b/content/canvas/src/WebGLContext.h @@ -754,7 +754,7 @@ protected: WebGL_MOZ_WEBGL_lose_context, WebGLExtensionID_Max }; - nsCOMPtr mEnabledExtensions[WebGLExtensionID_Max]; + nsRefPtr mEnabledExtensions[WebGLExtensionID_Max]; bool IsExtensionEnabled(WebGLExtensionID ext) const { NS_ABORT_IF_FALSE(ext >= 0 && ext < WebGLExtensionID_Max, "bogus index!"); return mEnabledExtensions[ext] != nsnull; diff --git a/content/canvas/test/webgl/test_webgl_conformance_test_suite.html b/content/canvas/test/webgl/test_webgl_conformance_test_suite.html index 8de194fa5115..fde004a6b0ff 100644 --- a/content/canvas/test/webgl/test_webgl_conformance_test_suite.html +++ b/content/canvas/test/webgl/test_webgl_conformance_test_suite.html @@ -377,7 +377,7 @@ function start() { default: throw 'unhandled'; break; - }; + } }; var getURLOptions = function(obj) { @@ -404,27 +404,12 @@ function start() { // try to create a dummy WebGL context, just to catch context creation failures once here, // rather than having them result in 100's of failures (one in each test page) - var canvas = document.getElementById("webglcheck-default"); var ctx; try { - ctx = canvas.getContext("experimental-webgl"); - } catch(e) { - ok(false, "canvas.getContext() failed", e); - } - - if (ctx) { - statusTextNode.textContent = 'Loading test lists...'; - var iframe = document.getElementById("testframe"); - var testHarness = new WebGLTestHarnessModule.TestHarness( - iframe, - '00_test_list.txt', - function(type, msg, success) { - return reporter.reportFunc(type, msg, success); - }, - OPTIONS); - testHarness.setTimeoutDelay(20000); // and make it much higher when running under valgrind. - window.webglTestHarness = testHarness; - } else { + ctx = document.getElementById("webglcheck-default") + .getContext("experimental-webgl"); + } catch(e) {} + if (!ctx) { var errmsg = "Can't create a WebGL context"; reporter.fullResultsNode.textContent = errmsg; // Workaround for SeaMonkey tinderboxes which don't support WebGL. @@ -434,8 +419,22 @@ function start() { ok(false, errmsg); dump("WebGL mochitest failed: " + errmsg + "\n"); reporter.finishedTestSuite(); + return; } - }; + + statusTextNode.textContent = 'Loading test lists...'; + var iframe = document.getElementById("testframe"); + var testHarness = new WebGLTestHarnessModule.TestHarness( + iframe, + '00_test_list.txt', + function(type, msg, success) { + return reporter.reportFunc(type, msg, success); + }, + OPTIONS); + // Make timeout delay much higher when running under valgrind. + testHarness.setTimeoutDelay(20000); + window.webglTestHarness = testHarness; + } SimpleTest.requestLongerTimeout(3); diff --git a/content/html/content/src/nsFormSubmission.cpp b/content/html/content/src/nsFormSubmission.cpp index af10d9e0f1e0..08627f66d530 100644 --- a/content/html/content/src/nsFormSubmission.cpp +++ b/content/html/content/src/nsFormSubmission.cpp @@ -60,7 +60,7 @@ #include "nsNetUtil.h" #include "nsLinebreakConverter.h" #include "nsICharsetConverterManager.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "nsEscape.h" #include "nsUnicharUtils.h" #include "nsIMultiplexInputStream.h" @@ -782,7 +782,6 @@ GetSubmitCharset(nsGenericHTMLElement* aForm, { oCharset.AssignLiteral("UTF-8"); // default to utf-8 - nsresult rv = NS_OK; nsAutoString acceptCharsetValue; aForm->GetAttr(kNameSpaceID_None, nsGkAtoms::acceptcharset, acceptCharsetValue); @@ -792,26 +791,19 @@ GetSubmitCharset(nsGenericHTMLElement* aForm, PRInt32 offset=0; PRInt32 spPos=0; // get charset from charsets one by one - nsCOMPtr calias(do_GetService(NS_CHARSETALIAS_CONTRACTID, &rv)); - if (NS_FAILED(rv)) { - return; - } - if (calias) { - do { - spPos = acceptCharsetValue.FindChar(PRUnichar(' '), offset); - PRInt32 cnt = ((-1==spPos)?(charsetLen-offset):(spPos-offset)); - if (cnt > 0) { - nsAutoString uCharset; - acceptCharsetValue.Mid(uCharset, offset, cnt); + do { + spPos = acceptCharsetValue.FindChar(PRUnichar(' '), offset); + PRInt32 cnt = ((-1==spPos)?(charsetLen-offset):(spPos-offset)); + if (cnt > 0) { + nsAutoString uCharset; + acceptCharsetValue.Mid(uCharset, offset, cnt); - if (NS_SUCCEEDED(calias-> - GetPreferred(NS_LossyConvertUTF16toASCII(uCharset), - oCharset))) - return; - } - offset = spPos + 1; - } while (spPos != -1); - } + if (NS_SUCCEEDED(nsCharsetAlias::GetPreferred(NS_LossyConvertUTF16toASCII(uCharset), + oCharset))) + return; + } + offset = spPos + 1; + } while (spPos != -1); } // if there are no accept-charset or all the charset are not supported // Get the charset from document diff --git a/content/html/content/src/nsHTMLAnchorElement.cpp b/content/html/content/src/nsHTMLAnchorElement.cpp index 995fdf960d53..3dee7b255776 100644 --- a/content/html/content/src/nsHTMLAnchorElement.cpp +++ b/content/html/content/src/nsHTMLAnchorElement.cpp @@ -96,8 +96,8 @@ public: // nsIDOMHTMLAnchorElement NS_DECL_NSIDOMHTMLANCHORELEMENT - // TODO: nsHTMLAnchorElement::SizeOfAnchorElement should call - // Link::SizeOfExcludingThis(). See bug 682431. + // DOM memory reporter participant + NS_DECL_SIZEOF_EXCLUDING_THIS // nsILink NS_IMETHOD LinkAdded() { return NS_OK; } @@ -520,3 +520,10 @@ nsHTMLAnchorElement::IntrinsicState() const return Link::LinkState() | nsGenericHTMLElement::IntrinsicState(); } +size_t +nsHTMLAnchorElement::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const +{ + return nsGenericHTMLElement::SizeOfExcludingThis(aMallocSizeOf) + + Link::SizeOfExcludingThis(aMallocSizeOf); +} + diff --git a/content/html/content/src/nsHTMLAreaElement.cpp b/content/html/content/src/nsHTMLAreaElement.cpp index 8e4b398c3935..bf174b510a96 100644 --- a/content/html/content/src/nsHTMLAreaElement.cpp +++ b/content/html/content/src/nsHTMLAreaElement.cpp @@ -61,8 +61,8 @@ public: // nsISupports NS_DECL_ISUPPORTS_INHERITED - // TODO: nsHTMLAreaElement::SizeOfAnchorElement should call - // Link::SizeOfExcludingThis(). See bug 682431. + // DOM memory reporter participant + NS_DECL_SIZEOF_EXCLUDING_THIS // nsIDOMNode NS_FORWARD_NSIDOMNODE(nsGenericHTMLElement::) @@ -335,3 +335,11 @@ nsHTMLAreaElement::IntrinsicState() const { return Link::LinkState() | nsGenericHTMLElement::IntrinsicState(); } + +size_t +nsHTMLAreaElement::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const +{ + return nsGenericHTMLElement::SizeOfExcludingThis(aMallocSizeOf) + + Link::SizeOfExcludingThis(aMallocSizeOf); +} + diff --git a/content/html/content/src/nsHTMLLinkElement.cpp b/content/html/content/src/nsHTMLLinkElement.cpp index 0f67ed074e8a..693b47a879e7 100644 --- a/content/html/content/src/nsHTMLLinkElement.cpp +++ b/content/html/content/src/nsHTMLLinkElement.cpp @@ -84,8 +84,8 @@ public: // nsIDOMHTMLLinkElement NS_DECL_NSIDOMHTMLLINKELEMENT - // TODO: nsHTMLLinkElement::SizeOfAnchorElement should call - // Link::SizeOfExcludingThis(). See bug 682431. + // DOM memory reporter participant + NS_DECL_SIZEOF_EXCLUDING_THIS // nsILink NS_IMETHOD LinkAdded(); @@ -458,3 +458,11 @@ nsHTMLLinkElement::IntrinsicState() const { return Link::LinkState() | nsGenericHTMLElement::IntrinsicState(); } + +size_t +nsHTMLLinkElement::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const +{ + return nsGenericHTMLElement::SizeOfExcludingThis(aMallocSizeOf) + + Link::SizeOfExcludingThis(aMallocSizeOf); +} + diff --git a/content/html/document/src/nsHTMLDocument.cpp b/content/html/document/src/nsHTMLDocument.cpp index 4ddfca8390ea..3f94697e4878 100644 --- a/content/html/document/src/nsHTMLDocument.cpp +++ b/content/html/document/src/nsHTMLDocument.cpp @@ -40,8 +40,6 @@ #include "mozilla/Util.h" -#include "nsICharsetAlias.h" - #include "nsCOMPtr.h" #include "nsXPIDLString.h" #include "nsPrintfCString.h" @@ -102,7 +100,6 @@ #include "nsFrameSelection.h" #include "nsISelectionPrivate.h"//for toStringwithformat code -#include "nsICharsetAlias.h" #include "nsContentUtils.h" #include "nsJSUtils.h" #include "nsIDocumentEncoder.h" //for outputting selection diff --git a/content/media/nsBuiltinDecoderStateMachine.cpp b/content/media/nsBuiltinDecoderStateMachine.cpp index af032c5b5d03..ec791756b6d7 100644 --- a/content/media/nsBuiltinDecoderStateMachine.cpp +++ b/content/media/nsBuiltinDecoderStateMachine.cpp @@ -2019,6 +2019,13 @@ void nsBuiltinDecoderStateMachine::AdvanceFrame() // duration. RenderVideoFrame(currentFrame, presTime); } + // If we're no longer playing after dropping and reacquiring the lock, + // playback must've been stopped on the decode thread (by a seek, for + // example). In that case, the current frame is probably out of date. + if (!IsPlaying()) { + ScheduleStateMachine(); + return; + } mDecoder->GetFrameStatistics().NotifyPresentedFrame(); PRInt64 now = DurationToUsecs(TimeStamp::Now() - mPlayStartTime) + mPlayDuration; remainingTime = currentFrame->mEndTime - mStartTime - now; diff --git a/content/media/nsMediaCache.cpp b/content/media/nsMediaCache.cpp index 575fe21b5c31..1f283a515b59 100644 --- a/content/media/nsMediaCache.cpp +++ b/content/media/nsMediaCache.cpp @@ -42,6 +42,7 @@ #include "nsMediaCache.h" #include "nsDirectoryServiceUtils.h" #include "nsDirectoryServiceDefs.h" +#include "nsXULAppAPI.h" #include "nsNetUtil.h" #include "prio.h" #include "nsThreadUtils.h" @@ -545,8 +546,15 @@ nsMediaCache::Init() NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); NS_ASSERTION(!mFD, "Cache file already open?"); + // In single process Gecko, store the media cache in the profile directory + // so that multiple users can use separate media caches concurrently. + // In multi-process Gecko, there is no profile dir, so just store it in the + // system temp directory instead. + nsresult rv; nsCOMPtr tmp; - nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmp)); + const char* dir = (XRE_GetProcessType() == GeckoProcessType_Content) ? + NS_OS_TEMP_DIR : NS_APP_USER_PROFILE_LOCAL_50_DIR; + rv = NS_GetSpecialDirectory(dir, getter_AddRefs(tmp)); NS_ENSURE_SUCCESS(rv,rv); nsCOMPtr tmpFile = do_QueryInterface(tmp); diff --git a/content/svg/content/src/nsSVGUseElement.cpp b/content/svg/content/src/nsSVGUseElement.cpp index e34524ecba41..1e77b3ae0af5 100644 --- a/content/svg/content/src/nsSVGUseElement.cpp +++ b/content/svg/content/src/nsSVGUseElement.cpp @@ -428,9 +428,7 @@ nsSVGUseElement::SyncWidthOrHeight(nsIAtom* aName) // Our width/height attribute is now no longer explicitly set, so we // need to revert the clone's width/height to the width/height of the // content that's being cloned. - nsSVGSVGElement* svgElement = - static_cast(mSource.get()); - svgElement->SyncWidthOrHeight(aName, target); + TriggerReclone(); return; } // Our width/height attribute is now no longer explicitly set, so we diff --git a/content/xbl/src/nsBindingManager.cpp b/content/xbl/src/nsBindingManager.cpp index 5f56ec3938e2..82d9b93467f4 100644 --- a/content/xbl/src/nsBindingManager.cpp +++ b/content/xbl/src/nsBindingManager.cpp @@ -41,7 +41,6 @@ #include "nsCOMPtr.h" #include "nsIXBLService.h" #include "nsIInputStream.h" -#include "nsDoubleHashtable.h" #include "nsIURI.h" #include "nsIURL.h" #include "nsIChannel.h" diff --git a/content/xml/document/src/nsXMLDocument.cpp b/content/xml/document/src/nsXMLDocument.cpp index ad39e06267e2..6574ba276eae 100644 --- a/content/xml/document/src/nsXMLDocument.cpp +++ b/content/xml/document/src/nsXMLDocument.cpp @@ -59,8 +59,6 @@ #include "nsIHttpChannel.h" #include "nsIURI.h" #include "nsIServiceManager.h" -#include "nsICharsetAlias.h" -#include "nsICharsetAlias.h" #include "nsNetUtil.h" #include "nsDOMError.h" #include "nsIScriptSecurityManager.h" diff --git a/content/xslt/src/xml/txDOM.h b/content/xslt/src/xml/txDOM.h index 7b0a3504b702..4dcb7cdd7c5f 100644 --- a/content/xslt/src/xml/txDOM.h +++ b/content/xslt/src/xml/txDOM.h @@ -55,7 +55,8 @@ #include "txList.h" #include "nsIAtom.h" -#include "nsDoubleHashtable.h" +#include "nsTHashtable.h" +#include "nsBaseHashtable.h" #include "nsString.h" #include "txCore.h" #include "nsAutoPtr.h" @@ -229,24 +230,7 @@ class NodeDefinition : public Node //Definition and Implementation of a Document. // -/** - * nsDoubleHashtable definitions for IDs - * - * It may be possible to share the key value with the element, - * but that may leave entries without keys, as the entries - * are constructed from the key value and the setting of mElement - * happens late. As pldhash.h ain't clear on this, we store the - * key by inheriting from PLDHashStringEntry. - */ -class txIDEntry : public PLDHashStringEntry -{ -public: - txIDEntry(const void* aKey) : PLDHashStringEntry(aKey), mElement(nsnull) - { - } - Element* mElement; -}; -DECL_DHASH_WRAPPER(txIDMap, txIDEntry, nsAString&) +typedef nsTHashtable > txIDMap; class Document : public NodeDefinition { diff --git a/content/xslt/src/xslt/txKey.h b/content/xslt/src/xslt/txKey.h index 9440766c4733..212ea4a6b4ae 100644 --- a/content/xslt/src/xslt/txKey.h +++ b/content/xslt/src/xslt/txKey.h @@ -39,7 +39,7 @@ #ifndef txKey_h__ #define txKey_h__ -#include "nsDoubleHashtable.h" +#include "nsTHashtable.h" #include "txNodeSet.h" #include "txList.h" #include "txXSLTPatterns.h" @@ -68,21 +68,31 @@ public: struct txKeyValueHashEntry : public PLDHashEntryHdr { - txKeyValueHashEntry(const void* aKey) - : mKey(*static_cast(aKey)), - mNodeSet(new txNodeSet(nsnull)) - { - } +public: + typedef const txKeyValueHashKey& KeyType; + typedef const txKeyValueHashKey* KeyTypePointer; - // @see nsDoubleHashtable.h - bool MatchEntry(const void* aKey) const; - static PLDHashNumber HashKey(const void* aKey); + txKeyValueHashEntry(KeyTypePointer aKey) + : mKey(*aKey), + mNodeSet(new txNodeSet(nsnull)) { } + + txKeyValueHashEntry(const txKeyValueHashEntry& entry) + : mKey(entry.mKey), + mNodeSet(entry.mNodeSet) { } + + bool KeyEquals(KeyTypePointer aKey) const; + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + + static PLDHashNumber HashKey(KeyTypePointer aKey); + + enum { ALLOW_MEMMOVE = true }; txKeyValueHashKey mKey; nsRefPtr mNodeSet; }; -DECL_DHASH_WRAPPER(txKeyValueHash, txKeyValueHashEntry, txKeyValueHashKey&) +typedef nsTHashtable txKeyValueHash; class txIndexedKeyHashKey { @@ -100,22 +110,31 @@ public: struct txIndexedKeyHashEntry : public PLDHashEntryHdr { - txIndexedKeyHashEntry(const void* aKey) - : mKey(*static_cast(aKey)), - mIndexed(false) - { - } +public: + typedef const txIndexedKeyHashKey& KeyType; + typedef const txIndexedKeyHashKey* KeyTypePointer; - // @see nsDoubleHashtable.h - bool MatchEntry(const void* aKey) const; - static PLDHashNumber HashKey(const void* aKey); + txIndexedKeyHashEntry(KeyTypePointer aKey) + : mKey(*aKey), + mIndexed(false) { } + + txIndexedKeyHashEntry(const txIndexedKeyHashEntry& entry) + : mKey(entry.mKey), + mIndexed(entry.mIndexed) { } + + bool KeyEquals(KeyTypePointer aKey) const; + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + + static PLDHashNumber HashKey(KeyTypePointer aKey); + + enum { ALLOW_MEMMOVE = true }; txIndexedKeyHashKey mKey; bool mIndexed; }; -DECL_DHASH_WRAPPER(txIndexedKeyHash, txIndexedKeyHashEntry, - txIndexedKeyHashKey&) +typedef nsTHashtable txIndexedKeyHash; /** * Class holding all s of a particular expanded name in the diff --git a/content/xslt/src/xslt/txKeyFunctionCall.cpp b/content/xslt/src/xslt/txKeyFunctionCall.cpp index f5f24676473c..e3993c23497d 100644 --- a/content/xslt/src/xslt/txKeyFunctionCall.cpp +++ b/content/xslt/src/xslt/txKeyFunctionCall.cpp @@ -152,51 +152,36 @@ txKeyFunctionCall::getNameAtom(nsIAtom** aAtom) * Hash functions */ -DHASH_WRAPPER(txKeyValueHash, txKeyValueHashEntry, txKeyValueHashKey&) -DHASH_WRAPPER(txIndexedKeyHash, txIndexedKeyHashEntry, txIndexedKeyHashKey&) - bool -txKeyValueHashEntry::MatchEntry(const void* aKey) const +txKeyValueHashEntry::KeyEquals(KeyTypePointer aKey) const { - const txKeyValueHashKey* key = - static_cast(aKey); - - return mKey.mKeyName == key->mKeyName && - mKey.mRootIdentifier == key->mRootIdentifier && - mKey.mKeyValue.Equals(key->mKeyValue); + return mKey.mKeyName == aKey->mKeyName && + mKey.mRootIdentifier == aKey->mRootIdentifier && + mKey.mKeyValue.Equals(aKey->mKeyValue); } PLDHashNumber -txKeyValueHashEntry::HashKey(const void* aKey) +txKeyValueHashEntry::HashKey(KeyTypePointer aKey) { - const txKeyValueHashKey* key = - static_cast(aKey); - - return key->mKeyName.mNamespaceID ^ - NS_PTR_TO_INT32(key->mKeyName.mLocalName.get()) ^ - key->mRootIdentifier ^ - HashString(key->mKeyValue); + return aKey->mKeyName.mNamespaceID ^ + NS_PTR_TO_INT32(aKey->mKeyName.mLocalName.get()) ^ + aKey->mRootIdentifier ^ + HashString(aKey->mKeyValue); } bool -txIndexedKeyHashEntry::MatchEntry(const void* aKey) const +txIndexedKeyHashEntry::KeyEquals(KeyTypePointer aKey) const { - const txIndexedKeyHashKey* key = - static_cast(aKey); - - return mKey.mKeyName == key->mKeyName && - mKey.mRootIdentifier == key->mRootIdentifier; + return mKey.mKeyName == aKey->mKeyName && + mKey.mRootIdentifier == aKey->mRootIdentifier; } PLDHashNumber -txIndexedKeyHashEntry::HashKey(const void* aKey) +txIndexedKeyHashEntry::HashKey(KeyTypePointer aKey) { - const txIndexedKeyHashKey* key = - static_cast(aKey); - - return key->mKeyName.mNamespaceID ^ - NS_PTR_TO_INT32(key->mKeyName.mLocalName.get()) ^ - key->mRootIdentifier; + return aKey->mKeyName.mNamespaceID ^ + NS_PTR_TO_INT32(aKey->mKeyName.mLocalName.get()) ^ + aKey->mRootIdentifier; } /* @@ -211,9 +196,6 @@ txKeyHash::getKeyNodes(const txExpandedName& aKeyName, txExecutionState& aEs, txNodeSet** aResult) { - NS_ENSURE_TRUE(mKeyValues.mHashTable.ops && mIndexedKeys.mHashTable.ops, - NS_ERROR_OUT_OF_MEMORY); - *aResult = nsnull; PRInt32 identifier = txXPathNodeUtils::getUniqueIdentifier(aRoot); @@ -241,7 +223,7 @@ txKeyHash::getKeyNodes(const txExpandedName& aKeyName, } txIndexedKeyHashKey indexKey(aKeyName, identifier); - txIndexedKeyHashEntry* indexEntry = mIndexedKeys.AddEntry(indexKey); + txIndexedKeyHashEntry* indexEntry = mIndexedKeys.PutEntry(indexKey); NS_ENSURE_TRUE(indexEntry, NS_ERROR_OUT_OF_MEMORY); if (indexEntry->mIndexed) { @@ -412,7 +394,7 @@ nsresult txXSLKey::testNode(const txXPathNode& aNode, txXPathNodeUtils::appendNodeValue(res->get(i), val); aKey.mKeyValue.Assign(val); - txKeyValueHashEntry* entry = aKeyValueHash.AddEntry(aKey); + txKeyValueHashEntry* entry = aKeyValueHash.PutEntry(aKey); NS_ENSURE_TRUE(entry && entry->mNodeSet, NS_ERROR_OUT_OF_MEMORY); @@ -427,7 +409,7 @@ nsresult txXSLKey::testNode(const txXPathNode& aNode, exprResult->stringValue(val); aKey.mKeyValue.Assign(val); - txKeyValueHashEntry* entry = aKeyValueHash.AddEntry(aKey); + txKeyValueHashEntry* entry = aKeyValueHash.PutEntry(aKey); NS_ENSURE_TRUE(entry && entry->mNodeSet, NS_ERROR_OUT_OF_MEMORY); diff --git a/content/xslt/src/xslt/txMozillaStylesheetCompiler.cpp b/content/xslt/src/xslt/txMozillaStylesheetCompiler.cpp index c97b80600972..614a54859777 100644 --- a/content/xslt/src/xslt/txMozillaStylesheetCompiler.cpp +++ b/content/xslt/src/xslt/txMozillaStylesheetCompiler.cpp @@ -39,7 +39,7 @@ #include "nsCOMArray.h" #include "nsIAuthPrompt.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "nsIDOMNode.h" #include "nsIDOMDocument.h" #include "nsIDocument.h" @@ -282,29 +282,23 @@ txStylesheetSink::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext, NS_IMETHODIMP txStylesheetSink::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) { - nsCAutoString charset(NS_LITERAL_CSTRING("UTF-8")); PRInt32 charsetSource = kCharsetFromDocTypeDefault; nsCOMPtr channel = do_QueryInterface(aRequest); // check channel's charset... nsCAutoString charsetVal; - nsresult rv = channel->GetContentCharset(charsetVal); - if (NS_SUCCEEDED(rv)) { - nsCOMPtr calias = - do_GetService(NS_CHARSETALIAS_CONTRACTID); - - if (calias) { - nsCAutoString preferred; - rv = calias->GetPreferred(charsetVal, - preferred); - if (NS_SUCCEEDED(rv)) { - charset = preferred; - charsetSource = kCharsetFromChannel; - } + nsCAutoString charset; + if (NS_SUCCEEDED(channel->GetContentCharset(charsetVal))) { + if (NS_SUCCEEDED(nsCharsetAlias::GetPreferred(charsetVal, charset))) { + charsetSource = kCharsetFromChannel; } } + if (charset.IsEmpty()) { + charset.AssignLiteral("UTF-8"); + } + nsCOMPtr parser = do_QueryInterface(aContext); parser->SetDocumentCharset(charset, charsetSource); @@ -318,6 +312,7 @@ txStylesheetSink::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) bool sniff; if (NS_SUCCEEDED(uri->SchemeIs("file", &sniff)) && sniff && contentType.Equals(UNKNOWN_CONTENT_TYPE)) { + nsresult rv; nsCOMPtr serv = do_GetService("@mozilla.org/streamConverters;1", &rv); if (NS_SUCCEEDED(rv)) { diff --git a/content/xslt/src/xslt/txMozillaTextOutput.cpp b/content/xslt/src/xslt/txMozillaTextOutput.cpp index db4ad01496db..f45ac55c0dd7 100644 --- a/content/xslt/src/xslt/txMozillaTextOutput.cpp +++ b/content/xslt/src/xslt/txMozillaTextOutput.cpp @@ -45,7 +45,7 @@ #include "nsIDocumentTransformer.h" #include "nsNetUtil.h" #include "nsIParser.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "nsIPrincipal.h" #include "txURIUtils.h" #include "nsContentCreatorFunctions.h" @@ -184,11 +184,9 @@ txMozillaTextOutput::createResultDocument(nsIDOMDocument* aSourceDocument) if (!mOutputFormat.mEncoding.IsEmpty()) { NS_LossyConvertUTF16toASCII charset(mOutputFormat.mEncoding); nsCAutoString canonicalCharset; - nsCOMPtr calias = - do_GetService("@mozilla.org/intl/charsetalias;1"); - if (calias && - NS_SUCCEEDED(calias->GetPreferred(charset, canonicalCharset))) { + if (NS_SUCCEEDED(nsCharsetAlias::GetPreferred(charset, + canonicalCharset))) { mDocument->SetDocumentCharacterSetSource(kCharsetFromOtherComponent); mDocument->SetDocumentCharacterSet(canonicalCharset); } diff --git a/content/xslt/src/xslt/txMozillaXMLOutput.cpp b/content/xslt/src/xslt/txMozillaXMLOutput.cpp index 172750139c68..3af32c19f390 100644 --- a/content/xslt/src/xslt/txMozillaXMLOutput.cpp +++ b/content/xslt/src/xslt/txMozillaXMLOutput.cpp @@ -64,7 +64,7 @@ #include "nsIDocumentTransformer.h" #include "mozilla/css/Loader.h" #include "mozilla/dom/Element.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "nsIHTMLContentSink.h" #include "nsContentUtils.h" #include "txXMLUtils.h" @@ -857,11 +857,7 @@ txMozillaXMLOutput::createResultDocument(const nsSubstring& aName, PRInt32 aNsID if (!mOutputFormat.mEncoding.IsEmpty()) { NS_LossyConvertUTF16toASCII charset(mOutputFormat.mEncoding); nsCAutoString canonicalCharset; - nsCOMPtr calias = - do_GetService("@mozilla.org/intl/charsetalias;1"); - - if (calias && - NS_SUCCEEDED(calias->GetPreferred(charset, canonicalCharset))) { + if (NS_SUCCEEDED(nsCharsetAlias::GetPreferred(charset, canonicalCharset))) { mDocument->SetDocumentCharacterSetSource(kCharsetFromOtherComponent); mDocument->SetDocumentCharacterSet(canonicalCharset); } diff --git a/dom/base/DOMRequestHelper.jsm b/dom/base/DOMRequestHelper.jsm new file mode 100644 index 000000000000..721f58ad57b9 --- /dev/null +++ b/dom/base/DOMRequestHelper.jsm @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * helper object for APIs that deal with DOMRequest and need to release them properly + * when the window goes out of scope + */ +const Cu = Components.utils; +const Cc = Components.classes; +const Ci = Components.interfaces; + +let EXPORTED_SYMBOLS = ["DOMRequestIpcHelper"]; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyGetter(Services, "rs", function() { + return Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci.nsIDOMRequestService); +}); + +XPCOMUtils.defineLazyGetter(this, "cpmm", function() { + return Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager); +}); + +function DOMRequestIpcHelper() { +} + +DOMRequestIpcHelper.prototype = { + getRequestId: function(aRequest) { + let id = "id" + this._getRandomId(); + this._requests[id] = aRequest; + return id; + }, + + getRequest: function(aId) { + if (this._requests[aId]) + return this._requests[aId]; + }, + + removeRequest: function(aId) { + if (this._requests[aId]) + delete this._requests[aId]; + }, + + _getRandomId: function() { + return Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString(); + }, + + observe: function(aSubject, aTopic, aData) { + let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data; + if (wId == this.innerWindowID) { + Services.obs.removeObserver(this, "inner-window-destroyed"); + this._requests = []; + this._window = null; + this._messages.forEach((function(msgName) { + cpmm.removeMessageListener(msgName, this); + }).bind(this)); + if(this.uninit) + this.uninit(); + } + }, + + initHelper: function(aWindow, aMessages) { + this._messages = aMessages; + this._requests = []; + this._window = aWindow; + let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); + this.innerWindowID = util.currentInnerWindowID; + this._id = this._getRandomId(); + Services.obs.addObserver(this, "inner-window-destroyed", false); + this._messages.forEach((function(msgName) { + cpmm.addMessageListener(msgName, this); + }).bind(this)); + }, + + createRequest: function() { + return Services.rs.createRequest(this._window); + } +} diff --git a/dom/base/Makefile.in b/dom/base/Makefile.in index 6537634d4ded..6024f020959c 100644 --- a/dom/base/Makefile.in +++ b/dom/base/Makefile.in @@ -69,6 +69,9 @@ EXTRA_JS_MODULES += Webapps.jsm \ $(NULL) endif +EXTRA_JS_MODULES += DOMRequestHelper.jsm \ + $(NULL) + XPIDLSRCS = \ nsIDOMDOMError.idl \ nsIDOMDOMRequest.idl \ diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index b8cf9f6a33aa..022e6779b90b 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -70,6 +70,8 @@ #include "mozilla/Telemetry.h" #include "BatteryManager.h" #include "PowerManager.h" +#include "nsIDOMWakeLock.h" +#include "nsIPowerManagerService.h" #include "SmsManager.h" #include "nsISmsService.h" #include "mozilla/Hal.h" @@ -172,7 +174,10 @@ Navigator::Invalidate() mBatteryManager = nsnull; } - mPowerManager = nsnull; + if (mPowerManager) { + mPowerManager->Shutdown(); + mPowerManager = nsnull; + } if (mSmsManager) { mSmsManager->Shutdown(); @@ -958,15 +963,38 @@ Navigator::GetMozBattery(nsIDOMMozBatteryManager** aBattery) NS_IMETHODIMP Navigator::GetMozPower(nsIDOMMozPowerManager** aPower) { + *aPower = nsnull; + if (!mPowerManager) { + nsCOMPtr win = do_QueryReferent(mWindow); + NS_ENSURE_TRUE(win, NS_OK); + mPowerManager = new power::PowerManager(); + mPowerManager->Init(win); } - NS_ADDREF(*aPower = mPowerManager); + nsCOMPtr power = + do_QueryInterface(NS_ISUPPORTS_CAST(nsIDOMMozPowerManager*, mPowerManager)); + power.forget(aPower); return NS_OK; } +NS_IMETHODIMP +Navigator::RequestWakeLock(const nsAString &aTopic, nsIDOMMozWakeLock **aWakeLock) +{ + *aWakeLock = nsnull; + + nsCOMPtr win = do_QueryReferent(mWindow); + NS_ENSURE_TRUE(win, NS_OK); + + nsCOMPtr pmService = + do_GetService(POWERMANAGERSERVICE_CONTRACTID); + NS_ENSURE_TRUE(pmService, NS_OK); + + return pmService->NewWakeLock(aTopic, win, aWakeLock); +} + //***************************************************************************** // Navigator::nsIDOMNavigatorSms //***************************************************************************** diff --git a/dom/base/Webapps.js b/dom/base/Webapps.js index 2c04c6aa8ab9..796e6c3ab49c 100644 --- a/dom/base/Webapps.js +++ b/dom/base/Webapps.js @@ -1,38 +1,6 @@ -/* ***** 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 Open Web Apps. - * - * The Initial Developer of the Original Code is Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Fabrice Desré - * - * 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 ***** */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ const Cc = Components.classes; const Ci = Components.interfaces; @@ -41,27 +9,31 @@ const Cr = Components.results; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/DOMRequestHelper.jsm"); + +XPCOMUtils.defineLazyGetter(Services, "rs", function() { + return Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci.nsIDOMRequestService); +}); + +XPCOMUtils.defineLazyGetter(this, "cpmm", function() { + return Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager); +}); + +function convertAppsArray(aApps, aWindow) { + let apps = new Array(); + for (let i = 0; i < aApps.length; i++) { + let app = aApps[i]; + apps.push(new WebappsApplication(aWindow, app.origin, app.manifest, app.manifestURL, + app.receipts, app.installOrigin, app.installTime)); + } + return apps; +} function WebappsRegistry() { - this.messages = ["Webapps:Install:Return:OK", "Webapps:Install:Return:KO", - "Webapps:Uninstall:Return:OK", "Webapps:Uninstall:Return:KO", - "Webapps:Enumerate:Return:OK", "Webapps:Enumerate:Return:KO"]; - - this.mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager); - - this.messages.forEach((function(msgName) { - this.mm.addMessageListener(msgName, this); - }).bind(this)); - - this._window = null; - this._id = this._getRandomId(); - this._callbacks = []; } WebappsRegistry.prototype = { - _onerror: null, - _oninstall: null, - _onuninstall: null, + __proto__: DOMRequestIpcHelper.prototype, /** from https://developer.mozilla.org/en/OpenWebApps/The_Manifest * only the name property is mandatory @@ -70,7 +42,7 @@ WebappsRegistry.prototype = { // TODO : check for install_allowed_from if (aManifest.name == undefined) return false; - + if (aManifest.installs_allowed_from) { ok = false; aManifest.installs_allowed_from.forEach(function(aOrigin) { @@ -81,97 +53,41 @@ WebappsRegistry.prototype = { } return true; }, - - getCallbackId: function(aCallback) { - let id = "id" + this._getRandomId(); - this._callbacks[id] = aCallback; - return id; - }, - - getCallback: function(aId) { - return this._callbacks[aId]; - }, - - removeCallback: function(aId) { - if (this._callbacks[aId]) - delete this._callbacks[aId]; - }, - - _getRandomId: function() { - return Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString(); - }, - - _convertAppsArray: function(aApps) { - let apps = new Array(); - for (let i = 0; i < aApps.length; i++) { - let app = aApps[i]; - apps.push(new WebappsApplication(app.origin, app.manifest, app.receipt, app.installOrigin, app.installTime)); - } - return apps; - }, - - set oninstall(aCallback) { - if (this.hasPrivileges) - this._oninstall = aCallback; - else - throw Components.results.NS_ERROR_NOT_IMPLEMENTED; - }, - - set onuninstall(aCallback) { - if (this.hasPrivileges) - this._onuninstall = aCallback; - else - throw Components.results.NS_ERROR_NOT_IMPLEMENTED; - }, - - set onerror(aCallback) { - this._onerror = aCallback; - }, receiveMessage: function(aMessage) { let msg = aMessage.json; - if (!(msg.oid == this._id || aMessage.name == "Webapps:Install:Return:OK" || aMessage.name == "Webapps:Uninstall:Return:OK")) + if (msg.oid != this._id) return + let req = this.getRequest(msg.requestID); + if (!req) + return; let app = msg.app; - let cb; switch (aMessage.name) { case "Webapps:Install:Return:OK": - if (this._oninstall) - this._oninstall.handleEvent(new WebappsApplication(app.origin, app.manifest, app.receipt, + Services.rs.fireSuccess(req, new WebappsApplication(this._window, app.origin, app.manifest, app.manifestURL, app.receipts, app.installOrigin, app.installTime)); break; case "Webapps:Install:Return:KO": - if (this._onerror) - this._onerror.handleEvent(new RegistryError(Ci.mozIDOMApplicationRegistryError.DENIED)); + Services.rs.fireError(req, "DENIED"); break; - case "Webapps:Uninstall:Return:OK": - if (this._onuninstall) - this._onuninstall.handleEvent(new WebappsApplication(msg.origin, null, null, null, 0)); - break; - case "Webapps:Uninstall:Return:KO": - if (this._onerror) - this._onerror.handleEvent(new RegistryError(Ci.mozIDOMApplicationRegistryError.PERMISSION_DENIED)); - break; - case "Webapps:Enumerate:Return:OK": - cb = this.getCallback(msg.callbackID); - if (cb.success) { - let apps = this._convertAppsArray(msg.apps); - cb.success.handleEvent(apps, apps.length); + case "Webapps:GetSelf:Return:OK": + if (msg.apps.length) { + app = msg.apps[0]; + Services.rs.fireSuccess(req, new WebappsApplication(this._window, app.origin, app.manifest, app.manifestURL, app.receipts, + app.installOrigin, app.installTime)); + } else { + Services.rs.fireSuccess(req, null); } break; - case "Webapps:Enumerate:Return:KO": - cb = this.getCallback(msg.callbackID); - if (cb.error) - cb.error.handleEvent(new RegistryError(Ci.mozIDOMApplicationRegistryError.PERMISSION_DENIED)); + case "Webapps:GetInstalled:Return:OK": + Services.rs.fireSuccess(req, convertAppsArray(msg.apps, this._window)); + break; + case "Webapps:GetSelf:Return:KO": + case "Webapps:GetInstalled:Return:KO": + Services.rs.fireError(req, "ERROR"); break; } - this.removeCallback(msg.callbackID); - }, - - _fireError: function(aCode) { - if (!this._onerror) - return; - this._onerror.handleEvent(new RegistryError(aCode)); + this.removeRequest(msg.requestID); }, _getOrigin: function(aURL) { @@ -181,7 +97,9 @@ WebappsRegistry.prototype = { // mozIDOMApplicationRegistry implementation - install: function(aURL, aReceipt) { + install: function(aURL, aParams) { + let request = this.createRequest(); + let requestID = this.getRequestId(request); let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest); xhr.open("GET", aURL, true); @@ -191,90 +109,70 @@ WebappsRegistry.prototype = { let installOrigin = this._getOrigin(this._window.location.href); let manifest = JSON.parse(xhr.responseText, installOrigin); if (!this.checkManifest(manifest, installOrigin)) { - this._fireError(Ci.mozIDOMApplicationRegistryError.INVALID_MANIFEST); + Services.rs.fireError(request, "INVALID_MANIFEST"); } else { - this.mm.sendAsyncMessage("Webapps:Install", { app: { installOrigin: installOrigin, - origin: this._getOrigin(aURL), - manifest: manifest, - receipt: aReceipt }, - from: this._window.location.href, - oid: this._id }); + let receipts = (aParams && aParams.receipts && Array.isArray(aParams.receipts)) ? aParams.receipts : []; + cpmm.sendAsyncMessage("Webapps:Install", { app: { installOrigin: installOrigin, + origin: this._getOrigin(aURL), + manifestURL: aURL, + manifest: manifest, + receipts: receipts }, + from: this._window.location.href, + oid: this._id, + requestID: requestID }); } } catch(e) { - this._fireError(Ci.mozIDOMApplicationRegistryError.MANIFEST_PARSE_ERROR); + Services.rs.fireError(request, "MANIFEST_PARSE_ERROR"); } } else { - this._fireError(Ci.mozIDOMApplicationRegistryError.MANIFEST_URL_ERROR); + Services.rs.fireError(request, "MANIFEST_URL_ERROR"); } }).bind(this), false); xhr.addEventListener("error", (function() { - this._fireError(Ci.mozIDOMApplicationRegistryError.NETWORK_ERROR); + Services.rs.fireError(request, "NETWORK_ERROR"); }).bind(this), false); xhr.send(null); + return request; }, - uninstall: function(aOrigin) { - if (this.hasPrivileges) - this.mm.sendAsyncMessage("Webapps:Uninstall", { from: this._window.location.href, - origin: aOrigin, - oid: this._id }); - else - throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + getSelf: function() { + let request = this.createRequest(); + cpmm.sendAsyncMessage("Webapps:GetSelf", { origin: this._getOrigin(this._window.location.href), + oid: this._id, + requestID: this.getRequestId(request) }); + return request; }, - launch: function(aOrigin) { - this.mm.sendAsyncMessage("Webapps:Launch", { origin: aOrigin, - from: this._window.location.href}); - }, - - enumerate: function(aSuccess, aError) { - this.mm.sendAsyncMessage("Webapps:Enumerate", { from: this._window.location.href, - origin: this._getOrigin(this._window.location.href), + getInstalled: function() { + let request = this.createRequest(); + cpmm.sendAsyncMessage("Webapps:GetInstalled", { origin: this._getOrigin(this._window.location.href), oid: this._id, - callbackID: this.getCallbackId({ success: aSuccess, error: aError }) }); + requestID: this.getRequestId(request) }); + return request; }, - enumerateAll: function(aSuccess, aError) { - if (this.hasPrivileges) { - this.mm.sendAsyncMessage("Webapps:EnumerateAll", { from: this._window.location.href, - origin: this._getOrigin(this._window.location.href), - oid: this._id, - callbackID: this.getCallbackId({ success: aSuccess, error: aError }) }); - } else { - if (aError) - aError.handleEvent(new RegistryError(Ci.mozIDOMApplicationRegistryError.PERMISSION_DENIED)); - } + get mgmt() { + if (!this._mgmt) + this._mgmt = new WebappsApplicationMgmt(this._window); + return this._mgmt; }, - observe: function(aSubject, aTopic, aData) { - let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data; - if (wId == this.innerWindowID) { - Services.obs.removeObserver(this, "inner-window-destroyed"); - this._oninstall = null; - this._onuninstall = null; - this._onerror = null; - this._callbacks = []; - this._window = null; - } + uninit: function() { + this._mgmt = null; }, // nsIDOMGlobalPropertyInitializer implementation init: function(aWindow) { - dump("DOMApplicationRegistry::init() " + aWindow + "\n"); - this._window = aWindow; - this._window.appId = this._id; - let from = Services.io.newURI(this._window.location.href, null, null); - let perm = Services.perms.testExactPermission(from, "webapps-manage"); - - //only pages with perm set and chrome or about pages can uninstall, enumerate all set oninstall an onuninstall - this.hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION || from.schemeIs("chrome") || from.schemeIs("about"); + this.initHelper(aWindow, ["Webapps:Install:Return:OK", "Webapps:Install:Return:KO", + "Webapps:GetInstalled:Return:OK", "Webapps:GetInstalled:Return:KO", + "Webapps:GetSelf:Return:OK", "Webapps:GetSelf:Return:KO"]); Services.obs.addObserver(this, "inner-window-destroyed", false); let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); - this.innerWindowID = util.currentInnerWindowID; + this._id = util.outerWindowID; }, classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"), @@ -288,18 +186,26 @@ WebappsRegistry.prototype = { classDescription: "Webapps Registry"}) } -function WebappsApplication(aOrigin, aManifest, aReceipt, aInstallOrigin, aInstallTime) { +/** + * mozIDOMApplication object + */ +function WebappsApplication(aWindow, aOrigin, aManifest, aManifestURL, aReceipts, aInstallOrigin, aInstallTime) { this._origin = aOrigin; this._manifest = aManifest; - this._receipt = aReceipt; + this._manifestURL = aManifestURL; + this._receipts = aReceipts; this._installOrigin = aInstallOrigin; this._installTime = aInstallTime; + + this.initHelper(aWindow, ["Webapps:Uninstall:Return:OK", "Webapps:Uninstall:Return:KO"]); } WebappsApplication.prototype = { + __proto__: DOMRequestIpcHelper.prototype, _origin: null, _manifest: null, - _receipt: null, + _manifestURL: null, + _receipts: [], _installOrigin: null, _installTime: 0, @@ -311,8 +217,12 @@ WebappsApplication.prototype = { return this._manifest; }, - get receipt() { - return this._receipt; + get manifestURL() { + return this._manifestURL; + }, + + get receipts() { + return this._receipts; }, get installOrigin() { @@ -323,6 +233,39 @@ WebappsApplication.prototype = { return this._installTime; }, + launch: function(aStartPoint) { + let request = this.createRequest(); + cpmm.sendAsyncMessage("Webapps:Launch", { origin: this._origin, + startPoint: aStartPoint, + oid: this._id, + requestID: this.getRequestId(request) }); + return request; + }, + + uninstall: function() { + let request = this.createRequest(); + cpmm.sendAsyncMessage("Webapps:Uninstall", { origin: this._origin, + oid: this._id, + requestID: this.getRequestId(request) }); + return request; + }, + + receiveMessage: function(aMessage) { + var msg = aMessage.json; + let req = this.getRequest(msg.requestID); + if (msg.oid != this._id || !req) + return; + switch (aMessage.name) { + case "Webapps:Uninstall:Return:OK": + Services.rs.fireSuccess(req, msg.origin); + break; + case "Webapps:Uninstall:Return:KO": + Services.rs.fireError(req, msg.origin); + break; + } + this.removeRequest(msg.requestID); + }, + classID: Components.ID("{723ed303-7757-4fb0-b261-4f78b1f6bd22}"), QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplication]), @@ -334,26 +277,131 @@ WebappsApplication.prototype = { classDescription: "Webapps Application"}) } -function RegistryError(aCode) { - this._code = aCode; +/** + * mozIDOMApplicationMgmt object + */ +function WebappsApplicationMgmt(aWindow) { + let principal = aWindow.document.nodePrincipal; + let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager); + + let perm = principal == secMan.getSystemPrincipal() ? + Ci.nsIPermissionManager.ALLOW_ACTION : + Services.perms.testExactPermission(principal.URI, "webapps-manage"); + + //only pages with perm set can use some functions + this.hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION; + + this.initHelper(aWindow, ["Webapps:GetAll:Return:OK", "Webapps:GetAll:Return:KO", + "Webapps:Install:Return:OK", "Webapps:Uninstall:Return:OK"]); + + this._oninstall = null; + this._onuninstall = null; } -RegistryError.prototype = { - _code: null, - - get code() { - return this._code; +WebappsApplicationMgmt.prototype = { + __proto__: DOMRequestIpcHelper.prototype, + + uninit: function() { + this._oninstall = null; + this._onuninstall = null; }, - - classID: Components.ID("{b4937718-11a3-400b-a69f-ab442a418569}"), - QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplicationRegistryError]), + getAll: function() { + let request = this.createRequest(); + cpmm.sendAsyncMessage("Webapps:GetAll", { oid: this._id, + requestID: this.getRequestId(request), + hasPrivileges: this.hasPrivileges }); + return request; + }, - classInfo: XPCOMUtils.generateCI({classID: Components.ID("{b4937718-11a3-400b-a69f-ab442a418569}"), - contractID: "@mozilla.org/webapps/error;1", - interfaces: [Ci.mozIDOMApplicationRegistryError], + get oninstall() { + return this._oninstall; + }, + + get onuninstall() { + this._onuninstall; + }, + + set oninstall(aCallback) { + if (this.hasPrivileges) + this._oninstall = aCallback; + else + + throw new Components.exception("Denied", Cr.NS_ERROR_FAILURE); + }, + + set onuninstall(aCallback) { + if (this.hasPrivileges) + this._onuninstall = aCallback; + else + throw new Components.exception("Denied", Cr.NS_ERROR_FAILURE); + }, + + receiveMessage: function(aMessage) { + var msg = aMessage.json; + let req = this.getRequest(msg.requestID); + // We want Webapps:Install:Return:OK and Webapps:Uninstall:Return:OK to be boradcasted + // to all instances of mozApps.mgmt + if (!((msg.oid == this._id && req) + || aMessage.name == "Webapps:Install:Return:OK" || aMessage.name == "Webapps:Uninstall:Return:OK")) + return; + switch (aMessage.name) { + case "Webapps:GetAll:Return:OK": + Services.rs.fireSuccess(req, convertAppsArray(msg.apps, this._window)); + break; + case "Webapps:GetAll:Return:KO": + Services.rs.fireError(req, "DENIED"); + break; + case "Webapps:Install:Return:OK": + if (this._oninstall) { + let app = msg.app; + let event = new WebappsApplicationEvent(new WebappsApplication(this._window, app.origin, app.manifest, app.manifestURL, app.receipts, + app.installOrigin, app.installTime)); + this._oninstall.handleEvent(event); + } + break; + case "Webapps:Uninstall:Return:OK": + if (this._onuninstall) { + let event = new WebappsApplicationEvent(new WebappsApplication(this._window, msg.origin, null, null, null, null, 0)); + this._onuninstall.handleEvent(event); + } + break; + } + this.removeRequest(msg.requestID); + }, + + classID: Components.ID("{8c1bca96-266f-493a-8d57-ec7a95098c15}"), + + QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplicationMgmt]), + + classInfo: XPCOMUtils.generateCI({classID: Components.ID("{8c1bca96-266f-493a-8d57-ec7a95098c15}"), + contractID: "@mozilla.org/webapps/application-mgmt;1", + interfaces: [Ci.mozIDOMApplicationMgmt], flags: Ci.nsIClassInfo.DOM_OBJECT, - classDescription: "Webapps Registry Error"}) + classDescription: "Webapps Application Mgmt"}) } -const NSGetFactory = XPCOMUtils.generateNSGetFactory([WebappsRegistry, WebappsApplication, RegistryError]); +/** + * mozIDOMApplicationEvent object + */ +function WebappsApplicationEvent(aApp) { + this._app = aApp; +} + +WebappsApplicationEvent.prototype = { + get application() { + return this._app; + }, + + classID: Components.ID("{5bc42b2a-9acc-49d5-a336-c353c8125e48}"), + + QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplicationEvent]), + + classInfo: XPCOMUtils.generateCI({classID: Components.ID("{8c1bca96-266f-493a-8d57-ec7a95098c15}"), + contractID: "@mozilla.org/webapps/application-event;1", + interfaces: [Ci.mozIDOMApplicationEvent], + flags: Ci.nsIClassInfo.DOM_OBJECT, + classDescription: "Webapps Application Event"}) +} + +const NSGetFactory = XPCOMUtils.generateNSGetFactory([WebappsRegistry, WebappsApplication]); diff --git a/dom/base/Webapps.jsm b/dom/base/Webapps.jsm index dbb4e1fe4c19..56b211541a3e 100644 --- a/dom/base/Webapps.jsm +++ b/dom/base/Webapps.jsm @@ -1,39 +1,6 @@ -/* ***** 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 Mobile Browser. - * - * The Initial Developer of the Original Code is Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Fabrice Desré - * Mark Finkle - * - * 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 ***** */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ const Cu = Components.utils; const Cc = Components.classes; @@ -50,17 +17,21 @@ XPCOMUtils.defineLazyGetter(this, "NetUtil", function() { return NetUtil; }); +XPCOMUtils.defineLazyGetter(this, "ppmm", function() { + return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager); +}); + let DOMApplicationRegistry = { appsFile: null, webapps: { }, init: function() { - this.mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager); let messages = ["Webapps:Install", "Webapps:Uninstall", - "Webapps:Enumerate", "Webapps:EnumerateAll", "Webapps:Launch"]; + "Webapps:GetSelf", "Webapps:GetInstalled", + "Webapps:Launch", "Webapps:GetAll"]; messages.forEach((function(msgName) { - this.mm.addMessageListener(msgName, this); + ppmm.addMessageListener(msgName, this); }).bind(this)); let appsDir = FileUtils.getDir("ProfD", ["webapps"], true, true); @@ -114,29 +85,29 @@ let DOMApplicationRegistry = { receiveMessage: function(aMessage) { let msg = aMessage.json; - let from = Services.io.newURI(msg.from, null, null); - let perm = Services.perms.testExactPermission(from, "webapps-manage"); - - //only pages with perm set and chrome or about pages can uninstall, enumerate all set oninstall an onuninstall - let hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION || from.schemeIs("chrome") || from.schemeIs("about"); switch (aMessage.name) { case "Webapps:Install": // always ask for UI to install Services.obs.notifyObservers(this, "webapps-ask-install", JSON.stringify(msg)); break; + case "Webapps:GetSelf": + this.getSelf(msg); + break; case "Webapps:Uninstall": - if (hasPrivileges) - this.uninstall(msg); + this.uninstall(msg); break; case "Webapps:Launch": Services.obs.notifyObservers(this, "webapps-launch", JSON.stringify(msg)); break; - case "Webapps:Enumerate": - this.enumerate(msg); + case "Webapps:GetInstalled": + this.getInstalled(msg); break; - case "Webapps:EnumerateAll": - this.enumerateAll(msg); + case "Webapps:GetAll": + if (msg.hasPrivileges) + this.getAll(msg); + else + ppmm.sendAsyncMessage("Webapps:GetAll:Return:KO", msg); break; } }, @@ -162,14 +133,15 @@ let DOMApplicationRegistry = { let clone = { installOrigin: aApp.installOrigin, origin: aApp.origin, - receipt: aApp.receipt, - installTime: aApp.installTime + receipts: aApp.receipts, + installTime: aApp.installTime, + manifestURL: aApp.manifestURL }; return clone; }, denyInstall: function(aData) { - this.mm.sendAsyncMessage("Webapps:Install:Return:KO", aData); + ppmm.sendAsyncMessage("Webapps:Install:Return:KO", aData); }, confirmInstall: function(aData, aFromSync) { @@ -202,7 +174,7 @@ let DOMApplicationRegistry = { if (!aFromSync) this._saveApps((function() { - this.mm.sendAsyncMessage("Webapps:Install:Return:OK", aData); + ppmm.sendAsyncMessage("Webapps:Install:Return:OK", aData); Services.obs.notifyObservers(this, "webapps-sync-install", id); }).bind(this)); }, @@ -244,9 +216,11 @@ let DOMApplicationRegistry = { }, uninstall: function(aData) { + let found = false; for (let id in this.webapps) { let app = this.webapps[id]; if (app.origin == aData.origin) { + found = true; delete this.webapps[id]; let dir = FileUtils.getDir("ProfD", ["webapps", id], true, true); try { @@ -254,62 +228,53 @@ let DOMApplicationRegistry = { } catch (e) { } this._saveApps((function() { - this.mm.sendAsyncMessage("Webapps:Uninstall:Return:OK", aData); + ppmm.sendAsyncMessage("Webapps:Uninstall:Return:OK", aData); Services.obs.notifyObservers(this, "webapps-sync-uninstall", id); }).bind(this)); } } + if (!found) + ppmm.sendAsyncMessage("Webapps:Uninstall:Return:KO", aData); }, - enumerate: function(aData) { + getSelf: function(aData) { aData.apps = []; let tmp = []; - let selfId; - let id = this._appId(aData.origin); - // if it's an app, add itself to the result + if (id) { let app = this._cloneAppObject(this.webapps[id]); aData.apps.push(app); tmp.push({ id: id }); - selfId = id; } - // check if it's a store. - let isStore = false; + this._readManifests(tmp, (function(aResult) { + for (let i = 0; i < aResult.length; i++) + aData.apps[i].manifest = aResult[i].manifest; + ppmm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData); + }).bind(this)); + }, + + getInstalled: function(aData) { + aData.apps = []; + let tmp = []; + let id = this._appId(aData.origin); + for (id in this.webapps) { - let app = this.webapps[id]; - if (app.installOrigin == aData.origin) { - isStore = true; - break; - } - } - - // add all the apps from this store - if (isStore) { - for (id in this.webapps) { - if (id == selfId) - continue; - let app = this._cloneAppObject(this.webapps[id]); - if (app.installOrigin == aData.origin) { - aData.apps.push(app); - tmp.push({ id: id }); - } + if (this.webapps[id].installOrigin == aData.origin) { + aData.apps.push(this._cloneAppObject(this.webapps[id])); + tmp.push({ id: id }); } } this._readManifests(tmp, (function(aResult) { for (let i = 0; i < aResult.length; i++) aData.apps[i].manifest = aResult[i].manifest; - this.mm.sendAsyncMessage("Webapps:Enumerate:Return:OK", aData); + ppmm.sendAsyncMessage("Webapps:GetInstalled:Return:OK", aData); }).bind(this)); }, - denyEnumerate: function(aData) { - this.mm.sendAsyncMessage("Webapps:Enumerate:Return:KO", aData); - }, - - enumerateAll: function(aData) { + getAll: function(aData) { aData.apps = []; let tmp = []; @@ -322,7 +287,7 @@ let DOMApplicationRegistry = { this._readManifests(tmp, (function(aResult) { for (let i = 0; i < aResult.length; i++) aData.apps[i].manifest = aResult[i].manifest; - this.mm.sendAsyncMessage("Webapps:Enumerate:Return:OK", aData); + ppmm.sendAsyncMessage("Webapps:GetAll:Return:OK", aData); }).bind(this)); }, @@ -368,7 +333,7 @@ let DOMApplicationRegistry = { dir.remove(true); } catch (e) { } - this.mm.sendAsyncMessage("Webapps:Uninstall:Return:OK", { origin: origin }); + ppmm.sendAsyncMessage("Webapps:Uninstall:Return:OK", { origin: origin }); } else { if (!!this.webapps[record.id]) { this.webapps[record.id] = record.value; @@ -377,7 +342,7 @@ let DOMApplicationRegistry = { else { let data = { app: record.value }; this.confirmInstall(data, true); - this.mm.sendAsyncMessage("Webapps:Install:Return:OK", data); + ppmm.sendAsyncMessage("Webapps:Install:Return:OK", data); } } } diff --git a/dom/base/Webapps.manifest b/dom/base/Webapps.manifest index e1bf05ae57c7..8699961c5390 100644 --- a/dom/base/Webapps.manifest +++ b/dom/base/Webapps.manifest @@ -2,9 +2,3 @@ component {fff440b3-fae2-45c1-bf03-3b5a2e432270} Webapps.js contract @mozilla.org/webapps;1 {fff440b3-fae2-45c1-bf03-3b5a2e432270} category JavaScript-navigator-property mozApps @mozilla.org/webapps;1 - -component {723ed303-7757-4fb0-b261-4f78b1f6bd22} Webapps.js -contract @mozilla.org/webapps/application;1 {723ed303-7757-4fb0-b261-4f78b1f6bd22} - -component {b4937718-11a3-400b-a69f-ab442a418569} Webapps.js -contract @mozilla.org/webapps/error;1 {b4937718-11a3-400b-a69f-ab442a418569} diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index ecbde6f25f58..cd7c6d2c7191 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -507,6 +507,7 @@ using mozilla::dom::indexedDB::IDBWrapperCache; #include "nsIDOMBatteryManager.h" #include "BatteryManager.h" #include "nsIDOMPowerManager.h" +#include "nsIDOMWakeLock.h" #include "nsIDOMSmsManager.h" #include "nsIDOMSmsMessage.h" #include "nsIDOMSmsEvent.h" @@ -548,6 +549,7 @@ static const char kDOMStringBundleURL[] = nsIXPCScriptable::WANT_ENUMERATE | \ nsIXPCScriptable::DONT_ENUM_QUERY_INTERFACE | \ nsIXPCScriptable::USE_STUB_EQUALITY_HOOK | \ + nsIXPCScriptable::IS_GLOBAL_OBJECT | \ nsIXPCScriptable::WANT_OUTER_OBJECT) #define NODE_SCRIPTABLE_FLAGS \ @@ -1435,6 +1437,9 @@ static nsDOMClassInfoData sClassInfoData[] = { NS_DEFINE_CLASSINFO_DATA(MozPowerManager, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(MozWakeLock, nsDOMGenericSH, + DOM_DEFAULT_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(MozSmsManager, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) @@ -1549,7 +1554,7 @@ static nsDOMClassInfoData sClassInfoData[] = { NS_DEFINE_CLASSINFO_DATA(AnimationEvent, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) NS_DEFINE_CLASSINFO_DATA(ContentFrameMessageManager, nsEventTargetSH, - DOM_DEFAULT_SCRIPTABLE_FLAGS) + DOM_DEFAULT_SCRIPTABLE_FLAGS | nsIXPCScriptable::IS_GLOBAL_OBJECT) NS_DEFINE_CLASSINFO_DATA(FormData, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) @@ -1617,8 +1622,8 @@ static nsDOMClassInfoData sClassInfoData[] = { #endif #ifdef MOZ_B2G_BT - NS_DEFINE_CLASSINFO_DATA(BluetoothAdapter, nsDOMGenericSH, - DOM_DEFAULT_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(BluetoothAdapter, nsEventTargetSH, + EVENTTARGET_SCRIPTABLE_FLAGS) #endif NS_DEFINE_CLASSINFO_DATA(DOMError, nsDOMGenericSH, @@ -4030,6 +4035,10 @@ nsDOMClassInfo::Init() DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozPowerManager) DOM_CLASSINFO_MAP_END + DOM_CLASSINFO_MAP_BEGIN(MozWakeLock, nsIDOMMozWakeLock) + DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozWakeLock) + DOM_CLASSINFO_MAP_END + DOM_CLASSINFO_MAP_BEGIN(MozSmsManager, nsIDOMMozSmsManager) DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozSmsManager) DOM_CLASSINFO_MAP_END diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h index 37d56358becd..14c7e78114ac 100644 --- a/dom/base/nsDOMClassInfoClasses.h +++ b/dom/base/nsDOMClassInfoClasses.h @@ -429,6 +429,7 @@ DOMCI_CLASS(GeoPositionError) DOMCI_CLASS(MozBatteryManager) DOMCI_CLASS(MozPowerManager) +DOMCI_CLASS(MozWakeLock) DOMCI_CLASS(MozSmsManager) DOMCI_CLASS(MozSmsMessage) diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 99e072a2f634..495e5658654b 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -144,7 +144,11 @@ static PRLogModuleInfo* gJSDiagnostics; #define NS_CC_SKIPPABLE_DELAY 400 // ms -#define NS_CC_FORCED (5 * 60 * PR_USEC_PER_SEC) // 5 min +// Force a CC after this long if there's anything in the purple buffer. +#define NS_CC_FORCED (2 * 60 * PR_USEC_PER_SEC) // 2 min + +// Trigger a CC if the purple buffer exceeds this size when we check it. +#define NS_CC_PURPLE_LIMIT 250 #define JAVASCRIPT nsIProgrammingLanguage::JAVASCRIPT @@ -2227,11 +2231,9 @@ nsJSContext::CreateNativeGlobalForInner( nsRefPtr jsholder; nsresult rv = xpc-> - InitClassesWithNewWrappedGlobal(mContext, - aNewInner, NS_GET_IID(nsISupports), + InitClassesWithNewWrappedGlobal(mContext, aNewInner, aIsChrome ? systemPrincipal.get() : aPrincipal, - nsnull, flags, - getter_AddRefs(jsholder)); + flags, getter_AddRefs(jsholder)); if (NS_FAILED(rv)) { return rv; } @@ -2324,7 +2326,7 @@ nsJSContext::SetOuterObject(JSObject* aOuterObject) NS_ENSURE_SUCCESS(rv, rv); NS_ABORT_IF_FALSE(wrapper, "bad wrapper"); - wrapper->RefreshPrototype(); + wrapper->FinishInitForWrappedGlobal(); JS_SetPrototype(mContext, aOuterObject, JS_GetPrototype(inner)); return NS_OK; @@ -3286,46 +3288,70 @@ ShrinkGCBuffersTimerFired(nsITimer *aTimer, void *aClosure) nsJSContext::ShrinkGCBuffersNow(); } -// static -void +static bool +ShouldTriggerCC(PRUint32 aSuspected) +{ + return sNeedsFullCC || + aSuspected > NS_CC_PURPLE_LIMIT || + sLastCCEndTime + NS_CC_FORCED < PR_Now(); +} + +static void +TimerFireForgetSkippable(PRUint32 aSuspected, bool aRemoveChildless) +{ + PRTime startTime = PR_Now(); + nsCycleCollector_forgetSkippable(aRemoveChildless); + sPreviousSuspectedCount = nsCycleCollector_suspectedCount(); + sCleanupSinceLastGC = true; + PRTime delta = PR_Now() - startTime; + if (sMinForgetSkippableTime > delta) { + sMinForgetSkippableTime = delta; + } + if (sMaxForgetSkippableTime < delta) { + sMaxForgetSkippableTime = delta; + } + sTotalForgetSkippableTime += delta; + sRemovedPurples += (aSuspected - sPreviousSuspectedCount); + ++sForgetSkippableBeforeCC; +} + +static void CCTimerFired(nsITimer *aTimer, void *aClosure) { - if (sDidShutdown) { - return; - } - if (sCCLockedOut) { + if (sDidShutdown || sCCLockedOut) { return; } ++sCCTimerFireCount; - if (sCCTimerFireCount < (NS_CC_DELAY / NS_CC_SKIPPABLE_DELAY)) { - PRUint32 suspected = nsCycleCollector_suspectedCount(); - if ((sPreviousSuspectedCount + 100) > suspected) { - // Just few new suspected objects, return early. - return; - } - - PRTime startTime = PR_Now(); - nsCycleCollector_forgetSkippable(); - sPreviousSuspectedCount = nsCycleCollector_suspectedCount(); - sCleanupSinceLastGC = true; - PRTime delta = PR_Now() - startTime; - if (sMinForgetSkippableTime > delta) { - sMinForgetSkippableTime = delta; - } - if (sMaxForgetSkippableTime < delta) { - sMaxForgetSkippableTime = delta; - } - sTotalForgetSkippableTime += delta; - sRemovedPurples += (suspected - sPreviousSuspectedCount); - ++sForgetSkippableBeforeCC; - } else { - sPreviousSuspectedCount = 0; - nsJSContext::KillCCTimer(); - if (sNeedsFullCC || - nsCycleCollector_suspectedCount() > 500 || - sLastCCEndTime + NS_CC_FORCED < PR_Now()) { + + // During early timer fires, we only run forgetSkippable. During the first + // late timer fire, we decide if we are going to have a second and final + // late timer fire, where we may run the CC. + const PRUint32 numEarlyTimerFires = NS_CC_DELAY / NS_CC_SKIPPABLE_DELAY - 2; + bool isLateTimerFire = sCCTimerFireCount > numEarlyTimerFires; + PRUint32 suspected = nsCycleCollector_suspectedCount(); + if (isLateTimerFire && ShouldTriggerCC(suspected)) { + if (sCCTimerFireCount == numEarlyTimerFires + 1) { + TimerFireForgetSkippable(suspected, true); + if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) { + // Our efforts to avoid a CC have failed, so we return to let the + // timer fire once more to trigger a CC. + return; + } + } else { + // We are in the final timer fire and still meet the conditions for + // triggering a CC. nsJSContext::CycleCollectNow(); } + } else if ((sPreviousSuspectedCount + 100) <= suspected) { + // Only do a forget skippable if there are more than a few new objects. + TimerFireForgetSkippable(suspected, false); + } + + if (isLateTimerFire) { + // We have either just run the CC or decided we don't want to run the CC + // next time, so kill the timer. + sPreviousSuspectedCount = 0; + nsJSContext::KillCCTimer(); } } diff --git a/dom/bluetooth/BluetoothAdapter.cpp b/dom/bluetooth/BluetoothAdapter.cpp index 446d9c4332ed..0b974922044d 100644 --- a/dom/bluetooth/BluetoothAdapter.cpp +++ b/dom/bluetooth/BluetoothAdapter.cpp @@ -16,47 +16,56 @@ #include #endif -#define POWERED_EVENT_NAME NS_LITERAL_STRING("powered") - -BEGIN_BLUETOOTH_NAMESPACE +USING_BLUETOOTH_NAMESPACE class ToggleBtResultTask : public nsRunnable { public: - ToggleBtResultTask(bool result, nsRefPtr& adapterPtr) + ToggleBtResultTask(nsRefPtr& adapterPtr, bool result) : mResult(result) { - MOZ_ASSERT(!NS_IsMainThread()); // This should be running on the worker thread + MOZ_ASSERT(!NS_IsMainThread()); mAdapterPtr.swap(adapterPtr); } - NS_IMETHOD Run() { - MOZ_ASSERT(NS_IsMainThread()); // This method is supposed to run on the main thread! + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mResult) { + //TODO:Bug-731361 + NS_WARNING("BT firmware loading fails.\n"); + } + + //mAdapterPtr must be null before returning to prevent the background + //thread from racing to release it during the destruction of this runnable. mAdapterPtr->FirePowered(); + mAdapterPtr = nsnull; return NS_OK; } private: - bool mResult; nsRefPtr mAdapterPtr; + bool mResult; }; class ToggleBtTask : public nsRunnable { public: - ToggleBtTask(bool onOff, BluetoothAdapter* adapterPtr) + ToggleBtTask(bool onOff, BluetoothAdapter* adapterPtr) : mOnOff(onOff), - mAdapterPtr(adapterPtr) + mAdapterPtr(adapterPtr) { - MOZ_ASSERT(NS_IsMainThread()); // The constructor should be running on the main thread. + MOZ_ASSERT(NS_IsMainThread()); } - NS_IMETHOD Run() { - bool result; + NS_IMETHOD Run() + { + MOZ_ASSERT(!NS_IsMainThread()); - MOZ_ASSERT(!NS_IsMainThread()); // This should be running on the worker thread. + bool result; //Toggle BT here #if defined(MOZ_WIDGET_GONK) @@ -65,13 +74,16 @@ class ToggleBtTask : public nsRunnable } else { result = bt_disable(); } -#else +#else result = true; #endif // Create a result thread and pass it to Main Thread, - nsCOMPtr resultRunnable = new ToggleBtResultTask(result, mAdapterPtr); - NS_DispatchToMainThread(resultRunnable); + nsCOMPtr resultRunnable = new ToggleBtResultTask(mAdapterPtr, result); + + if (NS_FAILED(NS_DispatchToMainThread(resultRunnable))) { + NS_WARNING("Failed to dispatch to main thread!"); + } return NS_OK; } @@ -81,25 +93,21 @@ class ToggleBtTask : public nsRunnable bool mOnOff; }; -END_BLUETOOTH_NAMESPACE - -DOMCI_DATA(BluetoothAdapter, mozilla::dom::bluetooth::BluetoothAdapter) - -USING_BLUETOOTH_NAMESPACE +DOMCI_DATA(BluetoothAdapter, BluetoothAdapter) NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothAdapter) -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothAdapter, - nsDOMEventTargetHelper) -NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(powered) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothAdapter, + nsDOMEventTargetHelper) + NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(powered) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothAdapter, - nsDOMEventTargetHelper) -NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(powered) - NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothAdapter, + nsDOMEventTargetHelper) + NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(powered) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END - NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothAdapter) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothAdapter) NS_INTERFACE_MAP_ENTRY(nsIDOMBluetoothAdapter) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(BluetoothAdapter) NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) @@ -107,18 +115,16 @@ NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) NS_IMPL_ADDREF_INHERITED(BluetoothAdapter, nsDOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(BluetoothAdapter, nsDOMEventTargetHelper) -BluetoothAdapter::BluetoothAdapter() : mPower(false) +BluetoothAdapter::BluetoothAdapter() + : mPower(false) { } NS_IMETHODIMP BluetoothAdapter::GetPower(bool* aPower) { -#if defined(MOZ_WIDGET_GONK) - *aPower = bt_is_enabled(); -#else *aPower = mPower; -#endif + return NS_OK; } @@ -128,13 +134,13 @@ BluetoothAdapter::SetPower(bool aPower) if (mPower != aPower) { mPower = aPower; - ToggleBluetoothAsync(); + return ToggleBluetoothAsync(); } return NS_OK; } -void +nsresult BluetoothAdapter::ToggleBluetoothAsync() { if (!mToggleBtThread) { @@ -143,14 +149,17 @@ BluetoothAdapter::ToggleBluetoothAsync() nsCOMPtr r = new ToggleBtTask(mPower, this); - mToggleBtThread->Dispatch(r, 0); + return mToggleBtThread->Dispatch(r, NS_DISPATCH_NORMAL); } nsresult BluetoothAdapter::FirePowered() { nsRefPtr event = new nsDOMEvent(nsnull, nsnull); - nsresult rv = event->InitEvent(POWERED_EVENT_NAME, false, false); + nsresult rv = event->InitEvent(NS_LITERAL_STRING("powered"), false, false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = event->SetTrusted(true); NS_ENSURE_SUCCESS(rv, rv); bool dummy; diff --git a/dom/bluetooth/BluetoothAdapter.h b/dom/bluetooth/BluetoothAdapter.h index fa395a800d2a..c0559824672f 100644 --- a/dom/bluetooth/BluetoothAdapter.h +++ b/dom/bluetooth/BluetoothAdapter.h @@ -11,10 +11,12 @@ #include "nsDOMEventTargetHelper.h" #include "nsIDOMBluetoothAdapter.h" +class nsIEventTarget; + BEGIN_BLUETOOTH_NAMESPACE class BluetoothAdapter : public nsIDOMBluetoothAdapter - ,public nsDOMEventTargetHelper + , public nsDOMEventTargetHelper { public: NS_DECL_ISUPPORTS @@ -36,7 +38,7 @@ protected: private: nsCOMPtr mToggleBtThread; - void ToggleBluetoothAsync(); + nsresult ToggleBluetoothAsync(); }; END_BLUETOOTH_NAMESPACE diff --git a/dom/contacts/ContactManager.js b/dom/contacts/ContactManager.js index 9821330351d1..00955a56fd7a 100644 --- a/dom/contacts/ContactManager.js +++ b/dom/contacts/ContactManager.js @@ -17,6 +17,15 @@ const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/DOMRequestHelper.jsm"); + +XPCOMUtils.defineLazyGetter(Services, "rs", function() { + return Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci.nsIDOMRequestService); +}); + +XPCOMUtils.defineLazyGetter(this, "cpmm", function() { + return Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager); +}); const nsIClassInfo = Ci.nsIClassInfo; const CONTACTPROPERTIES_CID = Components.ID("{53ed7c20-ceda-11e0-9572-0800200c9a66}"); @@ -184,6 +193,7 @@ function ContactManager() } ContactManager.prototype = { + __proto__: DOMRequestIpcHelper.prototype, save: function save(aContact) { let request; @@ -222,9 +232,9 @@ ContactManager.prototype = { this._setMetaData(newContact, aContact); debug("send: " + JSON.stringify(newContact)); - request = this._rs.createRequest(this._window); - this._mm.sendAsyncMessage("Contact:Save", {contact: newContact, - requestID: this.getRequestId({ request: request })}); + request = this.createRequest(); + cpmm.sendAsyncMessage("Contact:Save", {contact: newContact, + requestID: this.getRequestId(request)}); return request; } else { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; @@ -234,9 +244,9 @@ ContactManager.prototype = { remove: function removeContact(aRecord) { let request; if (this.hasPrivileges) { - request = this._rs.createRequest(this._window); - this._mm.sendAsyncMessage("Contact:Remove", {id: aRecord.id, - requestID: this.getRequestId({ request: request })}); + request = this.createRequest(); + cpmm.sendAsyncMessage("Contact:Remove", {id: aRecord.id, + requestID: this.getRequestId(request)}); return request; } else { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; @@ -260,26 +270,6 @@ ContactManager.prototype = { return contacts; }, - getRequestId: function(aRequest) { - let id = "id" + this._getRandomId(); - this._requests[id] = aRequest; - return id; - }, - - getRequest: function(aId) { - if (this._requests[aId]) - return this._requests[aId].request; - }, - - removeRequest: function(aId) { - if (this._requests[aId]) - delete this._requests[aId]; - }, - - _getRandomId: function() { - return Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString(); - }, - receiveMessage: function(aMessage) { debug("Contactmanager::receiveMessage: " + aMessage.name); let msg = aMessage.json; @@ -291,7 +281,7 @@ ContactManager.prototype = { if (req) { let result = this._convertContactsArray(contacts); debug("result: " + JSON.stringify(result)); - this._rs.fireSuccess(req, result); + Services.rs.fireSuccess(req, result); } else { debug("no request stored!" + msg.requestID); } @@ -301,7 +291,7 @@ ContactManager.prototype = { case "Contact:Remove:Return:OK": req = this.getRequest(msg.requestID); if (req) - this._rs.fireSuccess(req, 0); + Services.rs.fireSuccess(req, null); break; case "Contacts:Find:Return:KO": case "Contact:Save:Return:KO": @@ -309,7 +299,7 @@ ContactManager.prototype = { case "Contacts:Clear:Return:KO": req = this.getRequest(msg.requestID); if (req) - this._rs.fireError(req, msg.errorMsg); + Services.rs.fireError(req, msg.errorMsg); break; default: debug("Wrong message: " + aMessage.name); @@ -320,9 +310,9 @@ ContactManager.prototype = { find: function(aOptions) { let request; if (this.hasPrivileges) { - request = this._rs.createRequest(this._window); - this._mm.sendAsyncMessage("Contacts:Find", {findOptions: aOptions, - requestID: this.getRequestId({ request: request })}); + request = this.createRequest(); + cpmm.sendAsyncMessage("Contacts:Find", {findOptions: aOptions, + requestID: this.getRequestId(request)}); return request; } else { debug("find not allowed"); @@ -333,8 +323,8 @@ ContactManager.prototype = { clear: function() { let request; if (this.hasPrivileges) { - request = this._rs.createRequest(this._window); - this._mm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId({ request: request })}); + request = this.createRequest(); + cpmm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId(request)}); return request; } else { debug("clear not allowed"); @@ -347,22 +337,12 @@ ContactManager.prototype = { if (!Services.prefs.getBoolPref("dom.mozContacts.enabled")) return null; - this._window = aWindow; - this._messages = ["Contacts:Find:Return:OK", "Contacts:Find:Return:KO", + this.initHelper(aWindow, ["Contacts:Find:Return:OK", "Contacts:Find:Return:KO", "Contacts:Clear:Return:OK", "Contacts:Clear:Return:KO", "Contact:Save:Return:OK", "Contact:Save:Return:KO", - "Contact:Remove:Return:OK", "Contact:Remove:Return:KO"]; + "Contact:Remove:Return:OK", "Contact:Remove:Return:KO"]); - this._mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager); - this._messages.forEach((function(msgName) { - this._mm.addMessageListener(msgName, this); - }).bind(this)); - - this._rs = Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci.nsIDOMRequestService); - this._requests = []; Services.obs.addObserver(this, "inner-window-destroyed", false); - let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); - this._innerWindowID = util.currentInnerWindowID; let principal = aWindow.document.nodePrincipal; let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager); @@ -376,21 +356,6 @@ ContactManager.prototype = { debug("has privileges :" + this.hasPrivileges); }, - observe: function(aSubject, aTopic, aData) { - let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data; - if (wId == this.innerWindowID) { - Services.obs.removeObserver(this, "inner-window-destroyed"); - this._messages.forEach((function(msgName) { - this._mm.removeMessageListener(msgName, this); - }).bind(this)); - this._mm = null; - this._messages = null; - this._requests = null; - this._window = null; - this._innerWindowID = null; - } - }, - classID : CONTACTMANAGER_CID, QueryInterface : XPCOMUtils.generateQI([nsIDOMContactManager, Ci.nsIDOMGlobalPropertyInitializer]), diff --git a/dom/contacts/fallback/ContactService.jsm b/dom/contacts/fallback/ContactService.jsm index 9b76da688b17..63aa0c9dc006 100644 --- a/dom/contacts/fallback/ContactService.jsm +++ b/dom/contacts/fallback/ContactService.jsm @@ -20,16 +20,18 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/ContactDB.jsm"); +XPCOMUtils.defineLazyGetter(this, "ppmm", function() { + return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager); +}); + let myGlobal = this; let DOMContactManager = { - init: function() { debug("Init"); - this._mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager); this._messages = ["Contacts:Find", "Contacts:Clear", "Contact:Save", "Contact:Remove"]; this._messages.forEach((function(msgName) { - this._mm.addMessageListener(msgName, this); + ppmm.addMessageListener(msgName, this); }).bind(this)); var idbManager = Components.classes["@mozilla.org/dom/indexeddb/manager;1"].getService(Ci.nsIIndexedDatabaseManager); @@ -52,10 +54,10 @@ let DOMContactManager = { observe: function(aSubject, aTopic, aData) { myGlobal = null; this._messages.forEach((function(msgName) { - this._mm.removeMessageListener(msgName, this); + ppmm.removeMessageListener(msgName, this); }).bind(this)); Services.obs.removeObserver(this, "profile-before-change"); - this._mm = null; + ppmm = null; this._messages = null; if (this._db) this._db.close(); @@ -72,23 +74,23 @@ let DOMContactManager = { for (let i in contacts) result.push(contacts[i]); debug("result:" + JSON.stringify(result)); - this._mm.sendAsyncMessage("Contacts:Find:Return:OK", {requestID: msg.requestID, contacts: result}); + ppmm.sendAsyncMessage("Contacts:Find:Return:OK", {requestID: msg.requestID, contacts: result}); }.bind(this), - function(aErrorMsg) { this._mm.sendAsyncMessage("Contacts:Find:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }) }.bind(this), + function(aErrorMsg) { ppmm.sendAsyncMessage("Contacts:Find:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }) }.bind(this), msg.findOptions); break; case "Contact:Save": - this._db.saveContact(msg.contact, function() {this._mm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID }); }.bind(this), - function(aErrorMsg) { this._mm.sendAsyncMessage("Contact:Save:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)); + this._db.saveContact(msg.contact, function() { ppmm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID }); }.bind(this), + function(aErrorMsg) { ppmm.sendAsyncMessage("Contact:Save:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)); break; case "Contact:Remove": this._db.removeContact(msg.id, - function() {this._mm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID }); }.bind(this), - function(aErrorMsg) {this._mm.sendAsyncMessage("Contact:Remove:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)); + function() { ppmm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID }); }.bind(this), + function(aErrorMsg) { ppmm.sendAsyncMessage("Contact:Remove:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)); break; case "Contacts:Clear": - this._db.clear(function() { this._mm.sendAsyncMessage("Contacts:Clear:Return:OK", { requestID: msg.requestID }); }.bind(this), - function(aErrorMsg) { this._mm.sendAsyncMessage("Contacts:Clear:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)); + this._db.clear(function() { ppmm.sendAsyncMessage("Contacts:Clear:Return:OK", { requestID: msg.requestID }); }.bind(this), + function(aErrorMsg) { ppmm.sendAsyncMessage("Contacts:Clear:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)); } } } diff --git a/dom/indexedDB/AsyncConnectionHelper.cpp b/dom/indexedDB/AsyncConnectionHelper.cpp index 26cfa17d4ffb..8adb60d292b1 100644 --- a/dom/indexedDB/AsyncConnectionHelper.cpp +++ b/dom/indexedDB/AsyncConnectionHelper.cpp @@ -90,7 +90,7 @@ ConvertCloneReadInfosToArrayInternal( } if (!aReadInfos.IsEmpty()) { - if (!JS_SetArrayLength(aCx, array, jsuint(aReadInfos.Length()))) { + if (!JS_SetArrayLength(aCx, array, uint32_t(aReadInfos.Length()))) { NS_WARNING("Failed to set array length!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } diff --git a/dom/indexedDB/IDBDatabase.cpp b/dom/indexedDB/IDBDatabase.cpp index 11154d325535..22bdcffdcf36 100644 --- a/dom/indexedDB/IDBDatabase.cpp +++ b/dom/indexedDB/IDBDatabase.cpp @@ -410,7 +410,7 @@ IDBDatabase::CreateObjectStore(const nsAString& aName, JSObject* obj = JSVAL_TO_OBJECT(val); - jsuint length; + uint32_t length; if (!JS_GetArrayLength(aCx, obj, &length)) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } @@ -421,7 +421,7 @@ IDBDatabase::CreateObjectStore(const nsAString& aName, keyPathArray.SetCapacity(length); - for (jsuint index = 0; index < length; index++) { + for (uint32_t index = 0; index < length; index++) { jsval val; JSString* jsstr; nsDependentJSString str; @@ -566,7 +566,7 @@ IDBDatabase::Transaction(const jsval& aStoreNames, // See if this is a JS array. if (JS_IsArrayObject(aCx, obj)) { - jsuint length; + uint32_t length; if (!JS_GetArrayLength(aCx, obj, &length)) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } @@ -577,7 +577,7 @@ IDBDatabase::Transaction(const jsval& aStoreNames, storesToOpen.SetCapacity(length); - for (jsuint index = 0; index < length; index++) { + for (uint32_t index = 0; index < length; index++) { jsval val; JSString* jsstr; nsDependentJSString str; diff --git a/dom/indexedDB/IDBIndex.cpp b/dom/indexedDB/IDBIndex.cpp index d039e66e1580..f2d3551e317e 100644 --- a/dom/indexedDB/IDBIndex.cpp +++ b/dom/indexedDB/IDBIndex.cpp @@ -904,7 +904,7 @@ GetAllKeysHelper::GetSuccessResult(JSContext* aCx, } if (!keys.IsEmpty()) { - if (!JS_SetArrayLength(aCx, array, jsuint(keys.Length()))) { + if (!JS_SetArrayLength(aCx, array, uint32_t(keys.Length()))) { NS_WARNING("Failed to set array length!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index c2376f531f49..b3704c7cf4b1 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -618,12 +618,12 @@ IDBObjectStore::AppendIndexUpdateInfo(PRInt64 aIndexID, if (aMultiEntry && !JSVAL_IS_PRIMITIVE(key) && JS_IsArrayObject(aCx, JSVAL_TO_OBJECT(key))) { JSObject* array = JSVAL_TO_OBJECT(key); - jsuint arrayLength; + uint32_t arrayLength; if (!JS_GetArrayLength(aCx, array, &arrayLength)) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } - for (jsuint arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) { + for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) { jsval arrayItem; if (!JS_GetElement(aCx, array, arrayIndex, &arrayItem)) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; @@ -1700,7 +1700,7 @@ IDBObjectStore::CreateIndex(const nsAString& aName, JSObject* obj = JSVAL_TO_OBJECT(aKeyPath); - jsuint length; + uint32_t length; if (!JS_GetArrayLength(aCx, obj, &length)) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } @@ -1711,7 +1711,7 @@ IDBObjectStore::CreateIndex(const nsAString& aName, keyPathArray.SetCapacity(length); - for (jsuint index = 0; index < length; index++) { + for (uint32_t index = 0; index < length; index++) { jsval val; JSString* jsstr; nsDependentJSString str; diff --git a/dom/indexedDB/Key.cpp b/dom/indexedDB/Key.cpp index 5f3544396697..279cf8ee25f8 100644 --- a/dom/indexedDB/Key.cpp +++ b/dom/indexedDB/Key.cpp @@ -170,12 +170,12 @@ Key::EncodeJSVal(JSContext* aCx, const jsval aVal, PRUint8 aTypeOffset) aTypeOffset < (eMaxType * MaxArrayCollapse), "Wrong typeoffset"); - jsuint length; + uint32_t length; if (!JS_GetArrayLength(aCx, obj, &length)) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } - for (jsuint index = 0; index < length; index++) { + for (uint32_t index = 0; index < length; index++) { jsval val; if (!JS_GetElement(aCx, obj, index, &val)) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; @@ -220,7 +220,7 @@ Key::DecodeJSVal(const unsigned char*& aPos, const unsigned char* aEnd, aTypeOffset = 0; } - jsuint index = 0; + uint32_t index = 0; while (aPos < aEnd && *aPos - aTypeOffset != eTerminator) { jsval val; nsresult rv = DecodeJSVal(aPos, aEnd, aCx, aTypeOffset, &val); diff --git a/dom/interfaces/apps/nsIDOMApplicationRegistry.idl b/dom/interfaces/apps/nsIDOMApplicationRegistry.idl index 1f69cc5e4509..75f52ddaf41b 100644 --- a/dom/interfaces/apps/nsIDOMApplicationRegistry.idl +++ b/dom/interfaces/apps/nsIDOMApplicationRegistry.idl @@ -36,104 +36,79 @@ * ***** END LICENSE BLOCK ***** */ #include "domstubs.idl" +#include "nsIArray.idl" +#include "nsIDOMEvent.idl" +#include "nsIDOMEventTarget.idl" -[scriptable, uuid(e0c271cb-266b-48c9-a7e4-96590b445c26)] -interface mozIDOMApplicationRegistryError : nsISupports -{ - const unsigned short DENIED = 1; - const unsigned short PERMISSION_DENIED = 2; - const unsigned short MANIFEST_URL_ERROR = 3; - const unsigned short NETWORK_ERROR = 4; - const unsigned short MANIFEST_PARSE_ERROR = 5; - const unsigned short INVALID_MANIFEST = 6; +interface nsIDOMDOMRequest; - readonly attribute short code; -}; - -[scriptable, uuid(a6856a3d-dece-43ce-89b9-72dba07f4246)] -interface mozIDOMApplication : nsISupports +[scriptable, uuid(b70b84f1-7ac9-4a92-bc32-8b6a7eb7879e)] +interface mozIDOMApplication : nsISupports { readonly attribute jsval manifest; - readonly attribute DOMString receipt; + readonly attribute DOMString manifestURL; + readonly attribute nsIArray receipts; /* an array of strings */ readonly attribute DOMString origin; readonly attribute DOMString installOrigin; readonly attribute unsigned long installTime; + + /* startPoint will be used when several launch_path exists for an app */ + nsIDOMDOMRequest launch([optional] in DOMString startPoint); + nsIDOMDOMRequest uninstall(); }; -[scriptable, function, uuid(be170df5-9154-463b-9197-10a6195eba52)] -interface mozIDOMApplicationRegistryEnumerateCallback : nsISupports +[scriptable, uuid(870bfbdc-3e13-4042-99dd-18e25720782d)] +interface mozIDOMApplicationEvent : nsIDOMEvent { - void handleEvent([array, size_is(count)] in mozIDOMApplication apps, - in unsigned long count); + readonly attribute mozIDOMApplication application; }; -[scriptable, function, uuid(ae0ed33d-35cf-443a-837b-a6cebf16bd49)] -interface mozIDOMApplicationRegistryErrorCallback : nsISupports +[scriptable, uuid(a82771f6-ba46-4073-9e6e-f1ad3f42b1f6)] +interface mozIDOMApplicationMgmt : nsIDOMEventTarget { - void handleEvent(in mozIDOMApplicationRegistryError error); + /** + * the request will return the all the applications installed. Only accessible + * to privileged callers. + */ + nsIDOMDOMRequest getAll(); + + /** + * event listener to get notified of application installs. Only settable by + * privileged callers. + * the event will be a mozIDOMApplicationEvent + */ + attribute nsIDOMEventListener oninstall; + + /** + * event listener to get notified of application uninstalls. Only settable by + * privileged callers. + * the event will be a mozIDOMApplicationEvent + */ + attribute nsIDOMEventListener onuninstall; }; -[scriptable, uuid(ac63c0ba-1f33-4e3e-b9aa-6a3243a9adba)] +[scriptable, uuid(f6929871-288b-4613-9a37-9a150760ac50)] interface mozIDOMApplicationRegistry : nsISupports { /** * Install a web app. onerror can be used to report errors, * and oninstall if the caller is privileged. * - * @param manifestUrl : the URL of the webapps manifest - * @param receipt : An opaque string used to track payment status + * @param manifestUrl : the URL of the webapps manifest. + * @param parameters : A structure with optional information. + * { receipts: ... } will be used to specify the payment receipts for this installation. */ - void install(in DOMString manifestUrl, - [optional] in DOMString receipt); + nsIDOMDOMRequest install(in DOMString manifestUrl, [optional] in jsval parameters); /** - * This call is only accessible to privileged callers. - * - * @param origin : the origin of the application to uninstall. + * the request will return the application currently installed, or null. */ - void uninstall(in DOMString origin); + nsIDOMDOMRequest getSelf(); /** - * Enumerate apps : either return itself if caller is an app, or - * apps installed from a store if caller is a store - * - * @param success: the callback that will get the array of applications - * @param error: optional error callback + * the request will return the applications installed from this origin, or null. */ - void enumerate(in mozIDOMApplicationRegistryEnumerateCallback success, - [optional] in mozIDOMApplicationRegistryErrorCallback error); + nsIDOMDOMRequest getInstalled(); - /** - * Enumerate all apps installed from all stores. - * Only usable by privileged callers. - * - * @param success: the callback that will get the array of applications - * @param error: optional error callback - */ - void enumerateAll(in mozIDOMApplicationRegistryEnumerateCallback success, - [optional] in mozIDOMApplicationRegistryErrorCallback error); - - /** - * launch a webapp. Behavior is application dependant. - * - * @param origin : the origin of the application to launch - */ - void launch(in DOMString origin); - - /** - * event listener to get notified of application installs. Only settable by - * privileged callers - */ - attribute nsIDOMEventListener oninstall; - - /** - * event listener to get notified of application uninstalls. Only settable by - * privileged callers - */ - attribute nsIDOMEventListener onuninstall; - - /** - * event listener to get notified of errors. - */ - attribute nsIDOMEventListener onerror; + readonly attribute mozIDOMApplicationMgmt mgmt; }; diff --git a/dom/interfaces/base/domstubs.idl b/dom/interfaces/base/domstubs.idl index 0505386a7151..260919744301 100644 --- a/dom/interfaces/base/domstubs.idl +++ b/dom/interfaces/base/domstubs.idl @@ -131,3 +131,4 @@ interface nsIDOMFontFaceList; // Power interface nsIDOMMozPowerManager; +interface nsIDOMMozWakeLock; diff --git a/dom/interfaces/base/nsIDOMNavigator.idl b/dom/interfaces/base/nsIDOMNavigator.idl index 84c10fd2a11a..90bddee82d9b 100644 --- a/dom/interfaces/base/nsIDOMNavigator.idl +++ b/dom/interfaces/base/nsIDOMNavigator.idl @@ -39,7 +39,7 @@ #include "domstubs.idl" -[scriptable, uuid(b1f4b1fa-49c2-4375-9ce8-bf97ecf6b428)] +[scriptable, uuid(e610c037-db58-4cd7-8ed3-0d7f1422b4d3)] interface nsIDOMNavigator : nsISupports { readonly attribute DOMString appCodeName; @@ -110,4 +110,34 @@ interface nsIDOMNavigator : nsISupports */ [implicit_jscontext] void mozVibrate(in jsval aPattern); + + /** + * Request a wake lock for a resource. + * + * A page holds a wake lock to request that a resource not be turned + * off (or otherwise made unavailable). + * + * The topic is the name of a resource that might be made unavailable for + * various reasons. For example, on a mobile device the power manager might + * decide to turn off the screen after a period of idle time to save power. + * + * The resource manager checks the lock state of a topic before turning off + * the associated resource. For example, a page could hold a lock on the + * "screen" topic to prevent the screensaver from appearing or the screen + * from turning off. + * + * The resource manager defines what each topic means and sets policy. For + * example, the resource manager might decide to ignore 'screen' wake locks + * held by pages which are not visible. + * + * One topic can be locked multiple times; it is considered released only when + * all locks on the topic have been released. + * + * The returned nsIDOMMozWakeLock object is a token of the lock. You can + * unlock the lock via the object's |unlock| method. The lock is released + * automatically when its associated window is unloaded. + * + * @param aTopic resource name + */ + nsIDOMMozWakeLock requestWakeLock(in DOMString aTopic); }; diff --git a/dom/power/Makefile.in b/dom/power/Makefile.in index 0cb7635960d1..3969391db2fe 100644 --- a/dom/power/Makefile.in +++ b/dom/power/Makefile.in @@ -52,15 +52,19 @@ EXPORTS_NAMESPACES = mozilla/dom/power EXPORTS_mozilla/dom/power = \ PowerManagerService.h \ + Types.h \ $(NULL) CPPSRCS = \ PowerManager.cpp \ PowerManagerService.cpp \ + WakeLock.cpp \ $(NULL) XPIDLSRCS = \ nsIDOMPowerManager.idl \ + nsIDOMWakeLock.idl \ + nsIDOMWakeLockListener.idl \ nsIPowerManagerService.idl \ $(NULL) diff --git a/dom/power/PowerManager.cpp b/dom/power/PowerManager.cpp index b0196f2070a1..8d6ec7780864 100644 --- a/dom/power/PowerManager.cpp +++ b/dom/power/PowerManager.cpp @@ -36,9 +36,13 @@ * ***** END LICENSE BLOCK ***** */ #include "PowerManager.h" +#include "WakeLock.h" #include "nsContentUtils.h" #include "nsDOMClassInfoID.h" +#include "nsIDOMWakeLockListener.h" #include "nsIPowerManagerService.h" +#include "nsIPrincipal.h" +#include "nsPIDOMWindow.h" #include "nsServiceManagerUtils.h" DOMCI_DATA(MozPowerManager, mozilla::dom::power::PowerManager) @@ -50,20 +54,66 @@ namespace power { NS_INTERFACE_MAP_BEGIN(PowerManager) NS_INTERFACE_MAP_ENTRY(nsIDOMMozPowerManager) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMMozPowerManager) + NS_INTERFACE_MAP_ENTRY(nsIDOMMozWakeLockListener) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozPowerManager) NS_INTERFACE_MAP_END NS_IMPL_ADDREF(PowerManager) NS_IMPL_RELEASE(PowerManager) -NS_IMETHODIMP -PowerManager::Reboot() +nsresult +PowerManager::Init(nsIDOMWindow *aWindow) { - NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR); + mWindow = do_GetWeakReference(aWindow); nsCOMPtr pmService = do_GetService(POWERMANAGERSERVICE_CONTRACTID); - NS_ENSURE_TRUE(pmService, NS_OK); + NS_ENSURE_STATE(pmService); + + // Add ourself to the global notification list. + pmService->AddWakeLockListener(this); + return NS_OK; +} + +nsresult +PowerManager::Shutdown() +{ + nsCOMPtr pmService = + do_GetService(POWERMANAGERSERVICE_CONTRACTID); + NS_ENSURE_STATE(pmService); + + // Remove ourself from the global notification list. + pmService->RemoveWakeLockListener(this); + return NS_OK; +} + +nsresult +PowerManager::CheckPermission() +{ + nsCOMPtr win = do_QueryReferent(mWindow); + NS_ENSURE_STATE(win); + nsCOMPtr doc = do_QueryInterface(win->GetExtantDocument()); + NS_ENSURE_STATE(doc); + + nsCOMPtr uri; + doc->NodePrincipal()->GetURI(getter_AddRefs(uri)); + + if (!nsContentUtils::URIIsChromeOrInPref(uri, "dom.power.whitelist")) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + return NS_OK; +} + +NS_IMETHODIMP +PowerManager::Reboot() +{ + nsresult rv = CheckPermission(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr pmService = + do_GetService(POWERMANAGERSERVICE_CONTRACTID); + NS_ENSURE_STATE(pmService); pmService->Reboot(); @@ -73,17 +123,74 @@ PowerManager::Reboot() NS_IMETHODIMP PowerManager::PowerOff() { - NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR); + nsresult rv = CheckPermission(); + NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr pmService = do_GetService(POWERMANAGERSERVICE_CONTRACTID); - NS_ENSURE_TRUE(pmService, NS_OK); + NS_ENSURE_STATE(pmService); pmService->PowerOff(); return NS_OK; } +NS_IMETHODIMP +PowerManager::AddWakeLockListener(nsIDOMMozWakeLockListener *aListener) +{ + nsresult rv = CheckPermission(); + NS_ENSURE_SUCCESS(rv, rv); + + // already added? bail out. + if (mListeners.Contains(aListener)) + return NS_OK; + + mListeners.AppendElement(aListener); + return NS_OK; +} + +NS_IMETHODIMP +PowerManager::RemoveWakeLockListener(nsIDOMMozWakeLockListener *aListener) +{ + nsresult rv = CheckPermission(); + NS_ENSURE_SUCCESS(rv, rv); + + mListeners.RemoveElement(aListener); + return NS_OK; +} + +NS_IMETHODIMP +PowerManager::GetWakeLockState(const nsAString &aTopic, nsAString &aState) +{ + nsresult rv = CheckPermission(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr pmService = + do_GetService(POWERMANAGERSERVICE_CONTRACTID); + NS_ENSURE_STATE(pmService); + + return pmService->GetWakeLockState(aTopic, aState); +} + +NS_IMETHODIMP +PowerManager::Callback(const nsAString &aTopic, const nsAString &aState) +{ + /** + * We maintain a local listener list instead of using the global + * list so that when the window is destroyed we don't have to + * cleanup the mess. + * Copy the listeners list before we walk through the callbacks + * because the callbacks may install new listeners. We expect no + * more than one listener per window, so it shouldn't be too long. + */ + nsAutoTArray, 2> listeners(mListeners); + for (PRUint32 i = 0; i < listeners.Length(); ++i) { + listeners[i]->Callback(aTopic, aState); + } + + return NS_OK; +} + } // power } // dom } // mozilla diff --git a/dom/power/PowerManager.h b/dom/power/PowerManager.h index 799ec0f926d0..728941418526 100644 --- a/dom/power/PowerManager.h +++ b/dom/power/PowerManager.h @@ -37,7 +37,12 @@ #ifndef mozilla_dom_power_PowerManager_h #define mozilla_dom_power_PowerManager_h +#include "nsCOMPtr.h" +#include "nsTArray.h" #include "nsIDOMPowerManager.h" +#include "nsIDOMWakeLockListener.h" +#include "nsIDOMWindow.h" +#include "nsWeakReference.h" namespace mozilla { namespace dom { @@ -45,13 +50,24 @@ namespace power { class PowerManager : public nsIDOMMozPowerManager + , public nsIDOMMozWakeLockListener { public: NS_DECL_ISUPPORTS NS_DECL_NSIDOMMOZPOWERMANAGER + NS_DECL_NSIDOMMOZWAKELOCKLISTENER PowerManager() {}; virtual ~PowerManager() {}; + + nsresult Init(nsIDOMWindow *aWindow); + nsresult Shutdown(); + +private: + nsresult CheckPermission(); + + nsWeakPtr mWindow; + nsTArray > mListeners; }; } // namespace power diff --git a/dom/power/PowerManagerService.cpp b/dom/power/PowerManagerService.cpp index 6cd0092eb7be..baf4923a714a 100644 --- a/dom/power/PowerManagerService.cpp +++ b/dom/power/PowerManagerService.cpp @@ -36,7 +36,12 @@ * ***** END LICENSE BLOCK ***** */ #include "mozilla/Hal.h" +#include "mozilla/HalWakeLock.h" +#include "mozilla/ClearOnShutdown.h" +#include "nsIDOMWakeLockListener.h" +#include "nsIDOMWindow.h" #include "PowerManagerService.h" +#include "WakeLock.h" namespace mozilla { namespace dom { @@ -44,14 +49,67 @@ namespace power { NS_IMPL_ISUPPORTS1(PowerManagerService, nsIPowerManagerService) +/* static */ nsRefPtr PowerManagerService::sSingleton; + /* static */ already_AddRefed PowerManagerService::GetInstance() { - nsCOMPtr pmService; + if (!sSingleton) { + sSingleton = new PowerManagerService(); + sSingleton->Init(); + ClearOnShutdown(&sSingleton); + } - pmService = new PowerManagerService(); + nsCOMPtr service(do_QueryInterface(sSingleton)); + return service.forget(); +} - return pmService.forget(); +void +PowerManagerService::Init() +{ + hal::RegisterWakeLockObserver(this); +} + +PowerManagerService::~PowerManagerService() +{ + hal::UnregisterWakeLockObserver(this); +} + +void +PowerManagerService::ComputeWakeLockState(const hal::WakeLockInformation& aWakeLockInfo, + nsAString &aState) +{ + hal::WakeLockState state = hal::ComputeWakeLockState(aWakeLockInfo.numLocks(), + aWakeLockInfo.numHidden()); + switch (state) { + case hal::WAKE_LOCK_STATE_UNLOCKED: + aState.AssignLiteral("unlocked"); + break; + case hal::WAKE_LOCK_STATE_HIDDEN: + aState.AssignLiteral("locked-background"); + break; + case hal::WAKE_LOCK_STATE_VISIBLE: + aState.AssignLiteral("locked-foreground"); + break; + } +} + +void +PowerManagerService::Notify(const hal::WakeLockInformation& aWakeLockInfo) +{ + nsAutoString state; + ComputeWakeLockState(aWakeLockInfo, state); + + /** + * Copy the listeners list before we walk through the callbacks + * because the callbacks may install new listeners. We expect no + * more than one listener per window, so it shouldn't be too long. + */ + nsAutoTArray, 2> listeners(mWakeLockListeners); + + for (PRUint32 i = 0; i < listeners.Length(); ++i) { + listeners[i]->Callback(aWakeLockInfo.topic(), state); + } } NS_IMETHODIMP @@ -68,6 +126,50 @@ PowerManagerService::PowerOff() return NS_OK; } +NS_IMETHODIMP +PowerManagerService::AddWakeLockListener(nsIDOMMozWakeLockListener *aListener) +{ + if (mWakeLockListeners.Contains(aListener)) + return NS_OK; + + mWakeLockListeners.AppendElement(aListener); + return NS_OK; +} + +NS_IMETHODIMP +PowerManagerService::RemoveWakeLockListener(nsIDOMMozWakeLockListener *aListener) +{ + mWakeLockListeners.RemoveElement(aListener); + return NS_OK; +} + +NS_IMETHODIMP +PowerManagerService::GetWakeLockState(const nsAString &aTopic, nsAString &aState) +{ + hal::WakeLockInformation info; + hal::GetWakeLockInfo(aTopic, &info); + + ComputeWakeLockState(info, aState); + + return NS_OK; +} + +NS_IMETHODIMP +PowerManagerService::NewWakeLock(const nsAString &aTopic, + nsIDOMWindow *aWindow, + nsIDOMMozWakeLock **aWakeLock) +{ + nsRefPtr wakelock = new WakeLock(); + nsresult rv = wakelock->Init(aTopic, aWindow); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr wl = + do_QueryInterface(NS_ISUPPORTS_CAST(nsIDOMMozWakeLock*, wakelock)); + wl.forget(aWakeLock); + + return NS_OK; +} + } // power } // dom } // mozilla diff --git a/dom/power/PowerManagerService.h b/dom/power/PowerManagerService.h index 0a1f1f213508..2c912b188114 100644 --- a/dom/power/PowerManagerService.h +++ b/dom/power/PowerManagerService.h @@ -37,8 +37,13 @@ #ifndef mozilla_dom_power_PowerManagerService_h #define mozilla_dom_power_PowerManagerService_h +#include "nsCOMPtr.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" #include "nsIPowerManagerService.h" -#include "nsCOMPtr.h" // for already_AddRefed +#include "mozilla/Observer.h" +#include "Types.h" namespace mozilla { namespace dom { @@ -46,12 +51,29 @@ namespace power { class PowerManagerService : public nsIPowerManagerService + , public WakeLockObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSIPOWERMANAGERSERVICE static already_AddRefed GetInstance(); + + void Init(); + + // Implement WakeLockObserver + void Notify(const hal::WakeLockInformation& aWakeLockInfo); + +private: + + ~PowerManagerService(); + + void ComputeWakeLockState(const hal::WakeLockInformation& aWakeLockInfo, + nsAString &aState); + + static nsRefPtr sSingleton; + + nsTArray > mWakeLockListeners; }; } // namespace power diff --git a/dom/power/Types.h b/dom/power/Types.h new file mode 100644 index 000000000000..74a421772c14 --- /dev/null +++ b/dom/power/Types.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef mozilla_dom_power_Types_h +#define mozilla_dom_power_Types_h + +namespace mozilla { +namespace hal { +class WakeLockInformation; +} // namespace hal + +template +class Observer; + +typedef Observer WakeLockObserver; + +} // namespace mozilla + +#endif // mozilla_dom_power_Types_h + diff --git a/dom/power/WakeLock.cpp b/dom/power/WakeLock.cpp new file mode 100644 index 000000000000..e6f7cb09bbd0 --- /dev/null +++ b/dom/power/WakeLock.cpp @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Hal.h" +#include "mozilla/HalWakeLock.h" +#include "nsDOMClassInfoID.h" +#include "nsDOMError.h" +#include "nsIDOMWindow.h" +#include "nsIDOMEvent.h" +#include "nsIDOMDocument.h" +#include "nsIDOMEventTarget.h" +#include "nsPIDOMWindow.h" +#include "PowerManager.h" +#include "WakeLock.h" + +DOMCI_DATA(MozWakeLock, mozilla::dom::power::WakeLock) + +namespace mozilla { +namespace dom { +namespace power { + +NS_INTERFACE_MAP_BEGIN(WakeLock) + NS_INTERFACE_MAP_ENTRY(nsIDOMMozWakeLock) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMMozWakeLock) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozWakeLock) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(WakeLock) +NS_IMPL_RELEASE(WakeLock) + +WakeLock::WakeLock() + : mLocked(false) + , mHidden(true) +{ +} + +WakeLock::~WakeLock() +{ + DoUnlock(); + DetachEventListener(); +} + +nsresult +WakeLock::Init(const nsAString &aTopic, nsIDOMWindow *aWindow) +{ + mTopic.Assign(aTopic); + + mWindow = do_GetWeakReference(aWindow); + nsCOMPtr window = do_QueryInterface(aWindow); + + /** + * Null windows are allowed. A wake lock without associated window + * is always considered invisible. + */ + if (window) { + nsCOMPtr domDoc = window->GetExtantDocument(); + NS_ENSURE_STATE(domDoc); + domDoc->GetMozHidden(&mHidden); + } + + AttachEventListener(); + DoLock(); + + return NS_OK; +} + +void +WakeLock::DoLock() +{ + if (!mLocked) { + // Change the flag immediately to prevent recursive reentering + mLocked = true; + hal::ModifyWakeLock(mTopic, + hal::WAKE_LOCK_ADD_ONE, + mHidden ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_NO_CHANGE); + } +} + +void +WakeLock::DoUnlock() +{ + if (mLocked) { + // Change the flag immediately to prevent recursive reentering + mLocked = false; + hal::ModifyWakeLock(mTopic, + hal::WAKE_LOCK_REMOVE_ONE, + mHidden ? hal::WAKE_LOCK_REMOVE_ONE : hal::WAKE_LOCK_NO_CHANGE); + } +} + +void +WakeLock::AttachEventListener() +{ + nsCOMPtr window = do_QueryReferent(mWindow); + + if (window) { + nsCOMPtr domDoc = window->GetExtantDocument(); + if (domDoc) { + nsCOMPtr target = do_QueryInterface(domDoc); + target->AddSystemEventListener(NS_LITERAL_STRING("mozvisibilitychange"), + this, + /* useCapture = */ true, + /* wantsUntrusted = */ false); + + target = do_QueryInterface(window); + target->AddSystemEventListener(NS_LITERAL_STRING("pagehide"), + this, + /* useCapture = */ true, + /* wantsUntrusted = */ false); + target->AddSystemEventListener(NS_LITERAL_STRING("pageshow"), + this, + /* useCapture = */ true, + /* wantsUntrusted = */ false); + } + } +} + +void +WakeLock::DetachEventListener() +{ + nsCOMPtr window = do_QueryReferent(mWindow); + + if (window) { + nsCOMPtr domDoc = window->GetExtantDocument(); + if (domDoc) { + nsCOMPtr target = do_QueryInterface(domDoc); + target->RemoveSystemEventListener(NS_LITERAL_STRING("mozvisibilitychange"), + this, + /* useCapture = */ true); + target = do_QueryInterface(window); + target->RemoveSystemEventListener(NS_LITERAL_STRING("pagehide"), + this, + /* useCapture = */ true); + target->RemoveSystemEventListener(NS_LITERAL_STRING("pageshow"), + this, + /* useCapture = */ true); + } + } +} + +NS_IMETHODIMP +WakeLock::Unlock() +{ + /* + * We throw NS_ERROR_DOM_INVALID_STATE_ERR on double unlock. + */ + if (!mLocked) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + DoUnlock(); + DetachEventListener(); + + return NS_OK; +} + +NS_IMETHODIMP +WakeLock::GetTopic(nsAString &aTopic) +{ + aTopic.Assign(mTopic); + return NS_OK; +} + +NS_IMETHODIMP +WakeLock::HandleEvent(nsIDOMEvent *aEvent) +{ + nsAutoString type; + aEvent->GetType(type); + + if (type.EqualsLiteral("mozvisibilitychange")) { + nsCOMPtr target; + aEvent->GetTarget(getter_AddRefs(target)); + nsCOMPtr domDoc = do_QueryInterface(target); + NS_ENSURE_STATE(domDoc); + domDoc->GetMozHidden(&mHidden); + + if (mLocked) { + hal::ModifyWakeLock(mTopic, + hal::WAKE_LOCK_NO_CHANGE, + mHidden ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_REMOVE_ONE); + } + + return NS_OK; + } + + if (type.EqualsLiteral("pagehide")) { + DoUnlock(); + return NS_OK; + } + + if (type.EqualsLiteral("pageshow")) { + DoLock(); + return NS_OK; + } + + return NS_OK; +} + +} // power +} // dom +} // mozilla diff --git a/dom/power/WakeLock.h b/dom/power/WakeLock.h new file mode 100644 index 000000000000..e4e82d377a3b --- /dev/null +++ b/dom/power/WakeLock.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_power_WakeLock_h +#define mozilla_dom_power_WakeLock_h + +#include "nsCOMPtr.h" +#include "nsIDOMWakeLock.h" +#include "nsIDOMEventListener.h" +#include "nsString.h" +#include "nsWeakReference.h" + +class nsIDOMWindow; + +namespace mozilla { +namespace dom { +namespace power { + +class WakeLock + : public nsIDOMMozWakeLock + , public nsIDOMEventListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMMOZWAKELOCK + NS_DECL_NSIDOMEVENTLISTENER + + WakeLock(); + virtual ~WakeLock(); + + nsresult Init(const nsAString &aTopic, nsIDOMWindow *aWindow); + +private: + void DoUnlock(); + void DoLock(); + void AttachEventListener(); + void DetachEventListener(); + + bool mLocked; + bool mHidden; + nsString mTopic; + + // window that this was created for. Weak reference. + nsWeakPtr mWindow; +}; + +} // namespace power +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_power_WakeLock_h diff --git a/dom/power/nsIDOMPowerManager.idl b/dom/power/nsIDOMPowerManager.idl index 7ff1f7c6df51..b8247f128033 100644 --- a/dom/power/nsIDOMPowerManager.idl +++ b/dom/power/nsIDOMPowerManager.idl @@ -37,9 +37,40 @@ #include "nsISupports.idl" -[scriptable, uuid(6ec16abc-2fe8-4ab3-99b0-0f08405be81b)] +interface nsIDOMMozWakeLockListener; + +/** + * This interface implements navigator.mozPower + */ +[scriptable, uuid(abf4b2b1-139d-4eff-998d-8f24616910ae)] interface nsIDOMMozPowerManager : nsISupports { - void powerOff(); - void reboot(); + void powerOff(); + void reboot(); + + /** + * The listeners are notified when a resource changes its lock state to: + * - unlocked + * - locked but not visible + * - locked and visible + */ + void addWakeLockListener(in nsIDOMMozWakeLockListener aListener); + void removeWakeLockListener(in nsIDOMMozWakeLockListener aListener); + + /** + * Query the wake lock state of the topic. + * + * Possible states are: + * + * - "unlocked" - nobody holds the wake lock. + * + * - "locked-foreground" - at least one window holds the wake lock, + * and it is visible. + * + * - "locked-background" - at least one window holds the wake lock, + * but all of them are hidden. + * + * @param aTopic The resource name related to the wake lock. + */ + DOMString getWakeLockState(in DOMString aTopic); }; diff --git a/dom/power/nsIDOMWakeLock.idl b/dom/power/nsIDOMWakeLock.idl new file mode 100644 index 000000000000..c83ebd61383c --- /dev/null +++ b/dom/power/nsIDOMWakeLock.idl @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(2e61eed1-5983-4562-8f26-fd361ab4a00d)] +interface nsIDOMMozWakeLock : nsISupports +{ + readonly attribute DOMString topic; + + /** + * Release the wake lock. + * + * @throw NS_ERROR_DOM_INVALID_STATE_ERR if already unlocked. + */ + void unlock(); +}; diff --git a/dom/power/nsIDOMWakeLockListener.idl b/dom/power/nsIDOMWakeLockListener.idl new file mode 100644 index 000000000000..b837c3b0577d --- /dev/null +++ b/dom/power/nsIDOMWakeLockListener.idl @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, function, uuid(4e258af8-cffb-47bc-b16d-e8241243426e)] +interface nsIDOMMozWakeLockListener : nsISupports +{ + /** + * The callback will be called when a lock topic changes its lock + * state. + * + * Possible states are: + * + * - "unlocked" - nobody holds the wake lock. + * + * - "locked-foreground" - at least one window holds the wake lock, + * and it is visible. + * + * - "locked-background" - at least one window holds the wake lock, + * but all of them are hidden. + * + * @param aTopic The resource name related to the wake lock. + * @param aState The wake lock state + */ + void callback(in DOMString aTopic, in DOMString aState); +}; diff --git a/dom/power/nsIPowerManagerService.idl b/dom/power/nsIPowerManagerService.idl index 808a6cf7506f..afd28d570dc4 100644 --- a/dom/power/nsIPowerManagerService.idl +++ b/dom/power/nsIPowerManagerService.idl @@ -42,9 +42,26 @@ #define POWERMANAGERSERVICE_CONTRACTID "@mozilla.org/power/powermanagerservice;1" %} -[scriptable, builtinclass, uuid(38919539-4641-4f0b-9f11-6b6294a9386f)] +interface nsIDOMMozWakeLock; +interface nsIDOMMozWakeLockListener; +interface nsIDOMWindow; + +/** + * For use with non-content code. + */ +[scriptable, builtinclass, uuid(235ca1a1-d0c8-41f3-9b4a-dbaa4437d69c)] interface nsIPowerManagerService : nsISupports { - void powerOff(); - void reboot(); + void powerOff(); + void reboot(); + void addWakeLockListener(in nsIDOMMozWakeLockListener aListener); + void removeWakeLockListener(in nsIDOMMozWakeLockListener aListener); + DOMString getWakeLockState(in DOMString aTopic); + + /** + * Return a wake lock object of aTopic associated with aWindow. + * A wake lock without associated window, e.g. used in chrome, is + * always considered invisible. + */ + nsIDOMMozWakeLock newWakeLock(in DOMString aTopic, [optional] in nsIDOMWindow aWindow); }; diff --git a/dom/power/test/Makefile.in b/dom/power/test/Makefile.in index 9f9cdfe23380..14e27eb9ce44 100644 --- a/dom/power/test/Makefile.in +++ b/dom/power/test/Makefile.in @@ -52,8 +52,15 @@ _TEST_FILES = \ test_power_basics.html \ $(NULL) +_BROWSER_TEST_FILES = \ + browser_bug697132.js \ + $(NULL) + libs:: $(_TEST_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir) +libs:: $(_BROWSER_TEST_FILES) + $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir) + #libs:: $(_CHROME_TEST_FILES) # $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir) diff --git a/dom/power/test/browser_bug697132.js b/dom/power/test/browser_bug697132.js new file mode 100644 index 000000000000..da479d72b193 --- /dev/null +++ b/dom/power/test/browser_bug697132.js @@ -0,0 +1,235 @@ +"use strict"; + +waitForExplicitFinish(); + +let kPrefNode = "dom.power.whitelist"; +let kPageSource1 = "data:text/html,1"; +let kPageSource2 = "data:text/html,2"; + +let gOldPref; +let gWin, gWin1, gWin2; +let gTab, gTab1, gTab2; +let gLock, gLock1, gLock2; +let gCurStepIndex = -1; +let gSteps = [ + function basicWakeLock() { + gTab = gBrowser.addTab(kPageSource1); + gWin = gBrowser.getBrowserForTab(gTab).contentWindow; + let browser = gBrowser.getBrowserForTab(gTab); + + browser.addEventListener("load", function onLoad(e) { + browser.removeEventListener("load", onLoad, true); + let nav = gWin.navigator; + let power = nav.mozPower; + gLock = nav.requestWakeLock("test"); + + ok(gLock != null, + "navigator.requestWakeLock should return a wake lock"); + is(gLock.topic, "test", + "wake lock should remember the locked topic"); + isnot(power.getWakeLockState("test"), "unlocked", + "topic is locked"); + + gLock.unlock(); + + is(gLock.topic, "test", + "wake lock should remember the locked topic even after unlock"); + is(power.getWakeLockState("test"), "unlocked", + "topic is unlocked"); + + try { + gLock.unlock(); + ok(false, "Should have thrown an error."); + } catch (e) { + is(e.code, DOMException.INVALID_STATE_ERR, "double unlock should throw InvalidStateError"); + } + + gBrowser.removeTab(gTab); + + executeSoon(runNextStep); + }, true); + }, + function multiWakeLock() { + gTab = gBrowser.addTab(kPageSource1); + gWin = gBrowser.getBrowserForTab(gTab).contentWindow; + let browser = gBrowser.getBrowserForTab(gTab); + + browser.addEventListener("load", function onLoad(e) { + browser.removeEventListener("load", onLoad, true); + let nav = gWin.navigator; + let power = nav.mozPower; + let count = 0; + power.addWakeLockListener(function onWakeLockEvent(topic, state) { + is(topic, "test", "gLock topic is test"); + ok(state == "unlocked" || + state == "locked-foreground" || + state == "locked-background", + "wake lock should be either locked or unlocked"); + count++; + if (state == "locked-foreground" || + state == "locked-background") { + is(count, 1, + "wake lock should be locked and the listener should only fire once"); + } + if (state == "unlocked") { + is(count, 2, + "wake lock should be unlocked and the listener should only fire once"); + + ok(power.getWakeLockState("test") == "unlocked", + "topic is unlocked"); + power.removeWakeLockListener(onWakeLockEvent); + gBrowser.removeTab(gTab); + executeSoon(runNextStep); + } + }); + + gLock1 = nav.requestWakeLock("test"); + isnot(power.getWakeLockState("test"), "unlocked", + "topic is locked"); + + gLock2 = nav.requestWakeLock("test"); + isnot(power.getWakeLockState("test"), "unlocked", + "topic is locked"); + + gLock1.unlock(); + isnot(power.getWakeLockState("test"), "unlocked", + "topic is locked"); + + gLock2.unlock(); + }, true); + }, + function crossTabWakeLock1() { + gTab1 = gBrowser.addTab(kPageSource1); + gWin1 = gBrowser.getBrowserForTab(gTab1).contentWindow; + gTab2 = gBrowser.addTab(kPageSource1); + gWin2 = gBrowser.getBrowserForTab(gTab2).contentWindow; + + gBrowser.selectedTab = gTab1; + let browser = gBrowser.getBrowserForTab(gTab2); + + browser.addEventListener("load", function onLoad(e) { + browser.removeEventListener("load", onLoad, true); + gLock2 = gWin2.navigator.requestWakeLock("test"); + is(gWin2.document.mozHidden, true, + "window is background") + is(gWin2.navigator.mozPower.getWakeLockState("test"), "locked-background", + "wake lock is background"); + let doc2 = gWin2.document; + doc2.addEventListener("mozvisibilitychange", function onVisibilityChange(e) { + if (!doc2.mozHidden) { + doc2.removeEventListener("mozvisibilitychange", onVisibilityChange); + executeSoon(runNextStep); + } + }); + gBrowser.selectedTab = gTab2; + }, true); + }, + function crossTabWakeLock2() { + is(gWin2.document.mozHidden, false, + "window is foreground") + is(gWin2.navigator.mozPower.getWakeLockState("test"), "locked-foreground", + "wake lock is foreground"); + gWin2.addEventListener("pagehide", function onPageHide(e) { + gWin2.removeEventListener("pagehide", onPageHide, true); + executeSoon(runNextStep); + }, true); + gWin2.addEventListener("pageshow", function onPageShow(e) { + gWin2.removeEventListener("pageshow", onPageShow, true); + executeSoon(runNextStep); + }, true); + gWin2.location = kPageSource2; + }, + function crossTabWakeLock3() { + is(gWin1.navigator.mozPower.getWakeLockState("test"), "unlocked", + "wake lock should auto-unlock when page is unloaded"); + gWin2.back(); + // runNextStep called in onPageShow + }, + function crossTabWakeLock4() { + is(gWin1.navigator.mozPower.getWakeLockState("test"), "locked-foreground", + "wake lock should auto-reacquire when page is available again"); + gBrowser.selectedTab = gTab1; + executeSoon(runNextStep); + }, + function crossTabWakeLock5() { + // Test again in background tab + is(gWin2.document.mozHidden, true, + "window is background") + is(gWin2.navigator.mozPower.getWakeLockState("test"), "locked-background", + "wake lock is background"); + gWin2.addEventListener("pagehide", function onPageHide(e) { + gWin2.removeEventListener("pagehide", onPageHide, true); + executeSoon(runNextStep); + }, true); + gWin2.addEventListener("pageshow", function onPageShow(e) { + gWin2.removeEventListener("pageshow", onPageShow, true); + executeSoon(runNextStep); + }, true); + gWin2.location = kPageSource2; + }, + function crossTabWakeLock6() { + is(gWin1.navigator.mozPower.getWakeLockState("test"), "unlocked", + "wake lock should auto-unlock when page is unloaded"); + gWin2.back(); + // runNextStep called in onPageShow + }, + function crossTabWakeLock7() { + is(gWin1.navigator.mozPower.getWakeLockState("test"), "locked-background", + "wake lock should auto-reacquire when page is available again"); + gLock2.unlock(); + gBrowser.selectedTab = gTab2; + executeSoon(runNextStep); + }, + function crossTabWakeLock8() { + is(gWin1.document.mozHidden, true, + "gWin1 is background"); + is(gWin2.document.mozHidden, false, + "gWin2 is foreground"); + + gLock1 = gWin1.navigator.requestWakeLock("test"); + gLock2 = gWin2.navigator.requestWakeLock("test"); + + is(gWin1.navigator.mozPower.getWakeLockState("test"), "locked-foreground", + "topic is locked-foreground when one page is foreground and one is background"); + + gLock2.unlock(); + + is(gWin1.navigator.mozPower.getWakeLockState("test"), "locked-background", + "topic is locked-background when all locks are background"); + + gLock2 = gWin2.navigator.requestWakeLock("test"); + + is(gWin1.navigator.mozPower.getWakeLockState("test"), "locked-foreground", + "topic is locked-foreground when one page is foreground and one is background"); + + gLock1.unlock(); + + is(gWin1.navigator.mozPower.getWakeLockState("test"), "locked-foreground", + "topic is locked-foreground"); + + gBrowser.removeTab(gTab1); + gBrowser.removeTab(gTab2); + executeSoon(runNextStep); + }, +]; + +function runNextStep() { + gCurStepIndex++; + if (gCurStepIndex < gSteps.length) { + gSteps[gCurStepIndex](); + } else { + Services.prefs.setCharPref(kPrefNode, gOldPref); + finish(); + } +} + +function test() { + try { + gOldPref = Services.prefs.getCharPref(kPrefNode); + } catch (e) { + gOldPref = ""; + } + // data url inherits its parent's principal, which is |about:| here. + Services.prefs.setCharPref(kPrefNode, "about:"); + runNextStep(); +} diff --git a/dom/sms/src/SmsFilter.cpp b/dom/sms/src/SmsFilter.cpp index ae2b046f41c4..f0fe9ee9841f 100644 --- a/dom/sms/src/SmsFilter.cpp +++ b/dom/sms/src/SmsFilter.cpp @@ -190,12 +190,12 @@ SmsFilter::SetNumbers(JSContext* aCx, const jsval& aNumbers) return NS_ERROR_INVALID_ARG; } - jsuint size; + uint32_t size; JS_ALWAYS_TRUE(JS_GetArrayLength(aCx, &obj, &size)); nsTArray numbers; - for (jsuint i=0; i 1) { + if (DEBUG) debug("Got too many results for id " + messageId); + gSmsRequestManager.notifyGetSmsFailed( + requestId, Ci.nsISmsRequestManager.UNKNOWN_ERROR); + return; + } + let data = request.result[0]; + if (!data) { + if (DEBUG) debug("Message ID " + messageId + " not found"); + gSmsRequestManager.notifyGetSmsFailed( + requestId, Ci.nsISmsRequestManager.NOT_FOUND_ERROR); + return; + } + if (data.id != messageId) { + if (DEBUG) { + debug("Requested message ID (" + messageId + ") is " + + "different from the one we got"); + } + gSmsRequestManager.notifyGetSmsFailed( + requestId, Ci.nsISmsRequestManager.UNKNOWN_ERROR); + return; + } + let message = gSmsService.createSmsMessage(data.id, + data.delivery, + data.sender, + data.receiver, + data.body, + data.timestamp); + gSmsRequestManager.notifyGotSms(requestId, message); + }; + + txn.onerror = function onerror(event) { + if (DEBUG) debug("Caught error on transaction", event.target.errorCode); + //TODO look at event.target.errorCode, pick appropriate error constant + gSmsRequestManager.notifyGetSmsFailed( + requestId, Ci.nsISmsRequestManager.INTERNAL_ERROR); + }; + }); }, deleteMessage: function deleteMessage(messageId, requestId) { + let self = this; + this.newTxn(Ci.nsIIDBTransaction.READ_WRITE, function (error, txn, store) { + if (error) { + gSmsRequestManager.notifySmsDeleteFailed( + requestId, Ci.nsISmsRequestManager.INTERNAL_ERROR); + return; + } + let request = store.delete(messageId); + + request.onerror = function onerror(event) { + if (DEBUG) debug("Caught error on request ", event.target.errorCode); + //TODO look at event.target.errorCode + gSmsRequestManager.notifySmsDeleteFailed( + requestId, Ci.nsISmsRequestManager.INTERNAL_ERROR); + }; + + txn.oncomplete = function oncomplete(event) { + if (DEBUG) debug("Transaction " + txn + " completed."); + // Once we transaction is done, we need to check if we actually deleted + // the message. As IndexedDB does not provide the affected records info, + // we need to try to get the message from the database again to check + // that it is actually gone. + self.newTxn(Ci.nsIIDBTransaction.READ_ONLY, function (error, txn, store) { + let request = store.getAll(messageId); + request.onsuccess = function onsuccess(event) { + let deleted = (event.target.result.length == 0); + gSmsRequestManager.notifySmsDeleted(requestId, deleted); + }; + request.onerror = function onerror(event) { + if (DEBUG) { + debug("Error checking the message deletion " + + event.target.errorCode); + } + //TODO should we notify here as an internal error? The failed check + // does not mean that the deletion has failed, so maybe we + // should notify successfully. + gSmsRequestManager.notifySmsDeleteFailed( + requestId, Ci.nsISmsRequestManager.INTERNAL_ERROR); + }; + }); + }; + + txn.onerror = function onerror(event) { + if (DEBUG) debug("Caught error on transaction", event.target.errorCode); + //TODO look at event.target.errorCode, pick appropriate error constant + gSmsRequestManager.notifySmsDeleteFailed( + requestId, Ci.nsISmsRequestManager.INTERNAL_ERROR); + }; + }); }, createMessageList: function createMessageList(filter, reverse, requestId) { + if (DEBUG) { + debug("Creating a message list. Filters:" + + " startDate: " + filter.startDate + + " endDate: " + filter.endDate + + " delivery: " + filter.delivery + + " numbers: " + filter.numbers + + " reverse: " + reverse); + } + // This object keeps the lists of keys retrieved by the search specific to + // each nsIMozSmsFilter. Once all the keys have been retrieved from the + // store, the final intersection of this arrays will contain all the + // keys for the message list that we are creating. + let filteredKeys = {}; + filteredKeys[FILTER_TIMESTAMP] = []; + filteredKeys[FILTER_NUMBERS] = []; + filteredKeys[FILTER_DELIVERY] = []; + + // Callback function to iterate through request results via IDBCursor. + let successCb = function onsuccess(result, filter) { + // Once the cursor has retrieved all keys that matches its key range, + // the filter search is done. + if (!result) { + if (DEBUG) { + debug("These messages match the " + filter + " filter: " + + filteredKeys[filter]); + } + return; + } + // The cursor primaryKey is stored in its corresponding partial array + // according to the filter parameter. + let primaryKey = result.primaryKey; + filteredKeys[filter].push(primaryKey); + result.continue(); + }; + + let errorCb = function onerror(event) { + //TODO look at event.target.errorCode, pick appropriate error constant. + if (DEBUG) debug("IDBRequest error " + event.target.errorCode); + gSmsRequestManager.notifyReadMessageListFailed( + requestId, Ci.nsISmsRequestManager.INTERNAL_ERROR); + return; + }; + + let self = this; + this.newTxn(Ci.nsIIDBTransaction.READ_ONLY, function (error, txn, store) { + if (error) { + errorCb(error); + return; + } + + // In first place, we retrieve the keys that match the filter.startDate + // and filter.endDate search criteria. + let timeKeyRange = null; + if (!filter.startDate != null && filter.endDate != null) { + timeKeyRange = IDBKeyRange.bound(filter.startDate.getTime(), + filter.endDate.getTime()); + } else if (filter.startDate != null) { + timeKeyRange = IDBKeyRange.lowerBound(filter.startDate.getTime()); + } else if (filter.endDate != null) { + timeKeyRange = IDBKeyRange.upperBound(filter.endDate.getTime()); + } + let direction = reverse ? Ci.nsIIDBCursor.PREV : Ci.nsIIDBCursor.NEXT; + let timeRequest = store.index("timestamp").openKeyCursor(timeKeyRange, + direction); + + timeRequest.onsuccess = function onsuccess(event) { + successCb(event.target.result, FILTER_TIMESTAMP); + }; + timeRequest.onerror = errorCb; + + // Retrieve the keys from the 'delivery' index that matches the + // value of filter.delivery. + if (filter.delivery) { + let deliveryKeyRange = IDBKeyRange.only(filter.delivery); + let deliveryRequest = store.index("delivery") + .openKeyCursor(deliveryKeyRange); + deliveryRequest.onsuccess = function onsuccess(event) { + successCb(event.target.result, FILTER_DELIVERY); + }; + deliveryRequest.onerror = errorCb; + } + + // Retrieve the keys from the 'sender' and 'receiver' indexes that + // match the values of filter.numbers + if (filter.numbers) { + for (let i = 0; i < filter.numbers.length; i++) { + let numberKeyRange = IDBKeyRange.only(filter.numbers[i]); + let senderRequest = store.index("sender") + .openKeyCursor(numberKeyRange); + let receiverRequest = store.index("receiver") + .openKeyCursor(numberKeyRange); + senderRequest.onsuccess = receiverRequest.onsuccess = + function onsuccess(event){ + successCb(event.target.result, FILTER_NUMBERS); + }; + senderRequest.onerror = receiverRequest.onerror = errorCb; + } + } + + txn.oncomplete = function oncomplete(event) { + if (DEBUG) debug("Transaction " + txn + " completed."); + // We need to get the intersection of all the partial searches to + // get the final result array. + let result = self.keyIntersection(filteredKeys, filter); + if (!result.length) { + if (DEBUG) debug("No messages matching the filter criteria"); + gSmsRequestManager.notifyNoMessageInList(requestId); + return; + } + + // At this point, filteredKeys should have all the keys that matches + // all the search filters. So we take the first key and retrieve the + // corresponding message. The rest of the keys are added to the + // messageLists object as a new list. + self.onMessageListCreated(result, requestId); + }; + + txn.onerror = function onerror(event) { + errorCb(event); + }; + }); }, getNextMessageInList: function getNextMessageInList(listId, requestId) { + if (DEBUG) debug("Getting next message in list " + listId); + let messageId; + let list = this.messageLists[listId]; + if (!list) { + if (DEBUG) debug("Wrong list id"); + gSmsRequestManager.notifyReadMessageListFailed( + requestId, Ci.nsISmsRequestManager.NOT_FOUND_ERROR); + return; + } + messageId = list.shift(); + if (messageId == null) { + if (DEBUG) debug("Reached the end of the list!"); + gSmsRequestManager.notifyNoMessageInList(requestId); + return; + } + this.newTxn(Ci.nsIIDBTransaction.READ_ONLY, function (error, txn, store) { + if (DEBUG) debug("Fetching message " + messageId); + let request = store.get(messageId); + let message; + request.onsuccess = function onsuccess(event) { + message = request.result; + }; + + txn.oncomplete = function oncomplete(event) { + if (DEBUG) debug("Transaction " + txn + " completed."); + if (!message) { + if (DEBUG) debug("Could not get message id " + messageId); + gSmsRequestManager.notifyReadMessageListFailed( + requestId, Ci.nsISmsRequestManager.NOT_FOUND_ERROR); + } + let sms = gSmsService.createSmsMessage(message.id, + message.delivery, + message.sender, + message.receiver, + message.body, + message.timestamp); + gSmsRequestManager.notifyGotNextMessage(requestId, sms); + }; + + txn.onerror = function onerror(event) { + //TODO check event.target.errorCode + if (DEBUG) { + debug("Error retrieving message id: " + messageId + + ". Error code: " + event.target.errorCode); + } + gSmsRequestManager.notifyReadMessageListFailed( + requestId, Ci.nsISmsRequestManager.INTERNAL_ERROR); + }; + }); }, clearMessageList: function clearMessageList(listId) { + if (DEBUG) debug("Clearing message list: " + listId); + delete this.messageLists[listId]; } }; const NSGetFactory = XPCOMUtils.generateNSGetFactory([SmsDatabaseService]); + +function debug() { + dump("SmsDatabaseService: " + Array.slice(arguments).join(" ") + "\n"); +} diff --git a/dom/system/b2g/RadioInterfaceLayer.js b/dom/system/b2g/RadioInterfaceLayer.js index 67ce8d3d03ae..0d693062ddf4 100644 --- a/dom/system/b2g/RadioInterfaceLayer.js +++ b/dom/system/b2g/RadioInterfaceLayer.js @@ -69,6 +69,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "gSmsRequestManager", "@mozilla.org/sms/smsrequestmanager;1", "nsISmsRequestManager"); +XPCOMUtils.defineLazyServiceGetter(this, "gSmsDatabaseService", + "@mozilla.org/sms/rilsmsdatabaseservice;1", + "nsISmsDatabaseService"); + function convertRILCallState(state) { switch (state) { case RIL.CALL_STATE_ACTIVE: @@ -308,8 +312,11 @@ RadioInterfaceLayer.prototype = { }, handleSmsReceived: function handleSmsReceived(message) { - //TODO: put the sms into a database, assign it a proper id, yada yada - let sms = gSmsService.createSmsMessage(-1, + debug("handleSmsReceived: " + JSON.stringify(message)); + let id = gSmsDatabaseService.saveReceivedMessage(message.sender || null, + message.body || null, + message.timestamp); + let sms = gSmsService.createSmsMessage(id, DOM_SMS_DELIVERY_RECEIVED, message.sender || null, message.receiver || null, @@ -319,13 +326,15 @@ RadioInterfaceLayer.prototype = { }, handleSmsSent: function handleSmsSent(message) { - let sms = gSmsService.createSmsMessage(-1, + debug("handleSmsSent: " + JSON.stringify(message)); + let timestamp = Date.now(); + let id = gSmsDatabaseService.saveSentMessage(message.number, message.body, timestamp); + let sms = gSmsService.createSmsMessage(id, DOM_SMS_DELIVERY_SENT, null, message.number, message.body, - Date.now()); - //TODO At this point we should save the sms into the DB (bug 712809) + timestamp); //TODO handle errors (bug 727319) gSmsRequestManager.notifySmsSent(message.requestId, sms); }, diff --git a/dom/system/b2g/ril_consts.js b/dom/system/b2g/ril_consts.js index b03e294a1425..d75d27a890b2 100644 --- a/dom/system/b2g/ril_consts.js +++ b/dom/system/b2g/ril_consts.js @@ -178,6 +178,23 @@ const UNSOLICITED_OEM_HOOK_RAW = 1028; const UNSOLICITED_RINGBACK_TONE = 1029; const UNSOLICITED_RESEND_INCALL_MUTE = 1030; +const ERROR_SUCCESS = 0; +const ERROR_RADIO_NOT_AVAILABLE = 1; +const ERROR_GENERIC_FAILURE = 2; +const ERROR_PASSWORD_INCORRECT = 3; +const ERROR_SIM_PIN2 = 4; +const ERROR_SIM_PUK2 = 5; +const ERROR_REQUEST_NOT_SUPPORTED = 6; +const ERROR_CANCELLED = 7; +const ERROR_OP_NOT_ALLOWED_DURING_VOICE_CALL = 8; +const ERROR_OP_NOT_ALLOWED_BEFORE_REG_TO_NW = 9; +const ERROR_SMS_SEND_FAIL_RETRY = 10; +const ERROR_SIM_ABSENT = 11; +const ERROR_SUBSCRIPTION_NOT_AVAILABLE = 12; +const ERROR_MODE_NOT_SUPPORTED = 13; +const ERROR_FDN_CHECK_FAILURE = 14; +const ERROR_ILLEGAL_SIM_OR_ME = 15; + const RADIO_STATE_OFF = 0; const RADIO_STATE_UNAVAILABLE = 1; const RADIO_STATE_SIM_NOT_READY = 2; @@ -243,6 +260,80 @@ const CALL_PRESENTATION_PAYPHONE = 3; const SMS_HANDLED = 0; +// ICC commands, see TS 27.007 +CRSM commands +const ICC_COMMAND_READ_BINARY = 0xb0; +const ICC_COMMAND_UPDATE_BINARY = 0xd6; +const ICC_COMMAND_READ_RECORD = 0xb2; +const ICC_COMMAND_UPDATE_RECORD = 0xdc; +const ICC_COMMAND_SEEK = 0xa2; +const ICC_COMMAND_GET_RESPONSE = 0xc0; + +// ICC constants, GSM SIM file ids from TS 51.011 +const ICC_EF_ADN = 0x6F3A; +const ICC_EF_FDN = 0x6F3B; +const ICC_EF_SDN = 0x6F49; +const ICC_EF_EXT1 = 0x6F4A; +const ICC_EF_EXT2 = 0x6F4B; +const ICC_EF_EXT3 = 0x6F4C; +const ICC_EF_EXT6 = 0x6fc8; // Ext record for EF[MBDN] +const ICC_EF_MWIS = 0x6FCA; +const ICC_EF_MBDN = 0x6fc7; +const ICC_EF_PNN = 0x6fc5; +const ICC_EF_SPN = 0x6F46; +const ICC_EF_SMS = 0x6F3C; +const ICC_EF_ICCID = 0x2fe2; +const ICC_EF_AD = 0x6FAD; +const ICC_EF_MBI = 0x6fc9; +const ICC_EF_MSISDN = 0x6f40; +const ICC_EF_SPDI = 0x6fcd; +const ICC_EF_SST = 0x6f38; +const ICC_EF_CFIS = 0x6FCB; +const ICC_EF_IMG = 0x4f20; + +// Types of files TS 11.11 9.3 +const TYPE_RFU = 0; +const TYPE_MF = 1; +const TYPE_DF = 2; +const TYPE_EF = 4; + +const RESPONSE_DATA_FILE_ID_1 = 4; +const RESPONSE_DATA_FILE_ID_2 = 5; +const RESPONSE_DATA_FILE_TYPE = 6; +const RESPONSE_DATA_RFU_3 = 7; +const RESPONSE_DATA_ACCESS_CONDITION_1 = 8; +const RESPONSE_DATA_ACCESS_CONDITION_2 = 9; +const RESPONSE_DATA_ACCESS_CONDITION_3 = 10; +const RESPONSE_DATA_FILE_STATUS = 11; +const RESPONSE_DATA_LENGTH = 12; +const RESPONSE_DATA_STRUCTURE = 13; +const RESPONSE_DATA_RECORD_LENGTH = 14; + +// Types of files TS 11.11 9.3 +const EF_TYPE_TRANSPARENT = 0; +const EF_TYPE_LINEAR_FIXED = 1; +const EF_TYPE_CYCLIC = 3; + +// For retriveing MSISDN +const FOOTER_SIZE_BYTES = 14; +const MAX_NUMBER_SIZE_BYTES = 11; + +// READ_RECORD mode, TS 102.221 +const READ_RECORD_ABSOLUTE_MODE = 4; + +// GET_RESPONSE mandatory response size for EF, see TS 51.011 clause 9, +// 'Response data in case of an EF.' +const GET_RESPONSE_EF_SIZE_BYTES = 15; + +// EF path +const EF_PATH_MF_SIM = "3f00"; +const EF_PATH_DF_TELECOM = "7f10"; + +// Status code for ICC I/O, +// see GSM11.11 and TS 51.011 clause 9.4. +const STATUS_NORMAL_ENDING = 0x90; +const STATUS_NORMAL_ENDING_WITH_EXTRA = 0x91; +const STATUS_WITH_SIM_DATA = 0x9e; +const STATUS_WITH_RESPONSE_DATA = 0x9f; /** * GSM PDU constants @@ -336,80 +427,667 @@ const PDU_DCS_MSG_CLASS_TE_SPECIFIC = 0xF3; // Because service center timestamp omit the century. Yay. const PDU_TIMESTAMP_YEAR_OFFSET = 2000; -// 7bit Default Alphabet -//TODO: maybe convert this to a string? might be faster/cheaper -const PDU_ALPHABET_7BIT_DEFAULT = [ - "@", // COMMERCIAL AT - "\xa3", // POUND SIGN - "$", // DOLLAR SIGN - "\xa5", // YEN SIGN - "\xe8", // LATIN SMALL LETTER E WITH GRAVE - "\xe9", // LATIN SMALL LETTER E WITH ACUTE - "\xf9", // LATIN SMALL LETTER U WITH GRAVE - "\xec", // LATIN SMALL LETTER I WITH GRAVE - "\xf2", // LATIN SMALL LETTER O WITH GRAVE - "\xc7", // LATIN CAPITAL LETTER C WITH CEDILLA - "\n", // LINE FEED - "\xd8", // LATIN CAPITAL LETTER O WITH STROKE - "\xf8", // LATIN SMALL LETTER O WITH STROKE - "\r", // CARRIAGE RETURN - "\xc5", // LATIN CAPITAL LETTER A WITH RING ABOVE - "\xe5", // LATIN SMALL LETTER A WITH RING ABOVE - "\u0394", // GREEK CAPITAL LETTER DELTA - "_", // LOW LINE - "\u03a6", // GREEK CAPITAL LETTER PHI - "\u0393", // GREEK CAPITAL LETTER GAMMA - "\u039b", // GREEK CAPITAL LETTER LAMBDA - "\u03a9", // GREEK CAPITAL LETTER OMEGA - "\u03a0", // GREEK CAPITAL LETTER PI - "\u03a8", // GREEK CAPITAL LETTER PSI - "\u03a3", // GREEK CAPITAL LETTER SIGMA - "\u0398", // GREEK CAPITAL LETTER THETA - "\u039e", // GREEK CAPITAL LETTER XI - "\u20ac", // (escape to extension table) - "\xc6", // LATIN CAPITAL LETTER AE - "\xe6", // LATIN SMALL LETTER AE - "\xdf", // LATIN SMALL LETTER SHARP S (German) - "\xc9", // LATIN CAPITAL LETTER E WITH ACUTE - " ", // SPACE - "!", // EXCLAMATION MARK - "\"", // QUOTATION MARK - "#", // NUMBER SIGN - "\xa4", // CURRENCY SIGN - "%", // PERCENT SIGN - "&", // AMPERSAND - "'", // APOSTROPHE - "(", // LEFT PARENTHESIS - ")", // RIGHT PARENTHESIS - "*", // ASTERISK - "+", // PLUS SIGN - ",", // COMMA - "-", // HYPHEN-MINUS - ".", // FULL STOP - "/", // SOLIDUS (SLASH) - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", - ":", // COLON - ";", // SEMICOLON - "<", // LESS-THAN SIGN - "=", // EQUALS SIGN - ">", // GREATER-THAN SIGN - "?", // QUESTION MARK - "\xa1", // INVERTED EXCLAMATION MARK - "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", - "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", - "\xc4", // LATIN CAPITAL LETTER A WITH DIAERESIS - "\xd6", // LATIN CAPITAL LETTER O WITH DIAERESIS - "\xd1", // LATIN CAPITAL LETTER N WITH TILDE - "\xdc", // LATIN CAPITAL LETTER U WITH DIAERESIS - "\xa7", // SECTION SIGN - "\xbf", // INVERTED QUESTION MARK - "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", - "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", - "\xe4", // LATIN SMALL LETTER A WITH DIAERESIS - "\xf6", // LATIN SMALL LETTER O WITH DIAERESIS - "\xf1", // LATIN SMALL LETTER N WITH TILDE - "\xfc", // LATIN SMALL LETTER U WITH DIAERESIS - "\xe0" // LATIN SMALL LETTER A WITH GRAVE +// See 9.2.3.24 TP‑User Data (TP‑UD) +const PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT = 0x00; +const PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION = 0x01; +const PDU_IEI_APPLICATION_PORT_ADDREESING_SCHEME_8BIT = 0x04; +const PDU_IEI_APPLICATION_PORT_ADDREESING_SCHEME_16BIT = 0x05; +const PDU_IEI_SMSC_CONTROL_PARAMS = 0x06; +const PDU_IEI_UDH_SOURCE_INDICATOR = 0x07; +const PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT = 0x08; +const PDU_IEI_WIRELESS_CONTROL_MESSAGE_PROTOCOL = 0x09; +const PDU_IEI_TEXT_FORMATING = 0x0A; +const PDU_IEI_PREDEFINED_SOUND = 0x0B; +const PDU_IEI_USER_DATA_SOUND = 0x0C; +const PDU_IEI_PREDEFINED_ANIMATION = 0x0D; +const PDU_IEI_LARGE_ANIMATION = 0x0E; +const PDU_IEI_SMALL_ANIMATION = 0x0F; +const PDU_IEI_LARGE_PICTURE = 0x10; +const PDU_IEI_SMALL_PICTURE = 0x11; +const PDU_IEI_VARIABLE_PICTURE = 0x12; +const PDU_IEI_USER_PROMPT_INDICATOR = 0x13; +const PDU_IEI_EXTENDED_OBJECT = 0x14; +const PDU_IEI_REUSED_EXTENDED_OBJECT = 0x15; +const PDU_IEI_COMPRESS_CONTROL = 0x16; +const PDU_IEI_OBJECT_DISTRIBUTION_INDICATOR = 0x17; +const PDU_IEI_STANDARD_WVG_OBJECT = 0x18; +const PDU_IEI_CHARACTER_SIZE_WVG_OBJECT = 0x19; +const PDU_IEI_EXTENDED_OBJECT_DATA_REQUEST_COMMAND = 0x1A; +const PDU_IEI_RFC822_EMAIL_HEADER = 0x20; +const PDU_IEI_HYPERLINK_FORMAT_ELEMENT = 0x21; +const PDU_IEI_REPLY_ADDRESS_ELEMENT = 0x22; +const PDU_IEI_ENHANCED_VOICE_MAIL_INFORMATION = 0x23; +const PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT = 0x24; +const PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT = 0x25; + +// 7bit alphabet escape character. The encoded value of this code point is left +// undefined in official spec. Its code value is internally assigned to \uffff, +// in Unicode basic multilingual plane. +const PDU_NL_EXTENDED_ESCAPE = 0x1B; + +// , , are only defined in locking shift tables. +const PDU_NL_SPACE = 0x20; +const PDU_NL_LINE_FEED = 0x0A; +const PDU_NL_CARRIAGE_RETURN = 0x0D; + +// 7bit alphabet page break character, only defined in single shift tables. +// The encoded value of this code point is left undefined in official spec, but +// the code point itself maybe be used for example in compressed CBS messages. +// Its code value is internally assigned to \u000c, ASCII form feed, or new page. +const PDU_NL_PAGE_BREAK = 0x0A; +// 7bit alphabet reserved control character, only defined in single shift +// tables. The encoded value of this code point is left undefined in official +// spec. Its code value is internally assigned to \ufffe, +// in Unicode basic multilingual plane. +const PDU_NL_RESERVED_CONTROL = 0x0D; + +const PDU_NL_IDENTIFIER_DEFAULT = 0; +const PDU_NL_IDENTIFIER_TURKISH = 1; +const PDU_NL_IDENTIFIER_SPANISH = 2; +const PDU_NL_IDENTIFIER_PORTUGUESE = 3; +const PDU_NL_IDENTIFIER_BENGALI = 4; +const PDU_NL_IDENTIFIER_GUJARATI = 5; +const PDU_NL_IDENTIFIER_HINDI = 6; +const PDU_NL_IDENTIFIER_KANNADA = 7; +const PDU_NL_IDENTIFIER_MALAYALAM = 8; +const PDU_NL_IDENTIFIER_ORIYA = 9; +const PDU_NL_IDENTIFIER_PUNJABI = 10; +const PDU_NL_IDENTIFIER_TAMIL = 11; +const PDU_NL_IDENTIFIER_TELUGU = 12; +const PDU_NL_IDENTIFIER_URDU = 13; + +// National Language Locking Shift Tables, see 3GPP TS 23.038 +const PDU_NL_LOCKING_SHIFT_TABLES = [ + /** + * National Language Identifier: 0x00 + * 6.2.1 GSM 7 bit Default Alphabet + */ + // 01.....23.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F..... + "@\u00a3$\u00a5\u00e8\u00e9\u00f9\u00ec\u00f2\u00c7\n\u00d8\u00f8\r\u00c5\u00e5" + // 0.....12.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u0394_\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e\uffff\u00c6\u00e6\u00df\u00c9" + // 012.34.....56789ABCDEF + + " !\"#\u00a4%&'()*+,-./" + // 0123456789ABCDEF + + "0123456789:;<=>?" + // 0.....123456789ABCDEF + + "\u00a1ABCDEFGHIJKLMNO" + // 0123456789AB.....C.....D.....E.....F..... + + "PQRSTUVWXYZ\u00c4\u00d6\u00d1\u00dc\u00a7" + // 0.....123456789ABCDEF + + "\u00bfabcdefghijklmno" + // 0123456789AB.....C.....D.....E.....F..... + + "pqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0", + + /** + * National Language Identifier: 0x01 + * A.3.1 Turkish National Language Locking Shift Table + */ + // 01.....23.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F..... + "@\u00a3$\u00a5\u20ac\u00e9\u00f9\u0131\u00f2\u00c7\n\u011e\u011f\r\u00c5\u00e5" + // 0.....12.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u0394_\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e\uffff\u015e\u015f\u00df\u00c9" + // 012.34.....56789ABCDEF + + " !\"#\u00a4%&'()*+,-./" + // 0123456789ABCDEF + + "0123456789:;<=>?" + // 0.....123456789ABCDEF + + "\u0130ABCDEFGHIJKLMNO" + // 0123456789AB.....C.....D.....E.....F..... + + "PQRSTUVWXYZ\u00c4\u00d6\u00d1\u00dc\u00a7" + // 0.....123456789ABCDEF + + "\u00e7abcdefghijklmno" + // 0123456789AB.....C.....D.....E.....F..... + + "pqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0", + + /** + * National Language Identifier: 0x02 + * A.3.2 Void + */ + // 0123456789A.BCD.EF + " \n \r " + // 0123456789AB.....CDEF + + " \uffff " + // 0123456789ABCDEF + + " " + // 0123456789ABCDEF + + " " + // 0123456789ABCDEF + + " " + // 0123456789ABCDEF + + " " + // 0123456789ABCDEF + + " " + // 0123456789ABCDEF + + " ", + + /** + * National Language Identifier: 0x03 + * A.3.3 Portuguese National Language Locking Shift Table + */ + // 01.....23.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F..... + "@\u00a3$\u00a5\u00ea\u00e9\u00fa\u00ed\u00f3\u00e7\n\u00d4\u00f4\r\u00c1\u00e1" + // 0.....12.....3.....4.....5.....67.8.....9.....AB.....C.....D.....E.....F..... + + "\u0394_\u00aa\u00c7\u00c0\u221e^\\\u20ac\u00d3|\uffff\u00c2\u00e2\u00ca\u00c9" + // 012.34.....56789ABCDEF + + " !\"#\u00ba%&'()*+,-./" + // 0123456789ABCDEF + + "0123456789:;<=>?" + // 0.....123456789ABCDEF + + "\u00cdABCDEFGHIJKLMNO" + // 0123456789AB.....C.....D.....E.....F..... + + "PQRSTUVWXYZ\u00c3\u00d5\u00da\u00dc\u00a7" + // 0123456789ABCDEF + + "~abcdefghijklmno" + // 0123456789AB.....C.....DE.....F..... + + "pqrstuvwxyz\u00e3\u00f5`\u00fc\u00e0", + + /** + * National Language Identifier: 0x04 + * A.3.4 Bengali National Language Locking Shift Table + */ + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.EF..... + "\u0981\u0982\u0983\u0985\u0986\u0987\u0988\u0989\u098a\u098b\n\u098c \r \u098f" + // 0.....123.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u0990 \u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\uffff\u099b\u099c\u099d\u099e" + // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF..... + + " !\u099f\u09a0\u09a1\u09a2\u09a3\u09a4)(\u09a5\u09a6,\u09a7.\u09a8" + // 0123456789ABCD.....E.....F + + "0123456789:; \u09aa\u09ab?" + // 0.....1.....2.....3.....4.....56.....789A.....B.....C.....D.....E.....F..... + + "\u09ac\u09ad\u09ae\u09af\u09b0 \u09b2 \u09b6\u09b7\u09b8\u09b9\u09bc\u09bd" + // 0.....1.....2.....3.....4.....5.....6.....789.....A.....BCD.....E.....F..... + + "\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4 \u09c7\u09c8 \u09cb\u09cc\u09cd" + // 0.....123456789ABCDEF + + "\u09ceabcdefghijklmno" + // 0123456789AB.....C.....D.....E.....F..... + + "pqrstuvwxyz\u09d7\u09dc\u09dd\u09f0\u09f1", + + /** + * National Language Identifier: 0x05 + * A.3.5 Gujarati National Language Locking Shift Table + */ + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.EF..... + "\u0a81\u0a82\u0a83\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\n\u0a8c\u0a8d\r \u0a8f" + // 0.....1.....23.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u0a90\u0a91 \u0a93\u0a94\u0a95\u0a96\u0a97\u0a98\u0a99\u0a9a\uffff\u0a9b\u0a9c\u0a9d\u0a9e" + // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF..... + + " !\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4)(\u0aa5\u0aa6,\u0aa7.\u0aa8" + // 0123456789ABCD.....E.....F + + "0123456789:; \u0aaa\u0aab?" + // 0.....1.....2.....3.....4.....56.....7.....89.....A.....B.....C.....D.....E.....F..... + + "\u0aac\u0aad\u0aae\u0aaf\u0ab0 \u0ab2\u0ab3 \u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0abc\u0abd" + // 0.....1.....2.....3.....4.....5.....6.....7.....89.....A.....B.....CD.....E.....F..... + + "\u0abe\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5 \u0ac7\u0ac8\u0ac9 \u0acb\u0acc\u0acd" + // 0.....123456789ABCDEF + + "\u0ad0abcdefghijklmno" + // 0123456789AB.....C.....D.....E.....F..... + + "pqrstuvwxyz\u0ae0\u0ae1\u0ae2\u0ae3\u0af1", + + /** + * National Language Identifier: 0x06 + * A.3.6 Hindi National Language Locking Shift Table + */ + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F..... + "\u0901\u0902\u0903\u0905\u0906\u0907\u0908\u0909\u090a\u090b\n\u090c\u090d\r\u090e\u090f" + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a\uffff\u091b\u091c\u091d\u091e" + // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF..... + + " !\u091f\u0920\u0921\u0922\u0923\u0924)(\u0925\u0926,\u0927.\u0928" + // 0123456789ABC.....D.....E.....F + + "0123456789:;\u0929\u092a\u092b?" + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u092c\u092d\u092e\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093c\u093d" + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u093e\u093f\u0940\u0941\u0942\u0943\u0944\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d" + // 0.....123456789ABCDEF + + "\u0950abcdefghijklmno" + // 0123456789AB.....C.....D.....E.....F..... + + "pqrstuvwxyz\u0972\u097b\u097c\u097e\u097f", + + /** + * National Language Identifier: 0x07 + * A.3.7 Kannada National Language Locking Shift Table + */ + // 01.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.E.....F..... + " \u0c82\u0c83\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a\u0c8b\n\u0c8c \r\u0c8e\u0c8f" + // 0.....12.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u0c90 \u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\uffff\u0c9b\u0c9c\u0c9d\u0c9e" + // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF..... + + " !\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4)(\u0ca5\u0ca6,\u0ca7.\u0ca8" + // 0123456789ABCD.....E.....F + + "0123456789:; \u0caa\u0cab?" + // 0.....1.....2.....3.....4.....5.....6.....7.....89.....A.....B.....C.....D.....E.....F..... + + "\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2\u0cb3 \u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cbc\u0cbd" + // 0.....1.....2.....3.....4.....5.....6.....78.....9.....A.....BC.....D.....E.....F..... + + "\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4 \u0cc6\u0cc7\u0cc8 \u0cca\u0ccb\u0ccc\u0ccd" + // 0.....123456789ABCDEF + + "\u0cd5abcdefghijklmno" + // 0123456789AB.....C.....D.....E.....F..... + + "pqrstuvwxyz\u0cd6\u0ce0\u0ce1\u0ce2\u0ce3", + + /** + * National Language Identifier: 0x08 + * A.3.8 Malayalam National Language Locking Shift Table + */ + // 01.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.E.....F..... + " \u0d02\u0d03\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\n\u0d0c \r\u0d0e\u0d0f" + // 0.....12.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u0d10 \u0d12\u0d13\u0d14\u0d15\u0d16\u0d17\u0d18\u0d19\u0d1a\uffff\u0d1b\u0d1c\u0d1d\u0d1e" + // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF..... + + " !\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24)(\u0d25\u0d26,\u0d27.\u0d28" + // 0123456789ABCD.....E.....F + + "0123456789:; \u0d2a\u0d2b?" + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....EF..... + + "\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39 \u0d3d" + // 0.....1.....2.....3.....4.....5.....6.....78.....9.....A.....BC.....D.....E.....F..... + + "\u0d3e\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44 \u0d46\u0d47\u0d48 \u0d4a\u0d4b\u0d4c\u0d4d" + // 0.....123456789ABCDEF + + "\u0d57abcdefghijklmno" + // 0123456789AB.....C.....D.....E.....F..... + + "pqrstuvwxyz\u0d60\u0d61\u0d62\u0d63\u0d79", + + /** + * National Language Identifier: 0x09 + * A.3.9 Oriya National Language Locking Shift Table + */ + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.EF..... + "\u0b01\u0b02\u0b03\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\n\u0b0c \r \u0b0f" + // 0.....123.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u0b10 \u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\uffff\u0b1b\u0b1c\u0b1d\u0b1e" + // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF..... + + " !\u0b1f\u0b20\u0b21\u0b22\u0b23\u0b24)(\u0b25\u0b26,\u0b27.\u0b28" + // 0123456789ABCD.....E.....F + + "0123456789:; \u0b2a\u0b2b?" + // 0.....1.....2.....3.....4.....56.....7.....89.....A.....B.....C.....D.....E.....F..... + + "\u0b2c\u0b2d\u0b2e\u0b2f\u0b30 \u0b32\u0b33 \u0b35\u0b36\u0b37\u0b38\u0b39\u0b3c\u0b3d" + // 0.....1.....2.....3.....4.....5.....6.....789.....A.....BCD.....E.....F..... + + "\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44 \u0b47\u0b48 \u0b4b\u0b4c\u0b4d" + // 0.....123456789ABCDEF + + "\u0b56abcdefghijklmno" + // 0123456789AB.....C.....D.....E.....F..... + + "pqrstuvwxyz\u0b57\u0b60\u0b61\u0b62\u0b63", + + /** + * National Language Identifier: 0x0A + * A.3.10 Punjabi National Language Locking Shift Table + */ + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9A.BCD.EF..... + "\u0a01\u0a02\u0a03\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a \n \r \u0a0f" + // 0.....123.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u0a10 \u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\uffff\u0a1b\u0a1c\u0a1d\u0a1e" + // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF..... + + " !\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24)(\u0a25\u0a26,\u0a27.\u0a28" + // 0123456789ABCD.....E.....F + + "0123456789:; \u0a2a\u0a2b?" + // 0.....1.....2.....3.....4.....56.....7.....89.....A.....BC.....D.....E.....F + + "\u0a2c\u0a2d\u0a2e\u0a2f\u0a30 \u0a32\u0a33 \u0a35\u0a36 \u0a38\u0a39\u0a3c " + // 0.....1.....2.....3.....4.....56789.....A.....BCD.....E.....F..... + + "\u0a3e\u0a3f\u0a40\u0a41\u0a42 \u0a47\u0a48 \u0a4b\u0a4c\u0a4d" + // 0.....123456789ABCDEF + + "\u0a51abcdefghijklmno" + // 0123456789AB.....C.....D.....E.....F..... + + "pqrstuvwxyz\u0a70\u0a71\u0a72\u0a73\u0a74", + + /** + * National Language Identifier: 0x0B + * A.3.11 Tamil National Language Locking Shift Table + */ + // 01.....2.....3.....4.....5.....6.....7.....8.....9A.BCD.E.....F..... + " \u0b82\u0b83\u0b85\u0b86\u0b87\u0b88\u0b89\u0b8a \n \r\u0b8e\u0b8f" + // 0.....12.....3.....4.....5.....6789.....A.....B.....CD.....EF..... + + "\u0b90 \u0b92\u0b93\u0b94\u0b95 \u0b99\u0b9a\uffff \u0b9c \u0b9e" + // 012.....3456.....7.....89ABCDEF..... + + " !\u0b9f \u0ba3\u0ba4)( , .\u0ba8" + // 0123456789ABC.....D.....EF + + "0123456789:;\u0ba9\u0baa ?" + // 012.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....EF + + " \u0bae\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9 " + // 0.....1.....2.....3.....4.....5678.....9.....A.....BC.....D.....E.....F..... + + "\u0bbe\u0bbf\u0bc0\u0bc1\u0bc2 \u0bc6\u0bc7\u0bc8 \u0bca\u0bcb\u0bcc\u0bcd" + // 0.....123456789ABCDEF + + "\u0bd0abcdefghijklmno" + // 0123456789AB.....C.....D.....E.....F..... + + "pqrstuvwxyz\u0bd7\u0bf0\u0bf1\u0bf2\u0bf9", + + /** + * National Language Identifier: 0x0C + * A.3.12 Telugu National Language Locking Shift Table + */ + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.E.....F..... + "\u0c01\u0c02\u0c03\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\n\u0c0c \r\u0c0e\u0c0f" + // 0.....12.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u0c10 \u0c12\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\uffff\u0c1b\u0c1c\u0c1d\u0c1e" + // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF..... + + " !\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24)(\u0c25\u0c26,\u0c27.\u0c28" + // 0123456789ABCD.....E.....F + + "0123456789:; \u0c2a\u0c2b?" + // 0.....1.....2.....3.....4.....5.....6.....7.....89.....A.....B.....C.....D.....EF..... + + "\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33 \u0c35\u0c36\u0c37\u0c38\u0c39 \u0c3d" + // 0.....1.....2.....3.....4.....5.....6.....78.....9.....A.....BC.....D.....E.....F..... + + "\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44 \u0c46\u0c47\u0c48 \u0c4a\u0c4b\u0c4c\u0c4d" + // 0.....123456789ABCDEF + + "\u0c55abcdefghijklmno" + // 0123456789AB.....C.....D.....E.....F..... + + "pqrstuvwxyz\u0c56\u0c60\u0c61\u0c62\u0c63", + + /** + * National Language Identifier: 0x0D + * A.3.13 Urdu National Language Locking Shift Table + */ + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F..... + "\u0627\u0622\u0628\u067b\u0680\u067e\u06a6\u062a\u06c2\u067f\n\u0679\u067d\r\u067a\u067c" + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u062b\u062c\u0681\u0684\u0683\u0685\u0686\u0687\u062d\u062e\u062f\uffff\u068c\u0688\u0689\u068a" + // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF..... + + " !\u068f\u068d\u0630\u0631\u0691\u0693)(\u0699\u0632,\u0696.\u0698" + // 0123456789ABC.....D.....E.....F + + "0123456789:;\u069a\u0633\u0634?" + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u0635\u0636\u0637\u0638\u0639\u0641\u0642\u06a9\u06aa\u06ab\u06af\u06b3\u06b1\u0644\u0645\u0646" + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F..... + + "\u06ba\u06bb\u06bc\u0648\u06c4\u06d5\u06c1\u06be\u0621\u06cc\u06d0\u06d2\u064d\u0650\u064f\u0657" + // 0.....123456789ABCDEF + + "\u0654abcdefghijklmno" + // 0123456789AB.....C.....D.....E.....F..... + + "pqrstuvwxyz\u0655\u0651\u0653\u0656\u0670" +]; + +// National Language Single Shift Tables, see 3GPP TS 23.038 +const PDU_NL_SINGLE_SHIFT_TABLES = [ + /** + * National Language Identifier: 0x00 + * 6.2.1.1 GSM 7 bit default alphabet extension table + */ + // 0123456789A.....BCD.....EF + " \u000c \ufffe " + // 0123456789AB.....CDEF + + " ^ \uffff " + // 0123456789ABCDEF. + + " {} \\" + // 0123456789ABCDEF + + " [~] " + // 0123456789ABCDEF + + "| " + // 0123456789ABCDEF + + " " + // 012345.....6789ABCDEF + + " \u20ac " + // 0123456789ABCDEF + + " ", + + /** + * National Language Identifier: 0x01 + * A.2.1 Turkish National Language Single Shift Table + */ + // 0123456789A.....BCD.....EF + " \u000c \ufffe " + // 0123456789AB.....CDEF + + " ^ \uffff " + // 0123456789ABCDEF. + + " {} \\" + // 0123456789ABCDEF + + " [~] " + // 01234567.....89.....ABCDEF + + "| \u011e \u0130 " + // 0123.....456789ABCDEF + + " \u015e " + // 0123.....45.....67.....89.....ABCDEF + + " \u00e7 \u20ac \u011f \u0131 " + // 0123.....456789ABCDEF + + " \u015f ", + + /** + * National Language Identifier: 0x02 + * A.2.2 Spanish National Language Single Shift Table + */ + // 0123456789.....A.....BCD.....EF + " \u00e7\u000c \ufffe " + // 0123456789AB.....CDEF + + " ^ \uffff " + // 0123456789ABCDEF. + + " {} \\" + // 0123456789ABCDEF + + " [~] " + // 01.....23456789.....ABCDEF..... + + "|\u00c1 \u00cd \u00d3" + // 012345.....6789ABCDEF + + " \u00da " + // 01.....2345.....6789.....ABCDEF..... + + " \u00e1 \u20ac \u00ed \u00f3" + // 012345.....6789ABCDEF + + " \u00fa ", + + /** + * National Language Identifier: 0x03 + * A.2.3 Portuguese National Language Single Shift Table + */ + // 012345.....6789.....A.....B.....C.....D.....E.....F..... + " \u00ea \u00e7\u000c\u00d4\u00f4\ufffe\u00c1\u00e1" + // 012.....3.....45.....6.....7.....8.....9.....AB.....CDEF..... + + " \u03a6\u0393^\u03a9\u03a0\u03a8\u03a3\u0398 \uffff \u00ca" + // 0123456789ABCDEF. + + " {} \\" + // 0123456789ABCDEF + + " [~] " + // 01.....23456789.....ABCDEF..... + + "|\u00c0 \u00cd \u00d3" + // 012345.....6789AB.....C.....DEF + + " \u00da \u00c3\u00d5 " + // 01.....2345.....6789.....ABCDEF..... + + " \u00c2 \u20ac \u00ed \u00f3" + // 012345.....6789AB.....C.....DEF..... + + " \u00fa \u00e3\u00f5 \u00e2", + + /** + * National Language Identifier: 0x04 + * A.2.4 Bengali National Language Single Shift Table + */ + // 01.....23.....4.....5.6.....789A.....BCD.....EF + "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/" + // 0123.....45.....6789.....A.....B.....C.....D.....E.....F..... + + "<=>\u00a1^\u00a1_#*\u09e6\u09e7\uffff\u09e8\u09e9\u09ea\u09eb" + // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....E.....F. + + "\u09ec\u09ed\u09ee\u09ef\u09df\u09e0\u09e1\u09e2{}\u09e3\u09f2\u09f3\u09f4\u09f5\\" + // 0.....1.....2.....3.....4.....56789ABCDEF + + "\u09f6\u09f7\u09f8\u09f9\u09fa [~] " + // 0123456789ABCDEF + + "|ABCDEFGHIJKLMNO" + // 0123456789ABCDEF + + "PQRSTUVWXYZ " + // 012345.....6789ABCDEF + + " \u20ac " + // 0123456789ABCDEF + + " ", + + /** + * National Language Identifier: 0x05 + * A.2.5 Gujarati National Language Single Shift Table + */ + // 01.....23.....4.....5.6.....789A.....BCD.....EF + "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/" + // 0123.....45.....6789.....A.....B.....C.....D.....E.....F..... + + "<=>\u00a1^\u00a1_#*\u0964\u0965\uffff\u0ae6\u0ae7\u0ae8\u0ae9" + // 0.....1.....2.....3.....4.....5.....6789ABCDEF. + + "\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef {} \\" + // 0123456789ABCDEF + + " [~] " + // 0123456789ABCDEF + + "|ABCDEFGHIJKLMNO" + // 0123456789ABCDEF + + "PQRSTUVWXYZ " + // 012345.....6789ABCDEF + + " \u20ac " + // 0123456789ABCDEF + + " ", + + /** + * National Language Identifier: 0x06 + * A.2.6 Hindi National Language Single Shift Table + */ + // 01.....23.....4.....5.6.....789A.....BCD.....EF + "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/" + // 0123.....45.....6789.....A.....B.....C.....D.....E.....F..... + + "<=>\u00a1^\u00a1_#*\u0964\u0965\uffff\u0966\u0967\u0968\u0969" + // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....E.....F. + + "\u096a\u096b\u096c\u096d\u096e\u096f\u0951\u0952{}\u0953\u0954\u0958\u0959\u095a\\" + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....BCDEF + + "\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0970\u0971 [~] " + // 0123456789ABCDEF + + "|ABCDEFGHIJKLMNO" + // 0123456789ABCDEF + + "PQRSTUVWXYZ " + // 012345.....6789ABCDEF + + " \u20ac " + // 0123456789ABCDEF + + " ", + + /** + * National Language Identifier: 0x07 + * A.2.7 Kannada National Language Single Shift Table + */ + // 01.....23.....4.....5.6.....789A.....BCD.....EF + "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/" + // 0123.....45.....6789.....A.....B.....C.....D.....E.....F..... + + "<=>\u00a1^\u00a1_#*\u0964\u0965\uffff\u0ce6\u0ce7\u0ce8\u0ce9" + // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....BCDEF. + + "\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0cde\u0cf1{}\u0cf2 \\" + // 0123456789ABCDEF + + " [~] " + // 0123456789ABCDEF + + "|ABCDEFGHIJKLMNO" + // 0123456789ABCDEF + + "PQRSTUVWXYZ " + // 012345.....6789ABCDEF + + " \u20ac " + // 0123456789ABCDEF + + " ", + + /** + * National Language Identifier: 0x08 + * A.2.8 Malayalam National Language Single Shift Table + */ + // 01.....23.....4.....5.6.....789A.....BCD.....EF + "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/" + // 0123.....45.....6789.....A.....B.....C.....D.....E.....F..... + + "<=>\u00a1^\u00a1_#*\u0964\u0965\uffff\u0d66\u0d67\u0d68\u0d69" + // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....E.....F. + + "\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71{}\u0d72\u0d73\u0d74\u0d75\u0d7a\\" + // 0.....1.....2.....3.....4.....56789ABCDEF + + "\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f [~] " + // 0123456789ABCDEF + + "|ABCDEFGHIJKLMNO" + // 0123456789ABCDEF + + "PQRSTUVWXYZ " + // 012345.....6789ABCDEF + + " \u20ac " + // 0123456789ABCDEF + + " ", + + /** + * National Language Identifier: 0x09 + * A.2.9 Oriya National Language Single Shift Table + */ + // 01.....23.....4.....5.6.....789A.....BCD.....EF + "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/" + // 0123.....45.....6789.....A.....B.....C.....D.....E.....F..... + + "<=>\u00a1^\u00a1_#*\u0964\u0965\uffff\u0b66\u0b67\u0b68\u0b69" + // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....DEF. + + "\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b5c\u0b5d{}\u0b5f\u0b70\u0b71 \\" + // 0123456789ABCDEF + + " [~] " + // 0123456789ABCDEF + + "|ABCDEFGHIJKLMNO" + // 0123456789ABCDEF + + "PQRSTUVWXYZ " + // 012345.....6789ABCDEF + + " \u20ac " + // 0123456789ABCDEF + + " ", + + /** + * National Language Identifier: 0x0A + * A.2.10 Punjabi National Language Single Shift Table + */ + // 01.....23.....4.....5.6.....789A.....BCD.....EF + "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/" + // 0123.....45.....6789.....A.....B.....C.....D.....E.....F..... + + "<=>\u00a1^\u00a1_#*\u0964\u0965\uffff\u0a66\u0a67\u0a68\u0a69" + // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....EF. + + "\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0a59\u0a5a{}\u0a5b\u0a5c\u0a5e\u0a75 \\" + // 0123456789ABCDEF + + " [~] " + // 0123456789ABCDEF + + "|ABCDEFGHIJKLMNO" + // 0123456789ABCDEF + + "PQRSTUVWXYZ " + // 012345.....6789ABCDEF + + " \u20ac " + // 0123456789ABCDEF + + " ", + + /** + * National Language Identifier: 0x0B + * A.2.11 Tamil National Language Single Shift Table + */ + // 01.....23.....4.....5.6.....789A.....BCD.....EF + "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/" + // 0123.....45.....6789.....A.....B.....C.....D.....E.....F..... + + "<=>\u00a1^\u00a1_#*\u0964\u0965\uffff\u0be6\u0be7\u0be8\u0be9" + // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....E.....F. + + "\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf3\u0bf4{}\u0bf5\u0bf6\u0bf7\u0bf8\u0bfa\\" + // 0123456789ABCDEF + + " [~] " + // 0123456789ABCDEF + + "|ABCDEFGHIJKLMNO" + // 0123456789ABCDEF + + "PQRSTUVWXYZ " + // 012345.....6789ABCDEF + + " \u20ac " + // 0123456789ABCDEF + + " ", + + /** + * National Language Identifier: 0x0C + * A.2.12 Telugu National Language Single Shift Table + */ + // 01.....23.....4.....5.6.....789A.....BCD.....EF + "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/" + // 0123.....45.....6789AB.....C.....D.....E.....F..... + + "<=>\u00a1^\u00a1_#* \uffff\u0c66\u0c67\u0c68\u0c69" + // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....E.....F. + + "\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c58\u0c59{}\u0c78\u0c79\u0c7a\u0c7b\u0c7c\\" + // 0.....1.....2.....3456789ABCDEF + + "\u0c7d\u0c7e\u0c7f [~] " + // 0123456789ABCDEF + + "|ABCDEFGHIJKLMNO" + // 0123456789ABCDEF + + "PQRSTUVWXYZ " + // 012345.....6789ABCDEF + + " \u20ac " + // 0123456789ABCDEF + + " ", + + /** + * National Language Identifier: 0x0D + * A.2.13 Urdu National Language Single Shift Table + */ + // 01.....23.....4.....5.6.....789A.....BCD.....EF + "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/" + // 0123.....45.....6789.....A.....B.....C.....D.....E.....F..... + + "<=>\u00a1^\u00a1_#*\u0600\u0601\uffff\u06f0\u06f1\u06f2\u06f3" + // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....E.....F. + + "\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u060c\u060d{}\u060e\u060f\u0610\u0611\u0612\\" + // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....CDEF..... + + "\u0613\u0614\u061b\u061f\u0640\u0652\u0658\u066b\u066c\u0672\u0673\u06cd[~]\u06d4" + // 0123456789ABCDEF + + "|ABCDEFGHIJKLMNO" + // 0123456789ABCDEF + + "PQRSTUVWXYZ " + // 012345.....6789ABCDEF + + " \u20ac " + // 0123456789ABCDEF + + " " ]; const DATACALL_RADIOTECHNOLOGY_CDMA = 0; diff --git a/dom/system/b2g/ril_worker.js b/dom/system/b2g/ril_worker.js index b8b559017742..6fab63fe335b 100644 --- a/dom/system/b2g/ril_worker.js +++ b/dom/system/b2g/ril_worker.js @@ -463,12 +463,14 @@ let Buf = { options = this.tokenRequestMap[token]; request_type = options.rilRequestType; - if (error) { - //TODO + + options.rilRequestError = error; + if (error) { if (DEBUG) { debug("Received error " + error + " for solicited parcel type " + request_type); } + RIL.handleRequestError(options); return; } if (DEBUG) { @@ -509,6 +511,7 @@ let Buf = { options = {}; } options.rilRequestType = type; + options.rilRequestError = null; this.tokenRequestMap[token] = options; this.token++; return token; @@ -790,8 +793,16 @@ let RIL = { * @param dcs * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET * constants. - * @param bodyLengthInOctets - * Byte length of the message body when encoded with the given DCS. + * @param userDataHeaderLength + * Length of embedded user data header, in bytes. The whole header + * size will be userDataHeaderLength + 1; 0 for no header. + * @param encodedBodyLength + * Length of the message body when encoded with the given DCS. For + * UCS2, in bytes; for 7-bit, in septets. + * @param langIndex + * Table index used for normal 7-bit encoded character lookup. + * @param langShiftIndex + * Table index used for escaped 7-bit encoded character lookup. */ sendSMS: function sendSMS(options) { let token = Buf.newParcel(REQUEST_SEND_SMS, options); @@ -801,10 +812,7 @@ let RIL = { // handle it within tokenRequestMap[]. Buf.writeUint32(2); Buf.writeString(options.SMSC); - GsmPDUHelper.writeMessage(options.number, - options.body, - options.dcs, - options.bodyLengthInOctets); + GsmPDUHelper.writeMessage(options); Buf.sendParcel(); }, @@ -904,6 +912,41 @@ let RIL = { return token; }, + /** + * Request an ICC I/O operation. + * + * See TS 27.007 "restricted SIM" operation, "AT Command +CRSM". + * The sequence is in the same order as how libril reads this parcel, + * see the struct RIL_SIM_IO_v5 or RIL_SIM_IO_v6 defined in ril.h + * + * @param command + * The I/O command, one of the ICC_COMMAND_* constants. + * @param fileid + * The file to operate on, one of the ICC_EF_* constants. + * @param pathid + * String type, check pathid from TS 27.007 +CRSM + * @param p1, p2, p3 + * Arbitrary integer parameters for the command. + * @param data + * String parameter for the command. + * @param pin2 [optional] + * String containing the PIN2. + */ + iccIO: function iccIO (options) { + let token = Buf.newParcel(REQUEST_SIM_IO, options); + Buf.writeUint32(options.command); + Buf.writeUint32(options.fileid); + Buf.writeString(options.path); + Buf.writeUint32(options.p1); + Buf.writeUint32(options.p2); + Buf.writeUint32(options.p3); + Buf.writeString(options.data); + if (request.pin2 != null) { + Buf.writeString(pin2); + } + Buf.sendParcel(); + }, + /** * Deactivate a data call. * @@ -934,6 +977,14 @@ let RIL = { getFailCauseCode: function getFailCauseCode() { Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE); }, + + /** + * Handle the RIL request errors + */ + handleRequestError: function handleRequestError(options) { + options.type = "error"; + Phone.sendDOMMessage(options); + }, /** * Handle incoming requests from the RIL. We find the method that @@ -1109,7 +1160,9 @@ RIL[REQUEST_SETUP_DATA_CALL] = function REQUEST_SETUP_DATA_CALL() { let [cid, ifname, ipaddr, dns, gw] = Buf.readStringList(); Phone.onSetupDataCall(Buf.lastSolicitedToken, cid, ifname, ipaddr, dns, gw); }; -RIL[REQUEST_SIM_IO] = null; +RIL[REQUEST_SIM_IO] = function REQUEST_SIM_IO(length, options) { + Phone.onICCIO(options); +}; RIL[REQUEST_SEND_USSD] = null; RIL[REQUEST_CANCEL_USSD] = null; RIL[REQUEST_GET_CLIR] = null; @@ -1322,6 +1375,7 @@ let Phone = { IMEISV: null, IMSI: null, SMSC: null, + MSISDN: null, registrationState: {}, gprsRegistrationState: {}, @@ -1468,6 +1522,7 @@ let Phone = { this.requestNetworkInfo(); RIL.getSignalStrength(); RIL.getSMSCAddress(); + this.getMSISDN(); this.sendDOMMessage({type: "cardstatechange", cardState: GECKO_CARDSTATE_READY}); } @@ -1671,6 +1726,52 @@ let Phone = { this.IMEISV = imeiSV; }, + onICCIO: function onICCIO(options) { + switch (options.fileid) { + case ICC_EF_MSISDN: + this.readMSISDNResponse(options); + break; + } + }, + + readMSISDNResponse: function readMSISDNResponse(options) { + let sw1 = Buf.readUint32(); + let sw2 = Buf.readUint32(); + // See GSM11.11 section 9.4 for sw1 and sw2 + if (sw1 != STATUS_NORMAL_ENDING) { + // TODO: error + // Wait for fix for Bug 713451 to report error. + debug("Error in iccIO"); + } + if (DEBUG) debug("ICC I/O (" + sw1 + "/" + sw2 + ")"); + + switch (options.command) { + case ICC_COMMAND_GET_RESPONSE: + let response = Buf.readString(); + let recordSize = parseInt( + response.substr(RESPONSE_DATA_RECORD_LENGTH * 2, 2), 16) & 0xff; + let request = { + command: ICC_COMMAND_READ_RECORD, + fileid: ICC_EF_MSISDN, + pathid: EF_PATH_MF_SIM + EF_PATH_DF_TELECOM, + p1: 1, // Record number, MSISDN is always in the 1st record + p2: READ_RECORD_ABSOLUTE_MODE, + p3: recordSize, + data: null, + pin2: null, + }; + RIL.iccIO(request); + break; + + case ICC_COMMAND_READ_RECORD: + // Ignore 2 bytes prefix, which is 4 chars + let number = GsmPDUHelper.readStringAsBCD().toString().substr(4); + if (DEBUG) debug("MSISDN: " + number); + this.MSISDN = number; + break; + } + }, + onRegistrationState: function onRegistrationState(state) { let rs = this.registrationState; let stateChanged = false; @@ -2126,6 +2227,23 @@ let Phone = { RIL.getFailCauseCode(); }, + /** + * Get MSISDN + */ + getMSISDN: function getMSISDN() { + let request = { + command: ICC_COMMAND_GET_RESPONSE, + fileid: ICC_EF_MSISDN, + pathid: EF_PATH_MF_SIM + EF_PATH_DF_TELECOM, + p1: 0, // For GET_RESPONSE, p1 = 0 + p2: 0, // For GET_RESPONSE, p2 = 0 + p3: GET_RESPONSE_EF_SIZE_BYTES, + data: null, + pin2: null, + }; + RIL.iccIO(request); + }, + /** * Handle incoming messages from the main UI thread. * @@ -2164,6 +2282,13 @@ let Phone = { */ let GsmPDUHelper = { + /** + * List of tuples of national language identifier pairs. + */ + enabledGsmTableTuples: [ + [PDU_NL_IDENTIFIER_DEFAULT, PDU_NL_IDENTIFIER_DEFAULT], + ], + /** * Read one character (2 bytes) from a RIL string and decode as hex. * @@ -2247,6 +2372,8 @@ let GsmPDUHelper = { let number = 0; for (let i = 0; i < length; i++) { let octet = this.readHexOctet(); + if (octet == 0xff) + continue; // If the first nibble is an "F" , only the second nibble is to be taken // into account. if ((octet & 0xf0) == 0xf0) { @@ -2260,6 +2387,21 @@ let GsmPDUHelper = { return number; }, + /** + * Read a string from Buf and convert it to BCD + * + * @return the decimal as a number. + */ + readStringAsBCD: function readStringAsBCD() { + let length = Buf.readUint32(); + let bcd = this.readSwappedNibbleBCD(length / 2); + let delimiter = Buf.readUint16(); + if (!(length & 1)) { + delimiter |= Buf.readUint16(); + } + return bcd; + }, + /** * Write numerical data as swapped nibble BCD. * @@ -2283,66 +2425,145 @@ let GsmPDUHelper = { * * @param length * Number of septets to read (*not* octets) + * @param paddingBits + * Number of padding bits in the first byte of user data. + * @param langIndex + * Table index used for normal 7-bit encoded character lookup. + * @param langShiftIndex + * Table index used for escaped 7-bit encoded character lookup. * * @return a string. - * - * TODO: support other alphabets - * TODO: support escape chars */ - readSeptetsToString: function readSeptetsToString(length) { + readSeptetsToString: function readSeptetsToString(length, paddingBits, langIndex, langShiftIndex) { let ret = ""; - let byteLength = Math.ceil(length * 7 / 8); + let byteLength = Math.ceil((length * 7 + paddingBits) / 8); - let leftOver = 0; - for (let i = 0; i < byteLength; i++) { - let octet = this.readHexOctet(); - let shift = (i % 7); - let leftOver_mask = (0xff << (7 - shift)) & 0xff; - let septet_mask = (0xff >> (shift + 1)); - - let septet = ((octet & septet_mask) << shift) | leftOver; - ret += PDU_ALPHABET_7BIT_DEFAULT[septet]; - leftOver = (octet & leftOver_mask) >> (7 - shift); - - // Every 7th byte we have a whole septet left over that we can apply. - if (shift == 6) { - ret += PDU_ALPHABET_7BIT_DEFAULT[leftOver]; - leftOver = 0; - } + /** + * |<- last byte in header ->| + * |<- incompleteBits ->|<- last header septet->| + * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| + * + * |<- 1st byte in user data ->| + * |<- data septet 1 ->|<-paddingBits->| + * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| + * + * |<- 2nd byte in user data ->| + * |<- data spetet 2 ->|<-ds1->| + * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| + */ + let data = 0; + let dataBits = 0; + if (paddingBits) { + data = this.readHexOctet() >> paddingBits; + dataBits = 8 - paddingBits; + --byteLength; } + + let escapeFound = false; + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; + const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; + do { + // Read as much as fits in 32bit word + let bytesToRead = Math.min(byteLength, dataBits ? 3 : 4); + for (let i = 0; i < bytesToRead; i++) { + data |= this.readHexOctet() << dataBits; + dataBits += 8; + --byteLength; + } + + // Consume available full septets + for (; dataBits >= 7; dataBits -= 7) { + let septet = data & 0x7F; + data >>>= 7; + + if (escapeFound) { + escapeFound = false; + if (septet == PDU_NL_EXTENDED_ESCAPE) { + // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On + // receipt of this code, a receiving entity shall display a space + // until another extensiion table is defined." + ret += " "; + } else if (septet == PDU_NL_RESERVED_CONTROL) { + // According to 3GPP TS 23.038 B.2, "This code represents a control + // character and therefore must not be used for language specific + // characters." + ret += " "; + } else { + ret += langShiftTable[septet]; + } + } else if (septet == PDU_NL_EXTENDED_ESCAPE) { + escapeFound = true; + + // is not an effective character + --length; + } else { + ret += langTable[septet]; + } + } + } while (byteLength); + if (ret.length != length) { + /** + * If num of effective characters does not equal to the length of read + * string, cut the tail off. This happens when the last octet of user + * data has following layout: + * + * |<- penultimate octet in user data ->| + * |<- data septet N ->|<- dsN-1 ->| + * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| + * + * |<- last octet in user data ->| + * |<- fill bits ->|<-dsN->| + * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| + * + * The fill bits in the last octet may happen to form a full septet and + * be appended at the end of result string. + */ ret = ret.slice(0, length); } return ret; }, - writeStringAsSeptets: function writeStringAsSeptets(message) { - let right = 0; - for (let i = 0; i < message.length + 1; i++) { - let shift = (i % 8); - let septet; - if (i < message.length) { - septet = PDU_ALPHABET_7BIT_DEFAULT.indexOf(message[i]); - } else { - septet = 0; - } - if (septet == -1) { - if (DEBUG) debug("Fffff, " + message[i] + " not in 7 bit alphabet!"); - septet = 0; - } - if (shift == 0) { - // We're at the beginning of a cycle, but we need two septet values - // to make an octet. So we're going to have to sit this one out. - right = septet; + writeStringAsSeptets: function writeStringAsSeptets(message, paddingBits, langIndex, langShiftIndex) { + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; + const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; + + let dataBits = paddingBits; + let data = 0; + for (let i = 0; i < message.length; i++) { + let septet = langTable.indexOf(message[i]); + if (septet == PDU_NL_EXTENDED_ESCAPE) { continue; } - let left_mask = 0xff >> (8 - shift); - let right_mask = (0xff << shift) & 0xff; - let left = (septet & left_mask) << (8 - shift); - let octet = left | right; - this.writeHexOctet(left | right); - right = (septet & right_mask) >> shift; + if (septet >= 0) { + data |= septet << dataBits; + dataBits += 7; + } else { + septet = langShiftTable.indexOf(message[i]); + if (septet == -1) { + throw new Error(message[i] + " not in 7 bit alphabet " + + langIndex + ":" + langShiftIndex + "!"); + } + + if (septet == PDU_NL_RESERVED_CONTROL) { + continue; + } + + data |= PDU_NL_EXTENDED_ESCAPE << dataBits; + dataBits += 7; + data |= septet << dataBits; + dataBits += 7; + } + + for (; dataBits >= 8; dataBits -= 8) { + this.writeHexOctet(data & 0xFF); + data >>>= 8; + } + } + + if (dataBits != 0) { + this.writeHexOctet(data & 0xFF); } }, @@ -2381,37 +2602,231 @@ let GsmPDUHelper = { } }, + /** + * Calculate encoded length using specified locking/single shift table + * + * @param message + * message string to be encoded. + * @param langTable + * locking shift table string. + * @param langShiftTable + * single shift table string. + * + * @note that the algorithm used in this function must match exactly with + * #writeStringAsSeptets. + */ + _calculateLangEncodedLength: function _calculateLangEncodedLength(message, langTable, langShiftTable) { + let length = 0; + for (let msgIndex = 0; msgIndex < message.length; msgIndex++) { + let septet = langTable.indexOf(message.charAt(msgIndex)); + + // According to 3GPP TS 23.038, section 6.1.1 General notes, "The + // characters marked '1)' are not used but are displayed as a space." + if (septet == PDU_NL_EXTENDED_ESCAPE) { + continue; + } + + if (septet >= 0) { + length++; + continue; + } + + septet = langShiftTable.indexOf(message.charAt(msgIndex)); + if (septet == -1) { + return -1; + } + + // According to 3GPP TS 23.038 B.2, "This code represents a control + // character and therefore must not be used for language specific + // characters." + if (septet == PDU_NL_RESERVED_CONTROL) { + continue; + } + + // The character is not found in locking shfit table, but could be + // encoded as with single shift table. Note that it's + // still possible for septet to has the value of PDU_NL_EXTENDED_ESCAPE, + // but we can display it as a space in this case as said in previous + // comment. + length += 2; + } + + return length; + }, + /** * Calculate user data length and its encoding. * * The `options` parameter object should contain the `body` attribute, and - * the `dcs`, `bodyLengthInOctets` attributes will be set as return: + * the `dcs`, `userDataHeaderLength`, `encodedBodyLength`, `langIndex`, + * `langShiftIndex` attributes will be set as return: * * @param body * String containing the message body. * @param dcs * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET * constants. - * @param bodyLengthInOctets - * Byte length of the message body when encoded with the given DCS. + * @param userDataHeaderLength + * Length of embedded user data header, in bytes. The whole header + * size will be userDataHeaderLength + 1; 0 for no header. + * @param encodedBodyLength + * Length of the message body when encoded with the given DCS. For + * UCS2, in bytes; for 7-bit, in septets. + * @param langIndex + * Table index used for normal 7-bit encoded character lookup. + * @param langShiftIndex + * Table index used for escaped 7-bit encoded character lookup. */ calculateUserDataLength: function calculateUserDataLength(options) { - //TODO: support language tables, see bug 729876 //TODO: support multipart SMS, see bug 712933 - let needUCS2 = false; - for (let i = 0; i < options.body.length; ++i) { - if (options.body.charCodeAt(i) >= 128) { - needUCS2 = true; - break; + options.dcs = PDU_DCS_MSG_CODING_7BITS_ALPHABET; + options.langIndex = PDU_NL_IDENTIFIER_DEFAULT; + options.langShiftIndex = PDU_NL_IDENTIFIER_DEFAULT; + options.encodedBodyLength = 0; + options.userDataHeaderLength = 0; + + let needUCS2 = true; + let minUserDataLength = Number.MAX_VALUE; + for (let i = 0; i < this.enabledGsmTableTuples.length; i++) { + let [langIndex, langShiftIndex] = this.enabledGsmTableTuples[i]; + + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; + const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; + + let length = this._calculateLangEncodedLength(options.body, + langTable, + langShiftTable); + if (length < 0) { + continue; + } + + let headerLen = 0; + if (langIndex != PDU_NL_IDENTIFIER_DEFAULT) { + headerLen += 3; // IEI + len + langIndex + } + if (langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) { + headerLen += 3; // IEI + len + langShiftIndex + } + + // Calculate full user data length, note the extra byte is for header len + let userDataLength = length + (headerLen ? headerLen + 1 : 0); + if (userDataLength >= minUserDataLength) { + continue; + } + + needUCS2 = false; + minUserDataLength = userDataLength; + + options.encodedBodyLength = length; + options.userDataHeaderLength = headerLen; + options.langIndex = langIndex; + options.langShiftIndex = langShiftIndex; + + if (userDataLength <= options.body.length) { + // Found minimum user data length already + return; } } if (needUCS2) { options.dcs = PDU_DCS_MSG_CODING_16BITS_ALPHABET; - options.bodyLengthInOctets = options.body.length * 2; - } else { - options.dcs = PDU_DCS_MSG_CODING_7BITS_ALPHABET; - options.bodyLengthInOctets = Math.ceil(options.body.length * 7 / 8); + options.encodedBodyLength = options.body.length * 2; + options.userDataHeaderLength = 0; + } + }, + + /** + * Read 1 + UDHL octets and construct user data header at return. + * + * @return A header object with properties contained in received message. + * The properties set include: + *
    + *
  • length: totoal length of the header, default 0. + *
  • langIndex: used locking shift table index, default + * PDU_NL_IDENTIFIER_DEFAULT. + *
  • langShiftIndex: used locking shift table index, default + * PDU_NL_IDENTIFIER_DEFAULT. + *
+ */ + readUserDataHeader: function readUserDataHeader() { + let header = { + length: 0, + langIndex: PDU_NL_IDENTIFIER_DEFAULT, + langShiftIndex: PDU_NL_IDENTIFIER_DEFAULT + }; + + header.length = this.readHexOctet(); + let dataAvailable = header.length; + while (dataAvailable >= 2) { + let id = this.readHexOctet(); + let length = this.readHexOctet(); + dataAvailable -= 2; + + switch (id) { + case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: + let langShiftIndex = this.readHexOctet(); + --dataAvailable; + if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) { + header.langShiftIndex = langShiftIndex; + } + break; + case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: + let langIndex = this.readHexOctet(); + --dataAvailable; + if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) { + header.langIndex = langIndex; + } + break; + default: + if (DEBUG) { + debug("readUserDataHeader: unsupported IEI(" + id + + "), " + length + " bytes."); + } + + // Read out unsupported data + if (length) { + let octets; + if (DEBUG) octets = new Uint8Array(length); + + for (let i = 0; i < length; i++) { + let octet = this.readHexOctet(); + if (DEBUG) octets[i] = octet; + } + dataAvailable -= length; + + if (DEBUG) debug("readUserDataHeader: " + Array.slice(octets)); + } + break; + } + } + + if (dataAvailable != 0) { + throw new Error("Illegal user data header found!"); + } + + return header; + }, + + /** + * Write out user data header. + * + * @param options + * Options containing information for user data header write-out. The + * `userDataHeaderLength` property must be correctly pre-calculated. + */ + writeUserDataHeader: function writeUserDataHeader(options) { + this.writeHexOctet(options.userDataHeaderLength); + + if (options.langIndex != PDU_NL_IDENTIFIER_DEFAULT) { + this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT); + this.writeHexOctet(1); + this.writeHexOctet(options.langIndex); + } + + if (options.langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) { + this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT); + this.writeHexOctet(1); + this.writeHexOctet(options.langShiftIndex); } }, @@ -2419,7 +2834,7 @@ let GsmPDUHelper = { * User data can be 7 bit (default alphabet) data, 8 bit data, or 16 bit * (UCS2) data. */ - readUserData: function readUserData(length, codingScheme) { + readUserData: function readUserData(length, codingScheme, hasHeader) { if (DEBUG) { debug("Reading " + length + " bytes of user data."); debug("Coding scheme: " + codingScheme); @@ -2456,6 +2871,22 @@ let GsmPDUHelper = { break; } + let header; + let paddingBits = 0; + if (hasHeader) { + header = this.readUserDataHeader(); + + if (encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { + let headerBits = (header.length + 1) * 8; + let headerSeptets = Math.ceil(headerBits / 7); + + length -= headerSeptets; + paddingBits = headerSeptets * 7 - headerBits; + } else { + length -= (header.length + 1); + } + } + if (DEBUG) debug("PDU: message encoding is " + encoding + " bit."); switch (encoding) { case PDU_DCS_MSG_CODING_7BITS_ALPHABET: @@ -2465,7 +2896,11 @@ let GsmPDUHelper = { if (DEBUG) debug("PDU error: user data is too long: " + length); return null; } - return this.readSeptetsToString(length); + + return this.readSeptetsToString(length, + paddingBits, + hasHeader ? header.langIndex : PDU_NL_IDENTIFIER_DEFAULT, + hasHeader ? header.langShiftIndex : PDU_NL_IDENTIFIER_DEFAULT); case PDU_DCS_MSG_CODING_8BITS_ALPHABET: // Unsupported. return null; @@ -2505,6 +2940,10 @@ let GsmPDUHelper = { // First octet of this SMS-DELIVER or SMS-SUBMIT message let firstOctet = this.readHexOctet(); + + // User data header indicator + let hasUserDataHeader = firstOctet & PDU_UDHI; + // if the sms is of SMS-SUBMIT type it would contain a TP-MR let isSmsSubmit = firstOctet & PDU_MTI_SMS_SUBMIT; if (isSmsSubmit) { @@ -2560,10 +2999,10 @@ let GsmPDUHelper = { // - TP-Service-Center-Time-Stamp - let year = this.readSwappedNibbleBCD(1) + PDU_TIMESTAMP_YEAR_OFFSET; let month = this.readSwappedNibbleBCD(1) - 1; - let day = this.readSwappedNibbleBCD(1) - 1; - let hour = this.readSwappedNibbleBCD(1) - 1; - let minute = this.readSwappedNibbleBCD(1) - 1; - let second = this.readSwappedNibbleBCD(1) - 1; + let day = this.readSwappedNibbleBCD(1); + let hour = this.readSwappedNibbleBCD(1); + let minute = this.readSwappedNibbleBCD(1); + let second = this.readSwappedNibbleBCD(1); msg.timestamp = Date.UTC(year, month, day, hour, minute, second); // If the most significant bit of the least significant nibble is 1, @@ -2582,7 +3021,9 @@ let GsmPDUHelper = { // - TP-User-Data - if (userDataLength > 0) { - msg.body = this.readUserData(userDataLength, dataCodingScheme); + msg.body = this.readUserData(userDataLength, + dataCodingScheme, + hasUserDataHeader); } return msg; @@ -2602,13 +3043,29 @@ let GsmPDUHelper = { * @param dcs * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET * constants. - * @param userDataLengthInOctets - * Byte length of the user data when encoded with the given DCS. + * @param userDataHeaderLength + * Length of embedded user data header, in bytes. The whole header + * size will be userDataHeaderLength + 1; 0 for no header. + * @param encodedBodyLength + * Length of the user data when encoded with the given DCS. For UCS2, + * in bytes; for 7-bit, in septets. + * @param langIndex + * Table index used for normal 7-bit encoded character lookup. + * @param langShiftIndex + * Table index used for escaped 7-bit encoded character lookup. */ - writeMessage: function writeMessage(address, - userData, - dcs, - userDataLengthInOctets) { + writeMessage: function writeMessage(options) { + if (DEBUG) { + debug("writeMessage: " + JSON.stringify(options)); + } + let address = options.number; + let body = options.body; + let dcs = options.dcs; + let userDataHeaderLength = options.userDataHeaderLength; + let encodedBodyLength = options.encodedBodyLength; + let langIndex = options.langIndex; + let langShiftIndex = options.langShiftIndex; + // SMS-SUBMIT Format: // // PDU Type - 1 octet @@ -2628,6 +3085,20 @@ let GsmPDUHelper = { //TODO validity is unsupported for now let validity = 0; + let headerOctets = (userDataHeaderLength ? userDataHeaderLength + 1 : 0); + let paddingBits; + let userDataLengthInSeptets; + let userDataLengthInOctets; + if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { + let headerSeptets = Math.ceil(headerOctets * 8 / 7); + userDataLengthInSeptets = headerSeptets + encodedBodyLength; + userDataLengthInOctets = Math.ceil(userDataLengthInSeptets * 7 / 8); + paddingBits = headerSeptets * 7 - headerOctets * 8; + } else { + userDataLengthInOctets = headerOctets + encodedBodyLength; + paddingBits = 0; + } + let pduOctetLength = 4 + // PDU Type, Message Ref, address length + format Math.ceil(address.length / 2) + 3 + // PID, DCS, UDL @@ -2672,8 +3143,8 @@ let GsmPDUHelper = { if (validity) { //TODO: not supported yet, OR with one of PDU_VPF_* } - let udhi = ""; //TODO: for now this is unsupported - if (udhi) { + // User data header indicator + if (headerOctets) { firstOctet |= PDU_UDHI; } this.writeHexOctet(firstOctet); @@ -2699,20 +3170,25 @@ let GsmPDUHelper = { } // - User Data - - let userDataLength = userData.length; - if (dcs == PDU_DCS_MSG_CODING_16BITS_ALPHABET) { - userDataLength = userData.length * 2; + if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { + this.writeHexOctet(userDataLengthInSeptets); + } else { + this.writeHexOctet(userDataLengthInOctets); } - this.writeHexOctet(userDataLength); + + if (headerOctets) { + this.writeUserDataHeader(options); + } + switch (dcs) { case PDU_DCS_MSG_CODING_7BITS_ALPHABET: - this.writeStringAsSeptets(userData); + this.writeStringAsSeptets(body, paddingBits, langIndex, langShiftIndex); break; case PDU_DCS_MSG_CODING_8BITS_ALPHABET: // Unsupported. break; case PDU_DCS_MSG_CODING_16BITS_ALPHABET: - this.writeUCS2String(userData); + this.writeUCS2String(body); break; } diff --git a/dom/system/b2g/tests/test_ril_worker_sms.js b/dom/system/b2g/tests/test_ril_worker_sms.js new file mode 100644 index 000000000000..00137d865b2a --- /dev/null +++ b/dom/system/b2g/tests/test_ril_worker_sms.js @@ -0,0 +1,438 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +const ESCAPE = "\uffff"; +const RESCTL = "\ufffe"; +const LF = "\n"; +const CR = "\r"; +const SP = " "; +const FF = "\u000c"; + +function run_test() { + run_next_test(); +} + +/** + * Verify validity of the national language tables + */ +add_test(function test_nl_locking_shift_tables_validity() { + for (let lst = 0; lst < PDU_NL_LOCKING_SHIFT_TABLES.length; lst++) { + do_print("Verifying PDU_NL_LOCKING_SHIFT_TABLES[" + lst + "]"); + + let table = PDU_NL_LOCKING_SHIFT_TABLES[lst]; + + // Make sure table length is 128, or it will break table lookup algorithm. + do_check_eq(table.length, 128); + + // Make sure special values are preserved. + do_check_eq(table[PDU_NL_EXTENDED_ESCAPE], ESCAPE); + do_check_eq(table[PDU_NL_LINE_FEED], LF); + do_check_eq(table[PDU_NL_CARRIAGE_RETURN], CR); + do_check_eq(table[PDU_NL_SPACE], SP); + } + + run_next_test(); +}); + +add_test(function test_nl_single_shift_tables_validity() { + for (let sst = 0; sst < PDU_NL_SINGLE_SHIFT_TABLES.length; sst++) { + do_print("Verifying PDU_NL_SINGLE_SHIFT_TABLES[" + sst + "]"); + + let table = PDU_NL_SINGLE_SHIFT_TABLES[sst]; + + // Make sure table length is 128, or it will break table lookup algorithm. + do_check_eq(table.length, 128); + + // Make sure special values are preserved. + do_check_eq(table[PDU_NL_EXTENDED_ESCAPE], ESCAPE); + do_check_eq(table[PDU_NL_PAGE_BREAK], FF); + do_check_eq(table[PDU_NL_RESERVED_CONTROL], RESCTL); + } + + run_next_test(); +}); + +/** + * Verify GsmPDUHelper#_calculateLangEncodedLength() and + * GsmPDUHelper#writeStringAsSeptets() algorithm match each other. + */ +add_test(function test_GsmPDUHelper__calculateLangEncodedLength() { + let worker = newWorker({ + postRILMessage: function fakePostRILMessage(data) { + // Do nothing + }, + postMessage: function fakePostMessage(message) { + // Do nothing + } + }); + + let helper = worker.GsmPDUHelper; + helper.resetOctetWritten = function () { + helper.octetsWritten = 0; + }; + helper.writeHexOctet = function () { + helper.octetsWritten++; + }; + + function do_check_calc(str, expectedCalcLen, lst, sst) { + do_check_eq(expectedCalcLen, + helper._calculateLangEncodedLength(str, + PDU_NL_LOCKING_SHIFT_TABLES[lst], + PDU_NL_SINGLE_SHIFT_TABLES[sst])); + + helper.resetOctetWritten(); + helper.writeStringAsSeptets(str, 0, lst, sst); + do_check_eq(Math.ceil(expectedCalcLen * 7 / 8), helper.octetsWritten); + } + + // Test calculation encoded message length using both locking/single shift tables. + for (let lst = 0; lst < PDU_NL_LOCKING_SHIFT_TABLES.length; lst++) { + let langTable = PDU_NL_LOCKING_SHIFT_TABLES[lst]; + + let str = langTable.substring(0, PDU_NL_EXTENDED_ESCAPE) + + langTable.substring(PDU_NL_EXTENDED_ESCAPE + 1); + + for (let sst = 0; sst < PDU_NL_SINGLE_SHIFT_TABLES.length; sst++) { + let langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[sst]; + + // , should be ignored. + do_check_calc(ESCAPE + RESCTL, 0, lst, sst); + + // Characters defined in locking shift table should be encoded directly. + do_check_calc(str, str.length, lst, sst); + + let [str1, str2] = ["", ""]; + for (let i = 0; i < langShiftTable.length; i++) { + if ((i == PDU_NL_EXTENDED_ESCAPE) || (i == PDU_NL_RESERVED_CONTROL)) { + continue; + } + + let c = langShiftTable[i]; + if (langTable.indexOf(c) >= 0) { + str1 += c; + } else { + str2 += c; + } + } + + // Characters found in both locking/single shift tables should be + // directly encoded. + do_check_calc(str1, str1.length, lst, sst); + + // Characters found only in single shift tables should be encoded as + // , therefore doubles its original length. + do_check_calc(str2, str2.length * 2, lst, sst); + } + } + + run_next_test(); +}); + +/** + * Verify GsmPDUHelper#calculateUserDataLength handles national language + * selection correctly. + */ +add_test(function test_GsmPDUHelper_calculateUserDataLength() { + let worker = newWorker({ + postRILMessage: function fakePostRILMessage(data) { + // Do nothing + }, + postMessage: function fakePostMessage(message) { + // Do nothing + } + }); + + let helper = worker.GsmPDUHelper; + let calc = helper.calculateUserDataLength; + function test_calc(str, expected, enabledGsmTableTuples) { + helper.enabledGsmTableTuples = enabledGsmTableTuples; + let options = {body: str}; + calc.call(helper, options); + + do_check_eq(expected[0], options.dcs); + do_check_eq(expected[1], options.encodedBodyLength); + do_check_eq(expected[2], options.userDataHeaderLength); + do_check_eq(expected[3], options.langIndex); + do_check_eq(expected[4], options.langShiftIndex); + } + + // Test UCS fallback + // - No any default enabled nl tables + test_calc("A", [PDU_DCS_MSG_CODING_16BITS_ALPHABET, 2, 0, 0, 0], []); + // - Character not defined in enabled nl tables + test_calc("A", [PDU_DCS_MSG_CODING_16BITS_ALPHABET, 2, 0, 0, 0], [[2, 2]]); + + // With GSM default nl tables + test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 0, 0, 0], [[0, 0]]); + // - SP is defined in both locking/single shift tables, should be directly + // encoded. + test_calc(SP, [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 0, 0, 0], [[0, 0]]); + // - '^' is only defined in single shift table, should be encoded as + // ^. + test_calc("^", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 2, 0, 0, 0], [[0, 0]]); + + // Test userDataHeaderLength calculation + // - Header contains both IEIs + test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 6, 1, 1], [[1, 1]]); + // - Header contains only locking shift table IEI + test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 3, 1, 0], [[1, 0]]); + // - Header contains only single shift table IEI + test_calc("^", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 2, 3, 0, 1], [[0, 1]]); + + // Test minimum cost nl tables selection + // - 'A' is defined in locking shift table + test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 3, 1, 0], [[1, 0], [2, 0]]); + test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 3, 1, 0], [[2, 0], [1, 0]]); + // - 'A' is defined in single shift table + test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 2, 6, 2, 4], [[2, 0], [2, 4]]); + test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 2, 6, 2, 4], [[2, 4], [2, 0]]); + // - 'A' is defined in locking shift table of one tuple and in single shift + // table of another. + test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 3, 1, 0], [[1, 0], [2, 4]]); + test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 3, 1, 0], [[2, 4], [1, 0]]); + + run_next_test(); +}); + +/** + * Verify GsmPDUHelper#writeStringAsSeptets() padding bits handling. + */ +add_test(function test_GsmPDUHelper_writeStringAsSeptets() { + let worker = newWorker({ + postRILMessage: function fakePostRILMessage(data) { + // Do nothing + }, + postMessage: function fakePostMessage(message) { + // Do nothing + } + }); + + let helper = worker.GsmPDUHelper; + helper.resetOctetWritten = function () { + helper.octetsWritten = 0; + }; + helper.writeHexOctet = function () { + helper.octetsWritten++; + }; + + let base = "AAAAAAAA"; // Base string of 8 characters long + for (let len = 0; len < 8; len++) { + let str = base.substring(0, len); + + for (let paddingBits = 0; paddingBits < 8; paddingBits++) { + do_print("Verifying GsmPDUHelper.writeStringAsSeptets(" + + str + ", " + paddingBits + ", , )"); + helper.resetOctetWritten(); + helper.writeStringAsSeptets(str, paddingBits, PDU_NL_IDENTIFIER_DEFAULT, + PDU_NL_IDENTIFIER_DEFAULT); + do_check_eq(Math.ceil(((len * 7) + paddingBits) / 8), + helper.octetsWritten); + } + } + + run_next_test(); +}); + +/** + * Verify receiving SMS-DELIVERY messages + */ + +function hexToNibble(nibble) { + nibble &= 0x0f; + if (nibble < 10) { + nibble += 48; // ASCII '0' + } else { + nibble += 55; // ASCII 'A' + } + return nibble; +} + +function pduToParcelData(pdu) { + let dataLength = 4 + pdu.length * 4 + 4; + let data = new Uint8Array(dataLength); + let offset = 0; + + // String length + data[offset++] = pdu.length & 0xFF; + data[offset++] = (pdu.length >> 8) & 0xFF; + data[offset++] = (pdu.length >> 16) & 0xFF; + data[offset++] = (pdu.length >> 24) & 0xFF; + + // PDU data + for (let i = 0; i < pdu.length; i++) { + let hi = (pdu[i] >>> 4) & 0x0F; + let lo = pdu[i] & 0x0F; + + data[offset++] = hexToNibble(hi); + data[offset++] = 0; + data[offset++] = hexToNibble(lo); + data[offset++] = 0; + } + + // String delimitor + data[offset++] = 0; + data[offset++] = 0; + data[offset++] = 0; + data[offset++] = 0; + + return data; +} + +function compose7bitPdu(lst, sst, data, septets) { + if ((lst == 0) && (sst == 0)) { + return [0x00, // SMSC + PDU_MTI_SMS_DELIVER, // firstOctet + 1, 0x00, 0, // senderAddress + 0x00, // protocolIdentifier + PDU_DCS_MSG_CODING_7BITS_ALPHABET, // dataCodingScheme + 0, 0, 0, 0, 0, 0, 0, // y m d h m s tz + septets] // userDataLength + .concat(data); + } + + return [0x00, // SMSC + PDU_MTI_SMS_DELIVER | PDU_UDHI, // firstOctet + 1, 0x00, 0, // senderAddress + 0x00, // protocolIdentifier + PDU_DCS_MSG_CODING_7BITS_ALPHABET, // dataCodingScheme + 0, 0, 0, 0, 0, 0, 0, // y m d h m s tz + 8 + septets, // userDataLength + 6, // user data header length + PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT, 1, lst, // PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT + PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT, 1, sst] // PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT + .concat(data); +} + +function composeUcs2Pdu(rawBytes) { + return [0x00, // SMSC + PDU_MTI_SMS_DELIVER, // firstOctet + 1, 0x00, 0, // senderAddress + 0x00, // protocolIdentifier + PDU_DCS_MSG_CODING_16BITS_ALPHABET, // dataCodingScheme + 0, 0, 0, 0, 0, 0, 0, // y m d h m s tz + rawBytes.length] // userDataLength + .concat(rawBytes); +} + +function newSmsParcel(pdu) { + return newIncomingParcel(-1, + RESPONSE_TYPE_UNSOLICITED, + UNSOLICITED_RESPONSE_NEW_SMS, + pduToParcelData(pdu)); +} + +function removeSpecialChar(str, needle) { + for (let i = 0; i < needle.length; i++) { + let pos; + while ((pos = str.indexOf(needle[i])) >= 0) { + str = str.substring(0, pos) + str.substring(pos + 1); + } + } + return str; +} + +function newWriteHexOctetAsUint8Worker() { + let worker = newWorker({ + postRILMessage: function fakePostRILMessage(data) { + // Do nothing + }, + postMessage: function fakePostMessage(message) { + // Do nothing + } + }); + + worker.GsmPDUHelper.writeHexOctet = function (value) { + worker.Buf.writeUint8(value); + }; + + return worker; +} + +function add_test_receiving_sms(expected, pdu) { + add_test(function test_receiving_sms() { + let worker = newWorker({ + postRILMessage: function fakePostRILMessage(data) { + // Do nothing + }, + postMessage: function fakePostMessage(message) { + do_print("body: " + message.body); + do_check_eq(expected, message.body) + } + }); + + do_print("expect: " + expected); + do_print("pdu: " + pdu); + worker.onRILMessage(newSmsParcel(pdu)); + + run_next_test(); + }); +} + +function test_receiving_7bit_alphabets(lst, sst) { + let worker = newWriteHexOctetAsUint8Worker(); + let helper = worker.GsmPDUHelper; + let buf = worker.Buf; + + function get7bitRawBytes(expected) { + buf.outgoingIndex = 0; + helper.writeStringAsSeptets(expected, 0, lst, sst); + + let subArray = buf.outgoingBytes.subarray(0, buf.outgoingIndex); + return Array.slice(subArray); + } + + let langTable = PDU_NL_LOCKING_SHIFT_TABLES[lst]; + let langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[sst]; + + let text = removeSpecialChar(langTable + langShiftTable, ESCAPE + RESCTL); + for (let i = 0; i < text.length;) { + let len = Math.min(70, text.length - i); + let expected = text.substring(i, i + len); + let septets = helper._calculateLangEncodedLength(expected, langTable, + langShiftTable); + let rawBytes = get7bitRawBytes(expected); + let pdu = compose7bitPdu(lst, sst, rawBytes, septets); + add_test_receiving_sms(expected, pdu); + + i += len; + } +} + +function test_receiving_ucs2_alphabets(text) { + let worker = newWriteHexOctetAsUint8Worker(); + let buf = worker.Buf; + + function getUCS2RawBytes(expected) { + buf.outgoingIndex = 0; + worker.GsmPDUHelper.writeUCS2String(expected); + + let subArray = buf.outgoingBytes.subarray(0, buf.outgoingIndex); + return Array.slice(subArray); + } + + for (let i = 0; i < text.length;) { + let len = Math.min(70, text.length - i); + let expected = text.substring(i, i + len); + let rawBytes = getUCS2RawBytes(expected); + let pdu = composeUcs2Pdu(rawBytes); + add_test_receiving_sms(expected, pdu); + + i += len; + } +} + +let ucs2str = ""; +for (let lst = 0; lst < PDU_NL_LOCKING_SHIFT_TABLES.length; lst++) { + ucs2str += PDU_NL_LOCKING_SHIFT_TABLES[lst]; + for (let sst = 0; sst < PDU_NL_SINGLE_SHIFT_TABLES.length; sst++) { + test_receiving_7bit_alphabets(lst, sst); + + if (lst == 0) { + ucs2str += PDU_NL_SINGLE_SHIFT_TABLES[sst]; + } + } +} +test_receiving_ucs2_alphabets(ucs2str); + diff --git a/dom/system/b2g/tests/xpcshell.ini b/dom/system/b2g/tests/xpcshell.ini index ec58d374a5b9..fa5b5e068ed3 100644 --- a/dom/system/b2g/tests/xpcshell.ini +++ b/dom/system/b2g/tests/xpcshell.ini @@ -3,3 +3,4 @@ head = header_helpers.js tail = [test_ril_worker_buf.js] +[test_ril_worker_sms.js] diff --git a/dom/tests/mochitest/localstorage/test_localStorageBase.html b/dom/tests/mochitest/localstorage/test_localStorageBase.html index 4632fe9cc421..630702755f73 100644 --- a/dom/tests/mochitest/localstorage/test_localStorageBase.html +++ b/dom/tests/mochitest/localstorage/test_localStorageBase.html @@ -100,7 +100,7 @@ function startTest() var secondKey = localStorage.key(1); ok((firstKey == 'key1' && secondKey == 'key2') || (firstKey == 'key2' && secondKey == 'key1'), - 'Both keys should be present.'); + 'key() API works.'); // change the second key localStorage.setItem("key2", "value2-2"); diff --git a/dom/tests/mochitest/localstorage/test_localStorageBasePrivateBrowsing.html b/dom/tests/mochitest/localstorage/test_localStorageBasePrivateBrowsing.html index 2122b9306b6e..da96897abaa8 100644 --- a/dom/tests/mochitest/localstorage/test_localStorageBasePrivateBrowsing.html +++ b/dom/tests/mochitest/localstorage/test_localStorageBasePrivateBrowsing.html @@ -119,7 +119,7 @@ function doTest() var secondKey = localStorage.key(1); ok((firstKey == 'key1' && secondKey == 'key2') || (firstKey == 'key2' && secondKey == 'key1'), - 'Both keys should be present.'); + 'key() API works.'); // change the second key localStorage.setItem("key2", "value2-2"); diff --git a/dom/tests/mochitest/localstorage/test_localStorageBaseSessionOnly.html b/dom/tests/mochitest/localstorage/test_localStorageBaseSessionOnly.html index 1fded5cda0e5..e913d434e227 100644 --- a/dom/tests/mochitest/localstorage/test_localStorageBaseSessionOnly.html +++ b/dom/tests/mochitest/localstorage/test_localStorageBaseSessionOnly.html @@ -110,7 +110,7 @@ function startTest() var secondKey = localStorage.key(1); ok((firstKey == 'key1' && secondKey == 'key2') || (firstKey == 'key2' && secondKey == 'key1'), - 'Both keys should be present.'); + 'key() API works.'); // change the second key localStorage.setItem("key2", "value2-2"); diff --git a/dom/tests/mochitest/sessionstorage/test_sessionStorageBase.html b/dom/tests/mochitest/sessionstorage/test_sessionStorageBase.html index abad6d6b2263..ef57100f2deb 100644 --- a/dom/tests/mochitest/sessionstorage/test_sessionStorageBase.html +++ b/dom/tests/mochitest/sessionstorage/test_sessionStorageBase.html @@ -102,7 +102,7 @@ function startTest() var secondKey = sessionStorage.key(1); ok((firstKey == 'key1' && secondKey == 'key2') || (firstKey == 'key2' && secondKey == 'key1'), - 'Both keys should be present.'); + 'key() API works.'); // change the second key sessionStorage.setItem("key2", "value2-2"); diff --git a/dom/wifi/nsWifiWorker.js b/dom/wifi/nsWifiWorker.js index 7e81ba0621e5..872c1ebaa47a 100644 --- a/dom/wifi/nsWifiWorker.js +++ b/dom/wifi/nsWifiWorker.js @@ -434,20 +434,27 @@ var WifiManager = (function() { }); } + var dhcpInfo = null; function runDhcp(ifname, callback) { controlMessage({ cmd: "dhcp_do_request", ifname: ifname }, function(data) { + if (!data.status) + dhcpInfo = data; callback(data.status ? null : data); }); } function stopDhcp(ifname, callback) { controlMessage({ cmd: "dhcp_stop", ifname: ifname }, function(data) { + if (!data.status) + dhcpInfo = null; callback(!data.status); }); } function releaseDhcpLease(ifname, callback) { controlMessage({ cmd: "dhcp_release_lease", ifname: ifname }, function(data) { + if (!data.status) + dhcpInfo = null; callback(!data.status); }); } @@ -468,6 +475,8 @@ var WifiManager = (function() { function runDhcpRenew(ifname, callback) { controlMessage({ cmd: "dhcp_do_request", ifname: ifname }, function(data) { + if (!data.status) + dhcpInfo = data; callback(data.status ? null : data); }); } @@ -486,6 +495,12 @@ var WifiManager = (function() { function notifyStateChange(fields) { fields.prevState = manager.state; manager.state = fields.state; + + // If we got disconnected, kill the DHCP client in preparation for + // reconnection. + if (fields.state === "DISCONNECTED" && dhcpInfo) + stopDhcp(manager.ifname, function() {}); + notify("statechange", fields); } diff --git a/dom/workers/FileReaderSyncPrivate.cpp b/dom/workers/FileReaderSyncPrivate.cpp index 082ad2253bf8..cdec85411a5e 100644 --- a/dom/workers/FileReaderSyncPrivate.cpp +++ b/dom/workers/FileReaderSyncPrivate.cpp @@ -45,7 +45,7 @@ #include "nsDOMClassInfoID.h" #include "nsDOMError.h" #include "nsIDOMFile.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "nsICharsetDetector.h" #include "nsIConverterInputStream.h" #include "nsIInputStream.h" @@ -135,12 +135,8 @@ FileReaderSyncPrivate::ReadAsText(nsIDOMBlob* aBlob, CopyUTF16toUTF8(aEncoding, charsetGuess); } - nsCOMPtr alias = - do_GetService(NS_CHARSETALIAS_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - nsCString charset; - rv = alias->GetPreferred(charsetGuess, charset); + rv = nsCharsetAlias::GetPreferred(charsetGuess, charset); NS_ENSURE_SUCCESS(rv, rv); return ConvertStream(stream, charset.get(), aResult); diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index e64ef2d9592f..cc3d5be6cac0 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -85,9 +85,14 @@ using namespace mozilla::xpconnect::memory; // consistency. #define WORKER_STACK_SIZE 256 * sizeof(size_t) * 1024 -// The stack limit the JS engine will check. Half the size of the -// actual C stack, to be safe. +// The stack limit the JS engine will check. +#ifdef MOZ_ASAN +// For ASan, we need more stack space, so we use all that is available +#define WORKER_CONTEXT_NATIVE_STACK_LIMIT WORKER_STACK_SIZE +#else +// Half the size of the actual C stack, to be safe. #define WORKER_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024 +#endif // The maximum number of threads to use for workers, overridable via pref. #define MAX_WORKERS_PER_DOMAIN 10 diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index cce8087b8b13..811ee8c9a758 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -528,8 +528,8 @@ private: #ifdef ANDROID __android_log_print(ANDROID_LOG_INFO, "Gecko", buffer.ptr()); #endif - fputs(buffer.ptr(), stderr); - fflush(stderr); + fputs(buffer.ptr(), stdout); + fflush(stdout); } return true; diff --git a/dom/workers/test/importScripts_worker.js b/dom/workers/test/importScripts_worker.js index e14e934660fa..7176ce8386b3 100644 --- a/dom/workers/test/importScripts_worker.js +++ b/dom/workers/test/importScripts_worker.js @@ -20,9 +20,9 @@ function tryBadScripts() { // Throws an exception "importScripts_worker_imported4.js", // Shouldn't exist! - "http://flippety.com/floppety/foo.js", + "http://example.com/non-existing/importScripts_worker_foo.js", // Not a valid url - "http://flippety::foo_js ftw" + "http://notadomain::notafile aword" ]; for (var i = 0; i < badScripts.length; i++) { diff --git a/editor/libeditor/html/tests/test_bug674770-2.html b/editor/libeditor/html/tests/test_bug674770-2.html index 5630b0d3baeb..926449238699 100644 --- a/editor/libeditor/html/tests/test_bug674770-2.html +++ b/editor/libeditor/html/tests/test_bug674770-2.html @@ -49,6 +49,7 @@ function clickEventHnalder(aEvent) // are a lot of functions and SimpleTest.executeSoon()s. SimpleTest.waitForFocus(function() { + SpecialPowers.setBoolPref("middlemouse.contentLoadURL", false); SpecialPowers.setBoolPref("middlemouse.paste", true); frameWindow = iframe.contentWindow; @@ -384,7 +385,9 @@ function runBodyEditableDocumentTests2() function cleanup() { + SpecialPowers.clearUserPref("middlemouse.contentLoadURL"); SpecialPowers.clearUserPref("middlemouse.paste"); + SimpleTest.finish(); } diff --git a/embedding/android/GeckoAppShell.java b/embedding/android/GeckoAppShell.java index 499eed4f80b0..683f3831cdf1 100644 --- a/embedding/android/GeckoAppShell.java +++ b/embedding/android/GeckoAppShell.java @@ -1820,6 +1820,9 @@ public class GeckoAppShell return false; } + public static void emitGeckoAccessibilityEvent (int eventType, String[] textList, String description, boolean enabled, boolean checked, boolean password) { + } + public static double[] getCurrentNetworkInformation() { return GeckoNetworkManager.getInstance().getCurrentInformation(); } diff --git a/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/merge-dictionaries b/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/merge-dictionaries index 1e6b62484999..c67e7cd910a5 100644 --- a/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/merge-dictionaries +++ b/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/merge-dictionaries @@ -52,6 +52,7 @@ sed '1d' $HUNSPELL_PATCHED > $HUNSPELL_PATCHED_STRIPPED # Combine dictionaries and sort echo Combining dictionaries +export LC_ALL=C sort $CHROMIUM_AFFIX_CONVERTED $HUNSPELL_PATCHED_STRIPPED $MOZILLA_START > $MERGED_SORTED # Display any dupes. diff --git a/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/upstream-hunspell.diff b/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/upstream-hunspell.diff index 4d7c80803a57..20bd1c2c6ce6 100644 --- a/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/upstream-hunspell.diff +++ b/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/upstream-hunspell.diff @@ -9443,125 +9443,127 @@ > proprietorship/MS 39039a44848 > provender/M -40036a45846 +39564a45374 +> quinoa +40036a45847 > recency -40141a45952 +40141a45953 > recuse/DGS -40208a46020 +40208a46021 > refactor/SMDG -40244d46055 +40244d46056 < reflexion/SM -40829c46640 +40829c46641 < reverie/M --- > reverie/MS -41415a47227 +41415a47228 > sabre/MS -41914c47726 +41914c47727 < schnaps's --- > schnaps/M -41949c47761 +41949c47762 < schrod's --- > schrod/SM -41998a47811 +41998a47812 > scot-free -42883,42885c48696 +42883,42885c48697 < shit's < shit/S! < shite/S! --- > shit/MS! -42887,42888c48698,48699 +42887,42888c48699,48700 < shithead/S! < shitload/! --- > shithead/MS! > shitload/MS! -42891c48702 +42891c48703 < shitty/RT! --- > shitty/TR! -42976a48788 +42976a48789 > should've -43008c48820 +43008c48821 < showtime --- > showtime/MS -43724,43726c49536 +43724,43726c49537 < smoulder's < smouldered < smoulders --- > smoulder/GSMD -44062c49872 +44062c49873 < sonofabitch --- > sonofabitch/! -44371a50182 +44371a50183 > spick/S! -44383c50194 +44383c50195 < spik/S --- > spik/S! -46106a51918 +46106a51919 > syllabi -46160c51972 +46160c51973 < synch/GMD --- > synch/GMDS -46167d51978 +46167d51979 < synchs -46203,46204c52014,52015 +46203,46204c52015,52016 < sysadmin/S < sysop/S --- > sysadmin/MS > sysop/MS -46752a52564 +46752a52565 > terabit/MS -46753a52566,52567 +46753a52567,52568 > terahertz/M > terapixel/MS -46817a52632 +46817a52633 > testcase/MS -46831a52647 +46831a52648 > testsuite/MS -46925a52742 +46925a52743 > theremin/MS -47755a53573 +47755a53574 > transfect/DSMG -47774a53593,53594 +47774a53594,53595 > transgenderism > transgene/MS -47951c53771 +47951c53772 < triage/M --- > triage/MG -48869a54690 +48869a54691 > unlikeable -49211c55032 +49211c55033 < vagina/M --- > vagina/MS -49368,49369c55189 +49368,49369c55190 < velour's < velours's --- > velour/MS -49478a55299 +49478a55300 > vertices -50148a55970 +50148a55971 > weaponize/DSG -50260,50261d56081 +50260,50261d56082 < werwolf/M < werwolves -50728c56548 +50728c56549 < women --- > women/M -50794c56614 +50794c56615 < wop/S! --- > wop/MS! diff --git a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic index 9b5665d8fb46..361d285dec0d 100644 --- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic +++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic @@ -1,4 +1,4 @@ -57436 +57437 0/nm 0th/pt 1/n1 @@ -45698,6 +45698,7 @@ quince/SM quincentenary quine/S quinine/M +quinoa quinquennial quinsy/M quint/SM diff --git a/extensions/spellcheck/src/mozEnglishWordUtils.cpp b/extensions/spellcheck/src/mozEnglishWordUtils.cpp index bd24fe626d2f..da50b9ce84c7 100644 --- a/extensions/spellcheck/src/mozEnglishWordUtils.cpp +++ b/extensions/spellcheck/src/mozEnglishWordUtils.cpp @@ -36,7 +36,6 @@ * ***** END LICENSE BLOCK ***** */ #include "mozEnglishWordUtils.h" -#include "nsICharsetAlias.h" #include "nsReadableUtils.h" #include "nsIServiceManager.h" #include "nsUnicharUtils.h" diff --git a/extensions/spellcheck/src/mozPersonalDictionary.cpp b/extensions/spellcheck/src/mozPersonalDictionary.cpp index 2ece1e2438a3..13c3ab8d82a7 100644 --- a/extensions/spellcheck/src/mozPersonalDictionary.cpp +++ b/extensions/spellcheck/src/mozPersonalDictionary.cpp @@ -41,7 +41,6 @@ #include "nsIFile.h" #include "nsAppDirectoryServiceDefs.h" #include "nsICharsetConverterManager.h" -#include "nsICharsetAlias.h" #include "nsIObserverService.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" diff --git a/extensions/universalchardet/src/xpcom/nsUniversalCharDetModule.cpp b/extensions/universalchardet/src/xpcom/nsUniversalCharDetModule.cpp index e35838764126..bab956dc8b5a 100644 --- a/extensions/universalchardet/src/xpcom/nsUniversalCharDetModule.cpp +++ b/extensions/universalchardet/src/xpcom/nsUniversalCharDetModule.cpp @@ -37,7 +37,6 @@ #include "mozilla/ModuleUtils.h" -#include "nsICharsetAlias.h" #include "nsCOMPtr.h" #include "nspr.h" diff --git a/gfx/angle/angle-castrate-bug-241.patch b/gfx/angle/angle-castrate-bug-241.patch index ef9fae679acf..6b58f9065ae0 100644 --- a/gfx/angle/angle-castrate-bug-241.patch +++ b/gfx/angle/angle-castrate-bug-241.patch @@ -26,7 +26,25 @@ diff --git a/gfx/angle/README.mozilla b/gfx/angle/README.mozilla diff --git a/gfx/angle/src/compiler/Types.h b/gfx/angle/src/compiler/Types.h --- a/gfx/angle/src/compiler/Types.h +++ b/gfx/angle/src/compiler/Types.h -@@ -203,17 +203,17 @@ public: +@@ -5,16 +5,17 @@ + // + + #ifndef _TYPES_INCLUDED + #define _TYPES_INCLUDED + + #include "compiler/BaseTypes.h" + #include "compiler/Common.h" + #include "compiler/compilerdebug.h" ++#include + + // + // Need to have association of line numbers to types in a list for building structs. + // + class TType; + struct TTypeLine { + TType* type; + int line; +@@ -203,17 +204,17 @@ public: bool isVector() const { return size > 1 && !matrix; } bool isScalar() const { return size == 1 && !matrix && !structure; } diff --git a/gfx/angle/src/compiler/Types.h b/gfx/angle/src/compiler/Types.h index 00068c4b3246..8b639291c797 100644 --- a/gfx/angle/src/compiler/Types.h +++ b/gfx/angle/src/compiler/Types.h @@ -10,6 +10,7 @@ #include "compiler/BaseTypes.h" #include "compiler/Common.h" #include "compiler/compilerdebug.h" +#include // // Need to have association of line numbers to types in a list for building structs. diff --git a/gfx/cairo/cairo/src/cairo-dwrite-font.cpp b/gfx/cairo/cairo/src/cairo-dwrite-font.cpp index 22d7396523d3..9df827b8a428 100644 --- a/gfx/cairo/cairo/src/cairo-dwrite-font.cpp +++ b/gfx/cairo/cairo/src/cairo-dwrite-font.cpp @@ -1157,7 +1157,17 @@ _dwrite_draw_glyphs_to_gdi_surface_gdi(cairo_win32_surface_t *surface, surface->dc, area.left, area.top, SRCCOPY | NOMIRRORBITMAP); - HRESULT hr = rt->DrawGlyphRun(0, 0, DWRITE_MEASURING_MODE_NATURAL, run, params, color); + DWRITE_MEASURING_MODE measureMode; + switch (scaled_font->rendering_mode) { + case cairo_d2d_surface_t::TEXT_RENDERING_GDI_CLASSIC: + case cairo_d2d_surface_t::TEXT_RENDERING_NO_CLEARTYPE: + measureMode = DWRITE_MEASURING_MODE_GDI_CLASSIC; + break; + default: + measureMode = DWRITE_MEASURING_MODE_NATURAL; + break; + } + HRESULT hr = rt->DrawGlyphRun(0, 0, measureMode, run, params, color); BitBlt(surface->dc, area.left, area.top, area.right - area.left, area.bottom - area.top, diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index 68dda711e2a1..9afcd48a362d 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -1209,6 +1209,8 @@ GLContext::ResizeOffscreenFBO(const gfxIntSize& aSize, const bool aUseReadFBO, c fBindTexture(LOCAL_GL_TEXTURE_2D, newOffscreenTexture); fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR); fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR); + fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE); + fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE); if (alpha) { fTexImage2D(LOCAL_GL_TEXTURE_2D, diff --git a/gfx/gl/GLContextProviderEGL.cpp b/gfx/gl/GLContextProviderEGL.cpp index 2f7e2c9c6502..d321f6e5c6f8 100644 --- a/gfx/gl/GLContextProviderEGL.cpp +++ b/gfx/gl/GLContextProviderEGL.cpp @@ -39,6 +39,7 @@ * * ***** END LICENSE BLOCK ***** */ +#include "mozilla/Preferences.h" #include "mozilla/Util.h" #if defined(XP_UNIX) @@ -69,6 +70,12 @@ #include "AndroidBridge.h" #endif #include + +// We only need to explicitly dlopen egltrace +// on android as we can use LD_PRELOAD or other tricks +// on other platforms. We look for it in /data/local +// as that's writeable by all users +#define APITRACE_LIB "/data/local/egltrace.so" #endif #define EGL_LIB "libEGL.so" @@ -227,6 +234,38 @@ static EGLint gContextAttribsRobustness[] = { LOCAL_EGL_NONE }; +static PRLibrary* LoadApitraceLibrary() +{ + static PRLibrary* sApitraceLibrary = NULL; + + if (sApitraceLibrary) + return sApitraceLibrary; + +#if defined(ANDROID) + nsCString logFile = Preferences::GetCString("gfx.apitrace.logfile"); + + if (logFile.IsEmpty()) { + logFile = "firefox.trace"; + } + + // The firefox process can't write to /data/local, but it can write + // to $GRE_HOME/ + nsCAutoString logPath; + logPath.AppendPrintf("%s/%s", getenv("GRE_HOME"), logFile.get()); + + // apitrace uses the TRACE_FILE environment variable to determine where + // to log trace output to + printf_stderr("Logging GL tracing output to %s", logPath.get()); + setenv("TRACE_FILE", logPath.get(), false); + + printf_stderr("Attempting load of %s\n", APITRACE_LIB); + + sApitraceLibrary = PR_LoadLibrary(APITRACE_LIB); +#endif + + return sApitraceLibrary; +} + static int next_power_of_two(int v) { @@ -650,12 +689,17 @@ public: #endif if (!mEGLLibrary) { - mEGLLibrary = PR_LoadLibrary(EGL_LIB); -#if defined(XP_UNIX) + mEGLLibrary = LoadApitraceLibrary(); + if (!mEGLLibrary) { - mEGLLibrary = PR_LoadLibrary(EGL_LIB1); - } + printf_stderr("Attempting load of %s\n", EGL_LIB); + mEGLLibrary = PR_LoadLibrary(EGL_LIB); +#if defined(XP_UNIX) + if (!mEGLLibrary) { + mEGLLibrary = PR_LoadLibrary(EGL_LIB1); + } #endif + } } if (!mEGLLibrary) { @@ -1019,14 +1063,19 @@ public: bool Init() { - if (!OpenLibrary(GLES2_LIB)) { -#if defined(XP_UNIX) - if (!OpenLibrary(GLES2_LIB2)) { - NS_WARNING("Couldn't load EGL LIB."); - } +#if defined(ANDROID) + // We can't use LoadApitraceLibrary here because the GLContext + // expects its own handle to the GL library + if (!OpenLibrary(APITRACE_LIB)) #endif - return false; - } + if (!OpenLibrary(GLES2_LIB)) { +#if defined(XP_UNIX) + if (!OpenLibrary(GLES2_LIB2)) { + NS_WARNING("Couldn't load GLES2 LIB."); + return false; + } +#endif + } bool current = MakeCurrent(); if (!current) { diff --git a/gfx/harfbuzz/src/hb-ot-shape-complex-indic-machine.hh b/gfx/harfbuzz/src/hb-ot-shape-complex-indic-machine.hh index 334e607fdbed..28160f2c0697 100644 --- a/gfx/harfbuzz/src/hb-ot-shape-complex-indic-machine.hh +++ b/gfx/harfbuzz/src/hb-ot-shape-complex-indic-machine.hh @@ -232,25 +232,25 @@ _resume: #line 62 "hb-ot-shape-complex-indic-machine.rl" { found_consonant_syllable (map, buffer, mask_array, last, p); } #line 67 "hb-ot-shape-complex-indic-machine.rl" - { set_cluster (buffer, p, last); last = p; } + { set_cluster (buffer, last, p); last = p; } break; case 3: #line 63 "hb-ot-shape-complex-indic-machine.rl" { found_vowel_syllable (map, buffer, mask_array, last, p); } #line 67 "hb-ot-shape-complex-indic-machine.rl" - { set_cluster (buffer, p, last); last = p; } + { set_cluster (buffer, last, p); last = p; } break; case 4: #line 64 "hb-ot-shape-complex-indic-machine.rl" { found_standalone_cluster (map, buffer, mask_array, last, p); } #line 67 "hb-ot-shape-complex-indic-machine.rl" - { set_cluster (buffer, p, last); last = p; } + { set_cluster (buffer, last, p); last = p; } break; case 1: #line 65 "hb-ot-shape-complex-indic-machine.rl" { found_non_indic (map, buffer, mask_array, last, p); } #line 67 "hb-ot-shape-complex-indic-machine.rl" - { set_cluster (buffer, p, last); last = p; } + { set_cluster (buffer, last, p); last = p; } break; #line 256 "hb-ot-shape-complex-indic-machine.hh" } @@ -268,25 +268,25 @@ _again: #line 62 "hb-ot-shape-complex-indic-machine.rl" { found_consonant_syllable (map, buffer, mask_array, last, p); } #line 67 "hb-ot-shape-complex-indic-machine.rl" - { set_cluster (buffer, p, last); last = p; } + { set_cluster (buffer, last, p); last = p; } break; case 3: #line 63 "hb-ot-shape-complex-indic-machine.rl" { found_vowel_syllable (map, buffer, mask_array, last, p); } #line 67 "hb-ot-shape-complex-indic-machine.rl" - { set_cluster (buffer, p, last); last = p; } + { set_cluster (buffer, last, p); last = p; } break; case 4: #line 64 "hb-ot-shape-complex-indic-machine.rl" { found_standalone_cluster (map, buffer, mask_array, last, p); } #line 67 "hb-ot-shape-complex-indic-machine.rl" - { set_cluster (buffer, p, last); last = p; } + { set_cluster (buffer, last, p); last = p; } break; case 1: #line 65 "hb-ot-shape-complex-indic-machine.rl" { found_non_indic (map, buffer, mask_array, last, p); } #line 67 "hb-ot-shape-complex-indic-machine.rl" - { set_cluster (buffer, p, last); last = p; } + { set_cluster (buffer, last, p); last = p; } break; #line 292 "hb-ot-shape-complex-indic-machine.hh" } diff --git a/gfx/harfbuzz/src/hb-ot-shape-complex-indic-machine.rl b/gfx/harfbuzz/src/hb-ot-shape-complex-indic-machine.rl index 53dc20df54b4..bcca6dab26f7 100644 --- a/gfx/harfbuzz/src/hb-ot-shape-complex-indic-machine.rl +++ b/gfx/harfbuzz/src/hb-ot-shape-complex-indic-machine.rl @@ -64,7 +64,7 @@ action found_vowel_syllable { found_vowel_syllable (map, buffer, mask_array, las action found_standalone_cluster { found_standalone_cluster (map, buffer, mask_array, last, p); } action found_non_indic { found_non_indic (map, buffer, mask_array, last, p); } -action next_syllable { set_cluster (buffer, p, last); last = p; } +action next_syllable { set_cluster (buffer, last, p); last = p; } consonant_syllable = (c.N? (z.H|H.z?))* c.N? A? (H.z? | matra_group*)? syllable_tail %(found_consonant_syllable); vowel_syllable = (Ra H)? V N? (z.H.c | ZWJ.c)? matra_group* syllable_tail %(found_vowel_syllable); diff --git a/gfx/layers/Makefile.in b/gfx/layers/Makefile.in index fdd80f656d8c..3f07dbdae222 100644 --- a/gfx/layers/Makefile.in +++ b/gfx/layers/Makefile.in @@ -73,6 +73,7 @@ CPPSRCS = \ BasicImages.cpp \ BasicLayers.cpp \ Layers.cpp \ + RenderTrace.cpp \ ReadbackProcessor.cpp \ ThebesLayerBuffer.cpp \ CanvasLayerOGL.cpp \ diff --git a/gfx/layers/RenderTrace.cpp b/gfx/layers/RenderTrace.cpp new file mode 100644 index 000000000000..e3f70161a084 --- /dev/null +++ b/gfx/layers/RenderTrace.cpp @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * ***** 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 Corporation code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2012 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Benoit Girard + * + * 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 "Layers.h" +#include "RenderTrace.h" + +// If rendertrace is off let's no compile this code +#ifdef MOZ_RENDERTRACE + + +namespace mozilla { +namespace layers { + +static int colorId = 0; + +// This should be done in the printf but android's printf is buggy +const char* colors[] = { + "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", + "10", "11", "12", "13", "14", "15", "16", "17", "18", "19" + }; + +static gfx3DMatrix GetRootTransform(Layer *aLayer) { + gfx3DMatrix layerTrans = aLayer->GetTransform().ProjectTo2D(); + if (aLayer->GetParent() != NULL) { + return GetRootTransform(aLayer->GetParent()) * layerTrans; + } + return layerTrans; +} + +void RenderTraceLayers(Layer *aLayer, const char *aColor, const gfx3DMatrix aRootTransform, bool aReset) { + if (!aLayer) + return; + + gfx3DMatrix trans = aRootTransform * aLayer->GetTransform().ProjectTo2D(); + nsIntRect clipRect = aLayer->GetEffectiveVisibleRegion().GetBounds(); + gfxRect rect(clipRect.x, clipRect.y, clipRect.width, clipRect.height); + trans.TransformBounds(rect); + + printf_stderr("%s RENDERTRACE %u rect #%02X%s %i %i %i %i\n", + aLayer->Name(), (int)PR_IntervalNow(), + colorId, aColor, + (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height); + + colorId++; + + for (Layer* child = aLayer->GetFirstChild(); + child; child = child->GetNextSibling()) { + RenderTraceLayers(child, aColor, aRootTransform, false); + } + + if (aReset) colorId = 0; +} + +void RenderTraceInvalidateStart(Layer *aLayer, const char *aColor, const nsIntRect aRect) { + gfx3DMatrix trans = GetRootTransform(aLayer); + gfxRect rect(aRect.x, aRect.y, aRect.width, aRect.height); + trans.TransformBounds(rect); + + printf_stderr("%s RENDERTRACE %u fillrect #%s %i %i %i %i\n", + aLayer->Name(), (int)PR_IntervalNow(), + aColor, + (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height); +} +void RenderTraceInvalidateEnd(Layer *aLayer, const char *aColor) { + // Clear with an empty rect + RenderTraceInvalidateStart(aLayer, aColor, nsIntRect()); +} + +} +} + +#endif + diff --git a/gfx/layers/RenderTrace.h b/gfx/layers/RenderTrace.h new file mode 100644 index 000000000000..50cffff5715f --- /dev/null +++ b/gfx/layers/RenderTrace.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * ***** 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 Corporation code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2012 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Benoit Girard + * + * 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 ***** */ + +// This is a general tool that will let you visualize platform operation. +// Currently used for the layer system, the general syntax allows this +// tools to be adapted to trace other operations. +// +// For the front end see: https://github.com/staktrace/rendertrace + +// Uncomment this line to enable RENDERTRACE +//#define MOZ_RENDERTRACE +#ifdef MOZ_RENDERTRACE + +#include "gfx3DMatrix.h" +#include "nsRect.h" + +#ifndef GFX_RENDERTRACE_H +#define GFX_RENDERTRACE_H + +namespace mozilla { +namespace layers { + +class Layer; + +void RenderTraceLayers(Layer *aLayer, const char *aColor, gfx3DMatrix aRootTransform = gfx3DMatrix(), bool aReset = true); + +void RenderTraceInvalidateStart(Layer *aLayer, const char *aColor, const nsIntRect aRect); +void RenderTraceInvalidateEnd(Layer *aLayer, const char *aColor); + +} +} + +#endif //GFX_RENDERTRACE_H + +#endif // MOZ_RENDERTRACE diff --git a/gfx/layers/basic/BasicLayers.cpp b/gfx/layers/basic/BasicLayers.cpp index 97de77617c16..83526d43eeca 100644 --- a/gfx/layers/basic/BasicLayers.cpp +++ b/gfx/layers/basic/BasicLayers.cpp @@ -49,6 +49,7 @@ #include "BasicLayers.h" #include "ImageLayers.h" +#include "RenderTrace.h" #include "prprf.h" #include "nsTArray.h" @@ -688,6 +689,11 @@ BasicThebesLayer::PaintThebes(gfxContext* aContext, mBuffer.Clear(); nsIntRegion toDraw = IntersectWithClip(GetEffectiveVisibleRegion(), aContext); + +#ifdef MOZ_RENDERTRACE + RenderTraceInvalidateStart(this, "FFFF00", toDraw.GetBounds()); +#endif + if (!toDraw.IsEmpty() && !IsHidden()) { if (!aCallback) { BasicManager()->SetTransactionIncomplete(); @@ -723,6 +729,10 @@ BasicThebesLayer::PaintThebes(gfxContext* aContext, aContext->Restore(); } + +#ifdef MOZ_RENDERTRACE + RenderTraceInvalidateEnd(this, "FFFF00"); +#endif return; } @@ -748,11 +758,20 @@ BasicThebesLayer::PaintThebes(gfxContext* aContext, GetEffectiveVisibleRegion()); nsIntRegion extendedDrawRegion = state.mRegionToDraw; SetAntialiasingFlags(this, state.mContext); + +#ifdef MOZ_RENDERTRACE + RenderTraceInvalidateStart(this, "FFFF00", state.mRegionToDraw.GetBounds()); +#endif + PaintBuffer(state.mContext, state.mRegionToDraw, extendedDrawRegion, state.mRegionToInvalidate, state.mDidSelfCopy, aCallback, aCallbackData); Mutated(); + +#ifdef MOZ_RENDERTRACE + RenderTraceInvalidateEnd(this, "FFFF00"); +#endif } else { // It's possible that state.mRegionToInvalidate is nonempty here, // if we are shrinking the valid region to nothing. @@ -1600,6 +1619,11 @@ BasicLayerManager::EndTransactionInternal(DrawThebesLayerCallback aCallback, mPhase = PHASE_DRAWING; #endif +#ifdef MOZ_RENDERTRACE + Layer* aLayer = GetRoot(); + RenderTraceLayers(aLayer, "FF00"); +#endif + mTransactionIncomplete = false; if (mTarget && mRoot && !(aFlags & END_NO_IMMEDIATE_REDRAW)) { @@ -1820,6 +1844,8 @@ Transform3D(gfxASurface* aSource, gfxContext* aDest, return destImage.forget(); } + + void BasicLayerManager::PaintLayer(gfxContext* aTarget, Layer* aLayer, diff --git a/gfx/layers/ipc/CompositorParent.cpp b/gfx/layers/ipc/CompositorParent.cpp index a37d0a60e102..7d2e0430cd21 100644 --- a/gfx/layers/ipc/CompositorParent.cpp +++ b/gfx/layers/ipc/CompositorParent.cpp @@ -39,6 +39,7 @@ * ***** END LICENSE BLOCK ***** */ #include "CompositorParent.h" +#include "RenderTrace.h" #include "ShadowLayersParent.h" #include "LayerManagerOGL.h" #include "nsIWidget.h" @@ -80,6 +81,12 @@ CompositorParent::ScheduleComposition() { CancelableTask *composeTask = NewRunnableMethod(this, &CompositorParent::Composite); MessageLoop::current()->PostTask(FROM_HERE, composeTask); + +#ifdef MOZ_RENDERTRACE + Layer* aLayer = mLayerManager->GetRoot(); + mozilla::layers::RenderTraceLayers(aLayer, "0000"); +#endif + } void diff --git a/gfx/layers/ipc/ShadowLayersParent.cpp b/gfx/layers/ipc/ShadowLayersParent.cpp index 1f2471aa7e26..9d3356949301 100644 --- a/gfx/layers/ipc/ShadowLayersParent.cpp +++ b/gfx/layers/ipc/ShadowLayersParent.cpp @@ -43,6 +43,7 @@ #include "ShadowLayersParent.h" #include "ShadowLayerParent.h" #include "ShadowLayers.h" +#include "RenderTrace.h" #include "mozilla/unused.h" @@ -318,6 +319,10 @@ ShadowLayersParent::RecvUpdate(const InfallibleTArray& cset, static_cast(shadow->AsLayer()); const ThebesBuffer& newFront = op.newFrontBuffer(); +#ifdef MOZ_RENDERTRACE + RenderTraceInvalidateStart(thebes, "FF00FF", op.updatedRegion().GetBounds()); +#endif + OptionalThebesBuffer newBack; nsIntRegion newValidRegion; OptionalThebesBuffer readonlyFront; @@ -330,6 +335,10 @@ ShadowLayersParent::RecvUpdate(const InfallibleTArray& cset, shadow, NULL, newBack, newValidRegion, readonlyFront, frontUpdatedRegion)); + +#ifdef MOZ_RENDERTRACE + RenderTraceInvalidateEnd(thebes, "FF00FF"); +#endif break; } case Edit::TOpPaintCanvas: { @@ -340,6 +349,10 @@ ShadowLayersParent::RecvUpdate(const InfallibleTArray& cset, ShadowCanvasLayer* canvas = static_cast(shadow->AsLayer()); +#ifdef MOZ_RENDERTRACE + RenderTraceInvalidateStart(canvas, "FF00FF", canvas->GetVisibleRegion().GetBounds()); +#endif + canvas->SetAllocator(this); CanvasSurface newBack; canvas->Swap(op.newFrontBuffer(), op.needYFlip(), &newBack); @@ -347,6 +360,9 @@ ShadowLayersParent::RecvUpdate(const InfallibleTArray& cset, replyv.push_back(OpBufferSwap(shadow, NULL, newBack)); +#ifdef MOZ_RENDERTRACE + RenderTraceInvalidateEnd(canvas, "FF00FF"); +#endif break; } case Edit::TOpPaintImage: { @@ -357,12 +373,19 @@ ShadowLayersParent::RecvUpdate(const InfallibleTArray& cset, ShadowImageLayer* image = static_cast(shadow->AsLayer()); +#ifdef MOZ_RENDERTRACE + RenderTraceInvalidateStart(image, "FF00FF", image->GetVisibleRegion().GetBounds()); +#endif + image->SetAllocator(this); SharedImage newBack; image->Swap(op.newFrontBuffer(), &newBack); replyv.push_back(OpImageSwap(shadow, NULL, newBack)); +#ifdef MOZ_RENDERTRACE + RenderTraceInvalidateEnd(image, "FF00FF"); +#endif break; } diff --git a/gfx/layers/opengl/LayerManagerOGL.cpp b/gfx/layers/opengl/LayerManagerOGL.cpp index 6cf649e02524..31bd86054b1c 100644 --- a/gfx/layers/opengl/LayerManagerOGL.cpp +++ b/gfx/layers/opengl/LayerManagerOGL.cpp @@ -367,7 +367,20 @@ LayerManagerOGL::Initialize(nsRefPtr aContext, bool force) console->LogStringMessage(msg.get()); } - Preferences::AddBoolVarCache(&sDrawFPS, "layers.acceleration.draw-fps"); + if (NS_IsMainThread()) { + Preferences::AddBoolVarCache(&sDrawFPS, "layers.acceleration.draw-fps"); + } else { + // We have to dispatch an event to the main thread to read the pref. + class ReadDrawFPSPref : public nsRunnable { + public: + NS_IMETHOD Run() + { + Preferences::AddBoolVarCache(&sDrawFPS, "layers.acceleration.draw-fps"); + return NS_OK; + } + }; + NS_DispatchToMainThread(new ReadDrawFPSPref()); + } reporter.SetSuccessful(); return true; diff --git a/gfx/thebes/gfxContext.cpp b/gfx/thebes/gfxContext.cpp index 7b2661e26c40..97e8d2dc9b3e 100644 --- a/gfx/thebes/gfxContext.cpp +++ b/gfx/thebes/gfxContext.cpp @@ -96,8 +96,8 @@ private: }; gfxContext::gfxContext(gfxASurface *surface) - : mSurface(surface) - , mRefCairo(NULL) + : mRefCairo(NULL) + , mSurface(surface) { MOZ_COUNT_CTOR(gfxContext); @@ -1266,8 +1266,6 @@ gfxContext::ClipContainsRect(const gfxRect& aRect) } } - bool result = true; - // Since we always return false when the clip list contains a // non-rectangular clip or a non-rectilinear transform, our 'total' clip // is always a rectangle if we hit the end of this function. diff --git a/gfx/thebes/gfxUserFontSet.cpp b/gfx/thebes/gfxUserFontSet.cpp index 13563eb1f97d..dba646a706c1 100644 --- a/gfx/thebes/gfxUserFontSet.cpp +++ b/gfx/thebes/gfxUserFontSet.cpp @@ -402,9 +402,11 @@ StoreUserFontData(gfxFontEntry* aFontEntry, gfxProxyFontEntry* aProxy, } } -static void -CopyWOFFMetadata(const PRUint8* aFontData, PRUint32 aLength, - nsTArray* aMetadata, PRUint32* aMetaOrigLen) +void +gfxUserFontSet::CopyWOFFMetadata(const PRUint8* aFontData, + PRUint32 aLength, + nsTArray* aMetadata, + PRUint32* aMetaOrigLen) { // This function may be called with arbitrary, unvalidated "font" data // from @font-face, so it needs to be careful to bounds-check, etc., diff --git a/gfx/thebes/gfxUserFontSet.h b/gfx/thebes/gfxUserFontSet.h index 376aa2a49349..f1b0ee60dd23 100644 --- a/gfx/thebes/gfxUserFontSet.h +++ b/gfx/thebes/gfxUserFontSet.h @@ -281,6 +281,12 @@ protected: PRUint64 mGeneration; static PRLogModuleInfo *sUserFontsLog; + +private: + static void CopyWOFFMetadata(const PRUint8* aFontData, + PRUint32 aLength, + nsTArray* aMetadata, + PRUint32* aMetaOrigLen); }; // acts a placeholder until the real font is downloaded diff --git a/hal/Hal.cpp b/hal/Hal.cpp index 480e3b3678ac..da187f28a4bd 100644 --- a/hal/Hal.cpp +++ b/hal/Hal.cpp @@ -184,19 +184,36 @@ public: if (mObservers->Length() == 0) { DisableNotifications(); + OnNotificationsDisabled(); + delete mObservers; mObservers = 0; - - mHasValidCache = false; } } + void BroadcastInformation(const InfoType& aInfo) { + MOZ_ASSERT(mObservers); + mObservers->Broadcast(aInfo); + } + +protected: + virtual void EnableNotifications() = 0; + virtual void DisableNotifications() = 0; + virtual void OnNotificationsDisabled() {} + +private: + mozilla::ObserverList* mObservers; +}; + +template +class CachingObserversManager : public ObserversManager +{ +public: InfoType GetCurrentInformation() { if (mHasValidCache) { return mInfo; } - mHasValidCache = true; GetCurrentInformationInternal(&mInfo); return mInfo; } @@ -207,22 +224,22 @@ public: } void BroadcastCachedInformation() { - MOZ_ASSERT(mObservers); - mObservers->Broadcast(mInfo); + this->BroadcastInformation(mInfo); } protected: - virtual void EnableNotifications() = 0; - virtual void DisableNotifications() = 0; virtual void GetCurrentInformationInternal(InfoType*) = 0; + virtual void OnNotificationsDisabled() { + mHasValidCache = false; + } + private: - mozilla::ObserverList* mObservers; InfoType mInfo; bool mHasValidCache; }; -class BatteryObserversManager : public ObserversManager +class BatteryObserversManager : public CachingObserversManager { protected: void EnableNotifications() { @@ -240,7 +257,7 @@ protected: static BatteryObserversManager sBatteryObservers; -class NetworkObserversManager : public ObserversManager +class NetworkObserversManager : public CachingObserversManager { protected: void EnableNotifications() { @@ -258,6 +275,20 @@ protected: static NetworkObserversManager sNetworkObservers; +class WakeLockObserversManager : public ObserversManager +{ +protected: + void EnableNotifications() { + PROXY_IF_SANDBOXED(EnableWakeLockNotifications()); + } + + void DisableNotifications() { + PROXY_IF_SANDBOXED(DisableWakeLockNotifications()); + } +}; + +static WakeLockObserversManager sWakeLockObservers; + void RegisterBatteryObserver(BatteryObserver* aObserver) { @@ -435,5 +466,42 @@ void PowerOff() PROXY_IF_SANDBOXED(PowerOff()); } +void +RegisterWakeLockObserver(WakeLockObserver* aObserver) +{ + AssertMainThread(); + sWakeLockObservers.AddObserver(aObserver); +} + +void +UnregisterWakeLockObserver(WakeLockObserver* aObserver) +{ + AssertMainThread(); + sWakeLockObservers.RemoveObserver(aObserver); +} + +void +ModifyWakeLock(const nsAString &aTopic, + hal::WakeLockControl aLockAdjust, + hal::WakeLockControl aHiddenAdjust) +{ + AssertMainThread(); + PROXY_IF_SANDBOXED(ModifyWakeLock(aTopic, aLockAdjust, aHiddenAdjust)); +} + +void +GetWakeLockInfo(const nsAString &aTopic, WakeLockInformation *aWakeLockInfo) +{ + AssertMainThread(); + PROXY_IF_SANDBOXED(GetWakeLockInfo(aTopic, aWakeLockInfo)); +} + +void +NotifyWakeLockChange(const WakeLockInformation& aInfo) +{ + AssertMainThread(); + sWakeLockObservers.BroadcastInformation(aInfo); +} + } // namespace hal } // namespace mozilla diff --git a/hal/Hal.h b/hal/Hal.h index d4ab25ac1408..15072f464f15 100644 --- a/hal/Hal.h +++ b/hal/Hal.h @@ -14,6 +14,7 @@ #include "prlog.h" #include "mozilla/dom/battery/Types.h" #include "mozilla/dom/network/Types.h" +#include "mozilla/dom/power/Types.h" #include "mozilla/hal_sandbox/PHal.h" /* @@ -240,6 +241,55 @@ void Reboot(); */ void PowerOff(); +/** + * Enable wake lock notifications from the backend. + * + * This method is only used by WakeLockObserversManager. + */ +void EnableWakeLockNotifications(); + +/** + * Disable wake lock notifications from the backend. + * + * This method is only used by WakeLockObserversManager. + */ +void DisableWakeLockNotifications(); + +/** + * Inform the wake lock backend there is a new wake lock observer. + * @param aWakeLockObserver The observer that should be added. + */ +void RegisterWakeLockObserver(WakeLockObserver* aObserver); + +/** + * Inform the wake lock backend a wake lock observer unregistered. + * @param aWakeLockObserver The observer that should be removed. + */ +void UnregisterWakeLockObserver(WakeLockObserver* aObserver); + +/** + * Adjust the internal wake lock counts. + * @param aTopic lock topic + * @param aLockAdjust to increase or decrease active locks + * @param aHiddenAdjust to increase or decrease hidden locks + */ +void ModifyWakeLock(const nsAString &aTopic, + hal::WakeLockControl aLockAdjust, + hal::WakeLockControl aHiddenAdjust); + +/** + * Query the wake lock numbers of aTopic. + * @param aTopic lock topic + * @param aWakeLockInfo wake lock numbers + */ +void GetWakeLockInfo(const nsAString &aTopic, hal::WakeLockInformation *aWakeLockInfo); + +/** + * Notify of a change in the wake lock state. + * @param aWakeLockInfo The new wake lock information. + */ +void NotifyWakeLockChange(const hal::WakeLockInformation& aWakeLockInfo); + } // namespace MOZ_HAL_NAMESPACE } // namespace mozilla diff --git a/hal/HalTypes.h b/hal/HalTypes.h index 5c3e8a60c990..39bc1f24d5bb 100644 --- a/hal/HalTypes.h +++ b/hal/HalTypes.h @@ -36,9 +36,25 @@ enum FlashMode { eHalLightFlash_Timed = 1, // timed flashing. Use flashOnMS and flashOffMS for timing eHalLightFlash_Hardware = 2 // hardware assisted flashing }; + } // namespace hal } // namespace mozilla +namespace mozilla { +namespace hal { + +/** + * Used by ModifyWakeLock + */ +enum WakeLockControl { + WAKE_LOCK_REMOVE_ONE = -1, + WAKE_LOCK_NO_CHANGE = 0, + WAKE_LOCK_ADD_ONE = 1, +}; + +} +} + namespace IPC { /** @@ -71,6 +87,16 @@ struct ParamTraits mozilla::hal::eHalLightFlash_Hardware> {}; +/** + * WakeLockControl serializer. + */ +template <> +struct ParamTraits + : public EnumSerializer +{}; + } // namespace IPC #endif // mozilla_hal_Types_h diff --git a/hal/HalWakeLock.cpp b/hal/HalWakeLock.cpp new file mode 100644 index 000000000000..9d415ce56bc3 --- /dev/null +++ b/hal/HalWakeLock.cpp @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Hal.h" +#include "mozilla/HalWakeLock.h" +#include "mozilla/ClearOnShutdown.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" + +using namespace mozilla::hal; + +namespace mozilla { +namespace hal { + +WakeLockState +ComputeWakeLockState(int aNumLocks, int aNumHidden) +{ + if (aNumLocks == 0) { + return WAKE_LOCK_STATE_UNLOCKED; + } else if (aNumLocks == aNumHidden) { + return WAKE_LOCK_STATE_HIDDEN; + } else { + return WAKE_LOCK_STATE_VISIBLE; + } +} + +} // hal +} // mozilla + +namespace mozilla { +namespace hal_impl { + +namespace { +struct LockCount { + PRUint32 numLocks; + PRUint32 numHidden; +}; +} + +static int sActiveChildren = 0; +static nsAutoPtr > sLockTable; +static bool sInitialized = false; + +static void +Init() +{ + sLockTable = new nsDataHashtable(); + sLockTable->Init(); + ClearOnShutdown(&sLockTable); + sInitialized = true; +} + +void +EnableWakeLockNotifications() +{ + sActiveChildren++; +} + +void +DisableWakeLockNotifications() +{ + sActiveChildren--; +} + +void +ModifyWakeLock(const nsAString &aTopic, + hal::WakeLockControl aLockAdjust, + hal::WakeLockControl aHiddenAdjust) +{ + if (!sInitialized) { + Init(); + } + + LockCount count; + count.numLocks = 0; + count.numHidden = 0; + sLockTable->Get(aTopic, &count); + MOZ_ASSERT(count.numLocks >= count.numHidden); + MOZ_ASSERT(aLockAdjust >= 0 || count.numLocks > 0); + MOZ_ASSERT(aHiddenAdjust >= 0 || count.numHidden > 0); + + WakeLockState oldState = ComputeWakeLockState(count.numLocks, count.numHidden); + + count.numLocks += aLockAdjust; + count.numHidden += aHiddenAdjust; + MOZ_ASSERT(count.numLocks >= count.numHidden); + + if (count.numLocks) { + sLockTable->Put(aTopic, count); + } else { + sLockTable->Remove(aTopic); + } + + WakeLockState newState = ComputeWakeLockState(count.numLocks, count.numHidden); + + if (sActiveChildren && oldState != newState) { + WakeLockInformation info; + info.numLocks() = count.numLocks; + info.numHidden() = count.numHidden; + info.topic() = aTopic; + NotifyWakeLockChange(info); + } +} + +void +GetWakeLockInfo(const nsAString &aTopic, WakeLockInformation *aWakeLockInfo) +{ + if (!sInitialized) { + Init(); + } + + LockCount count; + count.numLocks = 0; + count.numHidden = 0; + sLockTable->Get(aTopic, &count); + + aWakeLockInfo->numLocks() = count.numLocks; + aWakeLockInfo->numHidden() = count.numHidden; + aWakeLockInfo->topic() = aTopic; +} + +} // hal_impl +} // mozilla diff --git a/hal/HalWakeLock.h b/hal/HalWakeLock.h new file mode 100644 index 000000000000..0462f30cba47 --- /dev/null +++ b/hal/HalWakeLock.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __HAL_WAKELOCK_H_ +#define __HAL_WAKELOCK_H_ + +namespace mozilla { +namespace hal { + +enum WakeLockState { + WAKE_LOCK_STATE_UNLOCKED, + WAKE_LOCK_STATE_HIDDEN, + WAKE_LOCK_STATE_VISIBLE +}; + +/** + * Return the wake lock state according to the numbers. + */ +WakeLockState ComputeWakeLockState(int aNumLocks, int aNumHidden); + +} // hal +} // mozilla + +#endif /* __HAL_WAKELOCK_H_ */ diff --git a/hal/Makefile.in b/hal/Makefile.in index 7592e0e14f62..8fde9d4d562a 100644 --- a/hal/Makefile.in +++ b/hal/Makefile.in @@ -63,12 +63,14 @@ EXPORTS_mozilla = \ HalSandbox.h \ HalSensor.h \ HalTypes.h \ + HalWakeLock.h \ $(NULL) CPPSRCS = \ Hal.cpp \ SandboxHal.cpp \ WindowIdentifier.cpp \ + HalWakeLock.cpp \ $(NULL) ifeq (android,$(MOZ_WIDGET_TOOLKIT)) @@ -113,4 +115,4 @@ include $(topsrcdir)/ipc/chromium/chromium-config.mk include $(topsrcdir)/config/rules.mk CFLAGS += $(MOZ_DBUS_GLIB_CFLAGS) -CXXFLAGS += $(MOZ_DBUS_GLIB_CFLAGS) -DHAVE_PTHREADS +CXXFLAGS += $(MOZ_DBUS_GLIB_CFLAGS) diff --git a/hal/gonk/GonkSensor.cpp b/hal/gonk/GonkSensor.cpp index 41a1e93f6183..842342e0d51c 100644 --- a/hal/gonk/GonkSensor.cpp +++ b/hal/gonk/GonkSensor.cpp @@ -11,10 +11,13 @@ #include "hardware/sensors.h" #include "mozilla/Util.h" #include "SensorDevice.h" +#include "nsThreadUtils.h" using namespace mozilla::hal; using namespace android; +namespace mozilla { + static SensorType HardwareSensorToHalSensor(int type) { @@ -63,19 +66,19 @@ SensorseventToSensorData(const sensors_event_t& data, SensorData* aSensorData) static void onSensorChanged(const sensors_event_t& data, SensorData* aSensorData) { - mozilla::DebugOnly convertedData = SensorseventToSensorData(data, aSensorData); + DebugOnly convertedData = SensorseventToSensorData(data, aSensorData); MOZ_ASSERT(convertedData); NotifySensorChange(*aSensorData); } -namespace mozilla { namespace hal_impl { static pthread_t sThread; -static bool sInitialized = false; -static bool sContinue = false; -static int sActivatedSensors = 0; +static bool sInitialized; +static bool sContinue; +static int sActivatedSensors; static SensorData sSensordata[NUM_SENSOR_TYPE]; +static nsCOMPtr sSwitchThread; static void* UpdateSensorData(void* /*unused*/) @@ -100,9 +103,10 @@ UpdateSensorData(void* /*unused*/) } static void -InitialResources() +InitializeResources() { pthread_create(&sThread, NULL, &UpdateSensorData, NULL); + NS_NewThread(getter_AddRefs(sSwitchThread)); sInitialized = true; sContinue = true; } @@ -112,9 +116,30 @@ ReleaseResources() { sContinue = false; pthread_join(sThread, NULL); + sSwitchThread->Shutdown(); sInitialized = false; } +// This class is used as a runnable on the sSwitchThread +class SensorInfo { + public: + NS_INLINE_DECL_REFCOUNTING(SensorInfo) + + SensorInfo(bool aActivate, sensor_t aSensor, pthread_t aThreadId) : + activate(aActivate), sensor(aSensor), threadId(aThreadId) { } + + void Switch() { + SensorDevice& device = SensorDevice::getInstance(); + device.activate((void*)threadId, sensor.handle, activate); + } + + protected: + SensorInfo() { }; + bool activate; + sensor_t sensor; + pthread_t threadId; +}; + static void SensorSwitch(SensorType aSensor, bool activate) { @@ -122,10 +147,12 @@ SensorSwitch(SensorType aSensor, bool activate) const sensor_t* sensors = NULL; SensorDevice& device = SensorDevice::getInstance(); size_t size = device.getSensorList(&sensors); - - for (size_t i=0; i event = NS_NewRunnableMethod(new SensorInfo(activate, sensors[i], pthread_self()), + &SensorInfo::Switch); + sSwitchThread->Dispatch(event, NS_DISPATCH_NORMAL); break; } } @@ -135,7 +162,7 @@ void EnableSensorNotifications(SensorType aSensor) { if (!sInitialized) { - InitialResources(); + InitializeResources(); } SensorSwitch(aSensor, true); diff --git a/hal/sandbox/PHal.ipdl b/hal/sandbox/PHal.ipdl index 1ea1baa8bab0..cdfd51397a16 100644 --- a/hal/sandbox/PHal.ipdl +++ b/hal/sandbox/PHal.ipdl @@ -48,6 +48,7 @@ using mozilla::hal::FlashMode; using mozilla::hal::LightType; using mozilla::hal::LightMode; using mozilla::hal::SensorType; +using mozilla::hal::WakeLockControl; namespace mozilla { @@ -81,6 +82,14 @@ namespace hal { }; } +namespace hal { + struct WakeLockInformation { + uint32_t numLocks; + uint32_t numHidden; + nsString topic; + }; +} + namespace hal_sandbox { sync protocol PHal { @@ -89,6 +98,7 @@ sync protocol PHal { child: NotifyBatteryChange(BatteryInformation aBatteryInfo); NotifyNetworkChange(NetworkInformation aNetworkInfo); + NotifyWakeLockChange(WakeLockInformation aWakeLockInfo); parent: Vibrate(uint32[] pattern, uint64[] id, PBrowser browser); @@ -121,6 +131,12 @@ parent: Reboot(); PowerOff(); + ModifyWakeLock(nsString aTopic, WakeLockControl aLockAdjust, WakeLockControl aHiddenAdjust); + EnableWakeLockNotifications(); + DisableWakeLockNotifications(); + sync GetWakeLockInfo(nsString aTopic) + returns (WakeLockInformation aWakeLockInfo); + child: NotifySensorChange(SensorData aSensorData); diff --git a/hal/sandbox/SandboxHal.cpp b/hal/sandbox/SandboxHal.cpp index 92a7d9c0a69a..0f190f5b062f 100644 --- a/hal/sandbox/SandboxHal.cpp +++ b/hal/sandbox/SandboxHal.cpp @@ -169,10 +169,35 @@ DisableSensorNotifications(SensorType aSensor) { Hal()->SendDisableSensorNotifications(aSensor); } +void +EnableWakeLockNotifications() +{ + Hal()->SendEnableWakeLockNotifications(); +} + +void +DisableWakeLockNotifications() +{ + Hal()->SendDisableWakeLockNotifications(); +} + +void +ModifyWakeLock(const nsAString &aTopic, WakeLockControl aLockAdjust, WakeLockControl aHiddenAdjust) +{ + Hal()->SendModifyWakeLock(nsString(aTopic), aLockAdjust, aHiddenAdjust); +} + +void +GetWakeLockInfo(const nsAString &aTopic, WakeLockInformation *aWakeLockInfo) +{ + Hal()->SendGetWakeLockInfo(nsString(aTopic), aWakeLockInfo); +} + class HalParent : public PHalParent , public BatteryObserver , public NetworkObserver , public ISensorObserver + , public WakeLockObserver { public: NS_OVERRIDE virtual bool @@ -343,6 +368,41 @@ public: void Notify(const SensorData& aSensorData) { unused << SendNotifySensorChange(aSensorData); } + + NS_OVERRIDE virtual bool + RecvModifyWakeLock(const nsString &aTopic, + const WakeLockControl &aLockAdjust, + const WakeLockControl &aHiddenAdjust) + { + hal::ModifyWakeLock(aTopic, aLockAdjust, aHiddenAdjust); + return true; + } + + NS_OVERRIDE virtual bool + RecvEnableWakeLockNotifications() + { + hal::RegisterWakeLockObserver(this); + return true; + } + + NS_OVERRIDE virtual bool + RecvDisableWakeLockNotifications() + { + hal::UnregisterWakeLockObserver(this); + return true; + } + + NS_OVERRIDE virtual bool + RecvGetWakeLockInfo(const nsString &aTopic, WakeLockInformation *aWakeLockInfo) + { + hal::GetWakeLockInfo(aTopic, aWakeLockInfo); + return true; + } + + void Notify(const WakeLockInformation& aWakeLockInfo) + { + unused << SendNotifyWakeLockChange(aWakeLockInfo); + } }; class HalChild : public PHalChild { @@ -361,6 +421,12 @@ public: hal::NotifyNetworkChange(aNetworkInfo); return true; } + + NS_OVERRIDE virtual bool + RecvNotifyWakeLockChange(const WakeLockInformation& aWakeLockInfo) { + hal::NotifyWakeLockChange(aWakeLockInfo); + return true; + } }; bool diff --git a/image/decoders/icon/nsIconURI.cpp b/image/decoders/icon/nsIconURI.cpp index 854e077aae31..22c729d346ab 100644 --- a/image/decoders/icon/nsIconURI.cpp +++ b/image/decoders/icon/nsIconURI.cpp @@ -98,7 +98,7 @@ NS_IMPL_THREADSAFE_ISUPPORTS2(nsMozIconURI, nsIMozIconURI, nsIURI) #define MOZICON_SCHEME_LEN (sizeof(MOZICON_SCHEME) - 1) //////////////////////////////////////////////////////////////////////////////// -// nsURI methods: +// nsIURI methods: NS_IMETHODIMP nsMozIconURI::GetSpec(nsACString &aSpec) diff --git a/intl/build/nsI18nModule.cpp b/intl/build/nsI18nModule.cpp index 40491caeab88..9b1724882134 100644 --- a/intl/build/nsI18nModule.cpp +++ b/intl/build/nsI18nModule.cpp @@ -89,7 +89,6 @@ NS_DEFINE_NAMED_CID(NS_LOCALESERVICE_CID); NS_DEFINE_NAMED_CID(NS_COLLATIONFACTORY_CID); NS_DEFINE_NAMED_CID(NS_SCRIPTABLEDATEFORMAT_CID); NS_DEFINE_NAMED_CID(NS_LANGUAGEATOMSERVICE_CID); -NS_DEFINE_NAMED_CID(NS_CHARSETALIAS_CID); NS_DEFINE_NAMED_CID(NS_PLATFORMCHARSET_CID); #ifdef XP_WIN NS_DEFINE_NAMED_CID(NS_COLLATION_CID); @@ -124,7 +123,6 @@ static const mozilla::Module::CIDEntry kIntlCIDs[] = { { &kNS_COLLATIONFACTORY_CID, false, NULL, nsCollationFactoryConstructor }, { &kNS_SCRIPTABLEDATEFORMAT_CID, false, NULL, NS_NewScriptableDateFormat }, { &kNS_LANGUAGEATOMSERVICE_CID, false, NULL, nsLanguageAtomServiceConstructor }, - { &kNS_CHARSETALIAS_CID, false, NULL, nsCharsetAlias2Constructor }, { &kNS_PLATFORMCHARSET_CID, false, NULL, nsPlatformCharsetConstructor }, #ifdef XP_WIN { &kNS_COLLATION_CID, false, NULL, nsCollationWinConstructor }, @@ -161,7 +159,6 @@ static const mozilla::Module::ContractIDEntry kIntlContracts[] = { { NS_COLLATIONFACTORY_CONTRACTID, &kNS_COLLATIONFACTORY_CID }, { NS_SCRIPTABLEDATEFORMAT_CONTRACTID, &kNS_SCRIPTABLEDATEFORMAT_CID }, { NS_LANGUAGEATOMSERVICE_CONTRACTID, &kNS_LANGUAGEATOMSERVICE_CID }, - { NS_CHARSETALIAS_CONTRACTID, &kNS_CHARSETALIAS_CID }, { NS_PLATFORMCHARSET_CONTRACTID, &kNS_PLATFORMCHARSET_CID }, #ifdef XP_WIN { NS_COLLATION_CONTRACTID, &kNS_COLLATION_CID }, diff --git a/intl/chardet/src/nsCharDetConstructors.h b/intl/chardet/src/nsCharDetConstructors.h index cea7d09858b7..cf96312ff286 100644 --- a/intl/chardet/src/nsCharDetConstructors.h +++ b/intl/chardet/src/nsCharDetConstructors.h @@ -47,7 +47,6 @@ // chardet #include "nsISupports.h" #include "nsICharsetDetector.h" -#include "nsICharsetAlias.h" #include "nsICharsetDetectionObserver.h" #include "nsIStringCharsetDetector.h" #include "nsCyrillicDetector.h" diff --git a/intl/locale/public/Makefile.in b/intl/locale/public/Makefile.in index aafb09d2d7a7..e7ce472d379a 100644 --- a/intl/locale/public/Makefile.in +++ b/intl/locale/public/Makefile.in @@ -52,7 +52,7 @@ EXPORTS = \ nsPosixLocale.h \ nsIOS2Locale.h \ nsWin32Locale.h \ - nsICharsetAlias.h \ + nsCharsetAlias.h \ nsIPlatformCharset.h \ nsLocaleCID.h \ $(NULL) diff --git a/intl/locale/src/nsCharsetAlias.h b/intl/locale/public/nsCharsetAlias.h similarity index 73% rename from intl/locale/src/nsCharsetAlias.h rename to intl/locale/public/nsCharsetAlias.h index f1ab02afbb5a..d443d2fbe1de 100644 --- a/intl/locale/src/nsCharsetAlias.h +++ b/intl/locale/public/nsCharsetAlias.h @@ -1,4 +1,4 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -12,7 +12,7 @@ * for the specific language governing rights and limitations under the * License. * - * The Original Code is mozilla.org code. + * The Original Code is Mozilla Communicator client code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. @@ -34,27 +34,18 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -#ifndef nsCharsetAlias_h__ -#define nsCharsetAlias_h__ -#include "nsICharsetAlias.h" +#ifndef nsCharsetAlias_h___ +#define nsCharsetAlias_h___ -//============================================================== -class nsCharsetAlias2 : public nsICharsetAlias +#include "nscore.h" +#include "nsStringGlue.h" + +class nsCharsetAlias { - NS_DECL_ISUPPORTS - public: - - nsCharsetAlias2(); - virtual ~nsCharsetAlias2(); - - NS_IMETHOD GetPreferred(const nsACString& aAlias, nsACString& aResult); - - NS_IMETHOD Equals(const nsACString& aCharset1, const nsACString& aCharset2, bool* oResult) ; - + static nsresult GetPreferred(const nsACString& aAlias, nsACString& aResult); + static nsresult Equals(const nsACString& aCharset1, const nsACString& aCharset2, bool* aResult); }; -#endif // nsCharsetAlias_h__ - - +#endif /* nsCharsetAlias_h___ */ diff --git a/intl/locale/src/Makefile.in b/intl/locale/src/Makefile.in index e58202900fcb..2bb94e23779e 100644 --- a/intl/locale/src/Makefile.in +++ b/intl/locale/src/Makefile.in @@ -67,7 +67,7 @@ CPPSRCS = \ nsLanguageAtomService.cpp \ nsLocale.cpp \ nsLocaleService.cpp \ - nsCharsetAliasImp.cpp \ + nsCharsetAlias.cpp \ nsUConvPropertySearch.cpp \ $(NULL) @@ -89,7 +89,7 @@ FORCE_STATIC_LIB = 1 include $(topsrcdir)/config/rules.mk -nsCharsetAliasImp.$(OBJ_SUFFIX): charsetalias.properties.h +nsCharsetAlias.$(OBJ_SUFFIX): charsetalias.properties.h charsetalias.properties.h: props2arrays.py charsetalias.properties $(PYTHON) $^ $@ diff --git a/intl/locale/src/nsCharsetAliasImp.cpp b/intl/locale/src/nsCharsetAlias.cpp similarity index 74% rename from intl/locale/src/nsCharsetAliasImp.cpp rename to intl/locale/src/nsCharsetAlias.cpp index 6d1abab50857..f33627346ca5 100644 --- a/intl/locale/src/nsCharsetAliasImp.cpp +++ b/intl/locale/src/nsCharsetAlias.cpp @@ -37,7 +37,7 @@ #include "mozilla/Util.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "pratom.h" // for NS_IMPL_IDS only @@ -46,46 +46,34 @@ #include "nsReadableUtils.h" #include "nsUnicharUtils.h" #include "nsUConvPropertySearch.h" -#include "nsCharsetAlias.h" using namespace mozilla; -//-------------------------------------------------------------- -NS_IMPL_THREADSAFE_ISUPPORTS1(nsCharsetAlias2, nsICharsetAlias) - -//-------------------------------------------------------------- -nsCharsetAlias2::nsCharsetAlias2() -{ -} -//-------------------------------------------------------------- -nsCharsetAlias2::~nsCharsetAlias2() -{ -} - // static const char* kAliases[][3] = { #include "charsetalias.properties.h" }; //-------------------------------------------------------------- -NS_IMETHODIMP nsCharsetAlias2::GetPreferred(const nsACString& aAlias, - nsACString& oResult) +// static +nsresult +nsCharsetAlias::GetPreferred(const nsACString& aAlias, + nsACString& oResult) { if (aAlias.IsEmpty()) return NS_ERROR_NULL_POINTER; nsCAutoString key(aAlias); ToLowerCase(key); - nsresult rv = nsUConvPropertySearch::SearchPropertyValue(kAliases, + return nsUConvPropertySearch::SearchPropertyValue(kAliases, ArrayLength(kAliases), key, oResult); - - return rv; } //-------------------------------------------------------------- -NS_IMETHODIMP -nsCharsetAlias2::Equals(const nsACString& aCharset1, - const nsACString& aCharset2, bool* oResult) +// static +nsresult +nsCharsetAlias::Equals(const nsACString& aCharset1, + const nsACString& aCharset2, bool* oResult) { nsresult res = NS_OK; @@ -101,15 +89,15 @@ nsCharsetAlias2::Equals(const nsACString& aCharset1, *oResult = false; nsCAutoString name1; - nsCAutoString name2; - res = this->GetPreferred(aCharset1, name1); - if(NS_SUCCEEDED(res)) { - res = this->GetPreferred(aCharset2, name2); - if(NS_SUCCEEDED(res)) { - *oResult = name1.Equals(name2); - } - } - - return res; -} + res = GetPreferred(aCharset1, name1); + if (NS_FAILED(res)) + return res; + nsCAutoString name2; + res = GetPreferred(aCharset2, name2); + if (NS_FAILED(res)) + return res; + + *oResult = name1.Equals(name2); + return NS_OK; +} diff --git a/intl/locale/src/nsLocaleConstructors.h b/intl/locale/src/nsLocaleConstructors.h index 448c018860a6..de02dfcf5a07 100644 --- a/intl/locale/src/nsLocaleConstructors.h +++ b/intl/locale/src/nsLocaleConstructors.h @@ -46,7 +46,6 @@ #include "nsIScriptableDateFormat.h" #include "nsIServiceManager.h" #include "nsLanguageAtomService.h" -#include "nsCharsetAlias.h" #include "nsPlatformCharset.h" #include "nsLocaleCID.h" @@ -100,7 +99,6 @@ NSLOCALE_MAKE_CTOR(CreateLocaleService, nsILocaleService, NS_NewLocaleService) NS_GENERIC_FACTORY_CONSTRUCTOR(nsCollationFactory) //NS_GENERIC_FACTORY_CONSTRUCTOR(nsScriptableDateTimeFormat) NS_GENERIC_FACTORY_CONSTRUCTOR(nsLanguageAtomService) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsCharsetAlias2) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPlatformCharset, Init) #ifdef XP_WIN diff --git a/intl/uconv/src/nsCharsetConverterManager.cpp b/intl/uconv/src/nsCharsetConverterManager.cpp index 109a758dfe6e..5646efa7a819 100644 --- a/intl/uconv/src/nsCharsetConverterManager.cpp +++ b/intl/uconv/src/nsCharsetConverterManager.cpp @@ -40,7 +40,7 @@ #include "nsString.h" #include "nsReadableUtils.h" #include "nsUnicharUtils.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "nsIServiceManager.h" #include "nsICategoryManager.h" #include "nsICharsetConverterManager.h" @@ -308,10 +308,8 @@ nsCharsetConverterManager::GetCharsetAlias(const char * aCharset, // We try to obtain the preferred name for this charset from the charset // aliases. nsresult rv; - nsCOMPtr csAlias(do_GetService(NS_CHARSETALIAS_CONTRACTID, &rv)); - NS_ENSURE_SUCCESS(rv, rv); - rv = csAlias->GetPreferred(nsDependentCString(aCharset), aResult); + rv = nsCharsetAlias::GetPreferred(nsDependentCString(aCharset), aResult); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; diff --git a/intl/uconv/tests/nsconv.cpp b/intl/uconv/tests/nsconv.cpp index a423892648d5..2b6b03744d4d 100644 --- a/intl/uconv/tests/nsconv.cpp +++ b/intl/uconv/tests/nsconv.cpp @@ -46,8 +46,6 @@ #include "nsIUnicodeEncoder.h" #include "nsIUnicodeDecoder.h" -#include "nsICharsetAlias.h" - static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID); #include @@ -90,15 +88,6 @@ int main(int argc, const char** argv) return -1; } - // Get the charset alias manager - nsCOMPtr aliasmgr = - do_GetService(NS_CHARSETALIAS_CONTRACTID, &res); - if (NS_FAILED(res)) - { - fprintf(stderr, "Cannot get Charset Alias Manager %x\n", res); - return -1; - } - int i; if(argc > 4) { @@ -111,7 +100,7 @@ int main(int argc, const char** argv) // First check if a charset alias was given, // and convert to the canonical name - res = aliasmgr->GetPreferred(nsDependentCString(argv[i+1]), str); + res = ccMain->GetCharsetAlias(argv[i+1], str); if (NS_FAILED(res)) { fprintf(stderr, "Cannot get charset alias for %s %x\n", @@ -136,7 +125,7 @@ int main(int argc, const char** argv) // First check if a charset alias was given, // and convert to the canonical name - res = aliasmgr->GetPreferred(nsDependentCString(argv[i+1]), str); + res = ccMain->GetCharsetAlias(argv[i+1], str); if (NS_FAILED(res)) { fprintf(stderr, "Cannot get charset alias for %s %x\n", diff --git a/ipc/chromium/src/base/histogram.cc b/ipc/chromium/src/base/histogram.cc index 56b9a2cf08b0..a6d3579957c0 100644 --- a/ipc/chromium/src/base/histogram.cc +++ b/ipc/chromium/src/base/histogram.cc @@ -916,6 +916,52 @@ BooleanHistogram::BooleanHistogram(const std::string& name) : LinearHistogram(name, 1, 2, 3) { } +//------------------------------------------------------------------------------ +// FlagHistogram: +//------------------------------------------------------------------------------ + +Histogram * +FlagHistogram::FactoryGet(const std::string &name, Flags flags) +{ + Histogram *h(nsnull); + + if (!StatisticsRecorder::FindHistogram(name, &h)) { + // To avoid racy destruction at shutdown, the following will be leaked. + FlagHistogram *fh = new FlagHistogram(name); + fh->InitializeBucketRange(); + fh->SetFlags(flags); + size_t zero_index = fh->BucketIndex(0); + fh->Histogram::Accumulate(1, 1, zero_index); + h = StatisticsRecorder::RegisterOrDeleteDuplicate(fh); + } + + return h; +} + +FlagHistogram::FlagHistogram(const std::string &name) + : BooleanHistogram(name), mSwitched(false) { +} + +Histogram::ClassType +FlagHistogram::histogram_type() const +{ + return FLAG_HISTOGRAM; +} + +void +FlagHistogram::Accumulate(Sample value, Count count, size_t index) +{ + if (mSwitched) { + return; + } + + mSwitched = true; + DCHECK_EQ(value, 1); + Histogram::Accumulate(value, 1, index); + size_t zero_index = BucketIndex(0); + Histogram::Accumulate(1, -1, zero_index); +} + //------------------------------------------------------------------------------ // CustomHistogram: //------------------------------------------------------------------------------ diff --git a/ipc/chromium/src/base/histogram.h b/ipc/chromium/src/base/histogram.h index e80f86a174f8..1995ddbbaf3e 100644 --- a/ipc/chromium/src/base/histogram.h +++ b/ipc/chromium/src/base/histogram.h @@ -276,6 +276,7 @@ class Histogram { HISTOGRAM, LINEAR_HISTOGRAM, BOOLEAN_HISTOGRAM, + FLAG_HISTOGRAM, CUSTOM_HISTOGRAM, NOT_VALID_IN_RENDERER }; @@ -642,7 +643,7 @@ class BooleanHistogram : public LinearHistogram { virtual void AddBoolean(bool value); - private: + protected: explicit BooleanHistogram(const std::string& name); DISALLOW_COPY_AND_ASSIGN(BooleanHistogram); @@ -650,6 +651,25 @@ class BooleanHistogram : public LinearHistogram { //------------------------------------------------------------------------------ +// FlagHistogram is like boolean histogram, but only allows a single off/on value. +class FlagHistogram : public BooleanHistogram +{ +public: + static Histogram *FactoryGet(const std::string &name, Flags flags); + + virtual ClassType histogram_type() const; + + virtual void Accumulate(Sample value, Count count, size_t index); + +private: + explicit FlagHistogram(const std::string &name); + bool mSwitched; + + DISALLOW_COPY_AND_ASSIGN(FlagHistogram); +}; + +//------------------------------------------------------------------------------ + // CustomHistogram is a histogram for a set of custom integers. class CustomHistogram : public Histogram { public: diff --git a/ipc/testshell/XPCShellEnvironment.cpp b/ipc/testshell/XPCShellEnvironment.cpp index c297635df6ee..4a5f56e79889 100644 --- a/ipc/testshell/XPCShellEnvironment.cpp +++ b/ipc/testshell/XPCShellEnvironment.cpp @@ -1169,9 +1169,7 @@ XPCShellEnvironment::Init() nsCOMPtr holder; rv = xpc->InitClassesWithNewWrappedGlobal(cx, backstagePass, - NS_GET_IID(nsISupports), principal, - nsnull, nsIXPConnect:: FLAG_SYSTEM_GLOBAL_OBJECT, getter_AddRefs(holder)); diff --git a/js/ipc/ObjectWrapperChild.cpp b/js/ipc/ObjectWrapperChild.cpp index f826177585e9..50f2d650ab38 100644 --- a/js/ipc/ObjectWrapperChild.cpp +++ b/js/ipc/ObjectWrapperChild.cpp @@ -506,11 +506,11 @@ ObjectWrapperChild::AnswerNewEnumerateNext(const JSVariant& in_state, jsval v = JS_GetReservedSlot(state, sNextIdIndexSlot); - jsuint i = JSVAL_TO_INT(v); + int32_t i = JSVAL_TO_INT(v); NS_ASSERTION(i >= 0, "Index of next jsid negative?"); NS_ASSERTION(i <= strIds->Length(), "Index of next jsid too large?"); - if (jsuint(i) == strIds->Length()) { + if (size_t(i) == strIds->Length()) { *status = JS_TRUE; return JSObject_to_JSVariant(cx, NULL, statep); } diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 97046eb75074..5c38969cbace 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -165,6 +165,7 @@ CPPSRCS = \ Parser.cpp \ SemanticAnalysis.cpp \ TokenStream.cpp \ + TestingFunctions.cpp \ LifoAlloc.cpp \ MapObject.cpp \ MemoryMetrics.cpp \ @@ -173,6 +174,7 @@ CPPSRCS = \ RegExp.cpp \ Memory.cpp \ Statistics.cpp \ + StringBuffer.cpp \ Unicode.cpp \ $(NULL) diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp index 745831a3b11c..7e95df0b97a4 100644 --- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -45,6 +45,7 @@ #include "vm/MethodGuard-inl.h" #include "vm/RegExpObject-inl.h" #include "vm/RegExpStatics-inl.h" +#include "vm/StringBuffer-inl.h" using namespace js; using namespace js::types; diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp new file mode 100644 index 000000000000..6de7947092c8 --- /dev/null +++ b/js/src/builtin/TestingFunctions.cpp @@ -0,0 +1,570 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jsapi.h" +#include "jsbool.h" +#include "jscntxt.h" +#include "jscompartment.h" +#include "jsfriendapi.h" +#include "jsgc.h" +#include "jsobj.h" +#include "jsprf.h" +#include "jswrapper.h" + +#include "methodjit/MethodJIT.h" + +using namespace js; +using namespace JS; + +static JSBool +GC(JSContext *cx, unsigned argc, jsval *vp) +{ + JSCompartment *comp = NULL; + if (argc == 1) { + Value arg = vp[2]; + if (arg.isObject()) + comp = UnwrapObject(&arg.toObject())->compartment(); + } + +#ifndef JS_MORE_DETERMINISTIC + size_t preBytes = cx->runtime->gcBytes; +#endif + + JS_CompartmentGC(cx, comp); + + char buf[256] = { '\0' }; +#ifndef JS_MORE_DETERMINISTIC + JS_snprintf(buf, sizeof(buf), "before %lu, after %lu\n", + (unsigned long)preBytes, (unsigned long)cx->runtime->gcBytes); +#endif + JSString *str = JS_NewStringCopyZ(cx, buf); + if (!str) + return false; + *vp = STRING_TO_JSVAL(str); + return true; +} + +static const struct ParamPair { + const char *name; + JSGCParamKey param; +} paramMap[] = { + {"maxBytes", JSGC_MAX_BYTES }, + {"maxMallocBytes", JSGC_MAX_MALLOC_BYTES}, + {"gcBytes", JSGC_BYTES}, + {"gcNumber", JSGC_NUMBER}, + {"sliceTimeBudget", JSGC_SLICE_TIME_BUDGET} +}; + +static JSBool +GCParameter(JSContext *cx, unsigned argc, jsval *vp) +{ + JSString *str; + if (argc == 0) { + str = JS_ValueToString(cx, JSVAL_VOID); + JS_ASSERT(str); + } else { + str = JS_ValueToString(cx, vp[2]); + if (!str) + return JS_FALSE; + vp[2] = STRING_TO_JSVAL(str); + } + + JSFlatString *flatStr = JS_FlattenString(cx, str); + if (!flatStr) + return false; + + size_t paramIndex = 0; + for (;; paramIndex++) { + if (paramIndex == ArrayLength(paramMap)) { + JS_ReportError(cx, + "the first argument argument must be maxBytes, " + "maxMallocBytes, gcStackpoolLifespan, gcBytes or " + "gcNumber"); + return false; + } + if (JS_FlatStringEqualsAscii(flatStr, paramMap[paramIndex].name)) + break; + } + JSGCParamKey param = paramMap[paramIndex].param; + + if (argc == 1) { + uint32_t value = JS_GetGCParameter(cx->runtime, param); + return JS_NewNumberValue(cx, value, &vp[0]); + } + + if (param == JSGC_NUMBER || + param == JSGC_BYTES) { + JS_ReportError(cx, "Attempt to change read-only parameter %s", + paramMap[paramIndex].name); + return false; + } + + uint32_t value; + if (!JS_ValueToECMAUint32(cx, vp[3], &value)) { + JS_ReportError(cx, + "the second argument must be convertable to uint32_t " + "with non-zero value"); + return false; + } + + if (param == JSGC_MAX_BYTES) { + uint32_t gcBytes = JS_GetGCParameter(cx->runtime, JSGC_BYTES); + if (value < gcBytes) { + JS_ReportError(cx, + "attempt to set maxBytes to the value less than the current " + "gcBytes (%u)", + gcBytes); + return false; + } + } + + JS_SetGCParameter(cx->runtime, param, value); + *vp = JSVAL_VOID; + return true; +} + +static JSBool +InternalConst(JSContext *cx, unsigned argc, jsval *vp) +{ + if (argc != 1) { + JS_ReportError(cx, "the function takes exactly one argument"); + return false; + } + + JSString *str = JS_ValueToString(cx, vp[2]); + if (!str) + return false; + JSFlatString *flat = JS_FlattenString(cx, str); + if (!flat) + return false; + + if (JS_FlatStringEqualsAscii(flat, "MARK_STACK_LENGTH")) { + vp[0] = UINT_TO_JSVAL(js::MARK_STACK_LENGTH); + } else { + JS_ReportError(cx, "unknown const name"); + return false; + } + return true; +} + +#ifdef JS_GC_ZEAL +static JSBool +GCZeal(JSContext *cx, unsigned argc, jsval *vp) +{ + uint32_t zeal, frequency = JS_DEFAULT_ZEAL_FREQ; + JSBool compartment = JS_FALSE; + + if (argc > 3) { + ReportUsageError(cx, &JS_CALLEE(cx, vp).toObject(), "Too many arguments"); + return JS_FALSE; + } + if (!JS_ValueToECMAUint32(cx, argc < 1 ? JSVAL_VOID : vp[2], &zeal)) + return JS_FALSE; + if (argc >= 2) + if (!JS_ValueToECMAUint32(cx, vp[3], &frequency)) + return JS_FALSE; + if (argc >= 3) + compartment = js_ValueToBoolean(vp[3]); + + JS_SetGCZeal(cx, (uint8_t)zeal, frequency, compartment); + *vp = JSVAL_VOID; + return JS_TRUE; +} + +static JSBool +ScheduleGC(JSContext *cx, unsigned argc, jsval *vp) +{ + uint32_t count; + bool compartment = false; + + if (argc != 1 && argc != 2) { + ReportUsageError(cx, &JS_CALLEE(cx, vp).toObject(), "Wrong number of arguments"); + return JS_FALSE; + } + if (!JS_ValueToECMAUint32(cx, vp[2], &count)) + return JS_FALSE; + if (argc == 2) + compartment = js_ValueToBoolean(vp[3]); + + JS_ScheduleGC(cx, count, compartment); + *vp = JSVAL_VOID; + return JS_TRUE; +} + +static JSBool +VerifyBarriers(JSContext *cx, unsigned argc, jsval *vp) +{ + if (argc) { + ReportUsageError(cx, &JS_CALLEE(cx, vp).toObject(), "Too many arguments"); + return JS_FALSE; + } + gc::VerifyBarriers(cx); + *vp = JSVAL_VOID; + return JS_TRUE; +} + +static JSBool +GCSlice(JSContext *cx, unsigned argc, jsval *vp) +{ + uint32_t budget; + + if (argc != 1) { + ReportUsageError(cx, &JS_CALLEE(cx, vp).toObject(), "Wrong number of arguments"); + return JS_FALSE; + } + + if (!JS_ValueToECMAUint32(cx, vp[2], &budget)) + return JS_FALSE; + + GCDebugSlice(cx, budget); + *vp = JSVAL_VOID; + return JS_TRUE; +} + +static JSBool +DeterministicGC(JSContext *cx, unsigned argc, jsval *vp) +{ + if (argc != 1) { + ReportUsageError(cx, &JS_CALLEE(cx, vp).toObject(), "Wrong number of arguments"); + return JS_FALSE; + } + + gc::SetDeterministicGC(cx, js_ValueToBoolean(vp[2])); + *vp = JSVAL_VOID; + return JS_TRUE; +} +#endif /* JS_GC_ZEAL */ + +typedef struct JSCountHeapNode JSCountHeapNode; + +struct JSCountHeapNode { + void *thing; + JSGCTraceKind kind; + JSCountHeapNode *next; +}; + +typedef struct JSCountHeapTracer { + JSTracer base; + JSDHashTable visited; + bool ok; + JSCountHeapNode *traceList; + JSCountHeapNode *recycleList; +} JSCountHeapTracer; + +static void +CountHeapNotify(JSTracer *trc, void **thingp, JSGCTraceKind kind) +{ + JSCountHeapTracer *countTracer; + JSDHashEntryStub *entry; + JSCountHeapNode *node; + void *thing = *thingp; + + JS_ASSERT(trc->callback == CountHeapNotify); + countTracer = (JSCountHeapTracer *)trc; + if (!countTracer->ok) + return; + + entry = (JSDHashEntryStub *) + JS_DHashTableOperate(&countTracer->visited, thing, JS_DHASH_ADD); + if (!entry) { + countTracer->ok = false; + return; + } + if (entry->key) + return; + entry->key = thing; + + node = countTracer->recycleList; + if (node) { + countTracer->recycleList = node->next; + } else { + node = (JSCountHeapNode *) js_malloc(sizeof *node); + if (!node) { + countTracer->ok = false; + return; + } + } + node->thing = thing; + node->kind = kind; + node->next = countTracer->traceList; + countTracer->traceList = node; +} + +static const struct TraceKindPair { + const char *name; + int32_t kind; +} traceKindNames[] = { + { "all", -1 }, + { "object", JSTRACE_OBJECT }, + { "string", JSTRACE_STRING }, +#if JS_HAS_XML_SUPPORT + { "xml", JSTRACE_XML }, +#endif +}; + +static JSBool +CountHeap(JSContext *cx, unsigned argc, jsval *vp) +{ + void* startThing; + JSGCTraceKind startTraceKind; + jsval v; + int32_t traceKind; + JSString *str; + JSCountHeapTracer countTracer; + JSCountHeapNode *node; + size_t counter; + + startThing = NULL; + startTraceKind = JSTRACE_OBJECT; + if (argc > 0) { + v = JS_ARGV(cx, vp)[0]; + if (JSVAL_IS_TRACEABLE(v)) { + startThing = JSVAL_TO_TRACEABLE(v); + startTraceKind = JSVAL_TRACE_KIND(v); + } else if (!JSVAL_IS_NULL(v)) { + JS_ReportError(cx, + "the first argument is not null or a heap-allocated " + "thing"); + return JS_FALSE; + } + } + + traceKind = -1; + if (argc > 1) { + str = JS_ValueToString(cx, JS_ARGV(cx, vp)[1]); + if (!str) + return JS_FALSE; + JSFlatString *flatStr = JS_FlattenString(cx, str); + if (!flatStr) + return JS_FALSE; + for (size_t i = 0; ;) { + if (JS_FlatStringEqualsAscii(flatStr, traceKindNames[i].name)) { + traceKind = traceKindNames[i].kind; + break; + } + if (++i == ArrayLength(traceKindNames)) { + JSAutoByteString bytes(cx, str); + if (!!bytes) + JS_ReportError(cx, "trace kind name '%s' is unknown", bytes.ptr()); + return JS_FALSE; + } + } + } + + JS_TracerInit(&countTracer.base, JS_GetRuntime(cx), CountHeapNotify); + if (!JS_DHashTableInit(&countTracer.visited, JS_DHashGetStubOps(), + NULL, sizeof(JSDHashEntryStub), + JS_DHASH_DEFAULT_CAPACITY(100))) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + countTracer.ok = true; + countTracer.traceList = NULL; + countTracer.recycleList = NULL; + + if (!startThing) { + JS_TraceRuntime(&countTracer.base); + } else { + JS_SET_TRACING_NAME(&countTracer.base, "root"); + JS_CallTracer(&countTracer.base, startThing, startTraceKind); + } + + counter = 0; + while ((node = countTracer.traceList) != NULL) { + if (traceKind == -1 || node->kind == traceKind) + counter++; + countTracer.traceList = node->next; + node->next = countTracer.recycleList; + countTracer.recycleList = node; + JS_TraceChildren(&countTracer.base, node->thing, node->kind); + } + while ((node = countTracer.recycleList) != NULL) { + countTracer.recycleList = node->next; + js_free(node); + } + JS_DHashTableFinish(&countTracer.visited); + if (!countTracer.ok) { + JS_ReportOutOfMemory(cx); + return false; + } + + return JS_NewNumberValue(cx, (double) counter, vp); +} + +static unsigned finalizeCount = 0; + +static void +finalize_counter_finalize(JSContext *cx, JSObject *obj) +{ + JS_ATOMIC_INCREMENT(&finalizeCount); +} + +static JSClass FinalizeCounterClass = { + "FinalizeCounter", JSCLASS_IS_ANONYMOUS, + JS_PropertyStub, /* addProperty */ + JS_PropertyStub, /* delProperty */ + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + finalize_counter_finalize +}; + +static JSBool +MakeFinalizeObserver(JSContext *cx, unsigned argc, jsval *vp) +{ + JSObject *obj = JS_NewObjectWithGivenProto(cx, &FinalizeCounterClass, NULL, + JS_GetGlobalObject(cx)); + if (!obj) + return false; + *vp = OBJECT_TO_JSVAL(obj); + return true; +} + +static JSBool +FinalizeCount(JSContext *cx, unsigned argc, jsval *vp) +{ + *vp = INT_TO_JSVAL(finalizeCount); + return true; +} + +JSBool +MJitCodeStats(JSContext *cx, unsigned argc, jsval *vp) +{ +#ifdef JS_METHODJIT + JSRuntime *rt = cx->runtime; + AutoLockGC lock(rt); + size_t n = 0; + for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) { + n += (*c)->sizeOfMjitCode(); + } + JS_SET_RVAL(cx, vp, INT_TO_JSVAL(n)); +#else + JS_SET_RVAL(cx, vp, JSVAL_VOID); +#endif + return true; +} + +JSBool +MJitChunkLimit(JSContext *cx, unsigned argc, jsval *vp) +{ + if (argc != 1) { + ReportUsageError(cx, &JS_CALLEE(cx, vp).toObject(), "Wrong number of arguments"); + return JS_FALSE; + } + + double t; + if (!JS_ValueToNumber(cx, JS_ARGV(cx, vp)[0], &t)) + return JS_FALSE; + +#ifdef JS_METHODJIT + mjit::SetChunkLimit((uint32_t) t); +#endif + + // Clear out analysis information which might refer to code compiled with + // the previous chunk limit. + JS_GC(cx); + + vp->setUndefined(); + return true; +} + +static JSBool +Terminate(JSContext *cx, unsigned arg, jsval *vp) +{ + JS_ClearPendingException(cx); + return JS_FALSE; +} + +static JSFunctionSpecWithHelp TestingFunctions[] = { + JS_FN_HELP("gc", ::GC, 0, 0, +"gc([obj])", +" Run the garbage collector. When obj is given, GC only its compartment."), + + JS_FN_HELP("gcparam", GCParameter, 2, 0, +"gcparam(name [, value])", +" Wrapper for JS_[GS]etGCParameter. The name is either maxBytes,\n" +" maxMallocBytes, gcBytes, gcNumber, or sliceTimeBudget."), + + JS_FN_HELP("countHeap", CountHeap, 0, 0, +"countHeap([start[, kind]])", +" Count the number of live GC things in the heap or things reachable from\n" +" start when it is given and is not null. kind is either 'all' (default) to\n" +" count all things or one of 'object', 'double', 'string', 'function',\n" +" 'qname', 'namespace', 'xml' to count only things of that kind."), + + JS_FN_HELP("makeFinalizeObserver", MakeFinalizeObserver, 0, 0, +"makeFinalizeObserver()", +" Get a special object whose finalization increases the counter returned\n" +" by the finalizeCount function."), + + JS_FN_HELP("finalizeCount", FinalizeCount, 0, 0, +"finalizeCount()", +" Return the current value of the finalization counter that is incremented\n" +" each time an object returned by the makeFinalizeObserver is finalized."), + +#ifdef JS_GC_ZEAL + JS_FN_HELP("gczeal", GCZeal, 2, 0, +"gczeal(level, [period], [compartmentGC?])", +" Specifies how zealous the garbage collector should be. Values for level:\n" +" 0: Normal amount of collection\n" +" 1: Collect when roots are added or removed\n" +" 2: Collect when memory is allocated\n" +" 3: Collect when the window paints (browser only)\n" +" 4: Verify write barriers between instructions\n" +" 5: Verify write barriers between paints\n" +" Period specifies that collection happens every n allocations.\n" +" If compartmentGC is true, the collections will be compartmental."), + + JS_FN_HELP("schedulegc", ScheduleGC, 1, 0, +"schedulegc(num, [compartmentGC?])", +" Schedule a GC to happen after num allocations."), + + JS_FN_HELP("verifybarriers", VerifyBarriers, 0, 0, +"verifybarriers()", +" Start or end a run of the write barrier verifier."), + + JS_FN_HELP("gcslice", GCSlice, 1, 0, +"gcslice(n)", +" Run an incremental GC slice that marks about n objects."), + + JS_FN_HELP("deterministicgc", DeterministicGC, 1, 0, +"deterministicgc(true|false)", +" If true, only allow determinstic GCs to run."), +#endif + + JS_FN_HELP("internalConst", InternalConst, 1, 0, +"internalConst(name)", +" Query an internal constant for the engine. See InternalConst source for\n" +" the list of constant names."), + +#ifdef JS_METHODJIT + JS_FN_HELP("mjitcodestats", MJitCodeStats, 0, 0, +"mjitcodestats()", +"Return stats on mjit code memory usage."), +#endif + + JS_FN_HELP("mjitChunkLimit", MJitChunkLimit, 1, 0, +"mjitChunkLimit(N)", +" Specify limit on compiled chunk size during mjit compilation."), + + JS_FN_HELP("terminate", Terminate, 0, 0, +"terminate()", +" Terminate JavaScript execution, as if we had run out of\n" +" memory or been terminated by the slow script dialog."), + + JS_FS_END +}; + +namespace js { + +bool +DefineTestingFunctions(JSContext *cx, JSObject *obj) +{ + return JS_DefineFunctionsWithHelp(cx, obj, TestingFunctions); +} + +} /* namespace js */ diff --git a/js/src/builtin/TestingFunctions.h b/js/src/builtin/TestingFunctions.h new file mode 100644 index 000000000000..186c64c11587 --- /dev/null +++ b/js/src/builtin/TestingFunctions.h @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TestingFunctions_h__ +#define TestingFunctions_h__ + +namespace js { + +bool +DefineTestingFunctions(JSContext *cx, JSObject *obj); + +} /* namespace js */ + +#endif /* TestingFunctions_h__ */ diff --git a/js/src/config/rules.mk b/js/src/config/rules.mk index 6c4f7ecdace6..5e299925f5dd 100644 --- a/js/src/config/rules.mk +++ b/js/src/config/rules.mk @@ -148,6 +148,9 @@ xpcshell-tests: $(testxpcsrcdir)/runxpcshelltests.py \ --symbols-path=$(DIST)/crashreporter-symbols \ --build-info-json=$(DEPTH)/mozinfo.json \ + --tests-root-dir=$(testxpcobjdir) \ + --xunit-file=$(testxpcobjdir)/$(relativesrcdir)/results.xml \ + --xunit-suite-name=xpcshell \ $(EXTRA_TEST_ARGS) \ $(LIBXUL_DIST)/bin/xpcshell \ $(foreach dir,$(XPCSHELL_TESTS),$(testxpcobjdir)/$(relativesrcdir)/$(dir)) diff --git a/js/src/configure.in b/js/src/configure.in index 82693952662e..a4a94b7e39bf 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -616,9 +616,10 @@ if test "$GXX" = "yes"; then GNU_CXX=1 CXX_VERSION=`$CXX -v 2>&1 | grep 'gcc version'` fi -if test "`echo | $AS -v 2>&1 | grep -c GNU`" != "0"; then +if test "`echo | $AS -o conftest.out -v 2>&1 | grep -c GNU`" != "0"; then GNU_AS=1 fi +rm -f conftest.out if test "`echo | $LD -v 2>&1 | grep -c GNU`" != "0"; then GNU_LD=1 fi @@ -1766,6 +1767,33 @@ if test -n "$CLANG_CXX"; then _WARNINGS_CXXFLAGS="-Qunused-arguments ${_WARNINGS_CXXFLAGS}" fi +dnl ======================================================== +dnl = Use Address Sanitizer +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(address-sanitizer, +[ --enable-address-sanitizer Enable Address Sanitizer (default=no)], + MOZ_ASAN=1, + MOZ_ASAN= ) +if test -n "$MOZ_ASAN"; then + MOZ_LLVM_HACKS=1 + AC_DEFINE(MOZ_ASAN) +fi +AC_SUBST(MOZ_ASAN) + +dnl ======================================================== +dnl = Enable hacks required for LLVM instrumentations +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(llvm-hacks, +[ --enable-llvm-hacks Enable workarounds required for several LLVM instrumentations (default=no)], + MOZ_LLVM_HACKS=1, + MOZ_LLVM_HACKS= ) +if test -n "$MOZ_LLVM_HACKS"; then + MOZ_NO_WLZDEFS=1 + MOZ_CFLAGS_NSS=1 +fi +AC_SUBST(MOZ_NO_WLZDEFS) +AC_SUBST(MOZ_CFLAGS_NSS) + dnl ======================================================== dnl GNU specific defaults dnl ======================================================== @@ -1774,8 +1802,13 @@ if test "$GNU_CC"; then MKCSHLIB='$(CC) $(CFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -Wl,-h,$@ -o $@' DSO_LDOPTS='-shared' if test "$GCC_USE_GNU_LD"; then - # Don't allow undefined symbols in libraries - DSO_LDOPTS="$DSO_LDOPTS -Wl,-z,defs" + # Some tools like ASan use a runtime library that is only + # linked against executables, so we must allow undefined + # symbols for shared objects in some cases. + if test -z "$MOZ_NO_WLZDEFS"; then + # Don't allow undefined symbols in libraries + DSO_LDOPTS="$DSO_LDOPTS -Wl,-z,defs" + fi fi WARNINGS_AS_ERRORS='-Werror -Wno-error=uninitialized' DSO_CFLAGS='' diff --git a/js/src/ctypes/CTypes.cpp b/js/src/ctypes/CTypes.cpp index ee2f85ed7750..265fecaaf034 100644 --- a/js/src/ctypes/CTypes.cpp +++ b/js/src/ctypes/CTypes.cpp @@ -1574,7 +1574,7 @@ jsvalToPtrExplicit(JSContext* cx, jsval val, uintptr_t* result) template void -IntegerToString(IntegerType i, jsuint radix, Vector& result) +IntegerToString(IntegerType i, int radix, Vector& result) { JS_STATIC_ASSERT(numeric_limits::is_exact); @@ -1955,7 +1955,7 @@ ImplicitConvert(JSContext* cx, JS_IsArrayObject(cx, JSVAL_TO_OBJECT(val))) { // Convert each element of the array by calling ImplicitConvert. JSObject* sourceArray = JSVAL_TO_OBJECT(val); - jsuint sourceLength; + uint32_t sourceLength; if (!JS_GetArrayLength(cx, sourceArray, &sourceLength) || targetLength != size_t(sourceLength)) { JS_ReportError(cx, "ArrayType length does not match source array length"); @@ -1971,7 +1971,7 @@ ImplicitConvert(JSContext* cx, return false; } - for (jsuint i = 0; i < sourceLength; ++i) { + for (uint32_t i = 0; i < sourceLength; ++i) { js::AutoValueRooter item(cx); if (!JS_GetElement(cx, sourceArray, i, item.jsval_addr())) return false; @@ -4112,7 +4112,7 @@ StructType::Create(JSContext* cx, unsigned argc, jsval* vp) JSBool StructType::DefineInternal(JSContext* cx, JSObject* typeObj, JSObject* fieldsObj) { - jsuint len; + uint32_t len; ASSERT_OK(JS_GetArrayLength(cx, fieldsObj, &len)); // Get the common prototype for CData objects of this type from @@ -4150,7 +4150,7 @@ StructType::DefineInternal(JSContext* cx, JSObject* typeObj, JSObject* fieldsObj structSize = 0; structAlign = 0; - for (jsuint i = 0; i < len; ++i) { + for (uint32_t i = 0; i < len; ++i) { js::AutoValueRooter item(cx); if (!JS_GetElement(cx, fieldsObj, i, item.jsval_addr())) return JS_FALSE; @@ -4925,7 +4925,7 @@ FunctionType::Create(JSContext* cx, unsigned argc, jsval* vp) } arrayObj = JSVAL_TO_OBJECT(argv[2]); - jsuint len; + uint32_t len; ASSERT_OK(JS_GetArrayLength(cx, arrayObj, &len)); if (!argTypes.appendN(JSVAL_VOID, len)) { @@ -4937,7 +4937,7 @@ FunctionType::Create(JSContext* cx, unsigned argc, jsval* vp) // Pull out the argument types from the array, if any. JS_ASSERT(!argTypes.length() || arrayObj); js::AutoArrayRooter items(cx, argTypes.length(), argTypes.begin()); - for (jsuint i = 0; i < argTypes.length(); ++i) { + for (uint32_t i = 0; i < argTypes.length(); ++i) { if (!JS_GetElement(cx, arrayObj, i, &argTypes[i])) return JS_FALSE; } @@ -4956,7 +4956,7 @@ FunctionType::CreateInternal(JSContext* cx, jsval abi, jsval rtype, jsval* argtypes, - jsuint arglen) + unsigned arglen) { // Determine and check the types, and prepare the function CIF. AutoPtr fninfo(NewFunctionInfo(cx, abi, rtype, argtypes, arglen)); @@ -5109,7 +5109,7 @@ FunctionType::Call(JSContext* cx, } jsval* argv = JS_ARGV(cx, vp); - for (jsuint i = 0; i < argcFixed; ++i) + for (unsigned i = 0; i < argcFixed; ++i) if (!ConvertArgument(cx, argv[i], fninfo->mArgTypes[i], &values[i], &strings)) return false; @@ -6044,7 +6044,7 @@ Int64Base::ToString(JSContext* cx, return JS_FALSE; } - jsuint radix = 10; + int radix = 10; if (argc == 1) { jsval arg = JS_ARGV(cx, vp)[0]; if (JSVAL_IS_INT(arg)) diff --git a/js/src/ctypes/CTypes.h b/js/src/ctypes/CTypes.h index 65322775c916..f1a98be08776 100644 --- a/js/src/ctypes/CTypes.h +++ b/js/src/ctypes/CTypes.h @@ -496,7 +496,7 @@ namespace StructType { namespace FunctionType { JSObject* CreateInternal(JSContext* cx, jsval abi, jsval rtype, - jsval* argtypes, jsuint arglen); + jsval* argtypes, unsigned arglen); JSObject* ConstructWithObject(JSContext* cx, JSObject* typeObj, JSObject* refObj, PRFuncPtr fnptr, JSObject* result); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 20d613f68b80..9d9e9c2658c0 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1010,7 +1010,7 @@ BytecodeEmitter::shouldNoteClosedName(ParseNode *pn) static int AdjustBlockSlot(JSContext *cx, BytecodeEmitter *bce, int slot) { - JS_ASSERT((jsuint) slot < bce->maxStackDepth); + JS_ASSERT((unsigned) slot < bce->maxStackDepth); if (bce->inFunction()) { slot += bce->bindings.countVars(); if ((unsigned) slot >= SLOTNO_LIMIT) { @@ -2468,7 +2468,7 @@ EmitSwitch(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) continue; } i = pn3->pn_pval->toInt32(); - if ((jsuint)(i + (int)JS_BIT(15)) >= (jsuint)JS_BIT(16)) { + if ((unsigned)(i + (int)JS_BIT(15)) >= (unsigned)JS_BIT(16)) { switchOp = JSOP_LOOKUPSWITCH; continue; } @@ -3068,7 +3068,7 @@ EmitDestructuringOpsHelper(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, { JS_ASSERT(emitOption != DefineVars); - jsuint index; + unsigned index; ParseNode *pn2, *pn3; JSBool doElemOp; @@ -3303,7 +3303,7 @@ static JSBool EmitGroupAssignment(JSContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *lhs, ParseNode *rhs) { - jsuint depth, limit, i, nslots; + unsigned depth, limit, i, nslots; ParseNode *pn; depth = limit = (unsigned) bce->stackDepth; @@ -4086,7 +4086,11 @@ EmitCatch(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) return true; } -static bool +/* + * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr12127. See + * the comment on EmitSwitch. + */ +MOZ_NEVER_INLINE static bool EmitTry(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) { StmtInfo stmtInfo; @@ -4446,7 +4450,11 @@ EmitIf(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) * JSOP_LEAVEBLOCKEXPR to the beginning of the let and is only needed for * let-expressions. */ -static bool +/* + * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr12127. See + * the comment on EmitSwitch. + */ +MOZ_NEVER_INLINE static bool EmitLet(JSContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) { JS_ASSERT(pnLet->isArity(PN_BINARY)); @@ -4590,7 +4598,11 @@ EmitXMLProcessingInstruction(JSContext *cx, BytecodeEmitter *bce, XMLProcessingI } #endif -static bool +/* + * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr12127. See + * the comment on EmitSwitch. + */ +MOZ_NEVER_INLINE static bool EmitLexicalScope(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) { JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE)); @@ -4931,6 +4943,9 @@ EmitNormalFor(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top) jmp = EmitJump(cx, bce, JSOP_GOTO, 0); if (jmp < 0) return false; + } else { + if (op != JSOP_NOP && Emit1(cx, bce, JSOP_NOP) < 0) + return false; } top = bce->offset(); @@ -5808,7 +5823,11 @@ EmitIncOrDec(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) return true; } -static bool +/* + * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr12127. See + * the comment on EmitSwitch. + */ +MOZ_NEVER_INLINE static bool EmitLabel(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) { /* diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 81a6de860cf4..29465f3258fa 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -6810,7 +6810,7 @@ Parser::primaryExpr(TokenKind tt, bool afterDoubleDot) case TOK_LB: { JSBool matched; - jsuint index; + unsigned index; pn = ListNode::create(PNK_RB, tc); if (!pn) diff --git a/js/src/gc/Barrier-inl.h b/js/src/gc/Barrier-inl.h index 3d6747bdbaef..54087b690b75 100644 --- a/js/src/gc/Barrier-inl.h +++ b/js/src/gc/Barrier-inl.h @@ -294,8 +294,10 @@ HeapId::pre() if (JS_UNLIKELY(JSID_IS_OBJECT(value))) { JSObject *obj = JSID_TO_OBJECT(value); JSCompartment *comp = obj->compartment(); - if (comp->needsBarrier()) - js::gc::MarkObjectUnbarriered(comp->barrierTracer(), obj, "write barrier"); + if (comp->needsBarrier()) { + js::gc::MarkObjectUnbarriered(comp->barrierTracer(), &obj, "write barrier"); + JS_ASSERT(obj == JSID_TO_OBJECT(value)); + } } #endif } diff --git a/js/src/jsapi-tests/testIsAboutToBeFinalized.cpp b/js/src/jsapi-tests/testIsAboutToBeFinalized.cpp index c4b30de2b64c..d3c6ed487c26 100644 --- a/js/src/jsapi-tests/testIsAboutToBeFinalized.cpp +++ b/js/src/jsapi-tests/testIsAboutToBeFinalized.cpp @@ -8,14 +8,14 @@ static JSGCCallback oldGCCallback; static void **checkPointers; -static jsuint checkPointersLength; +static unsigned checkPointersLength; static size_t checkPointersStaticStrings; static JSBool TestAboutToBeFinalizedCallback(JSContext *cx, JSGCStatus status) { if (status == JSGC_MARK_END && checkPointers) { - for (jsuint i = 0; i != checkPointersLength; ++i) { + for (unsigned i = 0; i != checkPointersLength; ++i) { void *p = checkPointers[i]; JS_ASSERT(p); if (JS_IsAboutToBeFinalized(p)) @@ -52,7 +52,7 @@ BEGIN_TEST(testIsAboutToBeFinalized_bug528645) JS_GC(cx); /* Everything is unrooted except unit strings. */ - for (jsuint i = 0; i != checkPointersLength; ++i) { + for (unsigned i = 0; i != checkPointersLength; ++i) { void *p = checkPointers[i]; if (p) { CHECK(JSString::isStatic(p)); @@ -97,7 +97,7 @@ cls_testIsAboutToBeFinalized_bug528645::createAndTestRooted() CHECK(checkPointers); checkPointersStaticStrings = 0; - for (jsuint i = 0; i != checkPointersLength; ++i) { + for (unsigned i = 0; i != checkPointersLength; ++i) { jsval v; ok = JS_GetElement(cx, array, i, &v); CHECK(ok); @@ -115,7 +115,7 @@ cls_testIsAboutToBeFinalized_bug528645::createAndTestRooted() * All GC things are rooted via the root holding the array containing them * and TestAboutToBeFinalizedCallback must keep them as is. */ - for (jsuint i = 0; i != checkPointersLength; ++i) + for (unsigned i = 0; i != checkPointersLength; ++i) CHECK(checkPointers[i]); /* @@ -127,7 +127,7 @@ cls_testIsAboutToBeFinalized_bug528645::createAndTestRooted() array = JSVAL_TO_OBJECT(root.value()); JS_ASSERT(JS_IsArrayObject(cx, array)); - jsuint tmp; + uint32_t tmp; CHECK(JS_GetArrayLength(cx, array, &tmp)); CHECK(ok); diff --git a/js/src/jsapi-tests/testNewObject.cpp b/js/src/jsapi-tests/testNewObject.cpp index 01ae34f685ef..1cabf39a3639 100644 --- a/js/src/jsapi-tests/testNewObject.cpp +++ b/js/src/jsapi-tests/testNewObject.cpp @@ -55,7 +55,7 @@ BEGIN_TEST(testNewObject_1) CHECK(obj); jsvalRoot rt(cx, OBJECT_TO_JSVAL(obj)); CHECK(JS_IsArrayObject(cx, obj)); - jsuint len; + uint32_t len; CHECK(JS_GetArrayLength(cx, obj, &len)); CHECK_EQUAL(len, 0); diff --git a/js/src/jsapi-tests/testStringBuffer.cpp b/js/src/jsapi-tests/testStringBuffer.cpp index 62e66b00e13d..3ed3c612d0f5 100644 --- a/js/src/jsapi-tests/testStringBuffer.cpp +++ b/js/src/jsapi-tests/testStringBuffer.cpp @@ -7,7 +7,8 @@ #include "jsatom.h" #include "jsobjinlines.h" -#include "jsstrinlines.h" + +#include "vm/StringBuffer-inl.h" BEGIN_TEST(testStringBuffer_finishString) { diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index b43249bb776a..04af605afd78 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -104,6 +104,7 @@ #include "vm/RegExpStatics-inl.h" #include "vm/Stack-inl.h" #include "vm/String-inl.h" +#include "vm/StringBuffer-inl.h" #if ENABLE_YARR_JIT #include "assembler/jit/ExecutableAllocator.h" @@ -752,6 +753,7 @@ JSRuntime::JSRuntime() gcZealFrequency(0), gcNextScheduled(0), gcDebugCompartmentGC(false), + gcDeterministicOnly(false), #endif gcCallback(NULL), gcSliceCallback(NULL), @@ -4264,7 +4266,9 @@ prop_iter_trace(JSTracer *trc, JSObject *obj) * barrier here because the pointer is updated via setPrivate, which * always takes a barrier. */ - MarkShapeUnbarriered(trc, (Shape *)pdata, "prop iter shape"); + Shape *tmp = (Shape *)pdata; + MarkShapeUnbarriered(trc, &tmp, "prop iter shape"); + JS_ASSERT(tmp == pdata); } else { /* Non-native case: mark each id in the JSIdArray private. */ JSIdArray *ida = (JSIdArray *) pdata; @@ -4404,9 +4408,9 @@ JS_NewArrayObject(JSContext *cx, int length, jsval *vector) JS_THREADSAFE_ASSERT(cx->compartment != cx->runtime->atomsCompartment); AssertNoGC(cx); CHECK_REQUEST(cx); - /* NB: jsuint cast does ToUint32. */ - assertSameCompartment(cx, JSValueArray(vector, vector ? (jsuint)length : 0)); - return NewDenseCopiedArray(cx, (jsuint)length, vector); + + assertSameCompartment(cx, JSValueArray(vector, vector ? (uint32_t)length : 0)); + return NewDenseCopiedArray(cx, (uint32_t)length, vector); } JS_PUBLIC_API(JSBool) @@ -4417,7 +4421,7 @@ JS_IsArrayObject(JSContext *cx, JSObject *obj) } JS_PUBLIC_API(JSBool) -JS_GetArrayLength(JSContext *cx, JSObject *obj, jsuint *lengthp) +JS_GetArrayLength(JSContext *cx, JSObject *obj, uint32_t *lengthp) { AssertNoGC(cx); CHECK_REQUEST(cx); @@ -4426,7 +4430,7 @@ JS_GetArrayLength(JSContext *cx, JSObject *obj, jsuint *lengthp) } JS_PUBLIC_API(JSBool) -JS_SetArrayLength(JSContext *cx, JSObject *obj, jsuint length) +JS_SetArrayLength(JSContext *cx, JSObject *obj, uint32_t length) { AssertNoGC(cx); CHECK_REQUEST(cx); @@ -6463,23 +6467,10 @@ JS_ClearPendingException(JSContext *cx) JS_PUBLIC_API(JSBool) JS_ReportPendingException(JSContext *cx) { - JSBool ok; - bool save; - AssertNoGC(cx); CHECK_REQUEST(cx); - /* - * Set cx->generatingError to suppress the standard error-to-exception - * conversion done by all {js,JS}_Report* functions except for OOM. The - * cx->generatingError flag was added to suppress recursive divergence - * under js_ErrorToException, but it serves for our purposes here too. - */ - save = cx->generatingError; - cx->generatingError = JS_TRUE; - ok = js_ReportUncaughtException(cx); - cx->generatingError = save; - return ok; + return js_ReportUncaughtException(cx); } struct JSExceptionState { diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 5a1a6aa84dc9..65baa1f37505 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1896,8 +1896,8 @@ JSID_TO_INT(jsid id) static JS_ALWAYS_INLINE JSBool INT_FITS_IN_JSID(int32_t i) { - return ((jsuint)(i) - (jsuint)JSID_INT_MIN <= - (jsuint)(JSID_INT_MAX - JSID_INT_MIN)); + return ((uint32_t)(i) - (uint32_t)JSID_INT_MIN <= + (uint32_t)(JSID_INT_MAX - JSID_INT_MIN)); } static JS_ALWAYS_INLINE jsid @@ -3986,10 +3986,10 @@ extern JS_PUBLIC_API(JSBool) JS_IsArrayObject(JSContext *cx, JSObject *obj); extern JS_PUBLIC_API(JSBool) -JS_GetArrayLength(JSContext *cx, JSObject *obj, jsuint *lengthp); +JS_GetArrayLength(JSContext *cx, JSObject *obj, uint32_t *lengthp); extern JS_PUBLIC_API(JSBool) -JS_SetArrayLength(JSContext *cx, JSObject *obj, jsuint length); +JS_SetArrayLength(JSContext *cx, JSObject *obj, uint32_t length); extern JS_PUBLIC_API(JSBool) JS_DefineElement(JSContext *cx, JSObject *obj, uint32_t index, jsval value, diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 07bd71d1cb32..823f6a289935 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -128,6 +128,7 @@ #include "vm/ArgumentsObject.h" #include "vm/MethodGuard.h" +#include "vm/StringBuffer-inl.h" #include "ds/Sort.h" @@ -147,7 +148,7 @@ using namespace js::gc; using namespace js::types; JSBool -js_GetLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp) +js_GetLengthProperty(JSContext *cx, JSObject *obj, uint32_t *lengthp) { if (obj->isArray()) { *lengthp = obj->getArrayLength(); @@ -167,11 +168,11 @@ js_GetLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp) return false; if (tvr.value().isInt32()) { - *lengthp = jsuint(tvr.value().toInt32()); /* jsuint cast does ToUint32_t */ + *lengthp = uint32_t(tvr.value().toInt32()); /* uint32_t cast does ToUint32_t */ return true; } - JS_STATIC_ASSERT(sizeof(jsuint) == sizeof(uint32_t)); + return ToUint32(cx, tvr.value(), (uint32_t *)lengthp); } @@ -197,7 +198,7 @@ namespace js { * */ JS_FRIEND_API(bool) -StringIsArrayIndex(JSLinearString *str, jsuint *indexp) +StringIsArrayIndex(JSLinearString *str, uint32_t *indexp) { const jschar *s = str->chars(); uint32_t length = str->length(); @@ -236,10 +237,10 @@ StringIsArrayIndex(JSLinearString *str, jsuint *indexp) } static JSBool -BigIndexToId(JSContext *cx, JSObject *obj, jsuint index, JSBool createAtom, +BigIndexToId(JSContext *cx, JSObject *obj, uint32_t index, JSBool createAtom, jsid *idp) { - JS_STATIC_ASSERT((jsuint)-1 == 4294967295U); + JS_ASSERT(index > JSID_INT_MAX); jschar buf[10]; @@ -318,8 +319,8 @@ IndexToId(JSContext* cx, JSObject* obj, double index, JSBool* hole, jsid* idp, return JS_TRUE; } - if (index <= jsuint(-1)) { - if (!BigIndexToId(cx, obj, jsuint(index), createAtom, idp)) + if (index <= uint32_t(-1)) { + if (!BigIndexToId(cx, obj, uint32_t(index), createAtom, idp)) return JS_FALSE; if (hole && JSID_IS_VOID(*idp)) *hole = JS_TRUE; @@ -437,7 +438,7 @@ GetElementsSlow(JSContext *cx, JSObject *aobj, uint32_t length, Value *vp) } bool -GetElements(JSContext *cx, JSObject *aobj, jsuint length, Value *vp) +GetElements(JSContext *cx, JSObject *aobj, uint32_t length, Value *vp) { if (aobj->isDenseArray() && length <= aobj->getDenseArrayInitializedLength() && !js_PrototypeHasIndexedProperties(cx, aobj)) { @@ -475,9 +476,9 @@ SetArrayElement(JSContext *cx, JSObject *obj, double index, const Value &v) /* Predicted/prefetched code should favor the remains-dense case. */ JSObject::EnsureDenseResult result = JSObject::ED_SPARSE; do { - if (index > jsuint(-1)) + if (index > uint32_t(-1)) break; - jsuint idx = jsuint(index); + uint32_t idx = uint32_t(index); result = obj->ensureDenseArrayElements(cx, idx, 1); if (result != JSObject::ED_OK) break; @@ -629,8 +630,8 @@ array_length_setter(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value * us to disregard length when reading from arrays as long we are within * the initialized capacity. */ - jsuint oldcap = obj->getDenseArrayCapacity(); - jsuint oldinit = obj->getDenseArrayInitializedLength(); + uint32_t oldcap = obj->getDenseArrayCapacity(); + uint32_t oldinit = obj->getDenseArrayInitializedLength(); if (oldinit > newlen) obj->setDenseArrayInitializedLength(newlen); if (oldcap > newlen) @@ -660,13 +661,13 @@ array_length_setter(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value if (!iter) return false; - jsuint gap = oldlen - newlen; + uint32_t gap = oldlen - newlen; for (;;) { if (!JS_CHECK_OPERATION_LIMIT(cx) || !JS_NextProperty(cx, iter, &id)) return false; if (JSID_IS_VOID(id)) break; - jsuint index; + uint32_t index; Value junk; if (js_IdIsIndex(id, &index) && index - newlen < gap && !obj->deleteElement(cx, index, &junk, false)) { @@ -863,7 +864,7 @@ array_getGeneric(JSContext *cx, JSObject *obj, JSObject *receiver, jsid id, Valu static JSBool slowarray_addProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp) { - jsuint index, length; + uint32_t index, length; if (!js_IdIsIndex(id, &index)) return JS_TRUE; @@ -1193,7 +1194,7 @@ array_trace(JSTracer *trc, JSObject *obj) JS_ASSERT(obj->isDenseArray()); uint32_t initLength = obj->getDenseArrayInitializedLength(); - MarkSlotRange(trc, initLength, obj->getDenseArrayElements(), "element"); + MarkArraySlots(trc, initLength, obj->getDenseArrayElements(), "element"); } static JSBool @@ -1488,11 +1489,11 @@ array_toSource(JSContext *cx, unsigned argc, Value *vp) if (!sb.append('[')) return false; - jsuint length; + uint32_t length; if (!js_GetLengthProperty(cx, obj, &length)) return false; - for (jsuint index = 0; index < length; index++) { + for (uint32_t index = 0; index < length; index++) { JSBool hole; Value elt; if (!JS_CHECK_OPERATION_LIMIT(cx) || @@ -1608,7 +1609,7 @@ array_toString_sub(JSContext *cx, JSObject *obj, JSBool locale, return true; } - jsuint length; + uint32_t length; if (!js_GetLengthProperty(cx, obj, &length)) return false; @@ -1649,7 +1650,7 @@ array_toString_sub(JSContext *cx, JSObject *obj, JSBool locale, } } } else { - for (jsuint index = 0; index < length; index++) { + for (uint32_t index = 0; index < length; index++) { if (!JS_CHECK_OPERATION_LIMIT(cx)) return false; @@ -1793,7 +1794,7 @@ InitArrayElements(JSContext *cx, JSObject *obj, uint32_t start, uint32_t count, JS_ASSERT(result == JSObject::ED_SPARSE); break; } - jsuint newlen = start + count; + uint32_t newlen = start + count; if (newlen > obj->getArrayLength()) obj->setDenseArrayLength(newlen); @@ -1836,7 +1837,7 @@ InitArrayElements(JSContext *cx, JSObject *obj, uint32_t start, uint32_t count, #if 0 static JSBool -InitArrayObject(JSContext *cx, JSObject *obj, jsuint length, const Value *vector) +InitArrayObject(JSContext *cx, JSObject *obj, uint32_t length, const Value *vector) { JS_ASSERT(obj->isArray()); @@ -1855,7 +1856,7 @@ InitArrayObject(JSContext *cx, JSObject *obj, jsuint length, const Value *vector obj->setDenseArrayInitializedLength(length); bool hole = false; - for (jsuint i = 0; i < length; i++) { + for (uint32_t i = 0; i < length; i++) { obj->setDenseArrayElement(i, vector[i]); hole |= vector[i].isMagic(JS_ARRAY_HOLE); } @@ -1898,7 +1899,7 @@ array_reverse(JSContext *cx, unsigned argc, Value *vp) if (!obj) return false; - jsuint len; + uint32_t len; if (!js_GetLengthProperty(cx, obj, &len)) return false; @@ -1960,7 +1961,7 @@ array_reverse(JSContext *cx, unsigned argc, Value *vp) } while (false); Value lowval, hival; - for (jsuint i = 0, half = len / 2; i < half; i++) { + for (uint32_t i = 0, half = len / 2; i < half; i++) { JSBool hole, hole2; if (!JS_CHECK_OPERATION_LIMIT(cx) || !GetElement(cx, obj, i, &hole, &lowval) || @@ -2202,7 +2203,7 @@ js::array_sort(JSContext *cx, unsigned argc, Value *vp) if (!obj) return false; - jsuint len; + uint32_t len; if (!js_GetLengthProperty(cx, obj, &len)) return false; if (len == 0) { @@ -2249,7 +2250,7 @@ js::array_sort(JSContext *cx, unsigned argc, Value *vp) undefs = 0; bool allStrings = true; bool allInts = true; - for (jsuint i = 0; i < len; i++) { + for (uint32_t i = 0; i < len; i++) { if (!JS_CHECK_OPERATION_LIMIT(cx)) return false; @@ -2340,7 +2341,7 @@ js::array_sort(JSContext *cx, unsigned argc, Value *vp) } } - if (!InitArrayElements(cx, obj, 0, jsuint(n), result, DontUpdateTypes)) + if (!InitArrayElements(cx, obj, 0, uint32_t(n), result, DontUpdateTypes)) return false; } @@ -2366,7 +2367,7 @@ js::array_sort(JSContext *cx, unsigned argc, Value *vp) static bool array_push_slowly(JSContext *cx, JSObject *obj, CallArgs &args) { - jsuint length; + uint32_t length; if (!js_GetLengthProperty(cx, obj, &length)) return false; @@ -2450,7 +2451,7 @@ js::array_push(JSContext *cx, unsigned argc, Value *vp) static JSBool array_pop_slowly(JSContext *cx, JSObject* obj, CallArgs &args) { - jsuint index; + uint32_t index; if (!js_GetLengthProperty(cx, obj, &index)) return false; @@ -2476,7 +2477,7 @@ array_pop_slowly(JSContext *cx, JSObject* obj, CallArgs &args) static JSBool array_pop_dense(JSContext *cx, JSObject* obj, CallArgs &args) { - jsuint index = obj->getArrayLength(); + uint32_t index = obj->getArrayLength(); if (index == 0) { args.rval().setUndefined(); return JS_TRUE; @@ -2537,7 +2538,7 @@ js::array_shift(JSContext *cx, unsigned argc, Value *vp) if (!obj) return JS_FALSE; - jsuint length; + uint32_t length; if (!js_GetLengthProperty(cx, obj, &length)) return JS_FALSE; @@ -2566,7 +2567,7 @@ js::array_shift(JSContext *cx, unsigned argc, Value *vp) /* Slide down the array above the first element. */ AutoValueRooter tvr(cx); - for (jsuint i = 0; i < length; i++) { + for (uint32_t i = 0; i < length; i++) { if (!JS_CHECK_OPERATION_LIMIT(cx) || !GetElement(cx, obj, i + 1, &hole, tvr.addr()) || !SetOrDeleteArrayElement(cx, obj, i, hole, tvr.value())) { @@ -2589,7 +2590,7 @@ array_unshift(JSContext *cx, unsigned argc, Value *vp) if (!obj) return false; - jsuint length; + uint32_t length; if (!js_GetLengthProperty(cx, obj, &length)) return JS_FALSE; @@ -2935,11 +2936,11 @@ js::array_concat(JSContext *cx, unsigned argc, Value *vp) return false; JSObject *nobj; - jsuint length; + uint32_t length; if (aobj->isDenseArray()) { length = aobj->getArrayLength(); const Value *vector = aobj->getDenseArrayElements(); - jsuint initlen = aobj->getDenseArrayInitializedLength(); + uint32_t initlen = aobj->getDenseArrayInitializedLength(); nobj = NewDenseCopiedArray(cx, initlen, vector); if (!nobj) return JS_FALSE; @@ -2966,7 +2967,7 @@ js::array_concat(JSContext *cx, unsigned argc, Value *vp) if (v.isObject()) { JSObject &obj = v.toObject(); if (ObjectClassIs(obj, ESClass_Array, cx)) { - jsuint alength; + uint32_t alength; if (!js_GetLengthProperty(cx, &obj, &alength)) return false; for (uint32_t slot = 0; slot < alength; slot++) { @@ -2999,7 +3000,7 @@ static JSBool array_slice(JSContext *cx, unsigned argc, Value *vp) { JSObject *nobj; - jsuint length, begin, end, slot; + uint32_t length, begin, end, slot; JSBool hole; CallArgs args = CallArgsFromVp(argc, vp); @@ -3024,7 +3025,7 @@ array_slice(JSContext *cx, unsigned argc, Value *vp) } else if (d > length) { d = length; } - begin = (jsuint)d; + begin = (uint32_t)d; if (args.hasDefined(1)) { if (!ToInteger(cx, args[1], &d)) @@ -3036,7 +3037,7 @@ array_slice(JSContext *cx, unsigned argc, Value *vp) } else if (d > length) { d = length; } - end = (jsuint)d; + end = (uint32_t)d; } } @@ -3080,7 +3081,7 @@ enum IndexOfKind { static JSBool array_indexOfHelper(JSContext *cx, IndexOfKind mode, CallArgs &args) { - jsuint length, i, stop; + uint32_t length, i, stop; Value tosearch; int direction; JSBool hole; @@ -3109,14 +3110,14 @@ array_indexOfHelper(JSContext *cx, IndexOfKind mode, CallArgs &args) goto not_found; i = 0; } else { - i = (jsuint)start; + i = (uint32_t)start; } } else if (start >= length) { if (mode == IndexOf) goto not_found; i = length - 1; } else { - i = (jsuint)start; + i = (uint32_t)start; } } @@ -3131,7 +3132,7 @@ array_indexOfHelper(JSContext *cx, IndexOfKind mode, CallArgs &args) for (;;) { Value elt; if (!JS_CHECK_OPERATION_LIMIT(cx) || - !GetElement(cx, obj, (jsuint)i, &hole, &elt)) { + !GetElement(cx, obj, (uint32_t)i, &hole, &elt)) { return JS_FALSE; } if (!hole) { @@ -3735,7 +3736,7 @@ js_InitArrayClass(JSContext *cx, JSObject *obj) namespace js { static inline bool -EnsureNewArrayElements(JSContext *cx, JSObject *obj, jsuint length) +EnsureNewArrayElements(JSContext *cx, JSObject *obj, uint32_t length) { /* * If ensureElements creates dynamically allocated slots, then having diff --git a/js/src/jsarray.h b/js/src/jsarray.h index c9b541720c4c..ffb054fc3c06 100644 --- a/js/src/jsarray.h +++ b/js/src/jsarray.h @@ -57,13 +57,13 @@ const uint32_t MAX_ARRAY_INDEX = 4294967294u; } inline JSBool -js_IdIsIndex(jsid id, jsuint *indexp) +js_IdIsIndex(jsid id, uint32_t *indexp) { if (JSID_IS_INT(id)) { int32_t i = JSID_TO_INT(id); if (i < 0) return JS_FALSE; - *indexp = (jsuint)i; + *indexp = (uint32_t)i; return JS_TRUE; } @@ -137,7 +137,7 @@ NewSlowEmptyArray(JSContext *cx); } /* namespace js */ extern JSBool -js_GetLengthProperty(JSContext *cx, JSObject *obj, jsuint *lengthp); +js_GetLengthProperty(JSContext *cx, JSObject *obj, uint32_t *lengthp); extern JSBool js_SetLengthProperty(JSContext *cx, JSObject *obj, double length); @@ -158,7 +158,7 @@ array_deleteElement(JSContext *cx, JSObject *obj, uint32_t index, Value *rval, J * js_GetLengthProperty on aobj. */ extern bool -GetElements(JSContext *cx, JSObject *aobj, jsuint length, js::Value *vp); +GetElements(JSContext *cx, JSObject *aobj, uint32_t length, js::Value *vp); /* Natives exposed for optimization by the interpreter and JITs. */ diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index 2666c53d6942..dab76542fd02 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -712,9 +712,9 @@ js_CheckForStringIndex(jsid id) const jschar *cp = s; const jschar *end = s + n; - jsuint index = JS7_UNDEC(*cp++); - jsuint oldIndex = 0; - jsuint c = 0; + uint32_t index = JS7_UNDEC(*cp++); + uint32_t oldIndex = 0; + uint32_t c = 0; if (index != 0) { while (JS7_ISDEC(*cp)) { diff --git a/js/src/jsbool.cpp b/js/src/jsbool.cpp index a5eac3e74257..98905bebf11c 100644 --- a/js/src/jsbool.cpp +++ b/js/src/jsbool.cpp @@ -57,10 +57,10 @@ #include "jsinferinlines.h" #include "jsobjinlines.h" -#include "jsstrinlines.h" #include "vm/BooleanObject-inl.h" #include "vm/MethodGuard-inl.h" +#include "vm/StringBuffer-inl.h" using namespace js; using namespace js::types; diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 80fe3078f2e9..414ed5b67d0d 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -504,6 +504,37 @@ js_ReportErrorVA(JSContext *cx, unsigned flags, const char *format, va_list ap) return warning; } +namespace js { + +/* |callee| requires a usage string provided by JS_DefineFunctionsWithHelp. */ +void +ReportUsageError(JSContext *cx, JSObject *callee, const char *msg) +{ + const char *usageStr = "usage"; + JSAtom *usageAtom = js_Atomize(cx, usageStr, strlen(usageStr)); + DebugOnly shape = callee->nativeLookup(cx, ATOM_TO_JSID(usageAtom)); + JS_ASSERT(!shape->configurable()); + JS_ASSERT(!shape->writable()); + JS_ASSERT(shape->hasDefaultGetter()); + + jsval usage; + if (!JS_LookupProperty(cx, callee, "usage", &usage)) + return; + + if (JSVAL_IS_VOID(usage)) { + JS_ReportError(cx, "%s", msg); + } else { + JSString *str = JSVAL_TO_STRING(usage); + JS::Anchor a_str(str); + const jschar *chars = JS_GetStringCharsZ(cx, str); + if (!chars) + return; + JS_ReportError(cx, "%s. Usage: %hs", msg, chars); + } +} + +} /* namespace js */ + /* * The arguments from ap need to be packaged up into an array and stored * into the report struct. diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 3604a2f3eed9..4d1afcb45f05 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -419,6 +419,7 @@ struct JSRuntime : js::RuntimeFriendFields int gcZealFrequency; int gcNextScheduled; bool gcDebugCompartmentGC; + bool gcDeterministicOnly; int gcZeal() { return gcZeal_; } @@ -829,11 +830,11 @@ struct JSContext : js::ContextFriendFields bool hasVersionOverride; /* Exception state -- the exception member is a GC root by definition. */ - JSBool throwing; /* is there a pending exception? */ - js::Value exception; /* most-recently-thrown exception */ + JSBool throwing; /* is there a pending exception? */ + js::Value exception; /* most-recently-thrown exception */ /* Per-context run options. */ - unsigned runOptions; /* see jsapi.h for JSOPTION_* */ + unsigned runOptions; /* see jsapi.h for JSOPTION_* */ public: int32_t reportGranularity; /* see jsprobes.h */ @@ -843,11 +844,8 @@ struct JSContext : js::ContextFriendFields js::AutoResolving *resolvingList; - /* - * True if generating an error, to prevent runaway recursion. - * NB: generatingError packs with throwing below. - */ - bool generatingError; + /* True if generating an error, to prevent runaway recursion. */ + bool generatingError; /* GC heap compartment. */ JSCompartment *compartment; @@ -1418,6 +1416,14 @@ js_ExpandErrorArguments(JSContext *cx, JSErrorCallback callback, bool charArgs, va_list ap); #endif +namespace js { + +/* |callee| requires a usage string provided by JS_DefineFunctionsWithHelp. */ +extern void +ReportUsageError(JSContext *cx, JSObject *callee, const char *msg); + +} /* namespace js */ + extern void js_ReportOutOfMemory(JSContext *cx); diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp index 3ea86bc6f326..c3323456b787 100644 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -77,10 +77,10 @@ #include "jsinferinlines.h" #include "jsobjinlines.h" -#include "jsstrinlines.h" #include "vm/MethodGuard-inl.h" #include "vm/Stack-inl.h" +#include "vm/StringBuffer-inl.h" using namespace mozilla; using namespace js; diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index 2a244796a596..c6180b2ce33f 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -68,10 +68,10 @@ #include "jsinferinlines.h" #include "jsobjinlines.h" -#include "jsstrinlines.h" #include "vm/Stack-inl.h" #include "vm/String-inl.h" +#include "vm/StringBuffer-inl.h" using namespace mozilla; using namespace js; @@ -1084,7 +1084,7 @@ js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp, */ JS_ASSERT(reportp); if (JSREPORT_IS_WARNING(reportp->flags)) - return JS_FALSE; + return false; /* Find the exception index associated with this error. */ errorNumber = (JSErrNum) reportp->errorNumber; @@ -1107,19 +1107,12 @@ js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp, * with the given error number. */ if (exn == JSEXN_NONE) - return JS_FALSE; + return false; - /* - * Prevent runaway recursion, via cx->generatingError. If an out-of-memory - * error occurs, no exception object will be created, but we don't assume - * that OOM is the only kind of error that subroutines of this function - * called below might raise. - */ + /* Prevent infinite recursion. */ if (cx->generatingError) - return JS_FALSE; - - MUST_FLOW_THROUGH("out"); - cx->generatingError = JS_TRUE; + return false; + AutoScopedAssign asa(&cx->generatingError, false); /* Protect the newly-created strings below from nesting GCs. */ PodArrayZero(tv); @@ -1132,43 +1125,34 @@ js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp, */ ok = js_GetClassPrototype(cx, NULL, GetExceptionProtoKey(exn), &errProto); if (!ok) - goto out; + return false; tv[0] = OBJECT_TO_JSVAL(errProto); errObject = NewObjectWithGivenProto(cx, &ErrorClass, errProto, NULL); - if (!errObject) { - ok = JS_FALSE; - goto out; - } + if (!errObject) + return false; tv[1] = OBJECT_TO_JSVAL(errObject); messageStr = JS_NewStringCopyZ(cx, message); - if (!messageStr) { - ok = JS_FALSE; - goto out; - } + if (!messageStr) + return false; tv[2] = STRING_TO_JSVAL(messageStr); filenameStr = JS_NewStringCopyZ(cx, reportp->filename); - if (!filenameStr) { - ok = JS_FALSE; - goto out; - } + if (!filenameStr) + return false; tv[3] = STRING_TO_JSVAL(filenameStr); ok = InitExnPrivate(cx, errObject, messageStr, filenameStr, reportp->lineno, reportp, exn); if (!ok) - goto out; + return false; JS_SetPendingException(cx, OBJECT_TO_JSVAL(errObject)); /* Flag the error report passed in to indicate an exception was raised. */ reportp->flags |= JSREPORT_EXCEPTION; - -out: - cx->generatingError = JS_FALSE; - return ok; + return true; } JSBool diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index f7d1423bd011..e9f8d0590fa1 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -47,6 +47,8 @@ #include "jsweakmap.h" #include "jswatchpoint.h" +#include "builtin/TestingFunctions.h" + #include "jsobjinlines.h" using namespace js; @@ -180,6 +182,51 @@ JS_TraceShapeCycleCollectorChildren(JSTracer *trc, void *shape) MarkCycleCollectorChildren(trc, (Shape *)shape); } +static bool +DefineHelpProperty(JSContext *cx, JSObject *obj, const char *prop, const char *value) +{ + JSAtom *atom = js_Atomize(cx, value, strlen(value)); + if (!atom) + return false; + jsval v = STRING_TO_JSVAL(atom); + return JS_DefineProperty(cx, obj, prop, v, + JS_PropertyStub, JS_StrictPropertyStub, + JSPROP_READONLY | JSPROP_PERMANENT); +} + +JS_FRIEND_API(bool) +JS_DefineFunctionsWithHelp(JSContext *cx, JSObject *obj, const JSFunctionSpecWithHelp *fs) +{ + RootObject objRoot(cx, &obj); + + JS_ASSERT(cx->compartment != cx->runtime->atomsCompartment); + + CHECK_REQUEST(cx); + assertSameCompartment(cx, obj); + for (; fs->name; fs++) { + JSAtom *atom = js_Atomize(cx, fs->name, strlen(fs->name)); + if (!atom) + return false; + + JSFunction *fun = js_DefineFunction(cx, objRoot, + ATOM_TO_JSID(atom), fs->call, fs->nargs, fs->flags); + if (!fun) + return false; + + if (fs->usage) { + if (!DefineHelpProperty(cx, fun, "usage", fs->usage)) + return false; + } + + if (fs->help) { + if (!DefineHelpProperty(cx, fun, "help", fs->help)) + return false; + } + } + + return true; +} + AutoPreserveCompartment::AutoPreserveCompartment(JSContext *cx JS_GUARD_OBJECT_NOTIFIER_PARAM_NO_INIT) : cx(cx), oldCompartment(cx->compartment) @@ -789,4 +836,17 @@ IncrementalValueBarrier(const Value &v) HeapValue::writeBarrierPre(v); } +JS_FRIEND_API(JSObject *) +GetTestingFunctions(JSContext *cx) +{ + JSObject *obj = JS_NewObject(cx, NULL, NULL, NULL); + if (!obj) + return NULL; + + if (!DefineTestingFunctions(cx, obj)) + return NULL; + + return obj; +} + } // namespace js diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 87eee7c7c5fe..a2273d29929f 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -166,6 +166,21 @@ JS_WrapPropertyDescriptor(JSContext *cx, js::PropertyDescriptor *desc); extern JS_FRIEND_API(JSBool) JS_EnumerateState(JSContext *cx, JSObject *obj, JSIterateOp enum_op, js::Value *statep, jsid *idp); +struct JSFunctionSpecWithHelp { + const char *name; + JSNative call; + uint16_t nargs; + uint16_t flags; + const char *usage; + const char *help; +}; + +#define JS_FN_HELP(name,call,nargs,flags,usage,help) \ + {name, call, nargs, (flags) | JSPROP_ENUMERATE | JSFUN_STUB_GSOPS, usage, help} + +extern JS_FRIEND_API(bool) +JS_DefineFunctionsWithHelp(JSContext *cx, JSObject *obj, const JSFunctionSpecWithHelp *fs); + #endif JS_END_EXTERN_C @@ -498,7 +513,7 @@ JS_FRIEND_API(bool) GetPropertyNames(JSContext *cx, JSObject *obj, unsigned flags, js::AutoIdVector *props); JS_FRIEND_API(bool) -StringIsArrayIndex(JSLinearString *str, jsuint *indexp); +StringIsArrayIndex(JSLinearString *str, uint32_t *indexp); JS_FRIEND_API(void) SetPreserveWrapperCallback(JSRuntime *rt, PreserveWrapperCallback callback); @@ -658,7 +673,7 @@ SizeOfJSContext(); D(LAST_DITCH) \ D(TOO_MUCH_MALLOC) \ D(ALLOC_TRIGGER) \ - D(UNUSED1) /* was CHUNK */ \ + D(DEBUG_GC) \ D(UNUSED2) /* was SHAPE */ \ D(UNUSED3) /* was REFILL */ \ \ @@ -808,6 +823,9 @@ class ObjectPtr operator JSObject *() const { return value; } }; +extern JS_FRIEND_API(JSObject *) +GetTestingFunctions(JSContext *cx); + } /* namespace js */ #endif diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 134386c29e8b..8106c65d6115 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -1094,13 +1094,13 @@ JSFunction::trace(JSTracer *trc) } if (atom) - MarkStringUnbarriered(trc, atom, "atom"); + MarkStringUnbarriered(trc, &atom, "atom"); if (isInterpreted()) { - if (script()) - MarkScript(trc, &script(), "script"); - if (environment()) - MarkObjectUnbarriered(trc, environment(), "fun_callscope"); + if (u.i.script_) + MarkScriptUnbarriered(trc, &u.i.script_, "script"); + if (u.i.env_) + MarkObjectUnbarriered(trc, &u.i.env_, "fun_callscope"); } } @@ -1296,7 +1296,7 @@ js_fun_apply(JSContext *cx, unsigned argc, Value *vp) * original version of ES5). */ JSObject *aobj = &vp[3].toObject(); - jsuint length; + uint32_t length; if (!js_GetLengthProperty(cx, aobj, &length)) return false; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index ca91d8920dfd..ba36807a7354 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -664,7 +664,7 @@ Chunk::init() info.age = 0; /* Initialize the arena header state. */ - for (jsuint i = 0; i < ArenasPerChunk; i++) { + for (unsigned i = 0; i < ArenasPerChunk; i++) { arenas[i].aheader.setAsNotAllocated(); arenas[i].aheader.next = (i + 1 < ArenasPerChunk) ? &arenas[i + 1].aheader @@ -724,14 +724,14 @@ Chunk::removeFromAvailableList() * it to the most recently freed arena when we free, and forcing it to * the last alloc + 1 when we allocate. */ -jsuint +uint32_t Chunk::findDecommittedArenaOffset() { /* Note: lastFreeArenaOffset can be past the end of the list. */ - for (jsuint i = info.lastDecommittedArenaOffset; i < ArenasPerChunk; i++) + for (unsigned i = info.lastDecommittedArenaOffset; i < ArenasPerChunk; i++) if (decommittedArenas.get(i)) return i; - for (jsuint i = 0; i < info.lastDecommittedArenaOffset; i++) + for (unsigned i = 0; i < info.lastDecommittedArenaOffset; i++) if (decommittedArenas.get(i)) return i; JS_NOT_REACHED("No decommitted arenas found."); @@ -744,7 +744,7 @@ Chunk::fetchNextDecommittedArena() JS_ASSERT(info.numArenasFreeCommitted == 0); JS_ASSERT(info.numArenasFree > 0); - jsuint offset = findDecommittedArenaOffset(); + unsigned offset = findDecommittedArenaOffset(); info.lastDecommittedArenaOffset = offset + 1; --info.numArenasFree; decommittedArenas.unset(offset); @@ -1708,13 +1708,13 @@ ArenaLists::finalizeScripts(JSContext *cx) } static void -RunLastDitchGC(JSContext *cx) +RunLastDitchGC(JSContext *cx, gcreason::Reason reason) { JSRuntime *rt = cx->runtime; /* The last ditch GC preserves all atoms. */ AutoKeepAtoms keep(rt); - GC(cx, rt->gcTriggerCompartment, GC_NORMAL, gcreason::LAST_DITCH); + GC(cx, rt->gcTriggerCompartment, GC_NORMAL, reason); } /* static */ void * @@ -1729,7 +1729,7 @@ ArenaLists::refillFreeList(JSContext *cx, AllocKind thingKind) bool runGC = rt->gcIncrementalState != NO_INCREMENTAL && comp->gcBytes > comp->gcTriggerBytes; for (;;) { if (JS_UNLIKELY(runGC)) { - RunLastDitchGC(cx); + RunLastDitchGC(cx, gcreason::LAST_DITCH); /* * The JSGC_END callback can legitimately allocate new GC @@ -3662,6 +3662,20 @@ GCCycle(JSContext *cx, JSCompartment *comp, int64_t budget, JSGCInvocationKind g #endif } +#ifdef JS_GC_ZEAL +static bool +IsDeterministicGCReason(gcreason::Reason reason) +{ + if (reason > gcreason::DEBUG_GC && reason != gcreason::CC_FORCED) + return false; + + if (reason == gcreason::MAYBEGC) + return false; + + return true; +} +#endif + static void Collect(JSContext *cx, JSCompartment *comp, int64_t budget, JSGCInvocationKind gckind, gcreason::Reason reason) @@ -3669,6 +3683,11 @@ Collect(JSContext *cx, JSCompartment *comp, int64_t budget, JSRuntime *rt = cx->runtime; JS_AbortIfWrongThread(rt); +#ifdef JS_GC_ZEAL + if (rt->gcDeterministicOnly && !IsDeterministicGCReason(reason)) + return; +#endif + JS_ASSERT_IF(budget != SliceBudget::Unlimited, JSGC_INCREMENTAL); #ifdef JS_GC_ZEAL @@ -3947,7 +3966,16 @@ RunDebugGC(JSContext *cx) if (rt->gcTriggerCompartment == rt->atomsCompartment) rt->gcTriggerCompartment = NULL; - RunLastDitchGC(cx); + RunLastDitchGC(cx, gcreason::DEBUG_GC); +#endif +} + +void +SetDeterministicGC(JSContext *cx, bool enabled) +{ +#ifdef JS_GC_ZEAL + JSRuntime *rt = cx->runtime; + rt->gcDeterministicOnly = enabled; #endif } diff --git a/js/src/jsgc.h b/js/src/jsgc.h index d696e33e2039..246c4e1a3ca1 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -808,7 +808,7 @@ struct Chunk { inline void init(); /* Search for a decommitted arena to allocate. */ - jsuint findDecommittedArenaOffset(); + unsigned findDecommittedArenaOffset(); ArenaHeader* fetchNextDecommittedArena(); public: @@ -1968,6 +1968,9 @@ NewCompartment(JSContext *cx, JSPrincipals *principals); void RunDebugGC(JSContext *cx); +void +SetDeterministicGC(JSContext *cx, bool enabled); + #if defined(JSGC_ROOT_ANALYSIS) && defined(DEBUG) && !defined(JS_THREADSAFE) /* Overwrites stack references to GC things which have not been rooted. */ void CheckStackRoots(JSContext *cx); diff --git a/js/src/jsgcmark.cpp b/js/src/jsgcmark.cpp index 2579613abac6..f179420d8aaa 100644 --- a/js/src/jsgcmark.cpp +++ b/js/src/jsgcmark.cpp @@ -126,10 +126,10 @@ MarkInternal(JSTracer *trc, T *thing) template static void -MarkUnbarriered(JSTracer *trc, T *thing, const char *name) +MarkUnbarriered(JSTracer *trc, T **thingp, const char *name) { JS_SET_TRACING_NAME(trc, name); - MarkInternal(trc, thing); + MarkInternal(trc, *thingp); } template @@ -186,9 +186,9 @@ Mark##base##Root(JSTracer *trc, type **thingp, const char *name) } \ \ void \ -Mark##base##Unbarriered(JSTracer *trc, type *thing, const char *name) \ +Mark##base##Unbarriered(JSTracer *trc, type **thingp, const char *name) \ { \ - MarkUnbarriered(trc, thing, name); \ + MarkUnbarriered(trc, thingp, name); \ } \ \ void Mark##base##Range(JSTracer *trc, size_t len, HeapPtr *vec, const char *name) \ @@ -202,6 +202,7 @@ void Mark##base##RootRange(JSTracer *trc, size_t len, type **vec, const char *na } \ DeclMarkerImpl(BaseShape, BaseShape) +DeclMarkerImpl(BaseShape, UnownedBaseShape) DeclMarkerImpl(Object, ArgumentsObject) DeclMarkerImpl(Object, GlobalObject) DeclMarkerImpl(Object, JSObject) @@ -366,7 +367,7 @@ MarkSlot(JSTracer *trc, HeapSlot *s, const char *name) } void -MarkSlotRange(JSTracer *trc, size_t len, HeapSlot *vec, const char *name) +MarkArraySlots(JSTracer *trc, size_t len, HeapSlot *vec, const char *name) { for (size_t i = 0; i < len; ++i) { JS_SET_TRACING_INDEX(trc, name, i); @@ -374,6 +375,16 @@ MarkSlotRange(JSTracer *trc, size_t len, HeapSlot *vec, const char *name) } } +void +MarkObjectSlots(JSTracer *trc, JSObject *obj, uint32_t start, uint32_t nslots) +{ + JS_ASSERT(obj->isNative()); + for (uint32_t i = start; i < (start + nslots); ++i) { + JS_SET_TRACING_DETAILS(trc, js_PrintObjectSlotName, obj, i); + MarkValueInternal(trc, obj->nativeGetSlotRef(i).unsafeGet()); + } +} + void MarkCrossCompartmentSlot(JSTracer *trc, HeapSlot *s, const char *name) { @@ -393,15 +404,11 @@ MarkCrossCompartmentSlot(JSTracer *trc, HeapSlot *s, const char *name) /*** Special Marking ***/ -/* - * The unioned HeapPtr stored in script->globalObj needs special treatment to - * typecheck correctly. - */ -static void -MarkObject(JSTracer *trc, const HeapPtr &thing, const char *name) +void +MarkObject(JSTracer *trc, HeapPtr *thingp, const char *name) { JS_SET_TRACING_NAME(trc, name); - MarkInternal(trc, thing.get()); + MarkInternal(trc, thingp->get()); } void @@ -644,113 +651,34 @@ PushMarkStack(GCMarker *gcmarker, JSString *str) void MarkChildren(JSTracer *trc, JSObject *obj) { - MarkTypeObject(trc, &obj->typeFromGC(), "type"); - - Shape *shape = obj->lastProperty(); - MarkShapeUnbarriered(trc, shape, "shape"); - - Class *clasp = shape->getObjectClass(); - if (clasp->trace) - clasp->trace(trc, obj); - - if (shape->isNative()) { - uint32_t nslots = obj->slotSpan(); - for (uint32_t i = 0; i < nslots; i++) { - JS_SET_TRACING_DETAILS(trc, js_PrintObjectSlotName, obj, i); - MarkValueInternal(trc, obj->nativeGetSlotRef(i).unsafeGet()); - } - } + obj->markChildren(trc); } static void MarkChildren(JSTracer *trc, JSString *str) { - /* - * We use custom barriers in JSString, so it's safe to use unbarriered - * marking here. - */ - if (str->isDependent()) { - MarkStringUnbarriered(trc, str->asDependent().base(), "base"); - } else if (str->isRope()) { - JSRope &rope = str->asRope(); - MarkStringUnbarriered(trc, rope.leftChild(), "left child"); - MarkStringUnbarriered(trc, rope.rightChild(), "right child"); - } + if (str->isDependent()) + str->asDependent().markChildren(trc); + else if (str->isRope()) + str->asRope().markChildren(trc); } static void MarkChildren(JSTracer *trc, JSScript *script) { - CheckScript(script, NULL); - - JS_ASSERT_IF(trc->runtime->gcCheckCompartment, - script->compartment() == trc->runtime->gcCheckCompartment); - - for (uint32_t i = 0; i < script->natoms; ++i) { - if (JSAtom *p = script->atoms[i]) - MarkStringUnbarriered(trc, p, "atom"); - } - - if (JSScript::isValidOffset(script->objectsOffset)) { - JSObjectArray *objarray = script->objects(); - MarkObjectRange(trc, objarray->length, objarray->vector, "objects"); - } - - if (JSScript::isValidOffset(script->regexpsOffset)) { - JSObjectArray *objarray = script->regexps(); - MarkObjectRange(trc, objarray->length, objarray->vector, "objects"); - } - - if (JSScript::isValidOffset(script->constOffset)) { - JSConstArray *constarray = script->consts(); - MarkValueRange(trc, constarray->length, constarray->vector, "consts"); - } - - if (script->function()) - MarkObjectUnbarriered(trc, script->function(), "function"); - - if (!script->isCachedEval && script->globalObject) - MarkObject(trc, script->globalObject, "object"); - - if (IS_GC_MARKING_TRACER(trc) && script->filename) - js_MarkScriptFilename(script->filename); - - script->bindings.trace(trc); - - if (script->types) - script->types->trace(trc); - - if (script->hasAnyBreakpointsOrStepMode()) - script->markTrapClosures(trc); + script->markChildren(trc); } static void MarkChildren(JSTracer *trc, Shape *shape) { - MarkBaseShapeUnbarriered(trc, shape->base(), "base"); - MarkId(trc, &shape->propidRef(), "propid"); - if (shape->previous()) - MarkShape(trc, &shape->previousRef(), "parent"); -} - -static inline void -MarkBaseShapeGetterSetter(JSTracer *trc, BaseShape *base) -{ - if (base->hasGetterObject()) - MarkObjectUnbarriered(trc, base->getterObject(), "getter"); - if (base->hasSetterObject()) - MarkObjectUnbarriered(trc, base->setterObject(), "setter"); + shape->markChildren(trc); } static void MarkChildren(JSTracer *trc, BaseShape *base) { - MarkBaseShapeGetterSetter(trc, base); - if (base->isOwned()) - MarkBaseShapeUnbarriered(trc, base->baseUnowned(), "base"); - - if (JSObject *parent = base->getObjectParent()) - MarkObjectUnbarriered(trc, parent, "parent"); + base->markChildren(trc); } /* @@ -773,11 +701,22 @@ MarkCycleCollectorChildren(JSTracer *trc, BaseShape *base, JSObject **prevParent */ base->assertConsistency(); - MarkBaseShapeGetterSetter(trc, base); + if (base->hasGetterObject()) { + JSObject *tmp = base->getterObject(); + MarkObjectUnbarriered(trc, &tmp, "getter"); + JS_ASSERT(tmp == base->getterObject()); + } + + if (base->hasSetterObject()) { + JSObject *tmp = base->setterObject(); + MarkObjectUnbarriered(trc, &tmp, "setter"); + JS_ASSERT(tmp == base->setterObject()); + } JSObject *parent = base->getObjectParent(); if (parent && parent != *prevParent) { - MarkObjectUnbarriered(trc, parent, "parent"); + MarkObjectUnbarriered(trc, &parent, "parent"); + JS_ASSERT(parent == base->getObjectParent()); *prevParent = parent; } } diff --git a/js/src/jsgcmark.h b/js/src/jsgcmark.h index 9e02debcb70d..3884a8786a2b 100644 --- a/js/src/jsgcmark.h +++ b/js/src/jsgcmark.h @@ -47,11 +47,12 @@ namespace gc { #define DeclMarker(base, type) \ void Mark##base(JSTracer *trc, HeapPtr *thing, const char *name); \ void Mark##base##Root(JSTracer *trc, type **thingp, const char *name); \ -void Mark##base##Unbarriered(JSTracer *trc, type *thing, const char *name); \ +void Mark##base##Unbarriered(JSTracer *trc, type **thingp, const char *name); \ void Mark##base##Range(JSTracer *trc, size_t len, HeapPtr *thing, const char *name); \ void Mark##base##RootRange(JSTracer *trc, size_t len, type **thing, const char *name); DeclMarker(BaseShape, BaseShape) +DeclMarker(BaseShape, UnownedBaseShape) DeclMarker(Object, ArgumentsObject) DeclMarker(Object, GlobalObject) DeclMarker(Object, JSObject) @@ -120,7 +121,10 @@ void MarkSlot(JSTracer *trc, HeapSlot *s, const char *name); void -MarkSlotRange(JSTracer *trc, size_t len, HeapSlot *vec, const char *name); +MarkArraySlots(JSTracer *trc, size_t len, HeapSlot *vec, const char *name); + +void +MarkObjectSlots(JSTracer *trc, JSObject *obj, uint32_t start, uint32_t nslots); /* * Mark a value that may be in a different compartment from the compartment @@ -129,8 +133,16 @@ MarkSlotRange(JSTracer *trc, size_t len, HeapSlot *vec, const char *name); void MarkCrossCompartmentSlot(JSTracer *trc, HeapSlot *s, const char *name); + /*** Special Cases ***/ +/* + * The unioned HeapPtr stored in script->globalObj needs special treatment to + * typecheck correctly. + */ +void +MarkObject(JSTracer *trc, HeapPtr *thingp, const char *name); + /* Direct value access used by the write barriers and the methodjit. */ void MarkValueUnbarriered(JSTracer *trc, Value *v, const char *name); diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h index d7923b84aaa0..223adc442d9b 100644 --- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -1312,8 +1312,11 @@ TypeObject::writeBarrierPre(TypeObject *type) return; JSCompartment *comp = type->compartment(); - if (comp->needsBarrier()) - MarkTypeObjectUnbarriered(comp->barrierTracer(), type, "write barrier"); + if (comp->needsBarrier()) { + TypeObject *tmp = type; + MarkTypeObjectUnbarriered(comp->barrierTracer(), &tmp, "write barrier"); + JS_ASSERT(tmp == type); + } #endif } @@ -1327,8 +1330,11 @@ TypeObject::readBarrier(TypeObject *type) { #ifdef JSGC_INCREMENTAL JSCompartment *comp = type->compartment(); - if (comp->needsBarrier()) - MarkTypeObjectUnbarriered(comp->barrierTracer(), type, "read barrier"); + if (comp->needsBarrier()) { + TypeObject *tmp = type; + MarkTypeObjectUnbarriered(comp->barrierTracer(), &tmp, "read barrier"); + JS_ASSERT(tmp == type); + } #endif } @@ -1341,7 +1347,7 @@ TypeNewScript::writeBarrierPre(TypeNewScript *newScript) JSCompartment *comp = newScript->fun->compartment(); if (comp->needsBarrier()) { - MarkObjectUnbarriered(comp->barrierTracer(), newScript->fun, "write barrier"); + MarkObject(comp->barrierTracer(), &newScript->fun, "write barrier"); MarkShape(comp->barrierTracer(), &newScript->shape, "write barrier"); } #endif diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index f95754c5b523..96ed1a0b741c 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -440,6 +440,21 @@ js::RunScript(JSContext *cx, JSScript *script, StackFrame *fp) } } +#ifdef DEBUG + struct CheckStackBalance { + JSContext *cx; + StackFrame *fp; + JSObject *enumerators; + CheckStackBalance(JSContext *cx) + : cx(cx), fp(cx->fp()), enumerators(cx->enumerators) + {} + ~CheckStackBalance() { + JS_ASSERT(fp == cx->fp()); + JS_ASSERT_IF(!fp->isGeneratorFrame(), enumerators == cx->enumerators); + } + } check(cx); +#endif + #ifdef JS_METHODJIT mjit::CompileStatus status; status = mjit::CanMethodJIT(cx, script, script->code, fp->isConstructing(), @@ -511,12 +526,9 @@ js::InvokeKernel(JSContext *cx, CallArgs args, MaybeConstruct construct) return false; /* Run function until JSOP_STOP, JSOP_RETURN or error. */ - JSBool ok; - { - AutoPreserveEnumerators preserve(cx); - ok = RunScript(cx, fun->script(), fp); - } + JSBool ok = RunScript(cx, fun->script(), fp); + /* Propagate the return value out. */ args.rval() = fp->returnValue(); JS_ASSERT_IF(ok && construct, !args.rval().isPrimitive()); return ok; @@ -653,17 +665,17 @@ js::ExecuteKernel(JSContext *cx, JSScript *script, JSObject &scopeChain, const V TypeScript::SetThis(cx, script, fp->thisValue()); - AutoPreserveEnumerators preserve(cx); - JSBool ok = RunScript(cx, script, fp); - if (result && ok) - *result = fp->returnValue(); + bool ok = RunScript(cx, script, fp); if (fp->isStrictEvalFrame()) js_PutCallObject(fp); Probes::stopExecution(cx, script); - return !!ok; + /* Propgate the return value out. */ + if (result) + *result = fp->returnValue(); + return ok; } bool @@ -977,31 +989,35 @@ js::UnwindScope(JSContext *cx, uint32_t stackDepth) } /* - * Find the results of incrementing or decrementing *vp. For pre-increments, - * both *vp and *vp2 will contain the result on return. For post-increments, - * vp will contain the original value converted to a number and vp2 will get - * the result. Both vp and vp2 must be roots. + * Increment/decrement the value 'v'. The resulting value is stored in *slot. + * The result of the expression (taking into account prefix/postfix) is stored + * in *expr. */ static bool -DoIncDec(JSContext *cx, const JSCodeSpec *cs, Value *vp, Value *vp2) +DoIncDec(JSContext *cx, JSScript *script, jsbytecode *pc, const Value &v, Value *slot, Value *expr) { - if (cs->format & JOF_POST) { - double d; - if (!ToNumber(cx, *vp, &d)) - return JS_FALSE; - vp->setNumber(d); - (cs->format & JOF_INC) ? ++d : --d; - vp2->setNumber(d); - return JS_TRUE; + const JSCodeSpec &cs = js_CodeSpec[*pc]; + + if (v.isInt32()) { + int32_t i = v.toInt32(); + if (i > JSVAL_INT_MIN && i < JSVAL_INT_MAX) { + int32_t sum = i + (cs.format & JOF_INC ? 1 : -1); + *slot = Int32Value(sum); + *expr = (cs.format & JOF_POST) ? Int32Value(i) : *slot; + return true; + } } double d; - if (!ToNumber(cx, *vp, &d)) - return JS_FALSE; - (cs->format & JOF_INC) ? ++d : --d; - vp->setNumber(d); - *vp2 = *vp; - return JS_TRUE; + if (!ToNumber(cx, *slot, &d)) + return false; + + double sum = d + (cs.format & JOF_INC ? 1 : -1); + *slot = NumberValue(sum); + *expr = (cs.format & JOF_POST) ? NumberValue(d) : *slot; + + TypeScript::MonitorOverflow(cx, script, pc); + return true; } const Value & @@ -1071,31 +1087,14 @@ js::FindUpvarFrame(JSContext *cx, unsigned targetLevel) #define POP_BOOLEAN(cx, vp, b) do { VALUE_TO_BOOLEAN(cx, vp, b); regs.sp--; } while(0) -#define VALUE_TO_OBJECT(cx, vp, obj) \ - JS_BEGIN_MACRO \ - if ((vp)->isObject()) { \ - obj = &(vp)->toObject(); \ - } else { \ - obj = js_ValueToNonNullObject(cx, *(vp)); \ - if (!obj) \ - goto error; \ - (vp)->setObject(*obj); \ - } \ - JS_END_MACRO - #define FETCH_OBJECT(cx, n, obj) \ JS_BEGIN_MACRO \ Value *vp_ = ®s.sp[n]; \ - VALUE_TO_OBJECT(cx, vp_, obj); \ + obj = ToObject(cx, (vp_)); \ + if (!obj) \ + goto error; \ JS_END_MACRO -/* Test whether v is an int in the range [-2^31 + 1, 2^31 - 2] */ -static JS_ALWAYS_INLINE bool -CanIncDecWithoutOverflow(int32_t i) -{ - return (i > JSVAL_INT_MIN) && (i < JSVAL_INT_MAX); -} - /* * Threaded interpretation via computed goto appears to be well-supported by * GCC 3 and higher. IBM's C compiler when run with the right options (e.g., @@ -2499,62 +2498,29 @@ BEGIN_CASE(JSOP_GNAMEDEC) /* No-op */ END_CASE(JSOP_INCPROP) -{ - int incr, incr2; - uint32_t slot; - Value *vp; - - /* Position cases so the most frequent i++ does not need a jump. */ BEGIN_CASE(JSOP_DECARG) - incr = -1; incr2 = -1; goto do_arg_incop; BEGIN_CASE(JSOP_ARGDEC) - incr = -1; incr2 = 0; goto do_arg_incop; BEGIN_CASE(JSOP_INCARG) - incr = 1; incr2 = 1; goto do_arg_incop; BEGIN_CASE(JSOP_ARGINC) - incr = 1; incr2 = 0; - - do_arg_incop: - slot = GET_ARGNO(regs.pc); - JS_ASSERT(slot < regs.fp()->numFormalArgs()); - vp = argv + slot; - goto do_int_fast_incop; +{ + Value &arg = regs.fp()->formalArg(GET_ARGNO(regs.pc)); + if (!DoIncDec(cx, script, regs.pc, arg, &arg, ®s.sp[0])) + goto error; + regs.sp++; +} +END_CASE(JSOP_ARGINC); BEGIN_CASE(JSOP_DECLOCAL) - incr = -1; incr2 = -1; goto do_local_incop; BEGIN_CASE(JSOP_LOCALDEC) - incr = -1; incr2 = 0; goto do_local_incop; BEGIN_CASE(JSOP_INCLOCAL) - incr = 1; incr2 = 1; goto do_local_incop; BEGIN_CASE(JSOP_LOCALINC) - incr = 1; incr2 = 0; - - /* - * do_local_incop comes right before do_int_fast_incop as we want to - * avoid an extra jump for variable cases as local++ is more frequent - * than arg++. - */ - do_local_incop: - slot = GET_SLOTNO(regs.pc); - JS_ASSERT(slot < regs.fp()->numSlots()); - vp = regs.fp()->slots() + slot; - - do_int_fast_incop: - int32_t tmp; - if (JS_LIKELY(vp->isInt32() && CanIncDecWithoutOverflow(tmp = vp->toInt32()))) { - vp->getInt32Ref() = tmp + incr; - JS_ASSERT(JSOP_INCARG_LENGTH == js_CodeSpec[op].length); - PUSH_INT32(tmp + incr2); - } else { - PUSH_COPY(*vp); - if (!DoIncDec(cx, &js_CodeSpec[op], ®s.sp[-1], vp)) - goto error; - TypeScript::MonitorOverflow(cx, script, regs.pc); - } - len = JSOP_INCARG_LENGTH; - JS_ASSERT(len == js_CodeSpec[op].length); - DO_NEXT_OP(len); +{ + Value &local = regs.fp()->localSlot(GET_SLOTNO(regs.pc)); + if (!DoIncDec(cx, script, regs.pc, local, &local, ®s.sp[0])) + goto error; + regs.sp++; } +END_CASE(JSOP_LOCALINC) BEGIN_CASE(JSOP_THIS) if (!ComputeThis(cx, regs.fp())) @@ -2915,7 +2881,7 @@ BEGIN_CASE(JSOP_TABLESWITCH) int32_t high = GET_JUMP_OFFSET(pc2); i -= low; - if ((jsuint)i < (jsuint)(high - low + 1)) { + if ((uint32_t)i < (uint32_t)(high - low + 1)) { pc2 += JUMP_OFFSET_LEN + JUMP_OFFSET_LEN * i; int32_t off = (int32_t) GET_JUMP_OFFSET(pc2); if (off) @@ -3567,9 +3533,9 @@ BEGIN_CASE(JSOP_INITELEM) if (rref.isMagic(JS_ARRAY_HOLE)) { JS_ASSERT(obj->isArray()); JS_ASSERT(JSID_IS_INT(id)); - JS_ASSERT(jsuint(JSID_TO_INT(id)) < StackSpace::ARGS_LENGTH_MAX); + JS_ASSERT(uint32_t(JSID_TO_INT(id)) < StackSpace::ARGS_LENGTH_MAX); if (JSOp(regs.pc[JSOP_INITELEM_LENGTH]) == JSOP_ENDINIT && - !js_SetLengthProperty(cx, obj, (jsuint) (JSID_TO_INT(id) + 1))) { + !js_SetLengthProperty(cx, obj, (uint32_t) (JSID_TO_INT(id) + 1))) { goto error; } } else { diff --git a/js/src/jsinterpinlines.h b/js/src/jsinterpinlines.h index 1f24a5d872b6..9c11ef007a28 100644 --- a/js/src/jsinterpinlines.h +++ b/js/src/jsinterpinlines.h @@ -61,21 +61,6 @@ namespace js { -class AutoPreserveEnumerators { - JSContext *cx; - JSObject *enumerators; - - public: - AutoPreserveEnumerators(JSContext *cx) : cx(cx), enumerators(cx->enumerators) - { - } - - ~AutoPreserveEnumerators() - { - cx->enumerators = enumerators; - } -}; - /* * Compute the implicit |this| parameter for a call expression where the callee * funval was resolved from an unqualified name reference to a property on obj @@ -232,7 +217,7 @@ GetPropertyOperation(JSContext *cx, jsbytecode *pc, const Value &lval, Value *vp if (lval.isObject()) { JSObject *obj = &lval.toObject(); if (obj->isArray()) { - jsuint length = obj->getArrayLength(); + uint32_t length = obj->getArrayLength(); *vp = NumberValue(length); return true; } @@ -255,7 +240,7 @@ GetPropertyOperation(JSContext *cx, jsbytecode *pc, const Value &lval, Value *vp } } - JSObject *obj = ValueToObjectOrPrototype(cx, lval); + JSObject *obj = ValueToObject(cx, lval); if (!obj) return false; @@ -804,13 +789,13 @@ SetObjectElementOperation(JSContext *cx, JSObject *obj, jsid id, const Value &va do { if (obj->isDenseArray() && JSID_IS_INT(id)) { - jsuint length = obj->getDenseArrayInitializedLength(); + uint32_t length = obj->getDenseArrayInitializedLength(); int32_t i = JSID_TO_INT(id); - if ((jsuint)i < length) { + if ((uint32_t)i < length) { if (obj->getDenseArrayElement(i).isMagic(JS_ARRAY_HOLE)) { if (js_PrototypeHasIndexedProperties(cx, obj)) break; - if ((jsuint)i >= obj->getArrayLength()) + if ((uint32_t)i >= obj->getArrayLength()) obj->setArrayLength(cx, i + 1); } obj->setDenseArrayElementWithType(cx, i, value); diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index 17c25123d492..c837982e274e 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -435,7 +435,7 @@ js_math_min(JSContext *cx, unsigned argc, Value *vp) static double powi(double x, int y) { - jsuint n = (y < 0) ? -y : y; + unsigned n = (y < 0) ? -y : y; double m = x; double p = 1; while (true) { diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 3ae22a765821..40a9777ab820 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -78,11 +78,11 @@ #include "jsinferinlines.h" #include "jsnuminlines.h" #include "jsobjinlines.h" -#include "jsstrinlines.h" #include "vm/MethodGuard-inl.h" #include "vm/NumberObject-inl.h" #include "vm/String-inl.h" +#include "vm/StringBuffer-inl.h" using namespace js; using namespace js::types; @@ -577,7 +577,7 @@ js_IntToString(JSContext *cx, int32_t si) static char * IntToCString(ToCStringBuf *cbuf, int i, int base = 10) { - jsuint u = (i < 0) ? -i : i; + unsigned u = (i < 0) ? -i : i; RangedPtr cp(cbuf->sbuf + cbuf->sbufSize - 1, cbuf->sbuf, cbuf->sbufSize); *cp = '\0'; @@ -589,7 +589,7 @@ IntToCString(ToCStringBuf *cbuf, int i, int base = 10) break; case 16: do { - jsuint newu = u / 16; + unsigned newu = u / 16; *--cp = "0123456789abcdef"[u - newu * 16]; u = newu; } while (u != 0); @@ -597,7 +597,7 @@ IntToCString(ToCStringBuf *cbuf, int i, int base = 10) default: JS_ASSERT(base >= 2 && base <= 36); do { - jsuint newu = u / base; + unsigned newu = u / base; *--cp = "0123456789abcdefghijklmnopqrstuvwxyz"[u - newu * base]; u = newu; } while (u != 0); @@ -1127,7 +1127,7 @@ js_NumberToStringWithBase(JSContext *cx, double d, int base) if (JSDOUBLE_IS_INT32(d, &i)) { if (base == 10 && StaticStrings::hasInt(i)) return cx->runtime->staticStrings.getInt(i); - if (jsuint(i) < jsuint(base)) { + if (unsigned(i) < unsigned(base)) { if (i < 10) return cx->runtime->staticStrings.getInt(i); jschar c = 'a' + i - 10; @@ -1346,7 +1346,7 @@ ValueToUint16Slow(JSContext *cx, const Value &v, uint16_t *out) bool neg = (d < 0); d = floor(neg ? -d : d); d = neg ? -d : d; - jsuint m = JS_BIT(16); + unsigned m = JS_BIT(16); d = fmod(d, (double) m); if (d < 0) d += m; diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index e47b39ccaeea..f1eef3864995 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -89,9 +89,9 @@ #include "jsobjinlines.h" #include "jsscopeinlines.h" #include "jsscriptinlines.h" -#include "jsstrinlines.h" #include "vm/MethodGuard-inl.h" +#include "vm/StringBuffer-inl.h" #if JS_HAS_XML_SUPPORT #include "jsxml.h" @@ -2194,7 +2194,7 @@ DefinePropertyOnArray(JSContext *cx, JSObject *obj, const jsid &id, const PropDe if (obj->isDenseArray() && !obj->makeDenseArraySlow(cx)) return JS_FALSE; - jsuint oldLen = obj->getArrayLength(); + uint32_t oldLen = obj->getArrayLength(); if (JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom)) { /* diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 83c282c2d4f8..1224884999db 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -695,7 +695,7 @@ struct JSObject : public js::ObjectImpl inline js::types::TypeObject *getType(JSContext *cx); - js::HeapPtr &typeFromGC() { + const js::HeapPtr &typeFromGC() const { /* Direct field access for use by GC. */ return type_; } diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index d09d13384b1b..5fe9f8a4df40 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -652,23 +652,6 @@ JSObject::denseArrayHasInlineSlots() const namespace js { -inline JSObject * -ValueToObjectOrPrototype(JSContext *cx, const Value &v) -{ - if (v.isObject()) - return &v.toObject(); - GlobalObject *global = &cx->fp()->scopeChain().global(); - if (v.isString()) - return global->getOrCreateStringPrototype(cx); - if (v.isNumber()) - return global->getOrCreateNumberPrototype(cx); - if (v.isBoolean()) - return global->getOrCreateBooleanPrototype(cx); - JS_ASSERT(v.isNull() || v.isUndefined()); - js_ReportIsNullOrUndefined(cx, JSDVG_SEARCH_STACK, v, NULL); - return NULL; -} - /* * Any name atom for a function which will be added as a DeclEnv object to the * scope chain above call objects for fun. diff --git a/js/src/json.cpp b/js/src/json.cpp index 5eddb5cc592e..5d8ba1e0475c 100644 --- a/js/src/json.cpp +++ b/js/src/json.cpp @@ -64,9 +64,9 @@ #include "jsboolinlines.h" #include "jsinferinlines.h" #include "jsobjinlines.h" -#include "jsstrinlines.h" #include "vm/Stack-inl.h" +#include "vm/StringBuffer-inl.h" using namespace js; using namespace js::gc; @@ -500,7 +500,7 @@ JA(JSContext *cx, JSObject *obj, StringifyContext *scx) return JS_FALSE; /* Step 6. */ - jsuint length; + uint32_t length; if (!js_GetLengthProperty(cx, obj, &length)) return JS_FALSE; @@ -649,7 +649,7 @@ js_Stringify(JSContext *cx, Value *vp, JSObject *replacer, Value space, StringBu */ /* Step 4b(ii). */ - jsuint len; + uint32_t len; JS_ALWAYS_TRUE(js_GetLengthProperty(cx, replacer, &len)); if (replacer->isDenseArray()) len = JS_MIN(len, replacer->getDenseArrayCapacity()); @@ -659,7 +659,7 @@ js_Stringify(JSContext *cx, Value *vp, JSObject *replacer, Value space, StringBu return false; /* Step 4b(iii). */ - jsuint i = 0; + uint32_t i = 0; /* Step 4b(iv). */ for (; i < len; i++) { diff --git a/js/src/jsonparser.cpp b/js/src/jsonparser.cpp index bd21ffc48064..fb4e021c0e9f 100644 --- a/js/src/jsonparser.cpp +++ b/js/src/jsonparser.cpp @@ -43,7 +43,8 @@ #include "jsonparser.h" #include "jsobjinlines.h" -#include "jsstrinlines.h" + +#include "vm/StringBuffer-inl.h" using namespace js; diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 8960317e2df2..41d0d90ef600 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -77,11 +77,11 @@ #include "jscntxtinlines.h" #include "jsobjinlines.h" #include "jsopcodeinlines.h" -#include "jsscriptinlines.h" #include "jsautooplen.h" #include "vm/RegExpObject-inl.h" +#include "vm/StringBuffer-inl.h" using namespace mozilla; using namespace js; @@ -1825,7 +1825,7 @@ GetLocal(SprintStack *ss, int i) if (obj->isBlock()) { uint32_t depth = obj->asBlock().stackDepth(); uint32_t count = obj->asBlock().slotCount(); - if (jsuint(i - depth) < jsuint(count)) + if (uint32_t(i - depth) < uint32_t(count)) return GetLocalInSlot(ss, i, int(i - depth), obj); } } @@ -1838,7 +1838,7 @@ GetLocal(SprintStack *ss, int i) if (obj->isBlock()) { uint32_t depth = obj->asBlock().stackDepth(); uint32_t count = obj->asBlock().slotCount(); - if (jsuint(i - depth) < jsuint(count)) + if (uint32_t(i - depth) < uint32_t(count)) return GetLocalInSlot(ss, i, int(i - depth), obj); } } @@ -2446,15 +2446,10 @@ SprintNormalFor(JSContext *cx, JSPrinter *jp, SprintStack *ss, const char *initP ptrdiff_t next = js_GetSrcNoteOffset(sn, 1); ptrdiff_t tail = js_GetSrcNoteOffset(sn, 2); - /* - * If this loop has a condition, then pc points at a goto - * targeting the condition. - */ + /* Find the loop head, skipping over any leading GOTO or NOP. */ jsbytecode *pc2 = pc; - if (cond != tail) { - LOCAL_ASSERT(*pc == JSOP_GOTO); - pc2 += JSOP_GOTO_LENGTH; - } + if (*pc == JSOP_GOTO || *pc == JSOP_NOP) + pc2 += GetBytecodeLength(pc); LOCAL_ASSERT(tail + GET_JUMP_OFFSET(pc + tail) == pc2 - pc); if (cond != tail) { diff --git a/js/src/jspropertytree.cpp b/js/src/jspropertytree.cpp index bab78a357290..3aeb248735c5 100644 --- a/js/src/jspropertytree.cpp +++ b/js/src/jspropertytree.cpp @@ -150,7 +150,7 @@ Shape::removeChild(Shape *child) if (hash->count() == 1) { /* Convert from HASH form back to SHAPE form. */ - KidsHash::Range r = hash->all(); + KidsHash::Range r = hash->all(); Shape *otherChild = r.front(); JS_ASSERT((r.popFront(), r.empty())); /* No more elements! */ kidp->setShape(otherChild); @@ -166,8 +166,11 @@ ReadBarrier(Shape *shape) { #ifdef JSGC_INCREMENTAL JSCompartment *comp = shape->compartment(); - if (comp->needsBarrier()) - MarkShapeUnbarriered(comp->barrierTracer(), shape, "read barrier"); + if (comp->needsBarrier()) { + Shape *tmp = shape; + MarkShapeUnbarriered(comp->barrierTracer(), &tmp, "read barrier"); + JS_ASSERT(tmp == shape); + } #endif return shape; } diff --git a/js/src/jsproxy.cpp b/js/src/jsproxy.cpp index e8a14d574f07..8878c2cacd9c 100644 --- a/js/src/jsproxy.cpp +++ b/js/src/jsproxy.cpp @@ -494,11 +494,11 @@ ArrayToIdVector(JSContext *cx, const Value &array, AutoIdVector &props) return true; JSObject *obj = &array.toObject(); - jsuint length; + uint32_t length; if (!js_GetLengthProperty(cx, obj, &length)) return false; - for (jsuint n = 0; n < length; ++n) { + for (uint32_t n = 0; n < length; ++n) { if (!JS_CHECK_OPERATION_LIMIT(cx)) return false; Value v; diff --git a/js/src/jspubtd.h b/js/src/jspubtd.h index e6ef4610a068..0859351f15c4 100644 --- a/js/src/jspubtd.h +++ b/js/src/jspubtd.h @@ -93,9 +93,6 @@ typedef ptrdiff_t jsid; JS_BEGIN_EXTERN_C -/* Scalar typedefs. */ -typedef uint32_t jsuint; - #ifdef WIN32 typedef wchar_t jschar; #else diff --git a/js/src/jsscope.cpp b/js/src/jsscope.cpp index 7afca25be9dd..53f381a56ede 100644 --- a/js/src/jsscope.cpp +++ b/js/src/jsscope.cpp @@ -644,7 +644,7 @@ JSObject::addPropertyInternal(JSContext *cx, jsid id, { shape = self->lastProperty(); - jsuint index; + uint32_t index; bool indexed = js_IdIsIndex(id, &index); UnownedBaseShape *nbase; if (shape->base()->matchesGetterSetter(getter, setter) && !indexed) { @@ -758,7 +758,7 @@ JSObject::putProperty(JSContext *cx, jsid id, RootedVar nbase(cx); { - jsuint index; + uint32_t index; bool indexed = js_IdIsIndex(id, &index); StackBaseShape base(self->lastProperty()->base()); base.updateGetterSetter(attrs, getter, setter); diff --git a/js/src/jsscope.h b/js/src/jsscope.h index bad89a349f44..35b5c0cb84d3 100644 --- a/js/src/jsscope.h +++ b/js/src/jsscope.h @@ -390,6 +390,8 @@ class BaseShape : public js::gc::Cell static inline ThingRootKind rootKind() { return THING_ROOT_BASE_SHAPE; } + inline void markChildren(JSTracer *trc); + private: static void staticAsserts() { JS_STATIC_ASSERT(offsetof(BaseShape, clasp) == offsetof(js::shadow::BaseShape, clasp)); @@ -563,10 +565,6 @@ struct Shape : public js::gc::Cell return parent; } - HeapPtrShape &previousRef() { - return parent; - } - class Range { protected: friend struct Shape; @@ -910,6 +908,8 @@ struct Shape : public js::gc::Cell static inline ThingRootKind rootKind() { return THING_ROOT_SHAPE; } + inline void markChildren(JSTracer *trc); + /* For JIT usage */ static inline size_t offsetOfBase() { return offsetof(Shape, base_); } diff --git a/js/src/jsscopeinlines.h b/js/src/jsscopeinlines.h index 75352eaf6eed..b6006c6f97f9 100644 --- a/js/src/jsscopeinlines.h +++ b/js/src/jsscopeinlines.h @@ -394,8 +394,11 @@ Shape::writeBarrierPre(const js::Shape *shape) return; JSCompartment *comp = shape->compartment(); - if (comp->needsBarrier()) - MarkShapeUnbarriered(comp->barrierTracer(), const_cast(shape), "write barrier"); + if (comp->needsBarrier()) { + Shape *tmp = const_cast(shape); + MarkShapeUnbarriered(comp->barrierTracer(), &tmp, "write barrier"); + JS_ASSERT(tmp == shape); + } #endif } @@ -409,11 +412,23 @@ Shape::readBarrier(const Shape *shape) { #ifdef JSGC_INCREMENTAL JSCompartment *comp = shape->compartment(); - if (comp->needsBarrier()) - MarkShapeUnbarriered(comp->barrierTracer(), const_cast(shape), "read barrier"); + if (comp->needsBarrier()) { + Shape *tmp = const_cast(shape); + MarkShapeUnbarriered(comp->barrierTracer(), &tmp, "read barrier"); + JS_ASSERT(tmp == shape); + } #endif } +inline void +Shape::markChildren(JSTracer *trc) +{ + MarkBaseShape(trc, &base_, "base"); + gc::MarkId(trc, &propidRef(), "propid"); + if (parent) + MarkShape(trc, &parent, "parent"); +} + inline void BaseShape::writeBarrierPre(BaseShape *base) { @@ -422,8 +437,11 @@ BaseShape::writeBarrierPre(BaseShape *base) return; JSCompartment *comp = base->compartment(); - if (comp->needsBarrier()) - MarkBaseShapeUnbarriered(comp->barrierTracer(), base, "write barrier"); + if (comp->needsBarrier()) { + BaseShape *tmp = base; + MarkBaseShapeUnbarriered(comp->barrierTracer(), &tmp, "write barrier"); + JS_ASSERT(tmp == base); + } #endif } @@ -437,11 +455,30 @@ BaseShape::readBarrier(BaseShape *base) { #ifdef JSGC_INCREMENTAL JSCompartment *comp = base->compartment(); - if (comp->needsBarrier()) - MarkBaseShapeUnbarriered(comp->barrierTracer(), base, "read barrier"); + if (comp->needsBarrier()) { + BaseShape *tmp = base; + MarkBaseShapeUnbarriered(comp->barrierTracer(), &tmp, "read barrier"); + JS_ASSERT(tmp == base); + } #endif } +inline void +BaseShape::markChildren(JSTracer *trc) +{ + if (hasGetterObject()) + MarkObjectUnbarriered(trc, &getterObj, "getter"); + + if (hasSetterObject()) + MarkObjectUnbarriered(trc, &setterObj, "setter"); + + if (isOwned()) + MarkBaseShape(trc, &unowned_, "base"); + + if (parent) + MarkObject(trc, &parent, "parent"); +} + } /* namespace js */ #endif /* jsscopeinlines_h___ */ diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index cadbcca7db6e..fd4fa7036c29 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -322,20 +322,6 @@ Bindings::trace(JSTracer *trc) MarkShape(trc, &lastBinding, "shape"); } -#ifdef JS_CRASH_DIAGNOSTICS - -void -CheckScript(JSScript *script, JSScript *prev) -{ - if (script->cookie1[0] != JS_SCRIPT_COOKIE || script->cookie2[0] != JS_SCRIPT_COOKIE) { - crash::StackBuffer buf1(script); - crash::StackBuffer buf2(prev); - JS_OPT_ASSERT(false); - } -} - -#endif /* JS_CRASH_DIAGNOSTICS */ - #if JS_HAS_XDR static bool @@ -1453,10 +1439,24 @@ js_CallDestroyScriptHook(JSContext *cx, JSScript *script) JS_ClearScriptTraps(cx, script); } +#ifdef JS_CRASH_DIAGNOSTICS + +void +JSScript::CheckScript(JSScript *prev) +{ + if (cookie1[0] != JS_SCRIPT_COOKIE || cookie2[0] != JS_SCRIPT_COOKIE) { + crash::StackBuffer buf1(this); + crash::StackBuffer buf2(prev); + JS_OPT_ASSERT(false); + } +} + +#endif /* JS_CRASH_DIAGNOSTICS */ + void JSScript::finalize(JSContext *cx, bool background) { - CheckScript(this, NULL); + CheckScript(NULL); js_CallDestroyScriptHook(cx, this); @@ -1945,13 +1945,52 @@ JSScript::clearTraps(JSContext *cx) } void -JSScript::markTrapClosures(JSTracer *trc) +JSScript::markChildren(JSTracer *trc) { - JS_ASSERT(hasAnyBreakpointsOrStepMode()); + CheckScript(NULL); - for (unsigned i = 0; i < length; i++) { - BreakpointSite *site = debug->breakpoints[i]; - if (site && site->trapHandler) - MarkValue(trc, &site->trapClosure, "trap closure"); + JS_ASSERT_IF(trc->runtime->gcCheckCompartment, + compartment() == trc->runtime->gcCheckCompartment); + + for (uint32_t i = 0; i < natoms; ++i) { + if (atoms[i]) + MarkStringUnbarriered(trc, &atoms[i], "atom"); + } + + if (JSScript::isValidOffset(objectsOffset)) { + JSObjectArray *objarray = objects(); + MarkObjectRange(trc, objarray->length, objarray->vector, "objects"); + } + + if (JSScript::isValidOffset(regexpsOffset)) { + JSObjectArray *objarray = regexps(); + MarkObjectRange(trc, objarray->length, objarray->vector, "objects"); + } + + if (JSScript::isValidOffset(constOffset)) { + JSConstArray *constarray = consts(); + MarkValueRange(trc, constarray->length, constarray->vector, "consts"); + } + + if (function()) + MarkObject(trc, &function_, "function"); + + if (!isCachedEval && globalObject) + MarkObject(trc, &globalObject, "object"); + + if (IS_GC_MARKING_TRACER(trc) && filename) + js_MarkScriptFilename(filename); + + bindings.trace(trc); + + if (types) + types->trace(trc); + + if (hasAnyBreakpointsOrStepMode()) { + for (unsigned i = 0; i < length; i++) { + BreakpointSite *site = debug->breakpoints[i]; + if (site && site->trapHandler) + MarkValue(trc, &site->trapClosure, "trap closure"); + } } } diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 96783b5797cc..e7d2ad2c4195 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -554,7 +554,11 @@ struct JSScript : public js::gc::Cell { #ifdef JS_CRASH_DIAGNOSTICS /* All diagnostic fields must be multiples of Cell::CellSize. */ uint32_t cookie2[Cell::CellSize / sizeof(uint32_t)]; -#endif + + void CheckScript(JSScript *prev); +#else + void CheckScript(JSScript *prev) {} +#endif /* !JS_CRASH_DIAGNOSTICS */ #ifdef DEBUG /* @@ -834,6 +838,8 @@ struct JSScript : public js::gc::Cell { JSPrincipals *originPrincipals) { return originPrincipals ? originPrincipals : principals; } + + void markChildren(JSTracer *trc); }; /* If this fails, padding_ can be removed. */ diff --git a/js/src/jsscriptinlines.h b/js/src/jsscriptinlines.h index 9d56c318997b..951b6e5df15c 100644 --- a/js/src/jsscriptinlines.h +++ b/js/src/jsscriptinlines.h @@ -245,7 +245,9 @@ JSScript::writeBarrierPre(JSScript *script) JSCompartment *comp = script->compartment(); if (comp->needsBarrier()) { JS_ASSERT(!comp->rt->gcRunning); - MarkScriptUnbarriered(comp->barrierTracer(), script, "write barrier"); + JSScript *tmp = script; + MarkScriptUnbarriered(comp->barrierTracer(), &tmp, "write barrier"); + JS_ASSERT(tmp == script); } #endif } diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 995112384751..ec570757ebc7 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -79,6 +79,7 @@ #include "jsinferinlines.h" #include "jsobjinlines.h" +#include "jsstrinlines.h" #include "jsautooplen.h" // generated headers last #include "vm/MethodGuard-inl.h" @@ -86,6 +87,7 @@ #include "vm/RegExpStatics-inl.h" #include "vm/StringObject-inl.h" #include "vm/String-inl.h" +#include "vm/StringBuffer-inl.h" using namespace js; using namespace js::gc; @@ -875,31 +877,31 @@ out_of_range: * * Return the index of pat in text, or -1 if not found. */ -static const jsuint sBMHCharSetSize = 256; /* ISO-Latin-1 */ -static const jsuint sBMHPatLenMax = 255; /* skip table element is uint8_t */ -static const int sBMHBadPattern = -2; /* return value if pat is not ISO-Latin-1 */ +static const uint32_t sBMHCharSetSize = 256; /* ISO-Latin-1 */ +static const uint32_t sBMHPatLenMax = 255; /* skip table element is uint8_t */ +static const int sBMHBadPattern = -2; /* return value if pat is not ISO-Latin-1 */ int -js_BoyerMooreHorspool(const jschar *text, jsuint textlen, - const jschar *pat, jsuint patlen) +js_BoyerMooreHorspool(const jschar *text, uint32_t textlen, + const jschar *pat, uint32_t patlen) { uint8_t skip[sBMHCharSetSize]; JS_ASSERT(0 < patlen && patlen <= sBMHPatLenMax); - for (jsuint i = 0; i < sBMHCharSetSize; i++) + for (uint32_t i = 0; i < sBMHCharSetSize; i++) skip[i] = (uint8_t)patlen; - jsuint m = patlen - 1; - for (jsuint i = 0; i < m; i++) { + uint32_t m = patlen - 1; + for (uint32_t i = 0; i < m; i++) { jschar c = pat[i]; if (c >= sBMHCharSetSize) return sBMHBadPattern; skip[c] = (uint8_t)(m - i); } jschar c; - for (jsuint k = m; + for (uint32_t k = m; k < textlen; k += ((c = text[k]) >= sBMHCharSetSize) ? patlen : skip[c]) { - for (jsuint i = k, j = m; ; i--, j--) { + for (uint32_t i = k, j = m; ; i--, j--) { if (text[i] != pat[j]) break; if (j == 0) @@ -910,8 +912,8 @@ js_BoyerMooreHorspool(const jschar *text, jsuint textlen, } struct MemCmp { - typedef jsuint Extent; - static JS_ALWAYS_INLINE Extent computeExtent(const jschar *, jsuint patlen) { + typedef uint32_t Extent; + static JS_ALWAYS_INLINE Extent computeExtent(const jschar *, uint32_t patlen) { return (patlen - 1) * sizeof(jschar); } static JS_ALWAYS_INLINE bool match(const jschar *p, const jschar *t, Extent extent) { @@ -921,7 +923,7 @@ struct MemCmp { struct ManualCmp { typedef const jschar *Extent; - static JS_ALWAYS_INLINE Extent computeExtent(const jschar *pat, jsuint patlen) { + static JS_ALWAYS_INLINE Extent computeExtent(const jschar *pat, uint32_t patlen) { return pat + patlen; } static JS_ALWAYS_INLINE bool match(const jschar *p, const jschar *t, Extent extent) { @@ -935,7 +937,7 @@ struct ManualCmp { template static int -UnrolledMatch(const jschar *text, jsuint textlen, const jschar *pat, jsuint patlen) +UnrolledMatch(const jschar *text, uint32_t textlen, const jschar *pat, uint32_t patlen) { JS_ASSERT(patlen > 0 && textlen > 0); const jschar *textend = text + textlen - (patlen - 1); @@ -980,8 +982,8 @@ UnrolledMatch(const jschar *text, jsuint textlen, const jschar *pat, jsuint patl } static JS_ALWAYS_INLINE int -StringMatch(const jschar *text, jsuint textlen, - const jschar *pat, jsuint patlen) +StringMatch(const jschar *text, uint32_t textlen, + const jschar *pat, uint32_t patlen) { if (patlen == 0) return 0; @@ -1044,7 +1046,7 @@ static const size_t sRopeMatchThresholdRatioLog2 = 5; * the 'match' outparam (-1 for not found). */ static bool -RopeMatch(JSContext *cx, JSString *textstr, const jschar *pat, jsuint patlen, int *match) +RopeMatch(JSContext *cx, JSString *textstr, const jschar *pat, uint32_t patlen, int *match) { JS_ASSERT(textstr->isRope()); @@ -1155,21 +1157,21 @@ str_indexOf(JSContext *cx, unsigned argc, Value *vp) if (!patstr) return false; - jsuint textlen = str->length(); + uint32_t textlen = str->length(); const jschar *text = str->getChars(cx); if (!text) return false; - jsuint patlen = patstr->length(); + uint32_t patlen = patstr->length(); const jschar *pat = patstr->chars(); - jsuint start; + uint32_t start; if (args.length() > 1) { if (args[1].isInt32()) { int i = args[1].toInt32(); if (i <= 0) { start = 0; - } else if (jsuint(i) > textlen) { + } else if (uint32_t(i) > textlen) { start = textlen; textlen = 0; } else { @@ -3112,28 +3114,6 @@ js_NewString(JSContext *cx, jschar *chars, size_t length) return s; } -static JS_ALWAYS_INLINE JSFixedString * -NewShortString(JSContext *cx, const jschar *chars, size_t length) -{ - /* - * Don't bother trying to find a static atom; measurement shows that not - * many get here (for one, Atomize is catching them). - */ - - JS_ASSERT(JSShortString::lengthFits(length)); - JSInlineString *str = JSInlineString::lengthFits(length) - ? JSInlineString::new_(cx) - : JSShortString::new_(cx); - if (!str) - return NULL; - - jschar *storage = str->init(length); - PodCopy(storage, chars, length); - storage[length] = 0; - Probes::createString(cx, str, length); - return str; -} - static JSInlineString * NewShortString(JSContext *cx, const char *chars, size_t length) { @@ -3165,74 +3145,6 @@ NewShortString(JSContext *cx, const char *chars, size_t length) return str; } -jschar * -StringBuffer::extractWellSized() -{ - size_t capacity = cb.capacity(); - size_t length = cb.length(); - - jschar *buf = cb.extractRawBuffer(); - if (!buf) - return NULL; - - /* For medium/big buffers, avoid wasting more than 1/4 of the memory. */ - JS_ASSERT(capacity >= length); - if (length > CharBuffer::sMaxInlineStorage && capacity - length > length / 4) { - size_t bytes = sizeof(jschar) * (length + 1); - JSContext *cx = context(); - jschar *tmp = (jschar *)cx->realloc_(buf, bytes); - if (!tmp) { - cx->free_(buf); - return NULL; - } - buf = tmp; - } - - return buf; -} - -JSFixedString * -StringBuffer::finishString() -{ - JSContext *cx = context(); - if (cb.empty()) - return cx->runtime->atomState.emptyAtom; - - size_t length = cb.length(); - if (!checkLength(length)) - return NULL; - - JS_STATIC_ASSERT(JSShortString::MAX_SHORT_LENGTH < CharBuffer::InlineLength); - if (JSShortString::lengthFits(length)) - return NewShortString(cx, cb.begin(), length); - - if (!cb.append('\0')) - return NULL; - - jschar *buf = extractWellSized(); - if (!buf) - return NULL; - - JSFixedString *str = js_NewString(cx, buf, length); - if (!str) - cx->free_(buf); - return str; -} - -JSAtom * -StringBuffer::finishAtom() -{ - JSContext *cx = context(); - - size_t length = cb.length(); - if (length == 0) - return cx->runtime->atomState.emptyAtom; - - JSAtom *atom = js_AtomizeChars(cx, cb.begin(), length); - cb.clear(); - return atom; -} - JSLinearString * js_NewDependentString(JSContext *cx, JSString *baseArg, size_t start, size_t length) { @@ -4045,7 +3957,7 @@ Decode(JSContext *cx, JSString *str, const jschar *reservedSet, Value *rval) goto report_bad_uri; if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2])) goto report_bad_uri; - jsuint B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]); + uint32_t B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]); k += 2; if (!(B & 0x80)) { c = (jschar)B; diff --git a/js/src/jsstrinlines.h b/js/src/jsstrinlines.h index 576e90d9604b..70ee796ae2dd 100644 --- a/js/src/jsstrinlines.h +++ b/js/src/jsstrinlines.h @@ -51,197 +51,6 @@ namespace js { -/* - * String builder that eagerly checks for over-allocation past the maximum - * string length. - * - * Any operation which would exceed the maximum string length causes an - * exception report on the context and results in a failed return value. - * - * Well-sized extractions (which waste no more than 1/4 of their char - * buffer space) are guaranteed for strings built by this interface. - * See |extractWellSized|. - * - * Note: over-allocation is not checked for when using the infallible - * |replaceRawBuffer|, so the implementation of |finishString| also must check - * for over-allocation. - */ -class StringBuffer -{ - /* cb's buffer is taken by the new string so use ContextAllocPolicy. */ - typedef Vector CharBuffer; - - CharBuffer cb; - - static inline bool checkLength(JSContext *cx, size_t length); - inline bool checkLength(size_t length); - JSContext *context() const { return cb.allocPolicy().context(); } - jschar *extractWellSized(); - - StringBuffer(const StringBuffer &other) MOZ_DELETE; - void operator=(const StringBuffer &other) MOZ_DELETE; - - public: - explicit inline StringBuffer(JSContext *cx); - bool reserve(size_t len); - bool resize(size_t len); - bool append(const jschar c); - bool append(const jschar *chars, size_t len); - bool append(const jschar *begin, const jschar *end); - bool append(JSString *str); - bool append(JSLinearString *str); - bool appendN(const jschar c, size_t n); - bool appendInflated(const char *cstr, size_t len); - - /* Infallible variants usable when the corresponding space is reserved. */ - void infallibleAppend(const jschar c) { - cb.infallibleAppend(c); - } - void infallibleAppend(const jschar *chars, size_t len) { - cb.infallibleAppend(chars, len); - } - void infallibleAppend(const jschar *begin, const jschar *end) { - cb.infallibleAppend(begin, end); - } - void infallibleAppendN(const jschar c, size_t n) { - cb.infallibleAppendN(c, n); - } - - JSAtom *atomize(unsigned flags = 0); - static JSAtom *atomize(JSContext *cx, const CharBuffer &cb, unsigned flags = 0); - static JSAtom *atomize(JSContext *cx, const jschar *begin, size_t length, unsigned flags = 0); - - void replaceRawBuffer(jschar *chars, size_t len) { cb.replaceRawBuffer(chars, len); } - jschar *begin() { return cb.begin(); } - jschar *end() { return cb.end(); } - const jschar *begin() const { return cb.begin(); } - const jschar *end() const { return cb.end(); } - bool empty() const { return cb.empty(); } - inline int length() const; - - /* - * Creates a string from the characters in this buffer, then (regardless - * whether string creation succeeded or failed) empties the buffer. - */ - JSFixedString *finishString(); - - /* Identical to finishString() except that an atom is created. */ - JSAtom *finishAtom(); - - template - bool append(const char (&array)[ArrayLength]) { - return cb.append(array, array + ArrayLength - 1); /* No trailing '\0'. */ - } -}; - -inline -StringBuffer::StringBuffer(JSContext *cx) - : cb(cx) -{} - -inline bool -StringBuffer::reserve(size_t len) -{ - if (!checkLength(len)) - return false; - return cb.reserve(len); -} - -inline bool -StringBuffer::resize(size_t len) -{ - if (!checkLength(len)) - return false; - return cb.resize(len); -} - -inline bool -StringBuffer::append(const jschar c) -{ - if (!checkLength(cb.length() + 1)) - return false; - return cb.append(c); -} - -inline bool -StringBuffer::append(const jschar *chars, size_t len) -{ - if (!checkLength(cb.length() + len)) - return false; - return cb.append(chars, len); -} - -inline bool -StringBuffer::append(const jschar *begin, const jschar *end) -{ - if (!checkLength(cb.length() + (end - begin))) - return false; - return cb.append(begin, end); -} - -inline bool -StringBuffer::append(JSString *str) -{ - JSLinearString *linear = str->ensureLinear(context()); - if (!linear) - return false; - return append(linear); -} - -inline bool -StringBuffer::append(JSLinearString *str) -{ - JS::Anchor anch(str); - return cb.append(str->chars(), str->length()); -} - -inline bool -StringBuffer::appendN(const jschar c, size_t n) -{ - if (!checkLength(cb.length() + n)) - return false; - return cb.appendN(c, n); -} - -inline bool -StringBuffer::appendInflated(const char *cstr, size_t cstrlen) -{ - size_t lengthBefore = length(); - if (!cb.growByUninitialized(cstrlen)) - return false; - DebugOnly oldcstrlen = cstrlen; - DebugOnly ok = InflateStringToBuffer(context(), cstr, cstrlen, - begin() + lengthBefore, &cstrlen); - JS_ASSERT(ok && oldcstrlen == cstrlen); - return true; -} - -inline int -StringBuffer::length() const -{ - JS_STATIC_ASSERT(int(JSString::MAX_LENGTH) == JSString::MAX_LENGTH); - JS_ASSERT(cb.length() <= JSString::MAX_LENGTH); - return int(cb.length()); -} - -inline bool -StringBuffer::checkLength(size_t length) -{ - return JSString::validateLength(context(), length); -} - -extern bool -ValueToStringBufferSlow(JSContext *cx, const Value &v, StringBuffer &sb); - -inline bool -ValueToStringBuffer(JSContext *cx, const Value &v, StringBuffer &sb) -{ - if (v.isString()) - return sb.append(v.toString()); - - return ValueToStringBufferSlow(cx, v, sb); -} - class RopeBuilder { JSContext *cx; JSString *res; diff --git a/js/src/jstypedarray.cpp b/js/src/jstypedarray.cpp index 99e87fe9b041..0e274733f91f 100644 --- a/js/src/jstypedarray.cpp +++ b/js/src/jstypedarray.cpp @@ -81,7 +81,7 @@ using namespace js::types; static const uint8_t ARRAYBUFFER_RESERVED_SLOTS = JSObject::MAX_FIXED_SLOTS - 1; static bool -ValueIsLength(JSContext *cx, const Value &v, jsuint *len) +ValueIsLength(JSContext *cx, const Value &v, uint32_t *len) { if (v.isInt32()) { int32_t i = v.toInt32(); @@ -96,7 +96,7 @@ ValueIsLength(JSContext *cx, const Value &v, jsuint *len) if (JSDOUBLE_IS_NaN(d)) return false; - jsuint length = jsuint(d); + uint32_t length = uint32_t(d); if (d != double(length)) return false; @@ -330,8 +330,10 @@ ArrayBuffer::obj_trace(JSTracer *trc, JSObject *obj) * so it's safe to leave it Unbarriered. */ JSObject *delegate = static_cast(obj->getPrivate()); - if (delegate) - MarkObjectUnbarriered(trc, delegate, "arraybuffer.delegate"); + if (delegate) { + MarkObjectUnbarriered(trc, &delegate, "arraybuffer.delegate"); + obj->setPrivate(delegate); + } } static JSProperty * const PROPERTY_FOUND = reinterpret_cast(1); @@ -728,9 +730,9 @@ TypedArray::getTypedArray(JSObject *obj) } inline bool -TypedArray::isArrayIndex(JSContext *cx, JSObject *obj, jsid id, jsuint *ip) +TypedArray::isArrayIndex(JSContext *cx, JSObject *obj, jsid id, uint32_t *ip) { - jsuint index; + uint32_t index; if (js_IdIsIndex(id, &index) && index < getLength(obj)) { if (ip) *ip = index; @@ -1262,7 +1264,7 @@ class TypedArrayTemplate return true; } - jsuint index; + uint32_t index; // We can't just chain to js_SetPropertyHelper, because we're not a normal object. if (!isArrayIndex(cx, tarray, id, &index)) { // Silent ignore is better than an exception here, because @@ -1507,7 +1509,7 @@ class TypedArrayTemplate /* N.B. there may not be an argv[-2]/argv[-1]. */ /* () or (number) */ - jsuint len = 0; + uint32_t len = 0; if (argc == 0 || ValueIsLength(cx, argv[0], &len)) { JSObject *bufobj = createBufferWithSizeAndCount(cx, len); if (!bufobj) @@ -1661,7 +1663,7 @@ class TypedArrayTemplate if (!copyFromTypedArray(cx, obj, src, offset)) return false; } else { - jsuint len; + uint32_t len; if (!js_GetLengthProperty(cx, arg0, &len)) return false; @@ -1732,7 +1734,7 @@ class TypedArrayTemplate * Otherwise create a new typed array and copy len properties from the * object. */ - jsuint len; + uint32_t len; if (!js_GetLengthProperty(cx, other, &len)) return NULL; @@ -1822,7 +1824,7 @@ class TypedArrayTemplate static bool copyFromArray(JSContext *cx, JSObject *thisTypedArrayObj, - JSObject *ar, jsuint len, jsuint offset = 0) + JSObject *ar, uint32_t len, uint32_t offset = 0) { thisTypedArrayObj = getTypedArray(thisTypedArrayObj); JS_ASSERT(thisTypedArrayObj); @@ -1856,7 +1858,7 @@ class TypedArrayTemplate } static bool - copyFromTypedArray(JSContext *cx, JSObject *thisTypedArrayObj, JSObject *tarray, jsuint offset) + copyFromTypedArray(JSContext *cx, JSObject *thisTypedArrayObj, JSObject *tarray, uint32_t offset) { thisTypedArrayObj = getTypedArray(thisTypedArrayObj); JS_ASSERT(thisTypedArrayObj); @@ -1933,7 +1935,7 @@ class TypedArrayTemplate } static bool - copyFromWithOverlap(JSContext *cx, JSObject *self, JSObject *tarray, jsuint offset) + copyFromWithOverlap(JSContext *cx, JSObject *self, JSObject *tarray, uint32_t offset) { JS_ASSERT(offset <= getLength(self)); @@ -2530,13 +2532,13 @@ js_IsTypedArray(JSObject *obj) } JS_FRIEND_API(JSObject *) -js_CreateArrayBuffer(JSContext *cx, jsuint nbytes) +js_CreateArrayBuffer(JSContext *cx, uint32_t nbytes) { return ArrayBuffer::create(cx, nbytes); } JS_FRIEND_API(JSObject *) -JS_NewArrayBuffer(JSContext *cx, jsuint nbytes) +JS_NewArrayBuffer(JSContext *cx, uint32_t nbytes) { return js_CreateArrayBuffer(cx, nbytes); } @@ -2579,7 +2581,7 @@ TypedArrayConstruct(JSContext *cx, int atype, unsigned argc, Value *argv) } JS_FRIEND_API(JSObject *) -js_CreateTypedArray(JSContext *cx, int atype, jsuint nelements) +js_CreateTypedArray(JSContext *cx, int atype, uint32_t nelements) { JS_ASSERT(atype >= 0 && atype < TypedArray::TYPE_MAX); diff --git a/js/src/jstypedarray.h b/js/src/jstypedarray.h index d19194f98844..bfed3c2c73a7 100644 --- a/js/src/jstypedarray.h +++ b/js/src/jstypedarray.h @@ -252,7 +252,7 @@ struct JS_FRIEND_API(TypedArray) { public: static bool - isArrayIndex(JSContext *cx, JSObject *obj, jsid id, jsuint *ip = NULL); + isArrayIndex(JSContext *cx, JSObject *obj, jsid id, uint32_t *ip = NULL); static inline uint32_t slotWidth(int atype) { switch (atype) { @@ -335,7 +335,7 @@ JS_FRIEND_API(JSBool) JS_IsArrayBufferObject(JSObject *obj); JS_FRIEND_API(JSObject *) -JS_NewArrayBuffer(JSContext *cx, jsuint nbytes); +JS_NewArrayBuffer(JSContext *cx, uint32_t nbytes); JS_FRIEND_API(uint32_t) JS_GetArrayBufferByteLength(JSObject *obj); diff --git a/js/src/jsxml.cpp b/js/src/jsxml.cpp index 0c4e3fdee6aa..97a0ffcf30f1 100644 --- a/js/src/jsxml.cpp +++ b/js/src/jsxml.cpp @@ -75,10 +75,10 @@ #include "jsatominlines.h" #include "jsinferinlines.h" #include "jsobjinlines.h" -#include "jsstrinlines.h" #include "vm/Stack-inl.h" #include "vm/String-inl.h" +#include "vm/StringBuffer-inl.h" #ifdef DEBUG #include /* for #ifdef DEBUG memset calls */ @@ -4665,7 +4665,7 @@ HasFunctionProperty(JSContext *cx, JSObject *obj, jsid funid, JSBool *found) } static bool -IdValIsIndex(JSContext *cx, jsval id, jsuint *indexp, bool *isIndex) +IdValIsIndex(JSContext *cx, jsval id, uint32_t *indexp, bool *isIndex) { if (JSVAL_IS_INT(id)) { int32_t i = JSVAL_TO_INT(id); @@ -4673,7 +4673,7 @@ IdValIsIndex(JSContext *cx, jsval id, jsuint *indexp, bool *isIndex) *isIndex = false; return true; } - *indexp = (jsuint)i; + *indexp = (uint32_t)i; *isIndex = true; return true; } @@ -5160,8 +5160,10 @@ xml_trace(JSTracer *trc, JSObject *obj) * This is safe to leave Unbarriered for incremental GC, but we'll need * to fix somehow for generational. */ - if (xml) - MarkXMLUnbarriered(trc, xml, "private"); + if (xml) { + MarkXMLUnbarriered(trc, &xml, "private"); + JS_ASSERT(xml == obj->getPrivate()); + } } static JSBool @@ -6196,7 +6198,7 @@ static JSBool xml_namespace(JSContext *cx, unsigned argc, jsval *vp) { JSLinearString *prefix, *nsprefix; - jsuint i, length; + uint32_t i, length; JSObject *ns; NON_LIST_XML_METHOD_PROLOG; @@ -7308,8 +7310,11 @@ JSXML::writeBarrierPre(JSXML *xml) return; JSCompartment *comp = xml->compartment(); - if (comp->needsBarrier()) - MarkXMLUnbarriered(comp->barrierTracer(), xml, "write barrier"); + if (comp->needsBarrier()) { + JSXML *tmp = xml; + MarkXMLUnbarriered(comp->barrierTracer(), &tmp, "write barrier"); + JS_ASSERT(tmp == xml); + } #endif } diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index 6b050655d4d9..04c339d8738c 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -141,7 +141,7 @@ mjit::Compiler::Compiler(JSContext *cx, JSScript *outerScript, CompileStatus mjit::Compiler::compile() { - JS_ASSERT(!outerChunk.chunk); + JS_ASSERT(!outerChunkRef().chunk); void **checkAddr = isConstructing ? &outerScript->jitArityCheckCtor @@ -557,8 +557,8 @@ mjit::Compiler::performCompilation() #endif JaegerSpew(JSpew_Scripts, "successfully compiled (code \"%p\") (size \"%u\")\n", - outerChunk.chunk->code.m_code.executableAddress(), - unsigned(outerChunk.chunk->code.m_size)); + outerChunkRef().chunk->code.m_code.executableAddress(), + unsigned(outerChunkRef().chunk->code.m_size)); return Compile_Okay; } @@ -1213,25 +1213,29 @@ mjit::Compiler::ensureDoubleArguments() } void -mjit::Compiler::markUndefinedLocals() +mjit::Compiler::markUndefinedLocal(uint32_t offset, uint32_t i) { uint32_t depth = ssa.getFrame(a->inlineIndex).depth; + uint32_t slot = LocalSlot(script, i); + Address local(JSFrameReg, sizeof(StackFrame) + (depth + i) * sizeof(Value)); + if (!cx->typeInferenceEnabled() || !analysis->trackSlot(slot)) { + masm.storeValue(UndefinedValue(), local); + } else { + Lifetime *lifetime = analysis->liveness(slot).live(offset); + if (lifetime) + masm.storeValue(UndefinedValue(), local); + } +} +void +mjit::Compiler::markUndefinedLocals() +{ /* * Set locals to undefined, as in initCallFrameLatePrologue. * Skip locals which aren't closed and are known to be defined before used, */ - for (uint32_t i = 0; i < script->nfixed; i++) { - uint32_t slot = LocalSlot(script, i); - Address local(JSFrameReg, sizeof(StackFrame) + (depth + i) * sizeof(Value)); - if (!cx->typeInferenceEnabled() || !analysis->trackSlot(slot)) { - masm.storeValue(UndefinedValue(), local); - } else { - Lifetime *lifetime = analysis->liveness(slot).live(0); - if (lifetime) - masm.storeValue(UndefinedValue(), local); - } - } + for (uint32_t i = 0; i < script->nfixed; i++) + markUndefinedLocal(0, i); } CompileStatus @@ -1780,7 +1784,7 @@ mjit::Compiler::finishThisUp() result, masm.size(), result + masm.size(), stubcc.size()); - outerChunk.chunk = chunk; + outerChunkRef().chunk = chunk; /* Patch all incoming and outgoing cross-chunk jumps. */ CrossChunkEdge *crossEdges = jit->edges(); @@ -3050,6 +3054,10 @@ mjit::Compiler::generateMethod() { uint32_t slot = GET_SLOTNO(PC); JSFunction *fun = script->getFunction(GET_UINT32_INDEX(PC + SLOTNO_LEN)); + + /* See JSOP_DEFLOCALFUN. */ + markUndefinedLocal(PC - script->code, slot); + prepareStubCall(Uses(frame.frameSlots())); masm.move(ImmPtr(fun), Registers::ArgReg1); INLINE_STUBCALL(stubs::DefLocalFun_FC, REJOIN_DEFLOCALFUN); @@ -3130,6 +3138,16 @@ mjit::Compiler::generateMethod() { uint32_t slot = GET_SLOTNO(PC); JSFunction *fun = script->getFunction(GET_UINT32_INDEX(PC + SLOTNO_LEN)); + + /* + * The liveness analysis will report that the value in |slot| is + * defined at the start of this opcode. However, we don't actually + * fill it in until the stub returns. This will cause a problem if + * we GC inside the stub. So we write a safe value here so that the + * GC won't crash. + */ + markUndefinedLocal(PC - script->code, slot); + prepareStubCall(Uses(0)); masm.move(ImmPtr(fun), Registers::ArgReg1); INLINE_STUBCALL(stubs::DefLocalFun, REJOIN_DEFLOCALFUN); diff --git a/js/src/methodjit/Compiler.h b/js/src/methodjit/Compiler.h index 4bab9e9ba8f0..4c0218f850f4 100644 --- a/js/src/methodjit/Compiler.h +++ b/js/src/methodjit/Compiler.h @@ -398,7 +398,7 @@ class Compiler : public BaseCompiler JSScript *outerScript; unsigned chunkIndex; bool isConstructing; - ChunkDescriptor &outerChunk; + ChunkDescriptor outerChunk; /* SSA information for the outer script and all frames we will be inlining. */ analyze::CrossScriptSSA ssa; @@ -525,6 +525,10 @@ private: return outerScript->getJIT(isConstructing); } + ChunkDescriptor &outerChunkRef() { + return outerJIT()->chunkDescriptor(chunkIndex); + } + bool bytecodeInChunk(jsbytecode *pc) { return (unsigned(pc - outerScript->code) >= outerChunk.begin) && (unsigned(pc - outerScript->code) < outerChunk.end); @@ -570,6 +574,7 @@ private: /* Analysis helpers. */ CompileStatus prepareInferenceTypes(JSScript *script, ActiveFrame *a); void ensureDoubleArguments(); + void markUndefinedLocal(uint32_t offset, uint32_t i); void markUndefinedLocals(); void fixDoubleTypes(jsbytecode *target); void watchGlobalReallocation(); diff --git a/js/src/methodjit/MonoIC.cpp b/js/src/methodjit/MonoIC.cpp index cfade926ead5..b5b1183ea41f 100644 --- a/js/src/methodjit/MonoIC.cpp +++ b/js/src/methodjit/MonoIC.cpp @@ -1143,7 +1143,7 @@ ic::SplatApplyArgs(VMFrame &f) /* Steps 4-5. */ JSObject *aobj = &vp[3].toObject(); - jsuint length; + uint32_t length; if (!js_GetLengthProperty(cx, aobj, &length)) THROWV(false); diff --git a/js/src/methodjit/StubCalls.cpp b/js/src/methodjit/StubCalls.cpp index ea229603035c..2607c5bed260 100644 --- a/js/src/methodjit/StubCalls.cpp +++ b/js/src/methodjit/StubCalls.cpp @@ -234,13 +234,13 @@ stubs::SetElem(VMFrame &f) do { if (obj->isDenseArray() && JSID_IS_INT(id)) { - jsuint length = obj->getDenseArrayInitializedLength(); + uint32_t length = obj->getDenseArrayInitializedLength(); int32_t i = JSID_TO_INT(id); - if ((jsuint)i < length) { + if ((uint32_t)i < length) { if (obj->getDenseArrayElement(i).isMagic(JS_ARRAY_HOLE)) { if (js_PrototypeHasIndexedProperties(cx, obj)) break; - if ((jsuint)i >= obj->getArrayLength()) + if ((uint32_t)i >= obj->getArrayLength()) obj->setArrayLength(cx, i + 1); } obj->setDenseArrayElementWithType(cx, i, rval); @@ -1034,8 +1034,8 @@ stubs::InitElem(VMFrame &f, uint32_t last) if (rref.isMagic(JS_ARRAY_HOLE)) { JS_ASSERT(obj->isArray()); JS_ASSERT(JSID_IS_INT(id)); - JS_ASSERT(jsuint(JSID_TO_INT(id)) < StackSpace::ARGS_LENGTH_MAX); - if (last && !js_SetLengthProperty(cx, obj, (jsuint) (JSID_TO_INT(id) + 1))) + JS_ASSERT(uint32_t(JSID_TO_INT(id)) < StackSpace::ARGS_LENGTH_MAX); + if (last && !js_SetLengthProperty(cx, obj, (uint32_t) (JSID_TO_INT(id) + 1))) THROW(); } else { if (!obj->defineGeneric(cx, id, rref, NULL, NULL, JSPROP_ENUMERATE)) @@ -1584,7 +1584,7 @@ stubs::TableSwitch(VMFrame &f, jsbytecode *origPc) pc += JUMP_OFFSET_LEN; tableIdx -= low; - if ((jsuint) tableIdx < (jsuint)(high - low + 1)) { + if ((uint32_t) tableIdx < (uint32_t)(high - low + 1)) { pc += JUMP_OFFSET_LEN * tableIdx; if (uint32_t candidateOffset = GET_JUMP_OFFSET(pc)) jumpOffset = candidateOffset; diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 655049804981..d2d06854a79e 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -76,6 +76,7 @@ #include "jsxml.h" #include "jsperf.h" +#include "builtin/TestingFunctions.h" #include "frontend/BytecodeEmitter.h" #include "frontend/Parser.h" #include "methodjit/MethodJIT.h" @@ -1245,417 +1246,6 @@ AssertJit(JSContext *cx, unsigned argc, jsval *vp) return JS_TRUE; } -static JSBool -GC(JSContext *cx, unsigned argc, jsval *vp) -{ - JSCompartment *comp = NULL; - if (argc == 1) { - Value arg = vp[2]; - if (arg.isObject()) - comp = UnwrapObject(&arg.toObject())->compartment(); - } - -#ifndef JS_MORE_DETERMINISTIC - size_t preBytes = cx->runtime->gcBytes; -#endif - JS_CompartmentGC(cx, comp); - - char buf[256]; -#ifdef JS_MORE_DETERMINISTIC - buf[0] = '\0'; -#else - JS_snprintf(buf, sizeof(buf), "before %lu, after %lu, break %08lx\n", - (unsigned long)preBytes, (unsigned long)cx->runtime->gcBytes, -#ifdef HAVE_SBRK - (unsigned long)sbrk(0) -#else - 0 -#endif - ); -#endif - JSString *str = JS_NewStringCopyZ(cx, buf); - if (!str) - return false; - *vp = STRING_TO_JSVAL(str); - return true; -} - -static const struct ParamPair { - const char *name; - JSGCParamKey param; -} paramMap[] = { - {"maxBytes", JSGC_MAX_BYTES }, - {"maxMallocBytes", JSGC_MAX_MALLOC_BYTES}, - {"gcBytes", JSGC_BYTES}, - {"gcNumber", JSGC_NUMBER}, - {"sliceTimeBudget", JSGC_SLICE_TIME_BUDGET}, - {"markStackLimit", JSGC_MARK_STACK_LIMIT} -}; - -static JSBool -GCParameter(JSContext *cx, unsigned argc, jsval *vp) -{ - JSString *str; - if (argc == 0) { - str = JS_ValueToString(cx, JSVAL_VOID); - JS_ASSERT(str); - } else { - str = JS_ValueToString(cx, vp[2]); - if (!str) - return JS_FALSE; - vp[2] = STRING_TO_JSVAL(str); - } - - JSFlatString *flatStr = JS_FlattenString(cx, str); - if (!flatStr) - return false; - - size_t paramIndex = 0; - for (;; paramIndex++) { - if (paramIndex == ArrayLength(paramMap)) { - JS_ReportError(cx, - "the first argument argument must be maxBytes, " - "maxMallocBytes, gcStackpoolLifespan, gcBytes or " - "gcNumber"); - return false; - } - if (JS_FlatStringEqualsAscii(flatStr, paramMap[paramIndex].name)) - break; - } - JSGCParamKey param = paramMap[paramIndex].param; - - if (argc == 1) { - uint32_t value = JS_GetGCParameter(cx->runtime, param); - return JS_NewNumberValue(cx, value, &vp[0]); - } - - if (param == JSGC_NUMBER || - param == JSGC_BYTES) { - JS_ReportError(cx, "Attempt to change read-only parameter %s", - paramMap[paramIndex].name); - return false; - } - - uint32_t value; - if (!JS_ValueToECMAUint32(cx, vp[3], &value)) { - JS_ReportError(cx, - "the second argument must be convertable to uint32_t " - "with non-zero value"); - return false; - } - - if (param == JSGC_MAX_BYTES) { - uint32_t gcBytes = JS_GetGCParameter(cx->runtime, JSGC_BYTES); - if (value < gcBytes) { - JS_ReportError(cx, - "attempt to set maxBytes to the value less than the current " - "gcBytes (%u)", - gcBytes); - return false; - } - } - - JS_SetGCParameter(cx->runtime, param, value); - *vp = JSVAL_VOID; - return true; -} - -static JSBool -InternalConst(JSContext *cx, unsigned argc, jsval *vp) -{ - if (argc != 1) { - JS_ReportError(cx, "the function takes exactly one argument"); - return false; - } - - JSString *str = JS_ValueToString(cx, vp[2]); - if (!str) - return false; - JSFlatString *flat = JS_FlattenString(cx, str); - if (!flat) - return false; - - if (JS_FlatStringEqualsAscii(flat, "MARK_STACK_LENGTH")) { - vp[0] = UINT_TO_JSVAL(js::MARK_STACK_LENGTH); - } else { - JS_ReportError(cx, "unknown const name"); - return false; - } - return true; -} - -#ifdef JS_GC_ZEAL -static JSBool -GCZeal(JSContext *cx, unsigned argc, jsval *vp) -{ - uint32_t zeal, frequency = JS_DEFAULT_ZEAL_FREQ; - JSBool compartment = JS_FALSE; - - if (argc > 3) { - JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_TOO_MANY_ARGS, "gczeal"); - return JS_FALSE; - } - if (!JS_ValueToECMAUint32(cx, argc < 1 ? JSVAL_VOID : vp[2], &zeal)) - return JS_FALSE; - if (argc >= 2) - if (!JS_ValueToECMAUint32(cx, vp[3], &frequency)) - return JS_FALSE; - if (argc >= 3) - compartment = js_ValueToBoolean(vp[3]); - - JS_SetGCZeal(cx, (uint8_t)zeal, frequency, compartment); - *vp = JSVAL_VOID; - return JS_TRUE; -} - -static JSBool -ScheduleGC(JSContext *cx, unsigned argc, jsval *vp) -{ - uint32_t count; - bool compartment = false; - - if (argc != 1 && argc != 2) { - JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, - (argc < 1) - ? JSSMSG_NOT_ENOUGH_ARGS - : JSSMSG_TOO_MANY_ARGS, - "schedulegc"); - return JS_FALSE; - } - if (!JS_ValueToECMAUint32(cx, vp[2], &count)) - return JS_FALSE; - if (argc == 2) - compartment = js_ValueToBoolean(vp[3]); - - JS_ScheduleGC(cx, count, compartment); - *vp = JSVAL_VOID; - return JS_TRUE; -} - -static JSBool -VerifyBarriers(JSContext *cx, unsigned argc, jsval *vp) -{ - gc::VerifyBarriers(cx); - *vp = JSVAL_VOID; - return JS_TRUE; -} - -static JSBool -GCSlice(JSContext *cx, unsigned argc, jsval *vp) -{ - uint32_t budget; - - if (argc != 1) { - JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, - (argc < 1) - ? JSSMSG_NOT_ENOUGH_ARGS - : JSSMSG_TOO_MANY_ARGS, - "gcslice"); - return JS_FALSE; - } - if (!JS_ValueToECMAUint32(cx, vp[2], &budget)) - return JS_FALSE; - - GCDebugSlice(cx, budget); - *vp = JSVAL_VOID; - return JS_TRUE; -} -#endif /* JS_GC_ZEAL */ - -typedef struct JSCountHeapNode JSCountHeapNode; - -struct JSCountHeapNode { - void *thing; - JSGCTraceKind kind; - JSCountHeapNode *next; -}; - -typedef struct JSCountHeapTracer { - JSTracer base; - JSDHashTable visited; - bool ok; - JSCountHeapNode *traceList; - JSCountHeapNode *recycleList; -} JSCountHeapTracer; - -static void -CountHeapNotify(JSTracer *trc, void **thingp, JSGCTraceKind kind) -{ - JSCountHeapTracer *countTracer; - JSDHashEntryStub *entry; - JSCountHeapNode *node; - void *thing = *thingp; - - JS_ASSERT(trc->callback == CountHeapNotify); - countTracer = (JSCountHeapTracer *)trc; - if (!countTracer->ok) - return; - - entry = (JSDHashEntryStub *) - JS_DHashTableOperate(&countTracer->visited, thing, JS_DHASH_ADD); - if (!entry) { - countTracer->ok = false; - return; - } - if (entry->key) - return; - entry->key = thing; - - node = countTracer->recycleList; - if (node) { - countTracer->recycleList = node->next; - } else { - node = (JSCountHeapNode *) js_malloc(sizeof *node); - if (!node) { - countTracer->ok = false; - return; - } - } - node->thing = thing; - node->kind = kind; - node->next = countTracer->traceList; - countTracer->traceList = node; -} - -static const struct TraceKindPair { - const char *name; - int32_t kind; -} traceKindNames[] = { - { "all", -1 }, - { "object", JSTRACE_OBJECT }, - { "string", JSTRACE_STRING }, -#if JS_HAS_XML_SUPPORT - { "xml", JSTRACE_XML }, -#endif -}; - -static JSBool -CountHeap(JSContext *cx, unsigned argc, jsval *vp) -{ - void* startThing; - JSGCTraceKind startTraceKind; - jsval v; - int32_t traceKind; - JSString *str; - JSCountHeapTracer countTracer; - JSCountHeapNode *node; - size_t counter; - - startThing = NULL; - startTraceKind = JSTRACE_OBJECT; - if (argc > 0) { - v = JS_ARGV(cx, vp)[0]; - if (JSVAL_IS_TRACEABLE(v)) { - startThing = JSVAL_TO_TRACEABLE(v); - startTraceKind = JSVAL_TRACE_KIND(v); - } else if (!JSVAL_IS_NULL(v)) { - JS_ReportError(cx, - "the first argument is not null or a heap-allocated " - "thing"); - return JS_FALSE; - } - } - - traceKind = -1; - if (argc > 1) { - str = JS_ValueToString(cx, JS_ARGV(cx, vp)[1]); - if (!str) - return JS_FALSE; - JSFlatString *flatStr = JS_FlattenString(cx, str); - if (!flatStr) - return JS_FALSE; - for (size_t i = 0; ;) { - if (JS_FlatStringEqualsAscii(flatStr, traceKindNames[i].name)) { - traceKind = traceKindNames[i].kind; - break; - } - if (++i == ArrayLength(traceKindNames)) { - JSAutoByteString bytes(cx, str); - if (!!bytes) - JS_ReportError(cx, "trace kind name '%s' is unknown", bytes.ptr()); - return JS_FALSE; - } - } - } - - JS_TracerInit(&countTracer.base, JS_GetRuntime(cx), CountHeapNotify); - if (!JS_DHashTableInit(&countTracer.visited, JS_DHashGetStubOps(), - NULL, sizeof(JSDHashEntryStub), - JS_DHASH_DEFAULT_CAPACITY(100))) { - JS_ReportOutOfMemory(cx); - return JS_FALSE; - } - countTracer.ok = true; - countTracer.traceList = NULL; - countTracer.recycleList = NULL; - - if (!startThing) { - JS_TraceRuntime(&countTracer.base); - } else { - JS_SET_TRACING_NAME(&countTracer.base, "root"); - JS_CallTracer(&countTracer.base, startThing, startTraceKind); - } - - counter = 0; - while ((node = countTracer.traceList) != NULL) { - if (traceKind == -1 || node->kind == traceKind) - counter++; - countTracer.traceList = node->next; - node->next = countTracer.recycleList; - countTracer.recycleList = node; - JS_TraceChildren(&countTracer.base, node->thing, node->kind); - } - while ((node = countTracer.recycleList) != NULL) { - countTracer.recycleList = node->next; - js_free(node); - } - JS_DHashTableFinish(&countTracer.visited); - if (!countTracer.ok) { - JS_ReportOutOfMemory(cx); - return false; - } - - return JS_NewNumberValue(cx, (double) counter, vp); -} - -static unsigned finalizeCount = 0; - -static void -finalize_counter_finalize(JSContext *cx, JSObject *obj) -{ - JS_ATOMIC_INCREMENT(&finalizeCount); -} - -static JSClass FinalizeCounterClass = { - "FinalizeCounter", JSCLASS_IS_ANONYMOUS, - JS_PropertyStub, /* addProperty */ - JS_PropertyStub, /* delProperty */ - JS_PropertyStub, /* getProperty */ - JS_StrictPropertyStub, /* setProperty */ - JS_EnumerateStub, - JS_ResolveStub, - JS_ConvertStub, - finalize_counter_finalize -}; - -static JSBool -MakeFinalizeObserver(JSContext *cx, unsigned argc, jsval *vp) -{ - JSObject *obj = JS_NewObjectWithGivenProto(cx, &FinalizeCounterClass, NULL, - JS_GetGlobalObject(cx)); - if (!obj) - return false; - *vp = OBJECT_TO_JSVAL(obj); - return true; -} - -static JSBool -FinalizeCount(JSContext *cx, unsigned argc, jsval *vp) -{ - *vp = INT_TO_JSVAL(finalizeCount); - return true; -} - static JSScript * ValueToScript(JSContext *cx, jsval v, JSFunction **funp = NULL) { @@ -3893,47 +3483,6 @@ Deserialize(JSContext *cx, unsigned argc, jsval *vp) return true; } -JSBool -MJitCodeStats(JSContext *cx, unsigned argc, jsval *vp) -{ -#ifdef JS_METHODJIT - JSRuntime *rt = cx->runtime; - AutoLockGC lock(rt); - size_t n = 0; - for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) { - n += (*c)->sizeOfMjitCode(); - } - JS_SET_RVAL(cx, vp, INT_TO_JSVAL(n)); -#else - JS_SET_RVAL(cx, vp, JSVAL_VOID); -#endif - return true; -} - -JSBool -MJitChunkLimit(JSContext *cx, unsigned argc, jsval *vp) -{ - if (argc > 1 || argc == 0) { - JS_ReportError(cx, "Wrong number of arguments"); - return JS_FALSE; - } - - double t; - if (!JS_ValueToNumber(cx, JS_ARGV(cx, vp)[0], &t)) - return JS_FALSE; - -#ifdef JS_METHODJIT - mjit::SetChunkLimit((uint32_t) t); -#endif - - // Clear out analysis information which might refer to code compiled with - // the previous chunk limit. - JS_GC(cx); - - vp->setUndefined(); - return true; -} - enum CompartmentKind { SAME_COMPARTMENT, NEW_COMPARTMENT }; static JSObject * @@ -4009,183 +3558,151 @@ GetMaxArgs(JSContext *cx, unsigned arg, jsval *vp) return JS_TRUE; } -static JSBool -Terminate(JSContext *cx, unsigned arg, jsval *vp) -{ - JS_ClearPendingException(cx); - return JS_FALSE; -} +static JSFunctionSpecWithHelp shell_functions[] = { + JS_FN_HELP("version", Version, 0, 0, +"version([number])", +" Get or force a script compilation version number."), -static JSFunctionSpec shell_functions[] = { - JS_FN("version", Version, 0,0), - JS_FN("revertVersion", RevertVersion, 0,0), - JS_FN("options", Options, 0,0), - JS_FN("load", Load, 1,0), - JS_FN("evaluate", Evaluate, 1,0), - JS_FN("evalWithLocation", EvaluateWithLocation, 3,0), - JS_FN("run", Run, 1,0), - JS_FN("readline", ReadLine, 0,0), - JS_FN("print", Print, 0,0), - JS_FN("printErr", PrintErr, 0,0), - JS_FN("putstr", PutStr, 0,0), - JS_FN("dateNow", Now, 0,0), - JS_FN("help", Help, 0,0), - JS_FN("quit", Quit, 0,0), - JS_FN("assertEq", AssertEq, 2,0), - JS_FN("assertJit", AssertJit, 0,0), - JS_FN("gc", ::GC, 0,0), - JS_FN("gcparam", GCParameter, 2,0), - JS_FN("countHeap", CountHeap, 0,0), - JS_FN("makeFinalizeObserver", MakeFinalizeObserver, 0,0), - JS_FN("finalizeCount", FinalizeCount, 0,0), -#ifdef JS_GC_ZEAL - JS_FN("gczeal", GCZeal, 2,0), - JS_FN("schedulegc", ScheduleGC, 1,0), - JS_FN("verifybarriers", VerifyBarriers, 0,0), - JS_FN("gcslice", GCSlice, 1,0), -#endif - JS_FN("internalConst", InternalConst, 1,0), - JS_FN("setDebug", SetDebug, 1,0), - JS_FN("setDebuggerHandler", SetDebuggerHandler, 1,0), - JS_FN("setThrowHook", SetThrowHook, 1,0), - JS_FN("trap", Trap, 3,0), - JS_FN("untrap", Untrap, 2,0), - JS_FN("line2pc", LineToPC, 0,0), - JS_FN("pc2line", PCToLine, 0,0), - JS_FN("stringsAreUTF8", StringsAreUTF8, 0,0), - JS_FN("testUTF8", TestUTF8, 1,0), - JS_FN("throwError", ThrowError, 0,0), -#ifdef DEBUG - JS_FN("disassemble", DisassembleToString, 1,0), - JS_FN("dis", Disassemble, 1,0), - JS_FN("disfile", DisassFile, 1,0), - JS_FN("dissrc", DisassWithSrc, 1,0), - JS_FN("dumpHeap", DumpHeap, 0,0), - JS_FN("dumpObject", DumpObject, 1,0), - JS_FN("notes", Notes, 1,0), - JS_FN("stats", DumpStats, 1,0), - JS_FN("findReferences", FindReferences, 1,0), -#endif - JS_FN("dumpStack", DumpStack, 1,0), -#ifdef TEST_CVTARGS - JS_FN("cvtargs", ConvertArgs, 0,0), -#endif - JS_FN("build", BuildDate, 0,0), - JS_FN("clear", Clear, 0,0), - JS_FN("intern", Intern, 1,0), - JS_FN("clone", Clone, 1,0), - JS_FN("getpda", GetPDA, 1,0), - JS_FN("getslx", GetSLX, 1,0), - JS_FN("toint32", ToInt32, 1,0), - JS_FN("evalcx", EvalInContext, 1,0), - JS_FN("evalInFrame", EvalInFrame, 2,0), - JS_FN("shapeOf", ShapeOf, 1,0), - JS_FN("resolver", Resolver, 1,0), -#ifdef DEBUG - JS_FN("arrayInfo", js_ArrayInfo, 1,0), -#endif -#ifdef JS_THREADSAFE - JS_FN("sleep", Sleep_fn, 1,0), -#endif - JS_FN("snarf", Snarf, 0,0), - JS_FN("read", Snarf, 0,0), - JS_FN("compile", Compile, 1,0), - JS_FN("parse", Parse, 1,0), - JS_FN("timeout", Timeout, 1,0), - JS_FN("elapsed", Elapsed, 0,0), - JS_FN("parent", Parent, 1,0), - JS_FN("wrap", Wrap, 1,0), - JS_FN("serialize", Serialize, 1,0), - JS_FN("deserialize", Deserialize, 1,0), -#ifdef JS_METHODJIT - JS_FN("mjitcodestats", MJitCodeStats, 0,0), -#endif - JS_FN("mjitChunkLimit", MJitChunkLimit, 1,0), - JS_FN("newGlobal", NewGlobal, 1,0), - JS_FN("parseLegacyJSON",ParseLegacyJSON,1,0), - JS_FN("enableStackWalkingAssertion",EnableStackWalkingAssertion,1,0), - JS_FN("getMaxArgs", GetMaxArgs, 0,0), - JS_FN("terminate", Terminate, 0,0), - JS_FS_END -}; + JS_FN_HELP("revertVersion", RevertVersion, 0, 0, +"revertVersion()", +" Revert previously set version number."), -static const char shell_help_header[] = -"Command Description\n" -"======= ===========\n"; + JS_FN_HELP("options", Options, 0, 0, +"options([option ...])", +" Get or toggle JavaScript options."), -static const char *const shell_help_messages[] = { -"version([number]) Get or force a script compilation version number", -"revertVersion() Revert previously set version number", -"options([option ...]) Get or toggle JavaScript options", -"load(['foo.js' ...]) Load files named by string arguments", -"evaluate(code) Evaluate code as though it were the contents of a file", -"evalWithLocation(code, filename, lineno)\n" -" Eval code as if loaded from the given filename and line number", -"run('foo.js')\n" + JS_FN_HELP("load", Load, 1, 0, +"load(['foo.js' ...])", +" Load files named by string arguments."), + + JS_FN_HELP("evaluate", Evaluate, 1, 0, +"evaluate(code)", +" Evaluate code as though it were the contents of a file."), + + JS_FN_HELP("evalWithLocation", EvaluateWithLocation, 3, 0, +"evalWithLocation(code, filename, lineno)", +" Eval code as if loaded from the given filename and line number."), + + JS_FN_HELP("run", Run, 1, 0, +"run('foo.js')", " Run the file named by the first argument, returning the number of\n" -" of milliseconds spent compiling and executing it", -"readline() Read a single line from stdin", -"print([exp ...]) Evaluate and print expressions to stdout", -"printErr([exp ...]) Evaluate and print expressions to stderr", -"putstr([exp]) Evaluate and print expression without newline", -"dateNow() Return the current time with sub-ms precision", -"help([name ...]) Display usage and help messages", -"quit() Quit the shell", -"assertEq(actual, expected[, msg])\n" +" of milliseconds spent compiling and executing it."), + + JS_FN_HELP("readline", ReadLine, 0, 0, +"readline()", +" Read a single line from stdin."), + + JS_FN_HELP("print", Print, 0, 0, +"print([exp ...])", +" Evaluate and print expressions to stdout."), + + JS_FN_HELP("printErr", PrintErr, 0, 0, +"printErr([exp ...])", +" Evaluate and print expressions to stderr."), + + JS_FN_HELP("putstr", PutStr, 0, 0, +"putstr([exp])", +" Evaluate and print expression without newline."), + + JS_FN_HELP("dateNow", Now, 0, 0, +"dateNow()", +" Return the current time with sub-ms precision."), + + JS_FN_HELP("help", Help, 0, 0, +"help([name ...])", +" Display usage and help messages."), + + JS_FN_HELP("quit", Quit, 0, 0, +"quit()", +" Quit the shell."), + + JS_FN_HELP("assertEq", AssertEq, 2, 0, +"assertEq(actual, expected[, msg])", " Throw if the first two arguments are not the same (both +0 or both -0,\n" -" both NaN, or non-zero and ===)", -"assertJit() Throw if the calling function failed to JIT", -"gc([obj]) Run the garbage collector\n" -" When obj is given, GC only the compartment it's in", -"gcparam(name, value)\n" -" Wrapper for JS_SetGCParameter. The name must be either 'maxBytes' or\n" -" 'maxMallocBytes' and the value must be convertable to a positive uint32", -"countHeap([start[, kind]])\n" -" Count the number of live GC things in the heap or things reachable from\n" -" start when it is given and is not null. kind is either 'all' (default) to\n" -" count all things or one of 'object', 'double', 'string', 'function',\n" -" 'qname', 'namespace', 'xml' to count only things of that kind", -"makeFinalizeObserver()\n" -" get a special object whose finalization increases the counter returned\n" -" by the finalizeCount function", -"finalizeCount()\n" -" return the current value of the finalization counter that is incremented\n" -" each time an object returned by the makeFinalizeObserver is finalized", -#ifdef JS_GC_ZEAL -"gczeal(level, [freq], [compartmentGC?])\n" -" How zealous the garbage collector should be", -"schedulegc(num, [compartmentGC?])\n" -" Schedule a GC to happen after num allocations", -"verifybarriers() Start or end a run of the write barrier verifier", -"gcslice(n) Run an incremental GC slice that marks ~n objects", -#endif -"internalConst(name)\n" -" Query an internal constant for the engine. See InternalConst source for the\n" -" list of constant names", -"setDebug(debug) Set debug mode", -"setDebuggerHandler(f) Set handler for debugger keyword to f", -"setThrowHook(f) Set throw hook to f", -"trap([fun, [pc,]] exp) Trap bytecode execution", -"untrap(fun[, pc]) Remove a trap", -"line2pc([fun,] line) Map line number to PC", -"pc2line(fun[, pc]) Map PC to line number", -"stringsAreUTF8() Check if strings are UTF-8 encoded", -"testUTF8(mode) Perform UTF-8 tests (modes are 1 to 4)", -"throwError() Throw an error from JS_ReportError", +" both NaN, or non-zero and ===)."), + + JS_FN_HELP("assertJit", AssertJit, 0, 0, +"assertJit()", +" Throw if the calling function failed to JIT."), + + JS_FN_HELP("setDebug", SetDebug, 1, 0, +"setDebug(debug)", +" Set debug mode."), + + JS_FN_HELP("setDebuggerHandler", SetDebuggerHandler, 1, 0, +"setDebuggerHandler(f)", +" Set handler for debugger keyword to f."), + + JS_FN_HELP("setThrowHook", SetThrowHook, 1, 0, +"setThrowHook(f)", +" Set throw hook to f."), + + JS_FN_HELP("trap", Trap, 3, 0, +"trap([fun, [pc,]] exp)", +" Trap bytecode execution."), + + JS_FN_HELP("untrap", Untrap, 2, 0, +"untrap(fun[, pc])", +" Remove a trap."), + + JS_FN_HELP("line2pc", LineToPC, 0, 0, +"line2pc([fun,] line)", +" Map line number to PC."), + + JS_FN_HELP("pc2line", PCToLine, 0, 0, +"pc2line(fun[, pc])", +" Map PC to line number."), + + JS_FN_HELP("stringsAreUTF8", StringsAreUTF8, 0, 0, +"stringsAreUTF8()", +" Check if strings are UTF-8 encoded."), + + JS_FN_HELP("testUTF8", TestUTF8, 1, 0, +"testUTF8(mode)", +" Perform UTF-8 tests (modes are 1 to 4)."), + + JS_FN_HELP("throwError", ThrowError, 0, 0, +"throwError()", +" Throw an error from JS_ReportError."), + #ifdef DEBUG -"disassemble([fun]) Return the disassembly for the given function", -"dis([fun]) Disassemble functions into bytecodes", -"disfile('foo.js') Disassemble script file into bytecodes\n" -" dis and disfile take these options as preceeding string arguments\n" + JS_FN_HELP("disassemble", DisassembleToString, 1, 0, +"disassemble([fun])", +" Return the disassembly for the given function."), + + JS_FN_HELP("dis", Disassemble, 1, 0, +"dis([fun])", +" Disassemble functions into bytecodes."), + + JS_FN_HELP("disfile", DisassFile, 1, 0, +"disfile('foo.js')", +" Disassemble script file into bytecodes.\n" +" dis and disfile take these options as preceeding string arguments:\n" " \"-r\" (disassemble recursively)\n" -" \"-l\" (show line numbers)", -"dissrc([fun]) Disassemble functions with source lines", -"dumpHeap([fileName[, start[, toFind[, maxDepth[, toIgnore]]]]])\n" -" Interface to JS_DumpHeap with output sent to file", -"dumpObject() Dump an internal representation of an object", -"notes([fun]) Show source notes for functions", -"stats([string ...]) Dump 'atom' or 'global' stats", -"findReferences(target)\n" +" \"-l\" (show line numbers)"), + + JS_FN_HELP("dissrc", DisassWithSrc, 1, 0, +"dissrc([fun])", +" Disassemble functions with source lines."), + + JS_FN_HELP("dumpHeap", DumpHeap, 0, 0, +"dumpHeap([fileName[, start[, toFind[, maxDepth[, toIgnore]]]]])", +" Interface to JS_DumpHeap with output sent to file."), + + JS_FN_HELP("dumpObject", DumpObject, 1, 0, +"dumpObject()", +" Dump an internal representation of an object."), + + JS_FN_HELP("notes", Notes, 1, 0, +"notes([fun])", +" Show source notes for functions."), + + JS_FN_HELP("stats", DumpStats, 1, 0, +"stats([string ...])", +" Dump 'atom' or 'global' stats."), + + JS_FN_HELP("findReferences", FindReferences, 1, 0, +"findReferences(target)", " Walk the entire heap, looking for references to |target|, and return a\n" " \"references object\" describing what we found.\n" "\n" @@ -4207,91 +3724,144 @@ static const char *const shell_help_messages[] = { "\n" " If any references are found by the conservative scanner, the references\n" " object will have a property named \"edge: machine stack\"; the referrers will\n" -" be 'null', because they are roots.", +" be 'null', because they are roots."), + #endif -"dumpStack() Dump the stack as an array of callees (youngest first)", + JS_FN_HELP("dumpStack", DumpStack, 1, 0, +"dumpStack()", +" Dump the stack as an array of callees (youngest first)."), + #ifdef TEST_CVTARGS -"cvtargs(arg1..., arg12) Test argument formatter", + JS_FN_HELP("cvtargs", ConvertArgs, 0, 0, +"cvtargs(arg1..., arg12)", +" Test argument formatter."), + #endif -"build() Show build date and time", -"clear([obj]) Clear properties of object", -"intern(str) Internalize str in the atom table", -"clone(fun[, scope]) Clone function object", -"getpda(obj) Get the property descriptors for obj", -"getslx(obj) Get script line extent", -"toint32(n) Testing hook for JS_ValueToInt32", -"evalcx(s[, o])\n" -" Evaluate s in optional sandbox object o\n" + JS_FN_HELP("build", BuildDate, 0, 0, +"build()", +" Show build date and time."), + + JS_FN_HELP("clear", Clear, 0, 0, +"clear([obj])", +" Clear properties of object."), + + JS_FN_HELP("intern", Intern, 1, 0, +"intern(str)", +" Internalize str in the atom table."), + + JS_FN_HELP("clone", Clone, 1, 0, +"clone(fun[, scope])", +" Clone function object."), + + JS_FN_HELP("getpda", GetPDA, 1, 0, +"getpda(obj)", +" Get the property descriptors for obj."), + + JS_FN_HELP("getslx", GetSLX, 1, 0, +"getslx(obj)", +" Get script line extent."), + + JS_FN_HELP("toint32", ToInt32, 1, 0, +"toint32(n)", +" Testing hook for JS_ValueToInt32."), + + JS_FN_HELP("evalcx", EvalInContext, 1, 0, +"evalcx(s[, o])", +" Evaluate s in optional sandbox object o.\n" " if (s == '' && !o) return new o with eager standard classes\n" -" if (s == 'lazy' && !o) return new o with lazy standard classes", -"evalInFrame(n,str,save) Evaluate 'str' in the nth up frame.\n" -" If 'save' (default false), save the frame chain", -"shapeOf(obj) Get the shape of obj (an implementation detail)", -"resolver(src[, proto]) Create object with resolve hook that copies properties\n" -" from src. If proto is omitted, use Object.prototype.", +" if (s == 'lazy' && !o) return new o with lazy standard classes"), + + JS_FN_HELP("evalInFrame", EvalInFrame, 2, 0, +"evalInFrame(n,str,save)", +" Evaluate 'str' in the nth up frame.\n" +" If 'save' (default false), save the frame chain."), + + JS_FN_HELP("shapeOf", ShapeOf, 1, 0, +"shapeOf(obj)", +" Get the shape of obj (an implementation detail)."), + + JS_FN_HELP("resolver", Resolver, 1, 0, +"resolver(src[, proto])", +" Create object with resolve hook that copies properties\n" +" from src. If proto is omitted, use Object.prototype."), + #ifdef DEBUG -"arrayInfo(a1, a2, ...) Report statistics about arrays", + JS_FN_HELP("arrayInfo", js_ArrayInfo, 1, 0, +"arrayInfo(a1, a2, ...)", +" Report statistics about arrays."), + #endif #ifdef JS_THREADSAFE -"sleep(dt) Sleep for dt seconds", + JS_FN_HELP("sleep", Sleep_fn, 1, 0, +"sleep(dt)", +" Sleep for dt seconds."), + #endif -"snarf(filename) Read filename into returned string", -"read(filename) Synonym for snarf", -"compile(code) Compiles a string to bytecode, potentially throwing", -"parse(code) Parses a string, potentially throwing", -"timeout([seconds])\n" + JS_FN_HELP("snarf", Snarf, 0, 0, +"snarf(filename)", +" Read filename into returned string."), + + JS_FN_HELP("read", Snarf, 0, 0, +"read(filename)", +" Synonym for snarf."), + + JS_FN_HELP("compile", Compile, 1, 0, +"compile(code)", +" Compiles a string to bytecode, potentially throwing."), + + JS_FN_HELP("parse", Parse, 1, 0, +"parse(code)", +" Parses a string, potentially throwing."), + + JS_FN_HELP("timeout", Timeout, 1, 0, +"timeout([seconds])", " Get/Set the limit in seconds for the execution time for the current context.\n" -" A negative value (default) means that the execution time is unlimited.", -"elapsed() Execution time elapsed for the current context.", -"parent(obj) Returns the parent of obj.", -"wrap(obj) Wrap an object into a noop wrapper.", -"serialize(sd) Serialize sd using JS_WriteStructuredClone. Returns a TypedArray.", -"deserialize(a) Deserialize data generated by serialize.", -#ifdef JS_METHODJIT -"mjitcodestats() Return stats on mjit code memory usage.", -#endif -"mjitChunkLimit(N) Specify limit on compiled chunk size during mjit compilation.", -"newGlobal(kind) Return a new global object, in the current\n" -" compartment if kind === 'same-compartment' or in a\n" -" new compartment if kind === 'new-compartment'", -"parseLegacyJSON(str) Parse str as legacy JSON, returning the result if the\n" -" parse succeeded and throwing a SyntaxError if not.", -"enableStackWalkingAssertion(enabled)\n" +" A negative value (default) means that the execution time is unlimited."), + + JS_FN_HELP("elapsed", Elapsed, 0, 0, +"elapsed()", +" Execution time elapsed for the current context.."), + + JS_FN_HELP("parent", Parent, 1, 0, +"parent(obj)", +" Returns the parent of obj.."), + + JS_FN_HELP("wrap", Wrap, 1, 0, +"wrap(obj)", +" Wrap an object into a noop wrapper.."), + + JS_FN_HELP("serialize", Serialize, 1, 0, +"serialize(sd)", +" Serialize sd using JS_WriteStructuredClone. Returns a TypedArray.."), + + JS_FN_HELP("deserialize", Deserialize, 1, 0, +"deserialize(a)", +" Deserialize data generated by serialize.."), + + JS_FN_HELP("newGlobal", NewGlobal, 1, 0, +"newGlobal(kind)", +" Return a new global object, in the current\n" +" compartment if kind === 'same-compartment' or in a\n" +" new compartment if kind === 'new-compartment'."), + + JS_FN_HELP("parseLegacyJSON", ParseLegacyJSON, 1, 0, +"parseLegacyJSON(str)", +" Parse str as legacy JSON, returning the result if the\n" +" parse succeeded and throwing a SyntaxError if not."), + + JS_FN_HELP("enableStackWalkingAssertion", EnableStackWalkingAssertion, 1, 0, +"enableStackWalkingAssertion(enabled)", " Enables or disables a particularly expensive assertion in stack-walking\n" " code. If your test isn't ridiculously thorough, such that performing this\n" " assertion increases test duration by an order of magnitude, you shouldn't\n" -" use this.", -"getMaxArgs() Return the maximum number of supported args for a call.", -"terminate() Terminate JavaScript execution, as if we had run out of\n" -" memory or been terminated by the slow script dialog.", +" use this."), -/* Keep these last: see the static assertion below. */ -#ifdef MOZ_PROFILING -"startProfiling([profileName])\n" -" Start a profiling session\n" -" Profiler must be running with programatic sampling", -"stopProfiling([profileName])\n" -" Stop a running profiling session", -"pauseProfilers([profileName])\n" -" Pause a running profiling session", -"resumeProfilers([profileName])\n" -" Resume a paused profiling session", -"dumpProfile([outfile[, profileName]])\n" -" Dump out current profile info (only valid for callgrind)", -# ifdef MOZ_CALLGRIND -"startCallgrind() Start Callgrind instrumentation", -"stopCallgrind() Stop Callgrind instrumentation", -"dumpCallgrind([outfile]) Dump current Callgrind counters to file or stdout", -# endif -# ifdef MOZ_VTUNE -"startVtune() Start Vtune instrumentation", -"stopVtune() Stop Vtune instrumentation", -"pauseVtune() Pause Vtune collection", -"resumeVtune() Resume Vtune collection", -# endif -#endif + JS_FN_HELP("getMaxArgs", GetMaxArgs, 0, 0, +"getMaxArgs()", +" Return the maximum number of supported args for a call."), + + JS_FS_END }; - #ifdef MOZ_PROFILING # define PROFILING_FUNCTION_COUNT 5 # ifdef MOZ_CALLGRIND @@ -4309,99 +3879,71 @@ static const char *const shell_help_messages[] = { # define EXTERNAL_FUNCTION_COUNT 0 #endif -/* Help messages must match shell functions. */ -JS_STATIC_ASSERT(JS_ARRAY_LENGTH(shell_help_messages) - EXTERNAL_FUNCTION_COUNT == - JS_ARRAY_LENGTH(shell_functions) - 1 /* JS_FS_END */); - -#ifdef DEBUG -static void -CheckHelpMessages() -{ - const char *const *m; - const char *lp; - - /* Messages begin with "function_name(" prefix and don't end with \n. */ - for (m = shell_help_messages; m < ArrayEnd(shell_help_messages) - EXTERNAL_FUNCTION_COUNT; ++m) { - lp = strchr(*m, '('); - JS_ASSERT(lp); - JS_ASSERT(memcmp(shell_functions[m - shell_help_messages].name, - *m, lp - *m) == 0); - JS_ASSERT((*m)[strlen(*m) - 1] != '\n'); - } -} -#else -# define CheckHelpMessages() ((void) 0) -#endif - #undef PROFILING_FUNCTION_COUNT #undef CALLGRIND_FUNCTION_COUNT #undef VTUNE_FUNCTION_COUNT #undef EXTERNAL_FUNCTION_COUNT +static bool +PrintHelpString(JSContext *cx, jsval v) +{ + JSString *str = JSVAL_TO_STRING(v); + JS::Anchor a_str(str); + const jschar *chars = JS_GetStringCharsZ(cx, str); + if (!chars) + return false; + + for (const jschar *p = chars; *p; p++) + fprintf(gOutFile, "%c", char(*p)); + + fprintf(gOutFile, "\n"); + + return true; +} + +static bool +PrintHelp(JSContext *cx, JSObject *obj) +{ + jsval usage, help; + if (!JS_LookupProperty(cx, obj, "usage", &usage)) + return false; + if (!JS_LookupProperty(cx, obj, "help", &help)) + return false; + + if (JSVAL_IS_VOID(usage) || JSVAL_IS_VOID(help)) + return true; + + return PrintHelpString(cx, usage) && PrintHelpString(cx, help); +} + static JSBool Help(JSContext *cx, unsigned argc, jsval *vp) { - unsigned i, j; - int did_header, did_something; - JSType type; - JSFunction *fun; - JSString *str; - fprintf(gOutFile, "%s\n", JS_GetImplementationVersion()); + if (argc == 0) { - fputs(shell_help_header, gOutFile); - for (i = 0; i < ArrayLength(shell_help_messages); ++i) - fprintf(gOutFile, "%s\n", shell_help_messages[i]); + JSObject *global = JS_GetGlobalObject(cx); + AutoIdArray ida(cx, JS_Enumerate(cx, global)); + if (!ida) + return false; + + for (size_t i = 0; i < ida.length(); i++) { + jsval v; + if (!JS_LookupPropertyById(cx, global, ida[i], &v)) + return false; + if (JSVAL_IS_OBJECT(v) && !PrintHelp(cx, JSVAL_TO_OBJECT(v))) + return false; + } } else { - did_header = 0; jsval *argv = JS_ARGV(cx, vp); - for (i = 0; i < argc; i++) { - did_something = 0; - type = JS_TypeOfValue(cx, argv[i]); - if (type == JSTYPE_FUNCTION) { - fun = JS_ValueToFunction(cx, argv[i]); - str = fun->atom; - } else if (type == JSTYPE_STRING) { - str = JSVAL_TO_STRING(argv[i]); - } else { - str = NULL; - } - if (str) { - JSAutoByteString funcName(cx, str); - if (!funcName) - return JS_FALSE; - for (j = 0; j < ArrayLength(shell_help_messages); ++j) { - /* Help messages are required to be formatted "functionName(..." */ - const char *msg = shell_help_messages[j]; - const char *p = strchr(msg, '('); - JS_ASSERT(p); - - if (size_t(p - msg) != str->length()) - continue; - - if (strncmp(funcName.ptr(), msg, p - msg) == 0) { - if (!did_header) { - did_header = 1; - fputs(shell_help_header, gOutFile); - } - did_something = 1; - fprintf(gOutFile, "%s\n", shell_help_messages[j]); - break; - } - } - } - if (!did_something) { - str = JS_ValueToString(cx, argv[i]); - if (!str) - return JS_FALSE; - fputs("Sorry, no help for ", gErrFile); - JS_FileEscapedString(gErrFile, str, 0); - putc('\n', gErrFile); - } + for (unsigned i = 0; i < argc; i++) { + if (JSVAL_IS_OBJECT(argv[i]) && !PrintHelp(cx, JSVAL_TO_OBJECT(argv[i]))) + return false; } } + JS_SET_RVAL(cx, vp, JSVAL_VOID); - return JS_TRUE; + return true; } /* @@ -5082,10 +4624,12 @@ NewGlobalObject(JSContext *cx, CompartmentKind compartment) return NULL; if (!JS::RegisterPerfMeasurement(cx, glob)) return NULL; - if (!JS_DefineFunctions(cx, glob, shell_functions) || + if (!JS_DefineFunctionsWithHelp(cx, glob, shell_functions) || !JS_DefineProfilingFunctions(cx, glob)) { return NULL; } + if (!js::DefineTestingFunctions(cx, glob)) + return NULL; JSObject *it = JS_DefineObject(cx, glob, "it", &its_class, NULL, 0); if (!it) @@ -5377,7 +4921,6 @@ main(int argc, char **argv, char **envp) } #endif - CheckHelpMessages(); #ifdef HAVE_SETLOCALE setlocale(LC_ALL, ""); #endif diff --git a/js/src/shell/jsheaptools.cpp b/js/src/shell/jsheaptools.cpp index 03cc6a56c367..b93288ab696c 100644 --- a/js/src/shell/jsheaptools.cpp +++ b/js/src/shell/jsheaptools.cpp @@ -532,7 +532,7 @@ ReferenceFinder::addReferrer(jsval referrer, Path *path) JS_ASSERT(JS_IsArrayObject(context, array)); /* Append our referrer to this array. */ - jsuint length; + uint32_t length; return JS_GetArrayLength(context, array, &length) && JS_SetElement(context, array, length, &referrer); } diff --git a/js/src/tests/ecma_5/Expressions/jstests.list b/js/src/tests/ecma_5/Expressions/jstests.list index a6731f5135e7..155a55f14535 100644 --- a/js/src/tests/ecma_5/Expressions/jstests.list +++ b/js/src/tests/ecma_5/Expressions/jstests.list @@ -4,4 +4,5 @@ script named-accessor-function.js script nested-delete-name-in-evalcode.js script object-literal-accessor-arguments.js script object-literal-accessor-property-name.js +script primitive-this-boxing-behavior.js script string-literal-escape-sequences.js diff --git a/js/src/tests/ecma_5/Expressions/primitive-this-boxing-behavior.js b/js/src/tests/ecma_5/Expressions/primitive-this-boxing-behavior.js new file mode 100644 index 000000000000..36cc0e9e8b42 --- /dev/null +++ b/js/src/tests/ecma_5/Expressions/primitive-this-boxing-behavior.js @@ -0,0 +1,106 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + +//----------------------------------------------------------------------------- +var BUGNUMBER = 732669; +var summary = "Primitive values don't box correctly"; + +print(BUGNUMBER + ": " + summary); + +/************** + * BEGIN TEST * + **************/ + +var t; +function returnThis() { return this; } + +// Boolean + +Boolean.prototype.method = returnThis; +t = true.method(); +assertEq(t !== Boolean.prototype, true); +assertEq(t.toString(), "true"); + +Object.defineProperty(Boolean.prototype, "property", { get: returnThis, configurable: true }); +t = false.property; +assertEq(t !== Boolean.prototype, true); +assertEq(t.toString(), "false"); + +delete Boolean.prototype.method; +delete Boolean.prototype.property; + + +// Number + +Number.prototype.method = returnThis; +t = 5..method(); +assertEq(t !== Number.prototype, true); +assertEq(t.toString(), "5"); + +Object.defineProperty(Number.prototype, "property", { get: returnThis, configurable: true }); +t = 17..property; +assertEq(t !== Number.prototype, true); +assertEq(t.toString(), "17"); + +delete Number.prototype.method; +delete Number.prototype.property; + + +// String + +String.prototype.method = returnThis; +t = "foo".method(); +assertEq(t !== String.prototype, true); +assertEq(t.toString(), "foo"); + +Object.defineProperty(String.prototype, "property", { get: returnThis, configurable: true }); +t = "bar".property; +assertEq(t !== String.prototype, true); +assertEq(t.toString(), "bar"); + +delete String.prototype.method; +delete String.prototype.property; + + +// Object + +Object.prototype.method = returnThis; + +t = true.method(); +assertEq(t !== Object.prototype, true); +assertEq(t !== Boolean.prototype, true); +assertEq(t.toString(), "true"); + +t = 42..method(); +assertEq(t !== Object.prototype, true); +assertEq(t !== Number.prototype, true); +assertEq(t.toString(), "42"); + +t = "foo".method(); +assertEq(t !== Object.prototype, true); +assertEq(t !== String.prototype, true); +assertEq(t.toString(), "foo"); + +Object.defineProperty(Object.prototype, "property", { get: returnThis, configurable: true }); + +t = false.property; +assertEq(t !== Object.prototype, true); +assertEq(t !== Boolean.prototype, true); +assertEq(t.toString(), "false"); + +t = 8675309..property; +assertEq(t !== Object.prototype, true); +assertEq(t !== Number.prototype, true); +assertEq(t.toString(), "8675309"); + +t = "bar".property; +assertEq(t !== Object.prototype, true); +assertEq(t !== String.prototype, true); +assertEq(t.toString(), "bar"); + +/******************************************************************************/ + +if (typeof reportCompare === "function") + reportCompare(true, true); + +print("Tests complete"); diff --git a/js/src/tests/results.py b/js/src/tests/results.py index c1cc8a251b9c..34042f957bdb 100644 --- a/js/src/tests/results.py +++ b/js/src/tests/results.py @@ -1,4 +1,5 @@ import re +from subprocess import list2cmdline class TestOutput: """Output from a test run.""" diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 70338819f2cf..a4e45665cccc 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -2066,14 +2066,22 @@ GetScriptReferent(JSObject *obj) return static_cast(obj->getPrivate()); } +static inline void +SetScriptReferent(JSObject *obj, JSScript *script) +{ + JS_ASSERT(obj->getClass() == &DebuggerScript_class); + obj->setPrivate(script); +} + static void DebuggerScript_trace(JSTracer *trc, JSObject *obj) { if (!trc->runtime->gcCurrentCompartment) { /* This comes from a private pointer, so no barrier needed. */ - if (JSScript *script = GetScriptReferent(obj)) - MarkScriptUnbarriered(trc, script, "Debugger.Script referent"); - + if (JSScript *script = GetScriptReferent(obj)) { + MarkScriptUnbarriered(trc, &script, "Debugger.Script referent"); + SetScriptReferent(obj, script); + } } } @@ -3205,8 +3213,10 @@ DebuggerObject_trace(JSTracer *trc, JSObject *obj) * There is a barrier on private pointers, so the Unbarriered marking * is okay. */ - if (JSObject *referent = (JSObject *) obj->getPrivate()) - MarkObjectUnbarriered(trc, referent, "Debugger.Object referent"); + if (JSObject *referent = (JSObject *) obj->getPrivate()) { + MarkObjectUnbarriered(trc, &referent, "Debugger.Object referent"); + obj->setPrivate(referent); + } } } @@ -3847,8 +3857,10 @@ DebuggerEnv_trace(JSTracer *trc, JSObject *obj) * There is a barrier on private pointers, so the Unbarriered marking * is okay. */ - if (Env *referent = (JSObject *) obj->getPrivate()) - MarkObjectUnbarriered(trc, referent, "Debugger.Environment referent"); + if (Env *referent = (JSObject *) obj->getPrivate()) { + MarkObjectUnbarriered(trc, &referent, "Debugger.Environment referent"); + obj->setPrivate(referent); + } } } diff --git a/js/src/vm/ObjectImpl-inl.h b/js/src/vm/ObjectImpl-inl.h index 20d6b4b77fbc..3d9e91d471a9 100644 --- a/js/src/vm/ObjectImpl-inl.h +++ b/js/src/vm/ObjectImpl-inl.h @@ -88,7 +88,9 @@ js::ObjectImpl::readBarrier(ObjectImpl *obj) JSCompartment *comp = obj->compartment(); if (comp->needsBarrier()) { MOZ_ASSERT(!comp->rt->gcRunning); - MarkObjectUnbarriered(comp->barrierTracer(), obj->asObjectPtr(), "read barrier"); + JSObject *tmp = obj->asObjectPtr(); + MarkObjectUnbarriered(comp->barrierTracer(), &tmp, "read barrier"); + JS_ASSERT(tmp == obj->asObjectPtr()); } #endif } @@ -124,7 +126,9 @@ js::ObjectImpl::writeBarrierPre(ObjectImpl *obj) JSCompartment *comp = obj->compartment(); if (comp->needsBarrier()) { MOZ_ASSERT(!comp->rt->gcRunning); - MarkObjectUnbarriered(comp->barrierTracer(), obj->asObjectPtr(), "write barrier"); + JSObject *tmp = obj->asObjectPtr(); + MarkObjectUnbarriered(comp->barrierTracer(), &tmp, "write barrier"); + JS_ASSERT(tmp == obj->asObjectPtr()); } #endif } diff --git a/js/src/vm/ObjectImpl.cpp b/js/src/vm/ObjectImpl.cpp index 6f723d563ef2..7883c4800f3d 100644 --- a/js/src/vm/ObjectImpl.cpp +++ b/js/src/vm/ObjectImpl.cpp @@ -9,6 +9,7 @@ #include "mozilla/Attributes.h" #include "jsscope.h" +#include "jsobjinlines.h" #include "ObjectImpl.h" @@ -37,3 +38,19 @@ js::ObjectImpl::nativeLookup(JSContext *cx, jsid id) Shape **spp; return Shape::search(cx, lastProperty(), id, &spp); } + +void +js::ObjectImpl::markChildren(JSTracer *trc) +{ + MarkTypeObject(trc, &type_, "type"); + + MarkShape(trc, &shape_, "shape"); + + Class *clasp = shape_->getObjectClass(); + JSObject *obj = asObjectPtr(); + if (clasp->trace) + clasp->trace(trc, obj); + + if (shape_->isNative()) + MarkObjectSlots(trc, obj, 0, obj->slotSpan()); +} diff --git a/js/src/vm/ObjectImpl.h b/js/src/vm/ObjectImpl.h index 14b948b4f394..c1285520d224 100644 --- a/js/src/vm/ObjectImpl.h +++ b/js/src/vm/ObjectImpl.h @@ -279,12 +279,13 @@ class ObjectImpl : public gc::Cell return elements != emptyObjectElements && elements != fixedElements(); } - /* Write barrier support. */ + /* GC support. */ static inline void readBarrier(ObjectImpl *obj); static inline void writeBarrierPre(ObjectImpl *obj); static inline void writeBarrierPost(ObjectImpl *obj, void *addr); inline void privateWriteBarrierPre(void **oldval); inline void privateWriteBarrierPost(void **oldval); + void markChildren(JSTracer *trc); /* JIT Accessors */ static size_t offsetOfShape() { return offsetof(ObjectImpl, shape_); } diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 0f9bdf0a9271..01763085281d 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -43,10 +43,10 @@ #include "vm/MatchPairs.h" #include "jsobjinlines.h" -#include "jsstrinlines.h" #include "vm/RegExpObject-inl.h" #include "vm/RegExpStatics-inl.h" +#include "vm/StringBuffer-inl.h" using namespace js; using js::detail::RegExpCode; diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index d22f88d420a1..17f1308a3300 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -261,17 +261,18 @@ StackFrame::mark(JSTracer *trc) * this path. However, generators use a special write barrier when the stack * frame is copied to the floating frame. Therefore, no barrier is needed. */ - gc::MarkObjectUnbarriered(trc, &scopeChain(), "scope chain"); + if (flags_ & HAS_SCOPECHAIN) + gc::MarkObjectUnbarriered(trc, &scopeChain_, "scope chain"); if (isDummyFrame()) return; if (hasArgsObj()) - gc::MarkObjectUnbarriered(trc, &argsObj(), "arguments"); + gc::MarkObjectUnbarriered(trc, &argsObj_, "arguments"); if (isFunctionFrame()) { - gc::MarkObjectUnbarriered(trc, fun(), "fun"); + gc::MarkObjectUnbarriered(trc, &exec.fun, "fun"); if (isEvalFrame()) - gc::MarkScriptUnbarriered(trc, script(), "eval script"); + gc::MarkScriptUnbarriered(trc, &u.evalScript, "eval script"); } else { - gc::MarkScriptUnbarriered(trc, script(), "script"); + gc::MarkScriptUnbarriered(trc, &exec.script, "script"); } if (IS_GC_MARKING_TRACER(trc)) script()->compartment()->active = true; diff --git a/js/src/vm/String-inl.h b/js/src/vm/String-inl.h index 2fdea5081dd0..f0d0152e6510 100644 --- a/js/src/vm/String-inl.h +++ b/js/src/vm/String-inl.h @@ -41,10 +41,11 @@ #ifndef String_inl_h__ #define String_inl_h__ -#include "String.h" - #include "jscntxt.h" #include "jsgcmark.h" +#include "jsprobes.h" + +#include "String.h" #include "jsgcinlines.h" @@ -56,8 +57,11 @@ JSString::writeBarrierPre(JSString *str) return; JSCompartment *comp = str->compartment(); - if (comp->needsBarrier()) - MarkStringUnbarriered(comp->barrierTracer(), str, "write barrier"); + if (comp->needsBarrier()) { + JSString *tmp = str; + MarkStringUnbarriered(comp->barrierTracer(), &tmp, "write barrier"); + JS_ASSERT(tmp == str); + } #endif } @@ -81,8 +85,11 @@ JSString::readBarrier(JSString *str) { #ifdef JSGC_INCREMENTAL JSCompartment *comp = str->compartment(); - if (comp->needsBarrier()) - MarkStringUnbarriered(comp->barrierTracer(), str, "read barrier"); + if (comp->needsBarrier()) { + JSString *tmp = str; + MarkStringUnbarriered(comp->barrierTracer(), &tmp, "read barrier"); + JS_ASSERT(tmp == str); + } #endif } @@ -119,6 +126,13 @@ JSRope::new_(JSContext *cx, JSString *left, JSString *right, size_t length) return str; } +inline void +JSRope::markChildren(JSTracer *trc) +{ + js::gc::MarkStringUnbarriered(trc, &d.u1.left, "left child"); + js::gc::MarkStringUnbarriered(trc, &d.s.u2.right, "right child"); +} + JS_ALWAYS_INLINE void JSDependentString::init(JSLinearString *base, const jschar *chars, size_t length) { @@ -146,6 +160,12 @@ JSDependentString::new_(JSContext *cx, JSLinearString *base, const jschar *chars return str; } +inline void +JSDependentString::markChildren(JSTracer *trc) +{ + js::gc::MarkStringUnbarriered(trc, &d.s.u2.base, "base"); +} + inline js::PropertyName * JSFlatString::toPropertyName(JSContext *cx) { @@ -356,7 +376,7 @@ js::StaticStrings::lookup(const jschar *chars, size_t length) (chars[1] - '0') * 10 + (chars[2] - '0'); - if (jsuint(i) < INT_STATIC_LIMIT) + if (unsigned(i) < INT_STATIC_LIMIT) return getInt(i); } return NULL; @@ -419,4 +439,29 @@ JSExternalString::finalize() fin->finalize(fin, const_cast(chars())); } +namespace js { + +static JS_ALWAYS_INLINE JSFixedString * +NewShortString(JSContext *cx, const jschar *chars, size_t length) +{ + /* + * Don't bother trying to find a static atom; measurement shows that not + * many get here (for one, Atomize is catching them). + */ + JS_ASSERT(JSShortString::lengthFits(length)); + JSInlineString *str = JSInlineString::lengthFits(length) + ? JSInlineString::new_(cx) + : JSShortString::new_(cx); + if (!str) + return NULL; + + jschar *storage = str->init(length); + PodCopy(storage, chars, length); + storage[length] = 0; + Probes::createString(cx, str, length); + return str; +} + +} /* namespace js */ + #endif diff --git a/js/src/vm/String.cpp b/js/src/vm/String.cpp index 3d60f495d1b0..9711d501a48e 100644 --- a/js/src/vm/String.cpp +++ b/js/src/vm/String.cpp @@ -500,19 +500,19 @@ StaticStrings::trace(JSTracer *trc) /* These strings never change, so barriers are not needed. */ for (uint32_t i = 0; i < UNIT_STATIC_LIMIT; i++) { - if (JSAtom *atom = unitStaticTable[i]) - MarkStringUnbarriered(trc, atom, "unit-static-string"); + if (unitStaticTable[i]) + MarkStringUnbarriered(trc, &unitStaticTable[i], "unit-static-string"); } for (uint32_t i = 0; i < NUM_SMALL_CHARS * NUM_SMALL_CHARS; i++) { - if (JSAtom *atom = length2StaticTable[i]) - MarkStringUnbarriered(trc, atom, "length2-static-string"); + if (length2StaticTable[i]) + MarkStringUnbarriered(trc, &length2StaticTable[i], "length2-static-string"); } /* This may mark some strings more than once, but so be it. */ for (uint32_t i = 0; i < INT_STATIC_LIMIT; i++) { - if (JSAtom *atom = intStaticTable[i]) - MarkStringUnbarriered(trc, atom, "int-static-string"); + if (intStaticTable[i]) + MarkStringUnbarriered(trc, &intStaticTable[i], "int-static-string"); } } @@ -533,7 +533,7 @@ StaticStrings::isStatic(JSAtom *atom) (chars[1] - '0') * 10 + (chars[2] - '0'); - return (jsuint(i) < INT_STATIC_LIMIT); + return (unsigned(i) < INT_STATIC_LIMIT); } return false; default: diff --git a/js/src/vm/String.h b/js/src/vm/String.h index 6480e16205b4..7f7aa157f7f5 100644 --- a/js/src/vm/String.h +++ b/js/src/vm/String.h @@ -444,6 +444,8 @@ class JSRope : public JSString JS_ASSERT(isRope()); return d.s.u2.right; } + + inline void markChildren(JSTracer *trc); }; JS_STATIC_ASSERT(sizeof(JSRope) == sizeof(JSString)); @@ -486,6 +488,8 @@ class JSDependentString : public JSLinearString JS_ASSERT(JSString::isDependent()); return d.s.u2.base; } + + inline void markChildren(JSTracer *trc); }; JS_STATIC_ASSERT(sizeof(JSDependentString) == sizeof(JSString)); diff --git a/js/src/vm/StringBuffer-inl.h b/js/src/vm/StringBuffer-inl.h new file mode 100644 index 000000000000..b8b79fe372c6 --- /dev/null +++ b/js/src/vm/StringBuffer-inl.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef StringBuffer_inl_h___ +#define StringBuffer_inl_h___ + +#include "vm/StringBuffer.h" + +#include "vm/String-inl.h" + +namespace js { + +inline bool +StringBuffer::checkLength(size_t length) +{ + return JSString::validateLength(context(), length); +} + +inline bool +StringBuffer::reserve(size_t len) +{ + if (!checkLength(len)) + return false; + return cb.reserve(len); +} + +inline bool +StringBuffer::resize(size_t len) +{ + if (!checkLength(len)) + return false; + return cb.resize(len); +} + +inline bool +StringBuffer::append(const jschar c) +{ + if (!checkLength(cb.length() + 1)) + return false; + return cb.append(c); +} + +inline bool +StringBuffer::append(const jschar *chars, size_t len) +{ + if (!checkLength(cb.length() + len)) + return false; + return cb.append(chars, len); +} + +inline bool +StringBuffer::append(const jschar *begin, const jschar *end) +{ + if (!checkLength(cb.length() + (end - begin))) + return false; + return cb.append(begin, end); +} + +inline bool +StringBuffer::appendN(const jschar c, size_t n) +{ + if (!checkLength(cb.length() + n)) + return false; + return cb.appendN(c, n); +} + +extern bool +ValueToStringBufferSlow(JSContext *cx, const Value &v, StringBuffer &sb); + +inline bool +ValueToStringBuffer(JSContext *cx, const Value &v, StringBuffer &sb) +{ + if (v.isString()) + return sb.append(v.toString()); + + return ValueToStringBufferSlow(cx, v, sb); +} + +} /* namespace js */ + +#endif /* StringBuffer_inl_h__ */ diff --git a/js/src/vm/StringBuffer.cpp b/js/src/vm/StringBuffer.cpp new file mode 100644 index 000000000000..dab55c083a7c --- /dev/null +++ b/js/src/vm/StringBuffer.cpp @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "vm/StringBuffer.h" + +#include "vm/String-inl.h" +#include "vm/StringBuffer-inl.h" + +using namespace js; + +jschar * +StringBuffer::extractWellSized() +{ + size_t capacity = cb.capacity(); + size_t length = cb.length(); + + jschar *buf = cb.extractRawBuffer(); + if (!buf) + return NULL; + + /* For medium/big buffers, avoid wasting more than 1/4 of the memory. */ + JS_ASSERT(capacity >= length); + if (length > CharBuffer::sMaxInlineStorage && capacity - length > length / 4) { + size_t bytes = sizeof(jschar) * (length + 1); + JSContext *cx = context(); + jschar *tmp = (jschar *)cx->realloc_(buf, bytes); + if (!tmp) { + cx->free_(buf); + return NULL; + } + buf = tmp; + } + + return buf; +} + +JSFixedString * +StringBuffer::finishString() +{ + JSContext *cx = context(); + if (cb.empty()) + return cx->runtime->atomState.emptyAtom; + + size_t length = cb.length(); + if (!checkLength(length)) + return NULL; + + JS_STATIC_ASSERT(JSShortString::MAX_SHORT_LENGTH < CharBuffer::InlineLength); + if (JSShortString::lengthFits(length)) + return NewShortString(cx, cb.begin(), length); + + if (!cb.append('\0')) + return NULL; + + jschar *buf = extractWellSized(); + if (!buf) + return NULL; + + JSFixedString *str = js_NewString(cx, buf, length); + if (!str) + cx->free_(buf); + return str; +} + +JSAtom * +StringBuffer::finishAtom() +{ + JSContext *cx = context(); + + size_t length = cb.length(); + if (length == 0) + return cx->runtime->atomState.emptyAtom; + + JSAtom *atom = js_AtomizeChars(cx, cb.begin(), length); + cb.clear(); + return atom; +} diff --git a/js/src/vm/StringBuffer.h b/js/src/vm/StringBuffer.h new file mode 100644 index 000000000000..6e8f96c3ab6f --- /dev/null +++ b/js/src/vm/StringBuffer.h @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef StringBuffer_h___ +#define StringBuffer_h___ + +#include "mozilla/Attributes.h" + +#include "jscntxt.h" +#include "jspubtd.h" + +#include "js/Vector.h" + +namespace js { + +/* + * String builder that eagerly checks for over-allocation past the maximum + * string length. + * + * Any operation which would exceed the maximum string length causes an + * exception report on the context and results in a failed return value. + * + * Well-sized extractions (which waste no more than 1/4 of their char + * buffer space) are guaranteed for strings built by this interface. + * See |extractWellSized|. + * + * Note: over-allocation is not checked for when using the infallible + * |replaceRawBuffer|, so the implementation of |finishString| also must check + * for over-allocation. + */ +class StringBuffer +{ + /* cb's buffer is taken by the new string so use ContextAllocPolicy. */ + typedef Vector CharBuffer; + + CharBuffer cb; + + static inline bool checkLength(JSContext *cx, size_t length); + inline bool checkLength(size_t length); + JSContext *context() const { return cb.allocPolicy().context(); } + jschar *extractWellSized(); + + StringBuffer(const StringBuffer &other) MOZ_DELETE; + void operator=(const StringBuffer &other) MOZ_DELETE; + + public: + explicit inline StringBuffer(JSContext *cx); + inline bool reserve(size_t len); + inline bool resize(size_t len); + inline bool append(const jschar c); + inline bool append(const jschar *chars, size_t len); + inline bool append(const jschar *begin, const jschar *end); + inline bool append(JSString *str); + inline bool append(JSLinearString *str); + inline bool appendN(const jschar c, size_t n); + inline bool appendInflated(const char *cstr, size_t len); + + /* Infallible variants usable when the corresponding space is reserved. */ + void infallibleAppend(const jschar c) { + cb.infallibleAppend(c); + } + void infallibleAppend(const jschar *chars, size_t len) { + cb.infallibleAppend(chars, len); + } + void infallibleAppend(const jschar *begin, const jschar *end) { + cb.infallibleAppend(begin, end); + } + void infallibleAppendN(const jschar c, size_t n) { + cb.infallibleAppendN(c, n); + } + + JSAtom *atomize(unsigned flags = 0); + static JSAtom *atomize(JSContext *cx, const CharBuffer &cb, unsigned flags = 0); + static JSAtom *atomize(JSContext *cx, const jschar *begin, size_t length, unsigned flags = 0); + + void replaceRawBuffer(jschar *chars, size_t len) { cb.replaceRawBuffer(chars, len); } + jschar *begin() { return cb.begin(); } + jschar *end() { return cb.end(); } + const jschar *begin() const { return cb.begin(); } + const jschar *end() const { return cb.end(); } + bool empty() const { return cb.empty(); } + inline size_t length() const; + + /* + * Creates a string from the characters in this buffer, then (regardless + * whether string creation succeeded or failed) empties the buffer. + */ + JSFixedString *finishString(); + + /* Identical to finishString() except that an atom is created. */ + JSAtom *finishAtom(); + + template + bool append(const char (&array)[ArrayLength]) { + return cb.append(array, array + ArrayLength - 1); /* No trailing '\0'. */ + } +}; + +inline +StringBuffer::StringBuffer(JSContext *cx) + : cb(cx) +{} + +inline bool +StringBuffer::append(JSString *str) +{ + JSLinearString *linear = str->ensureLinear(context()); + if (!linear) + return false; + return append(linear); +} + +inline bool +StringBuffer::append(JSLinearString *str) +{ + JS::Anchor anch(str); + return cb.append(str->chars(), str->length()); +} + +inline size_t +StringBuffer::length() const +{ + JS_ASSERT(cb.length() <= JSString::MAX_LENGTH); + return cb.length(); +} + +inline bool +StringBuffer::appendInflated(const char *cstr, size_t cstrlen) +{ + size_t lengthBefore = length(); + if (!cb.growByUninitialized(cstrlen)) + return false; + DebugOnly oldcstrlen = cstrlen; + DebugOnly ok = InflateStringToBuffer(context(), cstr, cstrlen, + begin() + lengthBefore, &cstrlen); + JS_ASSERT(ok && oldcstrlen == cstrlen); + return true; +} + +} /* namespace js */ + +#endif /* StringBuffer_h___ */ diff --git a/js/xpconnect/idl/nsIXPCScriptable.idl b/js/xpconnect/idl/nsIXPCScriptable.idl index 367f60506187..eac8f4e9ffbd 100644 --- a/js/xpconnect/idl/nsIXPCScriptable.idl +++ b/js/xpconnect/idl/nsIXPCScriptable.idl @@ -92,6 +92,7 @@ interface nsIXPCScriptable : nsISupports const PRUint32 WANT_CALL = 1 << 13; const PRUint32 WANT_CONSTRUCT = 1 << 14; const PRUint32 WANT_HASINSTANCE = 1 << 15; + // Unused bit here! const PRUint32 USE_JSSTUB_FOR_ADDPROPERTY = 1 << 17; const PRUint32 USE_JSSTUB_FOR_DELPROPERTY = 1 << 18; const PRUint32 USE_JSSTUB_FOR_SETPROPERTY = 1 << 19; @@ -101,6 +102,7 @@ interface nsIXPCScriptable : nsISupports const PRUint32 CLASSINFO_INTERFACES_ONLY = 1 << 23; const PRUint32 ALLOW_PROP_MODS_DURING_RESOLVE = 1 << 24; const PRUint32 ALLOW_PROP_MODS_TO_PROTOTYPE = 1 << 25; + const PRUint32 IS_GLOBAL_OBJECT = 1 << 26; const PRUint32 DONT_REFLECT_INTERFACE_NAMES = 1 << 27; const PRUint32 WANT_EQUALITY = 1 << 28; const PRUint32 WANT_OUTER_OBJECT = 1 << 29; diff --git a/js/xpconnect/idl/nsIXPConnect.idl b/js/xpconnect/idl/nsIXPConnect.idl index 686f5517a3d9..598b02bfc52d 100644 --- a/js/xpconnect/idl/nsIXPConnect.idl +++ b/js/xpconnect/idl/nsIXPConnect.idl @@ -180,7 +180,14 @@ interface nsIXPConnectWrappedNative : nsIXPConnectJSObjectHolder void debugDump(in short depth); - void refreshPrototype(); + /* + * This finishes initializing a wrapped global, doing the parts that we + * couldn't do while the global and window were being simultaneously + * bootstrapped. This should be called exactly once, and only for wrapped + * globals. + */ + void finishInitForWrappedGlobal(); + /* * This returns a pointer into the instance and care should be taken * to make sure the pointer is not kept past the life time of the @@ -398,7 +405,7 @@ enum nsGCType { }; %} -[uuid(e92bf5e0-494c-11e1-b86c-0800200c9a66)] +[uuid(0213cb40-2dd5-4ac8-a9d3-157bd53c3824)] interface nsIXPConnect : nsISupports { %{ C++ @@ -421,12 +428,8 @@ interface nsIXPConnect : nsISupports * * @param aJSContext the context to use while creating the global object. * @param aCOMObj the native object that represents the global object. - * @param aIID the IID used to wrap the global object. * @param aPrincipal the principal of the code that will run in this * compartment. Can be null if not on the main thread. - * @param aExtraPtr must be passed if aPrincipal is null. Used to separate - * code from the same principal into different - * compartments, as for sandboxes. * @param aFlags one of the flags below specifying what options this * global object wants. */ @@ -434,9 +437,7 @@ interface nsIXPConnect : nsISupports initClassesWithNewWrappedGlobal( in JSContextPtr aJSContext, in nsISupports aCOMObj, - in nsIIDRef aIID, in nsIPrincipal aPrincipal, - in nsISupports aExtraPtr, in PRUint32 aFlags); const PRUint32 INIT_JS_STANDARD_CLASSES = 1 << 0; diff --git a/js/xpconnect/idl/xpccomponents.idl b/js/xpconnect/idl/xpccomponents.idl index 9610a89245ff..7602a44d3256 100644 --- a/js/xpconnect/idl/xpccomponents.idl +++ b/js/xpconnect/idl/xpccomponents.idl @@ -152,7 +152,7 @@ interface ScheduledGCCallback : nsISupports /** * interface of Components.utils */ -[scriptable, uuid(9e9ba9d6-a0fa-4767-9f49-9b74bb2368cd)] +[scriptable, uuid(9e43a260-5db2-11e1-b86c-0800200c9a66)] interface nsIXPCComponents_Utils : nsISupports { @@ -301,6 +301,9 @@ interface nsIXPCComponents_Utils : nsISupports [implicit_jscontext] jsval nondeterministicGetWeakMapKeys(in jsval aMap); + [implicit_jscontext] + jsval getJSTestingFunctions(); + /* * To be called from JS only. * diff --git a/js/xpconnect/loader/mozJSComponentLoader.cpp b/js/xpconnect/loader/mozJSComponentLoader.cpp index 11e6bc4d50c0..85eb682d27a0 100644 --- a/js/xpconnect/loader/mozJSComponentLoader.cpp +++ b/js/xpconnect/loader/mozJSComponentLoader.cpp @@ -694,9 +694,7 @@ mozJSComponentLoader::GlobalForLocation(nsILocalFile *aComponentFile, nsCOMPtr holder; rv = xpc->InitClassesWithNewWrappedGlobal(cx, backstagePass, - NS_GET_IID(nsISupports), mSystemPrincipal, - nsnull, nsIXPConnect:: FLAG_SYSTEM_GLOBAL_OBJECT, getter_AddRefs(holder)); @@ -1216,7 +1214,7 @@ mozJSComponentLoader::ImportInto(const nsACString & aLocation, // Iterate over symbols array, installing symbols on targetObj: - jsuint symbolCount = 0; + uint32_t symbolCount = 0; if (!JS_GetArrayLength(mContext, symbolsObj, &symbolCount)) { return ReportOnCaller(cxhelper, ERROR_GETTING_ARRAY_LENGTH, PromiseFlatCString(aLocation).get()); @@ -1226,7 +1224,7 @@ mozJSComponentLoader::ImportInto(const nsACString & aLocation, nsCAutoString logBuffer; #endif - for (jsuint i = 0; i < symbolCount; ++i) { + for (uint32_t i = 0; i < symbolCount; ++i) { jsval val; jsid symbolId; diff --git a/js/xpconnect/shell/xpcshell.cpp b/js/xpconnect/shell/xpcshell.cpp index 0e8f98bccad4..82a198cf5b94 100644 --- a/js/xpconnect/shell/xpcshell.cpp +++ b/js/xpconnect/shell/xpcshell.cpp @@ -1948,9 +1948,7 @@ main(int argc, char **argv, char **envp) nsCOMPtr holder; rv = xpc->InitClassesWithNewWrappedGlobal(cx, backstagePass, - NS_GET_IID(nsISupports), systemprincipal, - nsnull, nsIXPConnect:: FLAG_SYSTEM_GLOBAL_OBJECT, getter_AddRefs(holder)); diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp index c3e884934c59..66c285f35cb2 100644 --- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -2799,8 +2799,8 @@ SandboxDump(JSContext *cx, unsigned argc, jsval *vp) } #endif - fputs(cstr, stderr); - fflush(stderr); + fputs(cstr, stdout); + fflush(stdout); NS_Free(cstr); JS_SET_RVAL(cx, vp, JSVAL_TRUE); return true; @@ -3704,6 +3704,18 @@ nsXPCComponents_Utils::NondeterministicGetWeakMapKeys(const jsval &aMap, return NS_OK; } +/* void getDebugObject(); */ +NS_IMETHODIMP +nsXPCComponents_Utils::GetJSTestingFunctions(JSContext *cx, + JS::Value *retval) +{ + JSObject *obj = js::GetTestingFunctions(cx); + if (!obj) + return NS_ERROR_XPC_JAVASCRIPT_ERROR; + *retval = OBJECT_TO_JSVAL(obj); + return NS_OK; +} + /* void getGlobalForObject(); */ NS_IMETHODIMP nsXPCComponents_Utils::GetGlobalForObject(const JS::Value& object, @@ -4223,8 +4235,7 @@ nsXPCComponents::AttachNewComponentsObject(XPCCallContext& ccx, nsCOMPtr wrapper; xpcObjectHelper helper(cholder); - XPCWrappedNative::GetNewOrUsed(ccx, helper, aScope, iface, - OBJ_IS_NOT_GLOBAL, getter_AddRefs(wrapper)); + XPCWrappedNative::GetNewOrUsed(ccx, helper, aScope, iface, getter_AddRefs(wrapper)); if (!wrapper) return false; diff --git a/js/xpconnect/src/XPCConvert.cpp b/js/xpconnect/src/XPCConvert.cpp index f515e5843034..f3db1b0a2360 100644 --- a/js/xpconnect/src/XPCConvert.cpp +++ b/js/xpconnect/src/XPCConvert.cpp @@ -348,16 +348,9 @@ XPCConvert::NativeData2JS(XPCLazyCallContext& lccx, jsval* d, const void* s, pErr, d); } // else... - - // XXX The OBJ_IS_NOT_GLOBAL here is not really right. In - // fact, this code is depending on the fact that the - // global object will not have been collected, and - // therefore this NativeInterface2JSObject will not end up - // creating a new XPCNativeScriptableShared. xpcObjectHelper helper(iface); if (!NativeInterface2JSObject(lccx, d, nsnull, helper, iid, - nsnull, true, - OBJ_IS_NOT_GLOBAL, pErr)) + nsnull, true, pErr)) return false; #ifdef DEBUG @@ -876,7 +869,6 @@ XPCConvert::NativeInterface2JSObject(XPCLazyCallContext& lccx, const nsID* iid, XPCNativeInterface** Interface, bool allowNativeWrapper, - bool isGlobal, nsresult* pErr) { NS_ASSERTION(!Interface || iid, @@ -1016,7 +1008,6 @@ XPCConvert::NativeInterface2JSObject(XPCLazyCallContext& lccx, return false; rv = XPCWrappedNative::GetNewOrUsed(ccx, aHelper, xpcscope, iface, - isGlobal, getter_AddRefs(strongWrapper)); wrapper = strongWrapper; diff --git a/js/xpconnect/src/XPCInlines.h b/js/xpconnect/src/XPCInlines.h index 798ece56781d..1dcb18b5ffa4 100644 --- a/js/xpconnect/src/XPCInlines.h +++ b/js/xpconnect/src/XPCInlines.h @@ -658,14 +658,11 @@ inline JSObject* xpc_NewSystemInheritingJSObject(JSContext *cx, JSClass *clasp, JSObject *proto, bool uniqueType, JSObject *parent) { + // Global creation should go through XPCWrappedNative::WrapNewGlobal(). + MOZ_ASSERT(!(clasp->flags & JSCLASS_IS_GLOBAL)); + JSObject *obj; - if (clasp->flags & JSCLASS_IS_GLOBAL) { - obj = JS_NewGlobalObject(cx, clasp); - if (obj && proto) { - if (!JS_SplicePrototype(cx, obj, proto)) - obj = NULL; - } - } else if (uniqueType) { + if (uniqueType) { obj = JS_NewObjectWithUniqueType(cx, clasp, proto, parent); } else { obj = JS_NewObject(cx, clasp, proto, parent); diff --git a/js/xpconnect/src/XPCJSID.cpp b/js/xpconnect/src/XPCJSID.cpp index fba9f6a69d81..f2ad4b25e53b 100644 --- a/js/xpconnect/src/XPCJSID.cpp +++ b/js/xpconnect/src/XPCJSID.cpp @@ -260,10 +260,22 @@ NS_IMPL_THREADSAFE_RELEASE(SharedScriptableHelperForJSIID) #include "xpc_map_end.h" /* This will #undef the above */ static nsIXPCScriptable* gSharedScriptableHelperForJSIID; +static bool gClassObjectsWereInited = false; + +static void EnsureClassObjectsInitialized() +{ + if (!gClassObjectsWereInited) { + gSharedScriptableHelperForJSIID = new SharedScriptableHelperForJSIID(); + NS_ADDREF(gSharedScriptableHelperForJSIID); + + gClassObjectsWereInited = true; + } +} NS_METHOD GetSharedScriptableHelperForJSIID(PRUint32 language, nsISupports **helper) { + EnsureClassObjectsInitialized(); if (language == nsIProgrammingLanguage::JAVASCRIPT) { NS_IF_ADDREF(gSharedScriptableHelperForJSIID); *helper = gSharedScriptableHelperForJSIID; @@ -274,8 +286,6 @@ NS_METHOD GetSharedScriptableHelperForJSIID(PRUint32 language, /******************************************************/ -static JSBool gClassObjectsWereInited = false; - #define NULL_CID \ { 0x00000000, 0x0000, 0x0000, \ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } @@ -287,22 +297,15 @@ NS_IMPL_CLASSINFO(nsJSIID, GetSharedScriptableHelperForJSIID, NS_DECL_CI_INTERFACE_GETTER(nsJSCID) NS_IMPL_CLASSINFO(nsJSCID, NULL, nsIClassInfo::THREADSAFE, NULL_CID) -void xpc_InitJSxIDClassObjects() -{ - if (!gClassObjectsWereInited) { - gSharedScriptableHelperForJSIID = new SharedScriptableHelperForJSIID(); - NS_ADDREF(gSharedScriptableHelperForJSIID); - } - gClassObjectsWereInited = true; -} - void xpc_DestroyJSxIDClassObjects() { - NS_IF_RELEASE(NS_CLASSINFO_NAME(nsJSIID)); - NS_IF_RELEASE(NS_CLASSINFO_NAME(nsJSCID)); - NS_IF_RELEASE(gSharedScriptableHelperForJSIID); + if (gClassObjectsWereInited) { + NS_IF_RELEASE(NS_CLASSINFO_NAME(nsJSIID)); + NS_IF_RELEASE(NS_CLASSINFO_NAME(nsJSCID)); + NS_IF_RELEASE(gSharedScriptableHelperForJSIID); - gClassObjectsWereInited = false; + gClassObjectsWereInited = false; + } } /***************************************************************************/ diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index fc711aa66297..4bb7f9dece0b 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -1684,7 +1684,7 @@ ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats &rtStats, const nsACStri callback, closure); ReportMemoryBytes(pathPrefix + NS_LITERAL_CSTRING("runtime/gc-marker"), - nsIMemoryReporter::KIND_NONHEAP, rtStats.runtimeGCMarker, + nsIMemoryReporter::KIND_HEAP, rtStats.runtimeGCMarker, "Memory used for the GC mark stack and gray roots.", callback, closure); @@ -2061,7 +2061,12 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect) // to cause period, and we hope hygienic, last-ditch GCs from within // the GC's allocator. JS_SetGCParameter(mJSRuntime, JSGC_MAX_BYTES, 0xffffffff); +#ifdef MOZ_ASAN + // ASan requires more stack space due to redzones + JS_SetNativeStackQuota(mJSRuntime, 2 * 128 * sizeof(size_t) * 1024); +#else JS_SetNativeStackQuota(mJSRuntime, 128 * sizeof(size_t) * 1024); +#endif JS_SetContextCallback(mJSRuntime, ContextCallback); JS_SetCompartmentCallback(mJSRuntime, CompartmentCallback); JS_SetGCCallback(mJSRuntime, GCCallback); diff --git a/js/xpconnect/src/XPCMaps.cpp b/js/xpconnect/src/XPCMaps.cpp index 9b52f4109315..829234f9c93c 100644 --- a/js/xpconnect/src/XPCMaps.cpp +++ b/js/xpconnect/src/XPCMaps.cpp @@ -622,7 +622,6 @@ XPCNativeScriptableSharedMap::~XPCNativeScriptableSharedMap() JSBool XPCNativeScriptableSharedMap::GetNewOrUsed(uint32_t flags, char* name, - bool isGlobal, PRUint32 interfacesBitmap, XPCNativeScriptableInfo* si) { @@ -643,7 +642,7 @@ XPCNativeScriptableSharedMap::GetNewOrUsed(uint32_t flags, interfacesBitmap); if (!shared) return false; - shared->PopulateJSClass(isGlobal); + shared->PopulateJSClass(); } si->SetScriptableShared(shared); return true; diff --git a/js/xpconnect/src/XPCMaps.h b/js/xpconnect/src/XPCMaps.h index cad11e14bc36..56bfbcf62039 100644 --- a/js/xpconnect/src/XPCMaps.h +++ b/js/xpconnect/src/XPCMaps.h @@ -580,8 +580,8 @@ public: static XPCNativeScriptableSharedMap* newMap(int size); - JSBool GetNewOrUsed(uint32_t flags, char* name, bool isGlobal, - PRUint32 interfacesBitmap, XPCNativeScriptableInfo* si); + JSBool GetNewOrUsed(uint32_t flags, char* name, PRUint32 interfacesBitmap, + XPCNativeScriptableInfo* si); inline uint32_t Count() {return mTable->entryCount;} inline uint32_t Enumerate(JSDHashEnumerator f, void *arg) diff --git a/js/xpconnect/src/XPCQuickStubs.cpp b/js/xpconnect/src/XPCQuickStubs.cpp index 2e567810c864..be3696bf32f1 100644 --- a/js/xpconnect/src/XPCQuickStubs.cpp +++ b/js/xpconnect/src/XPCQuickStubs.cpp @@ -1111,16 +1111,10 @@ xpc_qsXPCOMObjectToJsval(XPCLazyCallContext &lccx, qsObjectHelper &aHelper, JSContext *cx = lccx.GetJSContext(); - // XXX The OBJ_IS_NOT_GLOBAL here is not really right. In - // fact, this code is depending on the fact that the - // global object will not have been collected, and - // therefore this NativeInterface2JSObject will not end up - // creating a new XPCNativeScriptableShared. - nsresult rv; if (!XPCConvert::NativeInterface2JSObject(lccx, rval, nsnull, aHelper, iid, iface, - true, OBJ_IS_NOT_GLOBAL, &rv)) { + true, &rv)) { // I can't tell if NativeInterface2JSObject throws JS exceptions // or not. This is a sloppy stab at the right semantics; the // method really ought to be fixed to behave consistently. diff --git a/js/xpconnect/src/XPCRuntimeService.cpp b/js/xpconnect/src/XPCRuntimeService.cpp index 33b5572ec967..0ff7dc4ff78a 100644 --- a/js/xpconnect/src/XPCRuntimeService.cpp +++ b/js/xpconnect/src/XPCRuntimeService.cpp @@ -63,6 +63,7 @@ NS_IMPL_THREADSAFE_RELEASE(BackstagePass) nsIXPCScriptable::USE_JSSTUB_FOR_SETPROPERTY | \ nsIXPCScriptable::DONT_ENUM_STATIC_PROPS | \ nsIXPCScriptable::DONT_ENUM_QUERY_INTERFACE | \ + nsIXPCScriptable::IS_GLOBAL_OBJECT | \ nsIXPCScriptable::DONT_REFLECT_INTERFACE_NAMES #include "xpc_map_end.h" /* This will #undef the above */ diff --git a/js/xpconnect/src/XPCVariant.cpp b/js/xpconnect/src/XPCVariant.cpp index a7d1d31a5a60..bacd8429bcf7 100644 --- a/js/xpconnect/src/XPCVariant.cpp +++ b/js/xpconnect/src/XPCVariant.cpp @@ -190,7 +190,7 @@ private: public: static JSBool GetTypeForArray(XPCCallContext& ccx, JSObject* array, - jsuint length, + uint32_t length, nsXPTType* resultType, nsID* resultID); }; @@ -215,7 +215,7 @@ XPCArrayHomogenizer::StateTable[tTypeCount][tTypeCount-1] = { // static JSBool XPCArrayHomogenizer::GetTypeForArray(XPCCallContext& ccx, JSObject* array, - jsuint length, + uint32_t length, nsXPTType* resultType, nsID* resultID) { Type state = tUnk; diff --git a/js/xpconnect/src/XPCWrappedJSClass.cpp b/js/xpconnect/src/XPCWrappedJSClass.cpp index e01cc2a36321..271973660903 100644 --- a/js/xpconnect/src/XPCWrappedJSClass.cpp +++ b/js/xpconnect/src/XPCWrappedJSClass.cpp @@ -1315,8 +1315,7 @@ nsXPCWrappedJSClass::CallMethod(nsXPCWrappedJS* wrapper, uint16_t methodIndex, JSBool ok = XPCConvert::NativeInterface2JSObject(ccx, &v, nsnull, helper, newWrapperIID, - nsnull, false, false, - nsnull); + nsnull, false, nsnull); if (newWrapperIID) nsMemory::Free(newWrapperIID); if (!ok) { diff --git a/js/xpconnect/src/XPCWrappedNative.cpp b/js/xpconnect/src/XPCWrappedNative.cpp index a15dac1a22e2..4372f9d2a4c9 100644 --- a/js/xpconnect/src/XPCWrappedNative.cpp +++ b/js/xpconnect/src/XPCWrappedNative.cpp @@ -53,6 +53,8 @@ #include "WrapperFactory.h" #include "dombindings.h" +#include "nsContentUtils.h" + #include "mozilla/Util.h" bool @@ -322,16 +324,161 @@ FinishCreate(XPCCallContext& ccx, XPCWrappedNativeScope* Scope, XPCNativeInterface* Interface, nsWrapperCache *cache, - XPCWrappedNative* wrapper, + XPCWrappedNative* inWrapper, XPCWrappedNative** resultWrapper); +// static +// +// This method handles the special case of wrapping a new global object. +// +// The normal code path for wrapping natives goes through +// XPCConvert::NativeInterface2JSObject, XPCWrappedNative::GetNewOrUsed, +// and finally into XPCWrappedNative::Init. Unfortunately, this path assumes +// very early on that we have an XPCWrappedNativeScope and corresponding global +// JS object, which are the very things we need to create here. So we special- +// case the logic and do some things in a different order. +nsresult +XPCWrappedNative::WrapNewGlobal(XPCCallContext &ccx, xpcObjectHelper &nativeHelper, + nsIPrincipal *principal, bool initStandardClasses, + XPCWrappedNative **wrappedGlobal) +{ + bool success; + nsresult rv; + nsISupports *identity = nativeHelper.GetCanonical(); + + // The object should specify that it's meant to be global. + MOZ_ASSERT(nativeHelper.GetScriptableFlags() & nsIXPCScriptable::IS_GLOBAL_OBJECT); + + // We shouldn't be reusing globals. + MOZ_ASSERT(!nativeHelper.GetWrapperCache() || + !nativeHelper.GetWrapperCache()->GetWrapperPreserveColor()); + + // Put together the ScriptableCreateInfo... + XPCNativeScriptableCreateInfo sciProto; + XPCNativeScriptableCreateInfo sciMaybe; + const XPCNativeScriptableCreateInfo& sciWrapper = + GatherScriptableCreateInfo(identity, nativeHelper.GetClassInfo(), + sciProto, sciMaybe); + + // ...and then ScriptableInfo. We need all this stuff now because it's going + // to tell us the JSClass of the object we're going to create. + XPCNativeScriptableInfo *si = XPCNativeScriptableInfo::Construct(ccx, &sciWrapper); + MOZ_ASSERT(si); + + // Finally, we get to the JSClass. + JSClass *clasp = si->GetJSClass(); + MOZ_ASSERT(clasp->flags & JSCLASS_IS_GLOBAL); + + // Create the global. + JSObject *global; + JSCompartment *compartment; + rv = xpc_CreateGlobalObject(ccx, clasp, principal, nsnull, false, + &global, &compartment); + NS_ENSURE_SUCCESS(rv, rv); + + // Immediately enter the global's compartment, so that everything else we + // create ends up there. + JSAutoEnterCompartment ac; + success = ac.enter(ccx, global); + MOZ_ASSERT(success); + + // If requested, immediately initialize the standard classes on the global. + // We need to do this before creating a scope, because + // XPCWrappedNativeScope::SetGlobal resolves |Object| via + // JS_ResolveStandardClass. JS_InitStandardClasses asserts if any of the + // standard classes are already initialized, so this is a problem. + if (initStandardClasses && ! JS_InitStandardClasses(ccx, global)) + return NS_ERROR_FAILURE; + + // Create a scope, but don't do any extra stuff like initializing |Components|. + // All of that stuff happens in the caller. + XPCWrappedNativeScope *scope = XPCWrappedNativeScope::GetNewOrUsed(ccx, global, identity); + MOZ_ASSERT(scope); + + // Make a proto. + XPCWrappedNativeProto *proto = + XPCWrappedNativeProto::GetNewOrUsed(ccx, scope, nativeHelper.GetClassInfo(), &sciProto, + UNKNOWN_OFFSETS, /* callPostCreatePrototype = */ false); + if (!proto) + return NS_ERROR_FAILURE; + proto->CacheOffsets(identity); + + // Set up the prototype on the global. + MOZ_ASSERT(proto->GetJSProtoObject()); + success = JS_SplicePrototype(ccx, global, proto->GetJSProtoObject()); + if (!success) + return NS_ERROR_FAILURE; + + // Construct the wrapper. + nsRefPtr wrapper = new XPCWrappedNative(identity, proto); + + // The wrapper takes over the strong reference to the native object. + nativeHelper.forgetCanonical(); + + // + // We don't call ::Init() on this wrapper, because our setup requirements + // are different for globals. We do our setup inline here, instead. + // + + // Share mScriptableInfo with the proto. + // + // This is probably more trouble than it's worth, since we've already created + // an XPCNativeScriptableInfo for ourselves. Moreover, most of that class is + // shared internally via XPCNativeScriptableInfoShared, so the memory + // savings are negligible. Nevertheless, this is what ::Init() does, and we + // want to be as consistent as possible with that code. + XPCNativeScriptableInfo* siProto = proto->GetScriptableInfo(); + if (siProto && siProto->GetCallback() == sciWrapper.GetCallback()) { + wrapper->mScriptableInfo = siProto; + delete si; + } else { + wrapper->mScriptableInfo = si; + } + + // Set the JS object to the global we already created. + wrapper->mFlatJSObject = global; + + // Set the private to the XPCWrappedNative. + JS_SetPrivate(global, wrapper); + + // There are dire comments elsewhere in the code about how a GC can + // happen somewhere after wrapper initialization but before the wrapper is + // added to the hashtable in FinishCreate(). It's not clear if that can + // happen here, but let's just be safe for now. + AutoMarkingWrappedNativePtr wrapperMarker(ccx, wrapper); + + // Call the common Init finish routine. This mainly just does an AddRef + // on behalf of XPConnect (the corresponding Release is in the finalizer + // hook), but it does some other miscellaneous things too, so we don't + // inline it. + success = wrapper->FinishInit(ccx); + MOZ_ASSERT(success); + + // Go through some extra work to find the tearoff. This is kind of silly + // on a conceptual level: the point of tearoffs is to cache the results + // of QI-ing mIdentity to different interfaces, and we don't need that + // since we're dealing with nsISupports. But lots of code expects tearoffs + // to exist for everything, so we just follow along. + XPCNativeInterface* iface = XPCNativeInterface::GetNewOrUsed(ccx, &NS_GET_IID(nsISupports)); + MOZ_ASSERT(iface); + nsresult status; + success = wrapper->FindTearOff(ccx, iface, false, &status); + if (!success) + return status; + + // Call the common creation finish routine. This does all of the bookkeeping + // like inserting the wrapper into the wrapper map and setting up the wrapper + // cache. + return FinishCreate(ccx, scope, iface, nativeHelper.GetWrapperCache(), + wrapper, wrappedGlobal); +} + // static nsresult XPCWrappedNative::GetNewOrUsed(XPCCallContext& ccx, xpcObjectHelper& helper, XPCWrappedNativeScope* Scope, XPCNativeInterface* Interface, - JSBool isGlobal, XPCWrappedNative** resultWrapper) { nsWrapperCache *cache = helper.GetWrapperCache(); @@ -354,30 +501,23 @@ XPCWrappedNative::GetNewOrUsed(XPCCallContext& ccx, XPCLock* mapLock = Scope->GetRuntime()->GetMapLock(); - // We use an AutoMarkingPtr here because it is possible for JS gc to happen - // after we have Init'd the wrapper but *before* we add it to the hashtable. - // This would cause the mSet to get collected and we'd later crash. I've - // *seen* this happen. - AutoMarkingWrappedNativePtr wrapper(ccx); + nsRefPtr wrapper; Native2WrappedNativeMap* map = Scope->GetWrappedNativeMap(); if (!cache) { { // scoped lock XPCAutoLock lock(mapLock); wrapper = map->Find(identity); - if (wrapper) - wrapper->AddRef(); } if (wrapper) { if (Interface && !wrapper->FindTearOff(ccx, Interface, false, &rv)) { - NS_RELEASE(wrapper); NS_ASSERTION(NS_FAILED(rv), "returning NS_OK on failure"); return rv; } DEBUG_CheckWrapperThreadSafety(wrapper); - *resultWrapper = wrapper; + *resultWrapper = wrapper.forget().get(); return NS_OK; } } @@ -452,8 +592,7 @@ XPCWrappedNative::GetNewOrUsed(XPCCallContext& ccx, XPCWrappedNativeScope* betterScope = XPCWrappedNativeScope::FindInJSObjectScope(ccx, parent); if (betterScope != Scope) - return GetNewOrUsed(ccx, helper, betterScope, Interface, - isGlobal, resultWrapper); + return GetNewOrUsed(ccx, helper, betterScope, Interface, resultWrapper); newParentVal = OBJECT_TO_JSVAL(parent); } @@ -466,35 +605,26 @@ XPCWrappedNative::GetNewOrUsed(XPCCallContext& ccx, JSObject *cached = cache->GetWrapper(); if (cached) { if (IS_SLIM_WRAPPER_OBJECT(cached)) { - nsRefPtr morphed; if (!XPCWrappedNative::Morph(ccx, cached, Interface, cache, - getter_AddRefs(morphed))) + getter_AddRefs(wrapper))) return NS_ERROR_FAILURE; - - wrapper = morphed.forget().get(); } else { - wrapper = - static_cast(xpc_GetJSPrivate(cached)); - if (wrapper) - wrapper->AddRef(); + wrapper = static_cast(xpc_GetJSPrivate(cached)); } } - } else - { // scoped lock + } else { + // scoped lock XPCAutoLock lock(mapLock); wrapper = map->Find(identity); - if (wrapper) - wrapper->AddRef(); } if (wrapper) { if (Interface && !wrapper->FindTearOff(ccx, Interface, false, &rv)) { - NS_RELEASE(wrapper); NS_ASSERTION(NS_FAILED(rv), "returning NS_OK on failure"); return rv; } DEBUG_CheckWrapperThreadSafety(wrapper); - *resultWrapper = wrapper; + *resultWrapper = wrapper.forget().get(); return NS_OK; } } else { @@ -522,7 +652,7 @@ XPCWrappedNative::GetNewOrUsed(XPCCallContext& ccx, // wrapper is actually created, but before JS code can see it. if (info && !isClassInfo) { - proto = XPCWrappedNativeProto::GetNewOrUsed(ccx, Scope, info, &sciProto, isGlobal); + proto = XPCWrappedNativeProto::GetNewOrUsed(ccx, Scope, info, &sciProto); if (!proto) return NS_ERROR_FAILURE; @@ -553,19 +683,19 @@ XPCWrappedNative::GetNewOrUsed(XPCCallContext& ccx, // forget about it. helper.forgetCanonical(); - NS_ADDREF(wrapper); - NS_ASSERTION(!xpc::WrapperFactory::IsXrayWrapper(parent), "Xray wrapper being used to parent XPCWrappedNative?"); - if (!wrapper->Init(ccx, parent, isGlobal, &sciWrapper)) { - NS_RELEASE(wrapper); + // We use an AutoMarkingPtr here because it is possible for JS gc to happen + // after we have Init'd the wrapper but *before* we add it to the hashtable. + // This would cause the mSet to get collected and we'd later crash. I've + // *seen* this happen. + AutoMarkingWrappedNativePtr wrapperMarker(ccx, wrapper); + + if (!wrapper->Init(ccx, parent, &sciWrapper)) return NS_ERROR_FAILURE; - } if (Interface && !wrapper->FindTearOff(ccx, Interface, false, &rv)) { - // Second reference will be released by the FlatJSObject's finalizer. - wrapper->Release(); NS_ASSERTION(NS_FAILED(rv), "returning NS_OK on failure"); return rv; } @@ -583,9 +713,11 @@ FinishCreate(XPCCallContext& ccx, XPCWrappedNativeScope* Scope, XPCNativeInterface* Interface, nsWrapperCache *cache, - XPCWrappedNative* wrapper, + XPCWrappedNative* inWrapper, XPCWrappedNative** resultWrapper) { + MOZ_ASSERT(inWrapper); + #if DEBUG_xpc_leaks { char* s = wrapper->ToString(ccx); @@ -600,30 +732,20 @@ FinishCreate(XPCCallContext& ccx, XPCLock* mapLock = Scope->GetRuntime()->GetMapLock(); Native2WrappedNativeMap* map = Scope->GetWrappedNativeMap(); - // Redundant wrapper must be killed outside of the map lock. - XPCWrappedNative* wrapperToKill = nsnull; - + nsRefPtr wrapper; { // scoped lock - XPCAutoLock lock(mapLock); // Deal with the case where the wrapper got created as a side effect - // of one of our calls out of this code (or on another thread). - XPCWrappedNative* wrapper2 = map->Add(wrapper); - if (!wrapper2) { - NS_ERROR("failed to add our wrapper!"); - wrapperToKill = wrapper; - wrapper = nsnull; - } else if (wrapper2 != wrapper) { - NS_ADDREF(wrapper2); - wrapperToKill = wrapper; - wrapper = wrapper2; - } + // of one of our calls out of this code (or on another thread). Add() + // returns the (possibly pre-existing) wrapper that ultimately ends up + // in the map, which is what we want. + XPCAutoLock lock(mapLock); + wrapper = map->Add(inWrapper); + if (!wrapper) + return NS_ERROR_FAILURE; } - if (wrapperToKill) { - // Second reference will be released by the FlatJSObject's finializer. - wrapperToKill->Release(); - } else if (wrapper) { + if (wrapper == inWrapper) { JSObject *flat = wrapper->GetFlatJSObject(); NS_ASSERTION(!cache || !cache->GetWrapperPreserveColor() || flat == cache->GetWrapperPreserveColor(), @@ -668,11 +790,8 @@ FinishCreate(XPCCallContext& ccx, } } - if (!wrapper) - return NS_ERROR_FAILURE; - DEBUG_CheckClassInfoClaims(wrapper); - *resultWrapper = wrapper; + *resultWrapper = wrapper.forget().get(); return NS_OK; } @@ -691,12 +810,6 @@ XPCWrappedNative::Morph(XPCCallContext& ccx, static_cast(xpc_GetJSPrivate(existingJSObject)); XPCWrappedNativeProto *proto = GetSlimWrapperProto(existingJSObject); - // We use an AutoMarkingPtr here because it is possible for JS gc to happen - // after we have Init'd the wrapper but *before* we add it to the hashtable. - // This would cause the mSet to get collected and we'd later crash. I've - // *seen* this happen. - AutoMarkingWrappedNativePtr wrapper(ccx); - #if DEBUG // FIXME Can't assert this until // https://bugzilla.mozilla.org/show_bug.cgi?id=343141 is fixed. @@ -717,31 +830,30 @@ XPCWrappedNative::Morph(XPCCallContext& ccx, #endif #endif - wrapper = new XPCWrappedNative(dont_AddRef(identity), proto); + nsRefPtr wrapper = new XPCWrappedNative(dont_AddRef(identity), proto); if (!wrapper) return NS_ERROR_FAILURE; - NS_ADDREF(wrapper); - NS_ASSERTION(!xpc::WrapperFactory::IsXrayWrapper(js::GetObjectParent(existingJSObject)), "Xray wrapper being used to parent XPCWrappedNative?"); + // We use an AutoMarkingPtr here because it is possible for JS gc to happen + // after we have Init'd the wrapper but *before* we add it to the hashtable. + // This would cause the mSet to get collected and we'd later crash. I've + // *seen* this happen. + AutoMarkingWrappedNativePtr wrapperMarker(ccx, wrapper); + JSAutoEnterCompartment ac; - if (!ac.enter(ccx, existingJSObject) || !wrapper->Init(ccx, existingJSObject)) { - NS_RELEASE(wrapper); + if (!ac.enter(ccx, existingJSObject) || !wrapper->Init(ccx, existingJSObject)) return NS_ERROR_FAILURE; - } nsresult rv; if (Interface && !wrapper->FindTearOff(ccx, Interface, false, &rv)) { - // Second reference will be released by the FlatJSObject's finalizer. - wrapper->Release(); NS_ASSERTION(NS_FAILED(rv), "returning NS_OK on failure"); return rv; } - return FinishCreate(ccx, wrapper->GetScope(), Interface, cache, wrapper, - resultWrapper); + return FinishCreate(ccx, wrapper->GetScope(), Interface, cache, wrapper, resultWrapper); } // static @@ -1062,8 +1174,7 @@ static PRUint32 sMorphedSlimWrappers; #endif JSBool -XPCWrappedNative::Init(XPCCallContext& ccx, - JSObject* parent, JSBool isGlobal, +XPCWrappedNative::Init(XPCCallContext& ccx, JSObject* parent, const XPCNativeScriptableCreateInfo* sci) { // setup our scriptable info... @@ -1076,7 +1187,7 @@ XPCWrappedNative::Init(XPCCallContext& ccx, } if (!mScriptableInfo) { mScriptableInfo = - XPCNativeScriptableInfo::Construct(ccx, isGlobal, sci); + XPCNativeScriptableInfo::Construct(ccx, sci); if (!mScriptableInfo) return false; @@ -1088,15 +1199,8 @@ XPCWrappedNative::Init(XPCCallContext& ccx, JSClass* jsclazz = si ? si->GetJSClass() : Jsvalify(&XPC_WN_NoHelper_JSClass.base); - if (isGlobal) { - // Resolving a global object's class can cause us to create a global's - // JS class without the proper global flags. Notice that here and fix - // the problem. - if (!(jsclazz->flags & JSCLASS_IS_GLOBAL)) - jsclazz->flags |= XPCONNECT_GLOBAL_FLAGS; - } else - NS_ASSERTION(!(jsclazz->flags & JSCLASS_IS_GLOBAL), - "Non-global object has the wrong flags"); + // We should have the global jsclass flag if and only if we're a global. + MOZ_ASSERT_IF(si, !!si->GetFlags().IsGlobalObject() == !!(jsclazz->flags & JSCLASS_IS_GLOBAL)); NS_ASSERTION(jsclazz && jsclazz->name && @@ -1377,6 +1481,43 @@ XPCWrappedNative::SystemIsBeingShutDown() /***************************************************************************/ +// If we have to transplant an object across compartments, we need to be +// careful if the underlying object implements nsWrapperCache and is preserving +// the wrapper. +// +// The class brackets a pair of Unpreserve/Preserve calls in the given scope. +// +// This class _must_ live on the stack, in part so that mPreservedWrapper is +// visible to the stack scanner. The caller wants the wrapper to be preserved, +// so we don't want it to get accidentally GCed. +class AutoWrapperChanger NS_STACK_CLASS { +public: + AutoWrapperChanger() : mCache(nsnull) + , mCOMObj(nsnull) + , mPreservedWrapper(nsnull) + {} + + void init(nsISupports* aCOMObj, nsWrapperCache* aWrapperCache) { + mCOMObj = aCOMObj; + mCache = aWrapperCache; + if (mCache->PreservingWrapper()) { + mPreservedWrapper = mCache->GetWrapper(); + MOZ_ASSERT(mPreservedWrapper); + nsContentUtils::ReleaseWrapper(mCOMObj, mCache); + } + } + + ~AutoWrapperChanger() { + if (mPreservedWrapper) + nsContentUtils::PreserveWrapper(mCOMObj, mCache); + } + +private: + nsWrapperCache* mCache; + nsISupports* mCOMObj; + JSObject* mPreservedWrapper; +}; + // static nsresult XPCWrappedNative::ReparentWrapperIfFound(XPCCallContext& ccx, @@ -1395,10 +1536,16 @@ XPCWrappedNative::ReparentWrapperIfFound(XPCCallContext& ccx, nsresult rv; nsRefPtr wrapper; + AutoWrapperChanger wrapperChanger; JSObject *flat; nsWrapperCache* cache = nsnull; CallQueryInterface(aCOMObj, &cache); if (cache) { + + // There's a wrapper cache. Make sure we keep it sane no matter what + // happens. + wrapperChanger.init(aCOMObj, cache); + flat = cache->GetWrapper(); if (flat && !IS_SLIM_WRAPPER_OBJECT(flat)) { wrapper = static_cast(xpc_GetJSPrivate(flat)); @@ -1458,9 +1605,7 @@ XPCWrappedNative::ReparentWrapperIfFound(XPCCallContext& ccx, newProto = XPCWrappedNativeProto::GetNewOrUsed(ccx, aNewScope, oldProto->GetClassInfo(), - &ci, - (info->GetJSClass()->flags & JSCLASS_IS_GLOBAL), - oldProto->GetOffsetsMasked()); + &ci, oldProto->GetOffsetsMasked()); if (!newProto) { return NS_ERROR_FAILURE; } @@ -2974,50 +3119,23 @@ inline nsresult UnexpectedFailure(nsresult rv) return rv; } -/* void refreshPrototype (); */ -NS_IMETHODIMP XPCWrappedNative::RefreshPrototype() +/* void finishInitForWrappedGlobal (); */ +NS_IMETHODIMP XPCWrappedNative::FinishInitForWrappedGlobal() { + // We can only be called under certain conditions. + MOZ_ASSERT(mScriptableInfo); + MOZ_ASSERT(mScriptableInfo->GetFlags().IsGlobalObject()); + MOZ_ASSERT(HasProto()); + + // Build a CCX. XPCCallContext ccx(NATIVE_CALLER); if (!ccx.IsValid()) return UnexpectedFailure(NS_ERROR_FAILURE); - if (!HasProto()) - return NS_OK; - - if (!mFlatJSObject) - return UnexpectedFailure(NS_ERROR_FAILURE); - - JSAutoEnterCompartment ac; - if (!ac.enter(ccx, GetFlatJSObject())) - return UnexpectedFailure(NS_ERROR_FAILURE); - - AutoMarkingWrappedNativeProtoPtr oldProto(ccx); - AutoMarkingWrappedNativeProtoPtr newProto(ccx); - - oldProto = GetProto(); - - XPCNativeScriptableInfo *info = oldProto->GetScriptableInfo(); - XPCNativeScriptableCreateInfo ci(*info); - newProto = XPCWrappedNativeProto::GetNewOrUsed(ccx, oldProto->GetScope(), - oldProto->GetClassInfo(), - &ci, - (info->GetJSClass()->flags & JSCLASS_IS_GLOBAL), - oldProto->GetOffsetsMasked()); - if (!newProto) - return UnexpectedFailure(NS_ERROR_FAILURE); - - // If nothing needs to change then we're done. - - if (newProto.get() == oldProto.get()) - return NS_OK; - - if (!JS_SplicePrototype(ccx, GetFlatJSObject(), newProto->GetJSProtoObject())) - return UnexpectedFailure(NS_ERROR_FAILURE); - - SetProto(newProto); - - if (mScriptableInfo == oldProto->GetScriptableInfo()) - UpdateScriptableInfo(newProto->GetScriptableInfo()); + // Call PostCreateProrotype. + bool success = GetProto()->CallPostCreatePrototype(ccx); + if (!success) + return NS_ERROR_FAILURE; return NS_OK; } @@ -3686,10 +3804,8 @@ ConstructSlimWrapper(XPCCallContext &ccx, sciProto(aHelper.forgetXPCClassInfo(), flags, interfacesBitmap); AutoMarkingWrappedNativeProtoPtr xpcproto(ccx); - JSBool isGlobal = false; xpcproto = XPCWrappedNativeProto::GetNewOrUsed(ccx, xpcScope, - classInfoHelper, &sciProto, - isGlobal); + classInfoHelper, &sciProto); if (!xpcproto) return false; diff --git a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp index 35ab772b9e8e..374e9b7f4fe7 100644 --- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp +++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp @@ -1349,7 +1349,6 @@ XPC_WN_JSOp_ThisObject(JSContext *cx, JSObject *obj) // static XPCNativeScriptableInfo* XPCNativeScriptableInfo::Construct(XPCCallContext& ccx, - JSBool isGlobal, const XPCNativeScriptableCreateInfo* sci) { NS_ASSERTION(sci, "bad param"); @@ -1372,7 +1371,7 @@ XPCNativeScriptableInfo::Construct(XPCCallContext& ccx, XPCNativeScriptableSharedMap* map = rt->GetNativeScriptableSharedMap(); { // scoped lock XPCAutoLock lock(rt->GetMapLock()); - success = map->GetNewOrUsed(sci->GetFlags(), name, isGlobal, + success = map->GetNewOrUsed(sci->GetFlags(), name, sci->GetInterfacesBitmap(), newObj); } @@ -1385,7 +1384,7 @@ XPCNativeScriptableInfo::Construct(XPCCallContext& ccx, } void -XPCNativeScriptableShared::PopulateJSClass(JSBool isGlobal) +XPCNativeScriptableShared::PopulateJSClass() { NS_ASSERTION(mJSClass.base.name, "bad state!"); @@ -1393,7 +1392,7 @@ XPCNativeScriptableShared::PopulateJSClass(JSBool isGlobal) JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_NEW_RESOLVE; - if (isGlobal) + if (mFlags.IsGlobalObject()) mJSClass.base.flags |= XPCONNECT_GLOBAL_FLAGS; JSPropertyOp addProperty; diff --git a/js/xpconnect/src/XPCWrappedNativeProto.cpp b/js/xpconnect/src/XPCWrappedNativeProto.cpp index c26f42937aef..d25b80c8949d 100644 --- a/js/xpconnect/src/XPCWrappedNativeProto.cpp +++ b/js/xpconnect/src/XPCWrappedNativeProto.cpp @@ -89,15 +89,15 @@ XPCWrappedNativeProto::~XPCWrappedNativeProto() JSBool XPCWrappedNativeProto::Init(XPCCallContext& ccx, - JSBool isGlobal, - const XPCNativeScriptableCreateInfo* scriptableCreateInfo) + const XPCNativeScriptableCreateInfo* scriptableCreateInfo, + bool callPostCreatePrototype) { nsIXPCScriptable *callback = scriptableCreateInfo ? scriptableCreateInfo->GetCallback() : nsnull; if (callback) { mScriptableInfo = - XPCNativeScriptableInfo::Construct(ccx, isGlobal, scriptableCreateInfo); + XPCNativeScriptableInfo::Construct(ccx, scriptableCreateInfo); if (!mScriptableInfo) return false; } @@ -128,24 +128,38 @@ XPCWrappedNativeProto::Init(XPCCallContext& ccx, mScope->GetPrototypeJSObject(), true, parent); - JSBool ok = !!mJSProtoObject; - - if (ok) { + bool success = !!mJSProtoObject; + if (success) { JS_SetPrivate(mJSProtoObject, this); - if (callback) { - nsresult rv = callback->PostCreatePrototype(ccx, mJSProtoObject); - if (NS_FAILED(rv)) { - JS_SetPrivate(mJSProtoObject, nsnull); - mJSProtoObject = nsnull; - XPCThrower::Throw(rv, ccx); - return false; - } - } + if (callPostCreatePrototype) + success = CallPostCreatePrototype(ccx); } DEBUG_ReportShadowedMembers(mSet, nsnull, this); - return ok; + return success; +} + +bool +XPCWrappedNativeProto::CallPostCreatePrototype(XPCCallContext& ccx) +{ + // Nothing to do if we don't have a scriptable callback. + nsIXPCScriptable *callback = mScriptableInfo ? mScriptableInfo->GetCallback() + : nsnull; + if (!callback) + return true; + + // Call the helper. This can handle being called if it's not implemented, + // so we don't have to check any sort of "want" here. See xpc_map_end.h. + nsresult rv = callback->PostCreatePrototype(ccx, mJSProtoObject); + if (NS_FAILED(rv)) { + JS_SetPrivate(mJSProtoObject, nsnull); + mJSProtoObject = nsnull; + XPCThrower::Throw(rv, ccx); + return false; + } + + return true; } void @@ -195,8 +209,8 @@ XPCWrappedNativeProto::GetNewOrUsed(XPCCallContext& ccx, XPCWrappedNativeScope* scope, nsIClassInfo* classInfo, const XPCNativeScriptableCreateInfo* scriptableCreateInfo, - JSBool isGlobal, - QITableEntry* offsets) + QITableEntry* offsets, + bool callPostCreatePrototype) { NS_ASSERTION(scope, "bad param"); NS_ASSERTION(classInfo, "bad param"); @@ -226,7 +240,7 @@ XPCWrappedNativeProto::GetNewOrUsed(XPCCallContext& ccx, proto = new XPCWrappedNativeProto(scope, classInfo, ciFlags, set, offsets); - if (!proto || !proto->Init(ccx, isGlobal, scriptableCreateInfo)) { + if (!proto || !proto->Init(ccx, scriptableCreateInfo, callPostCreatePrototype)) { delete proto.get(); return nsnull; } diff --git a/js/xpconnect/src/XPCWrappedNativeScope.cpp b/js/xpconnect/src/XPCWrappedNativeScope.cpp index b1addb8ce4d7..e5a27d0681f1 100644 --- a/js/xpconnect/src/XPCWrappedNativeScope.cpp +++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp @@ -112,19 +112,19 @@ XPCWrappedNativeScope* XPCWrappedNativeScope::gDyingScopes = nsnull; // static XPCWrappedNativeScope* -XPCWrappedNativeScope::GetNewOrUsed(XPCCallContext& ccx, JSObject* aGlobal) +XPCWrappedNativeScope::GetNewOrUsed(XPCCallContext& ccx, JSObject* aGlobal, nsISupports* aNative) { XPCWrappedNativeScope* scope = FindInJSObjectScope(ccx, aGlobal, true); if (!scope) - scope = new XPCWrappedNativeScope(ccx, aGlobal); + scope = new XPCWrappedNativeScope(ccx, aGlobal, aNative); else { // We need to call SetGlobal in order to refresh our cached // mPrototypeJSObject and to clear mPrototypeNoHelper (so we get a new // new one if requested in the new scope) in the case where the global // object is being reused (JS_ClearScope has been called). NOTE: We are // only called by nsXPConnect::InitClasses. - scope->SetGlobal(ccx, aGlobal); + scope->SetGlobal(ccx, aGlobal, aNative); } if (js::GetObjectClass(aGlobal)->flags & JSCLASS_XPCONNECT_GLOBAL) JS_SetReservedSlot(aGlobal, @@ -134,7 +134,8 @@ XPCWrappedNativeScope::GetNewOrUsed(XPCCallContext& ccx, JSObject* aGlobal) } XPCWrappedNativeScope::XPCWrappedNativeScope(XPCCallContext& ccx, - JSObject* aGlobal) + JSObject* aGlobal, + nsISupports* aNative) : mRuntime(ccx.GetRuntime()), mWrappedNativeMap(Native2WrappedNativeMap::newMap(XPC_NATIVE_MAP_SIZE)), mWrappedNativeProtoMap(ClassInfo2WrappedNativeProtoMap::newMap(XPC_NATIVE_PROTO_MAP_SIZE)), @@ -165,7 +166,7 @@ XPCWrappedNativeScope::XPCWrappedNativeScope(XPCCallContext& ccx, } if (aGlobal) - SetGlobal(ccx, aGlobal); + SetGlobal(ccx, aGlobal, aNative); DEBUG_TrackNewScope(this); MOZ_COUNT_CTOR(XPCWrappedNativeScope); @@ -226,33 +227,33 @@ js::Class XPC_WN_NoHelper_Proto_JSClass = { void -XPCWrappedNativeScope::SetGlobal(XPCCallContext& ccx, JSObject* aGlobal) +XPCWrappedNativeScope::SetGlobal(XPCCallContext& ccx, JSObject* aGlobal, + nsISupports* aNative) { // We allow for calling this more than once. This feature is used by // nsXPConnect::InitClassesWithNewWrappedGlobal. mGlobalJSObject = aGlobal; mScriptObjectPrincipal = nsnull; - // Now init our script object principal, if the new global has one - const JSClass* jsClass = js::GetObjectJSClass(aGlobal); - if (!(~jsClass->flags & (JSCLASS_HAS_PRIVATE | - JSCLASS_PRIVATE_IS_NSISUPPORTS))) { - // Our global has an nsISupports native pointer. Let's - // see whether it's what we want. - nsISupports* priv = (nsISupports*)xpc_GetJSPrivate(aGlobal); - nsCOMPtr native = - do_QueryInterface(priv); - nsCOMPtr sop; - if (native) { - sop = do_QueryWrappedNative(native); - } - if (!sop) { - sop = do_QueryInterface(priv); - } - mScriptObjectPrincipal = sop; + // Try to find the native global object. If we didn't receive it explicitly, + // we might be able to find it in the private slot. + nsISupports* native = aNative; + if (!native && + !(~js::GetObjectJSClass(aGlobal)->flags & (JSCLASS_HAS_PRIVATE | + JSCLASS_PRIVATE_IS_NSISUPPORTS))) + { + // Get the private. It might be a WN, in which case we dig deeper. + native = (nsISupports*)xpc_GetJSPrivate(aGlobal); + nsCOMPtr wn = do_QueryInterface(native); + if (wn) + native = static_cast(native)->GetIdentityObject(); } + // Now init our script object principal, if the new global has one. + nsCOMPtr sop = do_QueryInterface(native); + mScriptObjectPrincipal = sop; + // Lookup 'globalObject.Object.prototype' for our wrapper's proto { AutoJSErrorAndExceptionEater eater(ccx); // scoped error eater @@ -261,7 +262,15 @@ XPCWrappedNativeScope::SetGlobal(XPCCallContext& ccx, JSObject* aGlobal) jsid idObj = mRuntime->GetStringID(XPCJSRuntime::IDX_OBJECT); jsid idProto = mRuntime->GetStringID(XPCJSRuntime::IDX_PROTOTYPE); - if (JS_GetPropertyById(ccx, aGlobal, idObj, &val) && + // When creating a new scope to boostrap a new global, we don't yet have + // an XPCWrappedNative associated with the global object. However, the + // resolve hook on the JSClass assumes there is one. So we need to avoid + // resolving anything on the global object until things get a bit further + // along. As such, we manually resolve |Object| before accessing it below. + JSBool didResolve; + + if (JS_ResolveStandardClass(ccx, aGlobal, idObj, &didResolve) && + JS_GetPropertyById(ccx, aGlobal, idObj, &val) && !JSVAL_IS_PRIMITIVE(val) && JS_GetPropertyById(ccx, JSVAL_TO_OBJECT(val), idProto, &val) && !JSVAL_IS_PRIMITIVE(val)) { diff --git a/js/xpconnect/src/dombindings.cpp b/js/xpconnect/src/dombindings.cpp index 3c0b1e63b37c..3cbb64efca33 100644 --- a/js/xpconnect/src/dombindings.cpp +++ b/js/xpconnect/src/dombindings.cpp @@ -106,17 +106,11 @@ static bool XPCOMObjectToJsval(JSContext *cx, JSObject *scope, xpcObjectHelper &helper, bool allowNativeWrapper, jsval *rval) { - // XXX The OBJ_IS_NOT_GLOBAL here is not really right. In - // fact, this code is depending on the fact that the - // global object will not have been collected, and - // therefore this NativeInterface2JSObject will not end up - // creating a new XPCNativeScriptableShared. - XPCLazyCallContext lccx(JS_CALLER, cx, scope); nsresult rv; if (!XPCConvert::NativeInterface2JSObject(lccx, rval, NULL, helper, NULL, NULL, - allowNativeWrapper, OBJ_IS_NOT_GLOBAL, &rv)) { + allowNativeWrapper, &rv)) { // I can't tell if NativeInterface2JSObject throws JS exceptions // or not. This is a sloppy stab at the right semantics; the // method really ought to be fixed to behave consistently. @@ -613,7 +607,7 @@ GetArrayIndexFromId(JSContext *cx, jsid id) if (NS_LIKELY((unsigned)s >= 'a' && (unsigned)s <= 'z')) return -1; - jsuint i; + uint32_t i; JSLinearString *str = js::AtomToLinearString(JSID_TO_ATOM(id)); return js::StringIsArrayIndex(str, &i) ? i : -1; } diff --git a/js/xpconnect/src/nsXPConnect.cpp b/js/xpconnect/src/nsXPConnect.cpp index 82fff2e41afd..138f04672833 100644 --- a/js/xpconnect/src/nsXPConnect.cpp +++ b/js/xpconnect/src/nsXPConnect.cpp @@ -699,6 +699,18 @@ xpc_GCThingIsGrayCCThing(void *thing) xpc_IsGrayGCThing(thing); } +struct UnmarkGrayTracer : public JSTracer +{ + UnmarkGrayTracer() : mTracingShape(false), mPreviousShape(nsnull) {} + UnmarkGrayTracer(JSTracer *trc, bool aTracingShape) + : mTracingShape(aTracingShape), mPreviousShape(nsnull) + { + JS_TracerInit(this, trc->runtime, trc->callback); + } + bool mTracingShape; // true iff we are tracing the immediate children of a shape + void *mPreviousShape; // If mTracingShape, shape child or NULL. Otherwise, NULL. +}; + /* * The GC and CC are run independently. Consequently, the following sequence of * events can occur: @@ -729,15 +741,38 @@ UnmarkGrayChildren(JSTracer *trc, void **thingp, JSGCTraceKind kind) return; } - // If this thing is not a CC-kind or already non-gray then we're done. - if (!AddToCCKind(kind) || !xpc_IsGrayGCThing(thing)) + if (!xpc_IsGrayGCThing(thing)) return; - // Unmark. static_cast(thing)->unmark(js::gc::GRAY); - // Trace children. - JS_TraceChildren(trc, thing, kind); + /* + * Trace children of |thing|. If |thing| and its parent are both shapes, |thing| will + * get saved to mPreviousShape without being traced. The parent will later + * trace |thing|. This is done to avoid increasing the stack depth during shape + * tracing. It is safe to do because a shape can only have one child that is a shape. + */ + UnmarkGrayTracer *tracer = static_cast(trc); + UnmarkGrayTracer childTracer(tracer, kind == JSTRACE_SHAPE); + + if (kind != JSTRACE_SHAPE) { + JS_TraceChildren(&childTracer, thing, kind); + MOZ_ASSERT(!childTracer.mPreviousShape); + return; + } + + if (tracer->mTracingShape) { + MOZ_ASSERT(!tracer->mPreviousShape); + tracer->mPreviousShape = thing; + return; + } + + do { + MOZ_ASSERT(!xpc_IsGrayGCThing(thing)); + JS_TraceChildren(&childTracer, thing, JSTRACE_SHAPE); + thing = childTracer.mPreviousShape; + childTracer.mPreviousShape = nsnull; + } while (thing); } void @@ -749,7 +784,7 @@ xpc_UnmarkGrayObjectRecursive(JSObject *obj) js::gc::AsCell(obj)->unmark(js::gc::GRAY); // Trace children. - JSTracer trc; + UnmarkGrayTracer trc; JS_TracerInit(&trc, JS_GetObjectRuntime(obj), UnmarkGrayChildren); JS_TraceChildren(&trc, obj, JSTRACE_OBJECT); } @@ -1082,8 +1117,6 @@ nsXPConnect::InitClasses(JSContext * aJSContext, JSObject * aGlobalJSObj) if (!ac.enter(ccx, aGlobalJSObj)) return UnexpectedFailure(NS_ERROR_FAILURE); - xpc_InitJSxIDClassObjects(); - XPCWrappedNativeScope* scope = XPCWrappedNativeScope::GetNewOrUsed(ccx, aGlobalJSObj); @@ -1103,20 +1136,6 @@ nsXPConnect::InitClasses(JSContext * aJSContext, JSObject * aGlobalJSObj) return NS_OK; } -static JSBool -TempGlobalResolve(JSContext *aJSContext, JSObject *obj, jsid id) -{ - JSBool resolved; - return JS_ResolveStandardClass(aJSContext, obj, id, &resolved); -} - -static JSClass xpcTempGlobalClass = { - "xpcTempGlobalClass", JSCLASS_GLOBAL_FLAGS, - JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub, - JS_EnumerateStub, TempGlobalResolve, JS_ConvertStub, nsnull, - JSCLASS_NO_OPTIONAL_MEMBERS -}; - static bool CreateNewCompartment(JSContext *cx, JSClass *clasp, nsIPrincipal *principal, xpc::CompartmentPrivate *priv, JSObject **global, @@ -1205,7 +1224,12 @@ xpc_CreateGlobalObject(JSContext *cx, JSClass *clasp, } #ifdef DEBUG - if (clasp->flags & JSCLASS_XPCONNECT_GLOBAL) { + // Verify that the right trace hook is called. Note that this doesn't + // work right for wrapped globals, since the tracing situation there is + // more complicated. Manual inspection shows that they do the right thing. + if (clasp->flags & JSCLASS_XPCONNECT_GLOBAL && + !((js::Class*)clasp)->ext.isWrappedNative) + { VerifyTraceXPCGlobalCalledTracer trc; JS_TracerInit(&trc.base, JS_GetRuntime(cx), VerifyTraceXPCGlobalCalled); trc.ok = false; @@ -1220,120 +1244,55 @@ xpc_CreateGlobalObject(JSContext *cx, JSClass *clasp, NS_IMETHODIMP nsXPConnect::InitClassesWithNewWrappedGlobal(JSContext * aJSContext, nsISupports *aCOMObj, - const nsIID & aIID, nsIPrincipal * aPrincipal, - nsISupports * aExtraPtr, PRUint32 aFlags, nsIXPConnectJSObjectHolder **_retval) { NS_ASSERTION(aJSContext, "bad param"); NS_ASSERTION(aCOMObj, "bad param"); NS_ASSERTION(_retval, "bad param"); - NS_ASSERTION(aPrincipal, "must be able to find a compartment"); - // XXX This is not pretty. We make a temporary global object and - // init it with all the Components object junk just so we have a - // parent with an xpc scope to use when wrapping the object that will - // become the 'real' global. + // We pass null for the 'extra' pointer during global object creation, so + // we need to have a principal. + MOZ_ASSERT(aPrincipal); XPCCallContext ccx(NATIVE_CALLER, aJSContext); - JSCompartment* compartment; - JSObject* tempGlobal; - - nsresult rv = xpc_CreateGlobalObject(ccx, &xpcTempGlobalClass, aPrincipal, - aExtraPtr, false, &tempGlobal, &compartment); + // Call into XPCWrappedNative to make a new global object, scope, and global + // prototype. + xpcObjectHelper helper(aCOMObj); + MOZ_ASSERT(helper.GetScriptableFlags() & nsIXPCScriptable::IS_GLOBAL_OBJECT); + nsRefPtr wrappedGlobal; + nsresult rv = + XPCWrappedNative::WrapNewGlobal(ccx, helper, aPrincipal, + aFlags & nsIXPConnect::INIT_JS_STANDARD_CLASSES, + getter_AddRefs(wrappedGlobal)); NS_ENSURE_SUCCESS(rv, rv); + // Grab a copy of the global and enter its compartment. + JSObject *global = wrappedGlobal->GetFlatJSObject(); + MOZ_ASSERT(!js::GetObjectParent(global)); JSAutoEnterCompartment ac; - if (!ac.enter(ccx, tempGlobal)) - return UnexpectedFailure(NS_ERROR_FAILURE); - ccx.SetScopeForNewJSObjects(tempGlobal); + if (!ac.enter(ccx, global)) + return NS_ERROR_UNEXPECTED; + // Apply the system flag, if requested. bool system = (aFlags & nsIXPConnect::FLAG_SYSTEM_GLOBAL_OBJECT) != 0; - if (system && !JS_MakeSystemObject(aJSContext, tempGlobal)) + if (system && !JS_MakeSystemObject(aJSContext, global)) return UnexpectedFailure(NS_ERROR_FAILURE); - jsval v; - nsCOMPtr holder; - { - // Scope for our auto-marker; it just needs to keep tempGlobal alive - // long enough for InitClasses and WrapNative to do their work - AUTO_MARK_JSVAL(ccx, OBJECT_TO_JSVAL(tempGlobal)); - - if (NS_FAILED(InitClasses(aJSContext, tempGlobal))) - return UnexpectedFailure(NS_ERROR_FAILURE); - - nsresult rv; - xpcObjectHelper helper(aCOMObj); - if (!XPCConvert::NativeInterface2JSObject(ccx, &v, - getter_AddRefs(holder), - helper, &aIID, nsnull, - false, OBJ_IS_GLOBAL, &rv)) - return UnexpectedFailure(rv); - - NS_ASSERTION(NS_SUCCEEDED(rv) && holder, "Didn't wrap properly"); - } - - JSObject* globalJSObj = JSVAL_TO_OBJECT(v); - if (!globalJSObj) - return UnexpectedFailure(NS_ERROR_FAILURE); - - if (aFlags & nsIXPConnect::FLAG_SYSTEM_GLOBAL_OBJECT) - NS_ASSERTION(JS_IsSystemObject(aJSContext, globalJSObj), "huh?!"); - - // voodoo to fixup scoping and parenting... - - MOZ_ASSERT(!js::GetObjectParent(globalJSObj)); - - JSObject* oldGlobal = JS_GetGlobalObject(aJSContext); - if (!oldGlobal || oldGlobal == tempGlobal) - JS_SetGlobalObject(aJSContext, globalJSObj); - - if ((aFlags & nsIXPConnect::INIT_JS_STANDARD_CLASSES) && - !JS_InitStandardClasses(aJSContext, globalJSObj)) - return UnexpectedFailure(NS_ERROR_FAILURE); - - XPCWrappedNative* wrapper = - reinterpret_cast(holder.get()); - XPCWrappedNativeScope* scope = wrapper->GetScope(); - - if (!scope) - return UnexpectedFailure(NS_ERROR_FAILURE); - - // Note: This call cooperates with a call to wrapper->RefreshPrototype() - // in nsJSEnvironment::SetOuterObject in order to ensure that the - // prototype defines its constructor on the right global object. - if (wrapper->GetProto()->GetScriptableInfo()) - scope->RemoveWrappedNativeProtos(); - - NS_ASSERTION(scope->GetGlobalJSObject() == tempGlobal, "stealing scope!"); - - scope->SetGlobal(ccx, globalJSObj); - - JSObject* protoJSObject = wrapper->HasProto() ? - wrapper->GetProto()->GetJSProtoObject() : - globalJSObj; - if (protoJSObject) { - if (protoJSObject != globalJSObj) - JS_SetParent(aJSContext, protoJSObject, globalJSObj); - if (!JS_SplicePrototype(aJSContext, protoJSObject, scope->GetPrototypeJSObject())) - return UnexpectedFailure(NS_ERROR_FAILURE); - } - if (!(aFlags & nsIXPConnect::OMIT_COMPONENTS_OBJECT)) { // XPCCallContext gives us an active request needed to save/restore. - if (!nsXPCComponents::AttachNewComponentsObject(ccx, scope, globalJSObj)) + if (!nsXPCComponents::AttachNewComponentsObject(ccx, wrappedGlobal->GetScope(), global)) return UnexpectedFailure(NS_ERROR_FAILURE); if (XPCPerThreadData::IsMainThread(ccx)) { - if (!XPCNativeWrapper::AttachNewConstructorObject(ccx, globalJSObj)) + if (!XPCNativeWrapper::AttachNewConstructorObject(ccx, global)) return UnexpectedFailure(NS_ERROR_FAILURE); } } - NS_ADDREF(*_retval = holder); - + *_retval = wrappedGlobal.forget().get(); return NS_OK; } @@ -1370,8 +1329,7 @@ NativeInterface2JSObject(XPCLazyCallContext & lccx, nsresult rv; xpcObjectHelper helper(aCOMObj, aCache); if (!XPCConvert::NativeInterface2JSObject(lccx, aVal, aHolder, helper, aIID, - nsnull, aAllowWrapping, - OBJ_IS_NOT_GLOBAL, &rv)) + nsnull, aAllowWrapping, &rv)) return rv; #ifdef DEBUG @@ -2118,8 +2076,7 @@ nsXPConnect::GetWrappedNativePrototype(JSContext * aJSContext, XPCWrappedNative::GatherProtoScriptableCreateInfo(aClassInfo, sciProto); AutoMarkingWrappedNativeProtoPtr proto(ccx); - proto = XPCWrappedNativeProto::GetNewOrUsed(ccx, scope, aClassInfo, - &sciProto, OBJ_IS_NOT_GLOBAL); + proto = XPCWrappedNativeProto::GetNewOrUsed(ccx, scope, aClassInfo, &sciProto); if (!proto) return UnexpectedFailure(NS_ERROR_FAILURE); diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index 76a9d11a0d8f..8d17f18b41b3 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -48,6 +48,7 @@ #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" +#include "mozilla/Util.h" #include #include @@ -458,9 +459,6 @@ AddToCCKind(JSGCTraceKind kind) return kind == JSTRACE_OBJECT || kind == JSTRACE_XML || kind == JSTRACE_SCRIPT; } -const bool OBJ_IS_GLOBAL = true; -const bool OBJ_IS_NOT_GLOBAL = false; - class nsXPConnect : public nsIXPConnect, public nsIThreadObserver, public nsSupportsWeakReference, @@ -1501,7 +1499,7 @@ class XPCWrappedNativeScope : public PRCList public: static XPCWrappedNativeScope* - GetNewOrUsed(XPCCallContext& ccx, JSObject* aGlobal); + GetNewOrUsed(XPCCallContext& ccx, JSObject* aGlobal, nsISupports* aNative = nsnull); XPCJSRuntime* GetRuntime() const {return mRuntime;} @@ -1597,7 +1595,7 @@ public: IsDyingScope(XPCWrappedNativeScope *scope); void SetComponents(nsXPCComponents* aComponents); - void SetGlobal(XPCCallContext& ccx, JSObject* aGlobal); + void SetGlobal(XPCCallContext& ccx, JSObject* aGlobal, nsISupports* aNative); static void InitStatics() { gScopes = nsnull; gDyingScopes = nsnull; } @@ -1626,7 +1624,7 @@ public: } protected: - XPCWrappedNativeScope(XPCCallContext& ccx, JSObject* aGlobal); + XPCWrappedNativeScope(XPCCallContext& ccx, JSObject* aGlobal, nsISupports* aNative); virtual ~XPCWrappedNativeScope(); static void KillDyingScopes(); @@ -2021,6 +2019,7 @@ public: JSBool ClassInfoInterfacesOnly() GET_IT(CLASSINFO_INTERFACES_ONLY) JSBool AllowPropModsDuringResolve() GET_IT(ALLOW_PROP_MODS_DURING_RESOLVE) JSBool AllowPropModsToPrototype() GET_IT(ALLOW_PROP_MODS_TO_PROTOTYPE) + JSBool IsGlobalObject() GET_IT(IS_GLOBAL_OBJECT) JSBool DontReflectInterfaceNames() GET_IT(DONT_REFLECT_INTERFACE_NAMES) JSBool UseStubEqualityHook() GET_IT(USE_STUB_EQUALITY_HOOK) @@ -2074,7 +2073,7 @@ public: {char* name=(char*)mJSClass.base.name; mJSClass.base.name = nsnull; return name;} - void PopulateJSClass(JSBool isGlobal); + void PopulateJSClass(); void Mark() {mFlags.Mark();} void Unmark() {mFlags.Unmark();} @@ -2094,8 +2093,7 @@ class XPCNativeScriptableInfo { public: static XPCNativeScriptableInfo* - Construct(XPCCallContext& ccx, JSBool isGlobal, - const XPCNativeScriptableCreateInfo* sci); + Construct(XPCCallContext& ccx, const XPCNativeScriptableCreateInfo* sci); nsIXPCScriptable* GetCallback() const {return mCallback;} @@ -2206,8 +2204,8 @@ public: XPCWrappedNativeScope* scope, nsIClassInfo* classInfo, const XPCNativeScriptableCreateInfo* scriptableCreateInfo, - JSBool isGlobal, - QITableEntry* offsets = UNKNOWN_OFFSETS); + QITableEntry* offsets = UNKNOWN_OFFSETS, + bool callPostCreatePrototype = true); XPCWrappedNativeScope* GetScope() const {return mScope;} @@ -2286,6 +2284,7 @@ public: void SetScriptableInfo(XPCNativeScriptableInfo* si) {NS_ASSERTION(!mScriptableInfo, "leak here!"); mScriptableInfo = si;} + bool CallPostCreatePrototype(XPCCallContext& ccx); void JSProtoObjectFinalized(JSContext *cx, JSObject *obj); void SystemIsBeingShutDown(); @@ -2334,8 +2333,9 @@ protected: XPCNativeSet* Set, QITableEntry* offsets); - JSBool Init(XPCCallContext& ccx, JSBool isGlobal, - const XPCNativeScriptableCreateInfo* scriptableCreateInfo); + JSBool Init(XPCCallContext& ccx, + const XPCNativeScriptableCreateInfo* scriptableCreateInfo, + bool callPostCreatePrototype); private: #if defined(DEBUG_xpc_hacker) || defined(DEBUG) @@ -2583,12 +2583,16 @@ public: GetRuntime() const {XPCWrappedNativeScope* scope = GetScope(); return scope ? scope->GetRuntime() : nsnull;} + static nsresult + WrapNewGlobal(XPCCallContext &ccx, xpcObjectHelper &nativeHelper, + nsIPrincipal *principal, bool initStandardClasses, + XPCWrappedNative **wrappedGlobal); + static nsresult GetNewOrUsed(XPCCallContext& ccx, xpcObjectHelper& helper, XPCWrappedNativeScope* Scope, XPCNativeInterface* Interface, - JSBool isGlobal, XPCWrappedNative** wrapper); static nsresult @@ -2681,7 +2685,8 @@ public: JS_CALL_OBJECT_TRACER(trc, wrapper, "XPCWrappedNative::mWrapper"); if (mScriptableInfo && (mScriptableInfo->GetJSClass()->flags & JSCLASS_XPCONNECT_GLOBAL)) - GetScope()->TraceDOMPrototypes(trc); + TraceXPCGlobal(trc, mFlatJSObject); + } inline void AutoTrace(JSTracer* trc) @@ -2789,8 +2794,7 @@ private: private: - JSBool Init(XPCCallContext& ccx, JSObject* parent, JSBool isGlobal, - const XPCNativeScriptableCreateInfo* sci); + JSBool Init(XPCCallContext& ccx, JSObject* parent, const XPCNativeScriptableCreateInfo* sci); JSBool Init(XPCCallContext &ccx, JSObject *existingJSObject); JSBool FinishInit(XPCCallContext &ccx); @@ -3179,6 +3183,27 @@ public: return mXPCClassInfo.forget(); } + // We assert that we can reach an nsIXPCScriptable somehow. + PRUint32 GetScriptableFlags() + { + // Try getting an nsXPCClassInfo - this handles DOM scriptable helpers. + nsCOMPtr sinfo = GetXPCClassInfo(); + + // If that didn't work, try just QI-ing. This handles BackstagePass. + if (!sinfo) + sinfo = do_QueryInterface(GetCanonical()); + + // We should have something by now. + MOZ_ASSERT(sinfo); + + // Grab the flags. This should not fail. + PRUint32 flags; + mozilla::DebugOnly rv = sinfo->GetScriptableFlags(&flags); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + return flags; + } + nsWrapperCache *GetWrapperCache() { return mCache; @@ -3255,7 +3280,6 @@ public: * will be QI'ed to get the cache) * @param allowNativeWrapper if true, this method may wrap the resulting * JSObject in an XPCNativeWrapper and return that, as needed. - * @param isGlobal * @param pErr [out] relevant error code, if any. * @param src_is_identity optional performance hint. Set to true only * if src is the identity pointer. @@ -3267,12 +3291,11 @@ public: const nsID* iid, XPCNativeInterface** Interface, bool allowNativeWrapper, - bool isGlobal, nsresult* pErr) { XPCLazyCallContext lccx(ccx); return NativeInterface2JSObject(lccx, d, dest, aHelper, iid, Interface, - allowNativeWrapper, isGlobal, pErr); + allowNativeWrapper, pErr); } static JSBool NativeInterface2JSObject(XPCLazyCallContext& lccx, jsval* d, @@ -3281,7 +3304,6 @@ public: const nsID* iid, XPCNativeInterface** Interface, bool allowNativeWrapper, - bool isGlobal, nsresult* pErr); static JSBool GetNativeInterfaceFromJSObject(XPCCallContext& ccx, @@ -3480,10 +3502,10 @@ private: * member (as a hidden implementaion detail) to which they delegate many calls. */ -extern void xpc_InitJSxIDClassObjects(); +// Initialization is done on demand, and calling the destructor below is always +// safe. extern void xpc_DestroyJSxIDClassObjects(); - class nsJSID : public nsIJSID { public: diff --git a/layout/base/FramePropertyTable.cpp b/layout/base/FramePropertyTable.cpp index dbefe69d8a4d..9e8012db62a8 100644 --- a/layout/base/FramePropertyTable.cpp +++ b/layout/base/FramePropertyTable.cpp @@ -258,4 +258,18 @@ FramePropertyTable::DeleteAll() mEntries.EnumerateEntries(DeleteEnumerator, nsnull); } +size_t +FramePropertyTable::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const +{ + return mEntries.SizeOfExcludingThis(SizeOfPropertyTableEntryExcludingThis, + aMallocSizeOf); +} + +/* static */ size_t +FramePropertyTable::SizeOfPropertyTableEntryExcludingThis(Entry* aEntry, + nsMallocSizeOfFun aMallocSizeOf, void *) +{ + return aEntry->mProp.SizeOfExcludingThis(aMallocSizeOf); +} + } diff --git a/layout/base/FramePropertyTable.h b/layout/base/FramePropertyTable.h index 89ab723078a1..71f9f8cbbcf3 100644 --- a/layout/base/FramePropertyTable.h +++ b/layout/base/FramePropertyTable.h @@ -154,6 +154,8 @@ public: */ void DeleteAll(); + size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const; + protected: /** * Stores a property descriptor/value pair. It can also be used to @@ -179,6 +181,20 @@ protected: } } + size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) { + size_t n = 0; + // We don't need to measure mProperty because it always points to static + // memory. As for mValue: if it's a single value we can't measure it, + // because the type is opaque; if it's an array, we measure the array + // storage, but we can't measure the individual values, again because + // their types are opaque. + if (IsArray()) { + nsTArray* array = ToArray(); + n += array->SizeOfExcludingThis(aMallocSizeOf); + } + return n; + } + const FramePropertyDescriptor* mProperty; void* mValue; }; @@ -217,6 +233,9 @@ protected: static void DeleteAllForEntry(Entry* aEntry); static PLDHashOperator DeleteEnumerator(Entry* aEntry, void* aArg); + static size_t SizeOfPropertyTableEntryExcludingThis(Entry* aEntry, + nsMallocSizeOfFun aMallocSizeOf, void *); + nsTHashtable mEntries; nsIFrame* mLastFrame; Entry* mLastEntry; diff --git a/layout/base/nsBidiPresUtils.cpp b/layout/base/nsBidiPresUtils.cpp index 28b44382a32a..104b8888ae59 100644 --- a/layout/base/nsBidiPresUtils.cpp +++ b/layout/base/nsBidiPresUtils.cpp @@ -644,7 +644,7 @@ nsBidiPresUtils::Resolve(nsBlockFrame* aBlockFrame) for (nsBlockFrame* block = aBlockFrame; block; block = static_cast(block->GetNextContinuation())) { block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); - nsBlockInFlowLineIterator lineIter(block, block->begin_lines(), false); + nsBlockInFlowLineIterator lineIter(block, block->begin_lines()); bpd.mPrevFrame = nsnull; bpd.GetSubParagraph()->mPrevFrame = nsnull; TraverseFrames(aBlockFrame, &lineIter, block->GetFirstPrincipalChild(), &bpd); diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 425c67fed243..1a46283afac5 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -1048,14 +1048,14 @@ nsLayoutUtils::GetEventCoordinatesRelativeTo(const nsEvent* aEvent, nsIView* view = aFrame->GetView(); if (view) { - nsIWidget* fwidget = view->GetWidget(); - if (fwidget && fwidget == GUIEvent->widget) { + nsIWidget* frameWidget = view->GetWidget(); + if (frameWidget && frameWidget == GUIEvent->widget) { // Special case this cause it happens a lot. // This also fixes bug 664707, events in the extra-special case of select // dropdown popups that are transformed. nsPresContext* presContext = aFrame->PresContext(); - nsPoint pt(presContext->DevPixelsToAppUnits(GUIEvent->refPoint.x), - presContext->DevPixelsToAppUnits(GUIEvent->refPoint.y)); + nsPoint pt(presContext->DevPixelsToAppUnits(aPoint.x), + presContext->DevPixelsToAppUnits(aPoint.y)); return pt - view->ViewToWidgetOffset(); } } diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index a89e07f38d1f..343debcaa4c6 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -2357,6 +2357,16 @@ nsPresContext::CheckForInterrupt(nsIFrame* aFrame) return mHasPendingInterrupt; } +size_t +nsPresContext::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const +{ + return mPropertyTable.SizeOfExcludingThis(aMallocSizeOf); + mLangGroupFontPrefs.SizeOfExcludingThis(aMallocSizeOf); + + // Measurement of other members may be added later if DMD finds it is + // worthwhile. +} + bool nsPresContext::IsRootContentDocument() { @@ -2786,3 +2796,20 @@ nsRootPresContext::FlushWillPaintObservers() observers[i]->Run(); } } + +size_t +nsRootPresContext::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const +{ + return nsPresContext::SizeOfExcludingThis(aMallocSizeOf); + + // Measurement of the following members may be added later if DMD finds it is + // worthwhile: + // - mNotifyDidPaintTimer + // - mRegisteredPlugins + // - mWillPaintObservers + // - mWillPaintFallbackEvent + // + // The following member are not measured: + // - mUpdatePluginGeometryForFrame, because it is non-owning +} + diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h index cd2ac2835c22..e367c5f95bc4 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -968,22 +968,8 @@ public: bool MayHaveFixedBackgroundFrames() { return mMayHaveFixedBackgroundFrames; } void SetHasFixedBackgroundFrame() { mMayHaveFixedBackgroundFrames = true; } - virtual NS_MUST_OVERRIDE size_t - SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const { - size_t n = 0; - LangGroupFontPrefs *langGroupfontPrefs = mLangGroupFontPrefs.mNext; - while (langGroupfontPrefs) { - // XXX this doesn't include allocations made by the nsFont members - n += sizeof(LangGroupFontPrefs); - langGroupfontPrefs = langGroupfontPrefs->mNext; - } - return n; - - // Measurement of other members may be added later if DMD finds it is - // worthwhile. - } - virtual NS_MUST_OVERRIDE size_t - SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const { + virtual size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const; + virtual size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const { return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } @@ -1037,6 +1023,22 @@ protected: NS_FONT_STRETCH_NORMAL, 0, 0) {} + size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const { + size_t n = 0; + LangGroupFontPrefs *curr = mNext; + while (curr) { + n += aMallocSizeOf(curr); + + // Measurement of the following members may be added later if DMD finds + // it is worthwhile: + // - mLangGroup + // - mDefault*Font + + curr = curr->mNext; + } + return n; + } + nsCOMPtr mLangGroup; nscoord mMinimumFontSize; nsFont mDefaultVariableFont; @@ -1375,24 +1377,7 @@ public: */ void FlushWillPaintObservers(); - virtual NS_MUST_OVERRIDE size_t - SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const MOZ_OVERRIDE { - return nsPresContext::SizeOfExcludingThis(aMallocSizeOf); - - // Measurement of the following members may be added later if DMD finds it is - // worthwhile: - // - mNotifyDidPaintTimer - // - mRegisteredPlugins - // - mWillPaintObservers - // - mWillPaintFallbackEvent - // - // The following member are not measured: - // - mUpdatePluginGeometryForFrame, because it is non-owning - } - virtual NS_MUST_OVERRIDE size_t - SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const MOZ_OVERRIDE { - return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); - } + virtual size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const MOZ_OVERRIDE; protected: class RunWillPaintObservers : public nsRunnable { diff --git a/layout/base/tests/Makefile.in b/layout/base/tests/Makefile.in index 4f4ed0fd20b3..e667df82a55a 100644 --- a/layout/base/tests/Makefile.in +++ b/layout/base/tests/Makefile.in @@ -169,6 +169,7 @@ _TEST_FILES = \ test_bug588174.html \ test_bug607529.html \ file_bug607529.html \ + test_bug667512.html \ test_bug677878.html \ test_bug696020.html \ test_flush_on_paint.html \ diff --git a/layout/base/tests/test_bug667512.html b/layout/base/tests/test_bug667512.html new file mode 100644 index 000000000000..600b6d0f6b4c --- /dev/null +++ b/layout/base/tests/test_bug667512.html @@ -0,0 +1,41 @@ + + + + + Test for Bug 667512 + + + + + + +

+ + + + \ No newline at end of file diff --git a/layout/forms/nsComboboxControlFrame.cpp b/layout/forms/nsComboboxControlFrame.cpp index 3f605eae2f63..5a61ab784f48 100644 --- a/layout/forms/nsComboboxControlFrame.cpp +++ b/layout/forms/nsComboboxControlFrame.cpp @@ -1324,7 +1324,7 @@ nsComboboxControlFrame::DestroyFrom(nsIFrame* aDestructRoot) nsBlockFrame::DestroyFrom(aDestructRoot); } -nsFrameList +const nsFrameList& nsComboboxControlFrame::GetChildList(ChildListID aListID) const { if (kSelectPopupList == aListID) { diff --git a/layout/forms/nsComboboxControlFrame.h b/layout/forms/nsComboboxControlFrame.h index ea412a8a4fc1..91cd8b6a3d22 100644 --- a/layout/forms/nsComboboxControlFrame.h +++ b/layout/forms/nsComboboxControlFrame.h @@ -141,7 +141,7 @@ public: virtual void DestroyFrom(nsIFrame* aDestructRoot); NS_IMETHOD SetInitialChildList(ChildListID aListID, nsFrameList& aChildList); - virtual nsFrameList GetChildList(ChildListID aListID) const; + virtual const nsFrameList& GetChildList(ChildListID aListID) const; virtual void GetChildLists(nsTArray* aLists) const; virtual nsIFrame* GetContentInsertionFrame(); diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp index c6a384e6c1b3..6b0f5812a1e0 100644 --- a/layout/generic/nsBlockFrame.cpp +++ b/layout/generic/nsBlockFrame.cpp @@ -285,6 +285,11 @@ NS_DECLARE_FRAME_PROPERTY(LineCursorProperty, nsnull) NS_DECLARE_FRAME_PROPERTY(OverflowLinesProperty, DestroyOverflowLines) NS_DECLARE_FRAME_PROPERTY(OverflowOutOfFlowsProperty, nsContainerFrame::DestroyFrameList) +NS_DECLARE_FRAME_PROPERTY(PushedFloatProperty, + nsContainerFrame::DestroyFrameList) +NS_DECLARE_FRAME_PROPERTY(OutsideBulletProperty, + nsContainerFrame::DestroyFrameList) +NS_DECLARE_FRAME_PROPERTY(InsideBulletProperty, nsnull) //---------------------------------------------------------------------- @@ -308,12 +313,6 @@ void nsBlockFrame::DestroyFrom(nsIFrame* aDestructRoot) { DestroyAbsoluteFrames(aDestructRoot); - // Outside bullets are not in our child-list so check for them here - // and delete them when present. - if (mBullet && HaveOutsideBullet()) { - mBullet->DestroyFrom(aDestructRoot); - mBullet = nsnull; - } mFloats.DestroyFramesFrom(aDestructRoot); @@ -329,9 +328,10 @@ nsBlockFrame::DestroyFrom(nsIFrame* aDestructRoot) } // destroy overflow lines now - nsLineList* overflowLines = RemoveOverflowLines(); + FrameLines* overflowLines = RemoveOverflowLines(); if (overflowLines) { - nsLineBox::DeleteLineList(presContext, *overflowLines, aDestructRoot); + nsLineBox::DeleteLineList(presContext, overflowLines->mLines, + aDestructRoot); delete overflowLines; } @@ -454,12 +454,12 @@ nsBlockFrame::List(FILE* out, PRInt32 aIndent) const } // Output the overflow lines. - const nsLineList* overflowLines = GetOverflowLines(); - if (overflowLines && !overflowLines->empty()) { + const FrameLines* overflowLines = GetOverflowLines(); + if (overflowLines && !overflowLines->mLines.empty()) { IndentBy(out, aIndent); fputs("Overflow-lines<\n", out); - const_line_iterator line = overflowLines->begin(), - line_end = overflowLines->end(); + const_line_iterator line = overflowLines->mLines.begin(), + line_end = overflowLines->mLines.end(); for ( ; line != line_end; ++line) { line->List(out, aIndent + 1); } @@ -574,19 +574,15 @@ nsBlockFrame::GetCaretBaseline() const ///////////////////////////////////////////////////////////////////////////// // Child frame enumeration -nsFrameList +const nsFrameList& nsBlockFrame::GetChildList(ChildListID aListID) const { switch (aListID) { case kPrincipalList: return mFrames; case kOverflowList: { - // XXXbz once we start using nsFrameList for our overflow list, we - // could switch GetChildList to returning a |const nsFrameList&|. - nsLineList* overflowLines = GetOverflowLines(); - return overflowLines ? nsFrameList(overflowLines->front()->mFirstChild, - overflowLines->back()->LastChild()) - : nsFrameList::EmptyList(); + FrameLines* overflowLines = GetOverflowLines(); + return overflowLines ? overflowLines->mFrames : nsFrameList::EmptyList(); } case kFloatList: return mFloats; @@ -598,9 +594,10 @@ nsBlockFrame::GetChildList(ChildListID aListID) const const nsFrameList* list = GetPushedFloats(); return list ? *list : nsFrameList::EmptyList(); } - case kBulletList: - return HaveOutsideBullet() ? nsFrameList(mBullet, mBullet) - : nsFrameList::EmptyList(); + case kBulletList: { + const nsFrameList* list = GetOutsideBulletList(); + return list ? *list : nsFrameList::EmptyList(); + } default: return nsContainerFrame::GetChildList(aListID); } @@ -610,20 +607,18 @@ void nsBlockFrame::GetChildLists(nsTArray* aLists) const { nsContainerFrame::GetChildLists(aLists); - nsLineList* overflowLines = GetOverflowLines(); - if (overflowLines && overflowLines->front()->mFirstChild) { - nsFrameList overflowList(overflowLines->front()->mFirstChild, - overflowLines->back()->LastChild()); - overflowList.AppendIfNonempty(aLists, kOverflowList); + FrameLines* overflowLines = GetOverflowLines(); + if (overflowLines) { + overflowLines->mFrames.AppendIfNonempty(aLists, kOverflowList); } const nsFrameList* list = GetOverflowOutOfFlows(); if (list) { list->AppendIfNonempty(aLists, kOverflowOutOfFlowList); } mFloats.AppendIfNonempty(aLists, kFloatList); - if (HaveOutsideBullet()) { - nsFrameList bullet(mBullet, mBullet); - bullet.AppendIfNonempty(aLists, kBulletList); + list = GetOutsideBulletList(); + if (list) { + list->AppendIfNonempty(aLists, kBulletList); } list = GetPushedFloats(); if (list) { @@ -637,8 +632,9 @@ nsBlockFrame::IsFloatContainingBlock() const return true; } -static void ReparentFrame(nsIFrame* aFrame, nsIFrame* aOldParent, - nsIFrame* aNewParent) { +static void +ReparentFrame(nsIFrame* aFrame, nsIFrame* aOldParent, nsIFrame* aNewParent) +{ NS_ASSERTION(aOldParent == aFrame->GetParent(), "Parent not consistent with expectations"); @@ -650,8 +646,37 @@ static void ReparentFrame(nsIFrame* aFrame, nsIFrame* aOldParent, aOldParent, aNewParent); } -////////////////////////////////////////////////////////////////////// -// Frame structure methods +static void +ReparentFrames(nsFrameList& aFrameList, nsIFrame* aOldParent, + nsIFrame* aNewParent) +{ + for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { + ReparentFrame(e.get(), aOldParent, aNewParent); + } +} + +/** + * Remove the first line from aFromLines and adjust the associated frame list + * aFromFrames accordingly. The removed line is assigned to *aOutLine and + * a frame list with its frames is assigned to *aOutFrames, i.e. the frames + * that were extracted from the head of aFromFrames. + * aFromLines must contain at least one line, the line may be empty. + * @return true if aFromLines becomes empty + */ +static bool +RemoveFirstLine(nsLineList& aFromLines, nsFrameList& aFromFrames, + nsLineBox** aOutLine, nsFrameList* aOutFrames) +{ + nsLineList_iterator removedLine = aFromLines.begin(); + *aOutLine = removedLine; + nsLineList_iterator next = aFromLines.erase(removedLine); + bool isLastLine = next == aFromLines.end(); + nsIFrame* lastFrame = isLastLine ? aFromFrames.LastChild() + : next->mFirstChild->GetPrevSibling(); + nsFrameList::FrameLinkEnumerator linkToBreak(aFromFrames, lastFrame); + *aOutFrames = aFromFrames.ExtractHead(linkToBreak); + return isLastLine; +} ////////////////////////////////////////////////////////////////////// // Reflow methods @@ -1059,7 +1084,7 @@ nsBlockFrame::Reflow(nsPresContext* aPresContext, } if (!NS_FRAME_IS_FULLY_COMPLETE(state.mReflowStatus)) { - if (GetOverflowLines() || GetPushedFloats()) { + if (HasOverflowLines() || HasPushedFloats()) { state.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW; } @@ -1082,7 +1107,7 @@ nsBlockFrame::Reflow(nsPresContext* aPresContext, // rare case: an empty first line followed by a second line that // contains a block (example:
  • \n

    ... ). This is where // the second case can happen. - if (mBullet && HaveOutsideBullet() && !mLines.empty() && + if (HasOutsideBullet() && !mLines.empty() && (mLines.front()->IsBlock() || (0 == mLines.front()->mBounds.height && mLines.front() != mLines.back() && @@ -1094,7 +1119,8 @@ nsBlockFrame::Reflow(nsPresContext* aPresContext, bool havePosition = nsLayoutUtils::GetFirstLinePosition(this, &position); nscoord lineTop = havePosition ? position.mTop : reflowState->mComputedBorderPadding.top; - ReflowBullet(state, metrics, lineTop); + nsIFrame* bullet = GetOutsideBullet(); + ReflowBullet(bullet, state, metrics, lineTop); NS_ASSERTION(!BulletIsEmpty() || metrics.height == 0, "empty bullet took up space"); @@ -1105,9 +1131,9 @@ nsBlockFrame::Reflow(nsPresContext* aPresContext, // bullets that are placed next to a child block (bug 92896) // Tall bullets won't look particularly nice here... - nsRect bbox = mBullet->GetRect(); + nsRect bbox = bullet->GetRect(); bbox.y = position.mBaseline - metrics.ascent; - mBullet->SetRect(bbox); + bullet->SetRect(bbox); } // Otherwise just leave the bullet where it is, up against our top padding. } @@ -1463,13 +1489,14 @@ nsBlockFrame::ComputeOverflowAreas(const nsRect& aBounds, areas.UnionWith(line->GetOverflowAreas()); } - // Factor the bullet in; normally the bullet will be factored into + // Factor an outside bullet in; normally the bullet will be factored into // the line-box's overflow areas. However, if the line is a block // line then it won't; if there are no lines, it won't. So just // factor it in anyway (it can't hurt if it was already done). // XXXldb Can we just fix GetOverflowArea instead? - if (mBullet) { - areas.UnionAllWith(mBullet->GetRect()); + nsIFrame* outsideBullet = GetOutsideBullet(); + if (outsideBullet) { + areas.UnionAllWith(outsideBullet->GetRect()); } // Factor in the bottom edge of the children. Child frames will be added @@ -2190,7 +2217,7 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) line_iterator lineIter = this->end_lines(); if (lineIter != this->begin_lines()) { lineIter--; // I have lines; step back from dummy iterator to last line. - nsBlockInFlowLineIterator bifLineIter(this, lineIter, false); + nsBlockInFlowLineIterator bifLineIter(this, lineIter); // Check for next-in-flow-chain's first line. // (First, see if there is such a line, and second, see if it's clean) @@ -2212,92 +2239,59 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) if (!skipPull && aState.mNextInFlow) { // Pull data from a next-in-flow if there's still room for more // content here. - while (keepGoing && (nsnull != aState.mNextInFlow)) { + while (keepGoing && aState.mNextInFlow) { // Grab first line from our next-in-flow nsBlockFrame* nextInFlow = aState.mNextInFlow; - line_iterator nifLine = nextInFlow->begin_lines(); - nsLineBox *toMove; - bool toMoveIsOverflowLine; - if (nifLine != nextInFlow->end_lines()) { - toMove = nifLine; - nextInFlow->mLines.erase(nifLine); - toMoveIsOverflowLine = false; + nsLineBox* pulledLine; + nsFrameList pulledFrames; + bool isOverflowLine = false; + if (!nextInFlow->mLines.empty()) { + RemoveFirstLine(nextInFlow->mLines, nextInFlow->mFrames, + &pulledLine, &pulledFrames); } else { // Grab an overflow line if there are any - nsLineList* overflowLines = nextInFlow->GetOverflowLines(); + FrameLines* overflowLines = nextInFlow->GetOverflowLines(); if (!overflowLines) { aState.mNextInFlow = static_cast(nextInFlow->GetNextInFlow()); continue; } - nifLine = overflowLines->begin(); - NS_ASSERTION(nifLine != overflowLines->end(), - "Stored overflow line list should not be empty"); - toMove = nifLine; - nextInFlow->RemoveOverflowLines(); - nifLine = overflowLines->erase(nifLine); - if (nifLine != overflowLines->end()) { - // We need to this remove-and-put-back dance because we want - // to avoid making the overflow line list empty while it's - // stored in the property (because the property has the - // invariant that the list is never empty). - nextInFlow->SetOverflowLines(overflowLines); - } else { - delete overflowLines; + bool last = + RemoveFirstLine(overflowLines->mLines, overflowLines->mFrames, + &pulledLine, &pulledFrames); + if (last) { + nextInFlow->DestroyOverflowLines(); } - toMoveIsOverflowLine = true; + isOverflowLine = true; } - if (0 == toMove->GetChildCount()) { + if (pulledFrames.IsEmpty()) { // The line is empty. Try the next one. - NS_ASSERTION(nsnull == toMove->mFirstChild, "bad empty line"); - aState.FreeLineBox(toMove); + NS_ASSERTION(pulledLine->GetChildCount() == 0 && + !pulledLine->mFirstChild, "bad empty line"); + aState.FreeLineBox(pulledLine); continue; } - // XXX move to a subroutine: run-in, overflow, pullframe and this do this - // Make the children in the line ours. - nsIFrame* frame = toMove->mFirstChild; - nsIFrame* lastFrame = nsnull; - PRInt32 n = toMove->GetChildCount(); - while (--n >= 0) { - ReparentFrame(frame, nextInFlow, this); - lastFrame = frame; - frame = frame->GetNextSibling(); - } - - NS_ASSERTION(lastFrame == toMove->LastChild(), "Unexpected lastFrame"); + ReparentFrames(pulledFrames, nextInFlow, this); + NS_ASSERTION(pulledFrames.LastChild() == pulledLine->LastChild(), + "Unexpected last frame"); NS_ASSERTION(aState.mPrevChild || mLines.empty(), "should have a prevchild here"); - NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(), "Incorrect aState.mPrevChild before inserting line at end"); - // Shift toMove's frames into our mFrames list. - if (toMoveIsOverflowLine) { - // Pulling from an overflow list - // XXXbz If we switch overflow lines to nsFrameList, we should - // change this SetNextSibling call. - lastFrame->SetNextSibling(nsnull); - } else { - // Pulling from nextInFlow->mFrames - nsFrameList::FrameLinkEnumerator linkToBreak(nextInFlow->mFrames, lastFrame); - nextInFlow->mFrames.ExtractHead(linkToBreak); - } - nsFrameList newFrames(toMove->mFirstChild, lastFrame); - mFrames.AppendFrames(nsnull, newFrames); + // Shift pulledLine's frames into our mFrames list. + mFrames.AppendFrames(nsnull, pulledFrames); // Add line to our line list, and set its last child as our new prev-child - line = mLines.before_insert(end_lines(), toMove); - aState.mPrevChild = lastFrame; - - NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(), - "Incorrect aState.mPrevChild after inserting line at end"); + line = mLines.before_insert(end_lines(), pulledLine); + aState.mPrevChild = mFrames.LastChild(); // Reparent floats whose placeholders are in the line. - ReparentFloats(toMove->mFirstChild, nextInFlow, toMoveIsOverflowLine, true); + ReparentFloats(pulledLine->mFirstChild, nextInFlow, isOverflowLine, true); - DumpLine(aState, toMove, deltaY, 0); + DumpLine(aState, pulledLine, deltaY, 0); #ifdef DEBUG AutoNoisyIndenter indent2(gNoisyReflow); #endif @@ -2350,9 +2344,10 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) } // Handle an odd-ball case: a list-item with no lines - if (mBullet && HaveOutsideBullet() && mLines.empty()) { + if (HasOutsideBullet() && mLines.empty()) { nsHTMLReflowMetrics metrics; - ReflowBullet(aState, metrics, + nsIFrame* bullet = GetOutsideBullet(); + ReflowBullet(bullet, aState, metrics, aState.mReflowState.mComputedBorderPadding.top); NS_ASSERTION(!BulletIsEmpty() || metrics.height == 0, "empty bullet took up space"); @@ -2362,7 +2357,7 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) // we end up with *some* height. if (metrics.ascent == nsHTMLReflowMetrics::ASK_FOR_BASELINE && - !nsLayoutUtils::GetFirstLineBaseline(mBullet, &metrics.ascent)) { + !nsLayoutUtils::GetFirstLineBaseline(bullet, &metrics.ascent)) { metrics.ascent = metrics.height; } @@ -2380,7 +2375,7 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) nscoord offset = minAscent - metrics.ascent; if (offset > 0) { - mBullet->SetRect(mBullet->GetRect() + nsPoint(0, offset)); + bullet->SetRect(bullet->GetRect() + nsPoint(0, offset)); } } } @@ -2594,7 +2589,7 @@ nsBlockFrame::PullFrame(nsBlockReflowState& aState, { // First check our remaining lines. if (end_lines() != aLine.next()) { - return PullFrameFrom(aState, aLine, this, false, aLine.next()); + return PullFrameFrom(aState, aLine, this, false, mFrames, aLine.next()); } NS_ASSERTION(!GetOverflowLines(), @@ -2606,13 +2601,15 @@ nsBlockFrame::PullFrame(nsBlockReflowState& aState, // first normal lines, then overflow lines if (!nextInFlow->mLines.empty()) { return PullFrameFrom(aState, aLine, nextInFlow, false, + nextInFlow->mFrames, nextInFlow->mLines.begin()); } - nsLineList* overflowLines = nextInFlow->GetOverflowLines(); + FrameLines* overflowLines = nextInFlow->GetOverflowLines(); if (overflowLines) { return PullFrameFrom(aState, aLine, nextInFlow, true, - overflowLines->begin()); + overflowLines->mFrames, + overflowLines->mLines.begin()); } nextInFlow = static_cast(nextInFlow->GetNextInFlow()); @@ -2627,6 +2624,7 @@ nsBlockFrame::PullFrameFrom(nsBlockReflowState& aState, nsLineBox* aLine, nsBlockFrame* aFromContainer, bool aFromOverflowLine, + nsFrameList& aFromFrameList, nsLineList::iterator aFromLine) { nsLineBox* fromLine = aFromLine; @@ -2653,16 +2651,12 @@ nsBlockFrame::PullFrameFrom(nsBlockReflowState& aState, // The frame is being pulled from a next-in-flow; therefore we // need to add it to our sibling list. if (NS_LIKELY(!aFromOverflowLine)) { + NS_ASSERTION(&aFromFrameList == &aFromContainer->mFrames, + "must be normal flow if not overflow line"); NS_ASSERTION(aFromLine == aFromContainer->mLines.begin(), "should only pull from first line"); - // Pulling from the next-in-flow's normal line list - aFromContainer->mFrames.RemoveFrame(frame); - } else { - // Pulling from the next-in-flow's overflow list - // XXXbz If we switch overflow lines to nsFrameList, we should - // change this SetNextSibling call. - frame->SetNextSibling(nsnull); } + aFromFrameList.RemoveFrame(frame); // When pushing and pulling frames we need to check for whether any // views need to be reparented @@ -2692,9 +2686,10 @@ nsBlockFrame::PullFrameFrom(nsBlockReflowState& aState, // XXX WHY do we invalidate the bounds AND the combined area? doesn't // the combined area always enclose the bounds? Invalidate(fromLine->mBounds); - nsLineList* fromLineList = aFromOverflowLine - ? aFromContainer->RemoveOverflowLines() - : &aFromContainer->mLines; + FrameLines* overflowLines = + aFromOverflowLine ? aFromContainer->RemoveOverflowLines() : nsnull; + nsLineList* fromLineList = + aFromOverflowLine ? &overflowLines->mLines : &aFromContainer->mLines; if (aFromLine.next() != fromLineList->end()) aFromLine.next()->MarkPreviousMarginDirty(); @@ -2706,9 +2701,9 @@ nsBlockFrame::PullFrameFrom(nsBlockReflowState& aState, // Put any remaining overflow lines back. if (aFromOverflowLine) { if (!fromLineList->empty()) { - aFromContainer->SetOverflowLines(fromLineList); + aFromContainer->SetOverflowLines(overflowLines); } else { - delete fromLineList; + delete overflowLines; // Now any iterators into fromLineList are invalid (but // aFromLine already was invalidated above) } @@ -2866,7 +2861,7 @@ nsBlockFrame::IsSelfEmpty() return false; } - if (HaveOutsideBullet() && !BulletIsEmpty()) { + if (HasOutsideBullet() && !BulletIsEmpty()) { return false; } @@ -3780,15 +3775,15 @@ nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState, if (aLineLayout.GetDirtyNextLine()) { // aLine may have been pushed to the overflow lines. - nsLineList* overflowLines = GetOverflowLines(); + FrameLines* overflowLines = GetOverflowLines(); // We can't just compare iterators front() to aLine here, since they may be in // different lists. bool pushedToOverflowLines = overflowLines && - overflowLines->front() == aLine.get(); + overflowLines->mLines.front() == aLine.get(); if (pushedToOverflowLines) { // aLine is stale, it's associated with the main line list but it should // be associated with the overflow line list now - aLine = overflowLines->begin(); + aLine = overflowLines->mLines.begin(); } nsBlockInFlowLineIterator iter(this, aLine, pushedToOverflowLines); if (iter.Next() && iter.GetLine()->IsInline()) { @@ -4192,17 +4187,18 @@ nsBlockFrame::PlaceLine(nsBlockReflowState& aState, // first or second line. It's only placed on the second line in a // rare case: when the first line is empty. bool addedBullet = false; - if (mBullet && HaveOutsideBullet() && + if (HasOutsideBullet() && ((aLine == mLines.front() && (!aLineLayout.IsZeroHeight() || (aLine == mLines.back()))) || (mLines.front() != mLines.back() && 0 == mLines.front()->mBounds.height && aLine == mLines.begin().next()))) { nsHTMLReflowMetrics metrics; - ReflowBullet(aState, metrics, aState.mY); + nsIFrame* bullet = GetOutsideBullet(); + ReflowBullet(bullet, aState, metrics, aState.mY); NS_ASSERTION(!BulletIsEmpty() || metrics.height == 0, "empty bullet took up space"); - aLineLayout.AddBulletFrame(mBullet, metrics); + aLineLayout.AddBulletFrame(bullet, metrics); addedBullet = true; } aLineLayout.VerticalAlignLine(); @@ -4285,7 +4281,7 @@ nsBlockFrame::PlaceLine(nsBlockReflowState& aState, aLineLayout.RelativePositionFrames(overflowAreas); aLine->SetOverflowAreas(overflowAreas); if (addedBullet) { - aLineLayout.RemoveBulletFrame(mBullet); + aLineLayout.RemoveBulletFrame(GetOutsideBullet()); } // Inline lines do not have margins themselves; however they are @@ -4386,6 +4382,10 @@ void nsBlockFrame::PushLines(nsBlockReflowState& aState, nsLineList::iterator aLineBefore) { + // NOTE: aLineBefore is always a normal line, not an overflow line. + // The following expression will assert otherwise. + DebugOnly check = aLineBefore == mLines.begin(); + nsLineList::iterator overBegin(aLineBefore.next()); // PushTruncatedPlaceholderLine sometimes pushes the first line. Ugh. @@ -4407,32 +4407,27 @@ nsBlockFrame::PushLines(nsBlockReflowState& aState, // the height of some child block to grow which creates additional // overflowing content. In such cases we must prepend the new // overflow to the existing overflow. - nsLineList* overflowLines = RemoveOverflowLines(); + FrameLines* overflowLines = RemoveOverflowLines(); if (!overflowLines) { // XXXldb use presshell arena! - overflowLines = new nsLineList(); + overflowLines = new FrameLines(); } if (overflowLines) { - // First, remove the frames we're pushing from mFrames - nsIFrame* oldLastChild = mFrames.LastChild(); + nsIFrame* lineBeforeLastFrame; if (firstLine) { - mFrames.Clear(); + lineBeforeLastFrame = nsnull; // removes all frames } else { nsIFrame* f = overBegin->mFirstChild; - nsIFrame* lineBeforeLastFrame = - f ? f->GetPrevSibling() : aLineBefore->LastChild(); + lineBeforeLastFrame = f ? f->GetPrevSibling() : mFrames.LastChild(); NS_ASSERTION(!f || lineBeforeLastFrame == aLineBefore->LastChild(), "unexpected line frames"); - mFrames.RemoveFramesAfter(lineBeforeLastFrame); } - if (!overflowLines->empty()) { - // XXXbz If we switch overflow lines to nsFrameList, we should - // change this SetNextSibling call. - oldLastChild->SetNextSibling(overflowLines->front()->mFirstChild); - } - overflowLines->splice(overflowLines->begin(), mLines, overBegin, - end_lines()); - NS_ASSERTION(!overflowLines->empty(), "should not be empty"); + nsFrameList pushedFrames = mFrames.RemoveFramesAfter(lineBeforeLastFrame); + overflowLines->mFrames.InsertFrames(nsnull, nsnull, pushedFrames); + + overflowLines->mLines.splice(overflowLines->mLines.begin(), mLines, + overBegin, end_lines()); + NS_ASSERTION(!overflowLines->mLines.empty(), "should not be empty"); // this takes ownership but it won't delete it immediately so we // can keep using it. SetOverflowLines(overflowLines); @@ -4441,18 +4436,18 @@ nsBlockFrame::PushLines(nsBlockReflowState& aState, // they are pulled up by our next-in-flow. // XXXldb Can this get called O(N) times making the whole thing O(N^2)? - for (line_iterator line = overflowLines->begin(), - line_end = overflowLines->end(); + for (line_iterator line = overflowLines->mLines.begin(), + line_end = overflowLines->mLines.end(); line != line_end; ++line) - { - line->MarkDirty(); - line->MarkPreviousMarginDirty(); - line->mBounds.SetRect(0, 0, 0, 0); - if (line->HasFloats()) { - line->FreeFloats(aState.mFloatCacheFreeList); - } + { + line->MarkDirty(); + line->MarkPreviousMarginDirty(); + line->mBounds.SetRect(0, 0, 0, 0); + if (line->HasFloats()) { + line->FreeFloats(aState.mFloatCacheFreeList); } + } } } @@ -4471,31 +4466,23 @@ nsBlockFrame::DrainOverflowLines() #ifdef DEBUG VerifyOverflowSituation(); #endif - nsLineList* overflowLines = nsnull; - nsLineList* ourOverflowLines = nsnull; + FrameLines* overflowLines = nsnull; + FrameLines* ourOverflowLines = nsnull; // First grab the prev-in-flows overflow lines nsBlockFrame* prevBlock = (nsBlockFrame*) GetPrevInFlow(); if (prevBlock) { overflowLines = prevBlock->RemoveOverflowLines(); if (overflowLines) { - NS_ASSERTION(! overflowLines->empty(), + NS_ASSERTION(!overflowLines->mLines.empty(), "overflow lines should never be set and empty"); - // Make all the frames on the overflow line list mine - nsIFrame* frame = overflowLines->front()->mFirstChild; - while (nsnull != frame) { - ReparentFrame(frame, prevBlock, this); + // Make all the frames on the overflow line list mine. + ReparentFrames(overflowLines->mFrames, prevBlock, this); - // Get the next frame - frame = frame->GetNextSibling(); - } - - // make the overflow out-of-flow frames mine too + // Make the overflow out-of-flow frames mine too. nsAutoOOFFrameList oofs(prevBlock); if (oofs.mList.NotEmpty()) { - for (nsIFrame* f = oofs.mList.FirstChild(); f; f = f->GetNextSibling()) { - ReparentFrame(f, prevBlock, this); - } + ReparentFrames(oofs.mList, prevBlock, this); mFloats.InsertFrames(nsnull, nsnull, oofs.mList); } } @@ -4522,7 +4509,7 @@ nsBlockFrame::DrainOverflowLines() // Now join the line lists into mLines if (overflowLines) { - if (!overflowLines->empty()) { + if (!overflowLines->mLines.empty()) { // Join the line lists if (!mLines.empty()) { // Remember to recompute the margins on the first line. This will @@ -4531,26 +4518,18 @@ nsBlockFrame::DrainOverflowLines() } // Join the sibling lists together - nsIFrame* firstFrame = overflowLines->front()->mFirstChild; - nsIFrame* lastFrame = overflowLines->back()->LastChild(); - nsFrameList framesToInsert(firstFrame, lastFrame); - mFrames.InsertFrames(nsnull, nsnull, framesToInsert); + mFrames.InsertFrames(nsnull, nsnull, overflowLines->mFrames); // Place overflow lines at the front of our line list - mLines.splice(mLines.begin(), *overflowLines); - NS_ASSERTION(overflowLines->empty(), "splice should empty list"); + mLines.splice(mLines.begin(), overflowLines->mLines); + NS_ASSERTION(overflowLines->mLines.empty(), "splice should empty list"); } delete overflowLines; } if (ourOverflowLines) { - if (!ourOverflowLines->empty()) { - nsIFrame* firstFrame = ourOverflowLines->front()->mFirstChild; - nsIFrame* lastFrame = ourOverflowLines->back()->LastChild(); - nsFrameList framesToAppend(firstFrame, lastFrame); - mFrames.AppendFrames(nsnull, framesToAppend); - - // append the overflow to mLines - mLines.splice(mLines.end(), *ourOverflowLines); + if (!ourOverflowLines->mLines.empty()) { + mFrames.AppendFrames(nsnull, ourOverflowLines->mFrames); + mLines.splice(mLines.end(), ourOverflowLines->mLines); } delete ourOverflowLines; } @@ -4586,40 +4565,57 @@ nsBlockFrame::DrainPushedFloats(nsBlockReflowState& aState) } } -nsLineList* +nsBlockFrame::FrameLines* nsBlockFrame::GetOverflowLines() const { - if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_LINES)) { + if (!HasOverflowLines()) { return nsnull; } - nsLineList* lines = static_cast - (Properties().Get(OverflowLinesProperty())); - NS_ASSERTION(lines && !lines->empty(), + FrameLines* prop = + static_cast(Properties().Get(OverflowLinesProperty())); + NS_ASSERTION(prop && !prop->mLines.empty() && + prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(), "value should always be stored and non-empty when state set"); - return lines; + return prop; } -nsLineList* +nsBlockFrame::FrameLines* nsBlockFrame::RemoveOverflowLines() { - if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_LINES)) { + if (!HasOverflowLines()) { return nsnull; } - nsLineList* lines = static_cast - (Properties().Remove(OverflowLinesProperty())); - NS_ASSERTION(lines && !lines->empty(), + FrameLines* prop = + static_cast(Properties().Remove(OverflowLinesProperty())); + NS_ASSERTION(prop && !prop->mLines.empty() && + prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(), "value should always be stored and non-empty when state set"); RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); - return lines; + return prop; +} + +void +nsBlockFrame::DestroyOverflowLines() +{ + NS_ASSERTION(HasOverflowLines(), "huh?"); + FrameLines* prop = + static_cast(Properties().Remove(OverflowLinesProperty())); + NS_ASSERTION(prop && prop->mLines.empty(), + "value should always be stored but empty when destroying"); + RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); + delete prop; } // This takes ownership of aOverflowLines. // XXX We should allocate overflowLines from presShell arena! -nsresult -nsBlockFrame::SetOverflowLines(nsLineList* aOverflowLines) +void +nsBlockFrame::SetOverflowLines(FrameLines* aOverflowLines) { NS_ASSERTION(aOverflowLines, "null lines"); - NS_ASSERTION(!aOverflowLines->empty(), "empty lines"); + NS_ASSERTION(!aOverflowLines->mLines.empty(), "empty lines"); + NS_ASSERTION(aOverflowLines->mLines.front()->mFirstChild == + aOverflowLines->mFrames.FirstChild(), + "invalid overflow lines / frames"); NS_ASSERTION(!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_LINES), "Overwriting existing overflow lines"); @@ -4628,7 +4624,6 @@ nsBlockFrame::SetOverflowLines(nsLineList* aOverflowLines) NS_ASSERTION(!props.Get(OverflowLinesProperty()), "existing overflow list"); props.Set(OverflowLinesProperty(), aOverflowLines); AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); - return NS_OK; } nsFrameList* @@ -4675,10 +4670,47 @@ nsBlockFrame::SetOverflowOutOfFlows(const nsFrameList& aList, } } +nsBulletFrame* +nsBlockFrame::GetInsideBullet() const +{ + if (!HasInsideBullet()) { + return nsnull; + } + NS_ASSERTION(!HasOutsideBullet(), "invalid bullet state"); + nsBulletFrame* frame = + static_cast(Properties().Get(InsideBulletProperty())); + NS_ASSERTION(frame && frame->GetType() == nsGkAtoms::bulletFrame, + "bogus inside bullet frame"); + return frame; +} + +nsBulletFrame* +nsBlockFrame::GetOutsideBullet() const +{ + nsFrameList* list = GetOutsideBulletList(); + return list ? static_cast(list->FirstChild()) + : nsnull; +} + +nsFrameList* +nsBlockFrame::GetOutsideBulletList() const +{ + if (!HasOutsideBullet()) { + return nsnull; + } + NS_ASSERTION(!HasInsideBullet(), "invalid bullet state"); + nsFrameList* list = + static_cast(Properties().Get(OutsideBulletProperty())); + NS_ASSERTION(list && list->GetLength() == 1 && + list->FirstChild()->GetType() == nsGkAtoms::bulletFrame, + "bogus outside bullet list"); + return list; +} + nsFrameList* nsBlockFrame::GetPushedFloats() const { - if (!(GetStateBits() & NS_BLOCK_HAS_PUSHED_FLOATS)) { + if (!HasPushedFloats()) { return nsnull; } nsFrameList* result = @@ -4704,10 +4736,9 @@ nsBlockFrame::EnsurePushedFloats() nsFrameList* nsBlockFrame::RemovePushedFloats() { - if (!(GetStateBits() & NS_BLOCK_HAS_PUSHED_FLOATS)) { + if (!HasPushedFloats()) { return nsnull; } - nsFrameList *result = static_cast(Properties().Remove(PushedFloatProperty())); RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS); @@ -4839,16 +4870,14 @@ nsBlockFrame::AddFrames(nsFrameList& aFrameList, nsIFrame* aPrevSibling) // If we're inserting at the beginning of our list and we have an // inside bullet, insert after that bullet. - if (!aPrevSibling && mBullet && !HaveOutsideBullet()) { - NS_ASSERTION(!aFrameList.ContainsFrame(mBullet), - "Trying to make mBullet prev sibling to itself"); - aPrevSibling = mBullet; + if (!aPrevSibling && HasInsideBullet()) { + aPrevSibling = GetInsideBullet(); } nsIPresShell *presShell = PresContext()->PresShell(); // Attempt to find the line that contains the previous sibling - nsFrameList overflowFrames; + FrameLines* overflowLines; nsLineList* lineList = &mLines; nsLineList::iterator prevSibLine = lineList->end(); PRInt32 prevSiblingIndex = -1; @@ -4862,15 +4891,14 @@ nsBlockFrame::AddFrames(nsFrameList& aFrameList, nsIFrame* aPrevSibling) prevSibLine, mFrames.LastChild(), &prevSiblingIndex)) { // Not in mLines - try overflow lines. - lineList = GetOverflowLines(); - if (lineList) { - prevSibLine = lineList->end(); + overflowLines = GetOverflowLines(); + lineList = overflowLines ? &overflowLines->mLines : nsnull; + if (overflowLines) { + prevSibLine = overflowLines->mLines.end(); prevSiblingIndex = -1; - overflowFrames = nsFrameList(lineList->front()->mFirstChild, - lineList->back()->LastChild()); if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(), prevSibLine, - overflowFrames.LastChild(), + overflowLines->mFrames.LastChild(), &prevSiblingIndex)) { lineList = nsnull; } @@ -4914,7 +4942,7 @@ nsBlockFrame::AddFrames(nsFrameList& aFrameList, nsIFrame* aPrevSibling) lineList->front()->MarkDirty(); lineList->front()->SetInvalidateTextRuns(true); } - nsFrameList& frames = lineList == &mLines ? mFrames : overflowFrames; + nsFrameList& frames = lineList == &mLines ? mFrames : overflowLines->mFrames; const nsFrameList::Slice& newFrames = frames.InsertFrames(nsnull, aPrevSibling, aFrameList); @@ -5129,28 +5157,38 @@ void nsBlockFrame::TryAllLines(nsLineList::iterator* aIterator, nsLineList::iterator* aStartIterator, nsLineList::iterator* aEndIterator, - bool* aInOverflowLines) { + bool* aInOverflowLines, + FrameLines** aOverflowLines) +{ if (*aIterator == *aEndIterator) { if (!*aInOverflowLines) { - *aInOverflowLines = true; // Try the overflow lines - nsLineList* overflowLines = GetOverflowLines(); - if (overflowLines) { - *aStartIterator = overflowLines->begin(); + *aInOverflowLines = true; + FrameLines* lines = GetOverflowLines(); + if (lines) { + *aStartIterator = lines->mLines.begin(); *aIterator = *aStartIterator; - *aEndIterator = overflowLines->end(); + *aEndIterator = lines->mLines.end(); + *aOverflowLines = lines; } } } } +nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, + line_iterator aLine) + : mFrame(aFrame), mLine(aLine), mInOverflowLines(nsnull) +{ + // This will assert if aLine isn't in mLines of aFrame: + DebugOnly check = aLine == mFrame->begin_lines(); +} + nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, line_iterator aLine, bool aInOverflow) : mFrame(aFrame), mLine(aLine), mInOverflowLines(nsnull) { if (aInOverflow) { - mInOverflowLines = aFrame->GetOverflowLines(); - NS_ASSERTION(mInOverflowLines, "How can we be in overflow if there isn't any?"); + mInOverflowLines = &aFrame->GetOverflowLines()->mLines; } } @@ -5297,7 +5335,8 @@ nsBlockInFlowLineIterator::Prev() mFrame = static_cast(mFrame->GetPrevInFlow()); if (!mFrame) return false; - mInOverflowLines = mFrame->GetOverflowLines(); + nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines(); + mInOverflowLines = overflowLines ? &overflowLines->mLines : nsnull; if (mInOverflowLines) { mLine = mInOverflowLines->end(); NS_ASSERTION(mLine != mInOverflowLines->begin(), "empty overflow line list?"); @@ -5326,7 +5365,8 @@ nsBlockInFlowLineIterator::FindValidLine() if (mLine != mFrame->end_lines()) return true; } else { - mInOverflowLines = mFrame->GetOverflowLines(); + nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines(); + mInOverflowLines = overflowLines ? &overflowLines->mLines : nsnull; if (mInOverflowLines) { mLine = mInOverflowLines->begin(); NS_ASSERTION(mLine != mInOverflowLines->end(), "empty overflow line list?"); @@ -5384,16 +5424,19 @@ nsBlockFrame::DoRemoveFrame(nsIFrame* aDeletedFrame, PRUint32 aFlags) nsLineList::iterator line_start = mLines.begin(), line_end = mLines.end(); nsLineList::iterator line = line_start; + FrameLines* overflowLines = nsnull; bool searchingOverflowList = false; // Make sure we look in the overflow lines even if the normal line // list is empty - TryAllLines(&line, &line_start, &line_end, &searchingOverflowList); + TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, + &overflowLines); while (line != line_end) { if (line->Contains(aDeletedFrame)) { break; } ++line; - TryAllLines(&line, &line_start, &line_end, &searchingOverflowList); + TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, + &overflowLines); } if (line == line_end) { @@ -5412,7 +5455,7 @@ nsBlockFrame::DoRemoveFrame(nsIFrame* aDeletedFrame, PRUint32 aFlags) } } - while ((line != line_end) && (nsnull != aDeletedFrame)) { + while (line != line_end && aDeletedFrame) { NS_ASSERTION(this == aDeletedFrame->GetParent(), "messed up delete code"); NS_ASSERTION(line->Contains(aDeletedFrame), "frame not in line"); @@ -5428,19 +5471,19 @@ nsBlockFrame::DoRemoveFrame(nsIFrame* aDeletedFrame, PRUint32 aFlags) line_iterator next = line.next(); nsIFrame* lastFrame = next != line_end ? next->mFirstChild->GetPrevSibling() : - (searchingOverflowList ? line->LastChild() : mFrames.LastChild()); + (searchingOverflowList ? overflowLines->mFrames.LastChild() : + mFrames.LastChild()); NS_ASSERTION(next == line_end || lastFrame == line->LastChild(), "unexpected line frames"); isLastFrameOnLine = lastFrame == aDeletedFrame; } // Remove aDeletedFrame from the line - nsIFrame* nextFrame = aDeletedFrame->GetNextSibling(); if (line->mFirstChild == aDeletedFrame) { // We should be setting this to null if aDeletedFrame // is the only frame on the line. HOWEVER in that case // we will be removing the line anyway, see below. - line->mFirstChild = nextFrame; + line->mFirstChild = aDeletedFrame->GetNextSibling(); } // Hmm, this won't do anything if we're removing a frame in the first @@ -5457,13 +5500,7 @@ nsBlockFrame::DoRemoveFrame(nsIFrame* aDeletedFrame, PRUint32 aFlags) // prevSibling will only be nsnull when we are deleting the very // first frame in the main or overflow list. if (searchingOverflowList) { - nsIFrame* prevSibling = aDeletedFrame->GetPrevSibling(); - if (prevSibling) { - // XXXbz If we switch overflow lines to nsFrameList, we should - // change this SetNextSibling call. - prevSibling->SetNextSibling(nextFrame); - } - aDeletedFrame->SetNextSibling(nsnull); + overflowLines->mFrames.RemoveFrame(aDeletedFrame); } else { mFrames.RemoveFrame(aDeletedFrame); } @@ -5518,12 +5555,13 @@ nsBlockFrame::DoRemoveFrame(nsIFrame* aDeletedFrame, PRUint32 aFlags) #endif Invalidate(visOverflow); } else { - nsLineList* lineList = RemoveOverflowLines(); - line = lineList->erase(line); - if (!lineList->empty()) { - SetOverflowLines(lineList); + // XXX update searchingOverflowList directly, remove only when empty + FrameLines* overflowLines = RemoveOverflowLines(); + line = overflowLines->mLines.erase(line); + if (!overflowLines->mLines.empty()) { + SetOverflowLines(overflowLines); } else { - delete lineList; + delete overflowLines; // We just invalidated our iterators. Since we were in // the overflow lines list, which is now empty, set them // so we're at the end of the regular line list. @@ -5577,7 +5615,8 @@ nsBlockFrame::DoRemoveFrame(nsIFrame* aDeletedFrame, PRUint32 aFlags) line = line_end; } - TryAllLines(&line, &line_start, &line_end, &searchingOverflowList); + TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, + &overflowLines); #ifdef NOISY_REMOVE_FRAME printf("DoRemoveFrame: now on %s line=%p\n", searchingOverflowList?"overflow":"normal", line.get()); @@ -5628,25 +5667,22 @@ nsBlockFrame::StealFrame(nsPresContext* aPresContext, line_start = line, line_end = mLines.end(); bool searchingOverflowList = false; + FrameLines* overflowLines = nsnull; nsIFrame* prevSibling = nsnull; // Make sure we look in the overflow lines even if the normal line // list is empty - TryAllLines(&line, &line_start, &line_end, &searchingOverflowList); + TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, + &overflowLines); while (line != line_end) { nsIFrame* frame = line->mFirstChild; PRInt32 n = line->GetChildCount(); while (--n >= 0) { if (frame == aChild) { - // Disconnect from sibling list if (frame == line->mFirstChild) { line->mFirstChild = frame->GetNextSibling(); } if (searchingOverflowList) { - // XXXbz If we switch overflow lines to nsFrameList, we should - // change this SetNextSibling call. - if (prevSibling) - prevSibling->SetNextSibling(frame->GetNextSibling()); - frame->SetNextSibling(nsnull); + overflowLines->mFrames.RemoveFrame(frame); } else { mFrames.RemoveFrame(frame); } @@ -5662,13 +5698,13 @@ nsBlockFrame::StealFrame(nsPresContext* aPresContext, nsLineBox* lineBox = line; if (searchingOverflowList) { // Erase line, but avoid making the overflow line list empty - nsLineList* lineList = RemoveOverflowLines(); - line = lineList->erase(line); - if (!lineList->empty()) { - nsresult rv = SetOverflowLines(lineList); - NS_ENSURE_SUCCESS(rv, rv); + // XXX update overflowLines directly, remove only when empty + RemoveOverflowLines(); + line = overflowLines->mLines.erase(line); + if (!overflowLines->mLines.empty()) { + SetOverflowLines(overflowLines); } else { - delete lineList; + delete overflowLines; // We just invalidated our iterators. Since we were in // the overflow lines list, which is now empty, set them // so we're at the end of the regular line list. @@ -5694,7 +5730,8 @@ nsBlockFrame::StealFrame(nsPresContext* aPresContext, frame = frame->GetNextSibling(); } ++line; - TryAllLines(&line, &line_start, &line_end, &searchingOverflowList); + TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, + &overflowLines); if (prevSibling && !prevSibling->GetNextSibling()) { // We just switched to the overflow list. Null out prevSibling prevSibling = nsnull; @@ -6303,9 +6340,10 @@ nsBlockFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, } } - if (NS_SUCCEEDED(rv) && (nsnull != mBullet) && HaveOutsideBullet()) { + if (NS_SUCCEEDED(rv) && HasOutsideBullet()) { // Display outside bullets manually - rv = BuildDisplayListForChild(aBuilder, mBullet, aDirtyRect, aLists); + nsIFrame* bullet = GetOutsideBullet(); + rv = BuildDisplayListForChild(aBuilder, bullet, aDirtyRect, aLists); } #ifdef DEBUG @@ -6349,7 +6387,7 @@ nsBlockFrame::CreateAccessible() presContext->PresShell()); } - if (!mBullet || !presContext) { + if (!HasBullet() || !presContext) { if (!mContent->GetParent()) { // Don't create accessible objects for the root content node, they are redundant with // the nsDocAccessible object created with the document node @@ -6437,7 +6475,7 @@ nsBlockFrame::ChildIsDirty(nsIFrame* aChild) if (aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW && aChild->GetStyleDisplay()->IsAbsolutelyPositioned()) { // do nothing - } else if (aChild == mBullet && HaveOutsideBullet()) { + } else if (aChild == GetOutsideBullet()) { // The bullet lives in the first line, unless the first line has // height 0 and there is a second line, in which case it lives // in the second line. @@ -6472,15 +6510,9 @@ nsBlockFrame::Init(nsIContent* aContent, nsIFrame* aPrevInFlow) { if (aPrevInFlow) { - // Copy over the block frame type flags - nsBlockFrame* blockFrame = (nsBlockFrame*)aPrevInFlow; - - // Don't copy NS_BLOCK_HAS_FIRST_LETTER_CHILD as that is set on the first - // continuation only. - SetFlags(blockFrame->mState & - (NS_BLOCK_FLAGS_MASK & - (~NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET & - ~NS_BLOCK_HAS_FIRST_LETTER_CHILD))); + // Copy over the inherited block frame bits from the prev-in-flow. + SetFlags(aPrevInFlow->GetStateBits() & + (NS_BLOCK_FLAGS_MASK & ~NS_BLOCK_FLAGS_NON_INHERITED_MASK)); } nsresult rv = nsBlockFrameSuper::Init(aContent, aParent, aPrevInFlow); @@ -6496,6 +6528,11 @@ NS_IMETHODIMP nsBlockFrame::SetInitialChildList(ChildListID aListID, nsFrameList& aChildList) { + NS_ASSERTION(aListID != kPrincipalList || + (GetStateBits() & (NS_BLOCK_FRAME_HAS_INSIDE_BULLET | + NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET)) == 0, + "how can we have a bullet already?"); + nsresult rv = NS_OK; if (kAbsoluteList == aListID) { @@ -6554,10 +6591,9 @@ nsBlockFrame::SetInitialChildList(ChildListID aListID, } possibleListItem = parent; } - if ((nsnull == GetPrevInFlow()) && - (NS_STYLE_DISPLAY_LIST_ITEM == - possibleListItem->GetStyleDisplay()->mDisplay) && - (nsnull == mBullet)) { + if (NS_STYLE_DISPLAY_LIST_ITEM == + possibleListItem->GetStyleDisplay()->mDisplay && + !GetPrevInFlow()) { // Resolve style for the bullet frame const nsStyleList* styleList = GetStyleList(); nsCSSPseudoElements::Type pseudoType; @@ -6583,7 +6619,7 @@ nsBlockFrame::SetInitialChildList(ChildListID aListID, // Create bullet frame nsBulletFrame* bullet = new (shell) nsBulletFrame(kidSC); - if (nsnull == bullet) { + if (!bullet) { return NS_ERROR_OUT_OF_MEMORY; } bullet->Init(mContent, this, nsnull); @@ -6591,16 +6627,16 @@ nsBlockFrame::SetInitialChildList(ChildListID aListID, // If the list bullet frame should be positioned inside then add // it to the flow now. if (NS_STYLE_LIST_STYLE_POSITION_INSIDE == - styleList->mListStylePosition) { + styleList->mListStylePosition) { nsFrameList bulletList(bullet, bullet); AddFrames(bulletList, nsnull); - mState &= ~NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET; + Properties().Set(InsideBulletProperty(), bullet); + AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_BULLET); + } else { + nsFrameList* bulletList = new nsFrameList(bullet, bullet); + Properties().Set(OutsideBulletProperty(), bulletList); + AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET); } - else { - mState |= NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET; - } - - mBullet = bullet; } } @@ -6611,8 +6647,7 @@ bool nsBlockFrame::BulletIsEmpty() const { NS_ASSERTION(mContent->GetPrimaryFrame()->GetStyleDisplay()->mDisplay == - NS_STYLE_DISPLAY_LIST_ITEM && - HaveOutsideBullet(), + NS_STYLE_DISPLAY_LIST_ITEM && HasOutsideBullet(), "should only care when we have an outside bullet"); const nsStyleList* list = GetStyleList(); return list->mListStyleType == NS_STYLE_LIST_STYLE_NONE && @@ -6636,24 +6671,15 @@ nsBlockFrame::GetBulletText(nsAString& aText) const aText.Assign(kSquareCharacter); } else if (myList->mListStyleType != NS_STYLE_LIST_STYLE_NONE) { - nsAutoString text; - mBullet->GetListItemText(*myList, text); - aText = text; + nsBulletFrame* bullet = GetBullet(); + if (bullet) { + nsAutoString text; + bullet->GetListItemText(*myList, text); + aText = text; + } } } -bool -nsBlockFrame::HasBullet() const -{ - if (mBullet) { - const nsStyleList* styleList = GetStyleList(); - return styleList->GetListStyleImage() || - styleList->mListStyleType != NS_STYLE_LIST_STYLE_NONE; - } - - return false; -} - // static bool nsBlockFrame::FrameStartsCounterScope(nsIFrame* aFrame) @@ -6761,15 +6787,15 @@ nsBlockFrame::RenumberListsFor(nsPresContext* aPresContext, // something foreign has crept in. nsBlockFrame* listItem = nsLayoutUtils::GetAsBlock(kid); if (listItem) { - if (nsnull != listItem->mBullet) { + nsBulletFrame* bullet = listItem->GetBullet(); + if (bullet) { bool changed; - *aOrdinal = listItem->mBullet->SetListItemOrdinal(*aOrdinal, - &changed); + *aOrdinal = bullet->SetListItemOrdinal(*aOrdinal, &changed); if (changed) { kidRenumberedABullet = true; // The ordinal changed - mark the bullet frame dirty. - listItem->ChildIsDirty(listItem->mBullet); + listItem->ChildIsDirty(bullet); } } @@ -6801,7 +6827,8 @@ nsBlockFrame::RenumberListsFor(nsPresContext* aPresContext, } void -nsBlockFrame::ReflowBullet(nsBlockReflowState& aState, +nsBlockFrame::ReflowBullet(nsIFrame* aBulletFrame, + nsBlockReflowState& aState, nsHTMLReflowMetrics& aMetrics, nscoord aLineTop) { @@ -6817,10 +6844,10 @@ nsBlockFrame::ReflowBullet(nsBlockReflowState& aState, // XXXwaterson Should this look just like the logic in // nsBlockReflowContext::ReflowBlock and nsLineLayout::ReflowFrame? nsHTMLReflowState reflowState(aState.mPresContext, rs, - mBullet, availSize); + aBulletFrame, availSize); nsReflowStatus status; - mBullet->WillReflow(aState.mPresContext); - mBullet->Reflow(aState.mPresContext, aMetrics, reflowState, status); + aBulletFrame->WillReflow(aState.mPresContext); + aBulletFrame->Reflow(aState.mPresContext, aMetrics, reflowState, status); // Get the float available space using our saved state from before we // started reflowing the block, so that we ignore any floats inside @@ -6860,8 +6887,9 @@ nsBlockFrame::ReflowBullet(nsBlockReflowState& aState, // Approximate the bullets position; vertical alignment will provide // the final vertical location. nscoord y = aState.mContentArea.y; - mBullet->SetRect(nsRect(x, y, aMetrics.width, aMetrics.height)); - mBullet->DidReflow(aState.mPresContext, &aState.mReflowState, NS_FRAME_REFLOW_FINISHED); + aBulletFrame->SetRect(nsRect(x, y, aMetrics.width, aMetrics.height)); + aBulletFrame->DidReflow(aState.mPresContext, &aState.mReflowState, + NS_FRAME_REFLOW_FINISHED); } // This is used to scan frames for any float placeholders, add their @@ -7175,14 +7203,19 @@ nsBlockFrame::VerifyLines(bool aFinalCheckOK) void nsBlockFrame::VerifyOverflowSituation() { - nsBlockFrame* flow = (nsBlockFrame*) GetFirstInFlow(); - while (nsnull != flow) { - nsLineList* overflowLines = GetOverflowLines(); - if (nsnull != overflowLines) { - NS_ASSERTION(! overflowLines->empty(), "should not be empty if present"); - NS_ASSERTION(overflowLines->front()->mFirstChild, "bad overflow list"); + nsBlockFrame* flow = static_cast(GetFirstInFlow()); + while (flow) { + FrameLines* overflowLines = GetOverflowLines(); + if (overflowLines) { + NS_ASSERTION(!overflowLines->mLines.empty(), + "should not be empty if present"); + NS_ASSERTION(overflowLines->mLines.front()->mFirstChild, + "bad overflow lines"); + NS_ASSERTION(overflowLines->mLines.front()->mFirstChild == + overflowLines->mFrames.FirstChild(), + "bad overflow frames / lines"); } - flow = (nsBlockFrame*) flow->GetNextInFlow(); + flow = static_cast(flow->GetNextInFlow()); } } diff --git a/layout/generic/nsBlockFrame.h b/layout/generic/nsBlockFrame.h index 836e3a10d7f5..b61207fdc3b2 100644 --- a/layout/generic/nsBlockFrame.h +++ b/layout/generic/nsBlockFrame.h @@ -154,11 +154,6 @@ public: friend nsIFrame* NS_NewBlockFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRUint32 aFlags); - // This is a child list too, but we let nsBlockReflowState get to it - // directly too. - NS_DECLARE_FRAME_PROPERTY(PushedFloatProperty, - nsContainerFrame::DestroyFrameList) - // nsQueryFrame NS_DECL_QUERYFRAME @@ -175,7 +170,7 @@ public: nsFrameList& aFrameList); NS_IMETHOD RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame); - virtual nsFrameList GetChildList(ChildListID aListID) const; + virtual const nsFrameList& GetChildList(ChildListID aListID) const; virtual void GetChildLists(nsTArray* aLists) const; virtual nscoord GetBaseline() const; virtual nscoord GetCaretBaseline() const; @@ -246,12 +241,14 @@ public: /** * Return the bullet text equivalent. */ - virtual void GetBulletText(nsAString& aText) const; + void GetBulletText(nsAString& aText) const; /** * Return true if there's a bullet. */ - virtual bool HasBullet() const; + bool HasBullet() const { + return HasOutsideBullet() || HasInsideBullet(); + } virtual void MarkIntrinsicWidthsDirty(); virtual nscoord GetMinWidth(nsRenderingContext *aRenderingContext); @@ -328,6 +325,11 @@ public: */ static nsBlockFrame* GetNearestAncestorBlock(nsIFrame* aCandidate); + struct FrameLines { + nsLineList mLines; + nsFrameList mFrames; + }; + protected: nsBlockFrame(nsStyleContext* aContext) : nsContainerFrame(aContext) @@ -355,22 +357,14 @@ protected: void TryAllLines(nsLineList::iterator* aIterator, nsLineList::iterator* aStartIterator, nsLineList::iterator* aEndIterator, - bool* aInOverflowLines); + bool* aInOverflowLines, + FrameLines** aOverflowLines); void SetFlags(nsFrameState aFlags) { mState &= ~NS_BLOCK_FLAGS_MASK; mState |= aFlags; } - bool HaveOutsideBullet() const { -#if defined(DEBUG) && !defined(DEBUG_rods) - if(mState & NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET) { - NS_ASSERTION(mBullet,"NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET flag set and no mBullet"); - } -#endif - return 0 != (mState & NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET); - } - /** move the frames contained by aLine by aDY * if aLine is a block, its child floats are added to the state manager */ @@ -646,8 +640,14 @@ protected: nsLineBox* aLine, nsBlockFrame* aFromContainer, bool aFromOverflowLine, + nsFrameList& aFromFrameList, nsLineList::iterator aFromLine); + /** + * Push the line after aLineBefore to the overflow line list. + * @param aLineBefore a line in 'mLines' (or begin_lines() when + * pushing the first line) + */ void PushLines(nsBlockReflowState& aState, nsLineList::iterator aLineBefore); @@ -675,7 +675,8 @@ protected: static bool FrameStartsCounterScope(nsIFrame* aFrame); - void ReflowBullet(nsBlockReflowState& aState, + void ReflowBullet(nsIFrame* aBulletFrame, + nsBlockReflowState& aState, nsHTMLReflowMetrics& aMetrics, nscoord aLineTop); @@ -684,10 +685,14 @@ protected: virtual nsILineIterator* GetLineIterator(); public: - nsLineList* GetOverflowLines() const; + bool HasOverflowLines() const { + return 0 != (GetStateBits() & NS_BLOCK_HAS_OVERFLOW_LINES); + } + FrameLines* GetOverflowLines() const; protected: - nsLineList* RemoveOverflowLines(); - nsresult SetOverflowLines(nsLineList* aOverflowLines); + FrameLines* RemoveOverflowLines(); + void SetOverflowLines(FrameLines* aOverflowLines); + void DestroyOverflowLines(); // Determine the computed height that's in effect for this block // frame (that is, our computed height minus the heights of our @@ -724,6 +729,50 @@ protected: nsFrameList* GetOverflowOutOfFlows() const; void SetOverflowOutOfFlows(const nsFrameList& aList, nsFrameList* aPropValue); + /** + * @return true if this frame has an inside bullet frame. + */ + bool HasInsideBullet() const { + return 0 != (mState & NS_BLOCK_FRAME_HAS_INSIDE_BULLET); + } + + /** + * @return the inside bullet frame or nsnull if we don't have one. + */ + nsBulletFrame* GetInsideBullet() const; + + /** + * @return true if this frame has an outside bullet frame. + */ + bool HasOutsideBullet() const { + return 0 != (mState & NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET); + } + + /** + * @return the outside bullet frame or nsnull if we don't have one. + */ + nsBulletFrame* GetOutsideBullet() const; + + /** + * @return the outside bullet frame list frame property. + */ + nsFrameList* GetOutsideBulletList() const; + + /** + * @return the bullet frame or nsnull if we don't have one. + */ + nsBulletFrame* GetBullet() const { + nsBulletFrame* outside = GetOutsideBullet(); + return outside ? outside : GetInsideBullet(); + } + + /** + * @return true if this frame has pushed floats. + */ + bool HasPushedFloats() const { + return 0 != (GetStateBits() & NS_BLOCK_HAS_PUSHED_FLOATS); + } + // Get the pushed floats list nsFrameList* GetPushedFloats() const; // Get the pushed floats list, or if there is not currently one, @@ -743,12 +792,9 @@ protected: nsLineList mLines; // List of all floats in this block + // XXXmats blocks rarely have floats, make it a frame property nsFrameList mFloats; - // XXX_fix_me: subclass one more time! - // For list-item frames, this is the bullet frame. - nsBulletFrame* mBullet; - friend class nsBlockReflowState; friend class nsBlockInFlowLineIterator; @@ -798,7 +844,11 @@ private: class nsBlockInFlowLineIterator { public: typedef nsBlockFrame::line_iterator line_iterator; - nsBlockInFlowLineIterator(nsBlockFrame* aFrame, line_iterator aLine, bool aInOverflow); + /** + * Set up the iterator to point to aLine which must be a normal line + * in aFrame (not an overflow line). + */ + nsBlockInFlowLineIterator(nsBlockFrame* aFrame, line_iterator aLine); /** * Set up the iterator to point to the first line found starting from * aFrame. Sets aFoundValidLine to false if there is no such line. @@ -845,6 +895,10 @@ public: bool Prev(); private: + friend class nsBlockFrame; + // XXX nsBlockFrame uses this internally in one place. Try to remove it. + nsBlockInFlowLineIterator(nsBlockFrame* aFrame, line_iterator aLine, bool aInOverflow); + nsBlockFrame* mFrame; line_iterator mLine; nsLineList* mInOverflowLines; diff --git a/layout/generic/nsBlockReflowContext.cpp b/layout/generic/nsBlockReflowContext.cpp index 3ed89cc2ce1c..abd16f83301d 100644 --- a/layout/generic/nsBlockReflowContext.cpp +++ b/layout/generic/nsBlockReflowContext.cpp @@ -124,7 +124,8 @@ nsBlockReflowContext::ComputeCollapsedTopMargin(const nsHTMLReflowState& aRS, nsBlockFrame::line_iterator line_end; bool anyLines = true; if (overflowLines) { - nsLineList* lines = block->GetOverflowLines(); + nsBlockFrame::FrameLines* frames = block->GetOverflowLines(); + nsLineList* lines = frames ? &frames->mLines : nsnull; if (!lines) { anyLines = false; } else { diff --git a/layout/generic/nsContainerFrame.cpp b/layout/generic/nsContainerFrame.cpp index 8a4193b32575..8efb9538e6d5 100644 --- a/layout/generic/nsContainerFrame.cpp +++ b/layout/generic/nsContainerFrame.cpp @@ -273,7 +273,7 @@ nsContainerFrame::DestroyFrom(nsIFrame* aDestructRoot) ///////////////////////////////////////////////////////////////////////////// // Child frame enumeration -nsFrameList +const nsFrameList& nsContainerFrame::GetChildList(ChildListID aListID) const { // We only know about the principal child list and the overflow lists. diff --git a/layout/generic/nsContainerFrame.h b/layout/generic/nsContainerFrame.h index b8e5282a216d..b3b559d6e0a7 100644 --- a/layout/generic/nsContainerFrame.h +++ b/layout/generic/nsContainerFrame.h @@ -95,7 +95,7 @@ public: NS_IMETHOD RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame); - virtual nsFrameList GetChildList(ChildListID aList) const; + virtual const nsFrameList& GetChildList(ChildListID aList) const; virtual void GetChildLists(nsTArray* aLists) const; virtual void DestroyFrom(nsIFrame* aDestructRoot); virtual void ChildIsDirty(nsIFrame* aChild); diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index fc486698fc5c..a82bc6afde07 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -1187,7 +1187,7 @@ nsFrame::GetBaseline() const return mRect.height + GetUsedMargin().bottom; } -nsFrameList +const nsFrameList& nsFrame::GetChildList(ChildListID aListID) const { if (IsAbsoluteContainer() && diff --git a/layout/generic/nsFrame.h b/layout/generic/nsFrame.h index d75e62af473f..fbb922bc29b5 100644 --- a/layout/generic/nsFrame.h +++ b/layout/generic/nsFrame.h @@ -190,7 +190,7 @@ public: nsStyleContext* aStyleContext); virtual void SetParent(nsIFrame* aParent); virtual nscoord GetBaseline() const; - virtual nsFrameList GetChildList(ChildListID aListID) const; + virtual const nsFrameList& GetChildList(ChildListID aListID) const; virtual void GetChildLists(nsTArray* aLists) const; NS_IMETHOD HandleEvent(nsPresContext* aPresContext, diff --git a/layout/generic/nsHTMLParts.h b/layout/generic/nsHTMLParts.h index 7c5dbd8163b1..4a282fc6d295 100644 --- a/layout/generic/nsHTMLParts.h +++ b/layout/generic/nsHTMLParts.h @@ -73,6 +73,10 @@ class nsTableColFrame; * frame among the block's descendants. If there is a floating first-letter * frame, or the block has first-letter style but has no first letter, this * bit is not set. This bit is set on the first continuation only. + * + * NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET and NS_BLOCK_FRAME_HAS_INSIDE_BULLET + * means the block has an associated bullet frame, they are mutually exclusive. + * */ #define NS_BLOCK_MARGIN_ROOT NS_FRAME_STATE_BIT(22) #define NS_BLOCK_FLOAT_MGR NS_FRAME_STATE_BIT(23) @@ -80,14 +84,25 @@ class nsTableColFrame; #define NS_BLOCK_HAS_FIRST_LETTER_STYLE NS_FRAME_STATE_BIT(29) #define NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET NS_FRAME_STATE_BIT(30) #define NS_BLOCK_HAS_FIRST_LETTER_CHILD NS_FRAME_STATE_BIT(31) -// These are the bits that get inherited from a block frame to its -// next-in-flows and are not private to blocks -#define NS_BLOCK_FLAGS_MASK (NS_BLOCK_MARGIN_ROOT | \ - NS_BLOCK_FLOAT_MGR | \ - NS_BLOCK_CLIP_PAGINATED_OVERFLOW | \ - NS_BLOCK_HAS_FIRST_LETTER_STYLE | \ - NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET | \ - NS_BLOCK_HAS_FIRST_LETTER_CHILD) +#define NS_BLOCK_FRAME_HAS_INSIDE_BULLET NS_FRAME_STATE_BIT(63) +// These are all the block specific frame bits, they are copied from +// the prev-in-flow to a newly created next-in-flow, except for the +// NS_BLOCK_FLAGS_NON_INHERITED_MASK bits below. +#define NS_BLOCK_FLAGS_MASK (NS_BLOCK_MARGIN_ROOT | \ + NS_BLOCK_FLOAT_MGR | \ + NS_BLOCK_CLIP_PAGINATED_OVERFLOW | \ + NS_BLOCK_HAS_FIRST_LETTER_STYLE | \ + NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET | \ + NS_BLOCK_HAS_FIRST_LETTER_CHILD | \ + NS_BLOCK_FRAME_HAS_INSIDE_BULLET) + +// This is the subset of NS_BLOCK_FLAGS_MASK that is NOT inherited +// by default. They should only be set on the first-in-flow. +// See nsBlockFrame::Init. +#define NS_BLOCK_FLAGS_NON_INHERITED_MASK \ + (NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET | \ + NS_BLOCK_HAS_FIRST_LETTER_CHILD | \ + NS_BLOCK_FRAME_HAS_INSIDE_BULLET) // Factory methods for creating html layout objects diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index ab8112bc05e3..ced7251601d9 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -1057,12 +1057,8 @@ public: * @return the child list. If the requested list is unsupported by this * frame type, an empty list will be returned. */ - // XXXbz if all our frame storage were actually backed by nsFrameList, we - // could make this return a const reference... nsBlockFrame is the only real - // culprit here. Make sure to assign the return value of this function into - // a |const nsFrameList&|, not an nsFrameList. - virtual nsFrameList GetChildList(ChildListID aListID) const = 0; - nsFrameList PrincipalChildList() { return GetChildList(kPrincipalList); } + virtual const nsFrameList& GetChildList(ChildListID aListID) const = 0; + const nsFrameList& PrincipalChildList() { return GetChildList(kPrincipalList); } virtual void GetChildLists(nsTArray* aLists) const = 0; // XXXbz this method should go away nsIFrame* GetFirstChild(ChildListID aListID) const { diff --git a/layout/generic/nsLineBox.cpp b/layout/generic/nsLineBox.cpp index 6e4010c8c622..6ef3a1fc40bc 100644 --- a/layout/generic/nsLineBox.cpp +++ b/layout/generic/nsLineBox.cpp @@ -235,6 +235,7 @@ nsLineBox::List(FILE* out, PRInt32 aIndent) const } #endif +#ifdef DEBUG nsIFrame* nsLineBox::LastChild() const { @@ -245,13 +246,7 @@ nsLineBox::LastChild() const } return frame; } - -bool -nsLineBox::IsLastChild(nsIFrame* aFrame) const -{ - nsIFrame* lastFrame = LastChild(); - return aFrame == lastFrame; -} +#endif PRInt32 nsLineBox::IndexOf(nsIFrame* aFrame) const @@ -373,7 +368,7 @@ nsLineBox::RFindLineContaining(nsIFrame* aFrame, nsIFrame* curFrame = aLastFrameBeforeEnd; while (aBegin != aEnd) { --aEnd; - NS_ASSERTION(aEnd->IsLastChild(curFrame), "Unexpected curFrame"); + NS_ASSERTION(aEnd->LastChild() == curFrame, "Unexpected curFrame"); // i is the index of curFrame in aEnd PRInt32 i = aEnd->GetChildCount() - 1; while (i >= 0) { diff --git a/layout/generic/nsLineBox.h b/layout/generic/nsLineBox.h index ef88e822bd8f..f20023e30878 100644 --- a/layout/generic/nsLineBox.h +++ b/layout/generic/nsLineBox.h @@ -470,11 +470,8 @@ public: char* StateToString(char* aBuf, PRInt32 aBufSize) const; void List(FILE* out, PRInt32 aIndent) const; -#endif - nsIFrame* LastChild() const; - - bool IsLastChild(nsIFrame* aFrame) const; +#endif PRInt32 IndexOf(nsIFrame* aFrame) const; diff --git a/layout/generic/nsObjectFrame.cpp b/layout/generic/nsObjectFrame.cpp index d24cdb12400f..24c494c3c098 100644 --- a/layout/generic/nsObjectFrame.cpp +++ b/layout/generic/nsObjectFrame.cpp @@ -351,6 +351,9 @@ nsObjectFrame::DestroyFrom(nsIFrame* aDestructRoot) mBackgroundSink->Destroy(); } + if (mInstanceOwner) { + mInstanceOwner->SetFrame(nsnull); + } SetInstanceOwner(nsnull); nsObjectFrameSuper::DestroyFrom(aDestructRoot); diff --git a/layout/generic/nsTextFrameThebes.cpp b/layout/generic/nsTextFrameThebes.cpp index b57d5af5193e..c3a7acede07c 100644 --- a/layout/generic/nsTextFrameThebes.cpp +++ b/layout/generic/nsTextFrameThebes.cpp @@ -1212,7 +1212,7 @@ BuildTextRuns(gfxContext* aContext, nsTextFrame* aForFrame, bool isValid = true; nsBlockInFlowLineIterator backIterator(block, &isValid); if (aForFrameLine) { - backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine, false); + backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine); } else { backIterator = nsBlockInFlowLineIterator(block, lineContainerChild, &isValid); NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us"); diff --git a/layout/reftests/svg/smil/anim-gradient-attr-presence-01-ref.svg b/layout/reftests/svg/smil/anim-gradient-attr-presence-01-ref.svg new file mode 100644 index 000000000000..44e380bc0262 --- /dev/null +++ b/layout/reftests/svg/smil/anim-gradient-attr-presence-01-ref.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/layout/reftests/svg/smil/anim-gradient-attr-presence-01.svg b/layout/reftests/svg/smil/anim-gradient-attr-presence-01.svg new file mode 100644 index 000000000000..71a6aa89cf57 --- /dev/null +++ b/layout/reftests/svg/smil/anim-gradient-attr-presence-01.svg @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/layout/reftests/svg/smil/reftest.list b/layout/reftests/svg/smil/reftest.list index a029ca4d4b87..9e713e93bb40 100644 --- a/layout/reftests/svg/smil/reftest.list +++ b/layout/reftests/svg/smil/reftest.list @@ -226,6 +226,7 @@ random == anim-text-x-y-dx-dy-01.svg anim-text-x-y-dx-dy-01-ref.svg # bug 579588 == anim-pattern-attr-presence-01.svg anim-pattern-attr-presence-01-ref.svg fails == anim-pattern-attr-presence-02.svg anim-pattern-attr-presence-02-ref.svg # ^ bug 621651 +== anim-gradient-attr-presence-01.svg anim-gradient-attr-presence-01-ref.svg == api-sanity-1.svg lime.svg diff --git a/layout/style/Loader.cpp b/layout/style/Loader.cpp index 01c56a520fee..521a45762dc2 100644 --- a/layout/style/Loader.cpp +++ b/layout/style/Loader.cpp @@ -62,7 +62,6 @@ #include "nsIDOMNode.h" #include "nsIDOMDocument.h" #include "nsIDOMWindow.h" -#include "nsICharsetAlias.h" #include "nsHashtable.h" #include "nsIURI.h" #include "nsIServiceManager.h" diff --git a/layout/svg/base/src/nsSVGGradientFrame.cpp b/layout/svg/base/src/nsSVGGradientFrame.cpp index 9c0e49cd2156..8a1b7c2bb53e 100644 --- a/layout/svg/base/src/nsSVGGradientFrame.cpp +++ b/layout/svg/base/src/nsSVGGradientFrame.cpp @@ -51,6 +51,27 @@ using mozilla::SVGAnimatedTransformList; +//---------------------------------------------------------------------- +// Helper classes + +class nsSVGGradientFrame::AutoGradientReferencer +{ +public: + AutoGradientReferencer(nsSVGGradientFrame *aFrame) + : mFrame(aFrame) + { + // Reference loops should normally be detected in advance and handled, so + // we're not expecting to encounter them here + NS_ABORT_IF_FALSE(!mFrame->mLoopFlag, "Undetected reference loop!"); + mFrame->mLoopFlag = true; + } + ~AutoGradientReferencer() { + mFrame->mLoopFlag = false; + } +private: + nsSVGGradientFrame *mFrame; +}; + //---------------------------------------------------------------------- // Implementation @@ -134,6 +155,40 @@ nsSVGGradientFrame::GetStopInformation(PRInt32 aIndex, *aStopOpacity = stopFrame->GetStyleSVGReset()->mStopOpacity; } +PRUint16 +nsSVGGradientFrame::GetEnumValue(PRUint32 aIndex, nsIContent *aDefault) +{ + const nsSVGEnum& thisEnum = + static_cast(mContent)->mEnumAttributes[aIndex]; + + if (thisEnum.IsExplicitlySet()) + return thisEnum.GetAnimValue(); + + AutoGradientReferencer gradientRef(this); + + nsSVGGradientFrame *next = GetReferencedGradientIfNotInUse(); + return next ? next->GetEnumValue(aIndex, aDefault) : + static_cast(aDefault)-> + mEnumAttributes[aIndex].GetAnimValue(); +} + +const SVGAnimatedTransformList* +nsSVGGradientFrame::GetGradientTransformList(nsIContent* aDefault) +{ + SVGAnimatedTransformList *thisTransformList = + static_cast(mContent)->GetAnimatedTransformList(); + + if (thisTransformList->IsExplicitlySet()) + return thisTransformList; + + AutoGradientReferencer gradientRef(this); + + nsSVGGradientFrame *next = GetReferencedGradientIfNotInUse(); + return next ? next->GetGradientTransformList(aDefault) : + static_cast(aDefault) + ->mGradientTransform.get(); +} + gfxMatrix nsSVGGradientFrame::GetGradientTransform(nsIFrame *aSource, const gfxRect *aOverrideBounds) @@ -150,19 +205,19 @@ nsSVGGradientFrame::GetGradientTransform(nsIFrame *aSource, else mSource = aSource; } else { - NS_ASSERTION(gradientUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX, - "Unknown gradientUnits type"); + NS_ASSERTION( + gradientUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX, + "Unknown gradientUnits type"); // objectBoundingBox is the default anyway - gfxRect bbox = aOverrideBounds ? *aOverrideBounds : nsSVGUtils::GetBBox(aSource); - bboxMatrix = gfxMatrix(bbox.Width(), 0, 0, bbox.Height(), bbox.X(), bbox.Y()); + gfxRect bbox = + aOverrideBounds ? *aOverrideBounds : nsSVGUtils::GetBBox(aSource); + bboxMatrix = + gfxMatrix(bbox.Width(), 0, 0, bbox.Height(), bbox.X(), bbox.Y()); } - nsSVGGradientElement *element = - GetGradientWithAttr(nsGkAtoms::gradientTransform, mContent); - - SVGAnimatedTransformList* animTransformList = - element->GetAnimatedTransformList(); + const SVGAnimatedTransformList* animTransformList = + GetGradientTransformList(mContent); if (!animTransformList) return bboxMatrix; @@ -171,13 +226,32 @@ nsSVGGradientFrame::GetGradientTransform(nsIFrame *aSource, return bboxMatrix.PreMultiply(gradientTransform); } -PRUint16 -nsSVGGradientFrame::GetSpreadMethod() +nsSVGLinearGradientElement * +nsSVGGradientFrame::GetLinearGradientWithLength(PRUint32 aIndex, + nsSVGLinearGradientElement* aDefault) { - nsSVGGradientElement *element = - GetGradientWithAttr(nsGkAtoms::spreadMethod, mContent); + // If this was a linear gradient with the required length, we would have + // already found it in nsSVGLinearGradientFrame::GetLinearGradientWithLength. + // Since we didn't find the length, continue looking down the chain. - return element->mEnumAttributes[nsSVGGradientElement::SPREADMETHOD].GetAnimValue(); + AutoGradientReferencer gradientRef(this); + + nsSVGGradientFrame *next = GetReferencedGradientIfNotInUse(); + return next ? next->GetLinearGradientWithLength(aIndex, aDefault) : aDefault; +} + +nsSVGRadialGradientElement * +nsSVGGradientFrame::GetRadialGradientWithLength(PRUint32 aIndex, + nsSVGRadialGradientElement* aDefault) +{ + // If this was a radial gradient with the required length, we would have + // already found it in nsSVGRadialGradientFrame::GetRadialGradientWithLength. + // Since we didn't find the length, continue looking down the chain. + + AutoGradientReferencer gradientRef(this); + + nsSVGGradientFrame *next = GetReferencedGradientIfNotInUse(); + return next ? next->GetRadialGradientWithLength(aIndex, aDefault) : aDefault; } //---------------------------------------------------------------------- @@ -289,53 +363,20 @@ nsSVGGradientFrame::GetReferencedGradient() return static_cast(result); } -nsSVGGradientElement * -nsSVGGradientFrame::GetGradientWithAttr(nsIAtom *aAttrName, nsIContent *aDefault) +nsSVGGradientFrame * +nsSVGGradientFrame::GetReferencedGradientIfNotInUse() { - if (mContent->HasAttr(kNameSpaceID_None, aAttrName)) - return static_cast(mContent); + nsSVGGradientFrame *referenced = GetReferencedGradient(); + if (!referenced) + return nsnull; - nsSVGGradientElement *grad = static_cast(aDefault); + if (referenced->mLoopFlag) { + // XXXjwatt: we should really send an error to the JavaScript Console here: + NS_WARNING("gradient reference loop detected while inheriting attribute!"); + return nsnull; + } - nsSVGGradientFrame *next = GetReferencedGradient(); - if (!next) - return grad; - - // Set mLoopFlag before checking mNextGrad->mLoopFlag in case we are mNextGrad - mLoopFlag = true; - // XXXjwatt: we should really send an error to the JavaScript Console here: - NS_WARN_IF_FALSE(!next->mLoopFlag, "gradient reference loop detected " - "while inheriting attribute!"); - if (!next->mLoopFlag) - grad = next->GetGradientWithAttr(aAttrName, aDefault); - mLoopFlag = false; - - return grad; -} - -nsSVGGradientElement * -nsSVGGradientFrame::GetGradientWithAttr(nsIAtom *aAttrName, nsIAtom *aGradType, - nsIContent *aDefault) -{ - if (GetType() == aGradType && mContent->HasAttr(kNameSpaceID_None, aAttrName)) - return static_cast(mContent); - - nsSVGGradientElement *grad = static_cast(aDefault); - - nsSVGGradientFrame *next = GetReferencedGradient(); - if (!next) - return grad; - - // Set mLoopFlag before checking mNextGrad->mLoopFlag in case we are mNextGrad - mLoopFlag = true; - // XXXjwatt: we should really send an error to the JavaScript Console here: - NS_WARN_IF_FALSE(!next->mLoopFlag, "gradient reference loop detected " - "while inheriting attribute!"); - if (!next->mLoopFlag) - grad = next->GetGradientWithAttr(aAttrName, aGradType, aDefault); - mLoopFlag = false; - - return grad; + return referenced; } PRInt32 @@ -359,33 +400,12 @@ nsSVGGradientFrame::GetStopFrame(PRInt32 aIndex, nsIFrame * *aStopFrame) // Our gradient element doesn't have stops - try to "inherit" them - nsSVGGradientFrame *next = GetReferencedGradient(); - if (!next) { - if (aStopFrame) - *aStopFrame = nsnull; - return 0; - } + AutoGradientReferencer gradientRef(this); + nsSVGGradientFrame* next = GetReferencedGradientIfNotInUse(); + if (!next) + return nsnull; - // Set mLoopFlag before checking mNextGrad->mLoopFlag in case we are mNextGrad - mLoopFlag = true; - // XXXjwatt: we should really send an error to the JavaScript Console here: - NS_WARN_IF_FALSE(!next->mLoopFlag, "gradient reference loop detected " - "while inheriting stop!"); - if (!next->mLoopFlag) - stopCount = next->GetStopFrame(aIndex, aStopFrame); - mLoopFlag = false; - - return stopCount; -} - -PRUint16 -nsSVGGradientFrame::GetGradientUnits() -{ - // This getter is called every time the others are called - maybe cache it? - - nsSVGGradientElement *element = - GetGradientWithAttr(nsGkAtoms::gradientUnits, mContent); - return element->mEnumAttributes[nsSVGGradientElement::GRADIENTUNITS].GetAnimValue(); + return next->GetStopFrame(aIndex, aStopFrame); } // ------------------------------------------------------------------------- @@ -431,11 +451,16 @@ nsSVGLinearGradientFrame::AttributeChanged(PRInt32 aNameSpaceID, //---------------------------------------------------------------------- float -nsSVGLinearGradientFrame::GradientLookupAttribute(nsIAtom *aAtomName, - PRUint16 aEnumName) +nsSVGLinearGradientFrame::GetLengthValue(PRUint32 aIndex) { - nsSVGLinearGradientElement *element = - GetLinearGradientWithAttr(aAtomName, mContent); + nsSVGLinearGradientElement* lengthElement = + GetLinearGradientWithLength(aIndex, + static_cast(mContent)); + // We passed in mContent as a fallback, so, assuming mContent is non-null, the + // return value should also be non-null. + NS_ABORT_IF_FALSE(lengthElement, + "Got unexpected null element from GetLinearGradientWithLength"); + const nsSVGLength2 &length = lengthElement->mLengthAttributes[aIndex]; // Object bounding box units are handled by setting the appropriate // transform in GetGradientTransform, but we need to handle user @@ -443,15 +468,30 @@ nsSVGLinearGradientFrame::GradientLookupAttribute(nsIAtom *aAtomName, PRUint16 gradientUnits = GetGradientUnits(); if (gradientUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) { - return nsSVGUtils::UserSpace(mSource, - &element->mLengthAttributes[aEnumName]); + return nsSVGUtils::UserSpace(mSource, &length); } - NS_ASSERTION(gradientUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX, - "Unknown gradientUnits type"); + NS_ASSERTION( + gradientUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX, + "Unknown gradientUnits type"); - return element->mLengthAttributes[aEnumName]. - GetAnimValue(static_cast(nsnull)); + return length.GetAnimValue(static_cast(nsnull)); +} + +nsSVGLinearGradientElement * +nsSVGLinearGradientFrame::GetLinearGradientWithLength(PRUint32 aIndex, + nsSVGLinearGradientElement* aDefault) +{ + nsSVGLinearGradientElement* thisElement = + static_cast(mContent); + const nsSVGLength2 &length = thisElement->mLengthAttributes[aIndex]; + + if (length.IsExplicitlySet()) { + return thisElement; + } + + return nsSVGLinearGradientFrameBase::GetLinearGradientWithLength(aIndex, + aDefault); } already_AddRefed @@ -459,10 +499,10 @@ nsSVGLinearGradientFrame::CreateGradient() { float x1, y1, x2, y2; - x1 = GradientLookupAttribute(nsGkAtoms::x1, nsSVGLinearGradientElement::X1); - y1 = GradientLookupAttribute(nsGkAtoms::y1, nsSVGLinearGradientElement::Y1); - x2 = GradientLookupAttribute(nsGkAtoms::x2, nsSVGLinearGradientElement::X2); - y2 = GradientLookupAttribute(nsGkAtoms::y2, nsSVGLinearGradientElement::Y2); + x1 = GetLengthValue(nsSVGLinearGradientElement::X1); + y1 = GetLengthValue(nsSVGLinearGradientElement::Y1); + x2 = GetLengthValue(nsSVGLinearGradientElement::X2); + y2 = GetLengthValue(nsSVGLinearGradientElement::Y2); gfxPattern *pattern = new gfxPattern(x1, y1, x2, y2); NS_IF_ADDREF(pattern); @@ -513,17 +553,33 @@ nsSVGRadialGradientFrame::AttributeChanged(PRInt32 aNameSpaceID, //---------------------------------------------------------------------- float -nsSVGRadialGradientFrame::GradientLookupAttribute(nsIAtom *aAtomName, - PRUint16 aEnumName, - nsSVGRadialGradientElement *aElement) +nsSVGRadialGradientFrame::GetLengthValue(PRUint32 aIndex) { - nsSVGRadialGradientElement *element; + nsSVGRadialGradientElement* lengthElement = + GetRadialGradientWithLength(aIndex, + static_cast(mContent)); + // We passed in mContent as a fallback, so, assuming mContent is non-null, + // the return value should also be non-null. + NS_ABORT_IF_FALSE(lengthElement, + "Got unexpected null element from GetRadialGradientWithLength"); + return GetLengthValueFromElement(aIndex, *lengthElement); +} - if (aElement) { - element = aElement; - } else { - element = GetRadialGradientWithAttr(aAtomName, mContent); - } +float +nsSVGRadialGradientFrame::GetLengthValue(PRUint32 aIndex, float aDefaultValue) +{ + nsSVGRadialGradientElement* lengthElement = + GetRadialGradientWithLength(aIndex, nsnull); + + return lengthElement ? GetLengthValueFromElement(aIndex, *lengthElement) + : aDefaultValue; +} + +float +nsSVGRadialGradientFrame::GetLengthValueFromElement(PRUint32 aIndex, + nsSVGRadialGradientElement& aElement) +{ + const nsSVGLength2 &length = aElement.mLengthAttributes[aIndex]; // Object bounding box units are handled by setting the appropriate // transform in GetGradientTransform, but we need to handle user @@ -531,15 +587,30 @@ nsSVGRadialGradientFrame::GradientLookupAttribute(nsIAtom *aAtomName, PRUint16 gradientUnits = GetGradientUnits(); if (gradientUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) { - return nsSVGUtils::UserSpace(mSource, - &element->mLengthAttributes[aEnumName]); + return nsSVGUtils::UserSpace(mSource, &length); } - NS_ASSERTION(gradientUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX, - "Unknown gradientUnits type"); + NS_ASSERTION( + gradientUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX, + "Unknown gradientUnits type"); - return element->mLengthAttributes[aEnumName]. - GetAnimValue(static_cast(nsnull)); + return length.GetAnimValue(static_cast(nsnull)); +} + +nsSVGRadialGradientElement * +nsSVGRadialGradientFrame::GetRadialGradientWithLength(PRUint32 aIndex, + nsSVGRadialGradientElement* aDefault) +{ + nsSVGRadialGradientElement* thisElement = + static_cast(mContent); + const nsSVGLength2 &length = thisElement->mLengthAttributes[aIndex]; + + if (length.IsExplicitlySet()) { + return thisElement; + } + + return nsSVGRadialGradientFrameBase::GetRadialGradientWithLength(aIndex, + aDefault); } already_AddRefed @@ -547,21 +618,12 @@ nsSVGRadialGradientFrame::CreateGradient() { float cx, cy, r, fx, fy; - cx = GradientLookupAttribute(nsGkAtoms::cx, nsSVGRadialGradientElement::CX); - cy = GradientLookupAttribute(nsGkAtoms::cy, nsSVGRadialGradientElement::CY); - r = GradientLookupAttribute(nsGkAtoms::r, nsSVGRadialGradientElement::R); - - nsSVGRadialGradientElement *gradient; - - if (!(gradient = GetRadialGradientWithAttr(nsGkAtoms::fx, nsnull))) - fx = cx; // if fx isn't set, we must use cx - else - fx = GradientLookupAttribute(nsGkAtoms::fx, nsSVGRadialGradientElement::FX, gradient); - - if (!(gradient = GetRadialGradientWithAttr(nsGkAtoms::fy, nsnull))) - fy = cy; // if fy isn't set, we must use cy - else - fy = GradientLookupAttribute(nsGkAtoms::fy, nsSVGRadialGradientElement::FY, gradient); + cx = GetLengthValue(nsSVGRadialGradientElement::CX); + cy = GetLengthValue(nsSVGRadialGradientElement::CY); + r = GetLengthValue(nsSVGRadialGradientElement::R); + // If fx or fy are not set, use cx/cy instead + fx = GetLengthValue(nsSVGRadialGradientElement::FX, cx); + fy = GetLengthValue(nsSVGRadialGradientElement::FY, cy); if (fx != cx || fy != cy) { // The focal point (fFx and fFy) must be clamped to be *inside* - not on - diff --git a/layout/svg/base/src/nsSVGGradientFrame.h b/layout/svg/base/src/nsSVGGradientFrame.h index 07294fa29c42..3a19414417cb 100644 --- a/layout/svg/base/src/nsSVGGradientFrame.h +++ b/layout/svg/base/src/nsSVGGradientFrame.h @@ -86,45 +86,48 @@ private: // the referenced gradient's frame if available, null otherwise. nsSVGGradientFrame* GetReferencedGradient(); - // Helpers to look at our gradient and then along its reference chain (if any) - // to find the first gradient with the specified attribute. - // Returns aDefault if no content with that attribute is found - nsSVGGradientElement* GetGradientWithAttr(nsIAtom *aAttrName, nsIContent *aDefault); - - // Some attributes are only valid on one type of gradient, and we *must* get - // the right type or we won't have the data structures we require. - // Returns aDefault if no content with that attribute is found - nsSVGGradientElement* GetGradientWithAttr(nsIAtom *aAttrName, nsIAtom *aGradType, - nsIContent *aDefault); - // Optionally get a stop frame (returns stop index/count) PRInt32 GetStopFrame(PRInt32 aIndex, nsIFrame * *aStopFrame); - PRUint16 GetSpreadMethod(); PRUint32 GetStopCount(); void GetStopInformation(PRInt32 aIndex, float *aOffset, nscolor *aColor, float *aStopOpacity); + const mozilla::SVGAnimatedTransformList* GetGradientTransformList( + nsIContent* aDefault); // Will be singular for gradientUnits="objectBoundingBox" with an empty bbox. - gfxMatrix GetGradientTransform(nsIFrame *aSource, const gfxRect *aOverrideBounds); + gfxMatrix GetGradientTransform(nsIFrame *aSource, + const gfxRect *aOverrideBounds); protected: virtual already_AddRefed CreateGradient() = 0; - // Use these inline methods instead of GetGradientWithAttr(..., aGradType) - nsSVGLinearGradientElement* GetLinearGradientWithAttr(nsIAtom *aAttrName, nsIContent *aDefault) + // Internal methods for handling referenced gradients + class AutoGradientReferencer; + nsSVGGradientFrame* GetReferencedGradientIfNotInUse(); + + // Accessors to lookup gradient attributes + PRUint16 GetEnumValue(PRUint32 aIndex, nsIContent *aDefault); + PRUint16 GetEnumValue(PRUint32 aIndex) { - return static_cast( - GetGradientWithAttr(aAttrName, nsGkAtoms::svgLinearGradientFrame, aDefault)); + return GetEnumValue(aIndex, mContent); } - nsSVGRadialGradientElement* GetRadialGradientWithAttr(nsIAtom *aAttrName, nsIContent *aDefault) + PRUint16 GetGradientUnits() { - return static_cast( - GetGradientWithAttr(aAttrName, nsGkAtoms::svgRadialGradientFrame, aDefault)); + // This getter is called every time the others are called - maybe cache it? + return GetEnumValue(nsSVGGradientElement::GRADIENTUNITS); + } + PRUint16 GetSpreadMethod() + { + return GetEnumValue(nsSVGGradientElement::SPREADMETHOD); } - // Get the value of our gradientUnits attribute - PRUint16 GetGradientUnits(); + // Gradient-type-specific lookups since the length values differ between + // linear and radial gradients + virtual nsSVGLinearGradientElement * GetLinearGradientWithLength( + PRUint32 aIndex, nsSVGLinearGradientElement* aDefault); + virtual nsSVGRadialGradientElement * GetRadialGradientWithLength( + PRUint32 aIndex, nsSVGRadialGradientElement* aDefault); // The frame our gradient is (currently) being applied to nsIFrame* mSource; @@ -178,7 +181,9 @@ public: #endif // DEBUG protected: - float GradientLookupAttribute(nsIAtom *aAtomName, PRUint16 aEnumName); + float GetLengthValue(PRUint32 aIndex); + virtual nsSVGLinearGradientElement * GetLinearGradientWithLength( + PRUint32 aIndex, nsSVGLinearGradientElement* aDefault); virtual already_AddRefed CreateGradient(); }; @@ -220,8 +225,12 @@ public: #endif // DEBUG protected: - float GradientLookupAttribute(nsIAtom *aAtomName, PRUint16 aEnumName, - nsSVGRadialGradientElement *aElement = nsnull); + float GetLengthValue(PRUint32 aIndex); + float GetLengthValue(PRUint32 aIndex, float aDefaultValue); + float GetLengthValueFromElement(PRUint32 aIndex, + nsSVGRadialGradientElement& aElement); + virtual nsSVGRadialGradientElement * GetRadialGradientWithLength( + PRUint32 aIndex, nsSVGRadialGradientElement* aDefault); virtual already_AddRefed CreateGradient(); }; diff --git a/layout/svg/base/src/nsSVGUtils.cpp b/layout/svg/base/src/nsSVGUtils.cpp index 984cb86b18dc..9603735e899c 100644 --- a/layout/svg/base/src/nsSVGUtils.cpp +++ b/layout/svg/base/src/nsSVGUtils.cpp @@ -1215,6 +1215,11 @@ nsSVGUtils::HitTestChildren(nsIFrame *aFrame, const nsPoint &aPoint) current = current->GetPrevSibling()) { nsISVGChildFrame* SVGFrame = do_QueryFrame(current); if (SVGFrame) { + const nsIContent* content = current->GetContent(); + if (content->IsSVG() && + !static_cast(content)->HasValidDimensions()) { + continue; + } result = SVGFrame->GetFrameForPoint(aPoint); if (result) break; diff --git a/layout/svg/crashtests/732836-1.svg b/layout/svg/crashtests/732836-1.svg new file mode 100644 index 000000000000..a9abb46668c7 --- /dev/null +++ b/layout/svg/crashtests/732836-1.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/layout/svg/crashtests/crashtests.list b/layout/svg/crashtests/crashtests.list index ed07648dd113..467879c3006a 100644 --- a/layout/svg/crashtests/crashtests.list +++ b/layout/svg/crashtests/crashtests.list @@ -124,3 +124,4 @@ load 709920-2.svg load 713413-1.svg load 722003-1.svg load 725918-1.svg +load 732836-1.svg diff --git a/layout/tables/nsTableFrame.cpp b/layout/tables/nsTableFrame.cpp index 53062d114515..66436822ce04 100644 --- a/layout/tables/nsTableFrame.cpp +++ b/layout/tables/nsTableFrame.cpp @@ -1045,7 +1045,7 @@ nsTableFrame::InsertRowGroups(const nsFrameList::Slice& aRowGroups) ///////////////////////////////////////////////////////////////////////////// // Child frame enumeration -nsFrameList +const nsFrameList& nsTableFrame::GetChildList(ChildListID aListID) const { if (aListID == kColGroupList) { diff --git a/layout/tables/nsTableFrame.h b/layout/tables/nsTableFrame.h index 872ca469b929..e52d8f868e35 100644 --- a/layout/tables/nsTableFrame.h +++ b/layout/tables/nsTableFrame.h @@ -244,7 +244,7 @@ public: NS_IMETHOD SetInitialChildList(ChildListID aListID, nsFrameList& aChildList); - virtual nsFrameList GetChildList(ChildListID aListID) const; + virtual const nsFrameList& GetChildList(ChildListID aListID) const; virtual void GetChildLists(nsTArray* aLists) const; NS_IMETHOD BuildDisplayList(nsDisplayListBuilder* aBuilder, diff --git a/layout/tables/nsTableOuterFrame.cpp b/layout/tables/nsTableOuterFrame.cpp index c1bc6f30d010..49206de5e3c6 100644 --- a/layout/tables/nsTableOuterFrame.cpp +++ b/layout/tables/nsTableOuterFrame.cpp @@ -215,7 +215,7 @@ nsTableOuterFrame::DestroyFrom(nsIFrame* aDestructRoot) nsContainerFrame::DestroyFrom(aDestructRoot); } -nsFrameList +const nsFrameList& nsTableOuterFrame::GetChildList(ChildListID aListID) const { if (aListID == kCaptionList) { diff --git a/layout/tables/nsTableOuterFrame.h b/layout/tables/nsTableOuterFrame.h index fa33ce7015fd..d7be9e5ba845 100644 --- a/layout/tables/nsTableOuterFrame.h +++ b/layout/tables/nsTableOuterFrame.h @@ -102,7 +102,7 @@ public: NS_IMETHOD SetInitialChildList(ChildListID aListID, nsFrameList& aChildList); - virtual nsFrameList GetChildList(ChildListID aListID) const; + virtual const nsFrameList& GetChildList(ChildListID aListID) const; virtual void GetChildLists(nsTArray* aLists) const; NS_IMETHOD AppendFrames(ChildListID aListID, diff --git a/layout/xul/base/src/nsBoxFrame.h b/layout/xul/base/src/nsBoxFrame.h index bb58524c590a..3938e84dbf41 100644 --- a/layout/xul/base/src/nsBoxFrame.h +++ b/layout/xul/base/src/nsBoxFrame.h @@ -61,7 +61,7 @@ class nsBoxLayoutState; #define NS_STATE_CURRENTLY_IN_DEBUG NS_FRAME_STATE_BIT(25) //#define NS_STATE_SET_TO_DEBUG NS_FRAME_STATE_BIT(26) moved to nsBox.h //#define NS_STATE_DEBUG_WAS_SET NS_FRAME_STATE_BIT(27) moved to nsBox.h -// NS_FRAME_STATE_BIT(28) not used anymore +#define NS_STATE_MENU_HAS_POPUP_LIST NS_FRAME_STATE_BIT(28) /* used on nsMenuFrame */ #define NS_STATE_BOX_WRAPS_KIDS_IN_BLOCK NS_FRAME_STATE_BIT(29) #define NS_STATE_EQUAL_SIZE NS_FRAME_STATE_BIT(30) //#define NS_STATE_IS_DIRECTION_NORMAL NS_FRAME_STATE_BIT(31) moved to nsIFrame.h diff --git a/layout/xul/base/src/nsMenuFrame.cpp b/layout/xul/base/src/nsMenuFrame.cpp index 65efb99b610d..9b15135f3719 100644 --- a/layout/xul/base/src/nsMenuFrame.cpp +++ b/layout/xul/base/src/nsMenuFrame.cpp @@ -87,6 +87,13 @@ using namespace mozilla; #define NSCONTEXTMENUISMOUSEUP 1 #endif +static void +AssertNotCalled(void* aPropertyValue) +{ + NS_ERROR("popup list should never be destroyed by the FramePropertyTable"); +} +NS_DECLARE_FRAME_PROPERTY(PopupListProperty, AssertNotCalled) + static PRInt32 gEatMouseMove = false; const PRInt32 kBlinkDelay = 67; // milliseconds @@ -207,9 +214,6 @@ NS_QUERYFRAME_HEAD(nsMenuFrame) NS_QUERYFRAME_ENTRY(nsMenuFrame) NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) -// -// nsMenuFrame cntr -// nsMenuFrame::nsMenuFrame(nsIPresShell* aShell, nsStyleContext* aContext): nsBoxFrame(aShell, aContext), mIsMenu(false), @@ -217,11 +221,9 @@ nsMenuFrame::nsMenuFrame(nsIPresShell* aShell, nsStyleContext* aContext): mIgnoreAccelTextChange(false), mType(eMenuType_Normal), mMenuParent(nsnull), - mPopupFrame(nsnull), mBlinkState(0) { - -} // cntr +} void nsMenuFrame::SetParent(nsIFrame* aParent) @@ -298,11 +300,12 @@ nsMenuFrame::Init(nsIContent* aContent, return rv; } -nsFrameList +const nsFrameList& nsMenuFrame::GetChildList(ChildListID aListID) const { if (kPopupList == aListID) { - return nsFrameList(mPopupFrame, mPopupFrame); + nsFrameList* list = GetPopupList(); + return list ? *list : nsFrameList::EmptyList(); } return nsBoxFrame::GetChildList(aListID); } @@ -311,8 +314,44 @@ void nsMenuFrame::GetChildLists(nsTArray* aLists) const { nsBoxFrame::GetChildLists(aLists); - nsFrameList popupList(mPopupFrame, mPopupFrame); - popupList.AppendIfNonempty(aLists, kPopupList); + nsFrameList* list = GetPopupList(); + if (list) { + list->AppendIfNonempty(aLists, kPopupList); + } +} + +nsMenuPopupFrame* +nsMenuFrame::GetPopup() +{ + nsFrameList* popupList = GetPopupList(); + return popupList ? static_cast(popupList->FirstChild()) : + nsnull; +} + +nsFrameList* +nsMenuFrame::GetPopupList() const +{ + if (!HasPopup()) { + return nsnull; + } + nsFrameList* prop = + static_cast(Properties().Get(PopupListProperty())); + NS_ASSERTION(prop && prop->GetLength() == 1 && + prop->FirstChild()->GetType() == nsGkAtoms::menuPopupFrame, + "popup list should have exactly one nsMenuPopupFrame"); + return prop; +} + +void +nsMenuFrame::DestroyPopupList() +{ + NS_ASSERTION(HasPopup(), "huh?"); + nsFrameList* prop = + static_cast(Properties().Remove(PopupListProperty())); + NS_ASSERTION(prop && prop->IsEmpty(), + "popup list must exist and be empty when destroying"); + RemoveStateBits(NS_STATE_MENU_HAS_POPUP_LIST); + delete prop; } void @@ -320,9 +359,12 @@ nsMenuFrame::SetPopupFrame(nsFrameList& aFrameList) { for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { if (e.get()->GetType() == nsGkAtoms::menuPopupFrame) { - // Remove this frame from the list and set it as mPopupFrame - mPopupFrame = (nsMenuPopupFrame *)e.get(); - aFrameList.RemoveFrame(e.get()); + // Remove the frame from the list and store it in a nsFrameList* property. + nsIFrame* popupFrame = e.get(); + aFrameList.RemoveFrame(popupFrame); + nsFrameList* popupList = new nsFrameList(popupFrame, popupFrame); + Properties().Set(PopupListProperty(), popupList); + AddStateBits(NS_STATE_MENU_HAS_POPUP_LIST); break; } } @@ -332,7 +374,7 @@ NS_IMETHODIMP nsMenuFrame::SetInitialChildList(ChildListID aListID, nsFrameList& aChildList) { - NS_ASSERTION(!mPopupFrame, "already have a popup frame set"); + NS_ASSERTION(!HasPopup(), "SetInitialChildList called twice?"); if (aListID == kPrincipalList || aListID == kPopupList) { SetPopupFrame(aChildList); } @@ -365,8 +407,11 @@ nsMenuFrame::DestroyFrom(nsIFrame* aDestructRoot) mMenuParent->CurrentMenuIsBeingDestroyed(); } - if (mPopupFrame) - mPopupFrame->DestroyFrom(aDestructRoot); + nsFrameList* popupList = GetPopupList(); + if (popupList) { + popupList->DestroyFramesFrom(aDestructRoot); + DestroyPopupList(); + } nsBoxFrame::DestroyFrom(aDestructRoot); } @@ -675,8 +720,8 @@ nsMenuFrame::CloseMenu(bool aDeselectMenu) // Close the menu asynchronously nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm && mPopupFrame) - pm->HidePopup(mPopupFrame->GetContent(), false, aDeselectMenu, true); + if (pm && HasPopup()) + pm->HidePopup(GetPopup()->GetContent(), false, aDeselectMenu, true); } bool @@ -713,9 +758,10 @@ nsMenuFrame::DoLayout(nsBoxLayoutState& aState) // lay us out nsresult rv = nsBoxFrame::DoLayout(aState); - if (mPopupFrame) { + nsMenuPopupFrame* popupFrame = GetPopup(); + if (popupFrame) { bool sizeToPopup = IsSizedToPopup(mContent, false); - mPopupFrame->LayoutPopup(aState, this, sizeToPopup); + popupFrame->LayoutPopup(aState, this, sizeToPopup); } return rv; @@ -733,8 +779,9 @@ nsMenuFrame::SetDebug(nsBoxLayoutState& aState, bool aDebug) if (debugChanged) { nsBoxFrame::SetDebug(aState, aDebug); - if (mPopupFrame) - SetDebug(aState, mPopupFrame, aDebug); + nsMenuPopupFrame* popupFrame = GetPopup(); + if (popupFrame) + SetDebug(aState, popupFrame, aDebug); } return NS_OK; @@ -797,7 +844,8 @@ nsMenuFrame::Enter(nsGUIEvent *aEvent) bool nsMenuFrame::IsOpen() { - return mPopupFrame && mPopupFrame->IsOpen(); + nsMenuPopupFrame* popupFrame = GetPopup(); + return popupFrame && popupFrame->IsOpen(); } bool @@ -1248,21 +1296,15 @@ NS_IMETHODIMP nsMenuFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) { - nsresult rv = NS_OK; - - if (mPopupFrame == aOldFrame) { - // Go ahead and remove this frame. - mPopupFrame->Destroy(); - mPopupFrame = nsnull; + nsFrameList* popupList = GetPopupList(); + if (popupList && popupList->DestroyFrameIfPresent(aOldFrame)) { + DestroyPopupList(); PresContext()->PresShell()-> FrameNeedsReflow(this, nsIPresShell::eTreeChange, NS_FRAME_HAS_DIRTY_CHILDREN); - rv = NS_OK; - } else { - rv = nsBoxFrame::RemoveFrame(aListID, aOldFrame); + return NS_OK; } - - return rv; + return nsBoxFrame::RemoveFrame(aListID, aOldFrame); } NS_IMETHODIMP @@ -1270,9 +1312,9 @@ nsMenuFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, nsFrameList& aFrameList) { - if (!mPopupFrame && (aListID == kPrincipalList || aListID == kPopupList)) { + if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) { SetPopupFrame(aFrameList); - if (mPopupFrame) { + if (HasPopup()) { #ifdef DEBUG_LAYOUT nsBoxLayoutState state(PresContext()); SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG); @@ -1287,7 +1329,7 @@ nsMenuFrame::InsertFrames(ChildListID aListID, if (aFrameList.IsEmpty()) return NS_OK; - if (NS_UNLIKELY(aPrevFrame == mPopupFrame)) { + if (NS_UNLIKELY(aPrevFrame && aPrevFrame == GetPopup())) { aPrevFrame = nsnull; } @@ -1298,9 +1340,9 @@ NS_IMETHODIMP nsMenuFrame::AppendFrames(ChildListID aListID, nsFrameList& aFrameList) { - if (!mPopupFrame && (aListID == kPrincipalList || aListID == kPopupList)) { + if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) { SetPopupFrame(aFrameList); - if (mPopupFrame) { + if (HasPopup()) { #ifdef DEBUG_LAYOUT nsBoxLayoutState state(PresContext()); @@ -1326,9 +1368,10 @@ nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize) nsSize tmpSize(-1, 0); nsIBox::AddCSSPrefSize(this, tmpSize, widthSet, heightSet); if (!widthSet && GetFlex(aState) == 0) { - if (!mPopupFrame) + nsMenuPopupFrame* popupFrame = GetPopup(); + if (!popupFrame) return false; - tmpSize = mPopupFrame->GetPrefSize(aState); + tmpSize = popupFrame->GetPrefSize(aState); // Produce a size such that: // (1) the menu and its popup can be the same width @@ -1340,7 +1383,7 @@ nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize) GetBorderAndPadding(borderPadding); // if there is a scroll frame, add the desired width of the scrollbar as well - nsIScrollableFrame* scrollFrame = do_QueryFrame(mPopupFrame->GetFirstPrincipalChild()); + nsIScrollableFrame* scrollFrame = do_QueryFrame(popupFrame->GetFirstPrincipalChild()); nscoord scrollbarWidth = 0; if (scrollFrame) { scrollbarWidth = @@ -1380,10 +1423,11 @@ nsMenuFrame::GetPrefSize(nsBoxLayoutState& aState) NS_IMETHODIMP nsMenuFrame::GetActiveChild(nsIDOMElement** aResult) { - if (!mPopupFrame) + nsMenuPopupFrame* popupFrame = GetPopup(); + if (!popupFrame) return NS_ERROR_FAILURE; - nsMenuFrame* menuFrame = mPopupFrame->GetCurrentMenuItem(); + nsMenuFrame* menuFrame = popupFrame->GetCurrentMenuItem(); if (!menuFrame) { *aResult = nsnull; } @@ -1399,12 +1443,13 @@ nsMenuFrame::GetActiveChild(nsIDOMElement** aResult) NS_IMETHODIMP nsMenuFrame::SetActiveChild(nsIDOMElement* aChild) { - if (!mPopupFrame) + nsMenuPopupFrame* popupFrame = GetPopup(); + if (!popupFrame) return NS_ERROR_FAILURE; if (!aChild) { // Remove the current selection - mPopupFrame->ChangeMenuItem(nsnull, false); + popupFrame->ChangeMenuItem(nsnull, false); return NS_OK; } @@ -1412,17 +1457,18 @@ nsMenuFrame::SetActiveChild(nsIDOMElement* aChild) nsIFrame* kid = child->GetPrimaryFrame(); if (kid && kid->GetType() == nsGkAtoms::menuFrame) - mPopupFrame->ChangeMenuItem(static_cast(kid), false); + popupFrame->ChangeMenuItem(static_cast(kid), false); return NS_OK; } nsIScrollableFrame* nsMenuFrame::GetScrollTargetFrame() { - if (!mPopupFrame) + nsMenuPopupFrame* popupFrame = GetPopup(); + if (!popupFrame) return nsnull; - nsIFrame* childFrame = mPopupFrame->GetFirstPrincipalChild(); + nsIFrame* childFrame = popupFrame->GetFirstPrincipalChild(); if (childFrame) - return mPopupFrame->GetScrollFrame(childFrame); + return popupFrame->GetScrollFrame(childFrame); return nsnull; } diff --git a/layout/xul/base/src/nsMenuFrame.h b/layout/xul/base/src/nsMenuFrame.h index 9731c41bf9d5..dc5476dd9132 100644 --- a/layout/xul/base/src/nsMenuFrame.h +++ b/layout/xul/base/src/nsMenuFrame.h @@ -130,7 +130,7 @@ public: // The following methods are all overridden so that the menupopup // can be stored in a separate list, so that it doesn't impact reflow of the // actual menu item at all. - virtual nsFrameList GetChildList(ChildListID aList) const; + virtual const nsFrameList& GetChildList(ChildListID aList) const; virtual void GetChildLists(nsTArray* aLists) const; NS_IMETHOD SetInitialChildList(ChildListID aListID, nsFrameList& aChildList); @@ -185,7 +185,16 @@ public: virtual nsMenuParent *GetMenuParent() { return mMenuParent; } const nsAString& GetRadioGroupName() { return mGroupName; } nsMenuType GetMenuType() { return mType; } - nsMenuPopupFrame* GetPopup() { return mPopupFrame; } + nsMenuPopupFrame* GetPopup(); + + /** + * @return true if this frame has a popup child frame. + */ + bool HasPopup() const + { + return (GetStateBits() & NS_STATE_MENU_HAS_POPUP_LIST) != 0; + } + // nsMenuFrame methods @@ -226,10 +235,23 @@ protected: friend class nsASyncMenuInitialization; friend class nsMenuAttributeChangedEvent; - // initialize mPopupFrame to the first popup frame within - // aChildList. Removes the popup, if any, from aChildList. + /** + * Initialize the popup list to the first popup frame within + * aChildList. Removes the popup, if any, from aChildList. + */ void SetPopupFrame(nsFrameList& aChildList); + /** + * Get the popup frame list from the frame property. + * @return the property value if it exists, nsnull otherwise. + */ + nsFrameList* GetPopupList() const; + + /** + * Destroy the popup list property. The list must exist and be empty. + */ + void DestroyPopupList(); + // set mMenuParent to the nearest enclosing menu bar or menupopup frame of // aParent (or aParent itself). This is called when initializing the frame, // so aParent should be the expected parent of this frame. @@ -275,9 +297,6 @@ protected: nsMenuParent* mMenuParent; // Our parent menu. - // the popup for this menu, owned - nsMenuPopupFrame* mPopupFrame; - // Reference to the mediator which wraps this frame. nsRefPtr mTimerMediator; diff --git a/media/libtheora/bug468275-r18219.patch b/media/libtheora/bug468275-r18219.patch new file mode 100644 index 000000000000..7b64b4195a2c --- /dev/null +++ b/media/libtheora/bug468275-r18219.patch @@ -0,0 +1,22 @@ +diff --git a/media/libtheora/lib/state.c b/media/libtheora/lib/state.c +--- a/media/libtheora/lib/state.c ++++ b/media/libtheora/lib/state.c +@@ -583,17 +583,17 @@ static int oc_state_ref_bufs_init(oc_the + ref_frame_szfrag_buf_offs= + _ogg_malloc(_state->nfrags*sizeof(*frag_buf_offs)); + if(ref_frame_data==NULL||frag_buf_offs==NULL){ + _ogg_free(frag_buf_offs); +- _ogg_free(ref_frame_data); ++ oc_aligned_free(ref_frame_data); + return TH_EFAULT; + } + /*Set up the width, height and stride for the image buffers.*/ + _state->ref_frame_bufs[0][0].width=info->frame_width; + _state->ref_frame_bufs[0][0].height=info->frame_height; + _state->ref_frame_bufs[0][0].stride=yhstride; + _state->ref_frame_bufs[0][1].width=_state->ref_frame_bufs[0][2].width= + info->frame_width>>hdec; diff --git a/media/libtheora/lib/state.c b/media/libtheora/lib/state.c index fa47913b03ed..5e7b0ae65185 100644 --- a/media/libtheora/lib/state.c +++ b/media/libtheora/lib/state.c @@ -588,7 +588,7 @@ static int oc_state_ref_bufs_init(oc_theora_state *_state,int _nrefs){ _ogg_malloc(_state->nfrags*sizeof(*frag_buf_offs)); if(ref_frame_data==NULL||frag_buf_offs==NULL){ _ogg_free(frag_buf_offs); - _ogg_free(ref_frame_data); + oc_aligned_free(ref_frame_data); return TH_EFAULT; } /*Set up the width, height and stride for the image buffers.*/ diff --git a/media/libtheora/update.sh b/media/libtheora/update.sh index 5ba4725fc7d2..17064fa513c9 100644 --- a/media/libtheora/update.sh +++ b/media/libtheora/update.sh @@ -79,3 +79,4 @@ cp $1/include/theora/theoradec.h ./include/theora/theoradec.h cp $1/include/theora/theoraenc.h ./include/theora/theoraenc.h cp $1/include/theora/codec.h ./include/theora/codec.h patch -p3 < ./bug625773-r17780.patch +patch -p3 < ./bug468275-r18219.patch diff --git a/memory/jemalloc/jemalloc.c b/memory/jemalloc/jemalloc.c index 8d0f2decd53f..be7f9a3e804f 100644 --- a/memory/jemalloc/jemalloc.c +++ b/memory/jemalloc/jemalloc.c @@ -989,10 +989,10 @@ struct arena_bin_s { }; struct arena_s { - /* For bug 703087, we're temporarily adding arena.magic to release - builds. */ +#ifdef MALLOC_DEBUG uint32_t magic; # define ARENA_MAGIC 0x947d3d24 +#endif /* All operations on this arena require that lock be locked. */ #ifdef MOZ_MEMORY @@ -4413,15 +4413,7 @@ isalloc_validate(const void *ptr) return (0); if (chunk != ptr) { - /* For bug 703087, we've temporarily made what is normally a - debug-only assertion here into a fatal assertion. */ - if (chunk->arena->magic != ARENA_MAGIC) { - char* boom = (char*) 0; - _malloc_message("isalloc_validate called with invalid pointer. " - "Crashing...\n", "", "", ""); - *boom = 1; - } - + assert(chunk->arena->magic == ARENA_MAGIC); return (arena_salloc(ptr)); } else { size_t ret; @@ -4931,9 +4923,10 @@ arena_new(arena_t *arena) #endif } - /* For bug 703087, we're temporarily adding arena->magic for release - builds. */ +#ifdef MALLOC_DEBUG arena->magic = ARENA_MAGIC; +#endif + return (false); } diff --git a/mfbt/STYLE b/mfbt/STYLE new file mode 100644 index 000000000000..31b3ceec138d --- /dev/null +++ b/mfbt/STYLE @@ -0,0 +1,273 @@ += mfbt style rules = + +== Line length == + +The line limit is 80 characters, except that excessively long blocks of preprocessor directives may exceed this if it makes the code more readable (e.g. MOZ_STATIC_ASSERT in Assertions.h.), and unbreakable text in comments (e.g. URLs) may exceed this as well. Wrap expressions after binary operators. + +== Capitalization == + +Standalone functions, classes, structs, and template parameters are named InterCaps-style. Member functions and fields in classes and structs are named camelCaps-style. + +== Indentation == + +Indentation is two spaces, never tabs. + + if (x == 2) + return 17; + +== Whitespace == + +Surround binary operators with a single space on either side. + + if (x == 2) + return 17; + +When describing pointer types, the * shall be adjacent to the type name. (Same goes for references -- & goes by the type name.) + + int + Foo(int* p) + { + typedef void* VoidPtr; + int& i = *p; + } + +A corollary: don't mix declaration types by declaring a T and a T* (or a T**, &c.) in the same declaration. + + T* foo, bar; // BAD + +== Bracing == + +Don't brace single statements. + + if (y == 7) + return 3; + for (size_t i = 0; i < 5; i++) + frob(i); + +But do brace them if the statement (or condition(s) or any additional consequents, if the braces would be associated with an if statement) occupies multiple lines. + + if (cond1 || + cond2) + { + action(); + } + if (cond1) { + consequent(); + } else { + alternative(arg1, + arg2); + } + if (cond1 || cond2) { + callMethod(arg1, + arg2); + } + for (size_t j = 0; + j < 17; + j++) + { + action(); + } + +Braces in control flow go at the end of the line except when associated with an |if| or loop-head where the condition covers multiple lines + +== Classes and structs == + +Inside class and structure definitions, public/private consume one level of indentation. + + class Baz + { + public: + Baz() { } + }; + +The absence of public/private in structs in which all members are public still consumes a level. + + struct Foo + { + int field; + }; + +Braces delimiting a class or struct go on their own lines. + +Member initialization in constructors should be formatted as follows: + + class Fnord + { + size_t s1, s2, s3, s4, s5; + + public: + Fnord(size_t s) : s1(s), s2(s), s3(s), s4(s), s5(s) { } + Fnord() + : s1(0), /* member initialization can be compressed if desired */ + s2(0), + s3(0), + s4(0), + s5(0) + { + ... + } + }; + +Fields should go first in the class so that the basic structure is all in one place, consistently. + +Use the inline keyword to annotate functions defined inline in a header. (If the function is defined inline in the class, don't bother adding it redundantly.) + +Explicitly delete (using Attributes.h's MOZ_DELETE) the copy constructor and assignment operator from classes not intended to be copied or assigned to avoid mistakes. + + class Funky + { + public: + Funky() { } + + private: + Funky(const Funky& other) MOZ_DELETE; + void operator=(const Funky& other) MOZ_DELETE; + }; + +Include a blank line between sections of structs and classes with different access control. + +The "get" prefix is used when a method is fallible. If it's infallible, don't use it. + + class String + { + public: + size_t length() const; // not getLength() + }; + +== Templates == + +Capitalize template parameter names to distinguish them from fields. + + template + class BloomFilter + { + }; + +Use single-letter names if it makes sense (T for an arbitrary type, K for key type, V for value type, &c.). Otherwise use InterCaps-style names. + +When declaring or defining a function, template<...> goes on one line, the return type and other specifiers go on another line, and the function name and argument list go on a third line. + + template + inline bool + Vector::add(T t) + { + } + +== Namespaces == + +All C++ code shall be in the mozilla namespace, except that functionality only used to implement external-facing API should be in the mozilla::detail namespace, indicating that it should not be directly used. + +Namespace opening braces go on the same line as the namespace declaration. Namespace closing braces shall be commented. Namespace contents are not indented. + + namespace mozilla { + ... + } // namespace mozilla + +Don't use |using| in a header unless it's confined to a class or method. Implementation files for out-of-line functionality may use |using|. + +== #includes == + +Headers that include mfbt headers use a fully-qualified include path, even if full qualification is not strictly necessary. + + #include "mozilla/Assertions.h" + +mfbt headers should be included first, alphabetically. Standard includes should follow, separated from mfbt includes by a blank line. + + #include "mozilla/Assertions.h" + #include "mozilla/Attributes.h" + + #include + +If a header dependency is limited simply to the existence of a class, forward-declare it rather than #include that header. + + namespace mozilla { + + class BloomFilter; + extern bool + Test(BloomFilter* bf); + + } // namespace mozilla + +== Preprocessor == + +Include guards should be named by determining the fully-qualified include path, then substituting _ for / and . in it, and finally appending a trailing _. For example, "mozilla/Assertions.h" becomes mozilla_Assertions_h_. + +Nested preprocessor directives indent the directive name (but not the #) by two spaces. + + #ifdef __clang__ + # define FOO ... + #else + # define FOO ... + #endif + +Comments within nested preprocessor directives align with directive names at that nesting depth. + + #if defined(__GNUC__) + /* gcc supports C++11 override syntax. */ + # define MOZ_OVERRIDE override + #else + # define MOZ_OVERRIDE /* unsupported */ + #endif + +Feature-testing macros may be defined to nothing. Macros intended to be textually expanded should be defined to a comment indicating non-support, as above or as appropriate to the situation. + +No particular preference is expressed between testing for a macro being defined using defined(...) and using #ifdef. + +When defining a macro with different expansions for different compilers, the top level of distinction should be the compiler, and the next nested level should be the compiler version. Clang seems likely to be around for awhile, so to reduce confusion test for it separately from gcc even when it's not strictly necessary. + + #if defined(__clang__) + #elif defined(__GNUC__) + # if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) + # else + # endif + #elif defined(_MSC_VER) + #endif + +But don't distinguish clang's feature support using version checks: use the __has_feature() and __has_extension() macros instead, because vendors may customize clang's version numbers. + +Prefer inline functions to macros whenever possible. + +== Comments == + +Header files shall have a short descriptive comment underneath license boilerplate indicating what functionality the file implements, to be picked up by MXR and displayed in directory listings. (But see bug 717196, which currently prevents MXR from doing this if the MPL2 boilerplate is used.) + + Assertions.h: + ...license boilerplate... + + /* Implementations of runtime and static assertion macros for C and C++. */ + +Classes intended for public use shall have interface comments explaining their functionality from the user's perspective. These comments shall include examples of how the relevant functionality might be used. These interface comments use /** */ doxygen/Javadoc-style comments. + + /** + * The Frobber class simplifies the process of frobbing. + */ + class Frobber + { + }; + +Comments describing implementation details (tradeoffs considered, assumptions made, mathematical background, &c.) occur separately from interface comments so that users need not consider them. They should go inside the class definition or inside the appropriate method, depending on the specificity of the comment. + +Headers which are intended to be C-compatible shall use only /**/-style comments. (Code examples nested inside documentation comments may use //-style comments.) Headers which are C++-compatible may also use //-style comments. + +Non-interface comments that are /**/-style shall not also be doxygen-style. + +Use Python-style ** to denote exponentiation inside comments, not ^ (which can be confused with C-style bitwise xor). If you're writing sufficiently complex math, feel free to descend into LaTeX math mode ;-) inside implementation comments if you need to. (But keep it out of interface comments, because most people probably haven't seen LaTeX.) + +== Miscellaneous == + +Enclose C-compatible code in |extern "C"| blocks, and #ifdef __cplusplus the block start/end as needed. The contents of these blocks should not be indented. + +Add new functionality to new headers unless an existing header makes sense. Err on the side of more headers rather than fewer, as this helps to minimize dependencies. Don't add anything to Util.h, which will be split into multiple headers at some point (bug 713082). + +Don't use bool for argument types unless the method is a "set" or "enable"-style method where the method name and bool value together indicate the sense of its effect. Use well-named enums in all other places, so that the semantics of the argument are clear at a glance and do not require knowing how the method interprets that argument. + + void + setVisible(bool visible); // true clearly means visible, false clearly not + enum Enumerability { + Enumerable, + NonEnumerable + }; + bool + DefineProperty(JSObject* obj, const char* name, Value v, Enumerability e); + +Use NULL for the null pointer constant. diff --git a/mfbt/sources.mk b/mfbt/sources.mk index 747a8b3861d9..faa3f4f3ca9e 100644 --- a/mfbt/sources.mk +++ b/mfbt/sources.mk @@ -1,3 +1,3 @@ -CPPSRCS += \ - Assertions.cpp \ - $(NULL) +CPPSRCS += \ + Assertions.cpp \ + $(NULL) diff --git a/mobile/android/base/AutoCompletePopup.java b/mobile/android/base/AutoCompletePopup.java deleted file mode 100644 index a88be38159c5..000000000000 --- a/mobile/android/base/AutoCompletePopup.java +++ /dev/null @@ -1,188 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- - * ***** 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 Android code. - * - * The Initial Developer of the Original Code is Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Margaret Leibovic - * Sriram Ramasubramanian - * - * 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 ***** */ - -package org.mozilla.gecko; - -import org.mozilla.gecko.gfx.FloatSize; - -import android.content.Context; -import android.util.Log; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.ArrayAdapter; -import android.widget.AdapterView; -import android.widget.RelativeLayout; -import android.widget.ListView; -import android.widget.TextView; - -import org.json.JSONArray; -import org.json.JSONException; - -public class AutoCompletePopup extends ListView { - private Context mContext; - private RelativeLayout.LayoutParams mLayout; - - private int mWidth; - private int mHeight; - - private Animation mAnimation; - - private static final String LOGTAG = "AutoCompletePopup"; - - private static int sMinWidth = 0; - private static int sRowHeight = 0; - private static final int AUTOCOMPLETE_MIN_WIDTH_IN_DPI = 200; - private static final int AUTOCOMPLETE_ROW_HEIGHT_IN_DPI = 32; - - public AutoCompletePopup(Context context, AttributeSet attrs) { - super(context, attrs); - mContext = context; - - mAnimation = AnimationUtils.loadAnimation(context, R.anim.grow_fade_in); - mAnimation.setDuration(75); - - setFocusable(false); - - setOnItemClickListener(new OnItemClickListener() { - public void onItemClick(AdapterView parentView, View view, int position, long id) { - String value = ((TextView) view).getText().toString(); - GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FormAssist:AutoComplete", value)); - hide(); - } - }); - } - - public void show(JSONArray suggestions, JSONArray rect, double zoom) { - ArrayAdapter adapter = new ArrayAdapter(mContext, R.layout.autocomplete_list_item); - for (int i = 0; i < suggestions.length(); i++) { - try { - adapter.add(suggestions.get(i).toString()); - } catch (JSONException e) { - Log.i(LOGTAG, "JSONException: " + e); - } - } - - setAdapter(adapter); - - if (!isShown()) { - setVisibility(View.VISIBLE); - startAnimation(mAnimation); - } - - if (mLayout == null) { - mLayout = (RelativeLayout.LayoutParams) getLayoutParams(); - mWidth = mLayout.width; - mHeight = mLayout.height; - } - - int left = 0; - int top = 0; - int width = 0; - int height = 0; - - try { - left = (int) (rect.getDouble(0) * zoom); - top = (int) (rect.getDouble(1) * zoom); - width = (int) (rect.getDouble(2) * zoom); - height = (int) (rect.getDouble(3) * zoom); - } catch (JSONException e) { } - - int listWidth = mWidth; - int listHeight = mHeight; - int listLeft = left < 0 ? 0 : left; - int listTop = top + height; - - FloatSize viewport = GeckoApp.mAppContext.getLayerController().getViewportSize(); - - // Late initializing variable to allow DisplayMetrics not to be null and avoid NPE - if (sMinWidth == 0) { - DisplayMetrics metrics = new DisplayMetrics(); - GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics); - sMinWidth = (int) (AUTOCOMPLETE_MIN_WIDTH_IN_DPI * metrics.density); - sRowHeight = (int) (AUTOCOMPLETE_ROW_HEIGHT_IN_DPI * metrics.density); - } - - // If the textbox is smaller than the screen-width, - // shrink the list's width - if ((left + width) < viewport.width) - listWidth = left < 0 ? left + width : width; - - // listWidth can be negative if it is a constant - FILL_PARENT or MATCH_PARENT - if (listWidth >= 0 && listWidth < sMinWidth) { - listWidth = sMinWidth; - - if ((listLeft + listWidth) > viewport.width) - listLeft = (int) (viewport.width - listWidth); - } - - listHeight = sRowHeight * adapter.getCount(); - - // The text box doesnt fit below - if ((listTop + listHeight) > viewport.height) { - // Find where the maximum space is, and fit it there - if ((viewport.height - listTop) > top) { - // Shrink the height to fit it below the text-box - listHeight = (int) (viewport.height - listTop); - } else { - if (listHeight < top) { - // No shrinking needed to fit on top - listTop = (top - listHeight); - } else { - // Shrink to available space on top - listTop = 0; - listHeight = top; - } - } - } - - mLayout = new RelativeLayout.LayoutParams(listWidth, listHeight); - mLayout.setMargins(listLeft, listTop, 0, 0); - setLayoutParams(mLayout); - requestLayout(); - } - - public void hide() { - if (isShown()) { - setVisibility(View.GONE); - GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FormAssist:Closed", null)); - } - } -} diff --git a/mobile/android/base/AwesomeBar.java b/mobile/android/base/AwesomeBar.java index 02f7b9c56862..a364548b37d7 100644 --- a/mobile/android/base/AwesomeBar.java +++ b/mobile/android/base/AwesomeBar.java @@ -97,6 +97,7 @@ public class AwesomeBar extends Activity implements GeckoEventListener { private AwesomeBarEditText mText; private ImageButton mGoButton; private ContentResolver mResolver; + private ContextMenuSubject mContextMenuSubject; @Override public void onCreate(Bundle savedInstanceState) { @@ -308,9 +309,11 @@ public class AwesomeBar extends Activity implements GeckoEventListener { } mGoButton.setImageResource(imageResource); - if ((mText.getImeOptions() & EditorInfo.IME_MASK_ACTION) != imeAction) { + int actionBits = mText.getImeOptions() & EditorInfo.IME_MASK_ACTION; + if (actionBits != imeAction) { InputMethodManager imm = (InputMethodManager) mText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - mText.setImeOptions(imeAction); + int optionBits = mText.getImeOptions() & ~EditorInfo.IME_MASK_ACTION; + mText.setImeOptions(optionBits | imeAction); imm.restartInput(mText); } } @@ -410,14 +413,25 @@ public class AwesomeBar extends Activity implements GeckoEventListener { GeckoAppShell.unregisterGeckoEventListener("SearchEngines:Data", this); } - private Object mContextMenuSubject = null; + private class ContextMenuSubject { + public int id; + public String url; + public byte[] favicon; + public String title; + + public ContextMenuSubject(int id, String url, byte[] favicon, String title) { + this.id = id; + this.url = url; + this.favicon = favicon; + this.title = title; + } + }; @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, view, menuInfo); ListView list = (ListView) view; - Object selectedItem = null; - String title = ""; + mContextMenuSubject = null; if (list == findViewById(R.id.history_list)) { if (!(menuInfo instanceof ExpandableListView.ExpandableListContextMenuInfo)) { @@ -434,12 +448,12 @@ public class AwesomeBar extends Activity implements GeckoEventListener { return; ExpandableListView exList = (ExpandableListView) list; - selectedItem = exList.getExpandableListAdapter().getChild(groupPosition, childPosition); // The history list is backed by a SimpleExpandableListAdapter @SuppressWarnings("rawtypes") - Map map = (Map) selectedItem; - title = (String) map.get(URLColumns.TITLE); + Map map = (Map) exList.getExpandableListAdapter().getChild(groupPosition, childPosition); + mContextMenuSubject = new ContextMenuSubject(-1, (String)map.get(URLColumns.URL), + (byte[]) map.get(URLColumns.FAVICON), (String)map.get(URLColumns.TITLE)); } else { if (!(menuInfo instanceof AdapterView.AdapterContextMenuInfo)) { Log.e(LOGTAG, "menuInfo is not AdapterContextMenuInfo"); @@ -447,7 +461,7 @@ public class AwesomeBar extends Activity implements GeckoEventListener { } AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - selectedItem = list.getItemAtPosition(info.position); + Object selectedItem = list.getItemAtPosition(info.position); if (!(selectedItem instanceof Cursor)) { Log.e(LOGTAG, "item at " + info.position + " is not a Cursor"); @@ -455,21 +469,19 @@ public class AwesomeBar extends Activity implements GeckoEventListener { } Cursor cursor = (Cursor) selectedItem; - title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE)); // Don't show the context menu for folders - if (list == findViewById(R.id.bookmarks_list) && - cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.IS_FOLDER)) == 1) { - selectedItem = null; + if (!(list == findViewById(R.id.bookmarks_list) && cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.IS_FOLDER)) == 1)) { + mContextMenuSubject = new ContextMenuSubject(cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID)), + cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL)), + cursor.getBlob(cursor.getColumnIndexOrThrow(URLColumns.FAVICON)), + cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE)) + ); } } - if (selectedItem == null || !((selectedItem instanceof Cursor) || (selectedItem instanceof Map))) { - mContextMenuSubject = null; + if (mContextMenuSubject == null) return; - } - - mContextMenuSubject = selectedItem; MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.awesomebar_contextmenu, menu); @@ -479,7 +491,7 @@ public class AwesomeBar extends Activity implements GeckoEventListener { removeBookmarkItem.setVisible(false); } - menu.setHeaderTitle(title); + menu.setHeaderTitle(mContextMenuSubject.title); } @Override @@ -487,27 +499,10 @@ public class AwesomeBar extends Activity implements GeckoEventListener { if (mContextMenuSubject == null) return false; - final int id; - final String url; - byte[] b = null; - String title = ""; - if (mContextMenuSubject instanceof Cursor) { - Cursor cursor = (Cursor)mContextMenuSubject; - id = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID)); - url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL)); - b = cursor.getBlob(cursor.getColumnIndexOrThrow(URLColumns.FAVICON)); - title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE)); - } else if (mContextMenuSubject instanceof Map) { - @SuppressWarnings("rawtypes") Map map = (Map)mContextMenuSubject; - id = -1; - url = (String)map.get(URLColumns.URL); - b = (byte[]) map.get(URLColumns.FAVICON); - title = (String)map.get(URLColumns.TITLE); - } else { - return false; - } - - mContextMenuSubject = null; + final int id = mContextMenuSubject.id; + final String url = mContextMenuSubject.url; + final byte[] b = mContextMenuSubject.favicon; + final String title = mContextMenuSubject.title; switch (item.getItemId()) { case R.id.open_new_tab: { diff --git a/mobile/android/base/AwesomeBarTabs.java b/mobile/android/base/AwesomeBarTabs.java index 1662f153edc9..4d3a5f62307e 100644 --- a/mobile/android/base/AwesomeBarTabs.java +++ b/mobile/android/base/AwesomeBarTabs.java @@ -202,6 +202,7 @@ public class AwesomeBarTabs extends TabHost { private static final int VIEW_TYPE_COUNT = 2; private LayoutInflater mInflater; + private Resources mResources; private LinkedList> mParentStack; private RefreshBookmarkCursorTask mRefreshTask = null; private TextView mBookmarksTitleView; @@ -210,6 +211,7 @@ public class AwesomeBarTabs extends TabHost { super(context, layout, c, from, to); mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mResources = mContext.getResources(); // mParentStack holds folder id/title pairs that allow us to navigate // back up the folder heirarchy @@ -268,6 +270,24 @@ public class AwesomeBarTabs extends TabHost { if (!c.moveToPosition(position)) return ""; + String guid = c.getString(c.getColumnIndexOrThrow(Bookmarks.GUID)); + + // If we don't have a special GUID, just return the folder title from the DB. + if (guid == null || guid.length() == 12) + return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE)); + + // Use localized strings for special folder names. + if (guid.equals(Bookmarks.MOBILE_FOLDER_GUID)) + return mResources.getString(R.string.bookmarks_folder_mobile); + else if (guid.equals(Bookmarks.MENU_FOLDER_GUID)) + return mResources.getString(R.string.bookmarks_folder_menu); + else if (guid.equals(Bookmarks.TOOLBAR_FOLDER_GUID)) + return mResources.getString(R.string.bookmarks_folder_toolbar); + else if (guid.equals(Bookmarks.UNFILED_FOLDER_GUID)) + return mResources.getString(R.string.bookmarks_folder_unfiled); + + // If for some reason we have a folder with a special GUID, but it's not one of + // the special folders we expect in the UI, just return the title from the DB. return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE)); } diff --git a/mobile/android/base/BrowserToolbar.java b/mobile/android/base/BrowserToolbar.java index 8708e236de81..1658797cec56 100644 --- a/mobile/android/base/BrowserToolbar.java +++ b/mobile/android/base/BrowserToolbar.java @@ -196,7 +196,7 @@ public class BrowserToolbar extends LinearLayout { } private void onAwesomeBarSearch() { - GeckoApp.mAppContext.onEditRequested(); + GeckoApp.mAppContext.onSearchRequested(); } private void addTab() { @@ -294,13 +294,13 @@ public class BrowserToolbar extends LinearLayout { Tab tab = Tabs.getInstance().getSelectedTab(); // Setting a null title for about:home will ensure we just see // the "Enter Search or Address" placeholder text - if (tab != null && tab.getURL().equals("about:home")) + if (tab != null && "about:home".equals(tab.getURL())) title = null; mAwesomeBar.setText(title); } public void setFavicon(Drawable image) { - if (Tabs.getInstance().getSelectedTab().isLoading()) + if (Tabs.getInstance().getSelectedTab().getState() == Tab.STATE_LOADING) return; if (image != null) @@ -339,11 +339,12 @@ public class BrowserToolbar extends LinearLayout { public void refresh() { Tab tab = Tabs.getInstance().getSelectedTab(); if (tab != null) { + String url = tab.getURL(); setTitle(tab.getDisplayTitle()); setFavicon(tab.getFavicon()); setSecurityMode(tab.getSecurityMode()); - setProgressVisibility(tab.isLoading()); - setShadowVisibility(!(tab.getURL().startsWith("about:"))); + setProgressVisibility(tab.getState() == Tab.STATE_LOADING); + setShadowVisibility((url == null) || !url.startsWith("about:")); updateTabCount(Tabs.getInstance().getCount()); } } diff --git a/mobile/android/base/DoorHanger.java b/mobile/android/base/DoorHanger.java index 6650e8c48fec..372fba2f0906 100644 --- a/mobile/android/base/DoorHanger.java +++ b/mobile/android/base/DoorHanger.java @@ -39,6 +39,10 @@ package org.mozilla.gecko; import android.content.Context; +import android.text.SpannableString; +import android.text.method.LinkMovementMethod; +import android.text.style.ForegroundColorSpan; +import android.text.style.URLSpan; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; @@ -140,6 +144,28 @@ public class DoorHanger extends LinearLayout implements Button.OnClickListener { try { mTimeout = options.getLong("timeout"); } catch (JSONException e) { } + + try { + JSONObject link = options.getJSONObject("link"); + String title = mTextView.getText().toString(); + String linkLabel = link.getString("label"); + String linkUrl = link.getString("url"); + SpannableString titleWithLink = new SpannableString(title + " " + linkLabel); + URLSpan linkSpan = new URLSpan(linkUrl) { + @Override + public void onClick(View view) { + GeckoApp.mAppContext.loadUrlInTab(this.getURL()); + } + }; + + // prevent text outside the link from flashing when clicked + ForegroundColorSpan colorSpan = new ForegroundColorSpan(mTextView.getCurrentTextColor()); + titleWithLink.setSpan(colorSpan, 0, title.length(), 0); + + titleWithLink.setSpan(linkSpan, title.length() + 1, titleWithLink.length(), 0); + mTextView.setText(titleWithLink); + mTextView.setMovementMethod(LinkMovementMethod.getInstance()); + } catch (JSONException e) { } } // This method checks with persistence and timeout options to see if diff --git a/mobile/android/base/FormAssistPopup.java b/mobile/android/base/FormAssistPopup.java new file mode 100644 index 000000000000..f8950dfce272 --- /dev/null +++ b/mobile/android/base/FormAssistPopup.java @@ -0,0 +1,313 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** 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 Android code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Margaret Leibovic + * Sriram Ramasubramanian + * + * 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 ***** */ + +package org.mozilla.gecko; + +import org.mozilla.gecko.gfx.FloatSize; + +import android.content.Context; +import android.util.Log; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.inputmethod.InputMethodManager; +import android.widget.ArrayAdapter; +import android.widget.AdapterView; +import android.widget.RelativeLayout; +import android.widget.ListView; +import android.widget.TextView; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +public class FormAssistPopup extends ListView implements GeckoEventListener { + private Context mContext; + private RelativeLayout.LayoutParams mLayout; + + private int mWidth; + private int mHeight; + + private Animation mAnimation; + + private static final String LOGTAG = "FormAssistPopup"; + + private static int sMinWidth = 0; + private static int sRowHeight = 0; + private static final int POPUP_MIN_WIDTH_IN_DPI = 200; + private static final int POPUP_ROW_HEIGHT_IN_DPI = 32; + + private static enum PopupType { NONE, AUTOCOMPLETE, VALIDATION }; + + // Keep track of the type of popup we're currently showing + private PopupType mTypeShowing = PopupType.NONE; + + public FormAssistPopup(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + + mAnimation = AnimationUtils.loadAnimation(context, R.anim.grow_fade_in); + mAnimation.setDuration(75); + + setFocusable(false); + + setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView parentView, View view, int position, long id) { + if (mTypeShowing.equals(PopupType.AUTOCOMPLETE)) { + // Use the value stored with the autocomplete view, not the label text, + // since they can be different. + TextView textView = (TextView) view; + String value = (String) textView.getTag(); + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FormAssist:AutoComplete", value)); + hide(); + } + } + }); + + GeckoAppShell.registerGeckoEventListener("FormAssist:AutoComplete", this); + GeckoAppShell.registerGeckoEventListener("FormAssist:ValidationMessage", this); + GeckoAppShell.registerGeckoEventListener("FormAssist:Hide", this); + } + + public void handleMessage(String event, JSONObject message) { + try { + if (event.equals("FormAssist:AutoComplete")) { + handleAutoCompleteMessage(message); + } else if (event.equals("FormAssist:ValidationMessage")) { + handleValidationMessage(message); + } else if (event.equals("FormAssist:Hide")) { + handleHideMessage(message); + } + } catch (Exception e) { + Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e); + } + } + + private void handleAutoCompleteMessage(JSONObject message) throws JSONException { + final JSONArray suggestions = message.getJSONArray("suggestions"); + final JSONArray rect = message.getJSONArray("rect"); + final double zoom = message.getDouble("zoom"); + GeckoApp.mAppContext.mMainHandler.post(new Runnable() { + public void run() { + showAutoCompleteSuggestions(suggestions, rect, zoom); + } + }); + } + + private void handleValidationMessage(JSONObject message) throws JSONException { + final String validationMessage = message.getString("validationMessage"); + final JSONArray rect = message.getJSONArray("rect"); + final double zoom = message.getDouble("zoom"); + GeckoApp.mAppContext.mMainHandler.post(new Runnable() { + public void run() { + showValidationMessage(validationMessage, rect, zoom); + } + }); + } + + private void handleHideMessage(JSONObject message) { + GeckoApp.mAppContext.mMainHandler.post(new Runnable() { + public void run() { + hide(); + } + }); + } + + private void showAutoCompleteSuggestions(JSONArray suggestions, JSONArray rect, double zoom) { + AutoCompleteListAdapter adapter = new AutoCompleteListAdapter(mContext, R.layout.autocomplete_list_item); + adapter.populateSuggestionsList(suggestions); + setAdapter(adapter); + + if (positionAndShowPopup(rect, zoom)) + mTypeShowing = PopupType.AUTOCOMPLETE; + } + + // TODO: style the validation message popup differently (bug 731654) + private void showValidationMessage(String validationMessage, JSONArray rect, double zoom) { + ArrayAdapter adapter = new ArrayAdapter(mContext, R.layout.autocomplete_list_item); + adapter.add(validationMessage); + setAdapter(adapter); + + if (positionAndShowPopup(rect, zoom)) + mTypeShowing = PopupType.VALIDATION; + } + + // Returns true if the popup is successfully shown, false otherwise + public boolean positionAndShowPopup(JSONArray rect, double zoom) { + // Don't show the form assist popup when using fullscreen VKB + InputMethodManager imm = + (InputMethodManager) GeckoApp.mAppContext.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm.isFullscreenMode()) + return false; + + if (!isShown()) { + setVisibility(View.VISIBLE); + startAnimation(mAnimation); + } + + if (mLayout == null) { + mLayout = (RelativeLayout.LayoutParams) getLayoutParams(); + mWidth = mLayout.width; + mHeight = mLayout.height; + } + + int left = 0; + int top = 0; + int width = 0; + int height = 0; + + try { + left = (int) (rect.getDouble(0) * zoom); + top = (int) (rect.getDouble(1) * zoom); + width = (int) (rect.getDouble(2) * zoom); + height = (int) (rect.getDouble(3) * zoom); + } catch (JSONException e) { } + + int listWidth = mWidth; + int listHeight = mHeight; + int listLeft = left < 0 ? 0 : left; + int listTop = top + height; + + FloatSize viewport = GeckoApp.mAppContext.getLayerController().getViewportSize(); + + // Late initializing variable to allow DisplayMetrics not to be null and avoid NPE + if (sMinWidth == 0) { + DisplayMetrics metrics = new DisplayMetrics(); + GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics); + sMinWidth = (int) (POPUP_MIN_WIDTH_IN_DPI * metrics.density); + sRowHeight = (int) (POPUP_ROW_HEIGHT_IN_DPI * metrics.density); + } + + // If the textbox is smaller than the screen-width, + // shrink the list's width + if ((left + width) < viewport.width) + listWidth = left < 0 ? left + width : width; + + // listWidth can be negative if it is a constant - FILL_PARENT or MATCH_PARENT + if (listWidth >= 0 && listWidth < sMinWidth) { + listWidth = sMinWidth; + + if ((listLeft + listWidth) > viewport.width) + listLeft = (int) (viewport.width - listWidth); + } + + listHeight = sRowHeight * getAdapter().getCount(); + + // The text box doesnt fit below + if ((listTop + listHeight) > viewport.height) { + // Find where the maximum space is, and fit it there + if ((viewport.height - listTop) > top) { + // Shrink the height to fit it below the text-box + listHeight = (int) (viewport.height - listTop); + } else { + if (listHeight < top) { + // No shrinking needed to fit on top + listTop = (top - listHeight); + } else { + // Shrink to available space on top + listTop = 0; + listHeight = top; + } + } + } + + mLayout = new RelativeLayout.LayoutParams(listWidth, listHeight); + mLayout.setMargins(listLeft, listTop, 0, 0); + setLayoutParams(mLayout); + requestLayout(); + + return true; + } + + public void hide() { + if (isShown()) { + setVisibility(View.GONE); + mTypeShowing = PopupType.NONE; + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FormAssist:Hidden", null)); + } + } + + private class AutoCompleteListAdapter extends ArrayAdapter> { + private LayoutInflater mInflater; + private int mTextViewResourceId; + + public AutoCompleteListAdapter(Context context, int textViewResourceId) { + super(context, textViewResourceId); + + mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mTextViewResourceId = textViewResourceId; + } + + // This method takes an array of autocomplete suggestions with label/value properties + // and adds label/value Pair objects to the array that backs the adapter. + public void populateSuggestionsList(JSONArray suggestions) { + try { + for (int i = 0; i < suggestions.length(); i++) { + JSONObject suggestion = (JSONObject) suggestions.get(i); + String label = suggestion.getString("label"); + String value = suggestion.getString("value"); + add(new Pair(label, value)); + } + } catch (JSONException e) { + Log.e(LOGTAG, "JSONException: " + e); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) + convertView = mInflater.inflate(mTextViewResourceId, null); + + Pair item = getItem(position); + TextView itemView = (TextView) convertView; + + // Set the text with the suggestion label + itemView.setText(item.first); + + // Set a tag with the suggestion value + itemView.setTag(item.second); + + return convertView; + } + } +} diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index a868c6f88c3f..6009168dc7fe 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -136,7 +136,7 @@ abstract public class GeckoApp public static BrowserToolbar mBrowserToolbar; public static DoorHangerPopup mDoorHangerPopup; - public static AutoCompletePopup mAutoCompletePopup; + public static FormAssistPopup mFormAssistPopup; public Favicons mFavicons; private Geocoder mGeocoder; @@ -432,7 +432,7 @@ abstract public class GeckoApp MenuItem saveAsPDF = aMenu.findItem(R.id.save_as_pdf); MenuItem charEncoding = aMenu.findItem(R.id.char_encoding); - if (tab == null) { + if (tab == null || tab.getURL() == null) { bookmark.setEnabled(false); forward.setEnabled(false); share.setEnabled(false); @@ -455,9 +455,8 @@ abstract public class GeckoApp // Disable share menuitem for about:, chrome: and file: URIs String scheme = Uri.parse(tab.getURL()).getScheme(); - boolean enabled = scheme != null && !(scheme.equals("about") || scheme.equals("chrome") || - scheme.equals("file")); - share.setEnabled(enabled); + share.setEnabled(!(scheme.equals("about") || scheme.equals("chrome") || + scheme.equals("file"))); // Disable save as PDF for about:home and xul pages saveAsPDF.setEnabled(!(tab.getURL().equals("about:home") || @@ -500,8 +499,12 @@ abstract public class GeckoApp case R.id.share: tab = Tabs.getInstance().getSelectedTab(); if (tab != null) { - GeckoAppShell.openUriExternal(tab.getURL(), "text/plain", "", "", - Intent.ACTION_SEND, tab.getTitle()); + String url = tab.getURL(); + if (url == null) + return false; + + GeckoAppShell.openUriExternal(url, "text/plain", "", "", + Intent.ACTION_SEND, tab.getTitle()); } return true; case R.id.reload: @@ -607,7 +610,7 @@ abstract public class GeckoApp bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos); processThumbnail(tab, bitmap, bos.toByteArray()); } else { - if (!tab.hasLoaded()) { + if (tab.getState() == Tab.STATE_DELAYED) { byte[] thumbnail = BrowserDB.getThumbnailForUrl(getContentResolver(), tab.getURL()); if (thumbnail != null) processThumbnail(tab, null, thumbnail); @@ -780,7 +783,7 @@ abstract public class GeckoApp mBrowserToolbar.setTitle(tab.getDisplayTitle()); mBrowserToolbar.setFavicon(tab.getFavicon()); mBrowserToolbar.setSecurityMode(tab.getSecurityMode()); - mBrowserToolbar.setProgressVisibility(tab.isLoading()); + mBrowserToolbar.setProgressVisibility(tab.getState() == Tab.STATE_LOADING); } } }); @@ -922,6 +925,7 @@ abstract public class GeckoApp handleSecurityChange(tabId, mode); } else if (event.equals("Content:StateChange")) { final int tabId = message.getInt("tabID"); + final boolean success = message.getBoolean("success"); int state = message.getInt("state"); Log.i(LOGTAG, "State - " + state); if ((state & GeckoAppShell.WPL_STATE_IS_NETWORK) != 0) { @@ -931,7 +935,7 @@ abstract public class GeckoApp handleDocumentStart(tabId, showProgress); } else if ((state & GeckoAppShell.WPL_STATE_STOP) != 0) { Log.i(LOGTAG, "Got a document stop"); - handleDocumentStop(tabId); + handleDocumentStop(tabId, success); } } } else if (event.equals("Content:LoadError")) { @@ -969,31 +973,17 @@ abstract public class GeckoApp mBrowserToolbar.show(); } }); + } else if (event.equals("ToggleChrome:Focus")) { + mMainHandler.post(new Runnable() { + public void run() { + mBrowserToolbar.setVisibility(View.VISIBLE); + mBrowserToolbar.requestFocusFromTouch(); + } + }); } else if (event.equals("DOMFullScreen:Start")) { mDOMFullScreen = true; } else if (event.equals("DOMFullScreen:Stop")) { mDOMFullScreen = false; - } else if (event.equals("FormAssist:AutoComplete")) { - final JSONArray suggestions = message.getJSONArray("suggestions"); - if (suggestions.length() == 0) { - mMainHandler.post(new Runnable() { - public void run() { - mAutoCompletePopup.hide(); - } - }); - } else { - final JSONArray rect = message.getJSONArray("rect"); - final double zoom = message.getDouble("zoom"); - mMainHandler.post(new Runnable() { - public void run() { - // Don't show autocomplete popup when using fullscreen VKB - InputMethodManager imm = - (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - if (!imm.isFullscreenMode()) - mAutoCompletePopup.show(suggestions, rect, zoom); - } - }); - } } else if (event.equals("Permissions:Data")) { String host = message.getString("host"); JSONArray permissions = message.getJSONArray("permissions"); @@ -1105,7 +1095,7 @@ abstract public class GeckoApp } public void run() { - mAutoCompletePopup.hide(); + mFormAssistPopup.hide(); if (mShow) { if (mAboutHomeContent == null) { mAboutHomeContent = (AboutHomeContent) findViewById(R.id.abouthome_content); @@ -1236,7 +1226,7 @@ abstract public class GeckoApp if (tab == null) return; - tab.setLoading(true); + tab.setState(Tab.STATE_LOADING); tab.updateSecurityMode("unknown"); mMainHandler.post(new Runnable() { @@ -1251,12 +1241,12 @@ abstract public class GeckoApp }); } - void handleDocumentStop(int tabId) { + void handleDocumentStop(int tabId, boolean success) { final Tab tab = Tabs.getInstance().getTab(tabId); if (tab == null) return; - tab.setLoading(false); + tab.setState(success ? Tab.STATE_SUCCESS : Tab.STATE_ERROR); mMainHandler.post(new Runnable() { public void run() { @@ -1291,7 +1281,6 @@ abstract public class GeckoApp return; tab.updateTitle(title); - tab.setHasLoaded(true); // Make the UI changes mMainHandler.post(new Runnable() { @@ -1332,7 +1321,7 @@ abstract public class GeckoApp // want to load the image straight away. If tab is still // loading, we only load the favicon once the page's content // is fully loaded (see handleContentLoaded()). - if (!tab.isLoading()) { + if (tab.getState() != tab.STATE_LOADING) { mMainHandler.post(new Runnable() { public void run() { loadFavicon(tab); @@ -1662,6 +1651,7 @@ abstract public class GeckoApp mInitialized = true; Intent intent = getIntent(); + String action = intent.getAction(); String args = intent.getStringExtra("args"); if (args != null && args.contains("-profile")) { Pattern p = Pattern.compile("(?:-profile\\s*)(\\w*)(\\s*)"); @@ -1674,7 +1664,7 @@ abstract public class GeckoApp } } - if (ACTION_UPDATE.equals(intent.getAction()) || args != null && args.contains("-alert update-app")) { + if (ACTION_UPDATE.equals(action) || args != null && args.contains("-alert update-app")) { Log.i(LOGTAG,"onCreate: Update request"); checkAndLaunchUpdate(); } @@ -1715,9 +1705,20 @@ abstract public class GeckoApp } sGeckoThread = new GeckoThread(intent, passedUri, mRestoreSession); - if (!ACTION_DEBUG.equals(intent.getAction()) && - checkAndSetLaunchState(LaunchState.Launching, LaunchState.Launched)) + if (!ACTION_DEBUG.equals(action) && + checkAndSetLaunchState(LaunchState.Launching, LaunchState.Launched)) { sGeckoThread.start(); + } else if (ACTION_DEBUG.equals(action) && + checkAndSetLaunchState(LaunchState.Launching, LaunchState.WaitForDebugger)) { + mMainHandler.postDelayed(new Runnable() { + public void run() { + Log.i(LOGTAG, "Launching from debug intent after 5s wait"); + setLaunchState(LaunchState.Launching); + sGeckoThread.start(); + } + }, 1000 * 5 /* 5 seconds */); + Log.i(LOGTAG, "Intent : ACTION_DEBUG - waiting 5s before launching"); + } mFavicons = new Favicons(this); @@ -1754,7 +1755,7 @@ abstract public class GeckoApp mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container); mDoorHangerPopup = new DoorHangerPopup(this); - mAutoCompletePopup = (AutoCompletePopup) findViewById(R.id.autocomplete_popup); + mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup); Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - UI almost up"); @@ -1794,7 +1795,7 @@ abstract public class GeckoApp GeckoAppShell.registerGeckoEventListener("DOMFullScreen:Stop", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("ToggleChrome:Hide", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("ToggleChrome:Show", GeckoApp.mAppContext); - GeckoAppShell.registerGeckoEventListener("FormAssist:AutoComplete", GeckoApp.mAppContext); + GeckoAppShell.registerGeckoEventListener("ToggleChrome:Focus", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("Permissions:Data", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("Downloads:Done", GeckoApp.mAppContext); GeckoAppShell.registerGeckoEventListener("CharEncoding:Data", GeckoApp.mAppContext); @@ -1965,6 +1966,10 @@ abstract public class GeckoApp return; } + // don't perform any actions if launching from recent apps + if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) + return; + if (checkLaunchState(LaunchState.Launched)) { Uri data = intent.getData(); Bundle bundle = intent.getExtras(); @@ -2167,7 +2172,7 @@ abstract public class GeckoApp GeckoAppShell.unregisterGeckoEventListener("Toast:Show", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("ToggleChrome:Hide", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("ToggleChrome:Show", GeckoApp.mAppContext); - GeckoAppShell.unregisterGeckoEventListener("FormAssist:AutoComplete", GeckoApp.mAppContext); + GeckoAppShell.unregisterGeckoEventListener("ToggleChrome:Focus", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("Permissions:Data", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("Downloads:Done", GeckoApp.mAppContext); GeckoAppShell.unregisterGeckoEventListener("CharEncoding:Data", GeckoApp.mAppContext); @@ -2208,7 +2213,7 @@ abstract public class GeckoApp if (mOrientation != newConfig.orientation) { mOrientation = newConfig.orientation; - mAutoCompletePopup.hide(); + mFormAssistPopup.hide(); refreshActionBar(); } } @@ -2380,10 +2385,6 @@ abstract public class GeckoApp @Override public boolean onSearchRequested() { - return showAwesomebar(AwesomeBar.Type.ADD); - } - - public boolean onEditRequested() { return showAwesomebar(AwesomeBar.Type.EDIT); } diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java index 99a66ac39dbb..8857d2fd54f9 100644 --- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -1773,7 +1773,7 @@ public class GeckoAppShell return false; } - public static void emitGeckoAccessibilityEvent (int eventType, String role, String text, String description, boolean enabled, boolean checked, boolean password) { + public static void emitGeckoAccessibilityEvent (int eventType, String[] textList, String description, boolean enabled, boolean checked, boolean password) { AccessibilityManager accessibilityManager = (AccessibilityManager) GeckoApp.mAppContext.getSystemService(Context.ACCESSIBILITY_SERVICE); @@ -1784,13 +1784,14 @@ public class GeckoAppShell LayerView layerView = layerController.getView(); AccessibilityEvent event = AccessibilityEvent.obtain(eventType); - event.setClassName(layerView.getClass().getName() + "$" + role); + event.setClassName(layerView.getClass().getName()); event.setPackageName(GeckoApp.mAppContext.getPackageName()); event.setEnabled(enabled); event.setChecked(checked); event.setPassword(password); event.setContentDescription(description); - event.getText().add(text); + for (String text: textList) + event.getText().add(text); accessibilityManager.sendAccessibilityEvent(event); } diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 468921bc354b..a7c9f102770d 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -63,7 +63,6 @@ SYNC_PP_RES_XML=res/xml/sync_syncadapter.xml res/xml/sync_options.xml FENNEC_JAVA_FILES = \ AboutHomeContent.java \ AlertNotification.java \ - AutoCompletePopup.java \ AwesomeBar.java \ AwesomeBarTabs.java \ BrowserToolbar.java \ @@ -77,6 +76,7 @@ FENNEC_JAVA_FILES = \ DoorHangerPopup.java \ Favicons.java \ FloatUtils.java \ + FormAssistPopup.java \ GeckoActionBar.java \ GeckoApp.java \ GeckoAppShell.java \ diff --git a/mobile/android/base/Tab.java b/mobile/android/base/Tab.java index 2a9c0f3c2dfe..26795b293f48 100644 --- a/mobile/android/base/Tab.java +++ b/mobile/android/base/Tab.java @@ -78,7 +78,6 @@ public final class Tab { private int mHistoryIndex; private int mParentId; private boolean mExternal; - private boolean mLoading; private boolean mBookmark; private HashMap mDoorHangers; private long mFaviconLoadId; @@ -88,9 +87,14 @@ public final class Tab { private boolean mHasTouchListeners; private ArrayList mPluginViews; private HashMap mPluginLayers; - private boolean mHasLoaded; private ContentResolver mContentResolver; private ContentObserver mContentObserver; + private int mState; + + public static final int STATE_DELAYED = 0; + public static final int STATE_LOADING = 1; + public static final int STATE_SUCCESS = 2; + public static final int STATE_ERROR = 3; public static final class HistoryEntry { public String mUri; // must never be null @@ -102,10 +106,6 @@ public final class Tab { } } - public Tab() { - this(-1, "", false, -1, ""); - } - public Tab(int id, String url, boolean external, int parentId, String title) { mId = id; mUrl = url; @@ -125,7 +125,7 @@ public final class Tab { mContentType = ""; mPluginViews = new ArrayList(); mPluginLayers = new HashMap(); - mHasLoaded = false; + mState = STATE_LOADING; mContentResolver = Tabs.getInstance().getContentResolver(); mContentObserver = new ContentObserver(GeckoAppShell.getHandler()) { public void onChange(boolean selfChange) { @@ -148,6 +148,7 @@ public final class Tab { return mParentId; } + // may be null if user-entered query hasn't yet been resolved to a URI public String getURL() { return mUrl; } @@ -235,7 +236,9 @@ public final class Tab { b.getHeight()); Bitmap bitmap = Bitmap.createScaledBitmap(cropped, getThumbnailWidth(), getThumbnailHeight(), false); - saveThumbnailToDB(new BitmapDrawable(bitmap)); + + if (mState == Tab.STATE_SUCCESS) + saveThumbnailToDB(new BitmapDrawable(bitmap)); if (!cropped.equals(b)) b.recycle(); @@ -265,10 +268,6 @@ public final class Tab { return mSecurityMode; } - public boolean isLoading() { - return mLoading; - } - public boolean isBookmark() { return mBookmark; } @@ -324,8 +323,12 @@ public final class Tab { } } - public void setLoading(boolean loading) { - mLoading = loading; + public void setState(int state) { + mState = state; + } + + public int getState() { + return mState; } public void setHasTouchListeners(boolean aValue) { @@ -370,7 +373,11 @@ public final class Tab { if (mCheckBookmarkTask != null) mCheckBookmarkTask.cancel(false); - mCheckBookmarkTask = new CheckBookmarkTask(getURL()); + String url = getURL(); + if (url == null) + return; + + mCheckBookmarkTask = new CheckBookmarkTask(url); mCheckBookmarkTask.execute(); } }); @@ -379,7 +386,11 @@ public final class Tab { public void addBookmark() { GeckoAppShell.getHandler().post(new Runnable() { public void run() { - BrowserDB.addBookmark(mContentResolver, getTitle(), getURL()); + String url = getURL(); + if (url == null) + return; + + BrowserDB.addBookmark(mContentResolver, getTitle(), url); } }); } @@ -387,7 +398,11 @@ public final class Tab { public void removeBookmark() { GeckoAppShell.getHandler().post(new Runnable() { public void run() { - BrowserDB.removeBookmarksWithURL(mContentResolver, getURL()); + String url = getURL(); + if (url == null) + return; + + BrowserDB.removeBookmarksWithURL(mContentResolver, url); } }); } @@ -458,14 +473,6 @@ public final class Tab { return mDoorHangers; } - public void setHasLoaded(boolean hasLoaded) { - mHasLoaded = hasLoaded; - } - - public boolean hasLoaded() { - return mHasLoaded; - } - void handleSessionHistoryMessage(String event, JSONObject message) throws JSONException { if (event.equals("New")) { final String uri = message.getString("uri"); @@ -541,7 +548,11 @@ public final class Tab { private void saveThumbnailToDB(BitmapDrawable thumbnail) { try { - BrowserDB.updateThumbnailForUrl(mContentResolver, getURL(), thumbnail); + String url = getURL(); + if (url == null) + return; + + BrowserDB.updateThumbnailForUrl(mContentResolver, url, thumbnail); } catch (Exception e) { // ignore } diff --git a/mobile/android/base/Tabs.java b/mobile/android/base/Tabs.java index 32bc9acbbc52..43a52ddae74c 100644 --- a/mobile/android/base/Tabs.java +++ b/mobile/android/base/Tabs.java @@ -81,7 +81,8 @@ public class Tabs implements GeckoEventListener { if (tabs.containsKey(id)) return tabs.get(id); - String url = params.getString("uri"); + // null strings return "null" (http://code.google.com/p/android/issues/detail?id=13830) + String url = params.isNull("uri") ? null : params.getString("uri"); Boolean external = params.getBoolean("external"); int parentId = params.getInt("parentId"); String title = params.getString("title"); @@ -98,7 +99,7 @@ public class Tabs implements GeckoEventListener { }); } - Log.i(LOGTAG, "Added a tab with id: " + id + ", url: " + url); + Log.i(LOGTAG, "Added a tab with id: " + id); return tab; } @@ -121,22 +122,23 @@ public class Tabs implements GeckoEventListener { if (tab == null) return null; - if (tab.getURL().equals("about:home")) + if ("about:home".equals(tab.getURL())) GeckoApp.mAppContext.showAboutHome(); else GeckoApp.mAppContext.hideAboutHome(); GeckoApp.mAppContext.mMainHandler.post(new Runnable() { public void run() { - GeckoApp.mAutoCompletePopup.hide(); + GeckoApp.mFormAssistPopup.hide(); // Do we need to do this check? if (isSelectedTab(tab)) { + String url = tab.getURL(); GeckoApp.mBrowserToolbar.setTitle(tab.getDisplayTitle()); GeckoApp.mBrowserToolbar.setFavicon(tab.getFavicon()); GeckoApp.mBrowserToolbar.setSecurityMode(tab.getSecurityMode()); - GeckoApp.mBrowserToolbar.setProgressVisibility(tab.isLoading()); + GeckoApp.mBrowserToolbar.setProgressVisibility(tab.getState() == Tab.STATE_LOADING); GeckoApp.mDoorHangerPopup.updatePopup(); - GeckoApp.mBrowserToolbar.setShadowVisibility(!(tab.getURL().startsWith("about:"))); + GeckoApp.mBrowserToolbar.setShadowVisibility((url == null) || !url.startsWith("about:")); notifyListeners(tab, TabEvents.SELECTED); if (oldTab != null) @@ -281,7 +283,7 @@ public class Tabs implements GeckoEventListener { if (message.getBoolean("selected")) selectTab(tab.getId()); if (message.getBoolean("delayLoad")) - tab.setHasLoaded(false); + tab.setState(Tab.STATE_DELAYED); } else if (event.equals("Tab:Close")) { Tab tab = getTab(message.getInt("tabID")); closeTab(tab); diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index 8eeed44d3c98..ff734c51e0a1 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -89,6 +89,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { private static final String[] DEFAULT_BOOKMARK_COLUMNS = new String[] { Bookmarks._ID, + Bookmarks.GUID, Bookmarks.URL, Bookmarks.TITLE, Bookmarks.IS_FOLDER, diff --git a/mobile/android/base/resources/layout-v11/gecko_app.xml b/mobile/android/base/resources/layout-v11/gecko_app.xml index cac2df390ca6..92506744144e 100644 --- a/mobile/android/base/resources/layout-v11/gecko_app.xml +++ b/mobile/android/base/resources/layout-v11/gecko_app.xml @@ -13,13 +13,13 @@ android:layout_width="fill_parent" android:layout_height="fill_parent"/> - + diff --git a/mobile/android/base/resources/layout/doorhanger.xml b/mobile/android/base/resources/layout/doorhanger.xml index 46b81842ddd4..9df165dcdf4b 100644 --- a/mobile/android/base/resources/layout/doorhanger.xml +++ b/mobile/android/base/resources/layout/doorhanger.xml @@ -6,6 +6,7 @@ android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="?android:attr/textColorPrimary" + android:textColorLink="@color/doorhanger_link" android:padding="10dp"/> - + #ffffff #000000 #ffffff + #ACC4D5 diff --git a/mobile/android/base/sync/CryptoRecord.java b/mobile/android/base/sync/CryptoRecord.java index 4e3c0dc45e7a..8f69218caa45 100644 --- a/mobile/android/base/sync/CryptoRecord.java +++ b/mobile/android/base/sync/CryptoRecord.java @@ -152,6 +152,8 @@ public class CryptoRecord extends Record { * * @param jsonRecord * @return + * A CryptoRecord that encapsulates the provided record. + * * @throws NonObjectJSONException * @throws ParseException * @throws IOException diff --git a/mobile/android/base/sync/ExtendedJSONObject.java b/mobile/android/base/sync/ExtendedJSONObject.java index 0e9f195fd5d0..1ac2b57310ac 100644 --- a/mobile/android/base/sync/ExtendedJSONObject.java +++ b/mobile/android/base/sync/ExtendedJSONObject.java @@ -133,7 +133,7 @@ public class ExtendedJSONObject { /** * Return a server timestamp value as milliseconds since epoch. - * @param string + * @param key * @return A Long, or null if the value is non-numeric or doesn't exist. */ public Long getTimestamp(String key) { diff --git a/mobile/android/base/sync/GlobalSession.java b/mobile/android/base/sync/GlobalSession.java index d448ba8fa392..82d700a1deee 100644 --- a/mobile/android/base/sync/GlobalSession.java +++ b/mobile/android/base/sync/GlobalSession.java @@ -48,6 +48,7 @@ import java.util.Map; import org.json.simple.parser.ParseException; import org.mozilla.gecko.sync.crypto.CryptoException; import org.mozilla.gecko.sync.crypto.KeyBundle; +import org.mozilla.gecko.sync.delegates.ClientsDataDelegate; import org.mozilla.gecko.sync.delegates.FreshStartDelegate; import org.mozilla.gecko.sync.delegates.GlobalSessionCallback; import org.mozilla.gecko.sync.delegates.InfoCollectionsDelegate; @@ -69,6 +70,7 @@ import org.mozilla.gecko.sync.stage.FetchMetaGlobalStage; import org.mozilla.gecko.sync.stage.GlobalSyncStage; import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage; import org.mozilla.gecko.sync.stage.NoSuchStageException; +import org.mozilla.gecko.sync.stage.SyncClientsEngineStage; import android.content.Context; import android.content.SharedPreferences; @@ -92,6 +94,7 @@ public class GlobalSession implements CredentialsSource, PrefsSource { private GlobalSessionCallback callback; private Context context; + private ClientsDataDelegate clientsDelegate; /* * Key accessors. @@ -148,7 +151,8 @@ public class GlobalSession implements CredentialsSource, PrefsSource { KeyBundle syncKeyBundle, GlobalSessionCallback callback, Context context, - Bundle extras) + Bundle extras, + ClientsDataDelegate clientsDelegate) throws SyncConfigurationException, IllegalArgumentException, IOException, ParseException, NonObjectJSONException { if (callback == null) { throw new IllegalArgumentException("Must provide a callback to GlobalSession constructor."); @@ -174,6 +178,7 @@ public class GlobalSession implements CredentialsSource, PrefsSource { this.callback = callback; this.context = context; + this.clientsDelegate = clientsDelegate; config = new SyncConfiguration(prefsPath, this); config.userAPI = userAPI; @@ -192,6 +197,7 @@ public class GlobalSession implements CredentialsSource, PrefsSource { stages.put(Stage.fetchInfoCollections, new FetchInfoCollectionsStage()); stages.put(Stage.fetchMetaGlobal, new FetchMetaGlobalStage()); stages.put(Stage.ensureKeysStage, new EnsureKeysStage()); + stages.put(Stage.syncClientsEngine, new SyncClientsEngineStage()); // TODO: more stages. stages.put(Stage.syncBookmarks, new AndroidBrowserBookmarksServerSyncStage()); @@ -211,6 +217,7 @@ public class GlobalSession implements CredentialsSource, PrefsSource { * Advance and loop around the stages of a sync. * @param current * @return + * The next stage to execute. */ public static Stage nextStage(Stage current) { int index = current.ordinal() + 1; @@ -220,9 +227,6 @@ public class GlobalSession implements CredentialsSource, PrefsSource { /** * Move to the next stage in the syncing process. - * @param next - * The next stage. - * @throws NoSuchStageException if the stage does not exist. */ public void advance() { this.callback.handleStageCompleted(this.currentState, this); @@ -680,6 +684,9 @@ public class GlobalSession implements CredentialsSource, PrefsSource { * * @param engineName * @return + * true if the engine with the provided name is present in the + * meta/global "engines" object. + * * @throws MetaGlobalException */ public boolean engineIsEnabled(String engineName) throws MetaGlobalException { @@ -691,4 +698,8 @@ public class GlobalSession implements CredentialsSource, PrefsSource { } return this.config.metaGlobal.engines.get(engineName) != null; } + + public ClientsDataDelegate getClientsDelegate() { + return this.clientsDelegate; + } } diff --git a/mobile/android/base/sync/KeyBundleProvider.java b/mobile/android/base/sync/KeyBundleProvider.java new file mode 100644 index 000000000000..b6a8f80c6e9c --- /dev/null +++ b/mobile/android/base/sync/KeyBundleProvider.java @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.sync; + +import org.mozilla.gecko.sync.crypto.KeyBundle; + +public interface KeyBundleProvider { + public abstract KeyBundle keyBundle(); +} \ No newline at end of file diff --git a/mobile/android/base/sync/SyncConfiguration.java b/mobile/android/base/sync/SyncConfiguration.java index 4a38bdd28f11..b0019136965b 100644 --- a/mobile/android/base/sync/SyncConfiguration.java +++ b/mobile/android/base/sync/SyncConfiguration.java @@ -1,39 +1,6 @@ -/* ***** 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 Android Sync Client. - * - * The Initial Developer of the Original Code is - * the Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Richard Newman - * - * 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 ***** */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.sync; @@ -46,7 +13,6 @@ import org.mozilla.gecko.sync.crypto.KeyBundle; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; -import android.util.Log; public class SyncConfiguration implements CredentialsSource { @@ -229,7 +195,7 @@ public class SyncConfiguration implements CredentialsSource { * provide access to preferences. * * @param prefsPath - * @param context + * @param prefsSource */ public SyncConfiguration(String prefsPath, PrefsSource prefsSource) { this.prefsPath = prefsPath; @@ -238,7 +204,7 @@ public class SyncConfiguration implements CredentialsSource { } public SharedPreferences getPrefs() { - Log.d(LOG_TAG, "Returning prefs for " + prefsPath); + Logger.debug(LOG_TAG, "Returning prefs for " + prefsPath); return prefsSource.getPrefs(prefsPath, Utils.SHARED_PREFERENCES_MODE); } @@ -246,6 +212,8 @@ public class SyncConfiguration implements CredentialsSource { * Return a convenient accessor for part of prefs. * @param prefix * @return + * A ConfigurationBranch object representing this + * section of the preferences space. */ public ConfigurationBranch getBranch(String prefix) { return new ConfigurationBranch(this, prefix); @@ -257,14 +225,14 @@ public class SyncConfiguration implements CredentialsSource { String u = prefs.getString("clusterURL", null); try { clusterURL = new URI(u); - Log.i(LOG_TAG, "Set clusterURL from bundle: " + u); + Logger.info(LOG_TAG, "Set clusterURL from bundle: " + u); } catch (URISyntaxException e) { - Log.w(LOG_TAG, "Ignoring bundle clusterURL (" + u + "): invalid URI.", e); + Logger.warn(LOG_TAG, "Ignoring bundle clusterURL (" + u + "): invalid URI.", e); } } if (prefs.contains("syncID")) { syncID = prefs.getString("syncID", null); - Log.i(LOG_TAG, "Set syncID from bundle: " + syncID); + Logger.info(LOG_TAG, "Set syncID from bundle: " + syncID); } // TODO: MetaGlobal, password, infoCollections, collectionKeys. } @@ -371,8 +339,8 @@ public class SyncConfiguration implements CredentialsSource { public void setAndPersistClusterURL(URI u, SharedPreferences prefs) { boolean shouldPersist = (prefs != null) && (clusterURL == null); - Log.d(LOG_TAG, "Setting cluster URL to " + u.toASCIIString() + - (shouldPersist ? ". Persisting." : ". Not persisting.")); + Logger.debug(LOG_TAG, "Setting cluster URL to " + u.toASCIIString() + + (shouldPersist ? ". Persisting." : ". Not persisting.")); clusterURL = u; if (shouldPersist) { Editor edit = prefs.edit(); @@ -387,7 +355,7 @@ public class SyncConfiguration implements CredentialsSource { public void setClusterURL(URI u, SharedPreferences prefs) { if (u == null) { - Log.w(LOG_TAG, "Refusing to set cluster URL to null."); + Logger.warn(LOG_TAG, "Refusing to set cluster URL to null."); return; } URI uri = u.normalize(); @@ -396,7 +364,7 @@ public class SyncConfiguration implements CredentialsSource { return; } setAndPersistClusterURL(uri.resolve("/"), prefs); - Log.i(LOG_TAG, "Set cluster URL to " + clusterURL.toASCIIString() + ", given input " + u.toASCIIString()); + Logger.info(LOG_TAG, "Set cluster URL to " + clusterURL.toASCIIString() + ", given input " + u.toASCIIString()); } public void setClusterURL(String url) throws URISyntaxException { @@ -405,7 +373,6 @@ public class SyncConfiguration implements CredentialsSource { /** * Used for direct management of related prefs. - * @return */ public Editor getEditor() { return this.getPrefs().edit(); diff --git a/mobile/android/base/sync/delegates/ClientsDataDelegate.java b/mobile/android/base/sync/delegates/ClientsDataDelegate.java new file mode 100644 index 000000000000..84b555f99f37 --- /dev/null +++ b/mobile/android/base/sync/delegates/ClientsDataDelegate.java @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.sync.delegates; + +public interface ClientsDataDelegate { + public String getAccountGUID(); + public String getClientName(); + public void setClientsCount(int clientsCount); + public int getClientsCount(); +} diff --git a/mobile/android/base/sync/middleware/Crypto5MiddlewareRepositorySession.java b/mobile/android/base/sync/middleware/Crypto5MiddlewareRepositorySession.java index e2af12a8b7da..cf37738bebfe 100644 --- a/mobile/android/base/sync/middleware/Crypto5MiddlewareRepositorySession.java +++ b/mobile/android/base/sync/middleware/Crypto5MiddlewareRepositorySession.java @@ -1,39 +1,6 @@ -/* ***** 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 Android Sync Client. - * - * The Initial Developer of the Original Code is - * the Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Richard Newman - * - * 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 ***** */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.sync.middleware; @@ -43,16 +10,12 @@ import java.util.concurrent.ExecutorService; import org.mozilla.gecko.sync.CryptoRecord; import org.mozilla.gecko.sync.crypto.CryptoException; import org.mozilla.gecko.sync.crypto.KeyBundle; +import org.mozilla.gecko.sync.repositories.InactiveSessionException; import org.mozilla.gecko.sync.repositories.NoStoreDelegateException; import org.mozilla.gecko.sync.repositories.RecordFactory; import org.mozilla.gecko.sync.repositories.RepositorySession; -import org.mozilla.gecko.sync.repositories.RepositorySessionBundle; -import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate; import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate; -import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate; -import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate; import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate; -import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate; import org.mozilla.gecko.sync.repositories.domain.Record; /** @@ -94,14 +57,12 @@ import org.mozilla.gecko.sync.repositories.domain.Record; * @author rnewman * */ -public class Crypto5MiddlewareRepositorySession extends RepositorySession { +public class Crypto5MiddlewareRepositorySession extends MiddlewareRepositorySession { private KeyBundle keyBundle; - private RepositorySession inner; private RecordFactory recordFactory; public Crypto5MiddlewareRepositorySession(RepositorySession session, Crypto5MiddlewareRepository repository, RecordFactory recordFactory) { - super(repository); - this.inner = session; + super(session, repository); this.keyBundle = repository.keyBundle; this.recordFactory = recordFactory; } @@ -180,13 +141,6 @@ public class Crypto5MiddlewareRepositorySession extends RepositorySession { return new DecryptingTransformingFetchDelegate(inner, this.keyBundle, this.recordFactory); } - @Override - public void guidsSince(long timestamp, - RepositorySessionGuidsSinceDelegate delegate) { - // TODO: need to do anything here? - inner.guidsSince(timestamp, delegate); - } - @Override public void fetchSince(long timestamp, RepositorySessionFetchRecordsDelegate delegate) { @@ -195,7 +149,7 @@ public class Crypto5MiddlewareRepositorySession extends RepositorySession { @Override public void fetch(String[] guids, - RepositorySessionFetchRecordsDelegate delegate) { + RepositorySessionFetchRecordsDelegate delegate) throws InactiveSessionException { inner.fetch(guids, makeUnwrappingDelegate(delegate)); } @@ -230,92 +184,4 @@ public class Crypto5MiddlewareRepositorySession extends RepositorySession { // Allow the inner session to do delegate handling. inner.store(rec); } - - @Override - public void wipe(RepositorySessionWipeDelegate delegate) { - inner.wipe(delegate); - } - - @Override - public void storeDone() { - inner.storeDone(); - } - - @Override - public void storeDone(long storeEnd) { - inner.storeDone(storeEnd); - } - - public class Crypto5MiddlewareRepositorySessionBeginDelegate implements RepositorySessionBeginDelegate { - private Crypto5MiddlewareRepositorySession outerSession; - private RepositorySessionBeginDelegate next; - - public Crypto5MiddlewareRepositorySessionBeginDelegate(Crypto5MiddlewareRepositorySession outerSession, RepositorySessionBeginDelegate next) { - this.outerSession = outerSession; - this.next = next; - } - - @Override - public void onBeginFailed(Exception ex) { - next.onBeginFailed(ex); - } - - @Override - public void onBeginSucceeded(RepositorySession session) { - outerSession.setStatus(SessionStatus.ACTIVE); - next.onBeginSucceeded(outerSession); - } - - @Override - public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) { - return this; - } - } - - public void begin(RepositorySessionBeginDelegate delegate) { - inner.begin(new Crypto5MiddlewareRepositorySessionBeginDelegate(this, delegate)); - } - - public class Crypto5MiddlewareRepositorySessionFinishDelegate implements RepositorySessionFinishDelegate { - private Crypto5MiddlewareRepositorySession outerSession; - private RepositorySessionFinishDelegate next; - - public Crypto5MiddlewareRepositorySessionFinishDelegate(Crypto5MiddlewareRepositorySession outerSession, RepositorySessionFinishDelegate next) { - this.outerSession = outerSession; - this.next = next; - } - - @Override - public void onFinishFailed(Exception ex) { - next.onFinishFailed(ex); - } - - @Override - public void onFinishSucceeded(RepositorySession session, RepositorySessionBundle bundle) { - outerSession.setStatus(SessionStatus.DONE); - next.onFinishSucceeded(outerSession, bundle); - } - - @Override - public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) { - return this; - } - } - - @Override - public void finish(RepositorySessionFinishDelegate delegate) { - inner.finish(new Crypto5MiddlewareRepositorySessionFinishDelegate(this, delegate)); - } - - @Override - public void abort() { - setStatus(SessionStatus.ABORTED); - inner.abort(); - } - - @Override - public void abort(RepositorySessionFinishDelegate delegate) { - this.status = SessionStatus.DONE; // TODO: ABORTED? - inner.abort(new Crypto5MiddlewareRepositorySessionFinishDelegate(this, delegate)); - } } diff --git a/mobile/android/base/sync/middleware/MiddlewareRepository.java b/mobile/android/base/sync/middleware/MiddlewareRepository.java index 473bc1dbb6d0..abbc9200bcd9 100644 --- a/mobile/android/base/sync/middleware/MiddlewareRepository.java +++ b/mobile/android/base/sync/middleware/MiddlewareRepository.java @@ -51,6 +51,5 @@ public abstract class MiddlewareRepository extends Repository { public RepositorySessionCreationDelegate deferredCreationDelegate() { return this; } - } } diff --git a/mobile/android/base/sync/middleware/MiddlewareRepositorySession.java b/mobile/android/base/sync/middleware/MiddlewareRepositorySession.java new file mode 100644 index 000000000000..af9d9acf3b7b --- /dev/null +++ b/mobile/android/base/sync/middleware/MiddlewareRepositorySession.java @@ -0,0 +1,164 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.sync.middleware; + +import java.util.concurrent.ExecutorService; + +import org.mozilla.gecko.sync.Logger; +import org.mozilla.gecko.sync.repositories.InactiveSessionException; +import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException; +import org.mozilla.gecko.sync.repositories.RepositorySession; +import org.mozilla.gecko.sync.repositories.RepositorySessionBundle; +import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate; +import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate; +import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate; +import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate; + +public abstract class MiddlewareRepositorySession extends RepositorySession { + private static final String LOG_TAG = "MiddlewareSession"; + protected final RepositorySession inner; + + public MiddlewareRepositorySession(RepositorySession innerSession, MiddlewareRepository repository) { + super(repository); + this.inner = innerSession; + } + + @Override + public void wipe(RepositorySessionWipeDelegate delegate) { + inner.wipe(delegate); + } + + public class MiddlewareRepositorySessionBeginDelegate implements RepositorySessionBeginDelegate { + + private MiddlewareRepositorySession outerSession; + private RepositorySessionBeginDelegate next; + + public MiddlewareRepositorySessionBeginDelegate(MiddlewareRepositorySession outerSession, RepositorySessionBeginDelegate next) { + this.outerSession = outerSession; + this.next = next; + } + + @Override + public void onBeginFailed(Exception ex) { + next.onBeginFailed(ex); + } + + @Override + public void onBeginSucceeded(RepositorySession session) { + next.onBeginSucceeded(outerSession); + } + + @Override + public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) { + final RepositorySessionBeginDelegate deferred = next.deferredBeginDelegate(executor); + return new RepositorySessionBeginDelegate() { + @Override + public void onBeginSucceeded(RepositorySession session) { + if (inner != session) { + Logger.warn(LOG_TAG, "Got onBeginSucceeded for session " + session + ", not our inner session!"); + } + deferred.onBeginSucceeded(outerSession); + } + + @Override + public void onBeginFailed(Exception ex) { + deferred.onBeginFailed(ex); + } + + @Override + public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) { + return this; + } + }; + } + } + + public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException { + inner.begin(new MiddlewareRepositorySessionBeginDelegate(this, delegate)); + } + + public class MiddlewareRepositorySessionFinishDelegate implements RepositorySessionFinishDelegate { + private final MiddlewareRepositorySession outerSession; + private final RepositorySessionFinishDelegate next; + + public MiddlewareRepositorySessionFinishDelegate(MiddlewareRepositorySession outerSession, RepositorySessionFinishDelegate next) { + this.outerSession = outerSession; + this.next = next; + } + + @Override + public void onFinishFailed(Exception ex) { + next.onFinishFailed(ex); + } + + @Override + public void onFinishSucceeded(RepositorySession session, RepositorySessionBundle bundle) { + next.onFinishSucceeded(outerSession, bundle); + } + + @Override + public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) { + return this; + } + } + + @Override + public void finish(RepositorySessionFinishDelegate delegate) throws InactiveSessionException { + inner.finish(new MiddlewareRepositorySessionFinishDelegate(this, delegate)); + } + + + @Override + public synchronized void ensureActive() throws InactiveSessionException { + inner.ensureActive(); + } + + @Override + public synchronized boolean isActive() { + return inner.isActive(); + } + + @Override + public synchronized SessionStatus getStatus() { + return inner.getStatus(); + } + + @Override + public synchronized void setStatus(SessionStatus status) { + inner.setStatus(status); + } + + @Override + public synchronized void transitionFrom(SessionStatus from, SessionStatus to) + throws InvalidSessionTransitionException { + inner.transitionFrom(from, to); + } + + @Override + public void abort() { + inner.abort(); + } + + @Override + public void abort(RepositorySessionFinishDelegate delegate) { + inner.abort(new MiddlewareRepositorySessionFinishDelegate(this, delegate)); + } + + @Override + public void guidsSince(long timestamp, RepositorySessionGuidsSinceDelegate delegate) { + // TODO: need to do anything here? + inner.guidsSince(timestamp, delegate); + } + + @Override + public void storeDone() { + inner.storeDone(); + } + + @Override + public void storeDone(long storeEnd) { + inner.storeDone(storeEnd); + } +} \ No newline at end of file diff --git a/mobile/android/base/sync/net/SyncStorageRequestDelegate.java b/mobile/android/base/sync/net/SyncStorageRequestDelegate.java index 237e374674c1..d00f5871740f 100644 --- a/mobile/android/base/sync/net/SyncStorageRequestDelegate.java +++ b/mobile/android/base/sync/net/SyncStorageRequestDelegate.java @@ -1,39 +1,6 @@ -/* ***** 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 Android Sync Client. - * - * The Initial Developer of the Original Code is - * the Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Richard Newman - * - * 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 ***** */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.sync.net; diff --git a/mobile/android/base/sync/net/WBOCollectionRequestDelegate.java b/mobile/android/base/sync/net/WBOCollectionRequestDelegate.java index e4f03ce885cc..4428cf7da090 100644 --- a/mobile/android/base/sync/net/WBOCollectionRequestDelegate.java +++ b/mobile/android/base/sync/net/WBOCollectionRequestDelegate.java @@ -1,55 +1,25 @@ -/* ***** 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 Android Sync Client. - * - * The Initial Developer of the Original Code is - * the Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Richard Newman - * - * 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 ***** */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.sync.net; import org.mozilla.gecko.sync.crypto.KeyBundle; import org.mozilla.gecko.sync.CryptoRecord; +import org.mozilla.gecko.sync.KeyBundleProvider; /** * Subclass this to handle collection fetches. * @author rnewman * */ -public abstract class WBOCollectionRequestDelegate extends - SyncStorageCollectionRequestDelegate { +public abstract class WBOCollectionRequestDelegate +extends SyncStorageCollectionRequestDelegate +implements KeyBundleProvider { - public abstract void handleWBO(CryptoRecord record); + @Override public abstract KeyBundle keyBundle(); + public abstract void handleWBO(CryptoRecord record); @Override public void handleRequestProgress(String progress) { diff --git a/mobile/android/base/sync/net/WBORequestDelegate.java b/mobile/android/base/sync/net/WBORequestDelegate.java new file mode 100644 index 000000000000..fdec955d9f97 --- /dev/null +++ b/mobile/android/base/sync/net/WBORequestDelegate.java @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.sync.net; + +import org.mozilla.gecko.sync.KeyBundleProvider; +import org.mozilla.gecko.sync.crypto.KeyBundle; + +public abstract class WBORequestDelegate +implements SyncStorageRequestDelegate, KeyBundleProvider { + @Override + public abstract KeyBundle keyBundle(); +} diff --git a/mobile/android/base/sync/repositories/RepositorySession.java b/mobile/android/base/sync/repositories/RepositorySession.java index f30777b616cc..443b763c0292 100644 --- a/mobile/android/base/sync/repositories/RepositorySession.java +++ b/mobile/android/base/sync/repositories/RepositorySession.java @@ -80,7 +80,7 @@ public abstract class RepositorySession { Logger.trace(LOG_TAG, message); } - protected SessionStatus status = SessionStatus.UNSTARTED; + private SessionStatus status = SessionStatus.UNSTARTED; protected Repository repository; protected RepositorySessionStoreDelegate delegate; @@ -109,7 +109,7 @@ public abstract class RepositorySession { public abstract void guidsSince(long timestamp, RepositorySessionGuidsSinceDelegate delegate); public abstract void fetchSince(long timestamp, RepositorySessionFetchRecordsDelegate delegate); - public abstract void fetch(String[] guids, RepositorySessionFetchRecordsDelegate delegate); + public abstract void fetch(String[] guids, RepositorySessionFetchRecordsDelegate delegate) throws InactiveSessionException; public abstract void fetchAll(RepositorySessionFetchRecordsDelegate delegate); /** @@ -182,21 +182,19 @@ public abstract class RepositorySession { * */ protected void sharedBegin() throws InvalidSessionTransitionException { - if (this.status == SessionStatus.UNSTARTED) { - this.status = SessionStatus.ACTIVE; - } else { - Logger.error(LOG_TAG, "Tried to begin() an already active or finished session"); + Logger.debug(LOG_TAG, "Shared begin."); + if (delegateQueue.isShutdown()) { throw new InvalidSessionTransitionException(null); } + if (storeWorkQueue.isShutdown()) { + throw new InvalidSessionTransitionException(null); + } + this.transitionFrom(SessionStatus.UNSTARTED, SessionStatus.ACTIVE); } - public void begin(RepositorySessionBeginDelegate delegate) { - try { - sharedBegin(); - delegate.deferredBeginDelegate(delegateQueue).onBeginSucceeded(this); - } catch (Exception e) { - delegate.deferredBeginDelegate(delegateQueue).onBeginFailed(e); - } + public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException { + sharedBegin(); + delegate.deferredBeginDelegate(delegateQueue).onBeginSucceeded(this); } protected RepositorySessionBundle getBundle() { @@ -231,43 +229,85 @@ public abstract class RepositorySession { * @param delegate */ public void abort(RepositorySessionFinishDelegate delegate) { - this.status = SessionStatus.DONE; // TODO: ABORTED? + this.abort(); delegate.deferredFinishDelegate(delegateQueue).onFinishSucceeded(this, this.getBundle(null)); } - public void finish(final RepositorySessionFinishDelegate delegate) { - if (this.status == SessionStatus.ACTIVE) { - this.status = SessionStatus.DONE; - delegate.deferredFinishDelegate(delegateQueue).onFinishSucceeded(this, this.getBundle(null)); - } else { - Logger.error(LOG_TAG, "Tried to finish() an unstarted or already finished session"); - Exception e = new InvalidSessionTransitionException(null); - delegate.deferredFinishDelegate(delegateQueue).onFinishFailed(e); - } - Logger.info(LOG_TAG, "Shutting down work queues."); - // storeWorkQueue.shutdown(); - // delegateQueue.shutdown(); - } - - public boolean isActive() { - return status == SessionStatus.ACTIVE; - } - - public SessionStatus getStatus() { - return status; - } - - public void setStatus(SessionStatus status) { - this.status = status; - } - public void abort() { // TODO: do something here. - status = SessionStatus.ABORTED; + this.setStatus(SessionStatus.ABORTED); + try { + storeWorkQueue.shutdown(); + } catch (Exception e) { + Logger.error(LOG_TAG, "Caught exception shutting down store work queue.", e); + } + try { + delegateQueue.shutdown(); + } catch (Exception e) { + Logger.error(LOG_TAG, "Caught exception shutting down delegate queue.", e); + } + } + + public void finish(final RepositorySessionFinishDelegate delegate) throws InactiveSessionException { + try { + this.transitionFrom(SessionStatus.ACTIVE, SessionStatus.DONE); + delegate.deferredFinishDelegate(delegateQueue).onFinishSucceeded(this, this.getBundle(null)); + } catch (InvalidSessionTransitionException e) { + Logger.error(LOG_TAG, "Tried to finish() an unstarted or already finished session"); + InactiveSessionException ex = new InactiveSessionException(null); + ex.initCause(e); + throw ex; + } + + Logger.info(LOG_TAG, "Shutting down work queues."); storeWorkQueue.shutdown(); delegateQueue.shutdown(); } + /** + * Run the provided command if we're active and our delegate queue + * is not shut down. + * + * @param command + * @throws InactiveSessionException + */ + protected synchronized void executeDelegateCommand(Runnable command) + throws InactiveSessionException { + if (!isActive() || delegateQueue.isShutdown()) { + throw new InactiveSessionException(null); + } + delegateQueue.execute(command); + } + + public synchronized void ensureActive() throws InactiveSessionException { + if (!isActive()) { + throw new InactiveSessionException(null); + } + } + + public synchronized boolean isActive() { + return status == SessionStatus.ACTIVE; + } + + public synchronized SessionStatus getStatus() { + return status; + } + + public synchronized void setStatus(SessionStatus status) { + this.status = status; + } + + public synchronized void transitionFrom(SessionStatus from, SessionStatus to) throws InvalidSessionTransitionException { + if (from == null || this.status == from) { + Logger.trace(LOG_TAG, "Successfully transitioning from " + this.status + " to " + to); + + this.status = to; + return; + } + Logger.warn(LOG_TAG, "Wanted to transition from " + from + " but in state " + this.status); + throw new InvalidSessionTransitionException(null); + } + /** * Produce a record that is some combination of the remote and local records * provided. diff --git a/mobile/android/base/sync/repositories/StoreTrackingRepositorySession.java b/mobile/android/base/sync/repositories/StoreTrackingRepositorySession.java index 7c9743ee1ea4..32a792ec4df4 100644 --- a/mobile/android/base/sync/repositories/StoreTrackingRepositorySession.java +++ b/mobile/android/base/sync/repositories/StoreTrackingRepositorySession.java @@ -24,7 +24,7 @@ public abstract class StoreTrackingRepositorySession extends RepositorySession { } @Override - public void begin(RepositorySessionBeginDelegate delegate) { + public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException { RepositorySessionBeginDelegate deferredDelegate = delegate.deferredBeginDelegate(delegateQueue); try { super.sharedBegin(); @@ -74,7 +74,7 @@ public abstract class StoreTrackingRepositorySession extends RepositorySession { } @Override - public void finish(RepositorySessionFinishDelegate delegate) { + public void finish(RepositorySessionFinishDelegate delegate) throws InactiveSessionException { super.finish(delegate); this.storeTracker = null; } diff --git a/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java b/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java index ae14f93160e5..35c21e4518dd 100644 --- a/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java +++ b/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java @@ -1,40 +1,6 @@ -/* ***** 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 Android Sync Client. - * - * The Initial Developer of the Original Code is - * the Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Jason Voll - * Richard Newman - * - * 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 ***** */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.sync.repositories.android; @@ -62,8 +28,29 @@ public class AndroidBrowserBookmarksDataAccessor extends AndroidBrowserRepositor */ private static final String BOOKMARK_IS_FOLDER = BrowserContract.Bookmarks.IS_FOLDER + " = 1"; private static final String GUID_NOT_TAGS_OR_PLACES = BrowserContract.SyncColumns.GUID + " NOT IN ('" + - BrowserContract.Bookmarks.TAGS_FOLDER_GUID + "', '" + - BrowserContract.Bookmarks.PLACES_FOLDER_GUID + "')"; + BrowserContract.Bookmarks.TAGS_FOLDER_GUID + "', '" + + BrowserContract.Bookmarks.PLACES_FOLDER_GUID + "')"; + + private static final String EXCLUDE_SPECIAL_GUIDS_WHERE_CLAUSE; + static { + if (AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS.length > 0) { + StringBuilder b = new StringBuilder(BrowserContract.SyncColumns.GUID + " NOT IN ("); + + int remaining = AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS.length - 1; + for (String specialGuid : AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS) { + b.append('"'); + b.append(specialGuid); + b.append('"'); + if (remaining-- > 0) { + b.append(", "); + } + } + b.append(')'); + EXCLUDE_SPECIAL_GUIDS_WHERE_CLAUSE = b.toString(); + } else { + EXCLUDE_SPECIAL_GUIDS_WHERE_CLAUSE = null; // null is a valid WHERE clause. + } + } public static final String TYPE_FOLDER = "folder"; public static final String TYPE_BOOKMARK = "bookmark"; @@ -84,6 +71,13 @@ public class AndroidBrowserBookmarksDataAccessor extends AndroidBrowserRepositor return BrowserContractHelpers.BOOKMARKS_POSITIONS_CONTENT_URI; } + @Override + public void wipe() { + Uri uri = getUri(); + Logger.info(LOG_TAG, "wiping (except for special guids): " + uri); + context.getContentResolver().delete(uri, EXCLUDE_SPECIAL_GUIDS_WHERE_CLAUSE, null); + } + protected Cursor getGuidsIDsForFolders() throws NullCursorException { // Exclude "places" and "tags", in case they've ended up in the DB. String where = BOOKMARK_IS_FOLDER + " AND " + GUID_NOT_TAGS_OR_PLACES; diff --git a/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepository.java b/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepository.java index 6de0ac72fc3c..c23e6c08c769 100644 --- a/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepository.java +++ b/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepository.java @@ -1,40 +1,6 @@ -/* ***** 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 Android Sync Client. - * - * The Initial Developer of the Original Code is - * the Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Jason Voll - * Richard Newman - * - * 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 ***** */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.sync.repositories.android; @@ -48,7 +14,8 @@ public class AndroidBrowserBookmarksRepository extends AndroidBrowserRepository @Override protected void sessionCreator(RepositorySessionCreationDelegate delegate, Context context) { AndroidBrowserBookmarksRepositorySession session = new AndroidBrowserBookmarksRepositorySession(AndroidBrowserBookmarksRepository.this, context); - delegate.onSessionCreated(session); + final RepositorySessionCreationDelegate deferredCreationDelegate = delegate.deferredCreationDelegate(); + deferredCreationDelegate.onSessionCreated(session); } @Override diff --git a/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java b/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java index d229cd5bad5b..8f0044c04e9b 100644 --- a/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java +++ b/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java @@ -16,6 +16,8 @@ import org.mozilla.gecko.R; import org.mozilla.gecko.db.BrowserContract; import org.mozilla.gecko.sync.Logger; import org.mozilla.gecko.sync.Utils; +import org.mozilla.gecko.sync.repositories.InactiveSessionException; +import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException; import org.mozilla.gecko.sync.repositories.NoGuidForIdException; import org.mozilla.gecko.sync.repositories.NullCursorException; import org.mozilla.gecko.sync.repositories.ParentNotFoundException; @@ -478,7 +480,7 @@ public class AndroidBrowserBookmarksRepositorySession extends AndroidBrowserRepo } @Override - public void begin(RepositorySessionBeginDelegate delegate) { + public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException { // Check for the existence of special folders // and insert them if they don't exist. Cursor cur; @@ -526,7 +528,7 @@ public class AndroidBrowserBookmarksRepositorySession extends AndroidBrowserRepo } @Override - public void finish(RepositorySessionFinishDelegate delegate) { + public void finish(RepositorySessionFinishDelegate delegate) throws InactiveSessionException { // Override finish to do this check; make sure all records // needing re-parenting have been re-parented. if (needsReparenting != 0) { diff --git a/mobile/android/base/sync/repositories/android/AndroidBrowserHistoryRepositorySession.java b/mobile/android/base/sync/repositories/android/AndroidBrowserHistoryRepositorySession.java index a4737d7b41b3..19282ffc8824 100644 --- a/mobile/android/base/sync/repositories/android/AndroidBrowserHistoryRepositorySession.java +++ b/mobile/android/base/sync/repositories/android/AndroidBrowserHistoryRepositorySession.java @@ -6,7 +6,6 @@ package org.mozilla.gecko.sync.repositories.android; import org.json.simple.JSONArray; import org.json.simple.JSONObject; -import org.mozilla.gecko.db.BrowserContract; import org.mozilla.gecko.sync.repositories.NullCursorException; import org.mozilla.gecko.sync.repositories.Repository; import org.mozilla.gecko.sync.repositories.domain.HistoryRecord; diff --git a/mobile/android/base/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java b/mobile/android/base/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java index 4fc081725410..b9439aedaa0b 100644 --- a/mobile/android/base/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java +++ b/mobile/android/base/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java @@ -64,14 +64,27 @@ public abstract class AndroidBrowserRepositoryDataAccessor { protected abstract ContentValues getContentValues(Record record); protected abstract Uri getUri(); + public void dumpDB() { + Cursor cur = null; + try { + cur = queryHelper.safeQuery(".dumpDB", null, null, null, null); + RepoUtils.dumpCursor(cur); + } catch (NullCursorException e) { + } finally { + if (cur != null) { + cur.close(); + } + } + } + public String dateModifiedWhere(long timestamp) { return BrowserContract.SyncColumns.DATE_MODIFIED + " >= " + Long.toString(timestamp); } public void wipe() { - Logger.info(LOG_TAG, "wiping: " + getUri()); - String where = BrowserContract.SyncColumns.GUID + " NOT IN ('mobile')"; - context.getContentResolver().delete(getUri(), where, null); + Uri uri = getUri(); + Logger.info(LOG_TAG, "wiping: " + uri); + context.getContentResolver().delete(uri, null, null); } public void purgeDeleted() throws NullCursorException { diff --git a/mobile/android/base/sync/repositories/android/AndroidBrowserRepositorySession.java b/mobile/android/base/sync/repositories/android/AndroidBrowserRepositorySession.java index 566dfc0474bd..37beaa02972e 100644 --- a/mobile/android/base/sync/repositories/android/AndroidBrowserRepositorySession.java +++ b/mobile/android/base/sync/repositories/android/AndroidBrowserRepositorySession.java @@ -141,14 +141,9 @@ public abstract class AndroidBrowserRepositorySession extends StoreTrackingRepos } @Override - public void begin(RepositorySessionBeginDelegate delegate) { + public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException { RepositorySessionBeginDelegate deferredDelegate = delegate.deferredBeginDelegate(delegateQueue); - try { - super.sharedBegin(); - } catch (InvalidSessionTransitionException e) { - deferredDelegate.onBeginFailed(e); - return; - } + super.sharedBegin(); try { // We do this check here even though it results in one extra call to the DB @@ -241,9 +236,9 @@ public abstract class AndroidBrowserRepositorySession extends StoreTrackingRepos @Override public void fetch(String[] guids, - RepositorySessionFetchRecordsDelegate delegate) { + RepositorySessionFetchRecordsDelegate delegate) throws InactiveSessionException { FetchRunnable command = new FetchRunnable(guids, now(), null, delegate); - delegateQueue.execute(command); + executeDelegateCommand(command); } abstract class FetchingRunnable implements Runnable { @@ -289,7 +284,7 @@ public abstract class AndroidBrowserRepositorySession extends StoreTrackingRepos } } - class FetchRunnable extends FetchingRunnable { + public class FetchRunnable extends FetchingRunnable { private String[] guids; private long end; private RecordFilter filter; @@ -392,6 +387,7 @@ public abstract class AndroidBrowserRepositorySession extends StoreTrackingRepos @Override public void run() { if (!isActive()) { + Logger.warn(LOG_TAG, "AndroidBrowserRepositorySession is inactive. Store failing."); delegate.onRecordStoreFailed(new InactiveSessionException(null)); return; } diff --git a/mobile/android/base/sync/repositories/android/ClientsDatabase.java b/mobile/android/base/sync/repositories/android/ClientsDatabase.java new file mode 100644 index 000000000000..ac50979c253c --- /dev/null +++ b/mobile/android/base/sync/repositories/android/ClientsDatabase.java @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.sync.repositories.android; + +import org.mozilla.gecko.sync.Logger; +import org.mozilla.gecko.sync.repositories.NullCursorException; +import org.mozilla.gecko.sync.repositories.domain.ClientRecord; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +public class ClientsDatabase extends CachedSQLiteOpenHelper { + + public static final String LOG_TAG = "ClientsDatabase"; + + // Database Specifications. + protected static final String DB_NAME = "clients_database"; + protected static final int SCHEMA_VERSION = 1; + + // Clients Table. + public static final String TBL_CLIENTS = "clients"; + public static final String COL_ACCOUNT_GUID = "guid"; + public static final String COL_PROFILE = "profile"; + public static final String COL_NAME = "name"; + public static final String COL_TYPE = "device_type"; + + public static final String[] TBL_COLUMNS = new String[] { COL_ACCOUNT_GUID, COL_PROFILE, COL_NAME, COL_TYPE }; + public static final String TBL_KEY = COL_ACCOUNT_GUID + " = ? AND " + + COL_PROFILE + " = ?"; + + private final RepoUtils.QueryHelper queryHelper; + + public ClientsDatabase(Context context) { + super(context, DB_NAME, null, SCHEMA_VERSION); + this.queryHelper = new RepoUtils.QueryHelper(context, null, LOG_TAG); + } + + @Override + public void onCreate(SQLiteDatabase db) { + String createTableSql = "CREATE TABLE " + TBL_CLIENTS + " (" + + COL_ACCOUNT_GUID + " TEXT, " + + COL_PROFILE + " TEXT, " + + COL_NAME + " TEXT, " + + COL_TYPE + " TEXT, " + + "PRIMARY KEY (" + COL_ACCOUNT_GUID + ", " + COL_PROFILE + "))"; + db.execSQL(createTableSql); + } + + public void wipe() { + SQLiteDatabase db = this.getCachedWritableDatabase(); + onUpgrade(db, SCHEMA_VERSION, SCHEMA_VERSION); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // For now we'll just drop and recreate the tables. + db.execSQL("DROP TABLE IF EXISTS " + TBL_CLIENTS); + onCreate(db); + } + + // If a record with given GUID exists, we'll update it, + // otherwise we'll insert it. + public void store(String profileId, ClientRecord record) { + SQLiteDatabase db = this.getCachedWritableDatabase(); + + ContentValues cv = new ContentValues(); + cv.put(COL_ACCOUNT_GUID, record.guid); + cv.put(COL_PROFILE, profileId); + cv.put(COL_NAME, record.name); + cv.put(COL_TYPE, record.type); + + String[] args = new String[] { record.guid, profileId }; + int rowsUpdated = db.update(TBL_CLIENTS, cv, TBL_KEY, args); + + if (rowsUpdated >= 1) { + Logger.debug(LOG_TAG, "Replaced client record for row with accountGUID " + record.guid); + } else { + long rowId = db.insert(TBL_CLIENTS, null, cv); + Logger.debug(LOG_TAG, "Inserted client record into row: " + rowId); + } + } + + public Cursor fetch(String accountGuid, String profileId) throws NullCursorException { + String[] args = new String[] { accountGuid, profileId }; + SQLiteDatabase db = this.getCachedReadableDatabase(); + + return queryHelper.safeQuery(db, ".fetch", TBL_CLIENTS, TBL_COLUMNS, TBL_KEY, args); + } + + public Cursor fetchAll() throws NullCursorException { + SQLiteDatabase db = this.getCachedReadableDatabase(); + + return queryHelper.safeQuery(db, ".fetch", TBL_CLIENTS, TBL_COLUMNS, null, null); + } + + public void delete(String accountGUID, String profileId) { + String[] args = new String[] { accountGUID, profileId }; + + SQLiteDatabase db = this.getCachedWritableDatabase(); + db.delete(TBL_CLIENTS, TBL_KEY, args); + } +} diff --git a/mobile/android/base/sync/repositories/android/ClientsDatabaseAccessor.java b/mobile/android/base/sync/repositories/android/ClientsDatabaseAccessor.java new file mode 100644 index 000000000000..102a2fec3eb2 --- /dev/null +++ b/mobile/android/base/sync/repositories/android/ClientsDatabaseAccessor.java @@ -0,0 +1,115 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.sync.repositories.android; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.mozilla.gecko.sync.repositories.NullCursorException; +import org.mozilla.gecko.sync.repositories.domain.ClientRecord; +import org.mozilla.gecko.sync.setup.Constants; + +import android.content.Context; +import android.database.Cursor; + +public class ClientsDatabaseAccessor { + + public static final String LOG_TAG = "ClientsDatabaseAccessor"; + + private ClientsDatabase db; + + // Need this so we can properly stub out the class for testing. + public ClientsDatabaseAccessor() {} + + public ClientsDatabaseAccessor(Context context) { + db = new ClientsDatabase(context); + } + + public void store(ClientRecord record) { + db.store(getProfileId(), record); + } + + public void store(Collection records) { + for (ClientRecord record : records) { + this.store(record); + } + } + + public ClientRecord fetch(String accountGUID) throws NullCursorException { + Cursor cur = null; + try { + cur = db.fetch(accountGUID, getProfileId()); + + if (cur == null || !cur.moveToFirst()) { + return null; + } + return recordFromCursor(cur); + } finally { + if (cur != null) { + cur.close(); + } + } + } + + public Map fetchAll() throws NullCursorException { + HashMap map = new HashMap(); + Cursor cur = null; + try { + cur = db.fetchAll(); + if (cur == null || !cur.moveToFirst()) { + return Collections.unmodifiableMap(map); + } + while (!cur.isAfterLast()) { + ClientRecord clientRecord = recordFromCursor(cur); + map.put(clientRecord.guid, clientRecord); + cur.moveToNext(); + } + + return Collections.unmodifiableMap(map); + } finally { + if (cur != null) { + cur.close(); + } + } + } + + protected ClientRecord recordFromCursor(Cursor cur) { + String accountGUID = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_ACCOUNT_GUID); + String clientName = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_NAME); + String clientType = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_TYPE); + ClientRecord record = new ClientRecord(accountGUID); + record.name = clientName; + record.type = clientType; + return record; + } + + public int clientsCount() { + Cursor cur; + try { + cur = db.fetchAll(); + } catch (NullCursorException e) { + return 0; + } + try { + return cur.getCount(); + } finally { + cur.close(); + } + } + + private String getProfileId() { + return Constants.PROFILE_ID; + } + + public void wipe() { + db.wipe(); + } + + public void close() { + db.close(); + } +} diff --git a/mobile/android/base/sync/repositories/android/RepoUtils.java b/mobile/android/base/sync/repositories/android/RepoUtils.java index e5d2ec421959..247510601231 100644 --- a/mobile/android/base/sync/repositories/android/RepoUtils.java +++ b/mobile/android/base/sync/repositories/android/RepoUtils.java @@ -10,6 +10,7 @@ import org.json.simple.parser.ParseException; import org.mozilla.gecko.db.BrowserContract; import org.mozilla.gecko.sync.Logger; import org.mozilla.gecko.sync.repositories.NullCursorException; +import org.mozilla.gecko.sync.repositories.domain.ClientRecord; import org.mozilla.gecko.sync.repositories.domain.HistoryRecord; import org.mozilla.gecko.sync.repositories.domain.PasswordRecord; @@ -150,6 +151,16 @@ public class RepoUtils { return rec; } + public static void logClient(ClientRecord rec) { + if (Logger.logVerbose(LOG_TAG)) { + Logger.trace(LOG_TAG, "Returning client record " + rec.guid + " (" + rec.androidID + ")"); + Logger.trace(LOG_TAG, "Client Name: " + rec.name); + Logger.trace(LOG_TAG, "Client Type: " + rec.type); + Logger.trace(LOG_TAG, "Last Modified: " + rec.lastModified); + Logger.trace(LOG_TAG, "Deleted: " + rec.deleted); + } + } + public static PasswordRecord passwordFromMirrorCursor(Cursor cur) { String guid = getStringFromCursor(cur, BrowserContract.SyncColumns.GUID); @@ -187,4 +198,58 @@ public class RepoUtils { return a.equals(b); } + + private static String fixedWidth(int width, String s) { + if (s == null) { + return spaces(width); + } + int length = s.length(); + if (width == length) { + return s; + } + if (width > length) { + return s + spaces(width - length); + } + return s.substring(0, width); + } + + private static String spaces(int i) { + return " ".substring(0, i); + } + + public static void dumpCursor(Cursor cur) { + int originalPosition = cur.getPosition(); + try { + String[] columnNames = cur.getColumnNames(); + int columnCount = cur.getColumnCount(); + + // 12 chars each column. + for (int i = 0; i < columnCount; ++i) { + System.out.print(fixedWidth(12, columnNames[i]) + " | "); + } + System.out.println(""); + for (int i = 0; i < columnCount; ++i) { + System.out.print("------------" + " | "); + } + System.out.println(""); + if (!cur.moveToFirst()) { + System.out.println("EMPTY"); + return; + } + + cur.moveToFirst(); + while (cur.moveToNext()) { + for (int i = 0; i < columnCount; ++i) { + System.out.print(fixedWidth(12, cur.getString(i)) + " | "); + } + System.out.println(""); + } + for (int i = 0; i < columnCount; ++i) { + System.out.print("---------------"); + } + System.out.println(""); + } finally { + cur.moveToPosition(originalPosition); + } + } } diff --git a/mobile/android/base/sync/repositories/domain/ClientRecord.java b/mobile/android/base/sync/repositories/domain/ClientRecord.java new file mode 100644 index 000000000000..bfb4ff89649c --- /dev/null +++ b/mobile/android/base/sync/repositories/domain/ClientRecord.java @@ -0,0 +1,90 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.sync.repositories.domain; + +import org.mozilla.gecko.sync.ExtendedJSONObject; +import org.mozilla.gecko.sync.Utils; +import org.mozilla.gecko.sync.repositories.android.RepoUtils; +import org.mozilla.gecko.sync.setup.Constants; + +public class ClientRecord extends Record { + + public static final String COLLECTION_NAME = "clients"; + + public ClientRecord(String guid, String collection, long lastModified, boolean deleted) { + super(guid, collection, lastModified, deleted); + } + + public ClientRecord(String guid, String collection, long lastModified) { + this(guid, collection, lastModified, false); + } + + public ClientRecord(String guid, String collection) { + this(guid, collection, 0, false); + } + + public ClientRecord(String guid) { + this(guid, COLLECTION_NAME, 0, false); + } + + public ClientRecord() { + this(Utils.generateGuid(), COLLECTION_NAME, 0, false); + } + + public String name = Constants.DEFAULT_CLIENT_NAME; + public String type = Constants.CLIENT_TYPE; + + @Override + protected void initFromPayload(ExtendedJSONObject payload) { + this.name = (String) payload.get("name"); + this.type = (String) payload.get("type"); + } + + @Override + protected void populatePayload(ExtendedJSONObject payload) { + putPayload(payload, "id", this.guid); + putPayload(payload, "name", this.name); + putPayload(payload, "type", this.type); + } + + public boolean equals(Object o) { + if (!(o instanceof ClientRecord) || !super.equals(o)) { + return false; + } + + ClientRecord other = (ClientRecord) o; + if (!RepoUtils.stringsEqual(other.name, this.name) || + !RepoUtils.stringsEqual(other.type, this.type)) { + return false; + } + return true; + } + + @Override + public Record copyWithIDs(String guid, long androidID) { + ClientRecord out = new ClientRecord(guid, this.collection, this.lastModified, this.deleted); + out.androidID = androidID; + out.sortIndex = this.sortIndex; + + out.name = this.name; + out.type = this.type; + return out; + } + +/* +Example record: + +{id:"relf31w7B4F1", + name:"marina_mac", + type:"mobile" + commands:[{"args":["bookmarks"],"command":"wipeEngine"}, + {"args":["forms"],"command":"wipeEngine"}, + {"args":["history"],"command":"wipeEngine"}, + {"args":["passwords"],"command":"wipeEngine"}, + {"args":["prefs"],"command":"wipeEngine"}, + {"args":["tabs"],"command":"wipeEngine"}, + {"args":["addons"],"command":"wipeEngine"}]} +*/ +} diff --git a/mobile/android/base/sync/repositories/domain/ClientRecordFactory.java b/mobile/android/base/sync/repositories/domain/ClientRecordFactory.java new file mode 100644 index 000000000000..9a549f3507a1 --- /dev/null +++ b/mobile/android/base/sync/repositories/domain/ClientRecordFactory.java @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.sync.repositories.domain; + +import org.mozilla.gecko.sync.CryptoRecord; +import org.mozilla.gecko.sync.repositories.RecordFactory; + +public class ClientRecordFactory extends RecordFactory { + + @Override + public Record createRecord(Record record) { + ClientRecord r = new ClientRecord(); + r.initFromEnvelope((CryptoRecord) record); + return r; + } +} diff --git a/mobile/android/base/sync/repositories/domain/Record.java b/mobile/android/base/sync/repositories/domain/Record.java index 29ab7904234b..4ce59b4b6c17 100644 --- a/mobile/android/base/sync/repositories/domain/Record.java +++ b/mobile/android/base/sync/repositories/domain/Record.java @@ -150,11 +150,11 @@ public abstract class Record { } /** - * Return true iff the input is a Record which is substantially the - * same as this object. - * * @param o + * The object to which this object should be compared. * @return + * true iff the input is a Record which is substantially the + * same as this object. */ public boolean equalPayloads(Object o) { if (!this.equalIdentifiers(o)) { @@ -165,12 +165,14 @@ public abstract class Record { } /** - * Return true iff the input is a Record which is substantially the - * same as this object, considering the ability and desire two - * reconcile the two objects if possible. + * * * @param o + * The object to which this object should be compared. * @return + * true iff the input is a Record which is substantially the + * same as this object, considering the ability and desire to + * reconcile the two objects if possible. */ public boolean congruentWith(Object o) { if (!this.equalIdentifiers(o)) { @@ -306,13 +308,12 @@ public abstract class Record { } /** - * Return an identical copy of this record with the provided two values. - * * Oh for persistent data structures. * * @param guid * @param androidID * @return + * An identical copy of this record with the provided two values. */ public abstract Record copyWithIDs(String guid, long androidID); } diff --git a/mobile/android/base/sync/setup/Constants.java b/mobile/android/base/sync/setup/Constants.java index ab70baa41287..de04a7c475a1 100644 --- a/mobile/android/base/sync/setup/Constants.java +++ b/mobile/android/base/sync/setup/Constants.java @@ -13,6 +13,14 @@ public class Constants { public static final String OPTION_USERNAME = "option.username"; public static final String AUTHTOKEN_TYPE_PLAIN = "auth.plain"; public static final String OPTION_SERVER = "option.serverUrl"; + public static final String ACCOUNT_GUID = "account.guid"; + public static final String CLIENT_NAME = "account.clientName"; + public static final String NUM_CLIENTS = "account.numClients"; + + // Constants for client records. + public static final String PROFILE_ID = "default"; // Generic profile id for now, until multiple profiles are implemented. + public static final String CLIENT_TYPE = "mobile"; + public static final String DEFAULT_CLIENT_NAME = "Default Name"; // Constants for Activities. public static final String INTENT_EXTRA_IS_SETUP = "isSetup"; diff --git a/mobile/android/base/sync/stage/GlobalSyncStage.java b/mobile/android/base/sync/stage/GlobalSyncStage.java index 6f6849ae2d58..babecd4119b9 100644 --- a/mobile/android/base/sync/stage/GlobalSyncStage.java +++ b/mobile/android/base/sync/stage/GlobalSyncStage.java @@ -50,7 +50,9 @@ public interface GlobalSyncStage { /* ensureSpecialRecords, updateEngineTimestamps, + */ syncClientsEngine, + /* processFirstSyncPref, processClientCommands, updateEnabledEngines, diff --git a/mobile/android/base/sync/stage/ServerSyncStage.java b/mobile/android/base/sync/stage/ServerSyncStage.java index c591b00e2d6d..6679ed088ae3 100644 --- a/mobile/android/base/sync/stage/ServerSyncStage.java +++ b/mobile/android/base/sync/stage/ServerSyncStage.java @@ -73,7 +73,7 @@ public abstract class ServerSyncStage implements /** * Override these in your subclasses. * - * @return + * @return true if this stage should be executed. * @throws MetaGlobalException */ protected boolean isEnabled() throws MetaGlobalException { diff --git a/mobile/android/base/sync/stage/SyncClientsEngineStage.java b/mobile/android/base/sync/stage/SyncClientsEngineStage.java new file mode 100644 index 000000000000..8d57806a2325 --- /dev/null +++ b/mobile/android/base/sync/stage/SyncClientsEngineStage.java @@ -0,0 +1,277 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.sync.stage; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; + +import org.mozilla.gecko.sync.CryptoRecord; +import org.mozilla.gecko.sync.GlobalSession; +import org.mozilla.gecko.sync.HTTPFailureException; +import org.mozilla.gecko.sync.Logger; +import org.mozilla.gecko.sync.NoCollectionKeysSetException; +import org.mozilla.gecko.sync.crypto.CryptoException; +import org.mozilla.gecko.sync.crypto.KeyBundle; +import org.mozilla.gecko.sync.delegates.ClientsDataDelegate; +import org.mozilla.gecko.sync.net.SyncResourceDelegate; +import org.mozilla.gecko.sync.net.SyncStorageCollectionRequest; +import org.mozilla.gecko.sync.net.SyncStorageRecordRequest; +import org.mozilla.gecko.sync.net.SyncStorageResponse; +import org.mozilla.gecko.sync.net.WBOCollectionRequestDelegate; +import org.mozilla.gecko.sync.net.WBORequestDelegate; +import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor; +import org.mozilla.gecko.sync.repositories.android.RepoUtils; +import org.mozilla.gecko.sync.repositories.domain.ClientRecord; +import org.mozilla.gecko.sync.repositories.domain.ClientRecordFactory; + +import ch.boye.httpclientandroidlib.HttpResponse; + +public class SyncClientsEngineStage implements GlobalSyncStage { + protected static final String LOG_TAG = "SyncClientsEngineStage"; + protected static final String COLLECTION_NAME = "clients"; + + protected GlobalSession session; + protected final ClientRecordFactory factory = new ClientRecordFactory(); + protected ClientUploadDelegate clientUploadDelegate; + protected ClientDownloadDelegate clientDownloadDelegate; + protected ClientsDatabaseAccessor db; + + // Account/Profile info + protected boolean shouldWipe; + + /** + * The following two delegates, ClientDownloadDelegate and ClientUploadDelegate + * are both triggered in a chain, starting when execute() calls + * downloadClientRecords(). + * + * Client records are downloaded using a get() request. Upon success of the + * get() request, the local client record is uploaded. + * + * @author Marina Samuel + * + */ + public class ClientDownloadDelegate extends WBOCollectionRequestDelegate { + + @Override + public String credentials() { + return session.credentials(); + } + + @Override + public String ifUnmodifiedSince() { + // TODO last client download time? + return null; + } + + @Override + public void handleRequestSuccess(SyncStorageResponse response) { + try { + clientUploadDelegate = new ClientUploadDelegate(); + session.getClientsDelegate().setClientsCount(db.clientsCount()); + checkAndUpload(); + } finally { + // Close the database to clear cached readableDatabase/writableDatabase + // after we've completed our last transaction (db.store()). + db.close(); + } + } + + @Override + public void handleRequestFailure(SyncStorageResponse response) { + try { + Logger.info(LOG_TAG, "Client upload failed. Aborting sync."); + session.abort(new HTTPFailureException(response), "Client download failed."); + } finally { + // Close the database upon failure. + db.close(); + } + } + + @Override + public void handleRequestError(Exception ex) { + try { + Logger.info(LOG_TAG, "Client upload error. Aborting sync."); + session.abort(ex, "Failure fetching client record."); + } finally { + // Close the database upon error. + db.close(); + } + } + + @Override + public void handleWBO(CryptoRecord record) { + ClientRecord r; + try { + r = (ClientRecord) factory.createRecord(record.decrypt()); + RepoUtils.logClient(r); + } catch (Exception e) { + session.abort(e, "Exception handling client WBO."); + return; + } + wipeAndStore(r); + } + + @Override + public KeyBundle keyBundle() { + try { + return session.keyForCollection(COLLECTION_NAME); + } catch (NoCollectionKeysSetException e) { + session.abort(e, "No collection keys set."); + return null; + } + } + } + + public class ClientUploadDelegate extends WBORequestDelegate { + protected static final String LOG_TAG = "ClientUploadDelegate"; + + @Override + public String credentials() { + return session.credentials(); + } + + @Override + public String ifUnmodifiedSince() { + // TODO last client upload time? + return null; + } + + @Override + public void handleRequestSuccess(SyncStorageResponse response) { + try { + // Response entity must be consumed in order to reuse the connection. + HttpResponse httpResponse = response.httpResponse(); + if (httpResponse != null) { + SyncResourceDelegate.consumeEntity(httpResponse.getEntity()); + } + } finally { + session.advance(); + } + } + + @Override + public void handleRequestFailure(SyncStorageResponse response) { + Logger.info(LOG_TAG, "Client upload failed. Aborting sync."); + session.abort(new HTTPFailureException(response), "Client upload failed."); + } + + @Override + public void handleRequestError(Exception ex) { + Logger.info(LOG_TAG, "Client upload error. Aborting sync."); + session.abort(ex, "Client upload failed."); + } + + @Override + public KeyBundle keyBundle() { + try { + return session.keyForCollection(COLLECTION_NAME); + } catch (NoCollectionKeysSetException e) { + session.abort(e, "No collection keys set."); + return null; + } + } + } + + @Override + public void execute(GlobalSession session) throws NoSuchStageException { + this.session = session; + init(); + + if (shouldDownload()) { + downloadClientRecords(); // Will kick off upload, too… + } else { + // Upload if necessary. + } + } + + protected ClientRecord newLocalClientRecord(ClientsDataDelegate delegate) { + final String ourGUID = delegate.getAccountGUID(); + final String ourName = delegate.getClientName(); + + ClientRecord r = new ClientRecord(ourGUID); + r.name = ourName; + return r; + } + + protected void init() { + db = new ClientsDatabaseAccessor(session.getContext()); + } + + // TODO: Bug 726055 - More considered handling of when to sync. + protected boolean shouldDownload() { + // Ask info/collections whether a download is needed. + return true; + } + + // TODO: Bug 729248 - Smarter upload of client records. + protected boolean shouldUpload() { + return true; + } + + protected void checkAndUpload() { + if (!shouldUpload()) { + Logger.trace(LOG_TAG, "Not uploading client record."); + session.advance(); + return; + } + + // Generate CryptoRecord from ClientRecord to upload. + String encryptionFailure = "Couldn't encrypt new client record."; + ClientRecord localClient = newLocalClientRecord(session.getClientsDelegate()); + CryptoRecord cryptoRecord = localClient.getEnvelope(); + try { + cryptoRecord.keyBundle = clientUploadDelegate.keyBundle(); + cryptoRecord.encrypt(); + this.wipeAndStore(localClient); + this.uploadClientRecord(cryptoRecord); + } catch (UnsupportedEncodingException e) { + session.abort(e, encryptionFailure + " Unsupported encoding."); + } catch (CryptoException e) { + session.abort(e, encryptionFailure); + } + } + + protected void downloadClientRecords() { + shouldWipe = true; + clientDownloadDelegate = makeClientDownloadDelegate(); + + try { + URI getURI = session.config.collectionURI(COLLECTION_NAME, true); + + SyncStorageCollectionRequest request = new SyncStorageCollectionRequest(getURI); + request.delegate = clientDownloadDelegate; + + Logger.trace(LOG_TAG, "Downloading client records."); + request.get(); + } catch (URISyntaxException e) { + session.abort(e, "Invalid URI."); + } + } + + protected void uploadClientRecord(CryptoRecord record) { + try { + URI putURI = session.config.wboURI(COLLECTION_NAME, record.guid); + + SyncStorageRecordRequest request = new SyncStorageRecordRequest(putURI); + request.delegate = clientUploadDelegate; + request.put(record); + } catch (URISyntaxException e) { + session.abort(e, "Invalid URI."); + } + } + + protected ClientDownloadDelegate makeClientDownloadDelegate() { + return new ClientDownloadDelegate(); + } + + protected void wipeAndStore(ClientRecord record) { + if (shouldWipe) { + db.wipe(); + shouldWipe = false; + } + db.store(record); + } +} diff --git a/mobile/android/base/sync/syncadapter/SyncAdapter.java b/mobile/android/base/sync/syncadapter/SyncAdapter.java index 8fb971acd7d8..b1f5cb6f62a3 100644 --- a/mobile/android/base/sync/syncadapter/SyncAdapter.java +++ b/mobile/android/base/sync/syncadapter/SyncAdapter.java @@ -44,6 +44,7 @@ import java.util.concurrent.TimeUnit; import org.json.simple.parser.ParseException; import org.mozilla.gecko.sync.AlreadySyncingException; +import org.mozilla.gecko.sync.GlobalConstants; import org.mozilla.gecko.sync.GlobalSession; import org.mozilla.gecko.sync.NonObjectJSONException; import org.mozilla.gecko.sync.SyncConfiguration; @@ -51,6 +52,7 @@ import org.mozilla.gecko.sync.SyncConfigurationException; import org.mozilla.gecko.sync.SyncException; import org.mozilla.gecko.sync.Utils; import org.mozilla.gecko.sync.crypto.KeyBundle; +import org.mozilla.gecko.sync.delegates.ClientsDataDelegate; import org.mozilla.gecko.sync.delegates.GlobalSessionCallback; import org.mozilla.gecko.sync.setup.Constants; import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage; @@ -73,7 +75,7 @@ import android.os.Bundle; import android.os.Handler; import android.util.Log; -public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSessionCallback { +public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSessionCallback, ClientsDataDelegate { private static final String LOG_TAG = "SyncAdapter"; private static final String PREFS_EARLIEST_NEXT_SYNC = "earliestnextsync"; @@ -197,6 +199,8 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe public Object syncMonitor = new Object(); private SyncResult syncResult; + private Account localAccount; + /** * Return the number of milliseconds until we're allowed to sync again, * or 0 if now is fine. @@ -340,11 +344,12 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe IOException, ParseException, NonObjectJSONException { Log.i(LOG_TAG, "Performing sync."); - this.syncResult = syncResult; + this.syncResult = syncResult; + this.localAccount = account; // TODO: default serverURL. GlobalSession globalSession = new GlobalSession(SyncConfiguration.DEFAULT_USER_API, serverURL, username, password, prefsPath, - keyBundle, this, this.mContext, extras); + keyBundle, this, this.mContext, extras, this); globalSession.start(); } @@ -407,4 +412,40 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe this.extendEarliestNextSync(System.currentTimeMillis() + backoff); } } + + @Override + public synchronized String getAccountGUID() { + String accountGUID = mAccountManager.getUserData(localAccount, Constants.ACCOUNT_GUID); + if (accountGUID == null) { + accountGUID = Utils.generateGuid(); + mAccountManager.setUserData(localAccount, Constants.ACCOUNT_GUID, accountGUID); + } + return accountGUID; + } + + @Override + public synchronized String getClientName() { + String clientName = mAccountManager.getUserData(localAccount, Constants.CLIENT_NAME); + if (clientName == null) { + clientName = GlobalConstants.PRODUCT_NAME + " on " + android.os.Build.MODEL; + mAccountManager.setUserData(localAccount, Constants.CLIENT_NAME, clientName); + } + return clientName; + } + + @Override + public synchronized void setClientsCount(int clientsCount) { + mAccountManager.setUserData(localAccount, Constants.NUM_CLIENTS, + Integer.toString(clientsCount)); + } + + @Override + public synchronized int getClientsCount() { + String clientsCount = mAccountManager.getUserData(localAccount, Constants.NUM_CLIENTS); + if (clientsCount == null) { + clientsCount = "0"; + mAccountManager.setUserData(localAccount, Constants.NUM_CLIENTS, clientsCount); + } + return Integer.parseInt(clientsCount); + } } \ No newline at end of file diff --git a/mobile/android/base/sync/synchronizer/RecordsChannel.java b/mobile/android/base/sync/synchronizer/RecordsChannel.java index ff936364c12f..5f749f802cde 100644 --- a/mobile/android/base/sync/synchronizer/RecordsChannel.java +++ b/mobile/android/base/sync/synchronizer/RecordsChannel.java @@ -42,6 +42,7 @@ import java.util.concurrent.ExecutorService; import org.mozilla.gecko.sync.Logger; import org.mozilla.gecko.sync.ThreadPool; +import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException; import org.mozilla.gecko.sync.repositories.NoStoreDelegateException; import org.mozilla.gecko.sync.repositories.RepositorySession; import org.mozilla.gecko.sync.repositories.delegates.DeferredRepositorySessionBeginDelegate; @@ -166,8 +167,9 @@ class RecordsChannel implements /** * Begin both sessions, invoking flow() when done. + * @throws InvalidSessionTransitionException */ - public void beginAndFlow() { + public void beginAndFlow() throws InvalidSessionTransitionException { Logger.info(LOG_TAG, "Beginning source."); source.begin(this); } @@ -251,7 +253,11 @@ class RecordsChannel implements public void onBeginSucceeded(RepositorySession session) { if (session == source) { Logger.info(LOG_TAG, "Source session began. Beginning sink session."); - sink.begin(this); + try { + sink.begin(this); + } catch (InvalidSessionTransitionException e) { + onBeginFailed(e); + } } if (session == sink) { Logger.info(LOG_TAG, "Sink session began. Beginning flow."); diff --git a/mobile/android/base/sync/synchronizer/SynchronizerSession.java b/mobile/android/base/sync/synchronizer/SynchronizerSession.java index 4f124d8bfa9b..328c1291b63b 100644 --- a/mobile/android/base/sync/synchronizer/SynchronizerSession.java +++ b/mobile/android/base/sync/synchronizer/SynchronizerSession.java @@ -40,6 +40,8 @@ package org.mozilla.gecko.sync.synchronizer; import java.util.concurrent.ExecutorService; +import org.mozilla.gecko.sync.repositories.InactiveSessionException; +import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException; import org.mozilla.gecko.sync.repositories.RepositorySession; import org.mozilla.gecko.sync.repositories.RepositorySessionBundle; import org.mozilla.gecko.sync.repositories.delegates.DeferrableRepositorySessionCreationDelegate; @@ -170,7 +172,11 @@ implements RecordsChannelDelegate, }; final RecordsChannel channelAToB = new RecordsChannel(this.sessionA, this.sessionB, channelDelegate); info("Starting A to B flow. Channel is " + channelAToB); - channelAToB.beginAndFlow(); + try { + channelAToB.beginAndFlow(); + } catch (InvalidSessionTransitionException e) { + onFlowBeginFailed(channelAToB, e); + } } @Override @@ -183,7 +189,11 @@ implements RecordsChannelDelegate, flowBToACompleted = true; // Finish the two sessions. - this.sessionA.finish(this); + try { + this.sessionA.finish(this); + } catch (InactiveSessionException e) { + this.onFinishFailed(e); + } } @Override @@ -297,7 +307,11 @@ implements RecordsChannelDelegate, if (this.sessionB != null) { info("Finishing session B."); // On to the next. - this.sessionB.finish(this); + try { + this.sessionB.finish(this); + } catch (InactiveSessionException e) { + this.onFinishFailed(e); + } } } else if (session == sessionB) { if (flowBToACompleted) { diff --git a/mobile/android/base/tests/PixelTest.java.in b/mobile/android/base/tests/PixelTest.java.in index 0f88d3119ded..62bab34b0911 100644 --- a/mobile/android/base/tests/PixelTest.java.in +++ b/mobile/android/base/tests/PixelTest.java.in @@ -6,19 +6,19 @@ import @ANDROID_PACKAGE_NAME@.*; class PixelTest extends BaseTest { private static final long PAINT_CLEAR_DELAY = 500; // milliseconds - protected final int[][] loadAndPaint(String url) { + protected final PaintedSurface loadAndPaint(String url) { Actions.RepeatedEventExpecter paintExpecter = mActions.expectPaint(); loadUrl(url); paintExpecter.blockUntilClear(PAINT_CLEAR_DELAY); return mDriver.getPaintedSurface(); } - protected final int[][] waitForPaint(Actions.RepeatedEventExpecter expecter) { + protected final PaintedSurface waitForPaint(Actions.RepeatedEventExpecter expecter) { expecter.blockUntilClear(PAINT_CLEAR_DELAY); return mDriver.getPaintedSurface(); } - protected final int[][] waitWithNoPaint(Actions.RepeatedEventExpecter expecter) { + protected final PaintedSurface waitWithNoPaint(Actions.RepeatedEventExpecter expecter) { try { Thread.sleep(PAINT_CLEAR_DELAY); } catch (InterruptedException ie) { @@ -41,15 +41,15 @@ class PixelTest extends BaseTest { /** * Checks the top-left corner of the visible area of the page is at (x,y) of robocop_boxes.html. */ - protected final void checkScrollWithBoxes(int[][] painted, int x, int y) { + protected final void checkScrollWithBoxes(PaintedSurface painted, int x, int y) { int[] color = getBoxColorAt(x, y); - mAsserter.ispixel(painted[0][0], color[0], color[1], color[2], "Pixel at 0, 0"); + mAsserter.ispixel(painted.getPixelAt(0, 0), color[0], color[1], color[2], "Pixel at 0, 0"); color = getBoxColorAt(x + 100, y); - mAsserter.ispixel(painted[0][100], color[0], color[1], color[2], "Pixel at 100, 0"); + mAsserter.ispixel(painted.getPixelAt(100, 0), color[0], color[1], color[2], "Pixel at 100, 0"); color = getBoxColorAt(x, y + 100); - mAsserter.ispixel(painted[100][0], color[0], color[1], color[2], "Pixel at 0, 100"); + mAsserter.ispixel(painted.getPixelAt(0, 100), color[0], color[1], color[2], "Pixel at 0, 100"); color = getBoxColorAt(x + 100, y + 100); - mAsserter.ispixel(painted[100][100], color[0], color[1], color[2], "Pixel at 100, 100"); + mAsserter.ispixel(painted.getPixelAt(100, 100), color[0], color[1], color[2], "Pixel at 100, 100"); } /** @@ -57,8 +57,8 @@ class PixelTest extends BaseTest { * @param url URL of the robocop_boxes.html file. * @return The painted surface after rendering the file. */ - protected final int[][] loadAndVerifyBoxes(String url) { - int[][] painted = loadAndPaint(url); + protected final PaintedSurface loadAndVerifyBoxes(String url) { + PaintedSurface painted = loadAndPaint(url); checkScrollWithBoxes(painted, 0, 0); return painted; } diff --git a/mobile/android/base/tests/testAxisLocking.java.in b/mobile/android/base/tests/testAxisLocking.java.in index cf1ddc61453a..a8c54d09d1a9 100644 --- a/mobile/android/base/tests/testAxisLocking.java.in +++ b/mobile/android/base/tests/testAxisLocking.java.in @@ -28,12 +28,12 @@ public class testAxisLocking extends PixelTest { // axis locking prevents any horizontal scrolling Actions.RepeatedEventExpecter paintExpecter = mActions.expectPaint(); meh.dragSync(20, 150, 10, 50); - int[][] painted = waitForPaint(paintExpecter); + PaintedSurface painted = waitForPaint(paintExpecter); checkScrollWithBoxes(painted, 0, 100); // since checkScrollWithBoxes only checks 4 points, it may not pick up a // sub-100 pixel horizontal shift. so we check another point manually to make sure. int[] color = getBoxColorAt(0, 100); - mAsserter.ispixel(painted[0][99], color[0], color[1], color[2], "Pixel at 99, 0 indicates no horizontal scroll"); + mAsserter.ispixel(painted.getPixelAt(99, 0), color[0], color[1], color[2], "Pixel at 99, 0 indicates no horizontal scroll"); // now drag at a 45-degree angle to ensure we break the axis lock, and // verify that we have both horizontal and vertical scrolling diff --git a/mobile/android/base/tests/testFlingCorrectness.java.in b/mobile/android/base/tests/testFlingCorrectness.java.in index 5d2ff67eb65d..8cbfcd26f7f6 100644 --- a/mobile/android/base/tests/testFlingCorrectness.java.in +++ b/mobile/android/base/tests/testFlingCorrectness.java.in @@ -27,7 +27,7 @@ public class testFlingCorrectness extends PixelTest { Actions.RepeatedEventExpecter paintExpecter = mActions.expectPaint(); meh.dragSync(10, 150, 10, 50); meh.dragSync(10, 150, 10, 50); - int[][] painted = waitForPaint(paintExpecter); + PaintedSurface painted = waitForPaint(paintExpecter); checkScrollWithBoxes(painted, 0, 200); // now fling page downwards using a 100-pixel drag but a velocity of 15px/sec, so that diff --git a/mobile/android/base/tests/testOverscroll.java.in b/mobile/android/base/tests/testOverscroll.java.in index 205d06531b88..349312fcb4c8 100644 --- a/mobile/android/base/tests/testOverscroll.java.in +++ b/mobile/android/base/tests/testOverscroll.java.in @@ -28,7 +28,7 @@ public class testOverscroll extends PixelTest { // and back this should NOT trigger a gecko-paint Actions.RepeatedEventExpecter paintExpecter = mActions.expectPaint(); meh.dragSync(10, 50, 10, 150); - int[][] painted = waitWithNoPaint(paintExpecter); + PaintedSurface painted = waitWithNoPaint(paintExpecter); checkScrollWithBoxes(painted, 0, 0); // drag page rightwards to go into overscroll on the left. let it bounce and verify. diff --git a/mobile/android/base/tests/testPanCorrectness.java.in b/mobile/android/base/tests/testPanCorrectness.java.in index 131c82c45084..8657069b6944 100644 --- a/mobile/android/base/tests/testPanCorrectness.java.in +++ b/mobile/android/base/tests/testPanCorrectness.java.in @@ -25,7 +25,7 @@ public class testPanCorrectness extends PixelTest { // drag page upwards by 100 pixels Actions.RepeatedEventExpecter paintExpecter = mActions.expectPaint(); meh.dragSync(10, 150, 10, 50); - int[][] painted = waitForPaint(paintExpecter); + PaintedSurface painted = waitForPaint(paintExpecter); checkScrollWithBoxes(painted, 0, 100); // drag page leftwards by 100 pixels diff --git a/mobile/android/base/tests/test_bug720538.java.in b/mobile/android/base/tests/test_bug720538.java.in index f02976ec582e..3ef959089800 100644 --- a/mobile/android/base/tests/test_bug720538.java.in +++ b/mobile/android/base/tests/test_bug720538.java.in @@ -24,10 +24,10 @@ public class test_bug720538 extends PixelTest { * the gray shades of the checkerboard. */ - int[][] painted = loadAndPaint(url); + PaintedSurface painted = loadAndPaint(url); // first we check that the point we want to double-tap (100, 100) is blue, indicating it's inside the iframe - mAsserter.ispixel(painted[100][100], 0, 0, 0xFF, "Ensuring double-tap point is in the iframe"); + mAsserter.ispixel(painted.getPixelAt(100, 100), 0, 0, 0xFF, "Ensuring double-tap point is in the iframe"); // do the double tap and wait for the double-tap animation to finish. we assume the animation is done // when we find a 500ms period with no paint events that occurs after at least one paint event. @@ -39,10 +39,10 @@ public class test_bug720538 extends PixelTest { // check a few points to ensure that we did a good zoom-to-block on the iframe. this checks that // the background color is visible on the left and right edges of the viewport, but the iframe is // visible in between those edges - mAsserter.ispixel(painted[100][0], 0, 0x80, 0, "Checking page background to the left of the iframe"); - mAsserter.ispixel(painted[100][50], 0, 0, 0xFF, "Checking for iframe a few pixels from the left edge"); - mAsserter.ispixel(painted[100][mDriver.getGeckoWidth() - 51], 0, 0, 0xFF, "Checking for iframe a few pixels from the right edge"); - mAsserter.ispixel(painted[100][mDriver.getGeckoWidth() - 1], 0, 0x80, 0, "Checking page background the right of the iframe"); + mAsserter.ispixel(painted.getPixelAt(0, 100), 0, 0x80, 0, "Checking page background to the left of the iframe"); + mAsserter.ispixel(painted.getPixelAt(50, 100), 0, 0, 0xFF, "Checking for iframe a few pixels from the left edge"); + mAsserter.ispixel(painted.getPixelAt(mDriver.getGeckoWidth() - 51, 100), 0, 0, 0xFF, "Checking for iframe a few pixels from the right edge"); + mAsserter.ispixel(painted.getPixelAt(mDriver.getGeckoWidth() - 1, 100), 0, 0x80, 0, "Checking page background the right of the iframe"); // now we do double-tap again to zoom out and wait for the animation to finish, as before paintExpecter = mActions.expectPaint(); @@ -54,9 +54,9 @@ public class test_bug720538 extends PixelTest { // the last row because the last row is subject to rounding and clipping errors for (int y = 2; y < 10; y++) { for (int x = 0; x < 10; x++) { - mAsserter.dumpLog("Pixel at " + x + ", " + (mDriver.getGeckoHeight() - y) + ": " + Integer.toHexString(painted[mDriver.getGeckoHeight() - y][x])); + mAsserter.dumpLog("Pixel at " + x + ", " + (mDriver.getGeckoHeight() - y) + ": " + Integer.toHexString(painted.getPixelAt(x, mDriver.getGeckoHeight() - y))); } } - mAsserter.ispixel(painted[mDriver.getGeckoHeight() - 2][0], 0, 0x80, 0, "Checking bottom-left corner of viewport"); + mAsserter.ispixel(painted.getPixelAt(0, mDriver.getGeckoHeight() - 2), 0, 0x80, 0, "Checking bottom-left corner of viewport"); } } diff --git a/mobile/android/base/ui/PanZoomController.java b/mobile/android/base/ui/PanZoomController.java index 04a182f0d157..44fcb51d6aca 100644 --- a/mobile/android/base/ui/PanZoomController.java +++ b/mobile/android/base/ui/PanZoomController.java @@ -297,12 +297,12 @@ public class PanZoomController cancelTouch(); startPanning(event.getX(0), event.getY(0), event.getEventTime()); GeckoApp.mAppContext.hidePlugins(false /* don't hide layers */); - GeckoApp.mAutoCompletePopup.hide(); + GeckoApp.mFormAssistPopup.hide(); track(event); return true; case PANNING_HOLD_LOCKED: - GeckoApp.mAutoCompletePopup.hide(); + GeckoApp.mFormAssistPopup.hide(); mState = PanZoomState.PANNING_LOCKED; // fall through case PANNING_LOCKED: @@ -310,7 +310,7 @@ public class PanZoomController return true; case PANNING_HOLD: - GeckoApp.mAutoCompletePopup.hide(); + GeckoApp.mFormAssistPopup.hide(); mState = PanZoomState.PANNING; // fall through case PANNING: @@ -760,7 +760,7 @@ public class PanZoomController mState = PanZoomState.PINCHING; mLastZoomFocus = new PointF(detector.getFocusX(), detector.getFocusY()); GeckoApp.mAppContext.hidePlugins(false /* don't hide layers, only views */); - GeckoApp.mAutoCompletePopup.hide(); + GeckoApp.mFormAssistPopup.hide(); cancelTouch(); return true; @@ -863,7 +863,7 @@ public class PanZoomController @Override public boolean onSingleTapConfirmed(MotionEvent motionEvent) { - GeckoApp.mAutoCompletePopup.hide(); + GeckoApp.mFormAssistPopup.hide(); sendPointToGecko("Gesture:SingleTap", motionEvent); return true; } @@ -880,7 +880,7 @@ public class PanZoomController } private boolean animatedZoomTo(RectF zoomToRect) { - GeckoApp.mAutoCompletePopup.hide(); + GeckoApp.mFormAssistPopup.hide(); mState = PanZoomState.ANIMATED_ZOOM; final float startZoom = mController.getZoomFactor(); diff --git a/mobile/android/chrome/content/aboutAddons.js b/mobile/android/chrome/content/aboutAddons.js index b8a867b872a7..d1efd95a9027 100644 --- a/mobile/android/chrome/content/aboutAddons.js +++ b/mobile/android/chrome/content/aboutAddons.js @@ -247,18 +247,31 @@ var Addons = { xhr.open("GET", optionsURL, false); xhr.send(); if (xhr.responseXML) { - let currentNode; - let nodeIterator = xhr.responseXML.createNodeIterator(xhr.responseXML, NodeFilter.SHOW_TEXT, null, false); - while (currentNode = nodeIterator.nextNode()) { - let trimmed = currentNode.nodeValue.replace(/^\s\s*/, "").replace(/\s\s*$/, ""); - if (!trimmed.length) - currentNode.parentNode.removeChild(currentNode); + // This function removes and returns the text content of aNode without + // removing any child elements. Removing the text nodes ensures any XBL + // bindings apply properly. + function stripTextNodes(aNode) { + var text = ''; + for (var i = 0; i < aNode.childNodes.length; i++) { + if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) { + text += aNode.childNodes[i].textContent; + aNode.removeChild(aNode.childNodes[i--]); + } else { + text += stripTextNodes(aNode.childNodes[i]); + } + } + return text; } // Only allow for now - let prefs = xhr.responseXML.querySelectorAll(":root > setting"); - for (let i = 0; i < prefs.length; i++) - box.appendChild(prefs.item(i)); + let settings = xhr.responseXML.querySelectorAll(":root > setting"); + for (let i = 0; i < settings.length; i++) { + var setting = settings[i]; + var desc = stripTextNodes(setting).trim(); + if (!setting.hasAttribute("desc")) + setting.setAttribute("desc", desc); + box.appendChild(setting); + } /* // Send an event so add-ons can prepopulate any non-preference based // settings diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 43b22a98830b..ebb715ec682a 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -138,6 +138,11 @@ XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() { return ContentAreaUtils; }); +XPCOMUtils.defineLazyGetter(this, "Rect", function() { + Cu.import("resource://gre/modules/Geometry.jsm"); + return Rect; +}); + function resolveGeckoURI(aURI) { if (aURI.indexOf("chrome://") == 0) { let registry = Cc['@mozilla.org/chrome/chrome-registry;1'].getService(Ci["nsIChromeRegistry"]); @@ -467,6 +472,15 @@ var BrowserApp = { return null; }, + getTabForWindow: function getTabForWindow(aWindow) { + let tabs = this._tabs; + for (let i = 0; i < tabs.length; i++) { + if (tabs[i].browser.contentWindow == aWindow) + return tabs[i]; + } + return null; + }, + getBrowserForWindow: function getBrowserForWindow(aWindow) { let tabs = this._tabs; for (let i = 0; i < tabs.length; i++) { @@ -580,6 +594,10 @@ var BrowserApp = { return; } + // There's nothing to do if the tab is already selected + if (aTab == this.selectedTab) + return; + let message = { gecko: { type: "Tab:Select", @@ -1440,7 +1458,7 @@ nsBrowserAccess.prototype = { if (newTab) { let parentId = -1; if (!isExternal) { - let parent = BrowserApp.getTabForBrowser(BrowserApp.getBrowserForWindow(aOpener.top)); + let parent = BrowserApp.getTabForWindow(aOpener.top); if (parent) parentId = parent.id; } @@ -1523,22 +1541,32 @@ Tab.prototype = { let frameLoader = this.browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader; frameLoader.clipSubdocument = false; + // only set tab uri if uri is valid + let uri = null; + try { + uri = Services.io.newURI(aURL, null, null).spec; + } catch (e) {} + this.id = ++gTabIDFactory; let message = { gecko: { type: "Tab:Added", tabID: this.id, - uri: aURL, + uri: uri, parentId: ("parentId" in aParams) ? aParams.parentId : -1, external: ("external" in aParams) ? aParams.external : false, selected: ("selected" in aParams) ? aParams.selected : true, - title: aParams.title || "", + title: aParams.title || aURL, delayLoad: aParams.delayLoad || false } }; sendMessageToJava(message); + this.overscrollController = new OverscrollController(this); + this.browser.contentWindow.controllers + .insertControllerAt(0, this.overscrollController); + let flags = Ci.nsIWebProgress.NOTIFY_STATE_ALL | Ci.nsIWebProgress.NOTIFY_LOCATION | Ci.nsIWebProgress.NOTIFY_SECURITY; @@ -1549,6 +1577,7 @@ Tab.prototype = { this.browser.addEventListener("DOMLinkAdded", this, true); this.browser.addEventListener("DOMTitleChanged", this, true); this.browser.addEventListener("DOMWindowClose", this, true); + this.browser.addEventListener("DOMWillOpenModalDialog", this, true); this.browser.addEventListener("scroll", this, true); this.browser.addEventListener("PluginClickToPlay", this, true); this.browser.addEventListener("pagehide", this, true); @@ -1586,11 +1615,15 @@ Tab.prototype = { if (!this.browser) return; + this.browser.controllers.contentWindow + .removeController(this.overscrollController); + this.browser.removeProgressListener(this); this.browser.removeEventListener("DOMContentLoaded", this, true); this.browser.removeEventListener("DOMLinkAdded", this, true); this.browser.removeEventListener("DOMTitleChanged", this, true); this.browser.removeEventListener("DOMWindowClose", this, true); + this.browser.removeEventListener("DOMWillOpenModalDialog", this, true); this.browser.removeEventListener("scroll", this, true); this.browser.removeEventListener("PluginClickToPlay", this, true); this.browser.removeEventListener("pagehide", this, true); @@ -1868,6 +1901,17 @@ Tab.prototype = { break; } + case "DOMWillOpenModalDialog": { + if (!aEvent.isTrusted) + return; + + // We're about to open a modal dialog, make sure the opening + // tab is brought to the front. + let tab = BrowserApp.getTabForWindow(aEvent.target.top); + BrowserApp.selectTab(tab); + break; + } + case "scroll": { let win = this.browser.contentWindow; if (this.userScrollPos.x != win.scrollX || this.userScrollPos.y != win.scrollY) { @@ -1936,13 +1980,20 @@ Tab.prototype = { let restoring = aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING; let showProgress = restoring ? false : this.showProgress; + // true if the page loaded successfully (i.e., no 404s or other errors) + let success = false; + try { + success = aRequest.QueryInterface(Components.interfaces.nsIHttpChannel).requestSucceeded; + } catch (e) { } + let message = { gecko: { type: "Content:StateChange", tabID: this.id, uri: uri, state: aStateFlags, - showProgress: showProgress + showProgress: showProgress, + success: success } }; sendMessageToJava(message); @@ -2298,11 +2349,7 @@ var BrowserEventHandler = { this._cancelTapHighlight(); this.onDoubleTap(aData); } else if (aTopic == "dom-touch-listener-added") { - let browser = BrowserApp.getBrowserForWindow(aSubject); - if (!browser) - return; - - let tab = BrowserApp.getTabForBrowser(browser); + let tab = BrowserApp.getTabForWindow(aSubject); if (!tab) return; @@ -2316,17 +2363,12 @@ var BrowserEventHandler = { }, _zoomOut: function() { - this._zoomedToElement = null; - // zoom out, try to keep the center in the center of the page - setTimeout(function() { - sendMessageToJava({ gecko: { type: "Browser:ZoomToPageWidth"} }); - }, 0); + sendMessageToJava({ gecko: { type: "Browser:ZoomToPageWidth"} }); }, onDoubleTap: function(aData) { let data = JSON.parse(aData); - let rect = {}; let win = BrowserApp.selectedBrowser.contentWindow; let zoom = BrowserApp.selectedTab._viewport.zoom; @@ -2339,25 +2381,47 @@ var BrowserEventHandler = { win = element.ownerDocument.defaultView; while (element && win.getComputedStyle(element,null).display == "inline") element = element.parentNode; - if (!element || element == this._zoomedToElement) { + + if (!element) { this._zoomOut(); - } else if (element) { + } else { const margin = 15; - this._zoomedToElement = element; - rect = ElementTouchHelper.getBoundingContentRect(element); + const minDifference = -20; + const maxDifference = 20; + let rect = ElementTouchHelper.getBoundingContentRect(element); - let zoom = BrowserApp.selectedTab.viewport.zoom; - rect.x *= zoom; - rect.y *= zoom; - rect.w *= zoom; - rect.h *= zoom; + let viewport = BrowserApp.selectedTab.viewport; + let vRect = new Rect(viewport.x, viewport.y, viewport.width, viewport.height); - setTimeout(function() { - rect.type = "Browser:ZoomToRect"; - rect.x -= margin; - rect.w += 2*margin; - sendMessageToJava({ gecko: rect }); - }, 0); + let zoom = viewport.zoom; + let bRect = new Rect(Math.max(0,rect.x - margin), + rect.y, + rect.w + 2*margin, + rect.h); + // constrict the rect to the screen width + bRect.width = Math.min(bRect.width, viewport.pageWidth/zoom - bRect.x); + bRect.scale(zoom, zoom); + + let overlap = vRect.intersect(bRect); + let overlapArea = overlap.width*overlap.height; + // we want to know if the area of the element showing is near the max we can show + // on the screen at any time and if its already stretching the width of the screen + let availHeight = Math.min(bRect.width*vRect.height/vRect.width, bRect.height); + let showing = overlapArea/(bRect.width*availHeight); + let dw = (bRect.width - vRect.width)/zoom; + let dx = (bRect.x - vRect.x)/zoom; + + if (showing > 0.9 && + dx > minDifference && dx < maxDifference && + dw > minDifference && dw < maxDifference) { + this._zoomOut(); + return; + } + + rect.type = "Browser:ZoomToRect"; + rect.x = bRect.x; rect.y = bRect.y; + rect.w = bRect.width; rect.h = availHeight; + sendMessageToJava({ gecko: rect }); } }, @@ -2528,11 +2592,7 @@ const ElementTouchHelper = { if (!aWindow) throw "Must provide a window"; - let browser = BrowserApp.getBrowserForWindow(aWindow.top); - if (!browser) - throw "Unable to find a browser"; - - let tab = BrowserApp.getTabForBrowser(browser); + let tab = BrowserApp.getTabForWindow(aWindow.top); if (!tab) throw "Unable to find a tab"; @@ -2546,12 +2606,8 @@ const ElementTouchHelper = { toScreenCoords: function(aWindow, aX, aY) { if (!aWindow) throw "Must provide a window"; - - let browser = BrowserApp.getBrowserForWindow(aWindow.top); - if (!browser) - throw "Unable to find a browser"; - let tab = BrowserApp.getTabForBrowser(browser); + let tab = BrowserApp.getTabForWindow(aWindow.top); if (!tab) throw "Unable to find a tab"; @@ -2804,20 +2860,34 @@ var ErrorPageEventHandler = { }; var FormAssistant = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]), + // Used to keep track of the element that corresponds to the current // autocomplete suggestions _currentInputElement: null, + // Keep track of whether or not an invalid form has been submitted + _invalidSubmit: false, + init: function() { Services.obs.addObserver(this, "FormAssist:AutoComplete", false); - Services.obs.addObserver(this, "FormAssist:Closed", false); + Services.obs.addObserver(this, "FormAssist:Hidden", false); + Services.obs.addObserver(this, "invalidformsubmit", false); + // We need to use a capturing listener for focus events + BrowserApp.deck.addEventListener("focus", this, true); BrowserApp.deck.addEventListener("input", this, false); + BrowserApp.deck.addEventListener("pageshow", this, false); }, uninit: function() { Services.obs.removeObserver(this, "FormAssist:AutoComplete"); - Services.obs.removeObserver(this, "FormAssist:Closed"); + Services.obs.removeObserver(this, "FormAssist:Hidden"); + Services.obs.removeObserver(this, "invalidformsubmit"); + + BrowserApp.deck.removeEventListener("focus", this); + BrowserApp.deck.removeEventListener("input", this); + BrowserApp.deck.removeEventListener("pageshow", this); }, observe: function(aSubject, aTopic, aData) { @@ -2831,39 +2901,65 @@ var FormAssistant = { this._currentInputElement.value = aData; break; - case "FormAssist:Closed": + case "FormAssist:Hidden": this._currentInputElement = null; break; } }, + notifyInvalidSubmit: function notifyInvalidSubmit(aFormElement, aInvalidElements) { + if (!aInvalidElements.length) + return; + + // Ignore this notificaiton if the current tab doesn't contain the invalid form + if (BrowserApp.selectedBrowser.contentDocument != + aFormElement.ownerDocument.defaultView.top.document) + return; + + this._invalidSubmit = true; + + // Our focus listener will show the element's validation message + let currentElement = aInvalidElements.queryElementAt(0, Ci.nsISupports); + currentElement.focus(); + }, + handleEvent: function(aEvent) { switch (aEvent.type) { - case "input": + case "focus": let currentElement = aEvent.target; - if (!this._isAutocomplete(currentElement)) + + // Prioritize a form validation message over autocomplete suggestions + // when the element is first focused (a form validation message will + // only be available if an invalid form was submitted) + if (this._showValidationMessage(currentElement)) + break; + this._showAutoCompleteSuggestions(currentElement) + break; + + case "input": + currentElement = aEvent.target; + + // Since we can only show one popup at a time, prioritze autocomplete + // suggestions over a form validation message + if (this._showAutoCompleteSuggestions(currentElement)) + break; + if (this._showValidationMessage(currentElement)) break; - // Keep track of input element so we can fill it in if the user - // selects an autocomplete suggestion - this._currentInputElement = currentElement; - let suggestions = this._getAutocompleteSuggestions(currentElement.value, currentElement); + // If we're not showing autocomplete suggestions, hide the form assist popup + this._hideFormAssistPopup(); + break; - let rect = ElementTouchHelper.getBoundingContentRect(currentElement); - let viewport = BrowserApp.selectedTab.viewport; - - sendMessageToJava({ - gecko: { - type: "FormAssist:AutoComplete", - suggestions: suggestions, - rect: [rect.x - (viewport.x / viewport.zoom), rect.y - (viewport.y / viewport.zoom), rect.w, rect.h], - zoom: viewport.zoom - } - }); + // Reset invalid submit state on each pageshow + case "pageshow": + let target = aEvent.originalTarget; + if (target == content.document || target.ownerDocument == content.document) + this._invalidSubmit = false; } }, - _isAutocomplete: function (aElement) { + // We only want to show autocomplete suggestions for certain elements + _isAutoComplete: function _isAutoComplete(aElement) { if (!(aElement instanceof HTMLInputElement) || (aElement.getAttribute("type") == "password") || (aElement.hasAttribute("autocomplete") && @@ -2873,25 +2969,147 @@ var FormAssistant = { return true; }, - /** Retrieve the autocomplete list from the autocomplete service for an element */ - _getAutocompleteSuggestions: function(aSearchString, aElement) { - let results = Cc["@mozilla.org/satchel/form-autocomplete;1"]. - getService(Ci.nsIFormAutoComplete). - autoCompleteSearch(aElement.name || aElement.id, aSearchString, aElement, null); + // Retrieves autocomplete suggestions for an element from the form autocomplete service. + _getAutoCompleteSuggestions: function _getAutoCompleteSuggestions(aSearchString, aElement) { + // Cache the form autocomplete service for future use + if (!this._formAutoCompleteService) + this._formAutoCompleteService = Cc["@mozilla.org/satchel/form-autocomplete;1"]. + getService(Ci.nsIFormAutoComplete); + let results = this._formAutoCompleteService.autoCompleteSearch(aElement.name || aElement.id, + aSearchString, aElement, null); let suggestions = []; - if (results.matchCount > 0) { - for (let i = 0; i < results.matchCount; i++) { - let value = results.getValueAt(i); - // Do not show the value if it is the current one in the input field - if (value == aSearchString) - continue; + for (let i = 0; i < results.matchCount; i++) { + let value = results.getValueAt(i); - suggestions.push(value); - } + // Do not show the value if it is the current one in the input field + if (value == aSearchString) + continue; + + // Supply a label and value, since they can differ for datalist suggestions + suggestions.push({ label: value, value: value }); } return suggestions; + }, + + /** + * (Copied from mobile/xul/chrome/content/forms.js) + * This function is similar to getListSuggestions from + * components/satchel/src/nsInputListAutoComplete.js but sadly this one is + * used by the autocomplete.xml binding which is not in used in fennec + */ + _getListSuggestions: function _getListSuggestions(aElement) { + if (!(aElement instanceof HTMLInputElement) || !aElement.list) + return []; + + let suggestions = []; + let filter = !aElement.hasAttribute("mozNoFilter"); + let lowerFieldValue = aElement.value.toLowerCase(); + + let options = aElement.list.options; + let length = options.length; + for (let i = 0; i < length; i++) { + let item = options.item(i); + + let label = item.value; + if (item.label) + label = item.label; + else if (item.text) + label = item.text; + + if (filter && label.toLowerCase().indexOf(lowerFieldValue) == -1) + continue; + suggestions.push({ label: label, value: item.value }); + } + + return suggestions; + }, + + // Gets the element position data necessary for the Java UI to position + // the form assist popup. + _getElementPositionData: function _getElementPositionData(aElement) { + let rect = ElementTouchHelper.getBoundingContentRect(aElement); + let viewport = BrowserApp.selectedTab.viewport; + + return { rect: [rect.x - (viewport.x / viewport.zoom), + rect.y - (viewport.y / viewport.zoom), + rect.w, rect.h], + zoom: viewport.zoom } + }, + + // Retrieves autocomplete suggestions for an element from the form autocomplete service + // and sends the suggestions to the Java UI, along with element position data. + // Returns true if there are suggestions to show, false otherwise. + _showAutoCompleteSuggestions: function _showAutoCompleteSuggestions(aElement) { + if (!this._isAutoComplete(aElement)) + return false; + + let autoCompleteSuggestions = this._getAutoCompleteSuggestions(aElement.value, aElement); + let listSuggestions = this._getListSuggestions(aElement); + + // On desktop, we show datalist suggestions below autocomplete suggestions, + // without duplicates removed. + let suggestions = autoCompleteSuggestions.concat(listSuggestions); + + // Return false if there are no suggestions to show + if (!suggestions.length) + return false; + + let positionData = this._getElementPositionData(aElement); + sendMessageToJava({ + gecko: { + type: "FormAssist:AutoComplete", + suggestions: suggestions, + rect: positionData.rect, + zoom: positionData.zoom + } + }); + + // Keep track of input element so we can fill it in if the user + // selects an autocomplete suggestion + this._currentInputElement = aElement; + + return true; + }, + + // Only show a validation message if the user submitted an invalid form, + // there's a non-empty message string, and the element is the correct type + _isValidateable: function _isValidateable(aElement) { + if (!this._invalidSubmit || + !aElement.validationMessage || + !(aElement instanceof HTMLInputElement || + aElement instanceof HTMLTextAreaElement || + aElement instanceof HTMLSelectElement || + aElement instanceof HTMLButtonElement)) + return false; + + return true; + }, + + // Sends a validation message and position data for an element to the Java UI. + // Returns true if there's a validation message to show, false otherwise. + _showValidationMessage: function _sendValidationMessage(aElement) { + if (!this._isValidateable(aElement)) + return false; + + let positionData = this._getElementPositionData(aElement); + sendMessageToJava({ + gecko: { + type: "FormAssist:ValidationMessage", + validationMessage: aElement.validationMessage, + rect: positionData.rect, + zoom: positionData.zoom + } + }); + + return true; + }, + + _hideFormAssistPopup: function _hideFormAssistPopup() { + sendMessageToJava({ + gecko: { type: "FormAssist:Hide" } + }); } }; @@ -3327,8 +3545,7 @@ var OfflineApps = { if (!Services.prefs.getBoolPref("browser.offline-apps.notify")) return; - let browser = BrowserApp.getBrowserForWindow(aContentWindow); - let tab = BrowserApp.getTabForBrowser(browser); + let tab = BrowserApp.getTabForWindow(aContentWindow); let currentURI = aContentWindow.document.documentURIObject; // Don't bother showing UI if the user has already made a decision @@ -3427,8 +3644,8 @@ var IndexedDB = { let contentWindow = requestor.getInterface(Ci.nsIDOMWindow); let contentDocument = contentWindow.document; - let browser = BrowserApp.getBrowserForWindow(contentWindow); - if (!browser) + let tab = BrowserApp.getTabForWindow(contentWindow); + if (!tab) return; let host = contentDocument.documentURIObject.asciiHost; @@ -3447,7 +3664,6 @@ var IndexedDB = { } let notificationID = responseTopic + host; - let tab = BrowserApp.getTabForBrowser(browser); let observer = requestor.getInterface(Ci.nsIObserver); if (topic == this._quotaCancel) { @@ -4113,3 +4329,25 @@ var CharacterEncoding = { } }; +function OverscrollController(aTab) { + this.tab = aTab; +} + +OverscrollController.prototype = { + supportsCommand : function supportsCommand(aCommand) { + if (aCommand != "cmd_linePrevious" && aCommand != "cmd_scrollPageUp") + return false; + + return (this.tab.viewport.y == 0); + }, + + isCommandEnabled : function isCommandEnabled(aCommand) { + return this.supportsCommand(aCommand); + }, + + doCommand : function doCommand(aCommand){ + sendMessageToJava({ gecko: { type: "ToggleChrome:Focus" } }); + }, + + onEvent : function onEvent(aEvent) { } +}; diff --git a/mobile/android/components/PromptService.js b/mobile/android/components/PromptService.js index afc562566f53..fe832a8ff37a 100644 --- a/mobile/android/components/PromptService.js +++ b/mobile/android/components/PromptService.js @@ -156,6 +156,8 @@ Prompt.prototype = { if (aCheckMsg) aInputs.push({ type: "checkbox", label: PromptUtils.cleanUpLabel(aCheckMsg), checked: aCheckState.value }); + PromptUtils.fireDialogEvent(this._domWin, "DOMWillOpenModalDialog"); + let msg = { type: "Prompt:Show" }; if (aTitle) msg.title = aTitle; if (aText) msg.text = aText; @@ -765,9 +767,16 @@ let PromptUtils = { } return hostname; }, + sendMessageToJava: function(aMsg) { let data = Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge).handleGeckoMessage(JSON.stringify({ gecko: aMsg })); return JSON.parse(data); + }, + + fireDialogEvent: function(aDomWin, aEventName) { + let event = aDomWin.document.createEvent("Events"); + event.initEvent(aEventName, true, true); + aDomWin.dispatchEvent(event); } }; diff --git a/mobile/android/sync/java-sources.mn b/mobile/android/sync/java-sources.mn index ad12b40ea5e0..c89902fc47ef 100644 --- a/mobile/android/sync/java-sources.mn +++ b/mobile/android/sync/java-sources.mn @@ -1 +1 @@ -sync/AlreadySyncingException.java sync/CollectionKeys.java sync/CredentialsSource.java sync/crypto/CryptoException.java sync/crypto/CryptoInfo.java sync/crypto/HKDF.java sync/crypto/HMACVerificationException.java sync/crypto/KeyBundle.java sync/crypto/MissingCryptoInputException.java sync/crypto/NoKeyBundleException.java sync/CryptoRecord.java sync/DelayedWorkTracker.java sync/delegates/FreshStartDelegate.java sync/delegates/GlobalSessionCallback.java sync/delegates/InfoCollectionsDelegate.java sync/delegates/KeyUploadDelegate.java sync/delegates/MetaGlobalDelegate.java sync/delegates/WipeServerDelegate.java sync/ExtendedJSONObject.java sync/GlobalSession.java sync/HTTPFailureException.java sync/InfoCollections.java sync/jpake/BigIntegerHelper.java sync/jpake/Gx3OrGx4IsZeroOrOneException.java sync/jpake/IncorrectZkpException.java sync/jpake/JPakeClient.java sync/jpake/JPakeCrypto.java sync/jpake/JPakeJson.java sync/jpake/JPakeNoActivePairingException.java sync/jpake/JPakeNumGenerator.java sync/jpake/JPakeNumGeneratorRandom.java sync/jpake/JPakeParty.java sync/jpake/JPakeRequest.java sync/jpake/JPakeRequestDelegate.java sync/jpake/JPakeResponse.java sync/jpake/stage/CompleteStage.java sync/jpake/stage/ComputeFinalStage.java sync/jpake/stage/ComputeKeyVerificationStage.java sync/jpake/stage/ComputeStepOneStage.java sync/jpake/stage/ComputeStepTwoStage.java sync/jpake/stage/DecryptDataStage.java sync/jpake/stage/GetChannelStage.java sync/jpake/stage/GetRequestStage.java sync/jpake/stage/JPakeStage.java sync/jpake/stage/PutRequestStage.java sync/jpake/stage/VerifyPairingStage.java sync/jpake/Zkp.java sync/Logger.java sync/MetaGlobal.java sync/MetaGlobalException.java sync/MetaGlobalMissingEnginesException.java sync/MetaGlobalNotSetException.java sync/middleware/Crypto5MiddlewareRepository.java sync/middleware/Crypto5MiddlewareRepositorySession.java sync/middleware/MiddlewareRepository.java sync/net/BaseResource.java sync/net/CompletedEntity.java sync/net/HandleProgressException.java sync/net/Resource.java sync/net/ResourceDelegate.java sync/net/SyncResourceDelegate.java sync/net/SyncResponse.java sync/net/SyncStorageCollectionRequest.java sync/net/SyncStorageCollectionRequestDelegate.java sync/net/SyncStorageRecordRequest.java sync/net/SyncStorageRequest.java sync/net/SyncStorageRequestDelegate.java sync/net/SyncStorageRequestIncrementalDelegate.java sync/net/SyncStorageResponse.java sync/net/TLSSocketFactory.java sync/net/WBOCollectionRequestDelegate.java sync/NoCollectionKeysSetException.java sync/NonArrayJSONException.java sync/NonObjectJSONException.java sync/PrefsSource.java sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java sync/repositories/android/AndroidBrowserBookmarksRepository.java sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java sync/repositories/android/AndroidBrowserHistoryDataAccessor.java sync/repositories/android/AndroidBrowserHistoryDataExtender.java sync/repositories/android/AndroidBrowserHistoryRepository.java sync/repositories/android/AndroidBrowserHistoryRepositorySession.java sync/repositories/android/AndroidBrowserPasswordsDataAccessor.java sync/repositories/android/AndroidBrowserPasswordsRepository.java sync/repositories/android/AndroidBrowserPasswordsRepositorySession.java sync/repositories/android/AndroidBrowserRepository.java sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java sync/repositories/android/AndroidBrowserRepositorySession.java sync/repositories/android/BrowserContractHelpers.java sync/repositories/android/CachedSQLiteOpenHelper.java sync/repositories/android/PasswordColumns.java sync/repositories/android/RepoUtils.java sync/repositories/BookmarkNeedsReparentingException.java sync/repositories/BookmarksRepository.java sync/repositories/ConstrainedServer11Repository.java sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionBeginDelegate.java sync/repositories/delegates/RepositorySessionCleanDelegate.java sync/repositories/delegates/RepositorySessionCreationDelegate.java sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/RepositorySessionFinishDelegate.java sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java sync/repositories/delegates/RepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionWipeDelegate.java sync/repositories/domain/BookmarkRecord.java sync/repositories/domain/BookmarkRecordFactory.java sync/repositories/domain/HistoryRecord.java sync/repositories/domain/HistoryRecordFactory.java sync/repositories/domain/PasswordRecord.java sync/repositories/domain/Record.java sync/repositories/domain/TabsRecord.java sync/repositories/HashSetStoreTracker.java sync/repositories/HistoryRepository.java sync/repositories/IdentityRecordFactory.java sync/repositories/InactiveSessionException.java sync/repositories/InvalidBookmarkTypeException.java sync/repositories/InvalidRequestException.java sync/repositories/InvalidSessionTransitionException.java sync/repositories/MultipleRecordsForGuidException.java sync/repositories/NoGuidForIdException.java sync/repositories/NoStoreDelegateException.java sync/repositories/NullCursorException.java sync/repositories/ParentNotFoundException.java sync/repositories/ProfileDatabaseException.java sync/repositories/RecordFactory.java sync/repositories/RecordFilter.java sync/repositories/Repository.java sync/repositories/RepositorySession.java sync/repositories/RepositorySessionBundle.java sync/repositories/Server11Repository.java sync/repositories/Server11RepositorySession.java sync/repositories/StoreTracker.java sync/repositories/StoreTrackingRepositorySession.java sync/setup/activities/AccountActivity.java sync/setup/activities/SetupFailureActivity.java sync/setup/activities/SetupSuccessActivity.java sync/setup/activities/SetupSyncActivity.java sync/setup/Constants.java sync/setup/SyncAuthenticatorService.java sync/stage/AndroidBrowserBookmarksServerSyncStage.java sync/stage/AndroidBrowserHistoryServerSyncStage.java sync/stage/CheckPreconditionsStage.java sync/stage/CompletedStage.java sync/stage/EnsureClusterURLStage.java sync/stage/EnsureKeysStage.java sync/stage/FetchInfoCollectionsStage.java sync/stage/FetchMetaGlobalStage.java sync/stage/GlobalSyncStage.java sync/stage/NoSuchStageException.java sync/stage/NoSyncIDException.java sync/stage/ServerSyncStage.java sync/StubActivity.java sync/syncadapter/SyncAdapter.java sync/syncadapter/SyncService.java sync/SyncConfiguration.java sync/SyncConfigurationException.java sync/SyncException.java sync/synchronizer/ConcurrentRecordConsumer.java sync/synchronizer/RecordConsumer.java sync/synchronizer/RecordsChannel.java sync/synchronizer/RecordsChannelDelegate.java sync/synchronizer/RecordsConsumerDelegate.java sync/synchronizer/SerialRecordConsumer.java sync/synchronizer/SessionNotBegunException.java sync/synchronizer/Synchronizer.java sync/synchronizer/SynchronizerDelegate.java sync/synchronizer/SynchronizerSession.java sync/synchronizer/SynchronizerSessionDelegate.java sync/synchronizer/UnbundleError.java sync/synchronizer/UnexpectedSessionException.java sync/SynchronizerConfiguration.java sync/SynchronizerConfigurations.java sync/ThreadPool.java sync/UnexpectedJSONException.java sync/UnknownSynchronizerConfigurationVersionException.java sync/Utils.java +sync/AlreadySyncingException.java sync/CollectionKeys.java sync/CredentialsSource.java sync/crypto/CryptoException.java sync/crypto/CryptoInfo.java sync/crypto/HKDF.java sync/crypto/HMACVerificationException.java sync/crypto/KeyBundle.java sync/crypto/MissingCryptoInputException.java sync/crypto/NoKeyBundleException.java sync/CryptoRecord.java sync/DelayedWorkTracker.java sync/delegates/ClientsDataDelegate.java sync/delegates/FreshStartDelegate.java sync/delegates/GlobalSessionCallback.java sync/delegates/InfoCollectionsDelegate.java sync/delegates/KeyUploadDelegate.java sync/delegates/MetaGlobalDelegate.java sync/delegates/WipeServerDelegate.java sync/ExtendedJSONObject.java sync/GlobalSession.java sync/HTTPFailureException.java sync/InfoCollections.java sync/jpake/BigIntegerHelper.java sync/jpake/Gx3OrGx4IsZeroOrOneException.java sync/jpake/IncorrectZkpException.java sync/jpake/JPakeClient.java sync/jpake/JPakeCrypto.java sync/jpake/JPakeJson.java sync/jpake/JPakeNoActivePairingException.java sync/jpake/JPakeNumGenerator.java sync/jpake/JPakeNumGeneratorRandom.java sync/jpake/JPakeParty.java sync/jpake/JPakeRequest.java sync/jpake/JPakeRequestDelegate.java sync/jpake/JPakeResponse.java sync/jpake/stage/CompleteStage.java sync/jpake/stage/ComputeFinalStage.java sync/jpake/stage/ComputeKeyVerificationStage.java sync/jpake/stage/ComputeStepOneStage.java sync/jpake/stage/ComputeStepTwoStage.java sync/jpake/stage/DecryptDataStage.java sync/jpake/stage/GetChannelStage.java sync/jpake/stage/GetRequestStage.java sync/jpake/stage/JPakeStage.java sync/jpake/stage/PutRequestStage.java sync/jpake/stage/VerifyPairingStage.java sync/jpake/Zkp.java sync/KeyBundleProvider.java sync/Logger.java sync/MetaGlobal.java sync/MetaGlobalException.java sync/MetaGlobalMissingEnginesException.java sync/MetaGlobalNotSetException.java sync/middleware/Crypto5MiddlewareRepository.java sync/middleware/Crypto5MiddlewareRepositorySession.java sync/middleware/MiddlewareRepository.java sync/middleware/MiddlewareRepositorySession.java sync/net/BaseResource.java sync/net/CompletedEntity.java sync/net/HandleProgressException.java sync/net/Resource.java sync/net/ResourceDelegate.java sync/net/SyncResourceDelegate.java sync/net/SyncResponse.java sync/net/SyncStorageCollectionRequest.java sync/net/SyncStorageCollectionRequestDelegate.java sync/net/SyncStorageRecordRequest.java sync/net/SyncStorageRequest.java sync/net/SyncStorageRequestDelegate.java sync/net/SyncStorageRequestIncrementalDelegate.java sync/net/SyncStorageResponse.java sync/net/TLSSocketFactory.java sync/net/WBOCollectionRequestDelegate.java sync/net/WBORequestDelegate.java sync/NoCollectionKeysSetException.java sync/NonArrayJSONException.java sync/NonObjectJSONException.java sync/PrefsSource.java sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java sync/repositories/android/AndroidBrowserBookmarksRepository.java sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java sync/repositories/android/AndroidBrowserHistoryDataAccessor.java sync/repositories/android/AndroidBrowserHistoryDataExtender.java sync/repositories/android/AndroidBrowserHistoryRepository.java sync/repositories/android/AndroidBrowserHistoryRepositorySession.java sync/repositories/android/AndroidBrowserPasswordsDataAccessor.java sync/repositories/android/AndroidBrowserPasswordsRepository.java sync/repositories/android/AndroidBrowserPasswordsRepositorySession.java sync/repositories/android/AndroidBrowserRepository.java sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java sync/repositories/android/AndroidBrowserRepositorySession.java sync/repositories/android/BrowserContractHelpers.java sync/repositories/android/CachedSQLiteOpenHelper.java sync/repositories/android/ClientsDatabase.java sync/repositories/android/ClientsDatabaseAccessor.java sync/repositories/android/PasswordColumns.java sync/repositories/android/RepoUtils.java sync/repositories/BookmarkNeedsReparentingException.java sync/repositories/BookmarksRepository.java sync/repositories/ConstrainedServer11Repository.java sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionBeginDelegate.java sync/repositories/delegates/RepositorySessionCleanDelegate.java sync/repositories/delegates/RepositorySessionCreationDelegate.java sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/RepositorySessionFinishDelegate.java sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java sync/repositories/delegates/RepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionWipeDelegate.java sync/repositories/domain/BookmarkRecord.java sync/repositories/domain/BookmarkRecordFactory.java sync/repositories/domain/ClientRecord.java sync/repositories/domain/ClientRecordFactory.java sync/repositories/domain/HistoryRecord.java sync/repositories/domain/HistoryRecordFactory.java sync/repositories/domain/PasswordRecord.java sync/repositories/domain/Record.java sync/repositories/domain/TabsRecord.java sync/repositories/HashSetStoreTracker.java sync/repositories/HistoryRepository.java sync/repositories/IdentityRecordFactory.java sync/repositories/InactiveSessionException.java sync/repositories/InvalidBookmarkTypeException.java sync/repositories/InvalidRequestException.java sync/repositories/InvalidSessionTransitionException.java sync/repositories/MultipleRecordsForGuidException.java sync/repositories/NoGuidForIdException.java sync/repositories/NoStoreDelegateException.java sync/repositories/NullCursorException.java sync/repositories/ParentNotFoundException.java sync/repositories/ProfileDatabaseException.java sync/repositories/RecordFactory.java sync/repositories/RecordFilter.java sync/repositories/Repository.java sync/repositories/RepositorySession.java sync/repositories/RepositorySessionBundle.java sync/repositories/Server11Repository.java sync/repositories/Server11RepositorySession.java sync/repositories/StoreTracker.java sync/repositories/StoreTrackingRepositorySession.java sync/setup/activities/AccountActivity.java sync/setup/activities/SetupFailureActivity.java sync/setup/activities/SetupSuccessActivity.java sync/setup/activities/SetupSyncActivity.java sync/setup/Constants.java sync/setup/SyncAuthenticatorService.java sync/stage/AndroidBrowserBookmarksServerSyncStage.java sync/stage/AndroidBrowserHistoryServerSyncStage.java sync/stage/CheckPreconditionsStage.java sync/stage/CompletedStage.java sync/stage/EnsureClusterURLStage.java sync/stage/EnsureKeysStage.java sync/stage/FetchInfoCollectionsStage.java sync/stage/FetchMetaGlobalStage.java sync/stage/GlobalSyncStage.java sync/stage/NoSuchStageException.java sync/stage/NoSyncIDException.java sync/stage/ServerSyncStage.java sync/stage/SyncClientsEngineStage.java sync/StubActivity.java sync/syncadapter/SyncAdapter.java sync/syncadapter/SyncService.java sync/SyncConfiguration.java sync/SyncConfigurationException.java sync/SyncException.java sync/synchronizer/ConcurrentRecordConsumer.java sync/synchronizer/RecordConsumer.java sync/synchronizer/RecordsChannel.java sync/synchronizer/RecordsChannelDelegate.java sync/synchronizer/RecordsConsumerDelegate.java sync/synchronizer/SerialRecordConsumer.java sync/synchronizer/SessionNotBegunException.java sync/synchronizer/Synchronizer.java sync/synchronizer/SynchronizerDelegate.java sync/synchronizer/SynchronizerSession.java sync/synchronizer/SynchronizerSessionDelegate.java sync/synchronizer/UnbundleError.java sync/synchronizer/UnexpectedSessionException.java sync/SynchronizerConfiguration.java sync/SynchronizerConfigurations.java sync/ThreadPool.java sync/UnexpectedJSONException.java sync/UnknownSynchronizerConfigurationVersionException.java sync/Utils.java diff --git a/mobile/xul/chrome/content/bindings/extensions.xml b/mobile/xul/chrome/content/bindings/extensions.xml index 8ba48f46713b..6986eb856112 100644 --- a/mobile/xul/chrome/content/bindings/extensions.xml +++ b/mobile/xul/chrome/content/bindings/extensions.xml @@ -117,21 +117,31 @@ if (!xhr.responseXML) return; - let currentNode; - let nodeIterator = xhr.responseXML.createNodeIterator(xhr.responseXML, - NodeFilter.SHOW_TEXT, - null, - false); - while (currentNode = nodeIterator.nextNode()) { - let trimmed = currentNode.nodeValue.replace(/^\s\s*/, "").replace(/\s\s*$/, ""); - if (!trimmed.length) - currentNode.parentNode.removeChild(currentNode); + // This function removes and returns the text content of aNode without + // removing any child elements. Removing the text nodes ensures any XBL + // bindings apply properly. + function stripTextNodes(aNode) { + var text = ''; + for (var i = 0; i < aNode.childNodes.length; i++) { + if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) { + text += aNode.childNodes[i].textContent; + aNode.removeChild(aNode.childNodes[i--]); + } else { + text += stripTextNodes(aNode.childNodes[i]); + } + } + return text; } // Only allow for now - let prefs = xhr.responseXML.querySelectorAll(":root > setting"); - for (let i = 0; i < prefs.length; i++) - box.appendChild(prefs.item(i)); + let settings = xhr.responseXML.querySelectorAll(":root > setting"); + for (let i = 0; i < settings.length; i++) { + var setting = settings[i]; + var desc = stripTextNodes(setting).trim(); + if (!setting.hasAttribute("desc")) + setting.setAttribute("desc", desc); + box.appendChild(setting); + } // Send an event so add-ons can prepopulate any non-preference based // settings diff --git a/mobile/xul/chrome/content/browser.css b/mobile/xul/chrome/content/browser.css index 10c27e0f8c95..b9863e6f9073 100644 --- a/mobile/xul/chrome/content/browser.css +++ b/mobile/xul/chrome/content/browser.css @@ -30,41 +30,54 @@ settings { -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#settings"); } +setting { + display: none; +} + setting[type="bool"] { + display: -moz-box; -moz-binding: url("chrome://browser/content/bindings.xml#setting-fulltoggle-bool"); } setting[type="bool"][localized="true"] { + display: -moz-box; -moz-binding: url("chrome://browser/content/bindings.xml#setting-fulltoggle-localized-bool"); } setting[type="boolint"] { + display: -moz-box; -moz-binding: url("chrome://browser/content/bindings.xml#setting-fulltoggle-boolint"); } setting[type="integer"] { + display: -moz-box; -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-integer"); } setting[type="control"] { + display: -moz-box; -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-control"); } setting[type="string"] { + display: -moz-box; -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-string"); } setting[type="color"] { + display: -moz-box; -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-color"); } setting[type="file"], setting[type="directory"] { + display: -moz-box; -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-path"); } setting[type="radio"], setting[type="menulist"] { + display: -moz-box; -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-multi"); } diff --git a/mobile/xul/themes/core/browser.css b/mobile/xul/themes/core/browser.css index ca13c214909b..aae6d6479ee1 100644 --- a/mobile/xul/themes/core/browser.css +++ b/mobile/xul/themes/core/browser.css @@ -1273,10 +1273,14 @@ setting { -moz-box-orient: horizontal; } -.setting-label { +setting > vbox { -moz-box-flex: 1; } +.preferences-description:empty { + display: none; +} + .setting-group > setting { border-bottom: none; } @@ -1291,20 +1295,24 @@ setting { } /* Put setting textboxes on a separate row in portrait */ -@media (@orientation@: portrait) { +@media (@orientation@: portrait) and (max-width: 600px) { setting[type="integer"], setting[type="string"], setting[type="file"], setting[type="directory"] { - -moz-box-align: start; + -moz-box-align: stretch; -moz-box-orient: vertical; } +} - setting[type="integer"] > .setting-input > textbox, - setting[type="string"] > .setting-input > textbox { - width: 499px; /* textboxes seem to need a width in order to flex */ - -moz-box-flex: 1; - } +setting[type="integer"] > .preferences-alignment, +setting[type="string"] > .preferences-alignment { + -moz-box-flex: 3; +} + +setting[type="file"] > .preferences-alignment, +setting[type="directory"] > .preferences-alignment { + -moz-box-align: center; } .options-box { diff --git a/mobile/xul/themes/core/gingerbread/browser.css b/mobile/xul/themes/core/gingerbread/browser.css index dc4667e62ca1..fdf402870a2a 100644 --- a/mobile/xul/themes/core/gingerbread/browser.css +++ b/mobile/xul/themes/core/gingerbread/browser.css @@ -1264,10 +1264,14 @@ setting:hover:active { background-color: @color_background_highlight@; } -.setting-label { +setting > vbox { -moz-box-flex: 1; } +.preferences-description:empty { + display: none; +} + .setting-group > setting { border-bottom: none; } @@ -1282,18 +1286,22 @@ setting:hover:active { } /* Put setting textboxes on a separate row in portrait */ -@media (@orientation@: portrait) { +@media (@orientation@: portrait) and (max-width: 600px) { setting[type="integer"], setting[type="string"] { - -moz-box-align: start; + -moz-box-align: stretch; -moz-box-orient: vertical; } +} - setting[type="integer"] > .setting-input > textbox, - setting[type="string"] > .setting-input > textbox { - width: 499px; /* textboxes seem to need a width in order to flex */ - -moz-box-flex: 1; - } +setting[type="integer"] > .preferences-alignment, +setting[type="string"] > .preferences-alignment { + -moz-box-flex: 3; +} + +setting[type="file"] > .preferences-alignment, +setting[type="directory"] > .preferences-alignment { + -moz-box-align: center; } .options-box { diff --git a/mobile/xul/themes/core/honeycomb/browser.css b/mobile/xul/themes/core/honeycomb/browser.css index fc5d51b36fdd..9807e85f9707 100644 --- a/mobile/xul/themes/core/honeycomb/browser.css +++ b/mobile/xul/themes/core/honeycomb/browser.css @@ -1482,10 +1482,14 @@ setting[type="bool"]:hover:active .setting-input > checkbox[checked="true"] > .c background-image: url("chrome://browser/skin/images/check-selected-tap-hdpi.png"); } -.setting-label { +setting > vbox { -moz-box-flex: 1; } +.preferences-description:empty { + display: none; +} + .setting-group > setting { border-bottom: none; } @@ -1500,18 +1504,22 @@ setting[type="bool"]:hover:active .setting-input > checkbox[checked="true"] > .c } /* Put setting textboxes on a separate row in portrait */ -@media (@orientation@: portrait) { +@media (@orientation@: portrait) and (max-width: 600px) { setting[type="integer"], setting[type="string"] { - -moz-box-align: start; + -moz-box-align: stretch; -moz-box-orient: vertical; } +} - setting[type="integer"] > .setting-input > textbox, - setting[type="string"] > .setting-input > textbox { - width: 499px; /* textboxes seem to need a width in order to flex */ - -moz-box-flex: 1; - } +setting[type="integer"] > .preferences-alignment, +setting[type="string"] > .preferences-alignment { + -moz-box-flex: 3; +} + +setting[type="file"] > .preferences-alignment, +setting[type="directory"] > .preferences-alignment { + -moz-box-align: center; } .options-box { diff --git a/modules/libmar/tests/unit/test_sign_verify.js b/modules/libmar/tests/unit/test_sign_verify.js index e0c90697cb1d..3dd2c937116f 100644 --- a/modules/libmar/tests/unit/test_sign_verify.js +++ b/modules/libmar/tests/unit/test_sign_verify.js @@ -57,8 +57,7 @@ function run_test() { // The XPCShell test wiki indicates this is the preferred way for // Windows detection. - var isWindows = ("@mozilla.org/windows-registry-key;1" - in Cc); + var isWindows = ("@mozilla.org/windows-registry-key;1" in Cc); // Setup the command line arguments to create the MAR. // Windows vs. Linux/Mac/... have different command line for verification diff --git a/mozglue/linker/CustomElf.cpp b/mozglue/linker/CustomElf.cpp index 1a13824e6233..949e441b6693 100644 --- a/mozglue/linker/CustomElf.cpp +++ b/mozglue/linker/CustomElf.cpp @@ -547,6 +547,16 @@ CustomElf::InitDyn(const Phdr *pt_dyn) return false; } break; + case DT_FLAGS: + { + Word flags = dyn->d_un.d_val; + /* we can treat this like having a DT_SYMBOLIC tag */ + flags &= ~DF_SYMBOLIC; + if (flags) + log("%s: Warning: unhandled flags #%" PRIxAddr" not handled", + GetPath(), flags); + } + break; case DT_SONAME: /* Should match GetName(), but doesn't matter */ case DT_SYMBOLIC: /* Indicates internal symbols should be looked up in * the library itself first instead of the executable, diff --git a/mozglue/linker/CustomElf.h b/mozglue/linker/CustomElf.h index b21ede03a342..86906328d831 100644 --- a/mozglue/linker/CustomElf.h +++ b/mozglue/linker/CustomElf.h @@ -147,6 +147,12 @@ #ifndef DT_VERNEEDNUM #define DT_VERNEEDNUM 0x6fffffff #endif +#ifndef DT_FLAGS +#define DT_FLAGS 30 +#endif +#ifndef DF_SYMBOLIC +#define DF_SYMBOLIC 0x00000002 +#endif namespace Elf { diff --git a/mozglue/linker/Mappable.cpp b/mozglue/linker/Mappable.cpp index 94c876ef2fe0..6a867a5d3368 100644 --- a/mozglue/linker/Mappable.cpp +++ b/mozglue/linker/Mappable.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "Mappable.h" #ifdef ANDROID #include diff --git a/netwerk/base/src/nsSimpleURI.cpp b/netwerk/base/src/nsSimpleURI.cpp index 1539860b832a..7817a075a6f9 100644 --- a/netwerk/base/src/nsSimpleURI.cpp +++ b/netwerk/base/src/nsSimpleURI.cpp @@ -81,6 +81,7 @@ NS_INTERFACE_TABLE_TO_MAP_SEGUE if (aIID.Equals(kThisSimpleURIImplementationCID)) foundInterface = static_cast(this); else + NS_INTERFACE_MAP_ENTRY(nsISizeOf) NS_INTERFACE_MAP_END //////////////////////////////////////////////////////////////////////////////// @@ -656,3 +657,21 @@ nsSimpleURI::SetMutable(bool value) mMutable = value; return NS_OK; } + +//---------------------------------------------------------------------------- +// nsSimpleURI::nsISizeOf +//---------------------------------------------------------------------------- + +size_t +nsSimpleURI::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const +{ + return mScheme.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + mRef.SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +size_t +nsSimpleURI::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + diff --git a/netwerk/base/src/nsSimpleURI.h b/netwerk/base/src/nsSimpleURI.h index ce0a5629bb2c..f2202ec21326 100644 --- a/netwerk/base/src/nsSimpleURI.h +++ b/netwerk/base/src/nsSimpleURI.h @@ -45,6 +45,7 @@ #include "nsString.h" #include "nsIClassInfo.h" #include "nsIMutable.h" +#include "nsISizeOf.h" #define NS_THIS_SIMPLEURI_IMPLEMENTATION_CID \ { /* 0b9bb0c2-fee6-470b-b9b9-9fd9462b5e19 */ \ @@ -58,7 +59,8 @@ class nsSimpleURI : public nsIURI, public nsISerializable, public nsIIPCSerializable, public nsIClassInfo, - public nsIMutable + public nsIMutable, + public nsISizeOf { public: NS_DECL_ISUPPORTS @@ -73,6 +75,16 @@ public: nsSimpleURI(); virtual ~nsSimpleURI(); + // nsISizeOf + // Among the sub-classes that inherit (directly or indirectly) from + // nsSimpleURI, measurement of the following members may be added later if + // DMD finds it is worthwhile: + // - nsJSURI: mBaseURI + // - nsSimpleNestedURI: mInnerURI + // - nsBlobURI: mPrincipal + virtual size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const; + virtual size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const; + protected: // enum used in a few places to specify how .ref attribute should be handled enum RefHandlingEnum { diff --git a/netwerk/base/src/nsStandardURL.cpp b/netwerk/base/src/nsStandardURL.cpp index addca86b604b..86f4c5d8a467 100644 --- a/netwerk/base/src/nsStandardURL.cpp +++ b/netwerk/base/src/nsStandardURL.cpp @@ -978,6 +978,7 @@ NS_INTERFACE_MAP_BEGIN(nsStandardURL) if (aIID.Equals(kThisImplCID)) foundInterface = static_cast(this); else + NS_INTERFACE_MAP_ENTRY(nsISizeOf) NS_INTERFACE_MAP_END //---------------------------------------------------------------------------- @@ -3068,3 +3069,26 @@ nsStandardURL::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) *aClassIDNoAlloc = kStandardURLCID; return NS_OK; } + +//---------------------------------------------------------------------------- +// nsStandardURL::nsISizeOf +//---------------------------------------------------------------------------- + +size_t +nsStandardURL::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const +{ + return mSpec.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + mOriginCharset.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + aMallocSizeOf(mHostA); + + // Measurement of the following members may be added later if DMD finds it is + // worthwhile: + // - mParser + // - mFile +} + +size_t +nsStandardURL::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + diff --git a/netwerk/base/src/nsStandardURL.h b/netwerk/base/src/nsStandardURL.h index 902d2390d674..a090bbdd2bed 100644 --- a/netwerk/base/src/nsStandardURL.h +++ b/netwerk/base/src/nsStandardURL.h @@ -54,6 +54,7 @@ #include "nsCOMPtr.h" #include "nsURLHelper.h" #include "nsIClassInfo.h" +#include "nsISizeOf.h" #include "prclist.h" #ifdef NS_BUILD_REFCNT_LOGGING @@ -75,6 +76,7 @@ class nsStandardURL : public nsIFileURL , public nsISerializable , public nsIIPCSerializable , public nsIClassInfo + , public nsISizeOf { public: NS_DECL_ISUPPORTS @@ -87,6 +89,10 @@ public: NS_DECL_NSICLASSINFO NS_DECL_NSIMUTABLE + // nsISizeOf + virtual size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const; + virtual size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const; + nsStandardURL(bool aSupportsFileURL = false); virtual ~nsStandardURL(); @@ -115,7 +121,6 @@ public: /* internal -- HPUX compiler can't handle this being private */ mLen += 1 + right.mLen; } } - }; // diff --git a/netwerk/dns/effective_tld_names.dat b/netwerk/dns/effective_tld_names.dat index 889bcd5c495c..7fbce465e982 100644 --- a/netwerk/dns/effective_tld_names.dat +++ b/netwerk/dns/effective_tld_names.dat @@ -40,6 +40,8 @@ // // ***** END LICENSE BLOCK ***** +// ===BEGIN ICANN DOMAINS=== + // ac : http://en.wikipedia.org/wiki/.ac ac com.ac @@ -247,14 +249,6 @@ co.at gv.at or.at -// http://www.info.at/ -biz.at -info.at - -// priv.at : http://www.nic.priv.at/ -// Submitted by registry 2008-06-09 -priv.at - // au : http://en.wikipedia.org/wiki/.au // http://www.auda.org.au/ // 2LDs @@ -587,9 +581,6 @@ yk.ca // see also: http://registry.gc.ca/en/SubdomainFAQ gc.ca -// co.ca: http://registry.co.ca -co.ca - // cat : http://en.wikipedia.org/wiki/.cat cat @@ -713,35 +704,6 @@ web.co // com : http://en.wikipedia.org/wiki/.com com -// CentralNic names : http://www.centralnic.com/names/domains -// Confirmed by registry 2008-06-09 -// Updated by registry 2011-05-27 -ar.com -br.com -cn.com -de.com -eu.com -gb.com -gr.com -hu.com -jpn.com -kr.com -no.com -qc.com -ru.com -sa.com -se.com -uk.com -us.com -uy.com -za.com - -// Requested by Yngve Pettersen 2009-11-26 -operaunite.com - -// Requested by Eduardo Vela 2010-09-06 -appspot.com - // coop : http://en.wikipedia.org/wiki/.coop coop @@ -783,10 +745,6 @@ cz // reservations) 2008-07-01 de -// CentralNic names : http://www.centralnic.com/names/domains -// Submitted by registry 2011-05-27 -com.de - // dj : http://en.wikipedia.org/wiki/.dj dj @@ -895,8 +853,6 @@ fi // completely removed. // TODO: Check for updates (expected to be phased out around Q1/2009) aland.fi -// iki.fi : Submitted by Hannu Aronsson 2009-11-05 -iki.fi // fj : http://en.wikipedia.org/wiki/.fj *.fj @@ -1819,8 +1775,6 @@ gov.la per.la com.la org.la -// see http://www.c.la/ -c.la // lb : http://en.wikipedia.org/wiki/.lb // Submitted by registry 2008-06-17 @@ -2660,17 +2614,6 @@ ne // net : http://en.wikipedia.org/wiki/.net net -// CentralNic names : http://www.centralnic.com/names/domains -// Submitted by registry 2008-06-17 -gb.net -jp.net -se.net -uk.net - -// ZaNiC names : http://www.za.net/ -// Confirmed by registry 2009-10-03 -za.net - // nf : http://en.wikipedia.org/wiki/.nf nf com.nf @@ -2704,9 +2647,6 @@ nl // BV.nl will be a registry for dutch BV's (besloten vennootschap) bv.nl -// the co.nl domain is managed by CoDNS B.V. Added 2010-05-23. -co.nl - // no : http://www.norid.no/regelverk/index.en.html // The Norwegian registry has declined to notify us of updates. The web pages // referenced below are the official source of the data. There is also an @@ -3474,9 +3414,6 @@ våler.østfold.no valer.hedmark.no våler.hedmark.no -// the co.no domain is managed by CoDNS B.V. Added 2010-05-23. -co.no - // np : http://www.mos.com.np/register.html *.np @@ -3513,16 +3450,6 @@ nu // org : http://en.wikipedia.org/wiki/.org org -// CentralNic names : http://www.centralnic.com/names/domains -// Submitted by registry 2008-06-17 -// Updated by registry 2011-05-27 -ae.org -us.org - -// ZaNiC names : http://www.za.net/ -// Confirmed by registry 2009-10-03 -za.org - // pa : http://www.nic.pa/ // Some additional second level "domains" resolve directly as hostnames, such as // pannet.pa, so we add a rule for "pa". @@ -3776,9 +3703,6 @@ poznan.pl wroc.pl zakopane.pl -// co.pl : Mainseek Sp. z o.o. http://www.co.pl -co.pl - // pn : http://www.government.pn/PnRegistry/policies.htm pn gov.pn @@ -4942,7 +4866,77 @@ xxx // zw : http://en.wikipedia.org/wiki/.zw *.zw -// DynDNS.com Dynamic DNS zones : http://www.dyndns.com/services/dns/dyndns/ +// ===END ICANN DOMAINS=== +// ===BEGIN PRIVATE DOMAINS=== + +// info.at : http://www.info.at/ +biz.at +info.at + +// priv.at : http://www.nic.priv.at/ +// Submitted by registry 2008-06-09 +priv.at + +// co.ca : http://registry.co.ca +co.ca + +// CentralNic : http://www.centralnic.com/names/domains +// Confirmed by registry 2008-06-09 +ar.com +br.com +cn.com +de.com +eu.com +gb.com +gr.com +hu.com +jpn.com +kr.com +no.com +qc.com +ru.com +sa.com +se.com +uk.com +us.com +uy.com +za.com +gb.net +jp.net +se.net +uk.net +ae.org +us.org +com.de + +// Opera Software, A.S.A. +// Requested by Yngve Pettersen 2009-11-26 +operaunite.com + +// Google, Inc. +// Requested by Eduardo Vela 2010-09-06 +appspot.com + +// iki.fi : Submitted by Hannu Aronsson 2009-11-05 +iki.fi + +// c.la : http://www.c.la/ +c.la + +// ZaNiC : http://www.za.net/ +// Confirmed by registry 2009-10-03 +za.net +za.org + +// CoDNS B.V. +// Added 2010-05-23. +co.nl +co.no + +// Mainseek Sp. z o.o. : http://www.co.pl/ +co.pl + +// DynDNS.com : http://www.dyndns.com/services/dns/dyndns/ dyndns-at-home.com dyndns-at-work.com dyndns-blog.com @@ -5222,3 +5216,5 @@ webhop.net webhop.org worse-than.tv writesthisblog.com + +// ===END PRIVATE DOMAINS=== diff --git a/netwerk/test/unit/test_net_addr.js b/netwerk/test/unit/test_net_addr.js index d4bbffe60fa3..eb7555c484d1 100644 --- a/netwerk/test/unit/test_net_addr.js +++ b/netwerk/test/unit/test_net_addr.js @@ -121,22 +121,33 @@ var serv; */ var connectTimeout = 5*1000; +/** + * A place for individual tests to place Objects of importance for access + * throughout asynchronous testing. Particularly important for any output or + * input streams opened, as cleanup of those objects (by the garbage collector) + * causes the stream to close and may have other side effects. + */ +var testDataStore = null; + /** * IPv4 test. */ function testIpv4() { - var transport; + testDataStore = { + transport : null , + ouput : null + } serv.acceptCallback = function() { // disable the timeoutCallback serv.timeoutCallback = function(){}; - var selfAddr = transport.getScriptableSelfAddr(); - var peerAddr = transport.getScriptablePeerAddr(); + var selfAddr = testDataStore.transport.getScriptableSelfAddr(); + var peerAddr = testDataStore.transport.getScriptablePeerAddr(); // check peerAddr against expected values do_check_eq(peerAddr.family, Ci.nsINetAddr.FAMILY_INET); - do_check_eq(peerAddr.port, transport.port); + do_check_eq(peerAddr.port, testDataStore.transport.port); do_check_eq(peerAddr.port, serv.port); do_check_eq(peerAddr.address, "127.0.0.1"); @@ -148,6 +159,7 @@ function testIpv4() { checkAddrEqual(selfAddr, serv.peerAddr); checkAddrEqual(peerAddr, serv.selfAddr); + testDataStore = null; do_execute_soon(run_next_test); }; @@ -158,11 +170,16 @@ function testIpv4() { }; do_timeout(connectTimeout, function(){ serv.timeoutCallback('testIpv4'); });*/ - transport = sts.createTransport(null, 0, '127.0.0.1', serv.port, null); - transport.openOutputStream(Ci.nsITransport.OPEN_BLOCKING,0,0); + testDataStore.transport = sts.createTransport(null, 0, '127.0.0.1', serv.port, null); + /* + * Need to hold |output| so that the output stream doesn't close itself and + * the associated connection. + */ + testDataStore.output = testDataStore.transport.openOutputStream(Ci.nsITransport.OPEN_BLOCKING,0,0); + /* NEXT: * openOutputStream -> onSocketAccepted -> acceptedCallback -> run_next_test - * OR + * OR (if the above timeout is uncommented) * -> timeoutCallback -> do_throw */ } diff --git a/other-licenses/README b/other-licenses/README new file mode 100644 index 000000000000..03de0ff2093b --- /dev/null +++ b/other-licenses/README @@ -0,0 +1,8 @@ +This directory was created for code which is used in the Mozilla project in +some way but is not under the MPL or a compatible license like the Apache 2, +BSD or MIT licenses. + +It is _NOT_ for "all non-MPLed code". + +Before putting any new code in here, please consult licensing@mozilla.org. It +is quite likely that this is not the right place. diff --git a/parser/html/nsHtml5MetaScannerCppSupplement.h b/parser/html/nsHtml5MetaScannerCppSupplement.h index 2038471be215..f634a2a4e018 100644 --- a/parser/html/nsHtml5MetaScannerCppSupplement.h +++ b/parser/html/nsHtml5MetaScannerCppSupplement.h @@ -37,11 +37,10 @@ #include "nsICharsetConverterManager.h" #include "nsServiceManagerUtils.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "nsEncoderDecoderUtils.h" #include "nsTraceRefcnt.h" -static NS_DEFINE_CID(kCharsetAliasCID, NS_CHARSETALIAS_CID); void nsHtml5MetaScanner::sniff(nsHtml5ByteReadable* bytes, nsIUnicodeDecoder** decoder, nsACString& charset) @@ -82,12 +81,7 @@ nsHtml5MetaScanner::tryCharset(nsString* charset) return true; } nsCAutoString preferred; - nsCOMPtr calias(do_GetService(kCharsetAliasCID, &res)); - if (NS_FAILED(res)) { - NS_ERROR("Could not get CharsetAlias service."); - return false; - } - res = calias->GetPreferred(encoding, preferred); + res = nsCharsetAlias::GetPreferred(encoding, preferred); if (NS_FAILED(res)) { return false; } diff --git a/parser/html/nsHtml5Parser.cpp b/parser/html/nsHtml5Parser.cpp index 5194cef1ad25..fae4c0c7cf49 100644 --- a/parser/html/nsHtml5Parser.cpp +++ b/parser/html/nsHtml5Parser.cpp @@ -42,7 +42,6 @@ #include "nsScriptLoader.h" #include "nsNetUtil.h" #include "nsIStyleSheetLinkingElement.h" -#include "nsICharsetAlias.h" #include "nsIWebShellServices.h" #include "nsIDocShell.h" #include "nsEncoderDecoderUtils.h" diff --git a/parser/html/nsHtml5StreamParser.cpp b/parser/html/nsHtml5StreamParser.cpp index f4e131544ff8..f84a9d5cc88f 100644 --- a/parser/html/nsHtml5StreamParser.cpp +++ b/parser/html/nsHtml5StreamParser.cpp @@ -40,7 +40,7 @@ #include "nsHtml5StreamParser.h" #include "nsICharsetConverterManager.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "nsServiceManagerUtils.h" #include "nsEncoderDecoderUtils.h" #include "nsContentUtils.h" @@ -60,7 +60,6 @@ using namespace mozilla; -static NS_DEFINE_CID(kCharsetAliasCID, NS_CHARSETALIAS_CID); PRInt32 nsHtml5StreamParser::sTimerInitialDelay = 120; PRInt32 nsHtml5StreamParser::sTimerSubsequentDelay = 120; @@ -1171,13 +1170,8 @@ nsHtml5StreamParser::PreferredForInternalEncodingDecl(nsACString& aEncoding) } nsresult rv = NS_OK; - nsCOMPtr calias(do_GetService(kCharsetAliasCID, &rv)); - if (NS_FAILED(rv)) { - NS_NOTREACHED("Charset alias service not available."); - return false; - } bool eq; - rv = calias->Equals(newEncoding, mCharset, &eq); + rv = nsCharsetAlias::Equals(newEncoding, mCharset, &eq); if (NS_FAILED(rv)) { NS_NOTREACHED("Charset name equality check failed."); return false; @@ -1192,7 +1186,7 @@ nsHtml5StreamParser::PreferredForInternalEncodingDecl(nsACString& aEncoding) nsCAutoString preferred; - rv = calias->GetPreferred(newEncoding, preferred); + rv = nsCharsetAlias::GetPreferred(newEncoding, preferred); if (NS_FAILED(rv)) { // the encoding name is bogus return false; diff --git a/parser/html/nsHtml5StreamParser.h b/parser/html/nsHtml5StreamParser.h index 09f55c7ff031..00cfa1c8c43a 100644 --- a/parser/html/nsHtml5StreamParser.h +++ b/parser/html/nsHtml5StreamParser.h @@ -48,7 +48,6 @@ #include "nsHtml5TreeOpExecutor.h" #include "nsHtml5OwningUTF16Buffer.h" #include "nsIInputStream.h" -#include "nsICharsetAlias.h" #include "mozilla/Mutex.h" #include "nsHtml5AtomTable.h" #include "nsHtml5Speculation.h" diff --git a/parser/htmlparser/src/nsParser.cpp b/parser/htmlparser/src/nsParser.cpp index b3d0ad6a8757..ad1700949c90 100644 --- a/parser/htmlparser/src/nsParser.cpp +++ b/parser/htmlparser/src/nsParser.cpp @@ -47,7 +47,7 @@ #include "nsIChannel.h" #include "nsICachingChannel.h" #include "nsICacheEntryDescriptor.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "nsICharsetConverterManager.h" #include "nsIInputStream.h" #include "CNavDTD.h" @@ -161,7 +161,6 @@ public: //-------------- End ParseContinue Event Definition ------------------------ -nsICharsetAlias* nsParser::sCharsetAliasService = nsnull; nsICharsetConverterManager* nsParser::sCharsetConverterManager = nsnull; /** @@ -173,15 +172,10 @@ nsParser::Init() { nsresult rv; - nsCOMPtr charsetAlias = - do_GetService(NS_CHARSETALIAS_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr charsetConverter = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); - charsetAlias.swap(sCharsetAliasService); charsetConverter.swap(sCharsetConverterManager); return NS_OK; @@ -194,7 +188,6 @@ nsParser::Init() // static void nsParser::Shutdown() { - NS_IF_RELEASE(sCharsetAliasService); NS_IF_RELEASE(sCharsetConverterManager); } @@ -2064,8 +2057,7 @@ ParserWriteFunc(nsIInputStream* in, ((count >= 4) && DetectByteOrderMark((const unsigned char*)buf, theNumRead, guess, guessSource))) { - nsCOMPtr alias(do_GetService(NS_CHARSETALIAS_CONTRACTID)); - result = alias->GetPreferred(guess, preferred); + result = nsCharsetAlias::GetPreferred(guess, preferred); // Only continue if it's a recognized charset and not // one of a designated set that we ignore. if (NS_SUCCEEDED(result) && diff --git a/parser/htmlparser/src/nsParser.h b/parser/htmlparser/src/nsParser.h index f0f1ad72fc7a..1635b1d96172 100644 --- a/parser/htmlparser/src/nsParser.h +++ b/parser/htmlparser/src/nsParser.h @@ -89,7 +89,6 @@ #include "nsWeakReference.h" class nsICharsetConverterManager; -class nsICharsetAlias; class nsIDTD; class nsScanner; class nsIThreadPool; @@ -357,10 +356,6 @@ class nsParser : public nsIParser, */ void HandleParserContinueEvent(class nsParserContinueEvent *); - static nsICharsetAlias* GetCharsetAliasService() { - return sCharsetAliasService; - } - static nsICharsetConverterManager* GetCharsetConverterManager() { return sCharsetConverterManager; } @@ -465,7 +460,6 @@ protected: bool mProcessingNetworkData; - static nsICharsetAlias* sCharsetAliasService; static nsICharsetConverterManager* sCharsetConverterManager; }; diff --git a/parser/htmlparser/src/nsScanner.cpp b/parser/htmlparser/src/nsScanner.cpp index 5b55976a39c2..dcce8ce951c9 100644 --- a/parser/htmlparser/src/nsScanner.cpp +++ b/parser/htmlparser/src/nsScanner.cpp @@ -42,7 +42,7 @@ #include "nsDebug.h" #include "nsIServiceManager.h" #include "nsICharsetConverterManager.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "nsReadableUtils.h" #include "nsIInputStream.h" #include "nsILocalFile.h" @@ -154,14 +154,11 @@ nsresult nsScanner::SetDocumentCharset(const nsACString& aCharset , PRInt32 aSou if (aSource < mCharsetSource) // priority is lower the the current one , just return NS_OK; - nsICharsetAlias* calias = nsParser::GetCharsetAliasService(); - NS_ASSERTION(calias, "Must have the charset alias service!"); - nsresult res = NS_OK; if (!mCharset.IsEmpty()) { bool same; - res = calias->Equals(aCharset, mCharset, &same); + res = nsCharsetAlias::Equals(aCharset, mCharset, &same); if(NS_SUCCEEDED(res) && same) { return NS_OK; // no difference, don't change it @@ -170,7 +167,7 @@ nsresult nsScanner::SetDocumentCharset(const nsACString& aCharset , PRInt32 aSou // different, need to change it nsCString charsetName; - res = calias->GetPreferred(aCharset, charsetName); + res = nsCharsetAlias::GetPreferred(aCharset, charsetName); if(NS_FAILED(res) && (mCharsetSource == kCharsetUninitialized)) { diff --git a/parser/htmlparser/tests/mochitest/file_bug594730-4.html b/parser/htmlparser/tests/mochitest/file_bug594730-4.html index 2cbf7745cb99..bdce353a59f1 100644 --- a/parser/htmlparser/tests/mochitest/file_bug594730-4.html +++ b/parser/htmlparser/tests/mochitest/file_bug594730-4.html @@ -1,3 +1,3 @@ - + diff --git a/parser/xml/src/nsSAXXMLReader.cpp b/parser/xml/src/nsSAXXMLReader.cpp index fbe3c638e7d4..4ab9a6cc6b84 100644 --- a/parser/xml/src/nsSAXXMLReader.cpp +++ b/parser/xml/src/nsSAXXMLReader.cpp @@ -39,7 +39,7 @@ #include "nsIInputStream.h" #include "nsNetCID.h" #include "nsNetUtil.h" -#include "nsICharsetAlias.h" +#include "nsCharsetAlias.h" #include "nsParserCIID.h" #include "nsStreamUtils.h" #include "nsStringStream.h" @@ -648,17 +648,11 @@ nsSAXXMLReader::TryChannelCharset(nsIChannel *aChannel, nsCAutoString charsetVal; nsresult rv = aChannel->GetContentCharset(charsetVal); if (NS_SUCCEEDED(rv)) { - nsCOMPtr - calias(do_GetService(NS_CHARSETALIAS_CONTRACTID)); - if (calias) { - nsCAutoString preferred; - rv = calias->GetPreferred(charsetVal, preferred); - if (NS_SUCCEEDED(rv)) { - aCharset = preferred; - aCharsetSource = kCharsetFromChannel; - return true; - } - } + if (NS_FAILED(nsCharsetAlias::GetPreferred(charsetVal, aCharset))) + return false; + + aCharsetSource = kCharsetFromChannel; + return true; } } diff --git a/security/manager/Makefile.in b/security/manager/Makefile.in index 1da94ccd215e..535be9db0690 100644 --- a/security/manager/Makefile.in +++ b/security/manager/Makefile.in @@ -278,6 +278,13 @@ ifeq ($(OS_TARGET),Linux) DEFAULT_GMAKE_FLAGS += FREEBL_LOWHASH=1 endif +ifdef MOZ_NO_WLZDEFS +DEFAULT_GMAKE_FLAGS += ZDEFS_FLAG= +endif +ifdef MOZ_CFLAGS_NSS +DEFAULT_GMAKE_FLAGS += XCFLAGS="$(CFLAGS)" +endif + SUBMAKEFILES = boot/Makefile ssl/Makefile pki/Makefile locales/Makefile include $(topsrcdir)/config/rules.mk diff --git a/services/sync/modules/engines.js b/services/sync/modules/engines.js index cbd748a318fc..8f404e87f8dd 100644 --- a/services/sync/modules/engines.js +++ b/services/sync/modules/engines.js @@ -992,8 +992,10 @@ SyncEngine.prototype = { doApplyBatchAndPersistFailed.call(this); count.newFailed = Utils.arraySub(this.previousFailed, failedInPreviousSync).length; + count.succeeded = Math.max(0, count.applied - count.failed); this._log.info(["Records:", count.applied, "applied,", + count.succeeded, "successfully,", count.failed, "failed to apply,", count.newFailed, "newly failed to apply,", count.reconciled, "reconciled."].join(" ")); diff --git a/services/sync/modules/policies.js b/services/sync/modules/policies.js index baaca6f9987a..c1deb84c6624 100644 --- a/services/sync/modules/policies.js +++ b/services/sync/modules/policies.js @@ -221,10 +221,12 @@ let SyncScheduler = { } break; case "weave:engine:sync:applied": - let numItems = subject.applied; - this._log.trace("Engine " + data + " applied " + numItems + " items."); - if (numItems) + let numItems = subject.succeeded; + this._log.trace("Engine " + data + " successfully applied " + numItems + + " items."); + if (numItems) { this.hasIncomingItems = true; + } break; case "weave:service:setup-complete": Svc.Idle.addIdleObserver(this, Svc.Prefs.get("scheduler.idleTime")); diff --git a/services/sync/modules/util.js b/services/sync/modules/util.js index 6b92bee2ccd1..efe7ee09d900 100644 --- a/services/sync/modules/util.js +++ b/services/sync/modules/util.js @@ -755,6 +755,130 @@ let Utils = { return Utils.encodeKeyBase32(atob(encodedKey)); }, + /** + * Compute the HTTP MAC SHA-1 for an HTTP request. + * + * @param identifier + * (string) MAC Key Identifier. + * @param key + * (string) MAC Key. + * @param method + * (string) HTTP request method. + * @param URI + * (nsIURI) HTTP request URI. + * @param extra + * (object) Optional extra parameters. Valid keys are: + * nonce_bytes - How many bytes the nonce should be. This defaults + * to 8. Note that this many bytes are Base64 encoded, so the + * string length of the nonce will be longer than this value. + * ts - Timestamp to use. Should only be defined for testing. + * nonce - String nonce. Should only be defined for testing as this + * function will generate a cryptographically secure random one + * if not defined. + * ext - Extra string to be included in MAC. Per the HTTP MAC spec, + * the format is undefined and thus application specific. + * @returns + * (object) Contains results of operation and input arguments (for + * symmetry). The object has the following keys: + * + * identifier - (string) MAC Key Identifier (from arguments). + * key - (string) MAC Key (from arguments). + * method - (string) HTTP request method (from arguments). + * hostname - (string) HTTP hostname used (derived from arguments). + * port - (string) HTTP port number used (derived from arguments). + * mac - (string) Raw HMAC digest bytes. + * getHeader - (function) Call to obtain the string Authorization + * header value for this invocation. + * nonce - (string) Nonce value used. + * ts - (number) Integer seconds since Unix epoch that was used. + */ + computeHTTPMACSHA1: function computeHTTPMACSHA1(identifier, key, method, + uri, extra) { + let ts = (extra && extra.ts) ? extra.ts : Math.floor(Date.now() / 1000); + let nonce_bytes = (extra && extra.nonce_bytes > 0) ? extra.nonce_bytes : 8; + + // We are allowed to use more than the Base64 alphabet if we want. + let nonce = (extra && extra.nonce) + ? extra.nonce + : btoa(Utils.generateRandomBytes(nonce_bytes)); + + let host = uri.asciiHost; + let port; + let usedMethod = method.toUpperCase(); + + if (uri.port != -1) { + port = uri.port; + } else if (uri.scheme == "http") { + port = "80"; + } else if (uri.scheme == "https") { + port = "443"; + } else { + throw new Error("Unsupported URI scheme: " + uri.scheme); + } + + let ext = (extra && extra.ext) ? extra.ext : ""; + + let requestString = ts.toString(10) + "\n" + + nonce + "\n" + + usedMethod + "\n" + + uri.path + "\n" + + host + "\n" + + port + "\n" + + ext + "\n"; + + let hasher = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1, + Utils.makeHMACKey(key)); + let mac = Utils.digestBytes(requestString, hasher); + + function getHeader() { + return Utils.getHTTPMACSHA1Header(this.identifier, this.ts, this.nonce, + this.mac, this.ext); + } + + return { + identifier: identifier, + key: key, + method: usedMethod, + hostname: host, + port: port, + mac: mac, + nonce: nonce, + ts: ts, + ext: ext, + getHeader: getHeader + }; + }, + + /** + * Obtain the HTTP MAC Authorization header value from fields. + * + * @param identifier + * (string) MAC key identifier. + * @param ts + * (number) Integer seconds since Unix epoch. + * @param nonce + * (string) Nonce value. + * @param mac + * (string) Computed HMAC digest (raw bytes). + * @param ext + * (optional) (string) Extra string content. + * @returns + * (string) Value to put in Authorization header. + */ + getHTTPMACSHA1Header: function getHTTPMACSHA1Header(identifier, ts, nonce, + mac, ext) { + let header ='MAC id="' + identifier + '", ' + + 'ts="' + ts + '", ' + + 'nonce="' + nonce + '", ' + + 'mac="' + btoa(mac) + '"'; + + if (!ext) { + return header; + } + + return header += ', ext="' + ext +'"'; + }, + makeURI: function Weave_makeURI(URIString) { if (!URIString) return null; diff --git a/services/sync/tests/unit/head_helpers.js b/services/sync/tests/unit/head_helpers.js index 7aaa2322b327..cee2ec034ed3 100644 --- a/services/sync/tests/unit/head_helpers.js +++ b/services/sync/tests/unit/head_helpers.js @@ -5,7 +5,8 @@ Cu.import("resource://services-sync/async.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://services-sync/record.js"); Cu.import("resource://services-sync/engines.js"); -var btoa; +let btoa; +let atob; let provider = { getFile: function(prop, persistent) { @@ -39,6 +40,7 @@ function waitForZeroTimer(callback) { } btoa = Cu.import("resource://services-sync/log4moz.js").btoa; +atob = Cu.import("resource://services-sync/log4moz.js").atob; function getTestLogger(component) { return Log4Moz.repository.getLogger("Testing"); } diff --git a/services/sync/tests/unit/head_http_server.js b/services/sync/tests/unit/head_http_server.js index 5b517e3f2c33..430e2c8e1d4f 100644 --- a/services/sync/tests/unit/head_http_server.js +++ b/services/sync/tests/unit/head_http_server.js @@ -514,7 +514,8 @@ function track_collections_helper() { default: throw "Non-GET on info_collections."; } - + + response.setHeader("Content-Type", "application/json"); response.setHeader("X-Weave-Timestamp", "" + new_timestamp(), false); @@ -808,6 +809,18 @@ SyncServer.prototype = { * TODO: check username in path against username in BasicAuth. */ handleDefault: function handleDefault(handler, req, resp) { + try { + this._handleDefault(handler, req, resp); + } catch (e) { + if (e instanceof HttpError) { + this.respond(req, resp, e.code, e.description, "", {}); + } else { + throw e; + } + } + }, + + _handleDefault: function _handleDefault(handler, req, resp) { this._log.debug("SyncServer: Handling request: " + req.method + " " + req.path); let parts = this.pathRE.exec(req.path); if (!parts) { diff --git a/services/sync/tests/unit/test_load_modules.js b/services/sync/tests/unit/test_load_modules.js index f426affaae0d..0cbaa13ce47a 100644 --- a/services/sync/tests/unit/test_load_modules.js +++ b/services/sync/tests/unit/test_load_modules.js @@ -1,5 +1,8 @@ const modules = [ + "addonsreconciler.js", + "async.js", "constants.js", + "engines/addons.js", "engines/bookmarks.js", "engines/clients.js", "engines/forms.js", @@ -11,12 +14,16 @@ const modules = [ "ext/Observers.js", "ext/Preferences.js", "identity.js", + "jpakeclient.js", "log4moz.js", "main.js", "notifications.js", + "policies.js", "record.js", "resource.js", + "rest.js", "service.js", + "status.js", "util.js", ]; diff --git a/services/sync/tests/unit/test_service_login.js b/services/sync/tests/unit/test_service_login.js index d6dd3d0a4518..32b6dfe76d2b 100644 --- a/services/sync/tests/unit/test_service_login.js +++ b/services/sync/tests/unit/test_service_login.js @@ -13,6 +13,7 @@ function login_handling(handler) { } else { let body = "Unauthorized"; response.setStatusLine(request.httpVersion, 401, "Unauthorized"); + response.setHeader("Content-Type", "text/plain"); response.bodyOutputStream.write(body, body.length); } }; diff --git a/services/sync/tests/unit/test_syncengine_sync.js b/services/sync/tests/unit/test_syncengine_sync.js index 476300797a56..3d9875adfafb 100644 --- a/services/sync/tests/unit/test_syncengine_sync.js +++ b/services/sync/tests/unit/test_syncengine_sync.js @@ -973,6 +973,7 @@ add_test(function test_processIncoming_notify_count() { do_check_eq(counts.failed, 3); do_check_eq(counts.applied, 15); do_check_eq(counts.newFailed, 3); + do_check_eq(counts.succeeded, 12); // Sync again, 1 of the failed items are the same, the rest didn't fail. engine._processIncoming(); @@ -986,6 +987,7 @@ add_test(function test_processIncoming_notify_count() { do_check_eq(counts.failed, 1); do_check_eq(counts.applied, 3); do_check_eq(counts.newFailed, 0); + do_check_eq(counts.succeeded, 2); Svc.Obs.remove("weave:engine:sync:applied", onApplied); } finally { diff --git a/services/sync/tests/unit/test_syncscheduler.js b/services/sync/tests/unit/test_syncscheduler.js index 1a2159cc4e92..da4ed9697cd1 100644 --- a/services/sync/tests/unit/test_syncscheduler.js +++ b/services/sync/tests/unit/test_syncscheduler.js @@ -925,3 +925,29 @@ add_test(function test_loginError_fatal_clearsTriggers() { SyncScheduler.scheduleNextSync(0); }); + +add_test(function test_proper_interval_on_only_failing() { + _("Ensure proper behavior when only failed records are applied."); + + // If an engine reports that no records succeeded, we shouldn't decrease the + // sync interval. + do_check_false(SyncScheduler.hasIncomingItems); + const INTERVAL = 10000000; + SyncScheduler.syncInterval = INTERVAL; + + Svc.Obs.notify("weave:service:sync:applied", { + applied: 2, + succeeded: 0, + failed: 2, + newFailed: 2, + reconciled: 0 + }); + + Utils.nextTick(function() { + SyncScheduler.adjustSyncInterval(); + do_check_false(SyncScheduler.hasIncomingItems); + do_check_eq(SyncScheduler.syncInterval, SyncScheduler.singleDeviceInterval); + + run_next_test(); + }); +}); diff --git a/services/sync/tests/unit/test_utils_httpmac.js b/services/sync/tests/unit/test_utils_httpmac.js new file mode 100644 index 000000000000..813a1a4c2df7 --- /dev/null +++ b/services/sync/tests/unit/test_utils_httpmac.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://services-sync/util.js"); + +function run_test() { + initTestLogging(); + + run_next_test(); +} + +add_test(function test_sha1() { + _("Ensure HTTP MAC SHA1 generation works as expected."); + + let id = "vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7"; + let key = "b8u1cc5iiio5o319og7hh8faf2gi5ym4aq0zwf112cv1287an65fudu5zj7zo7dz"; + let ts = 1329181221; + let method = "GET"; + let nonce = "wGX71"; + let uri = Utils.makeURI("http://10.250.2.176/alias/"); + + let result = Utils.computeHTTPMACSHA1(id, key, method, uri, {ts: ts, + nonce: nonce}); + + do_check_eq(btoa(result.mac), "jzh5chjQc2zFEvLbyHnPdX11Yck="); + + do_check_eq(result.getHeader(), + 'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' + + 'ts="1329181221", nonce="wGX71", mac="jzh5chjQc2zFEvLbyHnPdX11Yck="'); + + let ext = "EXTRA DATA; foo,bar=1"; + + let result = Utils.computeHTTPMACSHA1(id, key, method, uri, {ts: ts, + nonce: nonce, + ext: ext}); + do_check_eq(btoa(result.mac), "bNf4Fnt5k6DnhmyipLPkuZroH68="); + do_check_eq(result.getHeader(), + 'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' + + 'ts="1329181221", nonce="wGX71", mac="bNf4Fnt5k6DnhmyipLPkuZroH68=", ' + + 'ext="EXTRA DATA; foo,bar=1"'); + + run_next_test(); +}); + +add_test(function test_nonce_length() { + _("Ensure custom nonce lengths are honoured."); + + function get_mac(length) { + let uri = Utils.makeURI("http://example.com/"); + return Utils.computeHTTPMACSHA1("foo", "bar", "GET", uri, { + nonce_bytes: length + }); + } + + let result = get_mac(12); + do_check_eq(12, atob(result.nonce).length); + + let result = get_mac(2); + do_check_eq(2, atob(result.nonce).length); + + let result = get_mac(0); + do_check_eq(8, atob(result.nonce).length); + + let result = get_mac(-1); + do_check_eq(8, atob(result.nonce).length); + + run_next_test(); +}); diff --git a/services/sync/tests/unit/xpcshell.ini b/services/sync/tests/unit/xpcshell.ini index 39b155ae8c6b..f50e0fed07b8 100644 --- a/services/sync/tests/unit/xpcshell.ini +++ b/services/sync/tests/unit/xpcshell.ini @@ -1,6 +1,8 @@ [DEFAULT] head = head_appinfo.js head_helpers.js head_http_server.js -tail = +tail = + +[test_load_modules.js] [test_Observers.js] [test_Preferences.js] @@ -49,7 +51,6 @@ skip-if = os == "android" # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini) skip-if = os == "win" || os == "android" [test_keys.js] -[test_load_modules.js] [test_log4moz.js] [test_node_reassignment.js] [test_notifications.js] @@ -115,6 +116,7 @@ skip-if = os == "android" [test_utils_getErrorString.js] [test_utils_getIcon.js] [test_utils_hkdfExpand.js] +[test_utils_httpmac.js] [test_utils_json.js] [test_utils_lazyStrings.js] [test_utils_lock.js] diff --git a/startupcache/test/TestStartupCache.cpp b/startupcache/test/TestStartupCache.cpp index 13a3700128a0..633318468555 100644 --- a/startupcache/test/TestStartupCache.cpp +++ b/startupcache/test/TestStartupCache.cpp @@ -359,7 +359,7 @@ GetHistogramCounts(const char *testmsg, JSContext *cx, jsval *counts) nsresult CompareCountArrays(JSContext *cx, JSObject *before, JSObject *after) { - jsuint before_size, after_size; + uint32_t before_size, after_size; if (!(JS_GetArrayLength(cx, before, &before_size) && JS_GetArrayLength(cx, after, &after_size))) { return NS_ERROR_UNEXPECTED; @@ -369,7 +369,7 @@ CompareCountArrays(JSContext *cx, JSObject *before, JSObject *after) return NS_ERROR_UNEXPECTED; } - for (jsuint i = 0; i < before_size; ++i) { + for (uint32_t i = 0; i < before_size; ++i) { jsval before_num, after_num; if (!(JS_GetElement(cx, before, i, &before_num) diff --git a/testing/mochitest/tests/SimpleTest/specialpowersAPI.js b/testing/mochitest/tests/SimpleTest/specialpowersAPI.js index d21c1690cf70..e46fcf6bec2e 100644 --- a/testing/mochitest/tests/SimpleTest/specialpowersAPI.js +++ b/testing/mochitest/tests/SimpleTest/specialpowersAPI.js @@ -117,9 +117,6 @@ function doApply(fun, invocant, args) { return Function.prototype.apply.call(fun, invocant, args); } -// Use a weak map to cache wrappers. This allows the wrappers to preserve identity. -var wrapperCache = WeakMap(); - function wrapPrivileged(obj) { // Primitives pass straight through. @@ -130,15 +127,10 @@ function wrapPrivileged(obj) { if (isWrapper(obj)) throw "Trying to double-wrap object!"; - // Try the cache. - if (wrapperCache.has(obj)) - return wrapperCache.get(obj); - // Make our core wrapper object. var handler = new SpecialPowersHandler(obj); // If the object is callable, make a function proxy. - var wrapper; if (typeof obj === "function") { var callTrap = function() { // The invocant and arguments may or may not be wrappers. Unwrap them if necessary. @@ -162,16 +154,11 @@ function wrapPrivileged(obj) { return wrapPrivileged(new FakeConstructor()); }; - wrapper = Proxy.createFunction(handler, callTrap, constructTrap); - } - // Otherwise, just make a regular object proxy. - else { - wrapper = Proxy.create(handler); + return Proxy.createFunction(handler, callTrap, constructTrap); } - // Cache the wrapper and return it. - wrapperCache.set(obj, wrapper); - return wrapper; + // Otherwise, just make a regular object proxy. + return Proxy.create(handler); }; function unwrapPrivileged(x) { @@ -382,6 +369,9 @@ SpecialPowersAPI.prototype = { * * Known Issues: * + * - The wrapping function does not preserve identity, so + * SpecialPowers.wrap(foo) !== SpecialPowers.wrap(foo). See bug 718543. + * * - The wrapper cannot see expando properties on unprivileged DOM objects. * That is to say, the wrapper uses Xray delegation. * diff --git a/testing/mochitest/tests/test_SpecialPowersExtension.html b/testing/mochitest/tests/test_SpecialPowersExtension.html index 2d63fc01a91e..8a755edb5dc0 100644 --- a/testing/mochitest/tests/test_SpecialPowersExtension.html +++ b/testing/mochitest/tests/test_SpecialPowersExtension.html @@ -134,13 +134,6 @@ function starttest(){ noxray.b = 122; is(noxray_wrapper.b, 122, "Should be able to shadow."); - // Check that the wrapper preserves identity. - var someIdentityObject = {a: 2}; - ok(SpecialPowers.wrap(someIdentityObject) === SpecialPowers.wrap(someIdentityObject), - "SpecialPowers wrapping should preserve identity!"); - ok(webnav === SpecialPowers.wrap(SpecialPowers.unwrap(webnav)), - "SpecialPowers wrapping should preserve identity!"); - info("\nProfile::SpecialPowersRunTime: " + (new Date() - startTime) + "\n"); SimpleTest.finish(); } diff --git a/testing/talos/talos.json b/testing/talos/talos.json index 549802c5ee63..4f1c0326bc33 100644 --- a/testing/talos/talos.json +++ b/testing/talos/talos.json @@ -1,6 +1,6 @@ { "talos.zip": { - "url": "http://build.mozilla.org/talos/zips/talos.bug731893.8197dc094fe3.zip", + "url": "http://build.mozilla.org/talos/zips/talos.bug732835.zip", "path": "" }, "pageloader.xpi": { diff --git a/testing/testsuite-targets.mk b/testing/testsuite-targets.mk index 25462ab184e3..d0fad4ce767d 100644 --- a/testing/testsuite-targets.mk +++ b/testing/testsuite-targets.mk @@ -241,6 +241,9 @@ xpcshell-tests: --manifest=$(DEPTH)/_tests/xpcshell/xpcshell.ini \ --build-info-json=$(DEPTH)/mozinfo.json \ --no-logfiles \ + --tests-root-dir=$(call core_abspath,_tests/xpcshell) \ + --xunit-file=$(call core_abspath,_tests/xpcshell/results.xml) \ + --xunit-suite-name=xpcshell \ $(SYMBOLS_PATH) \ $(TEST_PATH_ARG) $(EXTRA_TEST_ARGS) \ $(LIBXUL_DIST)/bin/xpcshell diff --git a/testing/xpcshell/runxpcshelltests.py b/testing/xpcshell/runxpcshelltests.py index 44ed98a837ae..ba6699e55f62 100644 --- a/testing/xpcshell/runxpcshelltests.py +++ b/testing/xpcshell/runxpcshelltests.py @@ -498,7 +498,8 @@ class XPCShellTests(object): thisChunk=1, totalChunks=1, debugger=None, debuggerArgs=None, debuggerInteractive=False, profileName=None, mozInfo=None, shuffle=False, - xunitFilename=None, xunitName=None, **otherOptions): + testsRootDir=None, xunitFilename=None, xunitName=None, + **otherOptions): """Run xpcshell tests. |xpcshell|, is the xpcshell executable to use to run the tests. @@ -523,6 +524,8 @@ class XPCShellTests(object): directory if running only a subset of tests. |mozInfo|, if set, specifies specifies build configuration information, either as a filename containing JSON, or a dict. |shuffle|, if True, execute tests in random order. + |testsRootDir|, absolute path to root directory of all tests. This is used + by xUnit generation to determine the package name of the tests. |xunitFilename|, if set, specifies the filename to which to write xUnit XML results. |xunitName|, if outputting an xUnit XML file, the str value to use for the @@ -535,6 +538,17 @@ class XPCShellTests(object): if testdirs is None: testdirs = [] + if xunitFilename is not None or xunitName is not None: + if not isinstance(testsRootDir, str): + raise Exception("testsRootDir must be a str when outputting xUnit.") + + if not os.path.isabs(testsRootDir): + testsRootDir = os.path.abspath(testsRootDir) + + if not os.path.exists(testsRootDir): + raise Exception("testsRootDir path does not exists: %s" % + testsRootDir) + self.xpcshell = xpcshell self.xrePath = xrePath self.appPath = appPath @@ -598,7 +612,16 @@ class XPCShellTests(object): self.testCount += 1 - xunitResult = {"classname": "xpcshell", "name": test["name"]} + xunitResult = {"name": test["name"], "classname": "xpcshell"} + # The xUnit package is defined as the path component between the root + # dir and the test with path characters replaced with '.' (using Java + # class notation). + if testsRootDir is not None: + if test["here"].find(testsRootDir) != 0: + raise Exception("testsRootDir is not a parent path of %s" % + test["here"]) + relpath = test["here"][len(testsRootDir):].lstrip("/\\") + xunitResult["classname"] = relpath.replace("/", ".").replace("\\", ".") # Check for skipped tests if 'disabled' in test: @@ -775,6 +798,9 @@ class XPCShellOptions(OptionParser): self.add_option("--test-path", type="string", dest="testPath", default=None, help="single path and/or test filename to test") + self.add_option("--tests-root-dir", + type="string", dest="testsRootDir", default=None, + help="absolute path to directory where all tests are located. this is typically $(objdir)/_tests") self.add_option("--total-chunks", type = "int", dest = "totalChunks", default=1, help = "how many chunks to split the tests up into") diff --git a/testing/xpcshell/selftest.py b/testing/xpcshell/selftest.py index ef9b62bb4f52..174ee6651176 100644 --- a/testing/xpcshell/selftest.py +++ b/testing/xpcshell/selftest.py @@ -72,6 +72,7 @@ tail = manifest=self.manifest, mozInfo={}, shuffle=shuffle, + testsRootDir=self.tempdir, xunitFilename=xunitFilename), msg="""Tests should have %s, log: ======== diff --git a/toolkit/components/alerts/nsAlertsService.cpp b/toolkit/components/alerts/nsAlertsService.cpp index 2a0905e49817..e197dcd3f072 100644 --- a/toolkit/components/alerts/nsAlertsService.cpp +++ b/toolkit/components/alerts/nsAlertsService.cpp @@ -74,6 +74,33 @@ nsAlertsService::nsAlertsService() nsAlertsService::~nsAlertsService() {} +bool nsAlertsService::ShouldShowAlert() +{ + bool result = true; + +#ifdef XP_WIN + HMODULE shellDLL = ::LoadLibraryW(L"shell32.dll"); + if (!shellDLL) + return result; + + SHQueryUserNotificationStatePtr pSHQueryUserNotificationState = + (SHQueryUserNotificationStatePtr) ::GetProcAddress(shellDLL, "SHQueryUserNotificationState"); + + if (pSHQueryUserNotificationState) { + MOZ_QUERY_USER_NOTIFICATION_STATE qstate; + if (SUCCEEDED(pSHQueryUserNotificationState(&qstate))) { + if (qstate != QUNS_ACCEPTS_NOTIFICATIONS) { + result = false; + } + } + } + + ::FreeLibrary(shellDLL); +#endif + + return result; +} + NS_IMETHODIMP nsAlertsService::ShowAlertNotification(const nsAString & aImageUrl, const nsAString & aAlertTitle, const nsAString & aAlertText, bool aAlertTextClickable, const nsAString & aAlertCookie, @@ -110,6 +137,13 @@ NS_IMETHODIMP nsAlertsService::ShowAlertNotification(const nsAString & aImageUrl return rv; } + if (!ShouldShowAlert()) { + // Do not display the alert. Instead call alertfinished and get out. + if (aAlertListener) + aAlertListener->Observe(NULL, "alertfinished", PromiseFlatString(aAlertCookie).get()); + return NS_OK; + } + nsCOMPtr wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); nsCOMPtr newWindow; diff --git a/toolkit/components/alerts/nsAlertsService.h b/toolkit/components/alerts/nsAlertsService.h index 7153ac75cea2..c3895633db18 100644 --- a/toolkit/components/alerts/nsAlertsService.h +++ b/toolkit/components/alerts/nsAlertsService.h @@ -42,6 +42,23 @@ #include "nsIAlertsService.h" #include "nsCOMPtr.h" +#ifdef XP_WIN +typedef enum tagMOZ_QUERY_USER_NOTIFICATION_STATE { + QUNS_NOT_PRESENT = 1, + QUNS_BUSY = 2, + QUNS_RUNNING_D3D_FULL_SCREEN = 3, + QUNS_PRESENTATION_MODE = 4, + QUNS_ACCEPTS_NOTIFICATIONS = 5, + QUNS_QUIET_TIME = 6, + QUNS_IMMERSIVE = 7 +} MOZ_QUERY_USER_NOTIFICATION_STATE; + +extern "C" { +// This function is Windows Vista or later +typedef HRESULT (__stdcall *SHQueryUserNotificationStatePtr)(MOZ_QUERY_USER_NOTIFICATION_STATE *pquns); +} +#endif // defined(XP_WIN) + class nsAlertsService : public nsIAlertsService, public nsIAlertsProgressListener { @@ -54,6 +71,7 @@ public: virtual ~nsAlertsService(); protected: + bool ShouldShowAlert(); }; #endif /* nsAlertsService_h__ */ diff --git a/toolkit/components/alerts/nsIAlertsService.idl b/toolkit/components/alerts/nsIAlertsService.idl index 7318b0f4026a..8726438a9d16 100644 --- a/toolkit/components/alerts/nsIAlertsService.idl +++ b/toolkit/components/alerts/nsIAlertsService.idl @@ -70,6 +70,11 @@ interface nsIAlertsService : nsISupports * topic - "alertfinished" when the alert goes away * "alertclickcallback" when the text is clicked * data - the value of the cookie parameter passed to showAlertNotification. + * + * @note Depending on current circumstances (if the user's in a fullscreen + * application, for instance), the alert might not be displayed at all. + * In that case, if an alert listener is passed in it will receive the + * "alertfinished" notification immediately. */ void showAlertNotification(in AString imageUrl, in AString title, diff --git a/toolkit/components/maintenanceservice/Makefile.in b/toolkit/components/maintenanceservice/Makefile.in index 7b7c5d30dc9e..524e54542bb5 100644 --- a/toolkit/components/maintenanceservice/Makefile.in +++ b/toolkit/components/maintenanceservice/Makefile.in @@ -64,7 +64,6 @@ MOZ_GLUE_PROGRAM_LDFLAGS = LIBS += \ ../../mozapps/update/common/$(LIB_PREFIX)updatecommon.$(LIB_SUFFIX) \ - ../../mozapps/readstrings/$(LIB_PREFIX)readstrings.$(LIB_SUFFIX) \ $(NULL) USE_STATIC_LIBS = 1 @@ -98,5 +97,4 @@ endif # Pick up nsWindowsRestart.cpp LOCAL_INCLUDES += -I$(topsrcdir)/toolkit/xre \ -I$(topsrcdir)/toolkit/mozapps/update/common \ - -I$(topsrcdir)/toolkit/mozapps/readstrings \ $(NULL) diff --git a/toolkit/components/maintenanceservice/workmonitor.cpp b/toolkit/components/maintenanceservice/workmonitor.cpp index f5efb257d1d5..3ad4dab16843 100644 --- a/toolkit/components/maintenanceservice/workmonitor.cpp +++ b/toolkit/components/maintenanceservice/workmonitor.cpp @@ -56,6 +56,7 @@ #include "registrycertificates.h" #include "uachelper.h" #include "updatehelper.h" +#include "errors.h" // Wait 15 minutes for an update operation to run at most. // Updates usually take less than a minute so this seems like a @@ -66,16 +67,6 @@ BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode); BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath, LPCWSTR newFileName); -// The error codes start from 16000 since Windows system error -// codes only go up to 15999 -const int SERVICE_UPDATER_COULD_NOT_BE_STARTED = 16000; -const int SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 16001; -const int SERVICE_UPDATER_SIGN_ERROR = 16002; -const int SERVICE_UPDATER_COMPARE_ERROR = 16003; -const int SERVICE_UPDATER_IDENTITY_ERROR = 16004; -const int SERVICE_STILL_APPLYING_ON_SUCCESS = 16005; -const int SERVICE_STILL_APPLYING_ON_FAILURE = 16006; - /* * Read the update.status file and sets isApplying to true if * the status is set to applying diff --git a/toolkit/components/passwordmgr/test/test_bug_627616.html b/toolkit/components/passwordmgr/test/test_bug_627616.html index e4f5f0892f5b..eaa2d8982d98 100644 --- a/toolkit/components/passwordmgr/test/test_bug_627616.html +++ b/toolkit/components/passwordmgr/test/test_bug_627616.html @@ -49,8 +49,8 @@ "realm=mochirealm&" + extra || ""); xhr.onloadend = function() { - is(xhr.status, expectedStatus); - is(xhr.statusText, expectedText); + is(xhr.status, expectedStatus, "xhr.status"); + is(xhr.statusText, expectedText, "xhr.statusText"); runNextTest(); } return xhr; diff --git a/toolkit/components/places/History.cpp b/toolkit/components/places/History.cpp index e30c5cc96a8a..6072a6cf572f 100644 --- a/toolkit/components/places/History.cpp +++ b/toolkit/components/places/History.cpp @@ -318,7 +318,7 @@ GetIntFromJSObject(JSContext* aCtx, nsresult GetJSObjectFromArray(JSContext* aCtx, JSObject* aArray, - jsuint aIndex, + uint32_t aIndex, JSObject** _rooter) { NS_PRECONDITION(JS_IsArrayObject(aCtx, aArray), @@ -2104,7 +2104,7 @@ History::UpdatePlaces(const jsval& aPlaceInfos, NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED); NS_ENSURE_TRUE(!JSVAL_IS_PRIMITIVE(aPlaceInfos), NS_ERROR_INVALID_ARG); - jsuint infosLength = 1; + uint32_t infosLength = 1; JSObject* infos; if (JS_IsArrayObject(aCtx, JSVAL_TO_OBJECT(aPlaceInfos))) { infos = JSVAL_TO_OBJECT(aPlaceInfos); @@ -2122,7 +2122,7 @@ History::UpdatePlaces(const jsval& aPlaceInfos, } nsTArray visitData; - for (jsuint i = 0; i < infosLength; i++) { + for (uint32_t i = 0; i < infosLength; i++) { JSObject* info; nsresult rv = GetJSObjectFromArray(aCtx, infos, i, &info); NS_ENSURE_SUCCESS(rv, rv); @@ -2168,7 +2168,7 @@ History::UpdatePlaces(const jsval& aPlaceInfos, } NS_ENSURE_ARG(visits); - jsuint visitsLength = 0; + uint32_t visitsLength = 0; if (visits) { (void)JS_GetArrayLength(aCtx, visits, &visitsLength); } @@ -2176,7 +2176,7 @@ History::UpdatePlaces(const jsval& aPlaceInfos, // Check each visit, and build our array of VisitData objects. visitData.SetCapacity(visitData.Length() + visitsLength); - for (jsuint j = 0; j < visitsLength; j++) { + for (uint32_t j = 0; j < visitsLength; j++) { JSObject* visit; rv = GetJSObjectFromArray(aCtx, visits, j, &visit); NS_ENSURE_SUCCESS(rv, rv); diff --git a/toolkit/components/places/tests/cpp/mock_Link.h b/toolkit/components/places/tests/cpp/mock_Link.h index 615ae37c37ce..146b8c682910 100644 --- a/toolkit/components/places/tests/cpp/mock_Link.h +++ b/toolkit/components/places/tests/cpp/mock_Link.h @@ -74,6 +74,11 @@ public: mDeathGrip = 0; } + virtual size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const + { + return 0; // the value shouldn't matter + } + ~mock_Link() { // Run the next test if we are supposed to. if (mRunNextTest) { @@ -135,6 +140,13 @@ Link::GetURI() const return nsnull; // suppress compiler warning } +size_t +Link::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const +{ + NS_NOTREACHED("Unexpected call to Link::SizeOfExcludingThis"); + return 0; +} + } // namespace dom } // namespace mozilla diff --git a/toolkit/components/telemetry/Telemetry.cpp b/toolkit/components/telemetry/Telemetry.cpp index 46f97c143876..2b0ab6331fe4 100644 --- a/toolkit/components/telemetry/Telemetry.cpp +++ b/toolkit/components/telemetry/Telemetry.cpp @@ -162,13 +162,11 @@ private: typedef AutoHashtable AddonHistogramMapType; typedef nsBaseHashtableET AddonEntryType; typedef AutoHashtable AddonMapType; - struct AddonEnumeratorArgs { - JSContext *cx; - JSObject *obj; - }; static bool AddonHistogramReflector(AddonHistogramEntryType *entry, JSContext *cx, JSObject *obj); static bool AddonReflector(AddonEntryType *entry, JSContext *cx, JSObject *obj); + static bool CreateHistogramForAddon(const nsACString &name, + AddonHistogramInfo &info); AddonMapType mAddonMap; // This is used for speedy string->Telemetry::ID conversions @@ -205,6 +203,7 @@ struct TelemetryHistogram { // errors. #define HISTOGRAM(id, min, max, bucket_count, histogram_type, b) \ PR_STATIC_ASSERT(nsITelemetry::HISTOGRAM_ ## histogram_type == nsITelemetry::HISTOGRAM_BOOLEAN || \ + nsITelemetry::HISTOGRAM_ ## histogram_type == nsITelemetry::HISTOGRAM_FLAG || \ (min < max && bucket_count > 2 && min >= 1)); #include "TelemetryHistograms.h" @@ -235,6 +234,8 @@ TelemetryHistogramType(Histogram *h, PRUint32 *result) case Histogram::BOOLEAN_HISTOGRAM: *result = nsITelemetry::HISTOGRAM_BOOLEAN; break; + case Histogram::FLAG_HISTOGRAM: + *result = nsITelemetry::HISTOGRAM_FLAG; default: return false; } @@ -245,7 +246,8 @@ nsresult HistogramGet(const char *name, PRUint32 min, PRUint32 max, PRUint32 bucketCount, PRUint32 histogramType, Histogram **result) { - if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN) { + if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN + && histogramType != nsITelemetry::HISTOGRAM_FLAG) { // Sanity checks for histogram parameters. if (min >= max) return NS_ERROR_ILLEGAL_VALUE; @@ -267,6 +269,9 @@ HistogramGet(const char *name, PRUint32 min, PRUint32 max, PRUint32 bucketCount, case nsITelemetry::HISTOGRAM_BOOLEAN: *result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); break; + case nsITelemetry::HISTOGRAM_FLAG: + *result = FlagHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); + break; default: return NS_ERROR_INVALID_ARG; } @@ -735,25 +740,14 @@ TelemetryImpl::GetAddonHistogram(const nsACString &id, const nsACString &name, } AddonHistogramInfo &info = histogramEntry->mData; - Histogram *h; - if (info.h) { - h = info.h; - } else { + if (!info.h) { nsCAutoString actualName; AddonHistogramName(id, name, actualName); - nsresult rv = HistogramGet(PromiseFlatCString(actualName).get(), - info.min, info.max, info.bucketCount, - info.histogramType, &h); - if (NS_FAILED(rv)) { - return rv; + if (!CreateHistogramForAddon(actualName, info)) { + return NS_ERROR_FAILURE; } - // Don't let this histogram be reported via the normal means - // (e.g. Telemetry.registeredHistograms); we'll make it available in - // other ways. - h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); - info.h = h; } - return WrapAndReturnHistogram(h, cx, ret); + return WrapAndReturnHistogram(info.h, cx, ret); } NS_IMETHODIMP @@ -780,6 +774,16 @@ TelemetryImpl::GetHistogramSnapshots(JSContext *cx, jsval *ret) return NS_ERROR_FAILURE; *ret = OBJECT_TO_JSVAL(root_obj); + // Ensure that all the HISTOGRAM_FLAG histograms have been created, so + // that their values are snapshotted. + for (size_t i = 0; i < Telemetry::HistogramCount; ++i) { + if (gHistograms[i].histogramType == nsITelemetry::HISTOGRAM_FLAG) { + Histogram *h; + DebugOnly rv = GetHistogramByEnumId(Telemetry::ID(i), &h); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + }; + StatisticsRecorder::Histograms hs; StatisticsRecorder::GetHistograms(&hs); @@ -821,13 +825,40 @@ TelemetryImpl::GetHistogramSnapshots(JSContext *cx, jsval *ret) return NS_OK; } +bool +TelemetryImpl::CreateHistogramForAddon(const nsACString &name, + AddonHistogramInfo &info) +{ + Histogram *h; + nsresult rv = HistogramGet(PromiseFlatCString(name).get(), + info.min, info.max, info.bucketCount, + info.histogramType, &h); + if (NS_FAILED(rv)) { + return false; + } + // Don't let this histogram be reported via the normal means + // (e.g. Telemetry.registeredHistograms); we'll make it available in + // other ways. + h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); + info.h = h; + return true; +} + bool TelemetryImpl::AddonHistogramReflector(AddonHistogramEntryType *entry, JSContext *cx, JSObject *obj) { + AddonHistogramInfo &info = entry->mData; + // Never even accessed the histogram. - if (!entry->mData.h) { - return true; + if (!info.h) { + // Have to force creation of HISTOGRAM_FLAG histograms. + if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG) + return true; + + if (!CreateHistogramForAddon(entry->GetKey(), info)) { + return false; + } } JSObject *snapshot = JS_NewObject(cx, NULL, NULL, NULL); @@ -836,7 +867,7 @@ TelemetryImpl::AddonHistogramReflector(AddonHistogramEntryType *entry, return true; } JS::AutoObjectRooter r(cx, snapshot); - switch (ReflectHistogramSnapshot(cx, snapshot, entry->mData.h)) { + switch (ReflectHistogramSnapshot(cx, snapshot, info.h)) { case REFLECT_FAILURE: case REFLECT_CORRUPT: return false; diff --git a/toolkit/components/telemetry/TelemetryHistograms.h b/toolkit/components/telemetry/TelemetryHistograms.h index 736493a4b480..3b025984ae09 100644 --- a/toolkit/components/telemetry/TelemetryHistograms.h +++ b/toolkit/components/telemetry/TelemetryHistograms.h @@ -51,6 +51,8 @@ /* Convenience macro for BOOLEAN histograms. */ #define HISTOGRAM_BOOLEAN(id, message) HISTOGRAM(id, 0, 1, 2, BOOLEAN, message) +/* Likewise for FLAG histograms. */ +#define HISTOGRAM_FLAG(id, message) HISTOGRAM(id, 0, 1, 2, FLAG, message) /** * a11y telemetry @@ -331,7 +333,7 @@ HISTOGRAM(PLACES_FRECENCY_CALC_TIME_MS, 1, 100, 10, EXPONENTIAL, "PLACES: Time t /** * Updater telemetry. */ -HISTOGRAM(UPDATE_STATUS, 1, 16006, 27, LINEAR, "Updater: the status of the latest update performed") +HISTOGRAM(UPDATER_STATUS_CODES, 1, 50, 51, LINEAR, "Updater: the status of the latest update performed") /** * Thunderbird-specific telemetry. @@ -412,5 +414,7 @@ HISTOGRAM(RANGE_CHECKSUM_ERRORS, 1, 3000, 10, EXPONENTIAL, "Number of histograms HISTOGRAM(BUCKET_ORDER_ERRORS, 1, 3000, 10, EXPONENTIAL, "Number of histograms with bucket order errors") HISTOGRAM(TOTAL_COUNT_HIGH_ERRORS, 1, 3000, 10, EXPONENTIAL, "Number of histograms with total count high errors") HISTOGRAM(TOTAL_COUNT_LOW_ERRORS, 1, 3000, 10, EXPONENTIAL, "Number of histograms with total count low errors") +HISTOGRAM_FLAG(TELEMETRY_TEST_FLAG, "a testing histogram; not meant to be touched") #undef HISTOGRAM_BOOLEAN +#undef HISTOGRAM_FLAG diff --git a/toolkit/components/telemetry/TelemetryPing.js b/toolkit/components/telemetry/TelemetryPing.js index a51df5a7c316..6ff1661c92d2 100644 --- a/toolkit/components/telemetry/TelemetryPing.js +++ b/toolkit/components/telemetry/TelemetryPing.js @@ -239,8 +239,7 @@ TelemetryPing.prototype = { return retgram; }, - getHistograms: function getHistograms() { - let hls = Telemetry.histogramSnapshots; + getHistograms: function getHistograms(hls) { let info = Telemetry.registeredHistograms; let ret = {}; @@ -294,7 +293,8 @@ TelemetryPing.prototype = { appName: ai.name, appBuildID: ai.appBuildID, appUpdateChannel: getUpdateChannel(), - platformBuildID: ai.platformBuildID + platformBuildID: ai.platformBuildID, + locale: getLocale() }; // sysinfo fields are not always available, get what we can. diff --git a/toolkit/components/telemetry/nsITelemetry.idl b/toolkit/components/telemetry/nsITelemetry.idl index 6cb6aa7c3316..9714cab73f5c 100644 --- a/toolkit/components/telemetry/nsITelemetry.idl +++ b/toolkit/components/telemetry/nsITelemetry.idl @@ -78,10 +78,12 @@ interface nsITelemetry : nsISupports * HISTOGRAM_EXPONENTIAL - buckets increase exponentially * HISTOGRAM_LINEAR - buckets increase linearly * HISTOGRAM_BOOLEAN - For storing 0/1 values + * HISTOGRAM_FLAG - For storing a single value; its count is always == 1. */ const unsigned long HISTOGRAM_EXPONENTIAL = 0; const unsigned long HISTOGRAM_LINEAR = 1; const unsigned long HISTOGRAM_BOOLEAN = 2; + const unsigned long HISTOGRAM_FLAG = 3; /* * An object containing a snapshot from all of the currently registered histograms. diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js b/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js index 5e2c5f6d055b..b743664aa58d 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js @@ -101,6 +101,9 @@ function checkHistograms(request, response) { do_check_eq(payload.info[f], expected_info[f]); } + do_check_true("appUpdateChannel" in payload.info); + do_check_true("locale" in payload.info); + try { // If we've not got nsIGfxInfoDebug, then this will throw and stop us doing // this test. @@ -118,6 +121,7 @@ function checkHistograms(request, response) { const TELEMETRY_PING = "TELEMETRY_PING"; const TELEMETRY_SUCCESS = "TELEMETRY_SUCCESS"; + const TELEMETRY_TEST_FLAG = "TELEMETRY_TEST_FLAG"; do_check_true(TELEMETRY_PING in payload.histograms); let rh = Telemetry.registeredHistograms; for (let name in rh) { @@ -128,6 +132,17 @@ function checkHistograms(request, response) { do_check_false(IGNORE_HISTOGRAM in payload.histograms); do_check_false(IGNORE_CLONED_HISTOGRAM in payload.histograms); + // Flag histograms should automagically spring to life. + const expected_flag = { + range: [1, 2], + bucket_count: 3, + histogram_type: 3, + values: {0:1, 1:0}, + sum: 1 + }; + let flag = payload.histograms[TELEMETRY_TEST_FLAG]; + do_check_eq(uneval(flag), uneval(expected_flag)); + // There should be one successful report from the previous telemetry ping. const expected_tc = { range: [1, 2], diff --git a/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js b/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js index 844f0471d43e..918462198729 100644 --- a/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js +++ b/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js @@ -85,6 +85,32 @@ function test_boolean_histogram() do_check_eq(s.counts[0], 2); } +function test_flag_histogram() +{ + var h = Telemetry.newHistogram("test::flag histogram", 130, 4, 5, Telemetry.HISTOGRAM_FLAG); + var r = h.snapshot().ranges; + // Flag histograms ignore numeric parameters. + do_check_eq(uneval(r), uneval([0, 1, 2])) + // Should already have a 0 counted. + var c = h.snapshot().counts; + var s = h.snapshot().sum; + do_check_eq(uneval(c), uneval([1, 0, 0])); + do_check_eq(s, 1); + // Should switch counts. + h.add(2); + var c2 = h.snapshot().counts; + var s2 = h.snapshot().sum; + do_check_eq(uneval(c2), uneval([0, 1, 0])); + do_check_eq(s, 1); + // Should only switch counts once. + h.add(3); + var c3 = h.snapshot().counts; + var s3 = h.snapshot().sum; + do_check_eq(uneval(c3), uneval([0, 1, 0])); + do_check_eq(s3, 1); + do_check_eq(h.snapshot().histogram_type, Telemetry.FLAG_HISTOGRAM); +} + function test_getHistogramById() { try { Telemetry.getHistogramById("nonexistent"); @@ -148,10 +174,19 @@ function test_addons() { expect_success(function () register(extra_addon, name1, 0, 1, 2, Telemetry.HISTOGRAM_BOOLEAN)); + // Check that we can register flag histograms. + var flag_addon = "testing-flag-addon"; + var flag_histogram = "flag-histogram"; + expect_success(function() + register(flag_addon, flag_histogram, 0, 1, 2, Telemetry.HISTOGRAM_FLAG)) + expect_success(function() + register(flag_addon, name2, 2, 4, 4, Telemetry.HISTOGRAM_LINEAR)); + // Check that we reflect registered addons and histograms. snapshots = Telemetry.addonHistogramSnapshots; do_check_true(addon_id in snapshots) do_check_true(extra_addon in snapshots); + do_check_true(flag_addon in snapshots); // Check that we have data for our created histograms. do_check_true(name1 in snapshots[addon_id]); @@ -168,6 +203,10 @@ function test_addons() { // Even though we've registered it, it shouldn't show up until data is added to it. do_check_false(name1 in snapshots[extra_addon]); + // Flag histograms should show up automagically. + do_check_true(flag_histogram in snapshots[flag_addon]); + do_check_false(name2 in snapshots[flag_addon]); + // Check that we can remove addon histograms. Telemetry.unregisterAddonHistograms(addon_id); snapshots = Telemetry.addonHistogramSnapshots; diff --git a/toolkit/content/license.html b/toolkit/content/license.html index 9c367671cdab..10dc6974b638 100644 --- a/toolkit/content/license.html +++ b/toolkit/content/license.html @@ -117,7 +117,7 @@


    -

    Mozilla Public License 2.0

    +

    Mozilla Public License 2.0

    1. Definitions

    diff --git a/toolkit/content/tests/chrome/test_autocomplete_delayOnPaste.xul b/toolkit/content/tests/chrome/test_autocomplete_delayOnPaste.xul index c061be632ae7..768230a1ff47 100644 --- a/toolkit/content/tests/chrome/test_autocomplete_delayOnPaste.xul +++ b/toolkit/content/tests/chrome/test_autocomplete_delayOnPaste.xul @@ -77,8 +77,13 @@ let autoCompleteSimple = { stopSearch: function () {} }; +SimpleTest.waitForExplicitFinish(); + +// XPFE AutoComplete needs to register early. +autoCompleteSimple.registerFactory(); + let gACTimer; -let gAutoComplete = $("autocomplete"); +let gAutoComplete; function searchComplete() { is(gAutoComplete.value, "result", "Value should be autocompleted now"); @@ -90,9 +95,8 @@ function searchComplete() { } function runTest() { - SimpleTest.waitForExplicitFinish(); + gAutoComplete = $("autocomplete"); - autoCompleteSimple.registerFactory(); const SEARCH_STRING = "res"; function cbCallback() { diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js index ff57058f8746..3c83b4b9174b 100644 --- a/toolkit/mozapps/extensions/content/extensions.js +++ b/toolkit/mozapps/extensions/content/extensions.js @@ -2882,12 +2882,9 @@ var gDetailView = { for (var i = 0; i < settings.length; i++) { var setting = settings[i]; - // Remove setting description, for replacement later var desc = stripTextNodes(setting).trim(); - if (setting.hasAttribute("desc")) { - desc = setting.getAttribute("desc").trim(); - setting.removeAttribute("desc"); - } + if (!setting.hasAttribute("desc")) + setting.setAttribute("desc", desc); var type = setting.getAttribute("type"); if (type == "file" || type == "directory") @@ -2899,23 +2896,10 @@ var gDetailView = { setting.setAttribute("first-row", true); firstSetting = setting; } - - // Add a new row containing the description - if (desc) { - var row = document.createElement("row"); - if (!visible) { - row.setAttribute("unsupported", "true"); - } - var label = document.createElement("label"); - label.className = "preferences-description"; - label.textContent = desc; - row.appendChild(label); - rows.appendChild(row); - } } - // Ensure the page has loaded and force the XBL bindings to be synchronously applied, - // then notify observers. + // Ensure the page has loaded and force the XBL bindings to be synchronously applied, + // then notify observers. if (gViewController.viewPort.selectedPanel.hasAttribute("loading")) { gDetailView.node.addEventListener("ViewChanged", function viewChangedEventListener() { gDetailView.node.removeEventListener("ViewChanged", viewChangedEventListener, false); diff --git a/toolkit/mozapps/extensions/content/setting.xml b/toolkit/mozapps/extensions/content/setting.xml index 2c19075c3228..a0af4a601e8e 100644 --- a/toolkit/mozapps/extensions/content/setting.xml +++ b/toolkit/mozapps/extensions/content/setting.xml @@ -51,7 +51,7 @@ - + @@ -160,7 +160,6 @@ false document.getAnonymousElementByAttribute(this, "anonid", "input"); - this.parentNode.localName == "settings" ? this.parentNode : null; @@ -169,14 +168,14 @@ - - - - - + + + + + - - + + @@ -204,19 +203,7 @@ - - - - - - - - - - - - - + @@ -234,9 +221,6 @@ ]]> - - - @@ -263,20 +247,20 @@ ]]> - - - - - - + + + + + - - + + @@ -297,21 +281,18 @@ ]]> - - - - - - - - + + + + + - + @@ -319,14 +300,15 @@ - - - - - + + + + + - - + + @@ -350,20 +332,18 @@ ]]> - - - - - - - + + + + + - + @@ -394,15 +374,15 @@ - - - - - + + + + + - - - + + + @@ -476,14 +456,14 @@ - - - - - + + + + + - - + + diff --git a/toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1/options.xul b/toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1/options.xul index febf7bbc8a00..095d3bcef20d 100644 --- a/toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1/options.xul +++ b/toolkit/mozapps/extensions/test/browser/addons/browser_inlinesettings1/options.xul @@ -1,6 +1,6 @@ - + diff --git a/toolkit/mozapps/extensions/test/browser/browser_inlinesettings.js b/toolkit/mozapps/extensions/test/browser/browser_inlinesettings.js index 1e331027b156..849c11cc7c13 100644 --- a/toolkit/mozapps/extensions/test/browser/browser_inlinesettings.js +++ b/toolkit/mozapps/extensions/test/browser/browser_inlinesettings.js @@ -21,7 +21,7 @@ var observer = { // Test if the binding has applied before the observers are notified. We test the second setting here, // because the code operates on the first setting and we want to check it applies to all. var setting = aSubject.querySelector("rows > setting[first-row] ~ setting"); - var input = gManagerWindow.document.getAnonymousElementByAttribute(setting, "class", "setting-label"); + var input = gManagerWindow.document.getAnonymousElementByAttribute(setting, "class", "preferences-title"); isnot(input, null, "XBL binding should be applied"); // Add some extra height to the scrolling pane to ensure that it needs to scroll when appropriate. @@ -174,6 +174,7 @@ add_test(function() { Services.prefs.setBoolPref("extensions.inlinesettings1.bool", false); var input = gManagerWindow.document.getAnonymousElementByAttribute(settings[0], "anonid", "input"); isnot(input.checked, true, "Checkbox should have initial value"); + is(input.label, "Check box label", "Checkbox should be labelled"); EventUtils.synthesizeMouseAtCenter(input, { clickCount: 1 }, gManagerWindow); is(input.checked, true, "Checkbox should have updated value"); is(Services.prefs.getBoolPref("extensions.inlinesettings1.bool"), true, "Bool pref should have been updated"); @@ -381,45 +382,26 @@ add_test(function() { is(node.nodeName, "setting", "Should be a setting node"); ok(node.hasAttribute("first-row"), "First visible row should have first-row attribute"); var description = gManagerWindow.document.getAnonymousElementByAttribute(node, "class", "preferences-description"); - is(description.textContent.trim(), "", "Description node should be empty"); - - node = node.nextSibling; - is(node.nodeName, "row", "Setting should be followed by a row node"); - is_element_visible(node, "Description should be visible"); - is(node.textContent, "Description Attribute", "Description should be in this row"); + is(description.textContent, "Description Attribute", "Description node should contain description"); node = settings[2]; is(node.nodeName, "setting", "Should be a setting node"); ok(!node.hasAttribute("first-row"), "Not the first row"); description = gManagerWindow.document.getAnonymousElementByAttribute(node, "class", "preferences-description"); - is(description.textContent.trim(), "", "Description node should be empty"); - - node = node.nextSibling; - is(node.nodeName, "row", "Setting should be followed by a row node"); - is_element_visible(node, "Description should be visible"); - is(node.textContent, "Description Text Node", "Description should be in this row"); + is(description.textContent, "Description Text Node", "Description node should contain description"); node = settings[3]; is(node.nodeName, "setting", "Should be a setting node"); ok(!node.hasAttribute("first-row"), "Not the first row"); description = gManagerWindow.document.getAnonymousElementByAttribute(node, "class", "preferences-description"); - is(description.textContent.trim(), "", "Description node should be empty"); + is(description.textContent, "This is a test, all this text should be visible", "Description node should contain description"); var button = node.firstElementChild; isnot(button, null, "There should be a button"); - node = node.nextSibling; - is(node.nodeName, "row", "Setting should be followed by a row node"); - is_element_visible(node, "Description should be visible"); - is(node.textContent.trim(), "This is a test, all this text should be visible", "Description should be in this row"); - node = settings[4]; is_element_hidden(node, "Unsupported settings should not be visible"); ok(!node.hasAttribute("first-row"), "Hidden row is not the first row"); - node = node.nextSibling; - is(node.nodeName, "row", "Setting should be followed by a row node"); - is_element_hidden(node, "Descriptions of unsupported settings should not be visible"); - var button = gManagerWindow.document.getElementById("detail-prefs-btn"); is_element_hidden(button, "Preferences button should not be visible"); diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_bug638292.js b/toolkit/mozapps/extensions/test/xpinstall/browser_bug638292.js index 37d9f63a7ccb..d5d590a3ff72 100644 --- a/toolkit/mozapps/extensions/test/xpinstall/browser_bug638292.js +++ b/toolkit/mozapps/extensions/test/xpinstall/browser_bug638292.js @@ -13,9 +13,13 @@ function test() { function check_load(aCallback) { gBrowser.addEventListener("load", function(aEvent) { - // SeaMonkey needs to deal with intermediate "about:blank" document(s). - if (!aEvent.target.location) { - info("Ignoring about:blank load. (Expected (a few times) on SeaMonkey only.)"); + if (!gBrowser.browsers[2] || + aEvent.target != gBrowser.browsers[2].contentDocument) { + // SeaMonkey tabbrowser needs to deal with additional loads. + if (navigator.userAgent.match(/ SeaMonkey\//)) + info("Ignoring unrelated load on SeaMonkey. (Expected 2-3 times.)"); + else + ok(false, "Ignoring unrelated load on Firefox. (Should never happen!)"); return; } diff --git a/toolkit/mozapps/readstrings/Makefile.in b/toolkit/mozapps/readstrings/Makefile.in deleted file mode 100644 index 8a69fb33cd85..000000000000 --- a/toolkit/mozapps/readstrings/Makefile.in +++ /dev/null @@ -1,54 +0,0 @@ -# ***** 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 readstrings -# -# The Initial Developer of the Original Code is The Mozilla Foundation. -# -# Portions created by the Initial Developer are Copyright (C) 2009 -# the Mozilla Foundation . All Rights Reserved. -# -# Contributor(s): -# Brad Lassey (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 ***** - -DEPTH = ../../.. -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ - -include $(DEPTH)/config/autoconf.mk - -MODULE = readstrings -LIBRARY_NAME = readstrings -FORCE_STATIC_LIB = 1 -export NO_SHUNT = 1 -USE_STATIC_LIBS = 1 - -CPPSRCS = readstrings.cpp -EXPORTS = readstrings.h - -include $(topsrcdir)/config/rules.mk diff --git a/toolkit/mozapps/update/Makefile.in b/toolkit/mozapps/update/Makefile.in index 752647bbad05..432046a9b40a 100644 --- a/toolkit/mozapps/update/Makefile.in +++ b/toolkit/mozapps/update/Makefile.in @@ -49,8 +49,6 @@ XPIDLSRCS = nsIUpdateTimerManager.idl EXTRA_PP_COMPONENTS = nsUpdateTimerManager.js nsUpdateTimerManager.manifest ifdef MOZ_UPDATER -DIRS = ../readstrings - ifneq ($(OS_TARGET),Android) DIRS += common DIRS += updater @@ -70,7 +68,6 @@ else # used for other things. We need to build update/common # which the maintenance service uses. ifdef MOZ_MAINTENANCE_SERVICE -DIRS = ../readstrings ifneq ($(OS_TARGET),Android) DIRS += common endif diff --git a/toolkit/mozapps/update/common/Makefile.in b/toolkit/mozapps/update/common/Makefile.in index d7b91bbc5aa8..242b7a5ec9d9 100644 --- a/toolkit/mozapps/update/common/Makefile.in +++ b/toolkit/mozapps/update/common/Makefile.in @@ -52,10 +52,12 @@ endif CPPSRCS = \ updatelogging.cpp \ + readstrings.cpp \ $(NULL) EXPORTS = updatelogging.h \ updatedefines.h \ + readstrings.h \ $(NULL) ifeq ($(MOZ_WIDGET_TOOLKIT),windows) diff --git a/toolkit/mozapps/readstrings/errors.h b/toolkit/mozapps/update/common/errors.h similarity index 82% rename from toolkit/mozapps/readstrings/errors.h rename to toolkit/mozapps/update/common/errors.h index 7a156b42053d..9fc8e7ed6642 100644 --- a/toolkit/mozapps/readstrings/errors.h +++ b/toolkit/mozapps/update/common/errors.h @@ -40,8 +40,12 @@ #define Errors_h__ #define OK 0 -//#define MEM_ERROR 1 // Replaced with errors 10-16 (inclusive) + +// Old unused error codes: +// #define MEM_ERROR 1 // Replaced with errors 10-16 (inclusive) // #define IO_ERROR 2 // Use READ_ERROR or WRITE_ERROR instead + +// Error codes 3-16 are for general update problems. #define USAGE_ERROR 3 #define CRC_ERROR 4 #define PARSE_ERROR 5 @@ -56,6 +60,9 @@ #define UPDATER_QUOTED_PATH_MEM_ERROR 14 #define BAD_ACTION_ERROR 15 #define STRING_CONVERSION_ERROR 16 + +// Error codes 17-23 are related to security tasks for MAR +// signing and MAR protection. #define CERT_LOAD_ERROR 17 #define CERT_HANDLING_ERROR 18 #define CERT_VERIFY_ERROR 19 @@ -64,6 +71,16 @@ #define MAR_CHANNEL_MISMATCH_ERROR 22 #define VERSION_DOWNGRADE_ERROR 23 +// Error codes 24-30 are related to the maintenance service +// and so are Windows only +#define SERVICE_UPDATER_COULD_NOT_BE_STARTED 24 +#define SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS 25 +#define SERVICE_UPDATER_SIGN_ERROR 26 +#define SERVICE_UPDATER_COMPARE_ERROR 27 +#define SERVICE_UPDATER_IDENTITY_ERROR 28 +#define SERVICE_STILL_APPLYING_ON_SUCCESS 29 +#define SERVICE_STILL_APPLYING_ON_FAILURE 30 + // The following error codes are only used by updater.exe // when a fallback key exists and XPCShell tests are being run. #define FALLBACKKEY_UNKNOWN_ERROR 100 diff --git a/toolkit/mozapps/readstrings/readstrings.cpp b/toolkit/mozapps/update/common/readstrings.cpp similarity index 100% rename from toolkit/mozapps/readstrings/readstrings.cpp rename to toolkit/mozapps/update/common/readstrings.cpp diff --git a/toolkit/mozapps/readstrings/readstrings.h b/toolkit/mozapps/update/common/readstrings.h similarity index 100% rename from toolkit/mozapps/readstrings/readstrings.h rename to toolkit/mozapps/update/common/readstrings.h diff --git a/toolkit/mozapps/update/nsUpdateService.js b/toolkit/mozapps/update/nsUpdateService.js index deb837bb7cce..bd8b5fa2d9e6 100644 --- a/toolkit/mozapps/update/nsUpdateService.js +++ b/toolkit/mozapps/update/nsUpdateService.js @@ -143,22 +143,13 @@ const UNEXPECTED_ERROR = 8; const ELEVATION_CANCELED = 9; // Windows service specific errors -const SERVICE_UPDATER_COULD_NOT_BE_STARTED = 16000; -const SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 16001; -const SERVICE_UPDATER_SIGN_ERROR = 16002; -const SERVICE_UPDATER_COMPARE_ERROR = 16003; -const SERVICE_UPDATER_IDENTITY_ERROR = 16004; -const SERVICE_STILL_APPLYING_ON_SUCCESS = 16005; -const SERVICE_STILL_APPLYING_ON_FAILURE = 16006; - -// Updater MAR security errors -const CERT_LOAD_ERROR = 17; -const CERT_HANDLING_ERROR = 18; -const CERT_VERIFY_ERROR = 19; -const ARCHIVE_NOT_OPEN = 20; -const COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR = 21; -const MAR_CHANNEL_MISMATCH_ERROR = 22; -const VERSION_DOWNGRADE_ERROR = 23; +const SERVICE_UPDATER_COULD_NOT_BE_STARTED = 24; +const SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 25; +const SERVICE_UPDATER_SIGN_ERROR = 26; +const SERVICE_UPDATER_COMPARE_ERROR = 27; +const SERVICE_UPDATER_IDENTITY_ERROR = 28; +const SERVICE_STILL_APPLYING_ON_SUCCESS = 29; +const SERVICE_STILL_APPLYING_ON_FAILURE = 30; const CERT_ATTR_CHECK_FAILED_NO_UPDATE = 100; const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101; @@ -1424,14 +1415,7 @@ UpdateService.prototype = { update.errorCode == SERVICE_UPDATER_COMPARE_ERROR || update.errorCode == SERVICE_UPDATER_IDENTITY_ERROR || update.errorCode == SERVICE_STILL_APPLYING_ON_SUCCESS || - update.errorCode == SERVICE_STILL_APPLYING_ON_FAILURE || - update.errorCode == CERT_LOAD_ERROR || - update.errorCode == CERT_HANDLING_ERROR || - update.errorCode == CERT_VERIFY_ERROR || - update.errorCode == ARCHIVE_NOT_OPEN || - update.errorCode == COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR || - update.errorCode == MAR_CHANNEL_MISMATCH_ERROR || - update.errorCode == VERSION_DOWNGRADE_ERROR) { + update.errorCode == SERVICE_STILL_APPLYING_ON_FAILURE) { var failCount = getPref("getIntPref", PREF_APP_UPDATE_SERVICE_ERRORS, 0); var maxFail = getPref("getIntPref", diff --git a/toolkit/mozapps/update/test/Makefile.in b/toolkit/mozapps/update/test/Makefile.in index 6fefd7761215..17ceb9499ecd 100644 --- a/toolkit/mozapps/update/test/Makefile.in +++ b/toolkit/mozapps/update/test/Makefile.in @@ -84,7 +84,7 @@ LOCAL_INCLUDES += \ MOZ_WINCONSOLE = 1 LIBS += \ - ../../readstrings/$(LIB_PREFIX)readstrings.$(LIB_SUFFIX) \ + ../common/$(LIB_PREFIX)updatecommon.$(LIB_SUFFIX) \ $(NULL) ifeq ($(OS_ARCH),WINNT) diff --git a/toolkit/mozapps/update/test/TestAUSHelper.cpp b/toolkit/mozapps/update/test/TestAUSHelper.cpp index 5a0983388758..e6405a777cc8 100644 --- a/toolkit/mozapps/update/test/TestAUSHelper.cpp +++ b/toolkit/mozapps/update/test/TestAUSHelper.cpp @@ -31,6 +31,9 @@ # define NS_ttoi _wtoi # define NS_tstat _wstat # define LOG_S "%S" + +#include "../common/updatehelper.h" + #else # include # define NS_main main @@ -153,214 +156,6 @@ VerifyCertificateTrustForFile(LPCWSTR filePath) return WinVerifyTrust(NULL, &policyGUID, &trustData); } -/** - * Waits for a service to enter a stopped state. - * This function does not stop the service, it just blocks until the service - * is stopped. - * - * @param serviceName The service to wait for. - * @param maxWaitSeconds The maximum number of seconds to wait - * @return state of the service after a timeout or when stopped. - * A value of 255 is returned for an error. Typical values are: - * SERVICE_STOPPED 0x00000001 - * SERVICE_START_PENDING 0x00000002 - * SERVICE_STOP_PENDING 0x00000003 - * SERVICE_RUNNING 0x00000004 - * SERVICE_CONTINUE_PENDING 0x00000005 - * SERVICE_PAUSE_PENDING 0x00000006 - * SERVICE_PAUSED 0x00000007 - * last status not set 0x000000CF - * Could no query status 0x000000DF - * Could not open service, access denied 0x000000EB - * Could not open service, invalid handle 0x000000EC - * Could not open service, invalid name 0x000000ED - * Could not open service, does not exist 0x000000EE - * Could not open service, other error 0x000000EF - * Could not open SCM, access denied 0x000000FD - * Could not open SCM, database does not exist 0x000000FE; - * Could not open SCM, other error 0x000000FF; - * Note: The strange choice of error codes above SERVICE_PAUSED are chosen - * in case Windows comes out with other service stats higher than 7, they - * would likely call it 8 and above. JS code that uses this in TestAUSHelper - * only handles values up to 255 so that's why we don't use GetLastError - * directly. - */ -DWORD -WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds) -{ - // 0x000000CF is defined above to be not set - DWORD lastServiceState = 0x000000CF; - - // Get a handle to the SCM database. - SC_HANDLE serviceManager = OpenSCManager(NULL, NULL, - SC_MANAGER_CONNECT | - SC_MANAGER_ENUMERATE_SERVICE); - if (!serviceManager) { - DWORD lastError = GetLastError(); - switch(lastError) { - case ERROR_ACCESS_DENIED: - return 0x000000FD; - case ERROR_DATABASE_DOES_NOT_EXIST: - return 0x000000FE; - default: - return 0x000000FF; - } - } - - // Get a handle to the service. - SC_HANDLE service = OpenServiceW(serviceManager, - serviceName, - SERVICE_QUERY_STATUS); - if (!service) { - DWORD lastError = GetLastError(); - CloseServiceHandle(serviceManager); - switch(lastError) { - case ERROR_ACCESS_DENIED: - return 0x000000EB; - case ERROR_INVALID_HANDLE: - return 0x000000EC; - case ERROR_INVALID_NAME: - return 0x000000ED; - // If the service does not exist, keep trying in case it does exist soon. - // I think there might be an issue with the TALOS machines and some of - // the machines having an old maintenanceservice.exe that used to - // uninstall when upgrading. Those should already be upgraded but this - // is safer. - case ERROR_SERVICE_DOES_NOT_EXIST: - if (maxWaitSeconds == 0) { - return 0x000000EE; - } else { - Sleep(1000); - return WaitForServiceStop(serviceName, maxWaitSeconds - 1); - } - default: - return 0x000000EF; - } - } - - DWORD currentWaitMS = 0; - SERVICE_STATUS_PROCESS ssp; - ssp.dwCurrentState = lastServiceState; - while (currentWaitMS < maxWaitSeconds * 1000) { - DWORD bytesNeeded; - if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp, - sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) { - DWORD lastError = GetLastError(); - switch (lastError) { - case ERROR_INVALID_HANDLE: - ssp.dwCurrentState = 0x000000D9; - break; - case ERROR_ACCESS_DENIED: - ssp.dwCurrentState = 0x000000DA; - break; - case ERROR_INSUFFICIENT_BUFFER: - ssp.dwCurrentState = 0x000000DB; - break; - case ERROR_INVALID_PARAMETER: - ssp.dwCurrentState = 0x000000DC; - break; - case ERROR_INVALID_LEVEL: - ssp.dwCurrentState = 0x000000DD; - break; - case ERROR_SHUTDOWN_IN_PROGRESS: - ssp.dwCurrentState = 0x000000DE; - break; - // These 3 errors can occur when the service is not yet stopped but - // it is stopping. - case ERROR_INVALID_SERVICE_CONTROL: - case ERROR_SERVICE_CANNOT_ACCEPT_CTRL: - case ERROR_SERVICE_NOT_ACTIVE: - currentWaitMS += 50; - Sleep(50); - continue; - default: - ssp.dwCurrentState = 0x000000DF; - } - - // We couldn't query the status so just break out - break; - } - - // The service is already in use. - if (ssp.dwCurrentState == SERVICE_STOPPED) { - break; - } - currentWaitMS += 50; - Sleep(50); - } - - lastServiceState = ssp.dwCurrentState; - CloseServiceHandle(service); - CloseServiceHandle(serviceManager); - return lastServiceState; -} - -/** - * Determines if there is at least one process running for the specified - * application. A match will be found across any session for any user. - * - * @param process The process to check for existance - * @return ERROR_NOT_FOUND if the process was not found - * @ ERROR_SUCCESS if the process was found and there were no errors - * @ Other Win32 system error code for other errors -**/ -DWORD -IsProcessRunning(LPCWSTR filename) -{ - // Take a snapshot of all processes in the system. - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (INVALID_HANDLE_VALUE == snapshot) { - return GetLastError(); - } - - PROCESSENTRY32W processEntry; - processEntry.dwSize = sizeof(PROCESSENTRY32W); - if (!Process32FirstW(snapshot, &processEntry)) { - DWORD lastError = GetLastError(); - CloseHandle(snapshot); - return lastError; - } - - do { - if (wcsicmp(filename, processEntry.szExeFile) == 0) { - CloseHandle(snapshot); - return ERROR_SUCCESS; - } - } while (Process32NextW(snapshot, &processEntry)); - CloseHandle(snapshot); - return ERROR_NOT_FOUND; -} - -/** - * Waits for the specified applicaiton to exit. - * - * @param filename The application to wait for. - * @param maxSeconds The maximum amount of seconds to wait for all - * instances of the application to exit. - * @return ERROR_SUCCESS if no instances of the application exist - * WAIT_TIMEOUT if the process is still running after maxSeconds. - * Any other Win32 system error code. -*/ -DWORD -WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds) -{ - DWORD applicationRunningError = WAIT_TIMEOUT; - for(DWORD i = 0; i < maxSeconds; i++) { - DWORD applicationRunningError = IsProcessRunning(filename); - if (ERROR_NOT_FOUND == applicationRunningError) { - return ERROR_SUCCESS; - } - Sleep(1000); - } - - if (ERROR_SUCCESS == applicationRunningError) { - return WAIT_TIMEOUT; - } - - return applicationRunningError; -} - - #endif int NS_main(int argc, NS_tchar **argv) diff --git a/toolkit/mozapps/update/test/TestAUSReadStrings.cpp b/toolkit/mozapps/update/test/TestAUSReadStrings.cpp index 335cc228695c..77cfeb51e6f1 100644 --- a/toolkit/mozapps/update/test/TestAUSReadStrings.cpp +++ b/toolkit/mozapps/update/test/TestAUSReadStrings.cpp @@ -65,8 +65,8 @@ #include "updater/resource.h" #include "updater/progressui.h" -#include "../../readstrings/readstrings.h" -#include "../../readstrings/errors.h" +#include "common/readstrings.h" +#include "common/errors.h" #ifndef MAXPATHLEN # ifdef PATH_MAX diff --git a/toolkit/mozapps/update/updater/Makefile.in b/toolkit/mozapps/update/updater/Makefile.in index d7321f5bf385..990ff477e11e 100644 --- a/toolkit/mozapps/update/updater/Makefile.in +++ b/toolkit/mozapps/update/updater/Makefile.in @@ -55,13 +55,13 @@ PROGRAM = updater$(BIN_SUFFIX) MOZ_GLUE_LDFLAGS = MOZ_GLUE_PROGRAM_LDFLAGS = -LOCAL_INCLUDES += -I$(srcdir)/../../readstrings \ - -I$(srcdir)/../common +LOCAL_INCLUDES += \ + -I$(srcdir)/../common \ + $(NULL) LIBS += \ ../common/$(LIB_PREFIX)updatecommon.$(LIB_SUFFIX) \ $(DEPTH)/modules/libmar/src/$(LIB_PREFIX)mar.$(LIB_SUFFIX) \ - ../../readstrings/$(LIB_PREFIX)readstrings.$(LIB_SUFFIX) \ $(BZ2_LIBS) \ $(NULL) diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp index 317a7f9c429a..4555be752e01 100644 --- a/toolkit/mozapps/update/updater/updater.cpp +++ b/toolkit/mozapps/update/updater/updater.cpp @@ -1563,23 +1563,9 @@ ReadMARChannelIDs(const NS_tchar *path, MARChannelStringTable *results) return result; } -struct UpdateThreadData -{ - UpdateThreadData(bool performMARChecks) : - mPerformMARChecks(performMARChecks) - { - } - - bool mPerformMARChecks; -}; - static void UpdateThreadFunc(void *param) { - UpdateThreadData *threadData = reinterpret_cast(param); - bool performMARChecks = threadData && threadData->mPerformMARChecks; - delete threadData; - // open ZIP archive and process... int rv; NS_tchar dataFile[MAXPATHLEN]; @@ -1588,30 +1574,28 @@ UpdateThreadFunc(void *param) rv = gArchiveReader.Open(dataFile); - if (performMARChecks) { #ifdef MOZ_VERIFY_MAR_SIGNATURE - if (rv == OK) { - rv = gArchiveReader.VerifySignature(); - } - - if (rv == OK) { - NS_tchar updateSettingsPath[MAX_TEXT_LEN]; - NS_tsnprintf(updateSettingsPath, - sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]), - NS_T("%supdate-settings.ini"), gDestPath); - MARChannelStringTable MARStrings; - if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) { - // If we can't read from update-settings.ini then we shouldn't impose - // a MAR restriction. Some installations won't even include this file. - MARStrings.MARChannelID[0] = '\0'; - } - - rv = gArchiveReader.VerifyProductInformation(MARStrings.MARChannelID, - MOZ_APP_VERSION); - } -#endif + if (rv == OK) { + rv = gArchiveReader.VerifySignature(); } + if (rv == OK) { + NS_tchar updateSettingsPath[MAX_TEXT_LEN]; + NS_tsnprintf(updateSettingsPath, + sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]), + NS_T("%supdate-settings.ini"), gDestPath); + MARChannelStringTable MARStrings; + if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) { + // If we can't read from update-settings.ini then we shouldn't impose + // a MAR restriction. Some installations won't even include this file. + MARStrings.MARChannelID[0] = '\0'; + } + + rv = gArchiveReader.VerifyProductInformation(MARStrings.MARChannelID, + MOZ_APP_VERSION); + } +#endif + if (rv == OK) { rv = DoUpdate(); gArchiveReader.Close(); @@ -2123,7 +2107,7 @@ int NS_main(int argc, NS_tchar **argv) // before QuitProgressUI has been called, so wait for UpdateThreadFunc to // terminate. Thread t; - if (t.Run(UpdateThreadFunc, new UpdateThreadData(usingService)) == 0) { + if (t.Run(UpdateThreadFunc, NULL) == 0) { ShowProgressUI(); } t.Join(); diff --git a/toolkit/themes/gnomestripe/mozapps/extensions/extensions.css b/toolkit/themes/gnomestripe/mozapps/extensions/extensions.css index 385c91462546..dff215039818 100644 --- a/toolkit/themes/gnomestripe/mozapps/extensions/extensions.css +++ b/toolkit/themes/gnomestripe/mozapps/extensions/extensions.css @@ -739,7 +739,7 @@ setting[first-row="true"] { setting { border-top: 1px solid ThreeDShadow; -moz-box-align: center; - min-height: 33px; + min-height: 32px; } #detail-controls { @@ -755,15 +755,25 @@ setting[first-row="true"] { margin-top: 2em; } +setting { + -moz-box-align: start; +} + +.preferences-alignment { + min-height: 32px; + -moz-box-align: center; +} + .preferences-description { font-size: 90.9%; color: graytext; margin-top: -2px; -moz-margin-start: 2em; + white-space: pre-wrap; } -setting[type="string"] > .setting-input > textbox { - -moz-box-flex: 1; +.preferences-description:empty { + display: none; } menulist { /* Fixes some styling inconsistencies */ diff --git a/toolkit/themes/pinstripe/mozapps/extensions/extensions.css b/toolkit/themes/pinstripe/mozapps/extensions/extensions.css index 93f8775bb824..095dfc2bfe83 100644 --- a/toolkit/themes/pinstripe/mozapps/extensions/extensions.css +++ b/toolkit/themes/pinstripe/mozapps/extensions/extensions.css @@ -934,15 +934,25 @@ setting[first-row="true"] { margin-top: 2em; } +setting { + -moz-box-align: start; +} + +.preferences-alignment { + min-height: 30px; + -moz-box-align: center; +} + .preferences-description { font-size: 90.9%; color: graytext; margin-top: -2px; -moz-margin-start: 2em; + white-space: pre-wrap; } -setting[type="string"] > .setting-input > textbox { - -moz-box-flex: 1; +.preferences-description:empty { + display: none; } setting[type="radio"] > radiogroup { diff --git a/toolkit/themes/winstripe/mozapps/extensions/extensions.css b/toolkit/themes/winstripe/mozapps/extensions/extensions.css index c5e4156ab509..0c32802e0626 100644 --- a/toolkit/themes/winstripe/mozapps/extensions/extensions.css +++ b/toolkit/themes/winstripe/mozapps/extensions/extensions.css @@ -913,15 +913,25 @@ setting[first-row="true"] { margin-top: 2em; } +setting { + -moz-box-align: start; +} + +.preferences-alignment { + min-height: 30px; + -moz-box-align: center; +} + .preferences-description { font-size: 90.9%; color: graytext; margin-top: -2px; -moz-margin-start: 2em; + white-space: pre-wrap; } -setting[type="string"] > .setting-input > textbox { - -moz-box-flex: 1; +.preferences-description:empty { + display: none; } setting[type="radio"] > radiogroup { diff --git a/toolkit/toolkit-makefiles.sh b/toolkit/toolkit-makefiles.sh index 6c91aa5633b9..f3d3138e70fb 100644 --- a/toolkit/toolkit-makefiles.sh +++ b/toolkit/toolkit-makefiles.sh @@ -1377,17 +1377,6 @@ if [ "$MOZ_UPDATER" ]; then fi fi -if [ "$MOZ_UPDATER" -o "$MOZ_MAINTENANCE_SERVICE" ]; then - add_makefiles " - toolkit/mozapps/readstrings/Makefile - " - if [ "$OS_TARGET" != "Android" ]; then - add_makefiles " - toolkit/mozapps/update/common/Makefile - " - fi -fi - if [ "$MOZ_UPDATER" -o "$MOZ_UPDATE_PACKAGING" ]; then add_makefiles " other-licenses/bsdiff/Makefile diff --git a/toolkit/xre/nsWindowsDllBlocklist.cpp b/toolkit/xre/nsWindowsDllBlocklist.cpp index 5fc42e4ce015..0a66402918fb 100644 --- a/toolkit/xre/nsWindowsDllBlocklist.cpp +++ b/toolkit/xre/nsWindowsDllBlocklist.cpp @@ -138,6 +138,9 @@ static DllBlockInfo sWindowsDllBlocklist[] = { {"rf-firefox.dll", MAKE_VERSION(7,6,1,0)}, {"roboform.dll", MAKE_VERSION(7,6,1,0)}, + // Topcrash in Firefox 10 + {"bexternal.dll", ALL_VERSIONS}, + // leave these two in always for tests { "mozdllblockingtest.dll", ALL_VERSIONS }, { "mozdllblockingtest_versioned.dll", 0x0000000400000000ULL }, diff --git a/widget/android/AndroidBridge.cpp b/widget/android/AndroidBridge.cpp index 85860800f5c5..b17deb1b8f82 100644 --- a/widget/android/AndroidBridge.cpp +++ b/widget/android/AndroidBridge.cpp @@ -178,6 +178,7 @@ AndroidBridge::Init(JNIEnv *jEnv, jGetCurrentNetworkInformation = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getCurrentNetworkInformation", "()[D"); jEnableNetworkNotifications = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "enableNetworkNotifications", "()V"); jDisableNetworkNotifications = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "disableNetworkNotifications", "()V"); + jEmitGeckoAccessibilityEvent = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "emitGeckoAccessibilityEvent", "(I[Ljava/lang/String;Ljava/lang/String;ZZZ)V"); jEGLContextClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("javax/microedition/khronos/egl/EGLContext")); jEGL10Class = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("javax/microedition/khronos/egl/EGL10")); @@ -186,6 +187,8 @@ AndroidBridge::Init(JNIEnv *jEnv, jEGLConfigImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLConfigImpl")); jEGLDisplayImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLDisplayImpl")); + jStringClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("java/lang/String")); + InitAndroidJavaWrappers(jEnv); // jEnv should NOT be cached here by anything -- the jEnv here @@ -1487,17 +1490,17 @@ AndroidBridge::MarkURIVisited(const nsAString& aURI) env->CallStaticVoidMethod(mGeckoAppShellClass, jMarkUriVisited, jstrURI); } -void AndroidBridge::EmitGeckoAccessibilityEvent (PRInt32 eventType, const nsAString& role, const nsAString& text, const nsAString& description, bool enabled, bool checked, bool password) { - - JNIEnv *env = GetJNIEnv(); - if (!env) - return; - - AutoLocalJNIFrame jniFrame(env); - jstring jstrRole = env->NewString(nsPromiseFlatString(role).get(), role.Length()); - jstring jstrText = env->NewString(nsPromiseFlatString(text).get(), text.Length()); - jstring jstrDescription = env->NewString(nsPromiseFlatString(description).get(), description.Length()); - env->CallStaticVoidMethod(mGeckoAppShellClass, jEmitGeckoAccessibilityEvent, eventType, jstrRole, jstrText, jstrDescription, enabled, checked, password); +void AndroidBridge::EmitGeckoAccessibilityEvent (PRInt32 eventType, const nsTArray& text, const nsAString& description, bool enabled, bool checked, bool password) { + AutoLocalJNIFrame jniFrame; + jobjectArray jarrayText = mJNIEnv->NewObjectArray(text.Length(), + jStringClass, 0); + for (PRUint32 i = 0; i < text.Length() ; i++) { + jstring jstrText = mJNIEnv->NewString(nsPromiseFlatString(text[i]).get(), + text[i].Length()); + mJNIEnv->SetObjectArrayElement(jarrayText, i, jstrText); + } + jstring jstrDescription = mJNIEnv->NewString(nsPromiseFlatString(description).get(), description.Length()); + mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jEmitGeckoAccessibilityEvent, eventType, jarrayText, jstrDescription, enabled, checked, password); } PRUint16 @@ -1584,8 +1587,8 @@ AndroidBridge::CreateMessageList(const dom::sms::SmsFilterData& aFilter, bool aR jobjectArray numbers = (jobjectArray)env->NewObjectArray(aFilter.numbers().Length(), - env->FindClass("java/lang/String"), - env->NewStringUTF("")); + jStringClass, + env->NewStringUTF("")); for (PRUint32 i = 0; i < aFilter.numbers().Length(); ++i) { env->SetObjectArrayElement(numbers, i, diff --git a/widget/android/AndroidBridge.h b/widget/android/AndroidBridge.h index 84e2de05561e..c9ee11a0f8e3 100644 --- a/widget/android/AndroidBridge.h +++ b/widget/android/AndroidBridge.h @@ -357,7 +357,7 @@ public: nsCOMPtr GetDrawMetadataProvider(); - void EmitGeckoAccessibilityEvent (PRInt32 eventType, const nsAString& role, const nsAString& text, const nsAString& description, bool enabled, bool checked, bool password); + void EmitGeckoAccessibilityEvent (PRInt32 eventType, const nsTArray& text, const nsAString& description, bool enabled, bool checked, bool password); void CheckURIVisited(const nsAString& uri); void MarkURIVisited(const nsAString& uri); @@ -498,6 +498,9 @@ protected: jclass jEGLContextClass; jclass jEGL10Class; + // some convinient types to have around + jclass jStringClass; + // calls we've dlopened from libjnigraphics.so int (* AndroidBitmap_getInfo)(JNIEnv *env, jobject bitmap, void *info); int (* AndroidBitmap_lockPixels)(JNIEnv *env, jobject bitmap, void **buffer); diff --git a/widget/windows/JumpListBuilder.cpp b/widget/windows/JumpListBuilder.cpp index 52b5f0840953..6894402bf7c9 100644 --- a/widget/windows/JumpListBuilder.cpp +++ b/widget/windows/JumpListBuilder.cpp @@ -56,6 +56,10 @@ #include "nsStringStream.h" #include "nsNetUtil.h" #include "nsThreadUtils.h" +#include "mozilla/LazyIdleThread.h" + +// The amount of time, in milliseconds, that our IO thread will stay alive after the last event it processes. +#define DEFAULT_THREAD_TIMEOUT_MS 30000 namespace mozilla { namespace widget { @@ -85,7 +89,8 @@ JumpListBuilder::JumpListBuilder() : CoCreateInstance(CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER, IID_ICustomDestinationList, getter_AddRefs(mJumpListMgr)); - NS_NewThread(getter_AddRefs(mIOThread)); + // Make a lazy thread for any IO + mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, LazyIdleThread::ManualShutdown); Preferences::AddStrongObserver(this, kPrefTaskbarEnabled); } diff --git a/widget/windows/Makefile.in b/widget/windows/Makefile.in index c9327f88168c..df811acbacaa 100644 --- a/widget/windows/Makefile.in +++ b/widget/windows/Makefile.in @@ -79,6 +79,7 @@ CPPSRCS = \ AudioSession.cpp \ nsWidgetFactory.cpp \ WinUtils.cpp \ + WinMouseScrollHandler.cpp \ $(NULL) ifdef MOZ_CRASHREPORTER diff --git a/widget/windows/WinMouseScrollHandler.cpp b/widget/windows/WinMouseScrollHandler.cpp new file mode 100644 index 000000000000..4a755f97d439 --- /dev/null +++ b/widget/windows/WinMouseScrollHandler.cpp @@ -0,0 +1,1438 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef MOZ_LOGGING +#define FORCE_PR_LOG /* Allow logging in the release build */ +#endif // MOZ_LOGGING +#include "prlog.h" + +#include "WinMouseScrollHandler.h" +#include "nsWindow.h" +#include "WinUtils.h" + +#include "mozilla/Preferences.h" + +#include + +namespace mozilla { +namespace widget { + +#ifdef PR_LOGGING +PRLogModuleInfo* gMouseScrollLog = nsnull; + +static const char* GetBoolName(bool aBool) +{ + return aBool ? "TRUE" : "FALSE"; +} + +static void LogKeyStateImpl() +{ + if (!PR_LOG_TEST(gMouseScrollLog, PR_LOG_DEBUG)) { + return; + } + BYTE keyboardState[256]; + if (::GetKeyboardState(keyboardState)) { + for (size_t i = 0; i < ArrayLength(keyboardState); i++) { + if (keyboardState[i]) { + PR_LOG(gMouseScrollLog, PR_LOG_DEBUG, + (" Current key state: keyboardState[0x%02X]=0x%02X (%s)", + i, keyboardState[i], + ((keyboardState[i] & 0x81) == 0x81) ? "Pressed and Toggled" : + (keyboardState[i] & 0x80) ? "Pressed" : + (keyboardState[i] & 0x01) ? "Toggled" : "Unknown")); + } + } + } else { + PR_LOG(gMouseScrollLog, PR_LOG_DEBUG, + ("MouseScroll::Device::Elantech::HandleKeyMessage(): Failed to print " + "current keyboard state")); + } +} + +#define LOG_KEYSTATE() LogKeyStateImpl() +#else // PR_LOGGING +#define LOG_KEYSTATE() +#endif + +MouseScrollHandler* MouseScrollHandler::sInstance = nsnull; + +bool MouseScrollHandler::Device::sFakeScrollableWindowNeeded = false; + +bool MouseScrollHandler::Device::Elantech::sUseSwipeHack = false; +bool MouseScrollHandler::Device::Elantech::sUsePinchHack = false; +DWORD MouseScrollHandler::Device::Elantech::sZoomUntil = 0; + +bool MouseScrollHandler::Device::SetPoint::sMightBeUsing = false; + +// The duration until timeout of events transaction. The value is 1.5 sec, +// it's just a magic number, it was suggested by Logitech's engineer, see +// bug 605648 comment 90. +#define DEFAULT_TIMEOUT_DURATION 1500 + +/****************************************************************************** + * + * MouseScrollHandler + * + ******************************************************************************/ + +/* static */ +void +MouseScrollHandler::Initialize() +{ +#ifdef PR_LOGGING + if (!gMouseScrollLog) { + gMouseScrollLog = PR_NewLogModule("MouseScrollHandlerWidgets"); + } +#endif + Device::Init(); +} + +/* static */ +void +MouseScrollHandler::Shutdown() +{ + delete sInstance; + sInstance = nsnull; +} + +/* static */ +MouseScrollHandler* +MouseScrollHandler::GetInstance() +{ + if (!sInstance) { + sInstance = new MouseScrollHandler(); + } + return sInstance; +} + +MouseScrollHandler::MouseScrollHandler() +{ + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll: Creating an instance, this=%p, sInstance=%p", + this, sInstance)); +} + +MouseScrollHandler::~MouseScrollHandler() +{ + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll: Destroying an instance, this=%p, sInstance=%p", + this, sInstance)); +} + +/* static */ +bool +MouseScrollHandler::ProcessMessage(nsWindow* aWindow, UINT msg, + WPARAM wParam, LPARAM lParam, + LRESULT *aRetValue, bool &aEatMessage) +{ + Device::Elantech::UpdateZoomUntil(); + + switch (msg) { + case WM_SETTINGCHANGE: + if (!sInstance) { + return false; + } + if (wParam == SPI_SETWHEELSCROLLLINES || + wParam == SPI_SETWHEELSCROLLCHARS) { + sInstance->mSystemSettings.MarkDirty(); + } + return false; + + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + GetInstance()-> + ProcessNativeMouseWheelMessage(aWindow, msg, wParam, lParam); + // We don't need to call next wndproc for WM_MOUSEWHEEL and + // WM_MOUSEHWHEEL. We should consume them always. If the messages + // would be handled by our window again, it caused making infinite + // message loop. + aEatMessage = true; + *aRetValue = (msg != WM_MOUSEHWHEEL); + return true; + + case WM_HSCROLL: + case WM_VSCROLL: + aEatMessage = + GetInstance()->ProcessNativeScrollMessage(aWindow, msg, wParam, lParam); + *aRetValue = 0; + return true; + + case MOZ_WM_MOUSEVWHEEL: + case MOZ_WM_MOUSEHWHEEL: + GetInstance()->HandleMouseWheelMessage(aWindow, msg, wParam, lParam); + // Doesn't need to call next wndproc for internal wheel message. + aEatMessage = true; + return true; + + case MOZ_WM_HSCROLL: + case MOZ_WM_VSCROLL: + GetInstance()-> + HandleScrollMessageAsMouseWheelMessage(aWindow, msg, wParam, lParam); + // Doesn't need to call next wndproc for internal scroll message. + aEatMessage = true; + return true; + + case WM_KEYDOWN: + case WM_KEYUP: + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::ProcessMessage(): aWindow=%p, " + "msg=%s(0x%04X), wParam=0x%02X, ::GetMessageTime()=%d", + aWindow, msg == WM_KEYDOWN ? "WM_KEYDOWN" : + msg == WM_KEYUP ? "WM_KEYUP" : "Unknown", msg, wParam, + ::GetMessageTime())); + LOG_KEYSTATE(); + if (Device::Elantech::HandleKeyMessage(aWindow, msg, wParam)) { + *aRetValue = 0; + aEatMessage = true; + return true; + } + return false; + + default: + return false; + } +} + +/* static */ +bool +MouseScrollHandler::DispatchEvent(nsWindow* aWindow, nsGUIEvent& aEvent) +{ + return aWindow->DispatchWindowEvent(&aEvent); +} + +/* static */ +nsModifierKeyState +MouseScrollHandler::GetModifierKeyState(UINT aMessage) +{ + nsModifierKeyState result; + // Assume the Control key is down if the Elantech touchpad has sent the + // mis-ordered WM_KEYDOWN/WM_MOUSEWHEEL messages. (See the comment in + // MouseScrollHandler::Device::Elantech::HandleKeyMessage().) + if ((aMessage == MOZ_WM_MOUSEVWHEEL || aMessage == WM_MOUSEWHEEL) && + !result.mIsControlDown) { + result.mIsControlDown = Device::Elantech::IsZooming(); + } + return result; +} + +POINT +MouseScrollHandler::ComputeMessagePos(UINT aMessage, + WPARAM aWParam, + LPARAM aLParam) +{ + POINT point; + if (Device::SetPoint::IsGetMessagePosResponseValid(aMessage, + aWParam, aLParam)) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::ComputeMessagePos: Using ::GetCursorPos()")); + ::GetCursorPos(&point); + } else { + DWORD dwPoints = ::GetMessagePos(); + point.x = GET_X_LPARAM(dwPoints); + point.y = GET_Y_LPARAM(dwPoints); + } + + return point; +} + +MouseScrollHandler::ScrollTargetInfo +MouseScrollHandler::GetScrollTargetInfo( + nsWindow* aWindow, + const EventInfo& aEventInfo, + const nsModifierKeyState& aModifierKeyState) +{ + ScrollTargetInfo result; + result.dispatchPixelScrollEvent = false; + result.reversePixelScrollDirection = false; + result.actualScrollAmount = aEventInfo.GetScrollAmount(); + result.actualScrollAction = nsQueryContentEvent::SCROLL_ACTION_NONE; + result.pixelsPerUnit = 0; + if (!mUserPrefs.IsPixelScrollingEnabled()) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::GetPixelScrollInfo: Succeeded, aWindow=%p, " + "result: { dispatchPixelScrollEvent: %s, actualScrollAmount: %d }", + aWindow, GetBoolName(result.dispatchPixelScrollEvent), + result.actualScrollAmount)); + return result; + } + + nsMouseScrollEvent testEvent(true, NS_MOUSE_SCROLL, aWindow); + aWindow->InitEvent(testEvent); + aModifierKeyState.InitInputEvent(testEvent); + + testEvent.scrollFlags = aEventInfo.GetScrollFlags(); + testEvent.delta = result.actualScrollAmount; + if ((aEventInfo.IsVertical() && aEventInfo.IsPositive()) || + (!aEventInfo.IsVertical() && !aEventInfo.IsPositive())) { + testEvent.delta *= -1; + } + + nsQueryContentEvent queryEvent(true, NS_QUERY_SCROLL_TARGET_INFO, aWindow); + aWindow->InitEvent(queryEvent); + queryEvent.InitForQueryScrollTargetInfo(&testEvent); + DispatchEvent(aWindow, queryEvent); + + // If the necessary interger isn't larger than 0, we should assume that + // the event failed for us. + if (!queryEvent.mSucceeded) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::GetPixelScrollInfo: Failed to query the " + "scroll target information, aWindow=%p" + "result: { dispatchPixelScrollEvent: %s, actualScrollAmount: %d }", + aWindow, GetBoolName(result.dispatchPixelScrollEvent), + result.actualScrollAmount)); + return result; + } + + result.actualScrollAction = queryEvent.mReply.mComputedScrollAction; + + if (result.actualScrollAction == nsQueryContentEvent::SCROLL_ACTION_PAGE) { + result.pixelsPerUnit = + aEventInfo.IsVertical() ? queryEvent.mReply.mPageHeight : + queryEvent.mReply.mPageWidth; + } else { + result.pixelsPerUnit = queryEvent.mReply.mLineHeight; + } + + result.actualScrollAmount = queryEvent.mReply.mComputedScrollAmount; + + if (result.pixelsPerUnit > 0 && result.actualScrollAmount != 0 && + result.actualScrollAction != nsQueryContentEvent::SCROLL_ACTION_NONE) { + result.dispatchPixelScrollEvent = true; + // If original delta's sign and computed delta's one are different, + // we need to reverse the pixel scroll direction at dispatching it. + result.reversePixelScrollDirection = + (testEvent.delta > 0 && result.actualScrollAmount < 0) || + (testEvent.delta < 0 && result.actualScrollAmount > 0); + // scroll amount must be positive. + result.actualScrollAmount = NS_ABS(result.actualScrollAmount); + } + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::GetPixelScrollInfo: Succeeded, aWindow=%p, " + "result: { dispatchPixelScrollEvent: %s, reversePixelScrollDirection: %s, " + "actualScrollAmount: %d, actualScrollAction: 0x%01X, " + "pixelsPerUnit: %d }", + aWindow, GetBoolName(result.dispatchPixelScrollEvent), + GetBoolName(result.reversePixelScrollDirection), result.actualScrollAmount, + result.actualScrollAction, result.pixelsPerUnit)); + + return result; +} + +void +MouseScrollHandler::ProcessNativeMouseWheelMessage(nsWindow* aWindow, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam) +{ + POINT point = ComputeMessagePos(aMessage, aWParam, aLParam); + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::ProcessNativeMouseWheelMessage: aWindow=%p, " + "aMessage=%s, wParam=0x%08X, lParam=0x%08X, point: { x=%d, y=%d }", + aWindow, aMessage == WM_MOUSEWHEEL ? "WM_MOUSEWHEEL" : + aMessage == WM_MOUSEHWHEEL ? "WM_MOUSEHWHEEL" : + aMessage == WM_VSCROLL ? "WM_VSCROLL" : "WM_HSCROLL", + aWParam, aLParam, point.x, point.y)); + LOG_KEYSTATE(); + + HWND underCursorWnd = ::WindowFromPoint(point); + if (!underCursorWnd) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::ProcessNativeMouseWheelMessage: " + "No window is not found under the cursor")); + return; + } + + if (Device::Elantech::IsPinchHackNeeded() && + Device::Elantech::IsHelperWindow(underCursorWnd)) { + // The Elantech driver places a window right underneath the cursor + // when sending a WM_MOUSEWHEEL event to us as part of a pinch-to-zoom + // gesture. We detect that here, and search for our window that would + // be beneath the cursor if that window wasn't there. + underCursorWnd = WinUtils::FindOurWindowAtPoint(point); + if (!underCursorWnd) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::ProcessNativeMouseWheelMessage: " + "Our window is not found under the Elantech helper window")); + return; + } + } + + // Handle most cases first. If the window under mouse cursor is our window + // except plugin window (MozillaWindowClass), we should handle the message + // on the window. + if (WinUtils::IsOurProcessWindow(underCursorWnd)) { + nsWindow* destWindow = WinUtils::GetNSWindowPtr(underCursorWnd); + if (!destWindow) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::ProcessNativeMouseWheelMessage: " + "Found window under the cursor isn't managed by nsWindow...")); + HWND wnd = ::GetParent(underCursorWnd); + for (; wnd; wnd = ::GetParent(wnd)) { + destWindow = WinUtils::GetNSWindowPtr(wnd); + if (destWindow) { + break; + } + } + if (!wnd) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::ProcessNativeMouseWheelMessage: Our window which is " + "managed by nsWindow is not found under the cursor")); + return; + } + } + + MOZ_ASSERT(destWindow, "destWindow must not be NULL"); + + // If the found window is our plugin window, it means that the message + // has been handled by the plugin but not consumed. We should handle the + // message on its parent window. However, note that the DOM event may + // cause accessing the plugin. Therefore, we should unlock the plugin + // process by using PostMessage(). + if (destWindow->GetWindowType() == eWindowType_plugin) { + destWindow = destWindow->GetParentWindow(false); + if (!destWindow) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::ProcessNativeMouseWheelMessage: " + "Our window which is a parent of a plugin window is not found")); + return; + } + } + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, " + "Posting internal message to an nsWindow (%p)...", + destWindow)); + UINT internalMessage = WinUtils::GetInternalMessage(aMessage); + ::PostMessage(destWindow->GetWindowHandle(), internalMessage, + aWParam, aLParam); + return; + } + + // If the window under cursor is not in our process, it means: + // 1. The window may be a plugin window (GeckoPluginWindow or its descendant). + // 2. The window may be another application's window. + HWND pluginWnd = WinUtils::FindOurProcessWindow(underCursorWnd); + if (!pluginWnd) { + // If there is no plugin window in ancestors of the window under cursor, + // the window is for another applications (case 2). + // We don't need to handle this message. + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::ProcessNativeMouseWheelMessage: " + "Our window is not found under the cursor")); + return; + } + + // If we're a plugin window (MozillaWindowClass) and cursor in this window, + // the message shouldn't go to plugin's wndproc again. So, we should handle + // it on parent window. However, note that the DOM event may cause accessing + // the plugin. Therefore, we should unlock the plugin process by using + // PostMessage(). + if (aWindow->GetWindowType() == eWindowType_plugin && + aWindow->GetWindowHandle() == pluginWnd) { + nsWindow* destWindow = aWindow->GetParentWindow(false); + if (!destWindow) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::ProcessNativeMouseWheelMessage: Our normal window which " + "is a parent of this plugin window is not found")); + return; + } + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, " + "Posting internal message to an nsWindow (%p) which is parent of this " + "plugin window...", + destWindow)); + UINT internalMessage = WinUtils::GetInternalMessage(aMessage); + ::PostMessage(destWindow->GetWindowHandle(), internalMessage, + aWParam, aLParam); + return; + } + + // If the window is a part of plugin, we should post the message to it. + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, " + "Redirecting the message to a window which is a plugin child window")); + ::PostMessage(underCursorWnd, aMessage, aWParam, aLParam); +} + +bool +MouseScrollHandler::ProcessNativeScrollMessage(nsWindow* aWindow, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam) +{ + if (aLParam || mUserPrefs.IsScrollMessageHandledAsWheelMessage()) { + // Scroll message generated by Thinkpad Trackpoint Driver or similar + // Treat as a mousewheel message and scroll appropriately + ProcessNativeMouseWheelMessage(aWindow, aMessage, aWParam, aLParam); + // Always consume the scroll message if we try to emulate mouse wheel + // action. + return true; + } + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::ProcessNativeScrollMessage: aWindow=%p, " + "aMessage=%s, wParam=0x%08X, lParam=0x%08X", + aWindow, aMessage == WM_VSCROLL ? "WM_VSCROLL" : "WM_HSCROLL", + aWParam, aLParam)); + + // Scroll message generated by external application + nsContentCommandEvent commandEvent(true, NS_CONTENT_COMMAND_SCROLL, aWindow); + + commandEvent.mScroll.mIsHorizontal = (aMessage == WM_HSCROLL); + + switch (LOWORD(aWParam)) { + case SB_LINEUP: // SB_LINELEFT + commandEvent.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Line; + commandEvent.mScroll.mAmount = -1; + break; + case SB_LINEDOWN: // SB_LINERIGHT + commandEvent.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Line; + commandEvent.mScroll.mAmount = 1; + break; + case SB_PAGEUP: // SB_PAGELEFT + commandEvent.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Page; + commandEvent.mScroll.mAmount = -1; + break; + case SB_PAGEDOWN: // SB_PAGERIGHT + commandEvent.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Page; + commandEvent.mScroll.mAmount = 1; + break; + case SB_TOP: // SB_LEFT + commandEvent.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Whole; + commandEvent.mScroll.mAmount = -1; + break; + case SB_BOTTOM: // SB_RIGHT + commandEvent.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Whole; + commandEvent.mScroll.mAmount = 1; + break; + default: + return false; + } + // XXX If this is a plugin window, we should dispatch the event from + // parent window. + DispatchEvent(aWindow, commandEvent); + return true; +} + +void +MouseScrollHandler::HandleMouseWheelMessage(nsWindow* aWindow, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam) +{ + NS_ABORT_IF_FALSE( + (aMessage == MOZ_WM_MOUSEVWHEEL || aMessage == MOZ_WM_MOUSEHWHEEL), + "HandleMouseWheelMessage must be called with " + "MOZ_WM_MOUSEVWHEEL or MOZ_WM_MOUSEHWHEEL"); + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::HandleMouseWheelMessage: aWindow=%p, " + "aMessage=MOZ_WM_MOUSE%sWHEEL, aWParam=0x%08X, aLParam=0x%08X", + aWindow, aMessage == MOZ_WM_MOUSEVWHEEL ? "V" : "H", + aWParam, aLParam)); + + EventInfo eventInfo(aWindow, WinUtils::GetNativeMessage(aMessage), + aWParam, aLParam); + if (!eventInfo.CanDispatchMouseScrollEvent()) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::HandleMouseWheelMessage: Cannot dispatch the events")); + mLastEventInfo.ResetTransaction(); + return; + } + + // Discard the remaining delta if current wheel message and last one are + // received by different window or to scroll different direction or + // different unit scroll. Furthermore, if the last event was too old. + if (!mLastEventInfo.CanContinueTransaction(eventInfo)) { + mLastEventInfo.ResetTransaction(); + } + + mLastEventInfo.RecordEvent(eventInfo); + + nsModifierKeyState modKeyState = GetModifierKeyState(aMessage); + + // Before dispatching line scroll event, we should get the current scroll + // event target information for pixel scroll. + ScrollTargetInfo scrollTargetInfo = + GetScrollTargetInfo(aWindow, eventInfo, modKeyState); + + // Grab the widget, it might be destroyed by a DOM event handler. + nsRefPtr kungFuDethGrip(aWindow); + + nsMouseScrollEvent scrollEvent(true, NS_MOUSE_SCROLL, aWindow); + if (mLastEventInfo.InitMouseScrollEvent(aWindow, scrollEvent, + scrollTargetInfo, modKeyState)) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::HandleMouseWheelMessage: dispatching " + "NS_MOUSE_SCROLL event")); + DispatchEvent(aWindow, scrollEvent); + if (aWindow->Destroyed()) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::HandleMouseWheelMessage: The window was destroyed " + "by NS_MOUSE_SCROLL event")); + mLastEventInfo.ResetTransaction(); + return; + } + } +#ifdef PR_LOGGING + else { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::HandleMouseWheelMessage: NS_MOUSE_SCROLL event is not " + "dispatched")); + } +#endif + + nsMouseScrollEvent pixelEvent(true, NS_MOUSE_PIXEL_SCROLL, aWindow); + if (mLastEventInfo.InitMousePixelScrollEvent(aWindow, pixelEvent, + scrollTargetInfo, modKeyState)) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::HandleMouseWheelMessage: dispatching " + "NS_MOUSE_PIXEL_SCROLL event")); + DispatchEvent(aWindow, pixelEvent); + if (aWindow->Destroyed()) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::HandleMouseWheelMessage: The window was destroyed " + "by NS_MOUSE_PIXEL_SCROLL event")); + mLastEventInfo.ResetTransaction(); + return; + } + } +#ifdef PR_LOGGING + else { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::HandleMouseWheelMessage: NS_MOUSE_PIXEL_SCROLL event is " + "not dispatched")); + } +#endif +} + +void +MouseScrollHandler::HandleScrollMessageAsMouseWheelMessage(nsWindow* aWindow, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam) +{ + NS_ABORT_IF_FALSE( + (aMessage == MOZ_WM_VSCROLL || aMessage == MOZ_WM_HSCROLL), + "HandleScrollMessageAsMouseWheelMessage must be called with " + "MOZ_WM_VSCROLL or MOZ_WM_HSCROLL"); + + nsModifierKeyState modKeyState = GetModifierKeyState(aMessage); + + nsMouseScrollEvent scrollEvent(true, NS_MOUSE_SCROLL, aWindow); + scrollEvent.scrollFlags = + (aMessage == MOZ_WM_VSCROLL) ? nsMouseScrollEvent::kIsVertical : + nsMouseScrollEvent::kIsHorizontal; + switch (LOWORD(aWParam)) { + case SB_PAGEDOWN: + scrollEvent.scrollFlags |= nsMouseScrollEvent::kIsFullPage; + case SB_LINEDOWN: + scrollEvent.delta = 1; + break; + case SB_PAGEUP: + scrollEvent.scrollFlags |= nsMouseScrollEvent::kIsFullPage; + case SB_LINEUP: + scrollEvent.delta = -1; + break; + default: + return; + } + modKeyState.InitInputEvent(scrollEvent); + // XXX Current mouse position may not be same as when the original message + // is received. We need to know the actual mouse cursor position when + // the original message was received. + aWindow->InitEvent(scrollEvent); + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::HandleScrollMessageAsMouseWheelMessage: aWindow=%p, " + "aMessage=MOZ_WM_%sSCROLL, aWParam=0x%08X, aLParam=0x%08X, " + "scrollEvent { refPoint: { x: %d, y: %d }, delta: %d, " + "scrollFlags: 0x%04X, isShift: %s, isControl: %s, isAlt: %s, isMeta: %s }", + aWindow, (aMessage == MOZ_WM_VSCROLL) ? "V" : "H", + aWParam, aLParam, scrollEvent.refPoint.x, scrollEvent.refPoint.y, + scrollEvent.delta, scrollEvent.scrollFlags, + GetBoolName(scrollEvent.isShift), + GetBoolName(scrollEvent.isControl), + GetBoolName(scrollEvent.isAlt), + GetBoolName(scrollEvent.isMeta))); + + DispatchEvent(aWindow, scrollEvent); +} + +/****************************************************************************** + * + * EventInfo + * + ******************************************************************************/ + +MouseScrollHandler::EventInfo::EventInfo(nsWindow* aWindow, + UINT aMessage, + WPARAM aWParam, LPARAM aLParam) +{ + NS_ABORT_IF_FALSE(aMessage == WM_MOUSEWHEEL || aMessage == WM_MOUSEHWHEEL, + "EventInfo must be initialized with WM_MOUSEWHEEL or WM_MOUSEHWHEEL"); + + MouseScrollHandler::GetInstance()->mSystemSettings.Init(); + + mIsVertical = (aMessage == WM_MOUSEWHEEL); + mIsPage = MouseScrollHandler::sInstance-> + mSystemSettings.IsPageScroll(mIsVertical); + mDelta = (short)HIWORD(aWParam); + mWnd = aWindow->GetWindowHandle(); + mTimeStamp = TimeStamp::Now(); +} + +bool +MouseScrollHandler::EventInfo::CanDispatchMouseScrollEvent() const +{ + if (!GetScrollAmount()) { + // XXX I think that we should dispatch mouse wheel events even if the + // operation will not scroll because the wheel operation really happened + // and web application may want to handle the event for non-scroll action. + return false; + } + + return (mDelta != 0); +} + +PRInt32 +MouseScrollHandler::EventInfo::GetScrollAmount() const +{ + if (mIsPage) { + return 1; + } + return MouseScrollHandler::sInstance-> + mSystemSettings.GetScrollAmount(mIsVertical); +} + +PRInt32 +MouseScrollHandler::EventInfo::GetScrollFlags() const +{ + PRInt32 result = mIsPage ? nsMouseScrollEvent::kIsFullPage : 0; + result |= mIsVertical ? nsMouseScrollEvent::kIsVertical : + nsMouseScrollEvent::kIsHorizontal; + return result; +} + +/****************************************************************************** + * + * LastEventInfo + * + ******************************************************************************/ + +bool +MouseScrollHandler::LastEventInfo::CanContinueTransaction( + const EventInfo& aNewEvent) +{ + return !mWnd || + (mWnd == aNewEvent.GetWindowHandle() && + IsPositive() == aNewEvent.IsPositive() && + mIsVertical == aNewEvent.IsVertical() && + mIsPage == aNewEvent.IsPage() && + TimeStamp::Now() - mTimeStamp <= + TimeDuration::FromMilliseconds(DEFAULT_TIMEOUT_DURATION)); +} + +void +MouseScrollHandler::LastEventInfo::ResetTransaction() +{ + if (!mWnd) { + return; + } + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::LastEventInfo::ResetTransaction()")); + + mWnd = nsnull; + mRemainingDeltaForScroll = 0; + mRemainingDeltaForPixel = 0; +} + +void +MouseScrollHandler::LastEventInfo::RecordEvent(const EventInfo& aEvent) +{ + mWnd = aEvent.GetWindowHandle(); + mDelta = aEvent.GetNativeDelta(); + mIsVertical = aEvent.IsVertical(); + mIsPage = aEvent.IsPage(); + mTimeStamp = TimeStamp::Now(); +} + +/* static */ +PRInt32 +MouseScrollHandler::LastEventInfo::RoundDelta(double aDelta) +{ + return (aDelta >= 0) ? (PRInt32)floor(aDelta) : (PRInt32)ceil(aDelta); +} + +bool +MouseScrollHandler::LastEventInfo::InitMouseScrollEvent( + nsWindow* aWindow, + nsMouseScrollEvent& aMouseScrollEvent, + const ScrollTargetInfo& aScrollTargetInfo, + const nsModifierKeyState& aModKeyState) +{ + NS_ABORT_IF_FALSE(aMouseScrollEvent.message == NS_MOUSE_SCROLL, + "aMouseScrollEvent must be NS_MOUSE_SCROLL"); + + // XXX Why don't we use lParam value? We should use lParam value because + // our internal message is always posted by original message handler. + // So, GetMessagePos() may return different cursor position. + aWindow->InitEvent(aMouseScrollEvent); + + aModKeyState.InitInputEvent(aMouseScrollEvent); + + // If we dispatch pixel scroll event after the line scroll event, + // we should set kHasPixels flag to the line scroll event. + aMouseScrollEvent.scrollFlags = + aScrollTargetInfo.dispatchPixelScrollEvent ? + nsMouseScrollEvent::kHasPixels : 0; + aMouseScrollEvent.scrollFlags |= GetScrollFlags(); + + // Our positive delta value means to bottom or right. + // But positive native delta value means to top or right. + // Use orienter for computing our delta value with native delta value. + PRInt32 orienter = mIsVertical ? -1 : 1; + + // NOTE: Don't use aScrollTargetInfo.actualScrollAmount for computing the + // delta value of line/page scroll event. The value will be recomputed + // in ESM. + PRInt32 nativeDelta = mDelta + mRemainingDeltaForScroll; + if (IsPage()) { + aMouseScrollEvent.delta = nativeDelta * orienter / WHEEL_DELTA; + PRInt32 recomputedNativeDelta = + aMouseScrollEvent.delta * orienter / WHEEL_DELTA; + mRemainingDeltaForScroll = nativeDelta - recomputedNativeDelta; + } else { + double deltaPerUnit = (double)WHEEL_DELTA / GetScrollAmount(); + aMouseScrollEvent.delta = + RoundDelta((double)nativeDelta * orienter / deltaPerUnit); + PRInt32 recomputedNativeDelta = + (PRInt32)(aMouseScrollEvent.delta * orienter * deltaPerUnit); + mRemainingDeltaForScroll = nativeDelta - recomputedNativeDelta; + } + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::LastEventInfo::InitMouseScrollEvent: aWindow=%p, " + "aMouseScrollEvent { refPoint: { x: %d, y: %d }, delta: %d, " + "scrollFlags: 0x%04X, isShift: %s, isControl: %s, isAlt: %s, " + "isMeta: %s }, mRemainingDeltaForScroll: %d", + aWindow, aMouseScrollEvent.refPoint.x, aMouseScrollEvent.refPoint.y, + aMouseScrollEvent.delta, aMouseScrollEvent.scrollFlags, + GetBoolName(aMouseScrollEvent.isShift), + GetBoolName(aMouseScrollEvent.isControl), + GetBoolName(aMouseScrollEvent.isAlt), + GetBoolName(aMouseScrollEvent.isMeta), mRemainingDeltaForScroll)); + + return (aMouseScrollEvent.delta != 0); +} + +bool +MouseScrollHandler::LastEventInfo::InitMousePixelScrollEvent( + nsWindow* aWindow, + nsMouseScrollEvent& aPixelScrollEvent, + const ScrollTargetInfo& aScrollTargetInfo, + const nsModifierKeyState& aModKeyState) +{ + NS_ABORT_IF_FALSE(aPixelScrollEvent.message == NS_MOUSE_PIXEL_SCROLL, + "aPixelScrollEvent must be NS_MOUSE_PIXEL_SCROLL"); + + if (!aScrollTargetInfo.dispatchPixelScrollEvent) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::LastEventInfo::InitMousePixelScrollEvent: aWindow=%p, " + "PixelScroll is disabled", + aWindow, mRemainingDeltaForPixel)); + + mRemainingDeltaForPixel = 0; + return false; + } + + // XXX Why don't we use lParam value? We should use lParam value because + // our internal message is always posted by original message handler. + // So, GetMessagePos() may return different cursor position. + aWindow->InitEvent(aPixelScrollEvent); + + aModKeyState.InitInputEvent(aPixelScrollEvent); + + aPixelScrollEvent.scrollFlags = nsMouseScrollEvent::kAllowSmoothScroll; + aPixelScrollEvent.scrollFlags |= mIsVertical ? + nsMouseScrollEvent::kIsVertical : nsMouseScrollEvent::kIsHorizontal; + if (aScrollTargetInfo.actualScrollAction == + nsQueryContentEvent::SCROLL_ACTION_PAGE) { + aPixelScrollEvent.scrollFlags |= nsMouseScrollEvent::kIsFullPage; + } + + // Our positive delta value means to bottom or right. + // But positive native delta value means to top or right. + // Use orienter for computing our delta value with native delta value. + PRInt32 orienter = mIsVertical ? -1 : 1; + // However, pixel scroll event won't be recomputed the scroll amout and + // direction by ESM. Therefore, we need to set the computed amout and + // direction here. + if (aScrollTargetInfo.reversePixelScrollDirection) { + orienter *= -1; + } + + PRInt32 nativeDelta = mDelta + mRemainingDeltaForPixel; + double deltaPerPixel = (double)WHEEL_DELTA / + aScrollTargetInfo.actualScrollAmount / aScrollTargetInfo.pixelsPerUnit; + aPixelScrollEvent.delta = + RoundDelta((double)nativeDelta * orienter / deltaPerPixel); + PRInt32 recomputedNativeDelta = + (PRInt32)(aPixelScrollEvent.delta * orienter * deltaPerPixel); + mRemainingDeltaForPixel = nativeDelta - recomputedNativeDelta; + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::LastEventInfo::InitMousePixelScrollEvent: aWindow=%p, " + "aPixelScrollEvent { refPoint: { x: %d, y: %d }, delta: %d, " + "scrollFlags: 0x%04X, isShift: %s, isControl: %s, isAlt: %s, " + "isMeta: %s }, mRemainingDeltaForScroll: %d", + aWindow, aPixelScrollEvent.refPoint.x, aPixelScrollEvent.refPoint.y, + aPixelScrollEvent.delta, aPixelScrollEvent.scrollFlags, + GetBoolName(aPixelScrollEvent.isShift), + GetBoolName(aPixelScrollEvent.isControl), + GetBoolName(aPixelScrollEvent.isAlt), + GetBoolName(aPixelScrollEvent.isMeta), mRemainingDeltaForPixel)); + + return (aPixelScrollEvent.delta != 0); +} + +/****************************************************************************** + * + * SystemSettings + * + ******************************************************************************/ + +void +MouseScrollHandler::SystemSettings::Init() +{ + if (mInitialized) { + return; + } + + mInitialized = true; + + if (!::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &mScrollLines, 0)) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::SystemSettings::Init(): ::SystemParametersInfo(" + "SPI_GETWHEELSCROLLLINES) failed")); + mScrollLines = 3; + } else if (mScrollLines > WHEEL_DELTA) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::SystemSettings::Init(): the result of " + "::SystemParametersInfo(SPI_GETWHEELSCROLLLINES) is too large: %d", + mScrollLines)); + // sScrollLines usually equals 3 or 0 (for no scrolling) + // However, if sScrollLines > WHEEL_DELTA, we assume that + // the mouse driver wants a page scroll. The docs state that + // sScrollLines should explicitly equal WHEEL_PAGESCROLL, but + // since some mouse drivers use an arbitrary large number instead, + // we have to handle that as well. + mScrollLines = WHEEL_PAGESCROLL; + } + + if (!::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &mScrollChars, 0)) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::SystemSettings::Init(): ::SystemParametersInfo(" + "SPI_GETWHEELSCROLLCHARS) failed, %s", + WinUtils::GetWindowsVersion() >= WinUtils::VISTA_VERSION ? + "this is unexpected on Vista or later" : + "but on XP or earlier, this is not a problem")); + mScrollChars = 1; + } else if (mScrollChars > WHEEL_DELTA) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::SystemSettings::Init(): the result of " + "::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS) is too large: %d", + mScrollChars)); + // See the comments for the case mScrollLines > WHEEL_DELTA. + mScrollChars = WHEEL_PAGESCROLL; + } + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::SystemSettings::Init(): initialized, " + "mScrollLines=%d, mScrollChars=%d", + mScrollLines, mScrollChars)); +} + +void +MouseScrollHandler::SystemSettings::MarkDirty() +{ + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScrollHandler::SystemSettings::MarkDirty(): " + "Marking SystemSettings dirty")); + mInitialized = false; + // When system settings are changed, we should reset current transaction. + MOZ_ASSERT(sInstance, + "Must not be called at initializing MouseScrollHandler"); + MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction(); +} + +/****************************************************************************** + * + * UserPrefs + * + ******************************************************************************/ + +MouseScrollHandler::UserPrefs::UserPrefs() : + mInitialized(false) +{ + // We need to reset mouse wheel transaction when all of mousewheel related + // prefs are changed. + DebugOnly rv = + Preferences::RegisterCallback(OnChange, "mousewheel.", this); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to register callback for mousewheel."); +} + +MouseScrollHandler::UserPrefs::~UserPrefs() +{ + DebugOnly rv = + Preferences::UnregisterCallback(OnChange, "mousewheel.", this); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to unregister callback for mousewheel."); +} + +void +MouseScrollHandler::UserPrefs::Init() +{ + if (mInitialized) { + return; + } + + mInitialized = true; + + mPixelScrollingEnabled = + Preferences::GetBool("mousewheel.enable_pixel_scrolling", true); + mScrollMessageHandledAsWheelMessage = + Preferences::GetBool("mousewheel.emulate_at_wm_scroll", false); + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::UserPrefs::Init(): initialized, " + "mPixelScrollingEnabled=%s, mScrollMessageHandledAsWheelMessage=%s", + GetBoolName(mPixelScrollingEnabled), + GetBoolName(mScrollMessageHandledAsWheelMessage))); +} + +void +MouseScrollHandler::UserPrefs::MarkDirty() +{ + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScrollHandler::UserPrefs::MarkDirty(): Marking UserPrefs dirty")); + mInitialized = false; + // When user prefs for mousewheel are changed, we should reset current + // transaction. + MOZ_ASSERT(sInstance, + "Must not be called at initializing MouseScrollHandler"); + MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction(); +} + +/****************************************************************************** + * + * Device + * + ******************************************************************************/ + +/* static */ +bool +MouseScrollHandler::Device::GetWorkaroundPref(const char* aPrefName, + bool aValueIfAutomatic) +{ + if (!aPrefName) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::GetWorkaroundPref(): Failed, aPrefName is NULL")); + return aValueIfAutomatic; + } + + PRInt32 lHackValue = 0; + if (NS_FAILED(Preferences::GetInt(aPrefName, &lHackValue))) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::GetWorkaroundPref(): Preferences::GetInt() failed," + " aPrefName=\"%s\", aValueIfAutomatic=%s", + aPrefName, GetBoolName(aValueIfAutomatic))); + return aValueIfAutomatic; + } + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::GetWorkaroundPref(): Succeeded, " + "aPrefName=\"%s\", aValueIfAutomatic=%s, lHackValue=%d", + aPrefName, GetBoolName(aValueIfAutomatic), lHackValue)); + + switch (lHackValue) { + case 0: // disabled + return false; + case 1: // enabled + return true; + default: // -1: autodetect + return aValueIfAutomatic; + } +} + +/* static */ +void +MouseScrollHandler::Device::Init() +{ + sFakeScrollableWindowNeeded = + GetWorkaroundPref("ui.trackpoint_hack.enabled", + (TrackPoint::IsDriverInstalled() || + UltraNav::IsObsoleteDriverInstalled())); + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::Init(): sFakeScrollableWindowNeeded=%s", + GetBoolName(sFakeScrollableWindowNeeded))); + + Elantech::Init(); +} + +/****************************************************************************** + * + * Device::Elantech + * + ******************************************************************************/ + +/* static */ +void +MouseScrollHandler::Device::Elantech::Init() +{ + PRInt32 version = GetDriverMajorVersion(); + bool needsHack = + Device::GetWorkaroundPref("ui.elantech_gesture_hacks.enabled", + version != 0); + sUseSwipeHack = needsHack && version <= 7; + sUsePinchHack = needsHack && version <= 8; + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::Elantech::Init(): version=%d, sUseSwipeHack=%s, " + "sUsePinchHack=%s", + version, GetBoolName(sUseSwipeHack), GetBoolName(sUsePinchHack))); +} + +/* static */ +PRInt32 +MouseScrollHandler::Device::Elantech::GetDriverMajorVersion() +{ + PRUnichar buf[40]; + // The driver version is found in one of these two registry keys. + bool foundKey = + WinUtils::GetRegistryKey(HKEY_CURRENT_USER, + L"Software\\Elantech\\MainOption", + L"DriverVersion", + buf, sizeof buf); + if (!foundKey) { + foundKey = + WinUtils::GetRegistryKey(HKEY_CURRENT_USER, + L"Software\\Elantech", + L"DriverVersion", + buf, sizeof buf); + } + + if (!foundKey) { + return 0; + } + + // Assume that the major version number can be found just after a space + // or at the start of the string. + for (PRUnichar* p = buf; *p; p++) { + if (*p >= L'0' && *p <= L'9' && (p == buf || *(p - 1) == L' ')) { + return wcstol(p, NULL, 10); + } + } + + return 0; +} + +/* static */ +bool +MouseScrollHandler::Device::Elantech::IsHelperWindow(HWND aWnd) +{ + // The helper window cannot be distinguished based on its window class, so we + // need to check if it is owned by the helper process, ETDCtrl.exe. + + const PRUnichar* filenameSuffix = L"\\etdctrl.exe"; + const int filenameSuffixLength = 12; + + DWORD pid; + ::GetWindowThreadProcessId(aWnd, &pid); + + HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); + if (!hProcess) { + return false; + } + + bool result = false; + PRUnichar path[256] = {L'\0'}; + if (::GetProcessImageFileNameW(hProcess, path, ArrayLength(path))) { + int pathLength = lstrlenW(path); + if (pathLength >= filenameSuffixLength) { + if (lstrcmpiW(path + pathLength - filenameSuffixLength, + filenameSuffix) == 0) { + result = true; + } + } + } + ::CloseHandle(hProcess); + + return result; +} + +/* static */ +bool +MouseScrollHandler::Device::Elantech::HandleKeyMessage(nsWindow* aWindow, + UINT aMsg, + WPARAM aWParam) +{ + // The Elantech touchpad driver understands three-finger swipe left and + // right gestures, and translates them into Page Up and Page Down key + // events for most applications. For Firefox 3.6, it instead sends + // Alt+Left and Alt+Right to trigger browser back/forward actions. As + // with the Thinkpad Driver hack in nsWindow::Create, the change in + // HWND structure makes Firefox not trigger the driver's heuristics + // any longer. + // + // The Elantech driver actually sends these messages for a three-finger + // swipe right: + // + // WM_KEYDOWN virtual_key = 0xCC or 0xFF (depending on driver version) + // WM_KEYDOWN virtual_key = VK_NEXT + // WM_KEYUP virtual_key = VK_NEXT + // WM_KEYUP virtual_key = 0xCC or 0xFF + // + // so we use the 0xCC or 0xFF key modifier to detect whether the Page Down + // is due to the gesture rather than a regular Page Down keypress. We then + // pretend that we should dispatch "Go Forward" command. Similarly + // for VK_PRIOR and "Go Back" command. + if (sUseSwipeHack && + (aWParam == VK_NEXT || aWParam == VK_PRIOR) && + (IS_VK_DOWN(0xFF) || IS_VK_DOWN(0xCC))) { + if (aMsg == WM_KEYDOWN) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::Elantech::HandleKeyMessage(): Dispatching " + "%s command event", + aWParam == VK_NEXT ? "Forward" : "Back")); + + nsCommandEvent commandEvent(true, nsGkAtoms::onAppCommand, + (aWParam == VK_NEXT) ? nsGkAtoms::Forward : nsGkAtoms::Back, aWindow); + aWindow->InitEvent(commandEvent); + MouseScrollHandler::DispatchEvent(aWindow, commandEvent); + } +#ifdef PR_LOGGING + else { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::Elantech::HandleKeyMessage(): Consumed")); + } +#endif + return true; // consume the message (doesn't need to dispatch key events) + } + + // Version 8 of the Elantech touchpad driver sends these messages for + // zoom gestures: + // + // WM_KEYDOWN virtual_key = 0xCC time = 10 + // WM_KEYDOWN virtual_key = VK_CONTROL time = 10 + // WM_MOUSEWHEEL time = ::GetTickCount() + // WM_KEYUP virtual_key = VK_CONTROL time = 10 + // WM_KEYUP virtual_key = 0xCC time = 10 + // + // The result of this is that we process all of the WM_KEYDOWN/WM_KEYUP + // messages first because their timestamps make them appear to have + // been sent before the WM_MOUSEWHEEL message. To work around this, + // we store the current time when we process the WM_KEYUP message and + // assume that any WM_MOUSEWHEEL message with a timestamp before that + // time is one that should be processed as if the Control key was down. + if (sUsePinchHack && aMsg == WM_KEYUP && + aWParam == VK_CONTROL && ::GetMessageTime() == 10) { + // We look only at the bottom 31 bits of the system tick count since + // GetMessageTime returns a LONG, which is signed, so we want values + // that are more easily comparable. + sZoomUntil = ::GetTickCount() & 0x7FFFFFFF; + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::Elantech::HandleKeyMessage(): sZoomUntil=%d", + sZoomUntil)); + } + + return false; +} + +/* static */ +void +MouseScrollHandler::Device::Elantech::UpdateZoomUntil() +{ + if (!sZoomUntil) { + return; + } + + // For the Elantech Touchpad Zoom Gesture Hack, we should check that the + // system time (32-bit milliseconds) hasn't wrapped around. Otherwise we + // might get into the situation where wheel events for the next 50 days of + // system uptime are assumed to be Ctrl+Wheel events. (It is unlikely that + // we would get into that state, because the system would already need to be + // up for 50 days and the Control key message would need to be processed just + // before the system time overflow and the wheel message just after.) + // + // We also take the chance to reset sZoomUntil if we simply have passed that + // time. + LONG msgTime = ::GetMessageTime(); + if ((sZoomUntil >= 0x3fffffffu && DWORD(msgTime) < 0x40000000u) || + (sZoomUntil < DWORD(msgTime))) { + sZoomUntil = 0; + + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::Elantech::UpdateZoomUntil(): " + "sZoomUntil was reset")); + } +} + +/* static */ +bool +MouseScrollHandler::Device::Elantech::IsZooming() +{ + // Assume the Control key is down if the Elantech touchpad has sent the + // mis-ordered WM_KEYDOWN/WM_MOUSEWHEEL messages. (See the comment in + // OnKeyUp.) + return (sZoomUntil && static_cast(::GetMessageTime()) < sZoomUntil); +} + +/****************************************************************************** + * + * Device::TrackPoint + * + ******************************************************************************/ + +/* static */ +bool +MouseScrollHandler::Device::TrackPoint::IsDriverInstalled() +{ + if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER, + L"Software\\Lenovo\\TrackPoint")) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::TrackPoint::IsDriverInstalled(): " + "Lenovo's TrackPoint driver is found")); + return true; + } + + if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER, + L"Software\\Alps\\Apoint\\TrackPoint")) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::TrackPoint::IsDriverInstalled(): " + "Alps's TrackPoint driver is found")); + } + + return false; +} + +/****************************************************************************** + * + * Device::UltraNav + * + ******************************************************************************/ + +/* static */ +bool +MouseScrollHandler::Device::UltraNav::IsObsoleteDriverInstalled() +{ + if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER, + L"Software\\Lenovo\\UltraNav")) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): " + "Lenovo's UltraNav driver is found")); + return true; + } + + bool installed = false; + if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER, + L"Software\\Synaptics\\SynTPEnh\\UltraNavUSB")) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): " + "Synaptics's UltraNav (USB) driver is found")); + installed = true; + } else if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER, + L"Software\\Synaptics\\SynTPEnh\\UltraNavPS2")) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): " + "Synaptics's UltraNav (PS/2) driver is found")); + installed = true; + } + + if (!installed) { + return false; + } + + PRUnichar buf[40]; + bool foundKey = + WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, + L"Software\\Synaptics\\SynTP\\Install", + L"DriverVersion", + buf, sizeof buf); + if (!foundKey) { + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): " + "Failed to get UltraNav driver version")); + return false; + } + + int majorVersion = wcstol(buf, NULL, 10); + int minorVersion = 0; + PRUnichar* p = wcschr(buf, L'.'); + if (p) { + minorVersion = wcstol(p + 1, NULL, 10); + } + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): " + "found driver version = %d.%d", + majorVersion, minorVersion)); + return majorVersion < 15 || majorVersion == 15 && minorVersion == 0; +} + +/****************************************************************************** + * + * Device::SetPoint + * + ******************************************************************************/ + +/* static */ +bool +MouseScrollHandler::Device::SetPoint::IsGetMessagePosResponseValid( + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam) +{ + if (aMessage != WM_MOUSEHWHEEL) { + return false; + } + + DWORD messagePos = ::GetMessagePos(); + + // XXX We should check whether SetPoint is installed or not by registry. + + // SetPoint, Logitech (Logicool) mouse driver, (confirmed with 4.82.11 and + // MX-1100) always sets 0 to the lParam of WM_MOUSEHWHEEL. The driver SENDs + // one message at first time, this time, ::GetMessagePos() works fine. + // Then, we will return 0 (0 means we process it) to the message. Then, the + // driver will POST the same messages continuously during the wheel tilted. + // But ::GetMessagePos() API always returns (0, 0) for them, even if the + // actual mouse cursor isn't 0,0. Therefore, we cannot trust the result of + // ::GetMessagePos API if the sender is SetPoint. + if (!sMightBeUsing && !aLParam && (DWORD)aLParam != messagePos && + ::InSendMessage()) { + sMightBeUsing = true; + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::SetPoint::IsGetMessagePosResponseValid(): " + "Might using SetPoint")); + } else if (sMightBeUsing && aLParam != 0 && ::InSendMessage()) { + // The user has changed the mouse from Logitech's to another one (e.g., + // the user has changed to the touchpad of the notebook. + sMightBeUsing = false; + PR_LOG(gMouseScrollLog, PR_LOG_ALWAYS, + ("MouseScroll::Device::SetPoint::IsGetMessagePosResponseValid(): " + "Might stop using SetPoint")); + } + return (sMightBeUsing && !aLParam && !messagePos); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/WinMouseScrollHandler.h b/widget/windows/WinMouseScrollHandler.h new file mode 100644 index 000000000000..d5d1901885f6 --- /dev/null +++ b/widget/windows/WinMouseScrollHandler.h @@ -0,0 +1,456 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_WinMouseScrollHandler_h__ +#define mozilla_widget_WinMouseScrollHandler_h__ + +#include "nscore.h" +#include "nsDebug.h" +#include "mozilla/Assertions.h" +#include "mozilla/TimeStamp.h" +#include + +class nsWindow; +class nsGUIEvent; +class nsMouseScrollEvent; +struct nsModifierKeyState; + +namespace mozilla { +namespace widget { + +class MouseScrollHandler { +public: + static MouseScrollHandler* GetInstance(); + + static void Initialize(); + static void Shutdown(); + + static bool ProcessMessage(nsWindow* aWindow, + UINT msg, + WPARAM wParam, + LPARAM lParam, + LRESULT *aRetValue, + bool &aEatMessage); + +private: + MouseScrollHandler(); + ~MouseScrollHandler(); + + static MouseScrollHandler* sInstance; + + /** + * DispatchEvent() dispatches aEvent on aWindow. + * + * @return TRUE if the event was consumed. Otherwise, FALSE. + */ + static bool DispatchEvent(nsWindow* aWindow, nsGUIEvent& aEvent); + + /** + * GetModifierKeyState() returns current modifier key state. + * Note that some devices need some hack for the modifier key state. + * This method does it automatically. + * + * @param aMessage Handling message. + */ + static nsModifierKeyState GetModifierKeyState(UINT aMessage); + + /** + * ProcessNativeMouseWheelMessage() processes WM_MOUSEWHEEL and + * WM_MOUSEHWHEEL. Additionally, processes WM_VSCROLL and WM_HSCROLL if they + * should be processed as mouse wheel message. + * This method posts MOZ_WM_MOUSEVWHEEL, MOZ_WM_MOUSEHWHEEL, + * MOZ_WM_VSCROLL or MOZ_WM_HSCROLL if we need to dispatch mouse scroll + * events. That avoids deadlock with plugin process. + * + * @param aWindow A window which receives the message. + * @param aMessage WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL or + * WM_HSCROLL. + * @param aWParam The wParam value of the message. + * @param aLParam The lParam value of the message. + */ + void ProcessNativeMouseWheelMessage(nsWindow* aWindow, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam); + + /** + * ProcessNativeScrollMessage() processes WM_VSCROLL and WM_HSCROLL. + * This method just call ProcessMouseWheelMessage() if the message should be + * processed as mouse wheel message. Otherwise, dispatches a content + * command event. + * + * @param aWindow A window which receives the message. + * @param aMessage WM_VSCROLL or WM_HSCROLL. + * @param aWParam The wParam value of the message. + * @param aLParam The lParam value of the message. + * @return TRUE if the message is processed. Otherwise, FALSE. + */ + bool ProcessNativeScrollMessage(nsWindow* aWindow, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam); + + /** + * HandleMouseWheelMessage() processes MOZ_WM_MOUSEVWHEEL and + * MOZ_WM_MOUSEHWHEEL which are posted when one of our windows received + * WM_MOUSEWHEEL or WM_MOUSEHWHEEL for avoiding deadlock with OOPP. + * + * @param aWindow A window which receives the wheel message. + * @param aMessage MOZ_WM_MOUSEWHEEL or MOZ_WM_MOUSEHWHEEL. + * @param aWParam The wParam value of the original message. + * @param aLParam The lParam value of the original message. + */ + void HandleMouseWheelMessage(nsWindow* aWindow, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam); + + /** + * HandleScrollMessageAsMouseWheelMessage() processes the MOZ_WM_VSCROLL and + * MOZ_WM_HSCROLL which are posted when one of mouse windows received + * WM_VSCROLL or WM_HSCROLL and user wants them to emulate mouse wheel + * message's behavior. + * + * @param aWindow A window which receives the scroll message. + * @param aMessage MOZ_WM_VSCROLL or MOZ_WM_HSCROLL. + * @param aWParam The wParam value of the original message. + * @param aLParam The lParam value of the original message. + */ + void HandleScrollMessageAsMouseWheelMessage(nsWindow* aWindow, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam); + + /** + * ComputeMessagePos() computes the cursor position when the message was + * added to the queue. + * + * @param aMessage Handling message. + * @param aWParam Handling message's wParam. + * @param aLParam Handling message's lParam. + * @return Mouse cursor position when the message is added to + * the queue or current cursor position if the result of + * ::GetMessagePos() is broken. + */ + POINT ComputeMessagePos(UINT aMessage, + WPARAM aWParam, + LPARAM aLParam); + + class EventInfo; + /** + * GetScrollTargetInfo() returns scroll target information which is + * computed from the result of NS_QUERY_SCROLL_TARGET_INFO event. + * + * @param aWindow An nsWindow which is handling the event. + * @param aEventInfo The EventInfo which is being handled. + * @param aModifierKeyState The modifier key state. + */ + struct ScrollTargetInfo { + // TRUE if pixel scroll event is needed. Otherwise, FALSE. + bool dispatchPixelScrollEvent; + // TRUE if pixel scroll event's delta value should be reversed. + // Otherwise, FALSE. + bool reversePixelScrollDirection; + // Actual scroll amount. It might be computed with user prefs. + PRInt32 actualScrollAmount; + // Actual scroll action. It might be computed with user prefs. + // The value is one of nsQueryContentEvent::SCROLL_ACTION_*. + PRInt32 actualScrollAction; + // Pixels per unit (line or page, depends on the action). + PRInt32 pixelsPerUnit; + }; + ScrollTargetInfo GetScrollTargetInfo( + nsWindow* aWindow, + const EventInfo& aEvent, + const nsModifierKeyState& aModiferKeyState); + + class EventInfo { + public: + /** + * @param aWindow An nsWindow which is handling the event. + * @param aMessage Must be WM_MOUSEWHEEL or WM_MOUSEHWHEEL. + */ + EventInfo(nsWindow* aWindow, UINT aMessage, WPARAM aWParam, LPARAM aLParam); + + bool CanDispatchMouseScrollEvent() const; + + PRInt32 GetNativeDelta() const { return mDelta; } + HWND GetWindowHandle() const { return mWnd; } + const TimeStamp& GetTimeStamp() const { return mTimeStamp; } + bool IsVertical() const { return mIsVertical; } + bool IsPositive() const { return (mDelta > 0); } + bool IsPage() const { return mIsPage; } + + /** + * @return Number of lines or pages scrolled per WHEEL_DELTA. + */ + PRInt32 GetScrollAmount() const; + + /** + * @return One or more values of + * nsMouseScrollEvent::nsMouseScrollFlags. + */ + PRInt32 GetScrollFlags() const; + + protected: + EventInfo() : + mIsVertical(false), mIsPage(false), mDelta(0), mWnd(nsnull) + { + } + + // TRUE if event is for vertical scroll. Otherwise, FALSE. + bool mIsVertical; + // TRUE if event scrolls per page, otherwise, FALSE. + bool mIsPage; + // The native delta value. + PRInt32 mDelta; + // The window handle which is handling the event. + HWND mWnd; + // Timestamp of the event. + TimeStamp mTimeStamp; + }; + + class LastEventInfo : public EventInfo { + public: + LastEventInfo() : + EventInfo(), mRemainingDeltaForScroll(0), mRemainingDeltaForPixel(0) + { + } + + /** + * CanContinueTransaction() checks whether the new event can continue the + * last transaction or not. Note that if there is no transaction, this + * returns true. + */ + bool CanContinueTransaction(const EventInfo& aNewEvent); + + /** + * ResetTransaction() resets the transaction, i.e., the instance forgets + * the last event information. + */ + void ResetTransaction(); + + /** + * RecordEvent() saves the information of new event. + */ + void RecordEvent(const EventInfo& aEvent); + + /** + * InitMouseScrollEvent() initializes NS_MOUSE_SCROLL event and + * recomputes the remaning detla for the event. + * This must be called only once during handling a message and after + * RecordEvent() is called. + * + * @param aWindow A window which will dispatch the event. + * @param aMouseScrollEvent An NS_MOUSE_SCROLL event, this will be + * initialized. + * @param aScrollTargetInfo The result of GetScrollTargetInfo(). + * @param aModKeyState Current modifier key state. + * @return TRUE if the event is ready to dispatch. + * Otherwise, FALSE. + */ + bool InitMouseScrollEvent(nsWindow* aWindow, + nsMouseScrollEvent& aMouseScrollEvent, + const ScrollTargetInfo& aScrollTargetInfo, + const nsModifierKeyState& aModKeyState); + + /** + * InitMousePixelScrollEvent() initializes NS_MOUSE_PIXEL_SCROLL event and + * recomputes the remaning detla for the event. + * This must be called only once during handling a message and after + * RecordEvent() is called. + * + * @param aWindow A window which will dispatch the event. + * @param aMouseScrollEvent An NS_MOUSE_PIXEL_SCROLL event, this will be + * initialized. + * @param aScrollTargetInfo The result of GetScrollTargetInfo(). + * @param aModKeyState Current modifier key state. + * @return TRUE if the event is ready to dispatch. + * Otherwise, FALSE. + */ + bool InitMousePixelScrollEvent(nsWindow* aWindow, + nsMouseScrollEvent& aPixelScrollEvent, + const ScrollTargetInfo& aScrollTargetInfo, + const nsModifierKeyState& aModKeyState); + + private: + static PRInt32 RoundDelta(double aDelta); + + // The remaining native delta value (i.e., not handled by previous + // message handler). + PRInt32 mRemainingDeltaForScroll; + PRInt32 mRemainingDeltaForPixel; + }; + + LastEventInfo mLastEventInfo; + + class SystemSettings { + public: + SystemSettings() : mInitialized(false) {} + + void Init(); + void MarkDirty(); + + PRInt32 GetScrollAmount(bool aForVertical) const + { + MOZ_ASSERT(mInitialized, "SystemSettings must be initialized"); + return aForVertical ? mScrollLines : mScrollChars; + } + + bool IsPageScroll(bool aForVertical) const + { + MOZ_ASSERT(mInitialized, "SystemSettings must be initialized"); + return aForVertical ? (mScrollLines == WHEEL_PAGESCROLL) : + (mScrollChars == WHEEL_PAGESCROLL); + } + + private: + bool mInitialized; + PRInt32 mScrollLines; + PRInt32 mScrollChars; + }; + + SystemSettings mSystemSettings; + + class UserPrefs { + public: + UserPrefs(); + ~UserPrefs(); + + void MarkDirty(); + + bool IsPixelScrollingEnabled() + { + Init(); + return mPixelScrollingEnabled; + } + + bool IsScrollMessageHandledAsWheelMessage() + { + Init(); + return mScrollMessageHandledAsWheelMessage; + } + + private: + void Init(); + + static int OnChange(const char* aPrefName, void* aClosure) + { + static_cast(aClosure)->MarkDirty(); + return 0; + } + + bool mInitialized; + bool mPixelScrollingEnabled; + bool mScrollMessageHandledAsWheelMessage; + }; + + UserPrefs mUserPrefs; + +public: + + class Device { + public: + class Elantech { + public: + /** + * GetDriverMajorVersion() returns the installed driver's major version. + * If Elantech's driver was installed, returns 0. + */ + static PRInt32 GetDriverMajorVersion(); + + /** + * IsHelperWindow() checks whether aWnd is a helper window of Elantech's + * touchpad. Returns TRUE if so. Otherwise, FALSE. + */ + static bool IsHelperWindow(HWND aWnd); + + /** + * Key message handler for Elantech's hack. Returns TRUE if the message + * is consumed by this handler. Otherwise, FALSE. + */ + static bool HandleKeyMessage(nsWindow* aWindow, + UINT aMsg, + WPARAM aWParam); + + static void UpdateZoomUntil(); + static bool IsZooming(); + + static void Init(); + + static bool IsPinchHackNeeded() { return sUsePinchHack; } + + + private: + // Whether to enable the Elantech swipe gesture hack. + static bool sUseSwipeHack; + // Whether to enable the Elantech pinch-to-zoom gesture hack. + static bool sUsePinchHack; + static DWORD sZoomUntil; + }; // class Elantech + + class TrackPoint { + public: + /** + * IsDriverInstalled() returns TRUE if TrackPoint's driver is installed. + * Otherwise, returns FALSE. + */ + static bool IsDriverInstalled(); + }; // class TrackPoint + + class UltraNav { + public: + /** + * IsObsoleteDriverInstalled() checks whether obsoleted UltraNav + * is installed on the environment. + * Returns TRUE if it was installed. Otherwise, FALSE. + */ + static bool IsObsoleteDriverInstalled(); + }; // class UltraNav + + class SetPoint { + public: + /** + * SetPoint, Logitech's mouse driver, may report wrong cursor position + * for WM_MOUSEHWHEEL message. See comment in the implementation for + * the detail. + */ + static bool IsGetMessagePosResponseValid(UINT aMessage, + WPARAM aWParam, + LPARAM aLParam); + private: + static bool sMightBeUsing; + }; + + static void Init(); + + static bool IsFakeScrollableWindowNeeded() + { + return sFakeScrollableWindowNeeded; + } + + private: + /** + * Gets the bool value of aPrefName used to enable or disable an input + * workaround (like the Trackpoint hack). The pref can take values 0 (for + * disabled), 1 (for enabled) or -1 (to automatically detect whether to + * enable the workaround). + * + * @param aPrefName The name of the pref. + * @param aValueIfAutomatic Whether the given input workaround should be + * enabled by default. + */ + static bool GetWorkaroundPref(const char* aPrefName, + bool aValueIfAutomatic); + + static bool sFakeScrollableWindowNeeded; + }; // class Device +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_WinMouseScrollHandler_h__ \ No newline at end of file diff --git a/widget/windows/WinUtils.cpp b/widget/windows/WinUtils.cpp index d5763a81e90a..2c523dcb785b 100644 --- a/widget/windows/WinUtils.cpp +++ b/widget/windows/WinUtils.cpp @@ -122,6 +122,26 @@ WinUtils::GetRegistryKey(HKEY aRoot, return true; } +/* static */ +bool +WinUtils::HasRegistryKey(HKEY aRoot, const PRUnichar* aKeyName) +{ + MOZ_ASSERT(aRoot, "aRoot must not be NULL"); + MOZ_ASSERT(aKeyName, "aKeyName must not be NULL"); + HKEY key; + LONG result = + ::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_32KEY, &key); + if (result != ERROR_SUCCESS) { + result = + ::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_64KEY, &key); + if (result != ERROR_SUCCESS) { + return false; + } + } + ::RegCloseKey(key); + return true; +} + /* static */ HWND WinUtils::GetTopLevelHWND(HWND aWnd, diff --git a/widget/windows/WinUtils.h b/widget/windows/WinUtils.h index 74d52a565e10..d68dc9377807 100644 --- a/widget/windows/WinUtils.h +++ b/widget/windows/WinUtils.h @@ -93,6 +93,17 @@ public: PRUnichar* aBuffer, DWORD aBufferLength); + /** + * Checks whether the registry key exists in either 32bit or 64bit branch on + * the environment. + * + * @param aRoot The registry root of aName. + * @param aKeyName The name of the registry key to check. + * @return TRUE if it exists and is readable. Otherwise, FALSE. + */ + static bool HasRegistryKey(HKEY aRoot, + const PRUnichar* aKeyName); + /** * GetTopLevelHWND() returns a window handle of the top level window which * aWnd belongs to. Note that the result may not be our window, i.e., it diff --git a/widget/windows/nsWidgetFactory.cpp b/widget/windows/nsWidgetFactory.cpp index 001c4efe107d..dee19fd206e0 100644 --- a/widget/windows/nsWidgetFactory.cpp +++ b/widget/windows/nsWidgetFactory.cpp @@ -53,6 +53,7 @@ #include "nsScreenManagerWin.h" #include "nsSound.h" #include "nsWindow.h" +#include "WinMouseScrollHandler.h" #include "WinTaskbar.h" #include "JumpListBuilder.h" #include "JumpListItem.h" @@ -209,6 +210,7 @@ static const mozilla::Module::ContractIDEntry kWidgetContracts[] = { static void nsWidgetWindowsModuleDtor() { + MouseScrollHandler::Shutdown(); nsLookAndFeel::Shutdown(); nsToolkit::Shutdown(); nsAppShellShutdown(); diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp index 85750b47c14c..e499c5bcdbd1 100644 --- a/widget/windows/nsWindow.cpp +++ b/widget/windows/nsWindow.cpp @@ -133,6 +133,7 @@ #include "nsIServiceManager.h" #include "nsIClipboard.h" #include "nsIMM32Handler.h" +#include "WinMouseScrollHandler.h" #include "nsILocalFile.h" #include "nsFontMetrics.h" #include "nsIFontEnumerator.h" @@ -270,14 +271,8 @@ BYTE nsWindow::sLastMouseButton = 0; // Trim heap on minimize. (initialized, but still true.) int nsWindow::sTrimOnMinimize = 2; -// Default value for Trackpoint hack (used when the pref is set to -1). -bool nsWindow::sDefaultTrackPointHack = false; // Default value for general window class (used when the pref is the empty string). const char* nsWindow::sDefaultMainWindowClass = kClassNameGeneral; -// Whether to enable the Elantech swipe gesture hack. -bool nsWindow::sUseElantechSwipeHack = false; -// Whether to enable the Elantech pinch-to-zoom gesture hack. -bool nsWindow::sUseElantechPinchHack = false; // If we're using D3D9, this will not be allowed during initial 5 seconds. bool nsWindow::sAllowD3D9 = false; @@ -291,19 +286,6 @@ PRUint32 nsWindow::sOOPPPluginFocusEvent = MSG nsWindow::sRedirectedKeyDown; -bool nsWindow::sEnablePixelScrolling = true; -bool nsWindow::sNeedsToInitMouseWheelSettings = true; -ULONG nsWindow::sMouseWheelScrollLines = 0; -ULONG nsWindow::sMouseWheelScrollChars = 0; - -HWND nsWindow::sLastMouseWheelWnd = NULL; -PRInt32 nsWindow::sRemainingDeltaForScroll = 0; -PRInt32 nsWindow::sRemainingDeltaForPixel = 0; -bool nsWindow::sLastMouseWheelDeltaIsPositive = false; -bool nsWindow::sLastMouseWheelOrientationIsVertical = false; -bool nsWindow::sLastMouseWheelUnitIsPage = false; -PRUint32 nsWindow::sLastMouseWheelTime = 0; - /************************************************************** * * SECTION: globals variables @@ -406,7 +388,6 @@ nsWindow::nsWindow() : nsBaseWidget() mOldExStyle = 0; mPainting = 0; mLastKeyboardLayout = 0; - mAssumeWheelIsZoomUntil = 0; mBlurSuppressLevel = 0; mLastPaintEndTime = TimeStamp::Now(); #ifdef MOZ_XUL @@ -427,27 +408,21 @@ nsWindow::nsWindow() : nsBaseWidget() // Global app registration id for Win7 and up. See // WinTaskbar.cpp for details. mozilla::widget::WinTaskbar::RegisterAppUserModelID(); - gKbdLayout.LoadLayout(::GetKeyboardLayout(0)); - // Init IME handler nsIMM32Handler::Initialize(); - #ifdef NS_ENABLE_TSF nsTextStore::Initialize(); #endif - - if (SUCCEEDED(::OleInitialize(NULL))) + if (SUCCEEDED(::OleInitialize(NULL))) { sIsOleInitialized = TRUE; + } NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n"); - - InitInputWorkaroundPrefDefaults(); - + MouseScrollHandler::Initialize(); // Init titlebar button info for custom frames. nsUXThemeData::InitTitlebarInfo(); // Init theme data nsUXThemeData::UpdateNativeThemeInfo(); - ForgetRedirectedKeyDownMessage(); } // !sInstanceCount @@ -608,7 +583,7 @@ nsWindow::Create(nsIWidget *aParent, if (mWindowType != eWindowType_plugin && mWindowType != eWindowType_invisible && - UseTrackPointHack()) { + MouseScrollHandler::Device::IsFakeScrollableWindowNeeded()) { // Ugly Thinkpad Driver Hack (Bugs 507222 and 594977) // // We create two zero-sized windows as descendants of the top-level window, @@ -4494,24 +4469,6 @@ static bool CleartypeSettingChanged() bool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam, LRESULT *aRetValue) { - // For the Elantech Touchpad Zoom Gesture Hack, we should check that the - // system time (32-bit milliseconds) hasn't wrapped around. Otherwise we - // might get into the situation where wheel events for the next 50 days of - // system uptime are assumed to be Ctrl+Wheel events. (It is unlikely that - // we would get into that state, because the system would already need to be - // up for 50 days and the Control key message would need to be processed just - // before the system time overflow and the wheel message just after.) - // - // We also take the chance to reset mAssumeWheelIsZoomUntil if we simply have - // passed that time. - if (mAssumeWheelIsZoomUntil) { - LONG msgTime = ::GetMessageTime(); - if ((mAssumeWheelIsZoomUntil >= 0x3fffffffu && DWORD(msgTime) < 0x40000000u) || - (mAssumeWheelIsZoomUntil < DWORD(msgTime))) { - mAssumeWheelIsZoomUntil = 0; - } - } - // (Large blocks of code should be broken out into OnEvent handlers.) if (mWindowHook.Notify(mWnd, msg, wParam, lParam, aRetValue)) return true; @@ -4528,6 +4485,11 @@ bool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam, return mWnd ? eatMessage : true; } + if (MouseScrollHandler::ProcessMessage(this, msg, wParam, lParam, aRetValue, + eatMessage)) { + return mWnd ? eatMessage : true; + } + if (PluginHasFocus()) { bool callDefaultWndProc; MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam); @@ -5094,19 +5056,6 @@ bool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam, } break; - case WM_HSCROLL: - case WM_VSCROLL: - *aRetValue = 0; - result = OnScroll(msg, wParam, lParam); - break; - - case MOZ_WM_HSCROLL: - case MOZ_WM_VSCROLL: - *aRetValue = 0; - OnScrollInternal(WinUtils::GetNativeMessage(msg), wParam, lParam); - // Doesn't need to call next wndproc for internal message. - return true; - // The WM_ACTIVATE event is fired when a window is raised or lowered, // and the loword of wParam specifies which. But we don't want to tell // the focus system about this until the WM_SETFOCUS or WM_KILLFOCUS @@ -5188,15 +5137,6 @@ bool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam, } break; - case WM_SETTINGCHANGE: - switch (wParam) { - case SPI_SETWHEELSCROLLLINES: - case SPI_SETWHEELSCROLLCHARS: - sNeedsToInitMouseWheelSettings = true; - break; - } - break; - case WM_INPUTLANGCHANGEREQUEST: *aRetValue = TRUE; result = false; @@ -5260,29 +5200,6 @@ bool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam, } break; - case WM_MOUSEWHEEL: - case WM_MOUSEHWHEEL: - OnMouseWheel(msg, wParam, lParam, aRetValue); - // We don't need to call next wndproc WM_MOUSEWHEEL and WM_MOUSEHWHEEL. - // We should consume them always. If the messages would be handled by - // our window again, it causes making infinite message loop. - return true; - - case MOZ_WM_MOUSEVWHEEL: - case MOZ_WM_MOUSEHWHEEL: - { - UINT nativeMessage = WinUtils::GetNativeMessage(msg); - // If OnMouseWheel returns true, the event was forwarded directly to another - // mozilla window message handler (ProcessMessage). In this case the return - // value of the forwarded event is in 'result' which we should return immediately. - // If OnMouseWheel returns false, OnMouseWheel processed the event internally. - // 'result' and 'aRetValue' will be set based on what we did with the event, so - // we should fall through. - OnMouseWheelInternal(nativeMessage, wParam, lParam, aRetValue); - // Doesn't need to call next wndproc for internal message. - return true; - } - case WM_DWMCOMPOSITIONCHANGED: // First, update the compositor state to latest one. All other methods // should use same state as here for consistency painting. @@ -6305,277 +6222,6 @@ bool nsWindow::OnGesture(WPARAM wParam, LPARAM lParam) return true; // Handled } -/* static */ void -nsWindow::InitMouseWheelScrollData() -{ - if (!sNeedsToInitMouseWheelSettings) { - return; - } - sNeedsToInitMouseWheelSettings = false; - ResetRemainingWheelDelta(); - - if (!::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, - &sMouseWheelScrollLines, 0)) { - NS_WARNING("Failed to get SPI_GETWHEELSCROLLLINES"); - sMouseWheelScrollLines = 3; - } else if (sMouseWheelScrollLines > WHEEL_DELTA) { - // sMouseWheelScrollLines usually equals 3 or 0 (for no scrolling) - // However, if sMouseWheelScrollLines > WHEEL_DELTA, we assume that - // the mouse driver wants a page scroll. The docs state that - // sMouseWheelScrollLines should explicitly equal WHEEL_PAGESCROLL, but - // since some mouse drivers use an arbitrary large number instead, - // we have to handle that as well. - sMouseWheelScrollLines = WHEEL_PAGESCROLL; - } - - if (!::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, - &sMouseWheelScrollChars, 0)) { - NS_ASSERTION(WinUtils::GetWindowsVersion() < WinUtils::VISTA_VERSION, - "Failed to get SPI_GETWHEELSCROLLCHARS"); - sMouseWheelScrollChars = 1; - } else if (sMouseWheelScrollChars > WHEEL_DELTA) { - // See the comments for the case sMouseWheelScrollLines > WHEEL_DELTA. - sMouseWheelScrollChars = WHEEL_PAGESCROLL; - } - - sEnablePixelScrolling = - Preferences::GetBool("mousewheel.enable_pixel_scrolling", true); -} - -/* static */ -void -nsWindow::ResetRemainingWheelDelta() -{ - sRemainingDeltaForPixel = 0; - sRemainingDeltaForScroll = 0; - sLastMouseWheelWnd = NULL; -} - -static PRInt32 RoundDelta(double aDelta) -{ - return aDelta >= 0 ? (PRInt32)floor(aDelta) : (PRInt32)ceil(aDelta); -} - -/** - * OnMouseWheelInternal - mouse wheel event processing. - * aMessage may be WM_MOUSEWHEEL or WM_MOUSEHWHEEL but this is called when - * ProcessMessage() handles MOZ_WM_MOUSEVWHEEL or MOZ_WM_MOUSEHWHEEL. - */ -void -nsWindow::OnMouseWheelInternal(UINT aMessage, WPARAM aWParam, LPARAM aLParam, - LRESULT *aRetValue) -{ - InitMouseWheelScrollData(); - - bool isVertical = (aMessage == WM_MOUSEWHEEL); - if ((isVertical && sMouseWheelScrollLines == 0) || - (!isVertical && sMouseWheelScrollChars == 0)) { - // XXX I think that we should dispatch mouse wheel events even if the - // operation will not scroll because the wheel operation really happened - // and web application may want to handle the event for non-scroll action. - ResetRemainingWheelDelta(); - *aRetValue = isVertical ? TRUE : FALSE; // means we don't process it - return; - } - - PRInt32 nativeDelta = (short)HIWORD(aWParam); - if (!nativeDelta) { - *aRetValue = isVertical ? TRUE : FALSE; // means we don't process it - ResetRemainingWheelDelta(); - return; // We cannot process this message - } - - bool isPageScroll = - ((isVertical && sMouseWheelScrollLines == WHEEL_PAGESCROLL) || - (!isVertical && sMouseWheelScrollChars == WHEEL_PAGESCROLL)); - - // Discard the remaining delta if current wheel message and last one are - // received by different window or to scroll different direction or - // different unit scroll. Furthermore, if the last event was too old. - PRUint32 now = PR_IntervalToMilliseconds(PR_IntervalNow()); - if (sLastMouseWheelWnd && - (sLastMouseWheelWnd != mWnd || - sLastMouseWheelDeltaIsPositive != (nativeDelta > 0) || - sLastMouseWheelOrientationIsVertical != isVertical || - sLastMouseWheelUnitIsPage != isPageScroll || - now - sLastMouseWheelTime > 1500)) { - ResetRemainingWheelDelta(); - } - sLastMouseWheelWnd = mWnd; - sLastMouseWheelDeltaIsPositive = (nativeDelta > 0); - sLastMouseWheelOrientationIsVertical = isVertical; - sLastMouseWheelUnitIsPage = isPageScroll; - sLastMouseWheelTime = now; - - *aRetValue = isVertical ? FALSE : TRUE; // means we process this message - nsModifierKeyState modKeyState; - - // Our positive delta value means to bottom or right. - // But positive nativeDelta value means to top or right. - // Use orienter for computing our delta value with native delta value. - PRInt32 orienter = isVertical ? -1 : 1; - - // Assume the Control key is down if the Elantech touchpad has sent the - // mis-ordered WM_KEYDOWN/WM_MOUSEWHEEL messages. (See the comment in - // OnKeyUp.) - bool isControl; - if (mAssumeWheelIsZoomUntil && - static_cast(::GetMessageTime()) < mAssumeWheelIsZoomUntil) { - isControl = true; - } else { - isControl = modKeyState.mIsControlDown; - } - - // Create line (or page) scroll event. - nsMouseScrollEvent scrollEvent(true, NS_MOUSE_SCROLL, this); - - // Initialize common members on line scroll event, pixel scroll event and - // test event. - InitEvent(scrollEvent); - scrollEvent.isShift = modKeyState.mIsShiftDown; - scrollEvent.isControl = isControl; - scrollEvent.isMeta = false; - scrollEvent.isAlt = modKeyState.mIsAltDown; - - // Before dispatching line scroll event, we should get the current scroll - // event target information for pixel scroll. - bool dispatchPixelScrollEvent = false; - bool reversePixelScrollDirection = false; - PRInt32 actualScrollAction = nsQueryContentEvent::SCROLL_ACTION_NONE; - PRInt32 pixelsPerUnit = 0; - // the amount is the number of lines (or pages) per WHEEL_DELTA - PRInt32 computedScrollAmount = isPageScroll ? 1 : - (isVertical ? sMouseWheelScrollLines : sMouseWheelScrollChars); - - if (sEnablePixelScrolling) { - nsMouseScrollEvent testEvent(true, NS_MOUSE_SCROLL, this); - InitEvent(testEvent); - testEvent.scrollFlags = isPageScroll ? nsMouseScrollEvent::kIsFullPage : 0; - testEvent.scrollFlags |= isVertical ? nsMouseScrollEvent::kIsVertical : - nsMouseScrollEvent::kIsHorizontal; - testEvent.isShift = scrollEvent.isShift; - testEvent.isControl = scrollEvent.isControl; - testEvent.isMeta = scrollEvent.isMeta; - testEvent.isAlt = scrollEvent.isAlt; - - testEvent.delta = computedScrollAmount; - if ((isVertical && sLastMouseWheelDeltaIsPositive) || - (!isVertical && !sLastMouseWheelDeltaIsPositive)) { - testEvent.delta *= -1; - } - nsQueryContentEvent queryEvent(true, NS_QUERY_SCROLL_TARGET_INFO, this); - InitEvent(queryEvent); - queryEvent.InitForQueryScrollTargetInfo(&testEvent); - DispatchWindowEvent(&queryEvent); - // If the necessary interger isn't larger than 0, we should assume that - // the event failed for us. - if (queryEvent.mSucceeded) { - actualScrollAction = queryEvent.mReply.mComputedScrollAction; - if (actualScrollAction == nsQueryContentEvent::SCROLL_ACTION_PAGE) { - if (isVertical) { - pixelsPerUnit = queryEvent.mReply.mPageHeight; - } else { - pixelsPerUnit = queryEvent.mReply.mPageWidth; - } - } else { - pixelsPerUnit = queryEvent.mReply.mLineHeight; - } - computedScrollAmount = queryEvent.mReply.mComputedScrollAmount; - if (pixelsPerUnit > 0 && computedScrollAmount != 0 && - actualScrollAction != nsQueryContentEvent::SCROLL_ACTION_NONE) { - dispatchPixelScrollEvent = true; - // If original delta's sign and computed delta's one are different, - // we need to reverse the pixel scroll direction at dispatching it. - reversePixelScrollDirection = - (testEvent.delta > 0 && computedScrollAmount < 0) || - (testEvent.delta < 0 && computedScrollAmount > 0); - // scroll amount must be positive. - computedScrollAmount = NS_ABS(computedScrollAmount); - } - } - } - - // If we dispatch pixel scroll event after the line scroll event, - // we should set kHasPixels flag to the line scroll event. - scrollEvent.scrollFlags = - dispatchPixelScrollEvent ? nsMouseScrollEvent::kHasPixels : 0; - - PRInt32 nativeDeltaForScroll = nativeDelta + sRemainingDeltaForScroll; - - // NOTE: Don't use computedScrollAmount for computing the delta value of - // line/page scroll event. The value will be recomputed in ESM. - if (isPageScroll) { - scrollEvent.scrollFlags |= nsMouseScrollEvent::kIsFullPage; - if (isVertical) { - scrollEvent.scrollFlags |= nsMouseScrollEvent::kIsVertical; - } else { - scrollEvent.scrollFlags |= nsMouseScrollEvent::kIsHorizontal; - } - scrollEvent.delta = nativeDeltaForScroll * orienter / WHEEL_DELTA; - PRInt32 recomputedNativeDelta = scrollEvent.delta * orienter / WHEEL_DELTA; - sRemainingDeltaForScroll = nativeDeltaForScroll - recomputedNativeDelta; - } else { - double deltaPerUnit; - if (isVertical) { - scrollEvent.scrollFlags |= nsMouseScrollEvent::kIsVertical; - deltaPerUnit = (double)WHEEL_DELTA / sMouseWheelScrollLines; - } else { - scrollEvent.scrollFlags |= nsMouseScrollEvent::kIsHorizontal; - deltaPerUnit = (double)WHEEL_DELTA / sMouseWheelScrollChars; - } - scrollEvent.delta = - RoundDelta((double)nativeDeltaForScroll * orienter / deltaPerUnit); - PRInt32 recomputedNativeDelta = - (PRInt32)(scrollEvent.delta * orienter * deltaPerUnit); - sRemainingDeltaForScroll = nativeDeltaForScroll - recomputedNativeDelta; - } - - if (scrollEvent.delta) { - DispatchWindowEvent(&scrollEvent); - if (mOnDestroyCalled) { - ResetRemainingWheelDelta(); - return; - } - } - - // If the query event failed, we cannot send pixel events. - if (!dispatchPixelScrollEvent) { - sRemainingDeltaForPixel = 0; - return; - } - - nsMouseScrollEvent pixelEvent(true, NS_MOUSE_PIXEL_SCROLL, this); - InitEvent(pixelEvent); - pixelEvent.scrollFlags = nsMouseScrollEvent::kAllowSmoothScroll; - pixelEvent.scrollFlags |= isVertical ? - nsMouseScrollEvent::kIsVertical : nsMouseScrollEvent::kIsHorizontal; - if (actualScrollAction == nsQueryContentEvent::SCROLL_ACTION_PAGE) { - pixelEvent.scrollFlags |= nsMouseScrollEvent::kIsFullPage; - } - // Use same modifier state for pixel scroll event. - pixelEvent.isShift = scrollEvent.isShift; - pixelEvent.isControl = scrollEvent.isControl; - pixelEvent.isMeta = scrollEvent.isMeta; - pixelEvent.isAlt = scrollEvent.isAlt; - - PRInt32 nativeDeltaForPixel = nativeDelta + sRemainingDeltaForPixel; - // Pixel scroll event won't be recomputed the scroll amout and direction by - // ESM. Therefore, we need to set the computed amout and direction here. - PRInt32 orienterForPixel = reversePixelScrollDirection ? -orienter : orienter; - - double deltaPerPixel = - (double)WHEEL_DELTA / computedScrollAmount / pixelsPerUnit; - pixelEvent.delta = - RoundDelta((double)nativeDeltaForPixel * orienterForPixel / deltaPerPixel); - PRInt32 recomputedNativeDelta = - (PRInt32)(pixelEvent.delta * orienterForPixel * deltaPerPixel); - sRemainingDeltaForPixel = nativeDeltaForPixel - recomputedNativeDelta; - if (pixelEvent.delta != 0) { - DispatchWindowEvent(&pixelEvent); - } - return; -} - static bool StringCaseInsensitiveEquals(const PRUnichar* aChars1, const PRUint32 aNumChars1, const PRUnichar* aChars2, const PRUint32 aNumChars2) @@ -6606,37 +6252,6 @@ bool nsWindow::IsRedirectedKeyDownMessage(const MSG &aMsg) WinUtils::GetScanCode(aMsg.lParam)); } -void -nsWindow::PerformElantechSwipeGestureHack(UINT& aVirtualKeyCode, - nsModifierKeyState& aModKeyState) -{ - // The Elantech touchpad driver understands three-finger swipe left and - // right gestures, and translates them into Page Up and Page Down key - // events for most applications. For Firefox 3.6, it instead sends - // Alt+Left and Alt+Right to trigger browser back/forward actions. As - // with the Thinkpad Driver hack in nsWindow::Create, the change in - // HWND structure makes Firefox not trigger the driver's heuristics - // any longer. - // - // The Elantech driver actually sends these messages for a three-finger - // swipe right: - // - // WM_KEYDOWN virtual_key = 0xCC or 0xFF (depending on driver version) - // WM_KEYDOWN virtual_key = VK_NEXT - // WM_KEYUP virtual_key = VK_NEXT - // WM_KEYUP virtual_key = 0xCC or 0xFF - // - // so we use the 0xCC or 0xFF key modifier to detect whether the Page Down - // is due to the gesture rather than a regular Page Down keypress. We then - // pretend that we were went an Alt+Right keystroke instead. Similarly - // for VK_PRIOR and Alt+Left. - if ((aVirtualKeyCode == VK_NEXT || aVirtualKeyCode == VK_PRIOR) && - (IS_VK_DOWN(0xFF) || IS_VK_DOWN(0xCC))) { - aModKeyState.mIsAltDown = true; - aVirtualKeyCode = aVirtualKeyCode == VK_NEXT ? VK_RIGHT : VK_LEFT; - } -} - /** * nsWindow::OnKeyDown peeks into the message queue and pulls out * WM_CHAR messages for processing. During testing we don't want to @@ -6654,10 +6269,6 @@ LRESULT nsWindow::OnKeyDown(const MSG &aMsg, aMsg.wParam != VK_PROCESSKEY ? aMsg.wParam : ::ImmGetVirtualKey(mWnd); gKbdLayout.OnKeyDown(virtualKeyCode); - if (sUseElantechSwipeHack) { - PerformElantechSwipeGestureHack(virtualKeyCode, aModKeyState); - } - // Use only DOMKeyCode for XP processing. // Use virtualKeyCode for gKbdLayout and native processing. UINT DOMKeyCode = nsIMM32Handler::IsComposingOn(this) ? @@ -6999,34 +6610,6 @@ LRESULT nsWindow::OnKeyUp(const MSG &aMsg, { UINT virtualKeyCode = aMsg.wParam; - if (sUseElantechSwipeHack) { - PerformElantechSwipeGestureHack(virtualKeyCode, aModKeyState); - } - - if (sUseElantechPinchHack) { - // Version 8 of the Elantech touchpad driver sends these messages for - // zoom gestures: - // - // WM_KEYDOWN virtual_key = 0xCC time = 10 - // WM_KEYDOWN virtual_key = VK_CONTROL time = 10 - // WM_MOUSEWHEEL time = ::GetTickCount() - // WM_KEYUP virtual_key = VK_CONTROL time = 10 - // WM_KEYUP virtual_key = 0xCC time = 10 - // - // The result of this is that we process all of the WM_KEYDOWN/WM_KEYUP - // messages first because their timestamps make them appear to have - // been sent before the WM_MOUSEWHEEL message. To work around this, - // we store the current time when we process the WM_KEYUP message and - // assume that any WM_MOUSEWHEEL message with a timestamp before that - // time is one that should be processed as if the Control key was down. - if (virtualKeyCode == VK_CONTROL && aMsg.time == 10) { - // We look only at the bottom 31 bits of the system tick count since - // GetMessageTime returns a LONG, which is signed, so we want values - // that are more easily comparable. - mAssumeWheelIsZoomUntil = ::GetTickCount() & 0x7FFFFFFF; - } - } - PR_LOG(gWindowsLog, PR_LOG_ALWAYS, ("nsWindow::OnKeyUp VK=%d\n", virtualKeyCode)); @@ -7433,259 +7016,6 @@ bool nsWindow::OnHotKey(WPARAM wParam, LPARAM lParam) return true; } -// Determine whether the given HWND is the handle for the Elantech helper -// window. The helper window cannot be distinguished based on its -// window class, so we need to check if it is owned by the helper process, -// ETDCtrl.exe. -static bool IsElantechHelperWindow(HWND aHWND) -{ - const PRUnichar* filenameSuffix = L"\\etdctrl.exe"; - const int filenameSuffixLength = 12; - - DWORD pid; - ::GetWindowThreadProcessId(aHWND, &pid); - - bool result = false; - - HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); - if (hProcess) { - PRUnichar path[256] = {L'\0'}; - if (GetProcessImageFileName(hProcess, path, ArrayLength(path))) { - int pathLength = lstrlenW(path); - if (pathLength >= filenameSuffixLength) { - if (lstrcmpiW(path + pathLength - filenameSuffixLength, filenameSuffix) == 0) { - result = true; - } - } - } - ::CloseHandle(hProcess); - } - - return result; -} - -/** - * OnMouseWheel() is called when ProcessMessage() handles WM_MOUSEWHEEL, - * WM_MOUSEHWHEEL and also OnScroll() tries to emulate mouse wheel action for - * WM_VSCROLL or WM_HSCROLL. - * So, aMsg may be WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL or WM_HSCROLL. - */ -void -nsWindow::OnMouseWheel(UINT aMsg, WPARAM aWParam, LPARAM aLParam, - LRESULT *aRetValue) -{ - *aRetValue = (aMsg != WM_MOUSEHWHEEL) ? TRUE : FALSE; - - POINT point; - DWORD dwPoints = ::GetMessagePos(); - point.x = GET_X_LPARAM(dwPoints); - point.y = GET_Y_LPARAM(dwPoints); - - static bool sMayBeUsingLogitechMouse = false; - if (aMsg == WM_MOUSEHWHEEL) { - // Logitech (Logicool) mouse driver (confirmed with 4.82.11 and MX-1100) - // always sets 0 to the lParam of WM_MOUSEHWHEEL. The driver SENDs one - // message at first time, this time, ::GetMessagePos works fine. - // Then, we will return 0 (0 means we process it) to the message. Then, the - // driver will POST the same messages continuously during the wheel tilted. - // But ::GetMessagePos API always returns (0, 0), even if the actual mouse - // cursor isn't 0,0. Therefore, we cannot trust the result of - // ::GetMessagePos API if the sender is the driver. - if (!sMayBeUsingLogitechMouse && aLParam == 0 && (DWORD)aLParam != dwPoints && - ::InSendMessage()) { - sMayBeUsingLogitechMouse = true; - } else if (sMayBeUsingLogitechMouse && aLParam != 0 && ::InSendMessage()) { - // The user has changed the mouse from Logitech's to another one (e.g., - // the user has changed to the touchpad of the notebook. - sMayBeUsingLogitechMouse = false; - } - // If the WM_MOUSEHWHEEL comes from Logitech's mouse driver, and the - // ::GetMessagePos isn't correct, probably, we should use ::GetCursorPos - // instead. - if (sMayBeUsingLogitechMouse && aLParam == 0 && dwPoints == 0) { - ::GetCursorPos(&point); - } - } - - HWND underCursorWnd = ::WindowFromPoint(point); - if (!underCursorWnd) { - return; - } - - if (sUseElantechPinchHack && IsElantechHelperWindow(underCursorWnd)) { - // The Elantech driver places a window right underneath the cursor - // when sending a WM_MOUSEWHEEL event to us as part of a pinch-to-zoom - // gesture. We detect that here, and search for our window that would - // be beneath the cursor if that window wasn't there. - underCursorWnd = WinUtils::FindOurWindowAtPoint(point); - if (!underCursorWnd) { - return; - } - } - - // Handle most cases first. If the window under mouse cursor is our window - // except plugin window (MozillaWindowClass), we should handle the message - // on the window. - if (WinUtils::IsOurProcessWindow(underCursorWnd)) { - nsWindow* destWindow = WinUtils::GetNSWindowPtr(underCursorWnd); - if (!destWindow) { - NS_WARNING("We're not sure what cause this is."); - HWND wnd = ::GetParent(underCursorWnd); - for (; wnd; wnd = ::GetParent(wnd)) { - destWindow = WinUtils::GetNSWindowPtr(wnd); - if (destWindow) { - break; - } - } - if (!wnd) { - return; - } - } - - NS_ASSERTION(destWindow, "destWindow must not be NULL"); - // If the found window is our plugin window, it means that the message - // has been handled by the plugin but not consumed. We should handle the - // message on its parent window. However, note that the DOM event may - // cause accessing the plugin. Therefore, we should unlock the plugin - // process by using PostMessage(). - if (destWindow->mWindowType == eWindowType_plugin) { - destWindow = destWindow->GetParentWindow(false); - NS_ENSURE_TRUE(destWindow, ); - } - UINT internalMessage = WinUtils::GetInternalMessage(aMsg); - ::PostMessage(destWindow->mWnd, internalMessage, aWParam, aLParam); - return; - } - - // If the window under cursor is not in our process, it means: - // 1. The window may be a plugin window (GeckoPluginWindow or its descendant). - // 2. The window may be another application's window. - HWND pluginWnd = WinUtils::FindOurProcessWindow(underCursorWnd); - if (!pluginWnd) { - // If there is no plugin window in ancestors of the window under cursor, - // the window is for another applications (case 2). - // We don't need to handle this message. - return; - } - - // If we're a plugin window (MozillaWindowClass) and cursor in this window, - // the message shouldn't go to plugin's wndproc again. So, we should handle - // it on parent window. However, note that the DOM event may cause accessing - // the plugin. Therefore, we should unlock the plugin process by using - // PostMessage(). - if (mWindowType == eWindowType_plugin && pluginWnd == mWnd) { - nsWindow* destWindow = GetParentWindow(false); - NS_ENSURE_TRUE(destWindow, ); - UINT internalMessage = WinUtils::GetInternalMessage(aMsg); - ::PostMessage(destWindow->mWnd, internalMessage, aWParam, aLParam); - return; - } - - // If the window is a part of plugin, we should post the message to it. - ::PostMessage(underCursorWnd, aMsg, aWParam, aLParam); -} - -/** - * OnScroll() is called when ProcessMessage() handles WM_VSCROLL or WM_HSCROLL. - * aMsg may be WM_VSCROLL or WM_HSCROLL. - */ -bool -nsWindow::OnScroll(UINT aMsg, WPARAM aWParam, LPARAM aLParam) -{ - static PRInt8 sMouseWheelEmulation = -1; - if (sMouseWheelEmulation < 0) { - bool emulate = - Preferences::GetBool("mousewheel.emulate_at_wm_scroll", false); - sMouseWheelEmulation = PRInt8(emulate); - } - - if (aLParam || sMouseWheelEmulation) { - // Scroll message generated by Thinkpad Trackpoint Driver or similar - // Treat as a mousewheel message and scroll appropriately - LRESULT retVal; - OnMouseWheel(aMsg, aWParam, aLParam, &retVal); - // Always consume the scroll message if we try to emulate mouse wheel - // action. - return true; - } - - // Scroll message generated by external application - nsContentCommandEvent command(true, NS_CONTENT_COMMAND_SCROLL, this); - - command.mScroll.mIsHorizontal = (aMsg == WM_HSCROLL); - - switch (LOWORD(aWParam)) - { - case SB_LINEUP: // SB_LINELEFT - command.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Line; - command.mScroll.mAmount = -1; - break; - case SB_LINEDOWN: // SB_LINERIGHT - command.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Line; - command.mScroll.mAmount = 1; - break; - case SB_PAGEUP: // SB_PAGELEFT - command.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Page; - command.mScroll.mAmount = -1; - break; - case SB_PAGEDOWN: // SB_PAGERIGHT - command.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Page; - command.mScroll.mAmount = 1; - break; - case SB_TOP: // SB_LEFT - command.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Whole; - command.mScroll.mAmount = -1; - break; - case SB_BOTTOM: // SB_RIGHT - command.mScroll.mUnit = nsContentCommandEvent::eCmdScrollUnit_Whole; - command.mScroll.mAmount = 1; - break; - default: - return false; - } - // XXX If this is a plugin window, we should dispatch the event from - // parent window. - DispatchWindowEvent(&command); - return true; -} - -/** - * OnScrollInternal() is called when ProcessMessage() handles MOZ_WM_VSCROLL or - * MOZ_WM_HSCROLL but aMsg may be WM_VSCROLL or WM_HSCROLL. - * These internal messages used only when OnScroll() tries to emulate mouse - * wheel action for the WM_VSCROLL or WM_HSCROLL message. - */ -void -nsWindow::OnScrollInternal(UINT aMsg, WPARAM aWParam, LPARAM aLParam) -{ - nsMouseScrollEvent scrollevent(true, NS_MOUSE_SCROLL, this); - scrollevent.scrollFlags = (aMsg == WM_VSCROLL) - ? nsMouseScrollEvent::kIsVertical - : nsMouseScrollEvent::kIsHorizontal; - switch (LOWORD(aWParam)) { - case SB_PAGEDOWN: - scrollevent.scrollFlags |= nsMouseScrollEvent::kIsFullPage; - case SB_LINEDOWN: - scrollevent.delta = 1; - break; - case SB_PAGEUP: - scrollevent.scrollFlags |= nsMouseScrollEvent::kIsFullPage; - case SB_LINEUP: - scrollevent.delta = -1; - break; - default: - return; - } - scrollevent.isShift = IS_VK_DOWN(NS_VK_SHIFT); - scrollevent.isControl = IS_VK_DOWN(NS_VK_CONTROL); - scrollevent.isMeta = false; - scrollevent.isAlt = IS_VK_DOWN(NS_VK_ALT); - InitEvent(scrollevent); - if (mEventCallback) { - DispatchWindowEvent(&scrollevent); - } -} - // Can be overriden. Controls auto-erase of background. bool nsWindow::AutoErase(HDC dc) { @@ -8587,6 +7917,14 @@ nsModifierKeyState::nsModifierKeyState() mIsAltDown = IS_VK_DOWN(NS_VK_ALT); } +void +nsModifierKeyState::InitInputEvent(nsInputEvent& aInputEvent) const +{ + aInputEvent.isShift = mIsShiftDown; + aInputEvent.isControl = mIsControlDown; + aInputEvent.isMeta = false; + aInputEvent.isAlt = mIsAltDown; +} // Note that the result of GetTopLevelWindow method can be different from the // result of WinUtils::GetTopLevelHWND(). The result can be non-floating @@ -8658,132 +7996,6 @@ void nsWindow::GetMainWindowClass(nsAString& aClass) } } -/** - * Gets the Boolean value of a pref used to enable or disable an input - * workaround (like the Trackpoint hack). The pref can take values 0 (for - * disabled), 1 (for enabled) or -1 (to automatically detect whether to - * enable the workaround). - * - * @param aPrefName The name of the pref. - * @param aValueIfAutomatic Whether the given input workaround should be - * enabled by default. - */ -bool nsWindow::GetInputWorkaroundPref(const char* aPrefName, - bool aValueIfAutomatic) -{ - if (!aPrefName) { - return aValueIfAutomatic; - } - - PRInt32 lHackValue = 0; - if (NS_SUCCEEDED(Preferences::GetInt(aPrefName, &lHackValue))) { - switch (lHackValue) { - case 0: // disabled - return false; - case 1: // enabled - return true; - default: // -1: autodetect - break; - } - } - return aValueIfAutomatic; -} - -bool nsWindow::UseTrackPointHack() -{ - return GetInputWorkaroundPref("ui.trackpoint_hack.enabled", - sDefaultTrackPointHack); -} - -static bool -HasRegistryKey(HKEY aRoot, PRUnichar* aName) -{ - HKEY key; - LONG result = ::RegOpenKeyExW(aRoot, aName, 0, KEY_READ | KEY_WOW64_32KEY, &key); - if (result != ERROR_SUCCESS) { - result = ::RegOpenKeyExW(aRoot, aName, 0, KEY_READ | KEY_WOW64_64KEY, &key); - if (result != ERROR_SUCCESS) - return false; - } - ::RegCloseKey(key); - return true; -} - -static bool -IsObsoleteSynapticsDriver() -{ - PRUnichar buf[40]; - bool foundKey = WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, - L"Software\\Synaptics\\SynTP\\Install", - L"DriverVersion", - buf, - sizeof buf); - if (!foundKey) - return false; - - int majorVersion = wcstol(buf, NULL, 10); - int minorVersion = 0; - PRUnichar* p = wcschr(buf, L'.'); - if (p) { - minorVersion = wcstol(p + 1, NULL, 10); - } - return majorVersion < 15 || majorVersion == 15 && minorVersion == 0; -} - -static PRInt32 -GetElantechDriverMajorVersion() -{ - PRUnichar buf[40]; - // The driver version is found in one of these two registry keys. - bool foundKey = WinUtils::GetRegistryKey(HKEY_CURRENT_USER, - L"Software\\Elantech\\MainOption", - L"DriverVersion", - buf, - sizeof buf); - if (!foundKey) - foundKey = WinUtils::GetRegistryKey(HKEY_CURRENT_USER, - L"Software\\Elantech", - L"DriverVersion", - buf, - sizeof buf); - - if (!foundKey) - return false; - - // Assume that the major version number can be found just after a space - // or at the start of the string. - for (PRUnichar* p = buf; *p; p++) { - if (*p >= L'0' && *p <= L'9' && (p == buf || *(p - 1) == L' ')) { - return wcstol(p, NULL, 10); - } - } - - return 0; -} - -void nsWindow::InitInputWorkaroundPrefDefaults() -{ - PRUint32 elantechDriverVersion = GetElantechDriverMajorVersion(); - - if (HasRegistryKey(HKEY_CURRENT_USER, L"Software\\Lenovo\\TrackPoint")) { - sDefaultTrackPointHack = true; - } else if (HasRegistryKey(HKEY_CURRENT_USER, L"Software\\Lenovo\\UltraNav")) { - sDefaultTrackPointHack = true; - } else if (HasRegistryKey(HKEY_CURRENT_USER, L"Software\\Alps\\Apoint\\TrackPoint")) { - sDefaultTrackPointHack = true; - } else if ((HasRegistryKey(HKEY_CURRENT_USER, L"Software\\Synaptics\\SynTPEnh\\UltraNavUSB") || - HasRegistryKey(HKEY_CURRENT_USER, L"Software\\Synaptics\\SynTPEnh\\UltraNavPS2")) && - IsObsoleteSynapticsDriver()) { - sDefaultTrackPointHack = true; - } - - bool useElantechGestureHacks = - GetInputWorkaroundPref("ui.elantech_gesture_hacks.enabled", - elantechDriverVersion != 0); - sUseElantechSwipeHack = useElantechGestureHacks && elantechDriverVersion <= 7; - sUseElantechPinchHack = useElantechGestureHacks && elantechDriverVersion <= 8; -} - LPARAM nsWindow::lParamToScreen(LPARAM lParam) { POINT pt; diff --git a/widget/windows/nsWindow.h b/widget/windows/nsWindow.h index f9ba80a5db2d..d7690d23e7c7 100644 --- a/widget/windows/nsWindow.h +++ b/widget/windows/nsWindow.h @@ -334,10 +334,6 @@ protected: void ResetLayout(); void InvalidateNonClientRegion(); HRGN ExcludeNonClientFromPaintRegion(HRGN aRegion); - static void InitInputWorkaroundPrefDefaults(); - static bool GetInputWorkaroundPref(const char* aPrefName, bool aValueIfAutomatic); - static bool UseTrackPointHack(); - static void PerformElantechSwipeGestureHack(UINT& aVirtualKeyCode, nsModifierKeyState& aModKeyState); static void GetMainWindowClass(nsAString& aClass); bool HasGlass() const { return mTransparencyMode == eTransparencyGlass || @@ -400,20 +396,12 @@ protected: PRUint32 aFlags = 0, const MSG *aMsg = nsnull, bool *aEventDispatched = nsnull); - bool OnScroll(UINT aMsg, WPARAM aWParam, LPARAM aLParam); - void OnScrollInternal(UINT aMsg, WPARAM aWParam, - LPARAM aLParam); bool OnGesture(WPARAM wParam, LPARAM lParam); bool OnTouch(WPARAM wParam, LPARAM lParam); bool OnHotKey(WPARAM wParam, LPARAM lParam); BOOL OnInputLangChange(HKL aHKL); bool OnPaint(HDC aDC, PRUint32 aNestingLevel); void OnWindowPosChanged(WINDOWPOS *wp, bool& aResult); - void OnMouseWheel(UINT aMsg, WPARAM aWParam, - LPARAM aLParam, LRESULT *aRetValue); - void OnMouseWheelInternal(UINT aMessage, WPARAM aWParam, - LPARAM aLParam, - LRESULT *aRetValue); void OnWindowPosChanging(LPWINDOWPOS& info); void OnSysColorChanged(); @@ -522,10 +510,7 @@ protected: static bool sJustGotActivate; static bool sIsInMouseCapture; static int sTrimOnMinimize; - static bool sDefaultTrackPointHack; static const char* sDefaultMainWindowClass; - static bool sUseElantechSwipeHack; - static bool sUseElantechPinchHack; static bool sAllowD3D9; // Always use the helper method to read this property. See bug 603793. @@ -608,21 +593,9 @@ protected: // was reirected to SendInput() API by OnKeyDown(). static MSG sRedirectedKeyDown; - static bool sEnablePixelScrolling; static bool sNeedsToInitMouseWheelSettings; - static ULONG sMouseWheelScrollLines; - static ULONG sMouseWheelScrollChars; static void InitMouseWheelScrollData(); - static HWND sLastMouseWheelWnd; - static PRInt32 sRemainingDeltaForScroll; - static PRInt32 sRemainingDeltaForPixel; - static bool sLastMouseWheelDeltaIsPositive; - static bool sLastMouseWheelOrientationIsVertical; - static bool sLastMouseWheelUnitIsPage; - static PRUint32 sLastMouseWheelTime; // in milliseconds - static void ResetRemainingWheelDelta(); - // If a window receives WM_KEYDOWN message or WM_SYSKEYDOWM message which is // redirected message, OnKeyDowm() prevents to dispatch NS_KEY_DOWN event // because it has been dispatched before the message was redirected. diff --git a/widget/windows/nsWindowDefs.h b/widget/windows/nsWindowDefs.h index 40bdf221539b..ad9d86448627 100644 --- a/widget/windows/nsWindowDefs.h +++ b/widget/windows/nsWindowDefs.h @@ -266,6 +266,8 @@ struct nsModifierKeyState { mIsAltDown(aIsAltDown) { } + + void InitInputEvent(nsInputEvent& aInputEvent) const; }; // Used for synthesizing events diff --git a/widget/xpwidgets/GfxInfoX11.cpp b/widget/xpwidgets/GfxInfoX11.cpp index bb0ea7ebbe4a..a0f58486a1fe 100644 --- a/widget/xpwidgets/GfxInfoX11.cpp +++ b/widget/xpwidgets/GfxInfoX11.cpp @@ -283,6 +283,8 @@ GfxInfo::GetFeatureStatusImpl(PRInt32 aFeature, OperatingSystem* aOS /* = nsnull */) { + GetData(); + NS_ENSURE_ARG_POINTER(aStatus); *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN; aSuggestedDriverVersion.SetIsVoid(true); @@ -310,7 +312,6 @@ GfxInfo::GetFeatureStatusImpl(PRInt32 aFeature, if (aFeature == nsIGfxInfo::FEATURE_OPENGL_LAYERS || aFeature == nsIGfxInfo::FEATURE_WEBGL_OPENGL || aFeature == nsIGfxInfo::FEATURE_WEBGL_MSAA) { - GetData(); // Disable OpenGL layers when we don't have texture_from_pixmap because it regresses performance. if (aFeature == nsIGfxInfo::FEATURE_OPENGL_LAYERS && !mHasTextureFromPixmap) { diff --git a/widget/xpwidgets/nsBaseWidget.h b/widget/xpwidgets/nsBaseWidget.h index 44895698b7f0..c367c7f14fc1 100644 --- a/widget/xpwidgets/nsBaseWidget.h +++ b/widget/xpwidgets/nsBaseWidget.h @@ -237,6 +237,8 @@ public: bool Destroyed() { return mOnDestroyCalled; } + nsWindowType GetWindowType() { return mWindowType; } + protected: virtual void ResolveIconName(const nsAString &aIconName, diff --git a/widget/xpwidgets/nsIdleService.cpp b/widget/xpwidgets/nsIdleService.cpp index e4b0b49e4f4f..fd39230251e5 100644 --- a/widget/xpwidgets/nsIdleService.cpp +++ b/widget/xpwidgets/nsIdleService.cpp @@ -81,13 +81,29 @@ public: //////////////////////////////////////////////////////////////////////////////// //// nsIdleServiceDaily -NS_IMPL_ISUPPORTS1(nsIdleServiceDaily, nsIObserver) +NS_IMPL_ISUPPORTS2(nsIdleServiceDaily, nsIObserver, nsISupportsWeakReference) NS_IMETHODIMP nsIdleServiceDaily::Observe(nsISupports *, - const char *, + const char *aTopic, const PRUnichar *) { + if (strcmp(aTopic, "profile-after-change") == 0) { + // We are back. Start sending notifications again. + mShutdownInProgress = false; + return NS_OK; + } + + if (strcmp(aTopic, "xpcom-will-shutdown") == 0 || + strcmp(aTopic, "profile-change-teardown") == 0) { + mShutdownInProgress = true; + } + + if (mShutdownInProgress || strcmp(aTopic, OBSERVER_TOPIC_BACK) == 0) { + return NS_OK; + } + MOZ_ASSERT(strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0); + // Notify anyone who cares. nsCOMPtr observerService = mozilla::services::GetObserverService(); @@ -123,6 +139,7 @@ nsIdleServiceDaily::nsIdleServiceDaily(nsIIdleService* aIdleService) : mIdleService(aIdleService) , mTimer(do_CreateInstance(NS_TIMER_CONTRACTID)) , mCategoryObservers(OBSERVER_TOPIC_IDLE_DAILY) + , mShutdownInProgress(false) { } @@ -150,6 +167,14 @@ nsIdleServiceDaily::Init() SECONDS_PER_DAY * PR_MSEC_PER_SEC, nsITimer::TYPE_ONE_SHOT); } + + // Register for when we should terminate/pause + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, "xpcom-will-shutdown", true); + obs->AddObserver(this, "profile-change-teardown", true); + obs->AddObserver(this, "profile-after-change", true); + } } nsIdleServiceDaily::~nsIdleServiceDaily() diff --git a/widget/xpwidgets/nsIdleService.h b/widget/xpwidgets/nsIdleService.h index fb8a3b583e33..174ee7c5a31a 100644 --- a/widget/xpwidgets/nsIdleService.h +++ b/widget/xpwidgets/nsIdleService.h @@ -49,6 +49,7 @@ #include "nsIObserver.h" #include "nsIIdleService.h" #include "nsCategoryCache.h" +#include "nsWeakReference.h" /** * Class we can use to store an observer with its associated idle time @@ -71,7 +72,8 @@ class nsIdleService; /** * Class to handle the daily idle timer. */ -class nsIdleServiceDaily : public nsIObserver +class nsIdleServiceDaily : public nsIObserver, + public nsSupportsWeakReference { public: NS_DECL_ISUPPORTS @@ -116,6 +118,11 @@ private: * Cache of observers for the "idle-daily" category. */ nsCategoryCache mCategoryObservers; + + /** + * Boolean set to true when daily idle notifications should be disabled. + */ + bool mShutdownInProgress; }; class nsIdleService : public nsIIdleService diff --git a/xpcom/base/Makefile.in b/xpcom/base/Makefile.in index 85f1509b7376..8b428c235db0 100644 --- a/xpcom/base/Makefile.in +++ b/xpcom/base/Makefile.in @@ -88,6 +88,7 @@ EXPORTS = \ nsDebugImpl.h \ nsIAllocator.h \ nsIID.h \ + nsISizeOf.h \ nsISupportsObsolete.h \ nsStackWalk.h \ nsTraceRefcntImpl.h \ diff --git a/xpcom/base/nsCycleCollector.cpp b/xpcom/base/nsCycleCollector.cpp index 3fdeac76d840..37e3d7d409a2 100644 --- a/xpcom/base/nsCycleCollector.cpp +++ b/xpcom/base/nsCycleCollector.cpp @@ -906,7 +906,10 @@ public: // RemoveSkippable removes entries from the purple buffer if // nsPurpleBufferEntry::mObject is null or if the object's // nsXPCOMCycleCollectionParticipant::CanSkip() returns true. - void RemoveSkippable(); + // If removeChildlessNodes is true, then any nodes in the purple buffer + // that will have no children in the cycle collector graph will also be + // removed. CanSkip() may be run on these children. + void RemoveSkippable(bool removeChildlessNodes); #ifdef DEBUG_CC void NoteAll(GCGraphBuilder &builder); @@ -1144,7 +1147,7 @@ struct nsCycleCollector void ScanRoots(); void ScanWeakMaps(); - void ForgetSkippable(); + void ForgetSkippable(bool removeChildlessNodes); // returns whether anything was collected bool CollectWhite(nsICycleCollectorListener *aListener); @@ -2114,6 +2117,69 @@ GCGraphBuilder::NoteWeakMapping(void *map, void *key, void *val) mapping->mVal = valNode; } +// MayHaveChild() will be false after a Traverse if the object does +// not have any children the CC will visit. +class ChildFinder : public nsCycleCollectionTraversalCallback +{ +public: + ChildFinder() : mMayHaveChild(false) {} + + // The logic of the Note*Child functions must mirror that of their + // respective functions in GCGraphBuilder. + NS_IMETHOD_(void) NoteXPCOMChild(nsISupports *child); + NS_IMETHOD_(void) NoteNativeChild(void *child, + nsCycleCollectionParticipant *helper); + NS_IMETHOD_(void) NoteScriptChild(PRUint32 langID, void *child); + + NS_IMETHOD_(void) DescribeRefCountedNode(nsrefcnt refcount, + size_t objsz, + const char *objname) {}; + NS_IMETHOD_(void) DescribeGCedNode(bool ismarked, + size_t objsz, + const char *objname) {}; + NS_IMETHOD_(void) NoteXPCOMRoot(nsISupports *root) {}; + NS_IMETHOD_(void) NoteRoot(PRUint32 langID, void *root, + nsCycleCollectionParticipant* helper) {}; + NS_IMETHOD_(void) NoteNextEdgeName(const char* name) {}; + NS_IMETHOD_(void) NoteWeakMapping(void *map, void *key, void *val) {}; + bool MayHaveChild() { + return mMayHaveChild; + }; +private: + bool mMayHaveChild; +}; + +NS_IMETHODIMP_(void) +ChildFinder::NoteXPCOMChild(nsISupports *child) +{ + if (!child || !(child = canonicalize(child))) + return; + nsXPCOMCycleCollectionParticipant *cp; + ToParticipant(child, &cp); + if (cp && !cp->CanSkip(child, true)) + mMayHaveChild = true; +}; + +NS_IMETHODIMP_(void) +ChildFinder::NoteNativeChild(void *child, + nsCycleCollectionParticipant *helper) +{ + if (child) + mMayHaveChild = true; +}; + +NS_IMETHODIMP_(void) +ChildFinder::NoteScriptChild(PRUint32 langID, void *child) +{ + if (!child) + return; + if (langID == nsIProgrammingLanguage::JAVASCRIPT && + !xpc_GCThingIsGrayCCThing(child)) { + return; + } + mMayHaveChild = true; +}; + static bool AddPurpleRoot(GCGraphBuilder &builder, nsISupports *root) { @@ -2137,8 +2203,16 @@ AddPurpleRoot(GCGraphBuilder &builder, nsISupports *root) return true; } +static bool +MayHaveChild(nsISupports *o, nsXPCOMCycleCollectionParticipant* cp) +{ + ChildFinder cf; + cp->Traverse(o, cf); + return cf.MayHaveChild(); +} + void -nsPurpleBuffer::RemoveSkippable() +nsPurpleBuffer::RemoveSkippable(bool removeChildlessNodes) { // Walk through all the blocks. for (Block *b = &mFirstBlock; b; b = b->mNext) { @@ -2152,7 +2226,8 @@ nsPurpleBuffer::RemoveSkippable() nsISupports* o = canonicalize(e->mObject); nsXPCOMCycleCollectionParticipant* cp; ToParticipant(o, &cp); - if (!cp->CanSkip(o, false)) { + if (!cp->CanSkip(o, false) && + (!removeChildlessNodes || MayHaveChild(o, cp))) { continue; } cp->UnmarkPurple(o); @@ -2197,13 +2272,13 @@ nsCycleCollector::SelectPurple(GCGraphBuilder &builder) } void -nsCycleCollector::ForgetSkippable() +nsCycleCollector::ForgetSkippable(bool removeChildlessNodes) { nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->NotifyObservers(nsnull, "cycle-collector-forget-skippable", nsnull); } - mPurpleBuf.RemoveSkippable(); + mPurpleBuf.RemoveSkippable(removeChildlessNodes); if (mForgetSkippableCB) { mForgetSkippableCB(); } @@ -4030,12 +4105,12 @@ nsCycleCollector_setForgetSkippableCallback(CC_ForgetSkippableCallback aCB) } void -nsCycleCollector_forgetSkippable() +nsCycleCollector_forgetSkippable(bool aRemoveChildlessNodes) { if (sCollector) { SAMPLE_LABEL("CC", "nsCycleCollector_forgetSkippable"); TimeLog timeLog; - sCollector->ForgetSkippable(); + sCollector->ForgetSkippable(aRemoveChildlessNodes); timeLog.Checkpoint("ForgetSkippable()"); } } diff --git a/xpcom/base/nsCycleCollector.h b/xpcom/base/nsCycleCollector.h index d3ecd57ca448..c2d468a60563 100644 --- a/xpcom/base/nsCycleCollector.h +++ b/xpcom/base/nsCycleCollector.h @@ -84,7 +84,7 @@ void nsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallback aCB); typedef void (*CC_ForgetSkippableCallback)(void); void nsCycleCollector_setForgetSkippableCallback(CC_ForgetSkippableCallback aCB); -void nsCycleCollector_forgetSkippable(); +void nsCycleCollector_forgetSkippable(bool aRemoveChildlessNodes = false); #ifdef DEBUG_CC void nsCycleCollector_logPurpleRemoval(void* aObject); diff --git a/intl/locale/public/nsICharsetAlias.h b/xpcom/base/nsISizeOf.h similarity index 58% rename from intl/locale/public/nsICharsetAlias.h rename to xpcom/base/nsISizeOf.h index bab0f0643d3b..a57d3119bffe 100644 --- a/intl/locale/public/nsICharsetAlias.h +++ b/xpcom/base/nsISizeOf.h @@ -1,4 +1,4 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -12,14 +12,15 @@ * for the specific language governing rights and limitations under the * License. * - * The Original Code is Mozilla Communicator client code. + * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 + * mozilla.org + * Portions created by the Initial Developer are Copyright (C) 2012 * the Initial Developer. All Rights Reserved. * * Contributor(s): + * Nicholas Nethercote * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), @@ -35,35 +36,31 @@ * * ***** END LICENSE BLOCK ***** */ -#ifndef nsICharsetAlias_h___ -#define nsICharsetAlias_h___ +#ifndef nsISizeOf_h___ +#define nsISizeOf_h___ -#include "nscore.h" -#include "nsStringGlue.h" #include "nsISupports.h" -/* 0b4028d6-7473-4958-9b3c-4dee46bf68cb */ -#define NS_ICHARSETALIAS_IID \ -{ 0x0b4028d6, \ - 0x7473, \ - 0x4958, \ - {0x9b, 0x3c, 0x4d, 0xee, 0x46, 0xbf, 0x68, 0xcb} } +#define NS_ISIZEOF_IID \ + {0x61d05579, 0xd7ec, 0x485c, \ + { 0xa4, 0x0c, 0x31, 0xc7, 0x9a, 0x5c, 0xf9, 0xf3 }} -// {98D41C21-CCF3-11d2-B3B1-00805F8A6670} -#define NS_CHARSETALIAS_CID \ -{ 0x98d41c21, 0xccf3, 0x11d2, { 0xb3, 0xb1, 0x0, 0x80, 0x5f, 0x8a, 0x66, 0x70 }} - -#define NS_CHARSETALIAS_CONTRACTID "@mozilla.org/intl/charsetalias;1" - -class nsICharsetAlias : public nsISupports +class nsISizeOf : public nsISupports { public: - NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICHARSETALIAS_IID) + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISIZEOF_IID) - NS_IMETHOD GetPreferred(const nsACString& aAlias, nsACString& aResult) = 0; - NS_IMETHOD Equals(const nsACString& aCharset1, const nsACString& aCharset2, bool* aResult) = 0; + /** + * Measures the size of the things pointed to by the object. + */ + virtual size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const = 0; + + /** + * Like SizeOfExcludingThis, but also includes the size of the object itself. + */ + virtual size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const = 0; }; -NS_DEFINE_STATIC_IID_ACCESSOR(nsICharsetAlias, NS_ICHARSETALIAS_IID) +NS_DEFINE_STATIC_IID_ACCESSOR(nsISizeOf, NS_ISIZEOF_IID) -#endif /* nsICharsetAlias_h___ */ +#endif /* nsISizeOf_h___ */ diff --git a/xpcom/base/nsStackWalk.cpp b/xpcom/base/nsStackWalk.cpp index 6f5507ac1076..647f1267ef4d 100644 --- a/xpcom/base/nsStackWalk.cpp +++ b/xpcom/base/nsStackWalk.cpp @@ -258,19 +258,19 @@ void PrintError(char *prefix) { LPVOID lpMsgBuf; DWORD lastErr = GetLastError(); - FormatMessage( + FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, lastErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language - (LPTSTR) &lpMsgBuf, + (LPSTR) &lpMsgBuf, 0, NULL ); fprintf(stderr, "### ERROR: %s: %s", prefix, lpMsgBuf ? lpMsgBuf : "(null)\n"); fflush(stderr); - LocalFree( lpMsgBuf ); + LocalFree(lpMsgBuf); } bool @@ -1262,6 +1262,15 @@ NS_StackWalk(NS_WalkStackCallback aCallback, PRUint32 aSkipFrames, return NS_ERROR_NOT_IMPLEMENTED; } +namespace mozilla { +nsresult +FramePointerStackWalk(NS_WalkStackCallback aCallback, PRUint32 aSkipFrames, + void *aClosure, void **bp) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} +} + EXPORT_XPCOM_API(nsresult) NS_DescribeCodeAddress(void *aPC, nsCodeAddressDetails *aDetails) { diff --git a/xpcom/components/ManifestParser.cpp b/xpcom/components/ManifestParser.cpp index 882ac08225bc..99befddcf14f 100644 --- a/xpcom/components/ManifestParser.cpp +++ b/xpcom/components/ManifestParser.cpp @@ -1,687 +1,687 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* ***** 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 Firefox - * - * The Initial Developer of the Original Code is - * the Mozilla Foundation . - * Portions created by the Initial Developer are Copyright (C) 2010 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Philipp Kewisch - * - * 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 "mozilla/Util.h" - -#include "ManifestParser.h" - -#include - -#include "prio.h" -#include "prprf.h" -#if defined(XP_WIN) -#include -#elif defined(MOZ_WIDGET_COCOA) -#include -#elif defined(MOZ_WIDGET_GTK2) -#include -#endif - -#ifdef MOZ_WIDGET_ANDROID -#include "AndroidBridge.h" -#endif - -#include "mozilla/Services.h" - -#include "nsConsoleMessage.h" -#include "nsTextFormatter.h" -#include "nsVersionComparator.h" -#include "nsXPCOMCIDInternal.h" - -#include "nsIConsoleService.h" -#include "nsIScriptError.h" -#include "nsIXULAppInfo.h" -#include "nsIXULRuntime.h" - -using namespace mozilla; - -struct ManifestDirective -{ - const char* directive; - int argc; - - // Some directives should only be delivered for NS_COMPONENT_LOCATION - // manifests. - bool componentonly; - - bool ischrome; - - bool allowbootstrap; - - // The platform/contentaccessible flags only apply to content directives. - bool contentflags; - - // Function to handle this directive. This isn't a union because C++ still - // hasn't learned how to initialize unions in a sane way. - void (nsComponentManagerImpl::*mgrfunc) - (nsComponentManagerImpl::ManifestProcessingContext& cx, - int lineno, char *const * argv); - void (nsChromeRegistry::*regfunc) - (nsChromeRegistry::ManifestProcessingContext& cx, - int lineno, char *const *argv, - bool platform, bool contentaccessible); - - bool isContract; -}; -static const ManifestDirective kParsingTable[] = { - { "manifest", 1, false, true, true, false, - &nsComponentManagerImpl::ManifestManifest, NULL }, - { "binary-component", 1, true, false, false, false, - &nsComponentManagerImpl::ManifestBinaryComponent, NULL }, - { "interfaces", 1, true, false, false, false, - &nsComponentManagerImpl::ManifestXPT, NULL }, - { "component", 2, true, false, false, false, - &nsComponentManagerImpl::ManifestComponent, NULL }, - { "contract", 2, true, false, false, false, - &nsComponentManagerImpl::ManifestContract, NULL, true}, - { "category", 3, true, false, false, false, - &nsComponentManagerImpl::ManifestCategory, NULL }, - { "content", 2, true, true, true, true, - NULL, &nsChromeRegistry::ManifestContent }, - { "locale", 3, true, true, true, false, - NULL, &nsChromeRegistry::ManifestLocale }, - { "skin", 3, false, true, true, false, - NULL, &nsChromeRegistry::ManifestSkin }, - { "overlay", 2, true, true, false, false, - NULL, &nsChromeRegistry::ManifestOverlay }, - { "style", 2, false, true, false, false, - NULL, &nsChromeRegistry::ManifestStyle }, - { "override", 2, true, true, true, false, - NULL, &nsChromeRegistry::ManifestOverride }, - { "resource", 2, true, true, false, false, - NULL, &nsChromeRegistry::ManifestResource } -}; - -static const char kWhitespace[] = "\t "; - -static bool IsNewline(char c) -{ - return c == '\n' || c == '\r'; -} - -namespace { -struct AutoPR_smprintf_free -{ - AutoPR_smprintf_free(char* buf) - : mBuf(buf) - { - } - - ~AutoPR_smprintf_free() - { - if (mBuf) - PR_smprintf_free(mBuf); - } - - operator char*() const { - return mBuf; - } - - char* mBuf; -}; - -} // anonymous namespace - -void LogMessage(const char* aMsg, ...) -{ - nsCOMPtr console = - do_GetService(NS_CONSOLESERVICE_CONTRACTID); - if (!console) - return; - - va_list args; - va_start(args, aMsg); - AutoPR_smprintf_free formatted(PR_vsmprintf(aMsg, args)); - va_end(args); - - nsCOMPtr error = - new nsConsoleMessage(NS_ConvertUTF8toUTF16(formatted).get()); - console->LogMessage(error); -} - -void LogMessageWithContext(FileLocation &aFile, - PRUint32 aLineNumber, const char* aMsg, ...) -{ - va_list args; - va_start(args, aMsg); - AutoPR_smprintf_free formatted(PR_vsmprintf(aMsg, args)); - va_end(args); - if (!formatted) - return; - - nsCString file; - aFile.GetURIString(file); - - nsCOMPtr error = - do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); - if (!error) { - // This can happen early in component registration. Fall back to a - // generic console message. - LogMessage("Warning: in '%s', line %i: %s", file.get(), - aLineNumber, (char*) formatted); - return; - } - - nsCOMPtr console = - do_GetService(NS_CONSOLESERVICE_CONTRACTID); - if (!console) - return; - - nsresult rv = error->Init(NS_ConvertUTF8toUTF16(formatted).get(), - NS_ConvertUTF8toUTF16(file).get(), NULL, - aLineNumber, 0, nsIScriptError::warningFlag, - "chrome registration"); - if (NS_FAILED(rv)) - return; - - console->LogMessage(error); -} - -/** - * Check for a modifier flag of the following forms: - * "flag" (same as "true") - * "flag=yes|true|1" - * "flag="no|false|0" - * @param aFlag The flag to compare. - * @param aData The tokenized data to check; this is lowercased - * before being passed in. - * @param aResult If the flag is found, the value is assigned here. - * @return Whether the flag was handled. - */ -static bool -CheckFlag(const nsSubstring& aFlag, const nsSubstring& aData, bool& aResult) -{ - if (!StringBeginsWith(aData, aFlag)) - return false; - - if (aFlag.Length() == aData.Length()) { - // the data is simply "flag", which is the same as "flag=yes" - aResult = true; - return true; - } - - if (aData.CharAt(aFlag.Length()) != '=') { - // the data is "flag2=", which is not anything we care about - return false; - } - - if (aData.Length() == aFlag.Length() + 1) { - aResult = false; - return true; - } - - switch (aData.CharAt(aFlag.Length() + 1)) { - case '1': - case 't': //true - case 'y': //yes - aResult = true; - return true; - - case '0': - case 'f': //false - case 'n': //no - aResult = false; - return true; - } - - return false; -} - -enum TriState { - eUnspecified, - eBad, - eOK -}; - -/** - * Check for a modifier flag of the following form: - * "flag=string" - * "flag!=string" - * @param aFlag The flag to compare. - * @param aData The tokenized data to check; this is lowercased - * before being passed in. - * @param aValue The value that is expected. - * @param aResult If this is "ok" when passed in, this is left alone. - * Otherwise if the flag is found it is set to eBad or eOK. - * @return Whether the flag was handled. - */ -static bool -CheckStringFlag(const nsSubstring& aFlag, const nsSubstring& aData, - const nsSubstring& aValue, TriState& aResult) -{ - if (aData.Length() < aFlag.Length() + 1) - return false; - - if (!StringBeginsWith(aData, aFlag)) - return false; - - bool comparison = true; - if (aData[aFlag.Length()] != '=') { - if (aData[aFlag.Length()] == '!' && - aData.Length() >= aFlag.Length() + 2 && - aData[aFlag.Length() + 1] == '=') - comparison = false; - else - return false; - } - - if (aResult != eOK) { - nsDependentSubstring testdata = Substring(aData, aFlag.Length() + (comparison ? 1 : 2)); - if (testdata.Equals(aValue)) - aResult = comparison ? eOK : eBad; - else - aResult = comparison ? eBad : eOK; - } - - return true; -} - -/** - * Check for a modifier flag of the following form: - * "flag=version" - * "flag<=version" - * "flag=version" - * "flag>version" - * @param aFlag The flag to compare. - * @param aData The tokenized data to check; this is lowercased - * before being passed in. - * @param aValue The value that is expected. If this is empty then no - * comparison will match. - * @param aResult If this is eOK when passed in, this is left alone. - * Otherwise if the flag is found it is set to eBad or eOK. - * @return Whether the flag was handled. - */ - -#define COMPARE_EQ 1 << 0 -#define COMPARE_LT 1 << 1 -#define COMPARE_GT 1 << 2 - -static bool -CheckVersionFlag(const nsString& aFlag, const nsString& aData, - const nsString& aValue, TriState& aResult) -{ - if (aData.Length() < aFlag.Length() + 2) - return false; - - if (!StringBeginsWith(aData, aFlag)) - return false; - - if (aValue.Length() == 0) { - if (aResult != eOK) - aResult = eBad; - return true; - } - - PRUint32 comparison; - nsAutoString testdata; - - switch (aData[aFlag.Length()]) { - case '=': - comparison = COMPARE_EQ; - testdata = Substring(aData, aFlag.Length() + 1); - break; - - case '<': - if (aData[aFlag.Length() + 1] == '=') { - comparison = COMPARE_EQ | COMPARE_LT; - testdata = Substring(aData, aFlag.Length() + 2); - } - else { - comparison = COMPARE_LT; - testdata = Substring(aData, aFlag.Length() + 1); - } - break; - - case '>': - if (aData[aFlag.Length() + 1] == '=') { - comparison = COMPARE_EQ | COMPARE_GT; - testdata = Substring(aData, aFlag.Length() + 2); - } - else { - comparison = COMPARE_GT; - testdata = Substring(aData, aFlag.Length() + 1); - } - break; - - default: - return false; - } - - if (testdata.Length() == 0) - return false; - - if (aResult != eOK) { - PRInt32 c = NS_CompareVersions(NS_ConvertUTF16toUTF8(aValue).get(), - NS_ConvertUTF16toUTF8(testdata).get()); - if ((c == 0 && comparison & COMPARE_EQ) || - (c < 0 && comparison & COMPARE_LT) || - (c > 0 && comparison & COMPARE_GT)) - aResult = eOK; - else - aResult = eBad; - } - - return true; -} - -// In-place conversion of ascii characters to lower case -static void -ToLowerCase(char* token) -{ - for (; *token; ++token) - *token = NS_ToLower(*token); -} - -namespace { - -struct CachedDirective -{ - int lineno; - char* argv[4]; -}; - -} // anonymous namespace - - -void -ParseManifest(NSLocationType type, FileLocation &file, char* buf, bool aChromeOnly) -{ - nsComponentManagerImpl::ManifestProcessingContext mgrcx(type, file, aChromeOnly); - nsChromeRegistry::ManifestProcessingContext chromecx(type, file); - nsresult rv; - - NS_NAMED_LITERAL_STRING(kPlatform, "platform"); - NS_NAMED_LITERAL_STRING(kContentAccessible, "contentaccessible"); - NS_NAMED_LITERAL_STRING(kApplication, "application"); - NS_NAMED_LITERAL_STRING(kAppVersion, "appversion"); - NS_NAMED_LITERAL_STRING(kGeckoVersion, "platformversion"); - NS_NAMED_LITERAL_STRING(kOs, "os"); - NS_NAMED_LITERAL_STRING(kOsVersion, "osversion"); - NS_NAMED_LITERAL_STRING(kABI, "abi"); -#if defined(MOZ_WIDGET_ANDROID) - NS_NAMED_LITERAL_STRING(kTablet, "tablet"); -#endif - - // Obsolete - NS_NAMED_LITERAL_STRING(kXPCNativeWrappers, "xpcnativewrappers"); - - nsAutoString appID; - nsAutoString appVersion; - nsAutoString geckoVersion; - nsAutoString osTarget; - nsAutoString abi; - - nsCOMPtr xapp (do_GetService(XULAPPINFO_SERVICE_CONTRACTID)); - if (xapp) { - nsCAutoString s; - rv = xapp->GetID(s); - if (NS_SUCCEEDED(rv)) - CopyUTF8toUTF16(s, appID); - - rv = xapp->GetVersion(s); - if (NS_SUCCEEDED(rv)) - CopyUTF8toUTF16(s, appVersion); - - rv = xapp->GetPlatformVersion(s); - if (NS_SUCCEEDED(rv)) - CopyUTF8toUTF16(s, geckoVersion); - - nsCOMPtr xruntime (do_QueryInterface(xapp)); - if (xruntime) { - rv = xruntime->GetOS(s); - if (NS_SUCCEEDED(rv)) { - ToLowerCase(s); - CopyUTF8toUTF16(s, osTarget); - } - - rv = xruntime->GetXPCOMABI(s); - if (NS_SUCCEEDED(rv) && osTarget.Length()) { - ToLowerCase(s); - CopyUTF8toUTF16(s, abi); - abi.Insert(PRUnichar('_'), 0); - abi.Insert(osTarget, 0); - } - } - } - - nsAutoString osVersion; -#if defined(XP_WIN) - OSVERSIONINFO info = { sizeof(OSVERSIONINFO) }; - if (GetVersionEx(&info)) { - nsTextFormatter::ssprintf(osVersion, NS_LITERAL_STRING("%ld.%ld").get(), - info.dwMajorVersion, - info.dwMinorVersion); - } -#elif defined(MOZ_WIDGET_COCOA) - SInt32 majorVersion, minorVersion; - if ((Gestalt(gestaltSystemVersionMajor, &majorVersion) == noErr) && - (Gestalt(gestaltSystemVersionMinor, &minorVersion) == noErr)) { - nsTextFormatter::ssprintf(osVersion, NS_LITERAL_STRING("%ld.%ld").get(), - majorVersion, - minorVersion); - } -#elif defined(MOZ_WIDGET_GTK2) - nsTextFormatter::ssprintf(osVersion, NS_LITERAL_STRING("%ld.%ld").get(), - gtk_major_version, - gtk_minor_version); -#elif defined(MOZ_WIDGET_ANDROID) - bool isTablet = false; - if (mozilla::AndroidBridge::Bridge()) { - mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build$VERSION", "RELEASE", osVersion); - isTablet = mozilla::AndroidBridge::Bridge()->IsTablet(); - } -#endif - - // Because contracts must be registered after CIDs, we save and process them - // at the end. - nsTArray contracts; - - char *token; - char *newline = buf; - PRUint32 line = 0; - - // outer loop tokenizes by newline - while (*newline) { - while (*newline && IsNewline(*newline)) { - ++newline; - ++line; - } - if (!*newline) - break; - - token = newline; - while (*newline && !IsNewline(*newline)) - ++newline; - - if (*newline) { - *newline = '\0'; - ++newline; - } - ++line; - - if (*token == '#') // ignore lines that begin with # as comments - continue; - - char *whitespace = token; - token = nsCRT::strtok(whitespace, kWhitespace, &whitespace); - if (!token) continue; - - const ManifestDirective* directive = NULL; - for (const ManifestDirective* d = kParsingTable; - d < ArrayEnd(kParsingTable); - ++d) { - if (!strcmp(d->directive, token)) { - directive = d; - break; - } - } - - if (!directive) { - LogMessageWithContext(file, line, - "Ignoring unrecognized chrome manifest directive '%s'.", - token); - continue; - } - - if (!directive->allowbootstrap && NS_BOOTSTRAPPED_LOCATION == type) { - LogMessageWithContext(file, line, - "Bootstrapped manifest not allowed to use '%s' directive.", - token); - continue; - } - - if (directive->componentonly && NS_SKIN_LOCATION == type) { - LogMessageWithContext(file, line, - "Skin manifest not allowed to use '%s' directive.", - token); - continue; - } - - NS_ASSERTION(directive->argc < 4, "Need to reset argv array length"); - char* argv[4]; - for (int i = 0; i < directive->argc; ++i) - argv[i] = nsCRT::strtok(whitespace, kWhitespace, &whitespace); - - if (!argv[directive->argc - 1]) { - LogMessageWithContext(file, line, - "Not enough arguments for chrome manifest directive '%s', expected %i.", - token, directive->argc); - continue; - } - - bool ok = true; - TriState stAppVersion = eUnspecified; - TriState stGeckoVersion = eUnspecified; - TriState stApp = eUnspecified; - TriState stOsVersion = eUnspecified; - TriState stOs = eUnspecified; - TriState stABI = eUnspecified; -#if defined(MOZ_WIDGET_ANDROID) - TriState stTablet = eUnspecified; -#endif - bool platform = false; - bool contentAccessible = false; - - while (NULL != (token = nsCRT::strtok(whitespace, kWhitespace, &whitespace)) && ok) { - ToLowerCase(token); - NS_ConvertASCIItoUTF16 wtoken(token); - - if (CheckStringFlag(kApplication, wtoken, appID, stApp) || - CheckStringFlag(kOs, wtoken, osTarget, stOs) || - CheckStringFlag(kABI, wtoken, abi, stABI) || - CheckVersionFlag(kOsVersion, wtoken, osVersion, stOsVersion) || - CheckVersionFlag(kAppVersion, wtoken, appVersion, stAppVersion) || - CheckVersionFlag(kGeckoVersion, wtoken, geckoVersion, stGeckoVersion)) - continue; - -#if defined(MOZ_WIDGET_ANDROID) - bool tablet = false; - if (CheckFlag(kTablet, wtoken, tablet)) { - stTablet = (tablet == isTablet) ? eOK : eBad; - continue; - } -#endif - - if (directive->contentflags && - (CheckFlag(kPlatform, wtoken, platform) || - CheckFlag(kContentAccessible, wtoken, contentAccessible))) - continue; - - bool xpcNativeWrappers = true; // Dummy for CheckFlag. - if (CheckFlag(kXPCNativeWrappers, wtoken, xpcNativeWrappers)) { - LogMessageWithContext(file, line, - "Ignoring obsolete chrome registration modifier '%s'.", - token); - continue; - } - - LogMessageWithContext(file, line, - "Unrecognized chrome manifest modifier '%s'.", - token); - ok = false; - } - - if (!ok || - stApp == eBad || - stAppVersion == eBad || - stGeckoVersion == eBad || - stOs == eBad || - stOsVersion == eBad || -#ifdef MOZ_WIDGET_ANDROID - stTablet == eBad || -#endif - stABI == eBad) - continue; - - if (directive->regfunc) { - if (GeckoProcessType_Default != XRE_GetProcessType()) - continue; - - if (!nsChromeRegistry::gChromeRegistry) { - nsCOMPtr cr = - mozilla::services::GetChromeRegistryService(); - if (!nsChromeRegistry::gChromeRegistry) { - LogMessageWithContext(file, line, - "Chrome registry isn't available yet."); - continue; - } - } - - (nsChromeRegistry::gChromeRegistry->*(directive->regfunc)) - (chromecx, line, argv, platform, contentAccessible); - } - else if (directive->ischrome || !aChromeOnly) { - if (directive->isContract) { - CachedDirective* cd = contracts.AppendElement(); - cd->lineno = line; - cd->argv[0] = argv[0]; - cd->argv[1] = argv[1]; - } - else - (nsComponentManagerImpl::gComponentManager->*(directive->mgrfunc)) - (mgrcx, line, argv); - } - } - - for (PRUint32 i = 0; i < contracts.Length(); ++i) { - CachedDirective& d = contracts[i]; - nsComponentManagerImpl::gComponentManager->ManifestContract - (mgrcx, d.lineno, d.argv); - } -} +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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 Firefox + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation . + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Philipp Kewisch + * + * 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 "mozilla/Util.h" + +#include "ManifestParser.h" + +#include + +#include "prio.h" +#include "prprf.h" +#if defined(XP_WIN) +#include +#elif defined(MOZ_WIDGET_COCOA) +#include +#elif defined(MOZ_WIDGET_GTK2) +#include +#endif + +#ifdef MOZ_WIDGET_ANDROID +#include "AndroidBridge.h" +#endif + +#include "mozilla/Services.h" + +#include "nsConsoleMessage.h" +#include "nsTextFormatter.h" +#include "nsVersionComparator.h" +#include "nsXPCOMCIDInternal.h" + +#include "nsIConsoleService.h" +#include "nsIScriptError.h" +#include "nsIXULAppInfo.h" +#include "nsIXULRuntime.h" + +using namespace mozilla; + +struct ManifestDirective +{ + const char* directive; + int argc; + + // Some directives should only be delivered for NS_COMPONENT_LOCATION + // manifests. + bool componentonly; + + bool ischrome; + + bool allowbootstrap; + + // The platform/contentaccessible flags only apply to content directives. + bool contentflags; + + // Function to handle this directive. This isn't a union because C++ still + // hasn't learned how to initialize unions in a sane way. + void (nsComponentManagerImpl::*mgrfunc) + (nsComponentManagerImpl::ManifestProcessingContext& cx, + int lineno, char *const * argv); + void (nsChromeRegistry::*regfunc) + (nsChromeRegistry::ManifestProcessingContext& cx, + int lineno, char *const *argv, + bool platform, bool contentaccessible); + + bool isContract; +}; +static const ManifestDirective kParsingTable[] = { + { "manifest", 1, false, true, true, false, + &nsComponentManagerImpl::ManifestManifest, NULL }, + { "binary-component", 1, true, false, false, false, + &nsComponentManagerImpl::ManifestBinaryComponent, NULL }, + { "interfaces", 1, true, false, false, false, + &nsComponentManagerImpl::ManifestXPT, NULL }, + { "component", 2, true, false, false, false, + &nsComponentManagerImpl::ManifestComponent, NULL }, + { "contract", 2, true, false, false, false, + &nsComponentManagerImpl::ManifestContract, NULL, true}, + { "category", 3, true, false, false, false, + &nsComponentManagerImpl::ManifestCategory, NULL }, + { "content", 2, true, true, true, true, + NULL, &nsChromeRegistry::ManifestContent }, + { "locale", 3, true, true, true, false, + NULL, &nsChromeRegistry::ManifestLocale }, + { "skin", 3, false, true, true, false, + NULL, &nsChromeRegistry::ManifestSkin }, + { "overlay", 2, true, true, false, false, + NULL, &nsChromeRegistry::ManifestOverlay }, + { "style", 2, false, true, false, false, + NULL, &nsChromeRegistry::ManifestStyle }, + { "override", 2, true, true, true, false, + NULL, &nsChromeRegistry::ManifestOverride }, + { "resource", 2, true, true, false, false, + NULL, &nsChromeRegistry::ManifestResource } +}; + +static const char kWhitespace[] = "\t "; + +static bool IsNewline(char c) +{ + return c == '\n' || c == '\r'; +} + +namespace { +struct AutoPR_smprintf_free +{ + AutoPR_smprintf_free(char* buf) + : mBuf(buf) + { + } + + ~AutoPR_smprintf_free() + { + if (mBuf) + PR_smprintf_free(mBuf); + } + + operator char*() const { + return mBuf; + } + + char* mBuf; +}; + +} // anonymous namespace + +void LogMessage(const char* aMsg, ...) +{ + nsCOMPtr console = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (!console) + return; + + va_list args; + va_start(args, aMsg); + AutoPR_smprintf_free formatted(PR_vsmprintf(aMsg, args)); + va_end(args); + + nsCOMPtr error = + new nsConsoleMessage(NS_ConvertUTF8toUTF16(formatted).get()); + console->LogMessage(error); +} + +void LogMessageWithContext(FileLocation &aFile, + PRUint32 aLineNumber, const char* aMsg, ...) +{ + va_list args; + va_start(args, aMsg); + AutoPR_smprintf_free formatted(PR_vsmprintf(aMsg, args)); + va_end(args); + if (!formatted) + return; + + nsCString file; + aFile.GetURIString(file); + + nsCOMPtr error = + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); + if (!error) { + // This can happen early in component registration. Fall back to a + // generic console message. + LogMessage("Warning: in '%s', line %i: %s", file.get(), + aLineNumber, (char*) formatted); + return; + } + + nsCOMPtr console = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (!console) + return; + + nsresult rv = error->Init(NS_ConvertUTF8toUTF16(formatted).get(), + NS_ConvertUTF8toUTF16(file).get(), NULL, + aLineNumber, 0, nsIScriptError::warningFlag, + "chrome registration"); + if (NS_FAILED(rv)) + return; + + console->LogMessage(error); +} + +/** + * Check for a modifier flag of the following forms: + * "flag" (same as "true") + * "flag=yes|true|1" + * "flag="no|false|0" + * @param aFlag The flag to compare. + * @param aData The tokenized data to check; this is lowercased + * before being passed in. + * @param aResult If the flag is found, the value is assigned here. + * @return Whether the flag was handled. + */ +static bool +CheckFlag(const nsSubstring& aFlag, const nsSubstring& aData, bool& aResult) +{ + if (!StringBeginsWith(aData, aFlag)) + return false; + + if (aFlag.Length() == aData.Length()) { + // the data is simply "flag", which is the same as "flag=yes" + aResult = true; + return true; + } + + if (aData.CharAt(aFlag.Length()) != '=') { + // the data is "flag2=", which is not anything we care about + return false; + } + + if (aData.Length() == aFlag.Length() + 1) { + aResult = false; + return true; + } + + switch (aData.CharAt(aFlag.Length() + 1)) { + case '1': + case 't': //true + case 'y': //yes + aResult = true; + return true; + + case '0': + case 'f': //false + case 'n': //no + aResult = false; + return true; + } + + return false; +} + +enum TriState { + eUnspecified, + eBad, + eOK +}; + +/** + * Check for a modifier flag of the following form: + * "flag=string" + * "flag!=string" + * @param aFlag The flag to compare. + * @param aData The tokenized data to check; this is lowercased + * before being passed in. + * @param aValue The value that is expected. + * @param aResult If this is "ok" when passed in, this is left alone. + * Otherwise if the flag is found it is set to eBad or eOK. + * @return Whether the flag was handled. + */ +static bool +CheckStringFlag(const nsSubstring& aFlag, const nsSubstring& aData, + const nsSubstring& aValue, TriState& aResult) +{ + if (aData.Length() < aFlag.Length() + 1) + return false; + + if (!StringBeginsWith(aData, aFlag)) + return false; + + bool comparison = true; + if (aData[aFlag.Length()] != '=') { + if (aData[aFlag.Length()] == '!' && + aData.Length() >= aFlag.Length() + 2 && + aData[aFlag.Length() + 1] == '=') + comparison = false; + else + return false; + } + + if (aResult != eOK) { + nsDependentSubstring testdata = Substring(aData, aFlag.Length() + (comparison ? 1 : 2)); + if (testdata.Equals(aValue)) + aResult = comparison ? eOK : eBad; + else + aResult = comparison ? eBad : eOK; + } + + return true; +} + +/** + * Check for a modifier flag of the following form: + * "flag=version" + * "flag<=version" + * "flag=version" + * "flag>version" + * @param aFlag The flag to compare. + * @param aData The tokenized data to check; this is lowercased + * before being passed in. + * @param aValue The value that is expected. If this is empty then no + * comparison will match. + * @param aResult If this is eOK when passed in, this is left alone. + * Otherwise if the flag is found it is set to eBad or eOK. + * @return Whether the flag was handled. + */ + +#define COMPARE_EQ 1 << 0 +#define COMPARE_LT 1 << 1 +#define COMPARE_GT 1 << 2 + +static bool +CheckVersionFlag(const nsString& aFlag, const nsString& aData, + const nsString& aValue, TriState& aResult) +{ + if (aData.Length() < aFlag.Length() + 2) + return false; + + if (!StringBeginsWith(aData, aFlag)) + return false; + + if (aValue.Length() == 0) { + if (aResult != eOK) + aResult = eBad; + return true; + } + + PRUint32 comparison; + nsAutoString testdata; + + switch (aData[aFlag.Length()]) { + case '=': + comparison = COMPARE_EQ; + testdata = Substring(aData, aFlag.Length() + 1); + break; + + case '<': + if (aData[aFlag.Length() + 1] == '=') { + comparison = COMPARE_EQ | COMPARE_LT; + testdata = Substring(aData, aFlag.Length() + 2); + } + else { + comparison = COMPARE_LT; + testdata = Substring(aData, aFlag.Length() + 1); + } + break; + + case '>': + if (aData[aFlag.Length() + 1] == '=') { + comparison = COMPARE_EQ | COMPARE_GT; + testdata = Substring(aData, aFlag.Length() + 2); + } + else { + comparison = COMPARE_GT; + testdata = Substring(aData, aFlag.Length() + 1); + } + break; + + default: + return false; + } + + if (testdata.Length() == 0) + return false; + + if (aResult != eOK) { + PRInt32 c = NS_CompareVersions(NS_ConvertUTF16toUTF8(aValue).get(), + NS_ConvertUTF16toUTF8(testdata).get()); + if ((c == 0 && comparison & COMPARE_EQ) || + (c < 0 && comparison & COMPARE_LT) || + (c > 0 && comparison & COMPARE_GT)) + aResult = eOK; + else + aResult = eBad; + } + + return true; +} + +// In-place conversion of ascii characters to lower case +static void +ToLowerCase(char* token) +{ + for (; *token; ++token) + *token = NS_ToLower(*token); +} + +namespace { + +struct CachedDirective +{ + int lineno; + char* argv[4]; +}; + +} // anonymous namespace + + +void +ParseManifest(NSLocationType type, FileLocation &file, char* buf, bool aChromeOnly) +{ + nsComponentManagerImpl::ManifestProcessingContext mgrcx(type, file, aChromeOnly); + nsChromeRegistry::ManifestProcessingContext chromecx(type, file); + nsresult rv; + + NS_NAMED_LITERAL_STRING(kPlatform, "platform"); + NS_NAMED_LITERAL_STRING(kContentAccessible, "contentaccessible"); + NS_NAMED_LITERAL_STRING(kApplication, "application"); + NS_NAMED_LITERAL_STRING(kAppVersion, "appversion"); + NS_NAMED_LITERAL_STRING(kGeckoVersion, "platformversion"); + NS_NAMED_LITERAL_STRING(kOs, "os"); + NS_NAMED_LITERAL_STRING(kOsVersion, "osversion"); + NS_NAMED_LITERAL_STRING(kABI, "abi"); +#if defined(MOZ_WIDGET_ANDROID) + NS_NAMED_LITERAL_STRING(kTablet, "tablet"); +#endif + + // Obsolete + NS_NAMED_LITERAL_STRING(kXPCNativeWrappers, "xpcnativewrappers"); + + nsAutoString appID; + nsAutoString appVersion; + nsAutoString geckoVersion; + nsAutoString osTarget; + nsAutoString abi; + + nsCOMPtr xapp (do_GetService(XULAPPINFO_SERVICE_CONTRACTID)); + if (xapp) { + nsCAutoString s; + rv = xapp->GetID(s); + if (NS_SUCCEEDED(rv)) + CopyUTF8toUTF16(s, appID); + + rv = xapp->GetVersion(s); + if (NS_SUCCEEDED(rv)) + CopyUTF8toUTF16(s, appVersion); + + rv = xapp->GetPlatformVersion(s); + if (NS_SUCCEEDED(rv)) + CopyUTF8toUTF16(s, geckoVersion); + + nsCOMPtr xruntime (do_QueryInterface(xapp)); + if (xruntime) { + rv = xruntime->GetOS(s); + if (NS_SUCCEEDED(rv)) { + ToLowerCase(s); + CopyUTF8toUTF16(s, osTarget); + } + + rv = xruntime->GetXPCOMABI(s); + if (NS_SUCCEEDED(rv) && osTarget.Length()) { + ToLowerCase(s); + CopyUTF8toUTF16(s, abi); + abi.Insert(PRUnichar('_'), 0); + abi.Insert(osTarget, 0); + } + } + } + + nsAutoString osVersion; +#if defined(XP_WIN) + OSVERSIONINFO info = { sizeof(OSVERSIONINFO) }; + if (GetVersionEx(&info)) { + nsTextFormatter::ssprintf(osVersion, NS_LITERAL_STRING("%ld.%ld").get(), + info.dwMajorVersion, + info.dwMinorVersion); + } +#elif defined(MOZ_WIDGET_COCOA) + SInt32 majorVersion, minorVersion; + if ((Gestalt(gestaltSystemVersionMajor, &majorVersion) == noErr) && + (Gestalt(gestaltSystemVersionMinor, &minorVersion) == noErr)) { + nsTextFormatter::ssprintf(osVersion, NS_LITERAL_STRING("%ld.%ld").get(), + majorVersion, + minorVersion); + } +#elif defined(MOZ_WIDGET_GTK2) + nsTextFormatter::ssprintf(osVersion, NS_LITERAL_STRING("%ld.%ld").get(), + gtk_major_version, + gtk_minor_version); +#elif defined(MOZ_WIDGET_ANDROID) + bool isTablet = false; + if (mozilla::AndroidBridge::Bridge()) { + mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build$VERSION", "RELEASE", osVersion); + isTablet = mozilla::AndroidBridge::Bridge()->IsTablet(); + } +#endif + + // Because contracts must be registered after CIDs, we save and process them + // at the end. + nsTArray contracts; + + char *token; + char *newline = buf; + PRUint32 line = 0; + + // outer loop tokenizes by newline + while (*newline) { + while (*newline && IsNewline(*newline)) { + ++newline; + ++line; + } + if (!*newline) + break; + + token = newline; + while (*newline && !IsNewline(*newline)) + ++newline; + + if (*newline) { + *newline = '\0'; + ++newline; + } + ++line; + + if (*token == '#') // ignore lines that begin with # as comments + continue; + + char *whitespace = token; + token = nsCRT::strtok(whitespace, kWhitespace, &whitespace); + if (!token) continue; + + const ManifestDirective* directive = NULL; + for (const ManifestDirective* d = kParsingTable; + d < ArrayEnd(kParsingTable); + ++d) { + if (!strcmp(d->directive, token)) { + directive = d; + break; + } + } + + if (!directive) { + LogMessageWithContext(file, line, + "Ignoring unrecognized chrome manifest directive '%s'.", + token); + continue; + } + + if (!directive->allowbootstrap && NS_BOOTSTRAPPED_LOCATION == type) { + LogMessageWithContext(file, line, + "Bootstrapped manifest not allowed to use '%s' directive.", + token); + continue; + } + + if (directive->componentonly && NS_SKIN_LOCATION == type) { + LogMessageWithContext(file, line, + "Skin manifest not allowed to use '%s' directive.", + token); + continue; + } + + NS_ASSERTION(directive->argc < 4, "Need to reset argv array length"); + char* argv[4]; + for (int i = 0; i < directive->argc; ++i) + argv[i] = nsCRT::strtok(whitespace, kWhitespace, &whitespace); + + if (!argv[directive->argc - 1]) { + LogMessageWithContext(file, line, + "Not enough arguments for chrome manifest directive '%s', expected %i.", + token, directive->argc); + continue; + } + + bool ok = true; + TriState stAppVersion = eUnspecified; + TriState stGeckoVersion = eUnspecified; + TriState stApp = eUnspecified; + TriState stOsVersion = eUnspecified; + TriState stOs = eUnspecified; + TriState stABI = eUnspecified; +#if defined(MOZ_WIDGET_ANDROID) + TriState stTablet = eUnspecified; +#endif + bool platform = false; + bool contentAccessible = false; + + while (NULL != (token = nsCRT::strtok(whitespace, kWhitespace, &whitespace)) && ok) { + ToLowerCase(token); + NS_ConvertASCIItoUTF16 wtoken(token); + + if (CheckStringFlag(kApplication, wtoken, appID, stApp) || + CheckStringFlag(kOs, wtoken, osTarget, stOs) || + CheckStringFlag(kABI, wtoken, abi, stABI) || + CheckVersionFlag(kOsVersion, wtoken, osVersion, stOsVersion) || + CheckVersionFlag(kAppVersion, wtoken, appVersion, stAppVersion) || + CheckVersionFlag(kGeckoVersion, wtoken, geckoVersion, stGeckoVersion)) + continue; + +#if defined(MOZ_WIDGET_ANDROID) + bool tablet = false; + if (CheckFlag(kTablet, wtoken, tablet)) { + stTablet = (tablet == isTablet) ? eOK : eBad; + continue; + } +#endif + + if (directive->contentflags && + (CheckFlag(kPlatform, wtoken, platform) || + CheckFlag(kContentAccessible, wtoken, contentAccessible))) + continue; + + bool xpcNativeWrappers = true; // Dummy for CheckFlag. + if (CheckFlag(kXPCNativeWrappers, wtoken, xpcNativeWrappers)) { + LogMessageWithContext(file, line, + "Ignoring obsolete chrome registration modifier '%s'.", + token); + continue; + } + + LogMessageWithContext(file, line, + "Unrecognized chrome manifest modifier '%s'.", + token); + ok = false; + } + + if (!ok || + stApp == eBad || + stAppVersion == eBad || + stGeckoVersion == eBad || + stOs == eBad || + stOsVersion == eBad || +#ifdef MOZ_WIDGET_ANDROID + stTablet == eBad || +#endif + stABI == eBad) + continue; + + if (directive->regfunc) { + if (GeckoProcessType_Default != XRE_GetProcessType()) + continue; + + if (!nsChromeRegistry::gChromeRegistry) { + nsCOMPtr cr = + mozilla::services::GetChromeRegistryService(); + if (!nsChromeRegistry::gChromeRegistry) { + LogMessageWithContext(file, line, + "Chrome registry isn't available yet."); + continue; + } + } + + (nsChromeRegistry::gChromeRegistry->*(directive->regfunc)) + (chromecx, line, argv, platform, contentAccessible); + } + else if (directive->ischrome || !aChromeOnly) { + if (directive->isContract) { + CachedDirective* cd = contracts.AppendElement(); + cd->lineno = line; + cd->argv[0] = argv[0]; + cd->argv[1] = argv[1]; + } + else + (nsComponentManagerImpl::gComponentManager->*(directive->mgrfunc)) + (mgrcx, line, argv); + } + } + + for (PRUint32 i = 0; i < contracts.Length(); ++i) { + CachedDirective& d = contracts[i]; + nsComponentManagerImpl::gComponentManager->ManifestContract + (mgrcx, d.lineno, d.argv); + } +} diff --git a/xpcom/ds/Makefile.in b/xpcom/ds/Makefile.in index 2f8569df2465..955648db116d 100644 --- a/xpcom/ds/Makefile.in +++ b/xpcom/ds/Makefile.in @@ -95,7 +95,6 @@ EXPORTS = \ nsCheapSets.h \ nsCppSharedAllocator.h \ nsCRT.h \ - nsDoubleHashtable.h \ nsExpirationTracker.h \ nsFixedSizeAllocator.h \ nsHashtable.h \ diff --git a/xpcom/ds/nsDoubleHashtable.h b/xpcom/ds/nsDoubleHashtable.h deleted file mode 100644 index 72ecdf8eda65..000000000000 --- a/xpcom/ds/nsDoubleHashtable.h +++ /dev/null @@ -1,483 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* ***** 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 Communicator client code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 2002 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either of 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 ***** */ - -/** - * nsDoubleHashtable.h is OBSOLETE. Use nsTHashtable or a derivative instead. - */ - -#ifndef __nsDoubleHashtable_h__ -#define __nsDoubleHashtable_h__ - -#include "pldhash.h" -#include "nscore.h" -#include "nsString.h" -#include "nsHashKeys.h" - -/* - * This file provides several major things to make PLDHashTable easier to use: - * - hash class macros for you to define a hashtable - * - default key classes to use as superclasses for your entries - * - empty maps for string, cstring, int and void - */ - -/* - * USAGE - * - * To use nsDoubleHashtable macros - * (1) Define an entry class - * (2) Create the hash class - * (3) Use the hash class - * - * EXAMPLE - * - * As an example, let's create a dictionary, a mapping from a string (the word) - * to the pronunciation and definition of those words. - * - * (1) Define an entry class - * - * What we want here is an entry class that contains the word, the - * pronunciation string, and the definition string. Since we have a string key - * we can use the standard PLDHashStringEntry class as our base, it will handle - * the key stuff for us automatically. - * - * #include "nsDoubleHashtable.h" - * - * // Do NOT ADD VIRTUAL METHODS INTO YOUR ENTRY. Everything will break. - * // This is because of the 4-byte pointer C++ magically prepends onto your - * // entry class. It interacts very unhappily with PLDHashTable. - * class DictionaryEntry : public PLDHashStringEntry { - * public: - * DictionaryEntry(const void* aKey) : PLDHashStringEntry(aKey) { } - * ~DictionaryEntry() { } - * nsString mPronunciation; - * nsString mDefinition; - * } - * - * (2) Create the hash class - * - * The final hash class you will use in step 3 is defined by 2 macros. - * - * DECL_DHASH_WRAPPER(Dictionary, DictionaryEntry, const nsAString&) - * DHASH_WRAPPER(Dictionary, DictionaryEntry, const nsAString&) - * - * (3) Use the hash class - * - * Here is a simple main() that might look up a string: - * - * int main(void) { - * Dictionary d; - * nsresult rv = d.Init(10); - * if (NS_FAILED(rv)) return 1; - * - * // Put an entry - * DictionaryEntry* a = d.AddEntry(NS_LITERAL_STRING("doomed")); - * if (!a) return 1; - * a->mDefinition.AssignLiteral("The state you get in when a Mozilla release is pending"); - * a->mPronunciation.AssignLiteral("doom-d"); - * - * // Get the entry - * DictionaryEntry* b = d.GetEntry(NS_LITERAL_STRING("doomed")); - * printf("doomed: %s\n", NS_ConvertUTF16toUTF8(b->mDefinition).get()); - * - * // Entries will be automagically cleaned up when the Dictionary object goes away - * return 0; - * } - * - * - * BONUS POINTS - * - * You may wish to extend this class and add helper functions like - * nsDependentString* GetDefinition(nsAString& aWord). For example: - * - * class MyDictionary : public Dictionary { - * public: - * MyDictionary() { } - * // Make SURE you have a virtual destructor - * virtual ~myDictionary() { } - * nsDependentString* GetDefinition(const nsAString& aWord) { - * DictionaryEntry* e = GetEntry(aWord); - * if (e) { - * // We're returning an nsDependentString here, callers need to delete it - * // and it doesn't last long, but at least it doesn't create a copy - * return new nsDependentString(e->mDefinition.get()); - * } else { - * return nsnull; - * } - * } - * nsresult PutDefinition(const nsAString& aWord, - * const nsAString& aDefinition, - * const nsAString& aPronunciation) { - * DictionaryEntry* e = AddEntry(aWord); - * if (!e) { - * return NS_ERROR_OUT_OF_MEMORY; - * } - * e->mDefinition = aDefinition; - * e->mPronunciation = aPronunciation; - * return NS_OK; - * } - * } - */ - -/* - * ENTRY CLASS DEFINITION - * - * The simplifications of PLDHashTable all hinge upon the idea of an entry - * class, which is a class you define, where you store the key and values that - * you will place in each hash entry. You must define six methods for an entry - * (the standard key classes, which you can extend from, define all of these - * for you except the constructor and destructor): - * - * CONSTRUCTOR(const void* aKey) - * When your entry is constructed it will only be given a pointer to the key. - * - * DESTRUCTOR - * Called when the entry is destroyed (of course). - * - * bool MatchEntry(const void* aKey) - return true or false depending on - * whether the key pointed to by aKey matches this entry - * - * static PLDHashNumber HashKey(const void* aKey) - get a hashcode based on the - * key (must be the same every time for the same key, but does not have - * to be unique) - * - * For a small hash that just does key->value, you will often just extend a - * standard key class and put a value member into it, and have a destructor and - * constructor--nothing else necessary. - * - * See the default key entry classes as example entry classes. - * - * NOTES: - * - Do NOT ADD VIRTUAL METHODS INTO YOUR ENTRY. Everything will break. - * This is because of the 4-byte pointer C++ magically prepends onto your - * entry class. It interacts very unhappily with PLDHashTable. - */ - -/* - * PRIVATE HASHTABLE MACROS - * - * These internal macros can be used to declare the callbacks for an entry - * class, but the wrapper class macros call these for you so don't call them. - */ - -// -// DHASH_CALLBACKS -// -// Define the hashtable callback functions. Do this in one place only, as you -// will have redundant symbols otherwise. -// -// ENTRY_CLASS: the classname of the entry -// -#define DHASH_CALLBACKS(ENTRY_CLASS) \ -static PLDHashNumber \ -ENTRY_CLASS##HashKey(PLDHashTable* table, const void* key) \ -{ \ - return ENTRY_CLASS::HashKey(key); \ -} \ -static bool \ -ENTRY_CLASS##MatchEntry(PLDHashTable *table, const PLDHashEntryHdr *entry, \ - const void *key) \ -{ \ - const ENTRY_CLASS* e = static_cast(entry); \ - return e->MatchEntry(key); \ -} \ -static void \ -ENTRY_CLASS##ClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry) \ -{ \ - ENTRY_CLASS* e = static_cast(entry); \ - e->~ENTRY_CLASS(); \ -} \ -static bool \ -ENTRY_CLASS##InitEntry(PLDHashTable *table, PLDHashEntryHdr *entry, \ - const void *key) \ -{ \ - new (entry) ENTRY_CLASS(key); \ - return true; \ -} - -// -// DHASH_INIT -// -// Initialize hashtable to a certain class. -// -// HASHTABLE: the name of the PLDHashTable variable -// ENTRY_CLASS: the classname of the entry -// NUM_INITIAL_ENTRIES: the number of entry slots the hashtable should start -// with -// RV: an nsresult variable to hold the outcome of the initialization. -// Will be NS_ERROR_OUT_OF_MEMORY if failed, NS_OK otherwise. -// -#define DHASH_INIT(HASHTABLE,ENTRY_CLASS,NUM_INITIAL_ENTRIES,RV) \ -PR_BEGIN_MACRO \ - static PLDHashTableOps hash_table_ops = \ - { \ - PL_DHashAllocTable, \ - PL_DHashFreeTable, \ - ENTRY_CLASS##HashKey, \ - ENTRY_CLASS##MatchEntry, \ - PL_DHashMoveEntryStub, \ - ENTRY_CLASS##ClearEntry, \ - PL_DHashFinalizeStub, \ - ENTRY_CLASS##InitEntry \ - }; \ - bool isLive = PL_DHashTableInit(&(HASHTABLE), \ - &hash_table_ops, nsnull, \ - sizeof(ENTRY_CLASS), \ - (NUM_INITIAL_ENTRIES)); \ - if (!isLive) { \ - (HASHTABLE).ops = nsnull; \ - RV = NS_ERROR_OUT_OF_MEMORY; \ - } else { \ - RV = NS_OK; \ - } \ -PR_END_MACRO - - -/* - * WRAPPER CLASS - * - * This class handles initialization and destruction of the hashtable - * (you must call Init() yourself). It defines these functions: - * - * Init(aNumInitialEntries) - * Initialize the hashtable. This must be called once, it is only separate - * from the constructor so that you can get the return value. You should pass - * in the number of entries you think the hashtable will typically hold (this - * will be the amount of space allocated initially so that it will not have to - * grow). - * - * ENTRY_CLASS* GetEntry(aKey): - * Get the entry referenced by aKey and return a pointer to it. THIS IS A - * TEMPORARY POINTER and is only guaranteed to exist until the next time you do - * an operation on the hashtable. But you can safely use it until then. - * - * Returns nsnull if the entry is not found. - * - * ENTRY_CLASS* AddEntry(aKey): - * Create a new, empty entry and return a pointer to it for you to fill values - * into. THIS IS A TEMPORARY POINTER and is only guaranteed to exist until the - * next time you do an operation on the hashtable. But you can safely fill it - * in. - * - * Returns nsnull if the entry cannot be created (usually a low memory - * constraint). - * - * void Remove(aKey) - * Remove the entry referenced by aKey. If the entry does not exist, nothing - * will happen. - * - * - * DECL_DHASH_WRAPPER(CLASSNAME,ENTRY_CLASS,KEY_TYPE) - * - * Declare the hash class but do not define the functions. - * - * CLASSNAME: the name of the class to declare. - * ENTRY_CLASS: the class of the entry struct. - * KEY_TYPE: the name of the key type for GetEntry and AddEntry. - * - * - * DHASH_WRAPPER(CLASSNAME,ENTRY_CLASS,KEY_TYPE) - * - * Define the functions for the hash class. - * - * CLASSNAME: the name of the class to declare. - * ENTRY_CLASS: the class of the entry struct. - * KEY_TYPE: the name of the key type for GetEntry and AddEntry. - * - * - * CAVEATS: - * - You may have only *one* wrapper class per entry class. - */ - -#define DECL_DHASH_WRAPPER(CLASSNAME,ENTRY_CLASS,KEY_TYPE) \ -class DHASH_EXPORT CLASSNAME { \ -public: \ - CLASSNAME(); \ - ~CLASSNAME(); \ - nsresult Init(PRUint32 aNumInitialEntries); \ - ENTRY_CLASS* GetEntry(const KEY_TYPE aKey); \ - ENTRY_CLASS* AddEntry(const KEY_TYPE aKey); \ - void Remove(const KEY_TYPE aKey); \ - PLDHashTable mHashTable; \ -}; - -#define DHASH_WRAPPER(CLASSNAME,ENTRY_CLASS,KEY_TYPE) \ -DHASH_CALLBACKS(ENTRY_CLASS) \ -CLASSNAME::CLASSNAME() { \ - mHashTable.ops = nsnull; \ -} \ -CLASSNAME::~CLASSNAME() { \ - if (mHashTable.ops) { \ - PL_DHashTableFinish(&mHashTable); \ - } \ -} \ -nsresult CLASSNAME::Init(PRUint32 aNumInitialEntries) { \ - if (!mHashTable.ops) { \ - nsresult rv; \ - DHASH_INIT(mHashTable,ENTRY_CLASS,aNumInitialEntries,rv); \ - return rv; \ - } \ - return NS_OK; \ -} \ -ENTRY_CLASS* CLASSNAME::GetEntry(const KEY_TYPE aKey) { \ - ENTRY_CLASS* e = static_cast( \ - PL_DHashTableOperate(&mHashTable, &aKey, \ - PL_DHASH_LOOKUP)); \ - return PL_DHASH_ENTRY_IS_BUSY(e) ? e : nsnull; \ -} \ -ENTRY_CLASS* CLASSNAME::AddEntry(const KEY_TYPE aKey) { \ - return static_cast( \ - PL_DHashTableOperate(&mHashTable, &aKey, \ - PL_DHASH_ADD)); \ -} \ -void CLASSNAME::Remove(const KEY_TYPE aKey) { \ - PL_DHashTableOperate(&mHashTable, &aKey, PL_DHASH_REMOVE); \ -} - -/* - * STANDARD KEY ENTRY CLASSES - * - * We have declared some standard key classes for you to make life a little - * easier. These include string, int and void* keys. You can extend these - * and add value data members to make a working hash entry class with your - * values. - * - * PLDHashStringEntry: nsAString - * PLDHashCStringEntry: nsACString - * PLDHashInt32Entry: PRInt32 - * PLDHashVoidEntry: void* - * - * As a short example, if you want to make a class that maps int to string, - * you could do: - * - * class MyIntStringEntry : public PLDHashInt32Entry - * { - * public: - * MyIntStringEntry(const void* aKey) : PLDHashInt32Entry(aKey) { } - * ~MyIntStringEntry() { }; - * nsString mMyStr; - * }; - * - * XXX It could be advisable (unless COW strings ever happens) to have a - * PLDHashDependentStringEntry - */ - -// -// String-key entry -// -class PLDHashStringEntry : public PLDHashEntryHdr -{ -public: - PLDHashStringEntry(const void* aKey) : - mKey(*static_cast(aKey)) { } - ~PLDHashStringEntry() { } - - static PLDHashNumber HashKey(const void* key) { - return HashString(*static_cast(key)); - } - bool MatchEntry(const void* key) const { - return static_cast(key)->Equals(mKey); - } - - const nsString mKey; -}; - -// -// CString-key entry -// -class PLDHashCStringEntry : public PLDHashEntryHdr -{ -public: - PLDHashCStringEntry(const void* aKey) : - mKey(*static_cast(aKey)) { } - ~PLDHashCStringEntry() { } - - static PLDHashNumber HashKey(const void* key) { - return HashString(*static_cast(key)); - } - bool MatchEntry(const void* key) const { - return static_cast(key)->Equals(mKey); - } - - const nsCString mKey; -}; - -// -// Int-key entry -// -class PLDHashInt32Entry : public PLDHashEntryHdr -{ -public: - PLDHashInt32Entry(const void* aKey) : - mKey(*(static_cast(aKey))) { } - ~PLDHashInt32Entry() { } - - static PLDHashNumber HashKey(const void* key) { - return *static_cast(key); - } - bool MatchEntry(const void* key) const { - return *(static_cast(key)) == mKey; - } - - const PRInt32 mKey; -}; - - -// -// Void-key entry -// -class PLDHashVoidEntry : public PLDHashEntryHdr -{ -public: - PLDHashVoidEntry(const void* aKey) : - mKey(*(const void**)aKey) { } - ~PLDHashVoidEntry() { } - - static PLDHashNumber HashKey(const void* key) { - return PLDHashNumber(NS_PTR_TO_INT32(*(const void**)key)) >> 2; - } - bool MatchEntry(const void* key) const { - return *(const void**)key == mKey; - } - - const void* mKey; -}; - - -#define DHASH_EXPORT - - -#endif diff --git a/xpcom/glue/nsTHashtable.h b/xpcom/glue/nsTHashtable.h index f0404d5c2e84..762517fb0aa3 100644 --- a/xpcom/glue/nsTHashtable.h +++ b/xpcom/glue/nsTHashtable.h @@ -286,7 +286,7 @@ public: * @return the summed size of all the entries */ size_t SizeOfExcludingThis(SizeOfEntryExcludingThisFun sizeOfEntryExcludingThis, - nsMallocSizeOfFun mallocSizeOf, void *userArg = NULL) + nsMallocSizeOfFun mallocSizeOf, void *userArg = NULL) const { if (IsInitialized()) { s_SizeOfArgs args = { sizeOfEntryExcludingThis, userArg }; diff --git a/xpcom/io/nsMediaCacheRemover.cpp b/xpcom/io/nsMediaCacheRemover.cpp index 69ee923c7e27..f754848e9a8e 100644 --- a/xpcom/io/nsMediaCacheRemover.cpp +++ b/xpcom/io/nsMediaCacheRemover.cpp @@ -45,6 +45,7 @@ #include "nsILocalFile.h" #include "nsAppDirectoryServiceDefs.h" #include "nsDirectoryServiceDefs.h" +#include "nsXULAppAPI.h" #include "nsString.h" #include "nsAutoPtr.h" #include "nsITimer.h" @@ -107,10 +108,14 @@ public: idleSvc->RemoveIdleObserver(this, TEMP_FILE_IDLE_TIME); nsCOMPtr tmpDir; - nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, getter_AddRefs(tmpDir)); if (NS_FAILED(rv)) return; + + NS_ABORT_IF_FALSE(XRE_GetProcessType() == GeckoProcessType_Default, + "Need to update media cache file location"); + rv = tmpDir->AppendNative(nsDependentCString("mozilla-media-cache")); if (NS_FAILED(rv)) return;