author | David Anderson <danderson@mozilla.com> |
Fri, 09 Mar 2012 13:37:58 -0800 | |
changeset 105961 | 57680b93b9c2cf4dea49c120c5a6321966bce3c3 |
parent 105960 | 8a9d061f5008a186b51bc115832c9bcce4d733f7 (current diff) |
parent 88636 | bfb1b7520ce9714dd7d089fb266fc40f004db923 (diff) |
child 105962 | 3ab9cb07980d077b9c0e4c4f0425314dec431d1d |
push id | 23447 |
push user | danderson@mozilla.com |
push date | Tue, 11 Sep 2012 17:34:27 +0000 |
treeherder | mozilla-central@fdfaef738a00 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 13.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/accessible/public/nsIAccessibleEditableText.idl +++ b/accessible/public/nsIAccessibleEditableText.idl @@ -38,17 +38,17 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsISupports.idl" interface nsIEditor; -[scriptable, uuid(52837507-202d-4e72-a482-5f068a1fd720)] +[scriptable, uuid(e242d495-5cde-4b1c-8c84-2525b14939f5)] interface nsIAccessibleEditableText : nsISupports { /** * Sets the attributes for the text between the two given indices. The old * attributes are replaced by the new list of attributes. For example, * sets font styles, such as italic, bold... * * @param startPos - start index of the text whose attributes are modified. @@ -98,24 +98,9 @@ interface nsIAccessibleEditableText : ns /** * Pastes text from the clipboard. * * @param position - index at which to insert the text from the system * 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. - -*/
--- a/accessible/src/atk/nsApplicationAccessibleWrap.cpp +++ b/accessible/src/atk/nsApplicationAccessibleWrap.cpp @@ -692,16 +692,26 @@ nsApplicationAccessibleWrap::Unload() } // if (sATKLib) { // PR_UnloadLibrary(sATKLib); // sATKLib = nsnull; // } } 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) { *aOutAccessible = nsnull; if (!mAtkObject) { mAtkObject = reinterpret_cast<AtkObject *> (g_object_new(MAI_TYPE_ATK_OBJECT, NULL));
--- a/accessible/src/atk/nsApplicationAccessibleWrap.h +++ b/accessible/src/atk/nsApplicationAccessibleWrap.h @@ -52,16 +52,18 @@ public: public: nsApplicationAccessibleWrap(); virtual ~nsApplicationAccessibleWrap(); // nsAccessNode virtual bool Init(); // nsAccessible + NS_IMETHOD GetName(nsAString &aName); + virtual bool AppendChild(nsAccessible* aChild); virtual bool RemoveChild(nsAccessible* aChild); // return the atk object for app root accessible NS_IMETHOD GetNativeInterface(void **aOutAccessible); }; #endif /* __NS_APP_ROOT_ACCESSIBLE_H__ */
--- a/accessible/src/base/NotificationController.cpp +++ b/accessible/src/base/NotificationController.cpp @@ -675,18 +675,17 @@ NotificationController::CreateTextChange return; nsHyperTextAccessible* textAccessible = container->AsHyperText(); if (!textAccessible) return; // Don't fire event for the first html:br in an editor. if (aEvent->mAccessible->Role() == roles::WHITESPACE) { - nsCOMPtr<nsIEditor> editor; - textAccessible->GetAssociatedEditor(getter_AddRefs(editor)); + nsCOMPtr<nsIEditor> editor = textAccessible->GetEditor(); if (editor) { bool isEmpty = false; editor->GetDocumentIsEmpty(&isEmpty); if (isEmpty) return; } }
--- a/accessible/src/base/nsAccessible.h +++ b/accessible/src/base/nsAccessible.h @@ -210,16 +210,21 @@ public: /** * Return the states of accessible, not taking into account ARIA states. * Use State() to get complete set of states. */ virtual PRUint64 NativeState(); /** + * Return bit set of invisible and offscreen states. + */ + PRUint64 VisibilityState(); + + /** * Returns attributes for accessible without explicitly setted ARIA * attributes. */ virtual nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes); /** * Used by ChildAtPoint() method to get direct or deepest child at point. */ @@ -697,18 +702,16 @@ protected: /** * Return ARIA role (helper method). */ mozilla::a11y::role ARIARoleInternal(); virtual nsIFrame* GetBoundsFrame(); virtual void GetBoundsRect(nsRect& aRect, nsIFrame** aRelativeFrame); - PRUint64 VisibilityState(); - ////////////////////////////////////////////////////////////////////////////// // Name helpers /** * Compute the name of HTML node. */ nsresult GetHTMLName(nsAString& aName);
--- a/accessible/src/base/nsDocAccessible.cpp +++ b/accessible/src/base/nsDocAccessible.cpp @@ -328,18 +328,17 @@ nsDocAccessible::NativeState() state |= states::BUSY; nsIFrame* frame = GetFrame(); if (!frame || !frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) { state |= states::INVISIBLE | states::OFFSCREEN; } - nsCOMPtr<nsIEditor> editor; - GetAssociatedEditor(getter_AddRefs(editor)); + nsCOMPtr<nsIEditor> editor = GetEditor(); state |= editor ? states::EDITABLE : states::READONLY; return state; } // nsAccessible public method void nsDocAccessible::ApplyARIAState(PRUint64* aState) @@ -548,47 +547,42 @@ nsDocAccessible::GetVirtualCursor(nsIAcc mVirtualCursor = new nsAccessiblePivot(this); mVirtualCursor->AddObserver(this); } NS_ADDREF(*aVirtualCursor = mVirtualCursor); return NS_OK; } -// nsIAccessibleHyperText method -NS_IMETHODIMP nsDocAccessible::GetAssociatedEditor(nsIEditor **aEditor) +// nsHyperTextAccessible method +already_AddRefed<nsIEditor> +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<nsISupports> container = mDocument->GetContainer(); nsCOMPtr<nsIEditingSession> editingSession(do_GetInterface(container)); if (!editingSession) - return NS_OK; // No editing session interface + return nsnull; // No editing session interface nsCOMPtr<nsIEditor> 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 nsAccessible* nsDocAccessible::GetAccessible(nsINode* aNode) const { nsAccessible* accessible = mNodeToAccessibleMap.Get(aNode);
--- a/accessible/src/base/nsDocAccessible.h +++ b/accessible/src/base/nsDocAccessible.h @@ -128,18 +128,18 @@ public: virtual void ApplyARIAState(PRUint64* aState); virtual void SetRoleMapEntry(nsRoleMapEntry* aRoleMapEntry); #ifdef DEBUG_ACCDOCMGR virtual nsresult HandleAccEvent(AccEvent* aAccEvent); #endif - // nsIAccessibleText - NS_IMETHOD GetAssociatedEditor(nsIEditor **aEditor); + // nsHyperTextAccessible + virtual already_AddRefed<nsIEditor> GetEditor() const; // nsDocAccessible /** * Return presentation shell for this document accessible. */ nsIPresShell* PresShell() const { return mPresShell; }
--- a/accessible/src/html/nsHTMLFormControlAccessible.cpp +++ b/accessible/src/html/nsHTMLFormControlAccessible.cpp @@ -539,39 +539,40 @@ NS_IMETHODIMP nsHTMLTextFieldAccessible: if ( element ) { return element->Focus(); } return NS_ERROR_FAILURE; } return NS_ERROR_INVALID_ARG; } -NS_IMETHODIMP nsHTMLTextFieldAccessible::GetAssociatedEditor(nsIEditor **aEditor) +already_AddRefed<nsIEditor> +nsHTMLTextFieldAccessible::GetEditor() const { - *aEditor = nsnull; nsCOMPtr<nsIDOMNSEditableElement> 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 // whatever script is currently running. nsCOMPtr<nsIJSContextStack> stack = do_GetService("@mozilla.org/js/xpc/ContextStack;1"); bool pushed = stack && NS_SUCCEEDED(stack->Push(nsnull)); nsCOMPtr<nsIEditor> editor; - nsresult rv = editableElt->GetEditor(aEditor); + editableElt->GetEditor(getter_AddRefs(editor)); if (pushed) { JSContext* cx; stack->Pop(&cx); NS_ASSERTION(!cx, "context should be null"); } - return rv; + return editor.forget(); } //////////////////////////////////////////////////////////////////////////////// // nsHTMLTextFieldAccessible: Widgets bool nsHTMLTextFieldAccessible::IsWidget() const {
--- a/accessible/src/html/nsHTMLFormControlAccessible.h +++ b/accessible/src/html/nsHTMLFormControlAccessible.h @@ -133,18 +133,18 @@ public: NS_DECL_ISUPPORTS_INHERITED // nsIAccessible NS_IMETHOD GetValue(nsAString& _retval); NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName); NS_IMETHOD DoAction(PRUint8 index); - // nsIAccessibleEditableText - NS_IMETHOD GetAssociatedEditor(nsIEditor **aEditor); + // nsHyperTextAccessible + virtual already_AddRefed<nsIEditor> GetEditor() const; // nsAccessible virtual void ApplyARIAState(PRUint64* aState); virtual nsresult GetNameInternal(nsAString& aName); virtual mozilla::a11y::role NativeRole(); virtual PRUint64 State(); virtual PRUint64 NativeState();
--- a/accessible/src/html/nsHyperTextAccessible.cpp +++ b/accessible/src/html/nsHyperTextAccessible.cpp @@ -42,16 +42,17 @@ #include "nsAccessibilityService.h" #include "nsAccUtils.h" #include "nsDocAccessible.h" #include "nsTextAttrs.h" #include "Role.h" #include "States.h" #include "nsIClipboard.h" +#include "nsContentUtils.h" #include "nsFocusManager.h" #include "nsIDOMCharacterData.h" #include "nsIDOMDocument.h" #include "nsIDOMRange.h" #include "nsIDOMXULDocument.h" #include "nsIEditingSession.h" #include "nsIEditor.h" #include "nsIFrame.h" @@ -160,18 +161,17 @@ nsHyperTextAccessible::NativeRole() return roles::TEXT_CONTAINER; // In ATK this works } PRUint64 nsHyperTextAccessible::NativeState() { PRUint64 states = nsAccessibleWrap::NativeState(); - nsCOMPtr<nsIEditor> editor; - GetAssociatedEditor(getter_AddRefs(editor)); + nsCOMPtr<nsIEditor> editor = GetEditor(); if (editor) { PRUint32 flags; editor->GetFlags(&flags); if (0 == (flags & nsIPlaintextEditor::eEditorReadonlyMask)) { states |= states::EDITABLE; } } else if (mContent->Tag() == nsGkAtoms::article) { // We want <article> to behave like a document in terms of readonly state. @@ -705,18 +705,17 @@ nsHyperTextAccessible::HypertextOffsetsT *aEndNode = nsnull; NS_ENSURE_ARG_POINTER(aEndOffset); *aEndOffset = -1; // 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<nsIEditor> editor; - GetAssociatedEditor(getter_AddRefs(editor)); + nsCOMPtr<nsIEditor> editor = GetEditor(); if (editor) { bool isEmpty = false; editor->GetDocumentIsEmpty(&isEmpty); if (isEmpty) { nsCOMPtr<nsIDOMElement> editorRootElm; editor->GetRootElement(getter_AddRefs(editorRootElm)); nsCOMPtr<nsIDOMNode> editorRoot(do_QueryInterface(editorRootElm)); @@ -1148,25 +1147,24 @@ nsHyperTextAccessible::GetTextAttributes accAtOffsetIdx); nsresult rv = textAttrsMgr.GetAttributes(*aAttributes, &startOffset, &endOffset); NS_ENSURE_SUCCESS(rv, rv); // Compute spelling attributes on text accessible only. nsIFrame *offsetFrame = accAtOffset->GetFrame(); if (offsetFrame && offsetFrame->GetType() == nsGkAtoms::textFrame) { - nsCOMPtr<nsIDOMNode> 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); } *aStartOffset = startOffset; *aEndOffset = endOffset; return NS_OK; } @@ -1450,118 +1448,121 @@ NS_IMETHODIMP nsHyperTextAccessible::Set return InsertText(aText, 0); } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsHyperTextAccessible::InsertText(const nsAString &aText, PRInt32 aPosition) { - nsCOMPtr<nsIEditor> editor; - GetAssociatedEditor(getter_AddRefs(editor)); + if (IsDefunct()) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIEditor> editor = GetEditor(); nsCOMPtr<nsIPlaintextEditor> peditor(do_QueryInterface(editor)); NS_ENSURE_STATE(peditor); nsresult rv = SetSelectionRange(aPosition, aPosition); NS_ENSURE_SUCCESS(rv, rv); return peditor->InsertText(aText); } NS_IMETHODIMP nsHyperTextAccessible::CopyText(PRInt32 aStartPos, PRInt32 aEndPos) { - nsCOMPtr<nsIEditor> editor; - GetAssociatedEditor(getter_AddRefs(editor)); + if (IsDefunct()) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIEditor> editor = GetEditor(); NS_ENSURE_STATE(editor); nsresult rv = SetSelectionRange(aStartPos, aEndPos); NS_ENSURE_SUCCESS(rv, rv); return editor->Copy(); } NS_IMETHODIMP nsHyperTextAccessible::CutText(PRInt32 aStartPos, PRInt32 aEndPos) { - nsCOMPtr<nsIEditor> editor; - GetAssociatedEditor(getter_AddRefs(editor)); + if (IsDefunct()) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIEditor> editor = GetEditor(); NS_ENSURE_STATE(editor); nsresult rv = SetSelectionRange(aStartPos, aEndPos); NS_ENSURE_SUCCESS(rv, rv); return editor->Cut(); } NS_IMETHODIMP nsHyperTextAccessible::DeleteText(PRInt32 aStartPos, PRInt32 aEndPos) { - nsCOMPtr<nsIEditor> editor; - GetAssociatedEditor(getter_AddRefs(editor)); + if (IsDefunct()) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIEditor> editor = GetEditor(); NS_ENSURE_STATE(editor); nsresult rv = SetSelectionRange(aStartPos, aEndPos); NS_ENSURE_SUCCESS(rv, rv); return editor->DeleteSelection(nsIEditor::eNone); } NS_IMETHODIMP nsHyperTextAccessible::PasteText(PRInt32 aPosition) { - nsCOMPtr<nsIEditor> editor; - GetAssociatedEditor(getter_AddRefs(editor)); + if (IsDefunct()) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIEditor> editor = GetEditor(); NS_ENSURE_STATE(editor); nsresult rv = SetSelectionRange(aPosition, aPosition); NS_ENSURE_SUCCESS(rv, rv); return editor->Paste(nsIClipboard::kGlobalClipboard); } -NS_IMETHODIMP -nsHyperTextAccessible::GetAssociatedEditor(nsIEditor **aEditor) +already_AddRefed<nsIEditor> +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<nsIAccessible> ancestor, current = this; - while (NS_SUCCEEDED(current->GetParent(getter_AddRefs(ancestor))) && ancestor) { - nsRefPtr<nsHyperTextAccessible> 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<nsIDocShellTreeItem> docShellTreeItem = nsCoreUtils::GetDocShellTreeItemFor(mContent); nsCOMPtr<nsIEditingSession> 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<nsIEditor> editor; - return editingSession->GetEditorForWindow(docNode->GetWindow(), aEditor); + nsIDocument* docNode = mDoc->GetDocumentNode(); + editingSession->GetEditorForWindow(docNode->GetWindow(), + getter_AddRefs(editor)); + return editor.forget(); } /** * =================== Caret & Selection ====================== */ nsresult nsHyperTextAccessible::SetSelectionRange(PRInt32 aStartPos, PRInt32 aEndPos) @@ -1765,18 +1766,17 @@ nsHyperTextAccessible::GetSelectionDOMRa return; nsISelection* domSel = frameSelection->GetSelection(aType); if (!domSel) return; nsCOMPtr<nsINode> startNode = GetNode(); - nsCOMPtr<nsIEditor> editor; - GetAssociatedEditor(getter_AddRefs(editor)); + nsCOMPtr<nsIEditor> editor = GetEditor(); if (editor) { nsCOMPtr<nsIDOMElement> editorRoot; editor->GetRootElement(getter_AddRefs(editorRoot)); startNode = do_QueryInterface(editorRoot); } if (!startNode) return; @@ -1784,23 +1784,21 @@ nsHyperTextAccessible::GetSelectionDOMRa PRUint32 childCount = startNode->GetChildCount(); nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(domSel)); nsresult rv = privSel-> GetRangesForIntervalArray(startNode, 0, startNode, childCount, true, aRanges); NS_ENSURE_SUCCESS(rv,); // 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; } } } /* * Gets the number of selected regions. */ NS_IMETHODIMP @@ -1832,39 +1830,29 @@ nsHyperTextAccessible::GetSelectionBound GetSelectionDOMRanges(nsISelectionController::SELECTION_NORMAL, &ranges); PRUint32 rangeCount = ranges.Length(); if (aSelectionNum < 0 || aSelectionNum >= rangeCount) return NS_ERROR_INVALID_ARG; nsRange* range = ranges[aSelectionNum]; - // Get start point - nsCOMPtr<nsIDOMNode> startDOMNode; - range->GetStartContainer(getter_AddRefs(startDOMNode)); - nsCOMPtr<nsINode> 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<nsIDOMNode> endDOMNode; - range->GetEndContainer(getter_AddRefs(endDOMNode)); - nsCOMPtr<nsINode> 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; } nsAccessible *startAccessible = DOMPointToHypertextOffset(startNode, startOffset, aStartOffset); if (!startAccessible) { @@ -2319,78 +2307,64 @@ nsHyperTextAccessible::GetDOMPointByFram // nsHyperTextAccessible nsresult nsHyperTextAccessible::RangeBoundToHypertextOffset(nsRange *aRange, bool aIsStartBound, bool aIsStartHTOffset, PRInt32 *aHTOffset) { - nsCOMPtr<nsIDOMNode> 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<nsINode> node(do_QueryInterface(DOMNode)); nsAccessible *startAcc = DOMPointToHypertextOffset(node, nodeOffset, aHTOffset); if (aIsStartHTOffset && !startAcc) *aHTOffset = 0; return NS_OK; } // nsHyperTextAccessible nsresult -nsHyperTextAccessible::GetSpellTextAttribute(nsIDOMNode *aNode, +nsHyperTextAccessible::GetSpellTextAttribute(nsINode* aNode, PRInt32 aNodeOffset, PRInt32 *aHTStartOffset, PRInt32 *aHTEndOffset, nsIPersistentProperties *aAttributes) { nsTArray<nsRange*> ranges; GetSelectionDOMRanges(nsISelectionController::SELECTION_SPELLCHECK, &ranges); PRUint32 rangeCount = ranges.Length(); if (!rangeCount) return NS_OK; + nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> 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 PRInt32 startHTOffset = 0; nsresult rv = RangeBoundToHypertextOffset(range, false, true, &startHTOffset); NS_ENSURE_SUCCESS(rv, rv);
--- a/accessible/src/html/nsHyperTextAccessible.h +++ b/accessible/src/html/nsHyperTextAccessible.h @@ -259,16 +259,24 @@ public: * * @param aOffset [in] the given text offset */ nsAccessible* GetChildAtOffset(PRUint32 aOffset) { return GetChildAt(GetChildIndexAtOffset(aOffset)); } + ////////////////////////////////////////////////////////////////////////////// + // EditableTextAccessible + + /** + * Return the editor associated with the accessible. + */ + virtual already_AddRefed<nsIEditor> GetEditor() const; + protected: // nsHyperTextAccessible /** * Transform magic offset into text offset. */ inline PRInt32 ConvertMagicOffset(PRInt32 aOffset) { @@ -404,17 +412,17 @@ protected: * * @param aIncludeDefAttrs [in] points whether text attributes having default * values of attributes should be included * @param aSourceNode [in] the node we start to traverse from * @param aStartOffset [in, out] the start offset * @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); private: /** * End text offsets array. */
--- a/accessible/src/xforms/nsXFormsAccessible.cpp +++ b/accessible/src/xforms/nsXFormsAccessible.cpp @@ -268,34 +268,36 @@ nsXFormsEditableAccessible::NativeState( bool isRelevant = false; rv = sXFormsService->IsRelevant(DOMNode, &isRelevant); NS_ENSURE_SUCCESS(rv, state); if (isRelevant) { state |= states::EDITABLE | states::SELECTABLE_TEXT; } } - nsCOMPtr<nsIEditor> editor; - GetAssociatedEditor(getter_AddRefs(editor)); + nsCOMPtr<nsIEditor> editor = GetEditor(); NS_ENSURE_TRUE(editor, state); PRUint32 flags; editor->GetFlags(&flags); if (flags & nsIPlaintextEditor::eEditorSingleLineMask) state |= states::SINGLE_LINE; else state |= states::MULTI_LINE; return state; } -NS_IMETHODIMP -nsXFormsEditableAccessible::GetAssociatedEditor(nsIEditor **aEditor) +already_AddRefed<nsIEditor> +nsXFormsEditableAccessible::GetEditor() const { nsCOMPtr<nsIDOMNode> DOMNode(do_QueryInterface(mContent)); - return sXFormsService->GetEditor(DOMNode, aEditor); + + nsCOMPtr<nsIEditor> editor; + sXFormsService->GetEditor(DOMNode, getter_AddRefs(editor)); + return editor.forget(); } //////////////////////////////////////////////////////////////////////////////// // nsXFormsSelectableAccessible //////////////////////////////////////////////////////////////////////////////// nsXFormsSelectableAccessible:: nsXFormsSelectableAccessible(nsIContent* aContent, nsDocAccessible* aDoc) :
--- a/accessible/src/xforms/nsXFormsAccessible.h +++ b/accessible/src/xforms/nsXFormsAccessible.h @@ -139,18 +139,18 @@ public: * The class is base for accessible objects for XForms elements that have * editable area. */ class nsXFormsEditableAccessible : public nsXFormsAccessible { public: nsXFormsEditableAccessible(nsIContent* aContent, nsDocAccessible* aDoc); - // nsIAccessibleEditableText - NS_IMETHOD GetAssociatedEditor(nsIEditor **aEditor); + // nsHyperTextAccessible + virtual already_AddRefed<nsIEditor> GetEditor() const; // nsAccessible virtual PRUint64 NativeState(); }; /** * The class is base for accessible objects for XForms select and XForms
--- a/accessible/src/xul/nsXULFormControlAccessible.cpp +++ b/accessible/src/xul/nsXULFormControlAccessible.cpp @@ -845,24 +845,27 @@ NS_IMETHODIMP nsXULTextFieldAccessible:: } bool nsXULTextFieldAccessible::CanHaveAnonChildren() { return false; } -NS_IMETHODIMP nsXULTextFieldAccessible::GetAssociatedEditor(nsIEditor **aEditor) +already_AddRefed<nsIEditor> +nsXULTextFieldAccessible::GetEditor() const { - *aEditor = nsnull; - nsCOMPtr<nsIContent> inputField = GetInputField(); nsCOMPtr<nsIDOMNSEditableElement> editableElt(do_QueryInterface(inputField)); - NS_ENSURE_TRUE(editableElt, NS_ERROR_FAILURE); - return editableElt->GetEditor(aEditor); + if (!editableElt) + return nsnull; + + nsCOMPtr<nsIEditor> editor; + editableElt->GetEditor(getter_AddRefs(editor)); + return editor.forget(); } //////////////////////////////////////////////////////////////////////////////// // nsXULTextFieldAccessible: nsAccessible protected void nsXULTextFieldAccessible::CacheChildren() {
--- a/accessible/src/xul/nsXULFormControlAccessible.h +++ b/accessible/src/xul/nsXULFormControlAccessible.h @@ -255,18 +255,18 @@ public: NS_DECL_ISUPPORTS_INHERITED // nsIAccessible NS_IMETHOD GetValue(nsAString& aValue); NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName); NS_IMETHOD DoAction(PRUint8 index); - // nsIAccessibleEditableText - NS_IMETHOD GetAssociatedEditor(nsIEditor **aEditor); + // nsHyperTextAccessible + virtual already_AddRefed<nsIEditor> GetEditor() const; // nsAccessible virtual void ApplyARIAState(PRUint64* aState); virtual mozilla::a11y::role NativeRole(); virtual PRUint64 NativeState(); virtual bool CanHaveAnonChildren(); // ActionAccessible
--- a/accessible/tests/mochitest/common.js +++ b/accessible/tests/mochitest/common.js @@ -608,14 +608,14 @@ function getNodePrettyName(aNode) } catch (e) { return "' no node info '"; } } 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(); }
--- a/accessible/tests/mochitest/elm/test_nsApplicationAcc.html +++ b/accessible/tests/mochitest/elm/test_nsApplicationAcc.html @@ -10,51 +10,56 @@ <script type="application/javascript" src="../common.js"></script> <script type="application/javascript" src="../role.js"></script> <script type="application/javascript"> function doTest() { - var accessible = getApplicationAccessible(); - if (!accessible) { - SimpleTest.finish(); - return; - } + var accessible = getApplicationAccessible(); + if (!accessible) { + SimpleTest.finish(); + return; + } + + var bundleServ = + Components.classes["@mozilla.org/intl/stringbundle;1"]. + getService(Components.interfaces.nsIStringBundleService); + var brandBundle = + bundleServ.createBundle("chrome://branding/locale/brand.properties"); - // nsIAccessible::name - var bundleServ = Components.classes["@mozilla.org/intl/stringbundle;1"] - .getService(Components.interfaces.nsIStringBundleService); - var bundle = bundleServ.createBundle("chrome://branding/locale/brand.properties"); + var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]. + getService(Components.interfaces.nsIXULAppInfo); - var applicationName = ""; - + // nsIAccessible::name + var applicationName = ""; + if (LINUX || SOLARIS) { + applicationName = appInfo.name; + } else { try { - applicationName = bundle.GetStringFromName("brandShortName"); - } catch(e) { + applicationName = brandBundle.GetStringFromName("brandShortName"); + } catch(e) { } if (applicationName == "") - applicationName = "Gecko based application"; - - is (accessible.name, applicationName, "wrong application accessible name"); - - // nsIAccessibleApplication - var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]. - getService(Components.interfaces.nsIXULAppInfo); + applicationName = "Gecko based application"; + } + is (accessible.name, applicationName, "wrong application accessible name"); - is(accessible.appName, appInfo.name, "Wrong application name"); - is(accessible.appVersion, appInfo.version, "Wrong application version"); - is(accessible.platformName, "Gecko", "Wrong platform name"); - is(accessible.platformVersion, appInfo.platformVersion, - "Wrong platform version"); + // nsIAccessibleApplication + is(accessible.appName, appInfo.name, "Wrong application name"); + is(accessible.appVersion, appInfo.version, "Wrong application version"); + is(accessible.platformName, "Gecko", "Wrong platform name"); + is(accessible.platformVersion, appInfo.platformVersion, + "Wrong platform version"); - SimpleTest.finish(); + SimpleTest.finish(); } + SimpleTest.waitForExplicitFinish(); addA11yLoadEvent(doTest); </script> </head> <body> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=456121" title="nsApplicationAccessible::GetName does not return a default value when brand.properties does not exist">
--- a/accessible/tests/mochitest/events.js +++ b/accessible/tests/mochitest/events.js @@ -299,22 +299,30 @@ function eventQueue(aEventType) SimpleTest.finish(); return; } // 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. this.processNextInvoker(); return; } if (this.areAllEventsUnexpected())
--- a/aclocal.m4 +++ b/aclocal.m4 @@ -13,16 +13,17 @@ builtin(include, build/autoconf/altoptio builtin(include, build/autoconf/mozprog.m4)dnl builtin(include, build/autoconf/mozheader.m4)dnl builtin(include, build/autoconf/mozcommonheader.m4)dnl builtin(include, build/autoconf/acwinpaths.m4)dnl builtin(include, build/autoconf/lto.m4)dnl builtin(include, build/autoconf/gcc-pr49911.m4)dnl builtin(include, build/autoconf/frameptr.m4)dnl builtin(include, build/autoconf/compiler-opts.m4)dnl +builtin(include, build/autoconf/expandlibs.m4)dnl MOZ_PROG_CHECKMSYS() # Read the user's .mozconfig script. We can't do this in # configure.in: autoconf puts the argument parsing code above anything # expanded from configure.in, and we need to get the configure options # from .mozconfig in place before that argument parsing code. MOZ_READ_MOZCONFIG(.)
--- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -41,17 +41,17 @@ pref("toolkit.defaultChromeURI", "chrome pref("browser.chromeURL", "chrome://browser/content/"); #ifdef MOZ_OFFICIAL_BRANDING pref("browser.homescreenURL", "file:///system/home/homescreen.html"); #else pref("browser.homescreenURL", "file:///data/local/homescreen.html,file:///system/home/homescreen.html"); #endif // URL for the dialer application. -pref("dom.telephony.app.phone.url", "http://localhost:7777/data/local/apps/dialer/dialer.html"); +pref("dom.telephony.app.phone.url", "http://localhost:7777/data/local/apps/dialer/dialer.html http://localhost:7777/data/local/apps/homescreen/homescreen.html http://localhost:7777/apps/dialer/dialer.html http://localhost:7777/apps/homescreen/homescreen.html"); // Device pixel to CSS px ratio, in percent. Set to -1 to calculate based on display density. pref("browser.viewport.scaleRatio", -1); /* disable text selection */ pref("browser.ignoreNativeFrameTextSelection", true); /* cache prefs */
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -61,17 +61,17 @@ pref("extensions.strictCompatibility", f // for it to be compatible by default. pref("extensions.minCompatibleAppVersion", "4.0"); // Preferences for AMO integration pref("extensions.getAddons.cache.enabled", true); pref("extensions.getAddons.maxResults", 15); pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%"); pref("extensions.getAddons.getWithPerformance.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%&tMain=%TIME_MAIN%&tFirstPaint=%TIME_FIRST_PAINT%&tSessionRestored=%TIME_SESSION_RESTORED%"); -pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/search?q=%TERMS%"); +pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/search?q=%TERMS%&platform=%OS%&appver=%VERSION%"); pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/%TERMS%/all/%MAX_RESULTS%/%OS%/%VERSION%/%COMPATIBILITY_MODE%?src=firefox"); pref("extensions.webservice.discoverURL", "https://services.addons.mozilla.org/%LOCALE%/firefox/discovery/pane/%VERSION%/%OS%/%COMPATIBILITY_MODE%"); // Blocklist preferences pref("extensions.blocklist.enabled", true); pref("extensions.blocklist.interval", 86400); // Controls what level the blocklist switches from warning about items to forcibly // blocking them. @@ -209,16 +209,17 @@ pref("app.update.service.enabled", true) // Symmetric (can be overridden by individual extensions) update preferences. // e.g. // extensions.{GUID}.update.enabled // extensions.{GUID}.update.url // .. etc .. // pref("extensions.update.enabled", true); pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%¤tAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%"); +pref("extensions.update.background.url", "https://versioncheck-bg.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%¤tAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%"); pref("extensions.update.interval", 86400); // Check for updates to Extensions and // Themes every day // Non-symmetric (not shared by extensions) extension-specific [update] preferences pref("extensions.getMoreThemesURL", "https://addons.mozilla.org/%LOCALE%/firefox/getpersonas"); pref("extensions.dss.enabled", false); // Dynamic Skin Switching pref("extensions.dss.switchPending", false); // Non-dynamic switch pending after next // restart. @@ -1036,16 +1037,19 @@ pref("devtools.inspector.sidebarOpen", f pref("devtools.inspector.activeSidebar", "ruleview"); // Enable the Debugger pref("devtools.debugger.enabled", false); // The default Debugger UI height pref("devtools.debugger.ui.height", 250); +// Disable remote debugging protocol logging +pref("devtools.debugger.log", false); + // Enable the style inspector pref("devtools.styleinspector.enabled", true); // Enable the Tilt inspector pref("devtools.tilt.enabled", true); pref("devtools.tilt.intro_transition", true); pref("devtools.tilt.outro_transition", true);
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -1558,59 +1558,16 @@ function delayedStartup(isLoadingBlank, gBrowser.tabContainer.updateVisibility(); gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false); var homeButton = document.getElementById("home-button"); 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) { document.getElementById("documentDirection-separator").hidden = false; document.getElementById("documentDirection-swap").hidden = false; document.getElementById("textfieldDirection-separator").hidden = false; document.getElementById("textfieldDirection-swap").hidden = false; } @@ -5369,29 +5326,33 @@ function setToolbarVisibility(toolbar, i #ifdef MENUBAR_CAN_AUTOHIDE updateAppButtonDisplay(); #endif } var TabsOnTop = { init: function TabsOnTop_init() { + this._initialized = true; this.syncUI(); Services.prefs.addObserver(this._prefName, this, false); }, uninit: function TabsOnTop_uninit() { Services.prefs.removeObserver(this._prefName, this); }, toggle: function () { this.enabled = !Services.prefs.getBoolPref(this._prefName); }, syncUI: function () { + if (!this._initialized) + return; + let userEnabled = Services.prefs.getBoolPref(this._prefName); let enabled = userEnabled && gBrowser.tabContainer.visible; document.getElementById("cmd_ToggleTabsOnTop") .setAttribute("checked", userEnabled); document.documentElement.setAttribute("tabsontop", enabled); document.getElementById("navigator-toolbox").setAttribute("tabsontop", enabled);
--- a/browser/base/content/newtab/dropTargetShim.js +++ b/browser/base/content/newtab/dropTargetShim.js @@ -35,20 +35,22 @@ let gDropTargetShim = { node.addEventListener("dragend", this._end.bind(this), true); }, /** * Handles the 'dragstart' event. * @param aEvent The 'dragstart' event. */ _start: function DropTargetShim_start(aEvent) { - gGrid.lock(); + if (aEvent.target.classList.contains("site")) { + gGrid.lock(); - // XXX bug 505521 - Listen for dragover on the document. - document.documentElement.addEventListener("dragover", this._dragover, false); + // XXX bug 505521 - Listen for dragover on the document. + document.documentElement.addEventListener("dragover", this._dragover, false); + } }, /** * Handles the 'drag' event and determines the current drop target. * @param aEvent The 'drag' event. */ _drag: function DropTargetShim_drag(aEvent) { // Let's see if we find a drop target.
--- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -845,17 +845,17 @@ nsContextMenu.prototype = { saveVideoFrameAsImage: function () { urlSecurityCheck(this.mediaURL, this.browser.contentPrincipal, Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); let name = ""; try { let uri = makeURI(this.mediaURL); let url = uri.QueryInterface(Ci.nsIURL); if (url.fileBaseName) - name = url.fileBaseName + ".jpg"; + name = decodeURI(url.fileBaseName) + ".jpg"; } catch (e) { } if (!name) name = "snapshot.jpg"; var video = this.target; var canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); canvas.width = video.videoWidth; canvas.height = video.videoHeight; var ctxDraw = canvas.getContext("2d");
--- a/browser/base/content/test/newtab/Makefile.in +++ b/browser/base/content/test/newtab/Makefile.in @@ -15,14 +15,16 @@ include $(topsrcdir)/config/rules.mk browser_newtab_block.js \ browser_newtab_disable.js \ browser_newtab_drag_drop.js \ browser_newtab_drop_preview.js \ browser_newtab_private_browsing.js \ browser_newtab_reset.js \ browser_newtab_tabsync.js \ browser_newtab_unpin.js \ + browser_newtab_bug722273.js \ browser_newtab_bug723102.js \ + browser_newtab_bug723121.js \ head.js \ $(NULL) libs:: $(_BROWSER_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug722273.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const NOW = Date.now() * 1000; +const URL = "http://fake-site.com/"; + +let tmp = {}; +Cu.import("resource:///modules/NewTabUtils.jsm", tmp); +Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://browser/content/sanitize.js", tmp); + +let {NewTabUtils, Sanitizer} = tmp; + +let bhist = Cc["@mozilla.org/browser/global-history;2"] + .getService(Ci.nsIBrowserHistory); + +function runTests() { + clearHistory(); + fillHistory(); + yield addNewTabPageTab(); + + is(cells[0].site.url, URL, "first site is our fake site"); + + let page = { + update: function () { + executeSoon(TestRunner.next); + }, + + observe: function () {} + }; + + NewTabUtils.allPages.register(page); + yield clearHistory(); + + NewTabUtils.allPages.unregister(page); + ok(!cells[0].site, "the fake site is gone"); +} + +function fillHistory() { + let uri = makeURI(URL); + for (let i = 59; i > 0; i--) + bhist.addPageWithDetails(uri, "fake site", NOW - i * 60 * 1000000); +} + +function clearHistory() { + let s = new Sanitizer(); + s.prefDomain = "privacy.cpd."; + + let prefs = gPrefService.getBranch(s.prefDomain); + prefs.setBoolPref("history", true); + prefs.setBoolPref("downloads", false); + prefs.setBoolPref("cache", false); + prefs.setBoolPref("cookies", false); + prefs.setBoolPref("formdata", false); + prefs.setBoolPref("offlineApps", false); + prefs.setBoolPref("passwords", false); + prefs.setBoolPref("sessions", false); + prefs.setBoolPref("siteSettings", false); + + s.sanitize(); +}
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug723121.js @@ -0,0 +1,44 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function runTests() { + setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(""); + + yield addNewTabPageTab(); + checkGridLocked(false, "grid is unlocked"); + + let cell = cells[0].node; + let site = cells[0].site.node; + + sendDragEvent(site, "dragstart"); + checkGridLocked(true, "grid is now locked"); + + sendDragEvent(site, "dragend"); + checkGridLocked(false, "grid isn't locked anymore"); + + sendDragEvent(cell, "dragstart"); + checkGridLocked(false, "grid isn't locked - dragstart was ignored"); +} + +function checkGridLocked(aLocked, aMessage) { + is(cw.gGrid.node.hasAttribute("locked"), aLocked, aMessage); +} + +function sendDragEvent(aNode, aType) { + let ifaceReq = cw.QueryInterface(Ci.nsIInterfaceRequestor); + let windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils); + + let dataTransfer = { + mozUserCancelled: false, + setData: function () null, + setDragImage: function () null, + getData: function () "about:blank" + }; + + let event = cw.document.createEvent("DragEvents"); + event.initDragEvent(aType, true, true, cw, 0, 0, 0, 0, 0, + false, false, false, false, 0, null, dataTransfer); + + windowUtils.dispatchDOMEventViaPresShell(aNode, event, true); +}
--- a/browser/base/content/test/newtab/head.js +++ b/browser/base/content/test/newtab/head.js @@ -124,20 +124,21 @@ function addNewTabPageTab() { // Wait for the new tab page to be loaded. browser.addEventListener("load", function onLoad() { browser.removeEventListener("load", onLoad, true); cw = browser.contentWindow; if (NewTabUtils.allPages.enabled) { - cells = cw.gGrid.cells; - // Continue when the link cache has been populated. - NewTabUtils.links.populateCache(TestRunner.next); + NewTabUtils.links.populateCache(function () { + cells = cw.gGrid.cells; + executeSoon(TestRunner.next); + }); } else { TestRunner.next(); } }, true); } /** @@ -241,16 +242,18 @@ function unpinCell(aCell) { /** * Simulates a drop and drop operation. * @param aDropTarget the cell that is the drop target * @param aDragSource the cell that contains the dragged site (optional) */ function simulateDrop(aDropTarget, aDragSource) { let event = { + clientX: 0, + clientY: 0, dataTransfer: { mozUserCancelled: false, setData: function () null, setDragImage: function () null, getData: function () "about:blank#99\nblank" } };
--- a/browser/components/certerror/content/aboutCertError.css +++ b/browser/components/certerror/content/aboutCertError.css @@ -42,25 +42,11 @@ /* Logical CSS rules belong here, but presentation & theming rules should live in the CSS of the appropriate theme */ #technicalContentText { overflow: auto; white-space: pre-wrap; } -#technicalContent > h2, #expertContent > h2 { - cursor: pointer; - -moz-padding-start: 20px; - position: relative; - left: -20px; -} - -body[dir="rtl"] #technicalContent > h2, -body[dir="rtl"] #expertContent > h2 { - left: auto; - right: -20px; -} - -div[collapsed] > p, -div[collapsed] > div { +.expander[collapsed] + * { display: none; }
--- a/browser/components/certerror/content/aboutCertError.xhtml +++ b/browser/components/certerror/content/aboutCertError.xhtml @@ -255,28 +255,28 @@ <div id="whatShouldIDoContentText"> <p>&certerror.whatShouldIDo.content;</p> <button id='getMeOutOfHereButton'>&certerror.getMeOutOfHere.label;</button> </div> </div> <!-- The following sections can be unhidden by default by setting the "browser.xul.error_pages.expert_bad_cert" pref to true --> - <div id="technicalContent" collapsed="true"> - <h2 onclick="toggle('technicalContent');" id="technicalContentHeading">&certerror.technical.heading;</h2> - <p id="technicalContentText"/> - </div> + <h2 id="technicalContent" class="expander" collapsed="true"> + <button onclick="toggle('technicalContent');">&certerror.technical.heading;</button> + </h2> + <p id="technicalContentText"/> - <div id="expertContent" collapsed="true"> - <h2 onclick="toggle('expertContent');" id="expertContentHeading">&certerror.expert.heading;</h2> - <div> - <p>&certerror.expert.content;</p> - <p>&certerror.expert.contentPara2;</p> - <button id='exceptionDialogButton'>&certerror.addException.label;</button> - </div> + <h2 id="expertContent" class="expander" collapsed="true"> + <button onclick="toggle('expertContent');">&certerror.expert.heading;</button> + </h2> + <div> + <p>&certerror.expert.content;</p> + <p>&certerror.expert.contentPara2;</p> + <button id='exceptionDialogButton'>&certerror.addException.label;</button> </div> </div> </div> <!-- - Note: It is important to run the script this way, instead of using - an onload handler. This is because error pages are loaded as - LOAD_BACKGROUND, which means that onload handlers will not be executed.
--- a/browser/components/dirprovider/DirectoryProvider.cpp +++ b/browser/components/dirprovider/DirectoryProvider.cpp @@ -51,16 +51,17 @@ #include "nsCategoryManagerUtils.h" #include "nsComponentManagerUtils.h" #include "nsCOMArray.h" #include "nsDirectoryServiceUtils.h" #include "mozilla/ModuleUtils.h" #include "nsServiceManagerUtils.h" #include "nsStringAPI.h" #include "nsXULAppAPI.h" +#include "nsIPrefLocalizedString.h" namespace mozilla { namespace browser { NS_IMPL_ISUPPORTS2(DirectoryProvider, nsIDirectoryServiceProvider, nsIDirectoryServiceProvider2) @@ -195,17 +196,28 @@ AppendDistroSearchDirs(nsIProperties* aD nsCOMPtr<nsIFile> localePlugins; rv = searchPlugins->Clone(getter_AddRefs(localePlugins)); if (NS_FAILED(rv)) return; localePlugins->AppendNative(NS_LITERAL_CSTRING("locale")); nsCString locale; - rv = prefs->GetCharPref("general.useragent.locale", getter_Copies(locale)); + nsCOMPtr<nsIPrefLocalizedString> prefString; + rv = prefs->GetComplexValue("general.useragent.locale", + NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(prefString)); + if (NS_SUCCEEDED(rv)) { + nsAutoString wLocale; + prefString->GetData(getter_Copies(wLocale)); + CopyUTF16toUTF8(wLocale, locale); + } else { + rv = prefs->GetCharPref("general.useragent.locale", getter_Copies(locale)); + } + if (NS_SUCCEEDED(rv)) { nsCOMPtr<nsIFile> curLocalePlugins; rv = localePlugins->Clone(getter_AddRefs(curLocalePlugins)); if (NS_SUCCEEDED(rv)) { curLocalePlugins->AppendNative(locale); rv = curLocalePlugins->Exists(&exists);
--- a/browser/components/migration/Makefile.in +++ b/browser/components/migration/Makefile.in @@ -38,16 +38,18 @@ DEPTH = ../../.. topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk DIRS = public src +TEST_DIRS += tests + include $(topsrcdir)/config/rules.mk # Needed for preprocessor removal of IE Profile Migrator label - bug 236901 ifeq ($(OS_ARCH),WINNT) ifdef GNU_CXX DEFINES += -DNO_IE_MIGRATOR=1 endif endif
--- a/browser/components/migration/content/migration.js +++ b/browser/components/migration/content/migration.js @@ -60,16 +60,17 @@ var MigrationWizard = { os.addObserver(this, "Migration:Ended", false); this._wiz = document.documentElement; if ("arguments" in window && window.arguments.length > 1) { this._source = window.arguments[0]; this._migrator = window.arguments[1].QueryInterface(kIMig); this._autoMigrate = window.arguments[2].QueryInterface(kIPStartup); + this._skipImportSourcePage = window.arguments[3]; if (this._autoMigrate) { // Show the "nothing" option in the automigrate case to provide an // easily identifiable way to avoid migration and create a new profile. var nothing = document.getElementById("nothing"); nothing.hidden = false; } } @@ -89,17 +90,17 @@ var MigrationWizard = { }, // 1 - Import Source onImportSourcePageShow: function () { // Reference to the "From File" radio button var fromfile = null; - //XXXquark This function is called before init, so check for bookmarks here + // init is not called when openDialog opens the wizard, so check for bookmarks here. if ("arguments" in window && window.arguments[0] == "bookmarks") { this._bookmarks = true; fromfile = document.getElementById("fromfile"); fromfile.hidden = false; var importBookmarks = document.getElementById("importBookmarks"); importBookmarks.hidden = false; @@ -146,16 +147,22 @@ var MigrationWizard = { // We didn't find a migrator, notify the user document.getElementById("noSources").hidden = false; this._wiz.canAdvance = false; document.getElementById("importBookmarks").hidden = true; document.getElementById("importAll").hidden = true; } + + // Advance to the next page if the caller told us to. + if (this._migrator && this._skipImportSourcePage) { + this._wiz.advance(); + this._wiz.canRewind = false; + } }, onImportSourcePageAdvanced: function () { var newSource = document.getElementById("importSourceGroup").selectedItem.id; if (newSource == "nothing" || newSource == "fromfile") { if(newSource == "fromfile")
--- a/browser/components/migration/src/FirefoxProfileMigrator.js +++ b/browser/components/migration/src/FirefoxProfileMigrator.js @@ -222,16 +222,22 @@ FirefoxProfileMigrator.prototype = { * profile directory path to migrate from */ migrate : function Firefox_migrate(aItems, aStartup, aProfile) { if (aStartup) { this._replaceBookmarks = true; } + // Ensure that aProfile is not the current profile. + if (this._paths.currentProfile.path === this._sourceProfile.path) { + throw new Exception("Source and destination profiles are the same"); + return; + } + Services.obs.notifyObservers(null, "Migration:Started", null); // Reset pending count. If this count becomes 0, "Migration:Ended" // notification is sent this._pendingCount = 1; if (aItems & MIGRATOR.HISTORY) this._migrateHistory(); @@ -273,16 +279,21 @@ FirefoxProfileMigrator.prototype = { * @todo Bug 715315 - make sure source databases are not in-use */ getMigrateData: function Firefox_getMigrateData(aProfile, aDoingStartup) { this._sourceProfile = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile); this._sourceProfile.initWithPath(aProfile); let result = 0; + + // Ensure that aProfile is not the current profile. + if (this._paths.currentProfile.path === this._sourceProfile.path) + return result; + if (!this._sourceProfile.exists() || !this._sourceProfile.isReadable()) { Cu.reportError("source profile directory doesn't exist or is not readable"); return result; } // Migration initiated from the UI is not supported. if (!aDoingStartup) return result;
--- a/browser/components/migration/src/ProfileMigrator.js +++ b/browser/components/migration/src/ProfileMigrator.js @@ -11,47 +11,67 @@ const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/FileUtils.jsm"); function ProfileMigrator() { } ProfileMigrator.prototype = { - migrate: function PM_migrate(aStartup) { + migrate: function PM_migrate(aStartup, aKey) { // By opening the wizard with a supplied migrator, it will automatically // migrate from it. - let [key, migrator] = this._getDefaultMigrator(); + let key = null, migrator = null; + let skipImportSourcePage = Cc["@mozilla.org/supports-PRBool;1"] + .createInstance(Ci.nsISupportsPRBool); + if (aKey) { + key = aKey; + migrator = this._getMigratorIfSourceExists(key); + if (!migrator) { + Cu.reportError("Invalid migrator key specified or source does not exist."); + return; + } + // If the migrator was passed to us from the caller, use that migrator + // and skip the import source page. + skipImportSourcePage.data = true; + } else { + [key, migrator] = this._getDefaultMigrator(); + } if (!key) return; let params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); params.appendElement(this._toCString(key), false); params.appendElement(migrator, false); params.appendElement(aStartup, false); + params.appendElement(skipImportSourcePage, false); Services.ww.openWindow(null, "chrome://browser/content/migration/migration.xul", "_blank", "chrome,dialog,modal,centerscreen,titlebar", params); }, _toCString: function PM__toCString(aStr) { let cstr = Cc["@mozilla.org/supports-cstring;1"]. createInstance(Ci.nsISupportsCString); cstr.data = aStr; return cstr; }, _getMigratorIfSourceExists: function PM__getMigratorIfSourceExists(aKey) { - let cid = "@mozilla.org/profile/migrator;1?app=browser&type=" + aKey; - let migrator = Cc[cid].createInstance(Ci.nsIBrowserProfileMigrator); - if (migrator.sourceExists) - return migrator; + try { + let cid = "@mozilla.org/profile/migrator;1?app=browser&type=" + aKey; + let migrator = Cc[cid].createInstance(Ci.nsIBrowserProfileMigrator); + if (migrator.sourceExists) + return migrator; + } catch (ex) { + Cu.reportError("Could not get migrator: " + ex); + } return null; }, // We don't yet support checking for the default browser on all platforms, // needless to say we don't have migrators for all browsers. Thus, for each // platform, there's a fallback list of migrators used in these cases. _PLATFORM_FALLBACK_LIST: #ifdef XP_WIN
--- a/browser/components/migration/src/nsIEProfileMigrator.cpp +++ b/browser/components/migration/src/nsIEProfileMigrator.cpp @@ -1407,28 +1407,36 @@ nsIEProfileMigrator::CopyFavoritesBatche // Initialize the default bookmarks nsCOMPtr<nsIFile> profile; GetProfilePath(nsnull, profile); rv = InitializeBookmarks(profile); NS_ENSURE_SUCCESS(rv, rv); // Locate the Links toolbar folder, we want to replace the Personal Toolbar // content with Favorites in this folder. + // On versions minor or equal to IE6 the folder name is stored in the + // LinksFolderName registry key, but in newer versions it may be just a + // Links subfolder inside the default Favorites folder. nsCOMPtr<nsIWindowsRegKey> regKey = do_CreateInstance("@mozilla.org/windows-registry-key;1"); if (regKey && NS_SUCCEEDED(regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, REGISTRY_IE_TOOLBAR_KEY, nsIWindowsRegKey::ACCESS_READ))) { nsAutoString linksFolderName; if (NS_SUCCEEDED(regKey->ReadStringValue( NS_LITERAL_STRING("LinksFolderName"), - linksFolderName))) + linksFolderName))) { personalToolbarFolderName = linksFolderName; + } + else { + personalToolbarFolderName.AssignLiteral("Links"); + } } + folder = bookmarksMenuFolderId; } nsCOMPtr<nsIProperties> fileLocator = do_GetService("@mozilla.org/file/directory_service;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIFile> favoritesDirectory; (void)fileLocator->Get(NS_WIN_FAVORITES_DIR, NS_GET_IID(nsIFile),
new file mode 100644 --- /dev/null +++ b/browser/components/migration/tests/Makefile.in @@ -0,0 +1,15 @@ +# 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/. + +DEPTH = ../../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ +relativesrcdir = browser/components/migration/tests + +include $(DEPTH)/config/autoconf.mk + +XPCSHELL_TESTS = unit + +include $(topsrcdir)/config/rules.mk
new file mode 100644 --- /dev/null +++ b/browser/components/migration/tests/unit/bookmarks.html @@ -0,0 +1,16 @@ +<!DOCTYPE NETSCAPE-Bookmark-file-1> +<!-- This is an automatically generated file. + It will be read and overwritten. + DO NOT EDIT! --> +<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8"> +<TITLE>Bookmarks</TITLE> +<H1>Bookmarks Menu</H1> + +<DL><p> + <DT><A HREF="http://example.com/" ADD_DATE="1233157972" LAST_MODIFIED="1233157984">example</A> + <DT><H3 ADD_DATE="1233157910" LAST_MODIFIED="1233157972" PERSONAL_TOOLBAR_FOLDER="true">Bookmarks Toolbar</H3> +<DD>Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar + <DL><p> + <DT><A HREF="http://example.com/" ADD_DATE="1233157972" LAST_MODIFIED="1233157984">example</A> + </DL><p> +</DL><p>
new file mode 100644 --- /dev/null +++ b/browser/components/migration/tests/unit/head_migration.js @@ -0,0 +1,28 @@ +/* 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; +const Cu = Components.utils; + +const IMIGRATOR = Ci.nsIBrowserProfileMigrator; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", + "resource://gre/modules/PlacesUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); + +// Initialize profile. +let gProfD = do_get_profile(); + +function newMigratorFor(aKey) { + let cid = "@mozilla.org/profile/migrator;1?app=browser&type=" + aKey; + return Cc[cid].createInstance(Ci.nsIBrowserProfileMigrator); +} + +let (bookmarkshtml = do_get_file("bookmarks.html")) { + bookmarkshtml.copyTo(gProfD, "bookmarks.html"); +}
new file mode 100644 --- /dev/null +++ b/browser/components/migration/tests/unit/test_IE_bookmarks.js @@ -0,0 +1,28 @@ +/* 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/. */ + +function run_test() { + let migrator = newMigratorFor("ie"); + + // Sanity check for the source. + do_check_true(migrator.sourceExists); + + // Ensure bookmarks migration is available. + let availableSources = migrator.getMigrateData("FieldOfFlowers", false); + do_check_true((availableSources & IMIGRATOR.BOOKMARKS) > 0); + + // Needed to enforce bookmarks replacing. + let startup = { + doStartup: function () {}, + get directory() do_get_profile() + } + migrator.migrate(IMIGRATOR.BOOKMARKS, startup, "FieldOfFlowers"); + + // Check that at least two bookmark have been added to the menu and the + // toolbar. The first one comes from bookmarks.html, the others from IE. + do_check_true(PlacesUtils.bookmarks + .getIdForItemAt(PlacesUtils.bookmarksMenuFolderId, 1) > 0); + do_check_true(PlacesUtils.bookmarks + .getIdForItemAt(PlacesUtils.toolbarFolderId, 1) > 0); +}
new file mode 100644 --- /dev/null +++ b/browser/components/migration/tests/unit/xpcshell.ini @@ -0,0 +1,6 @@ +[DEFAULT] +head = head_migration.js +tail = + +[test_IE_bookmarks.js] +skip-if = os != "win"
--- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -406,31 +406,76 @@ BrowserGlue.prototype = { // If there are plugins installed that are outdated, and the user hasn't // been warned about them yet, open the plugins update page. if (Services.prefs.getBoolPref(PREF_PLUGINS_NOTIFYUSER)) this._showPluginUpdatePage(); // For any add-ons that were installed disabled and can be enabled offer // them to the user - var win = this.getMostRecentBrowserWindow(); - var browser = win.gBrowser; var changedIDs = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED); - AddonManager.getAddonsByIDs(changedIDs, function(aAddons) { - aAddons.forEach(function(aAddon) { - // If the add-on isn't user disabled or can't be enabled then skip it - if (!aAddon.userDisabled || !(aAddon.permissions & AddonManager.PERM_CAN_ENABLE)) - return; + if (changedIDs.length > 0) { + AddonManager.getAddonsByIDs(changedIDs, function(aAddons) { + var win = this.getMostRecentBrowserWindow(); + var browser = win.gBrowser; + aAddons.forEach(function(aAddon) { + // If the add-on isn't user disabled or can't be enabled then skip it. + if (!aAddon.userDisabled || !(aAddon.permissions & AddonManager.PERM_CAN_ENABLE)) + return; - browser.selectedTab = browser.addTab("about:newaddon?id=" + aAddon.id); - }) - }); + browser.selectedTab = browser.addTab("about:newaddon?id=" + aAddon.id); + }) + }); + } 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) { // If user has already dismissed quit request, then do nothing if ((aCancelQuit instanceof Ci.nsISupportsPRBool) && aCancelQuit.data) return; // There are several cases where we won't show a dialog here:
--- a/browser/components/places/content/browserPlacesViews.js +++ b/browser/components/places/content/browserPlacesViews.js @@ -162,120 +162,87 @@ PlacesViewBase.prototype = { destroyContextMenu: function PVB_destroyContextMenu(aPopup) { this._contextMenuShown = false; if (window.content) window.content.focus(); }, _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); this._insertNewItemToPopup(child, aPopup, null); } } 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; }, _removeChild: function PVB__removeChild(aChild) { // If document.popupNode pointed to this child, null it out, // otherwise controller's command-updating may rely on the removed // item still being "selected". if (document.popupNode == aChild) document.popupNode = null; 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: function PVB__createMenuItemForPlacesNode(aPlacesNode) { delete aPlacesNode._DOMElement; let element; let type = aPlacesNode.type; if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) { @@ -303,33 +270,38 @@ PlacesViewBase.prototype = { element.setAttribute("hostContainer", "true"); } else if (itemId != -1) { PlacesUtils.livemarks.getLivemark( { id: itemId }, 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; aPlacesNode._siteURI = aLivemark.siteURI; } } ); } let popup = document.createElement("menupopup"); popup._placesNode = PlacesUtils.asContainer(aPlacesNode); - if (this._nativeView) { - popup._startMarker = -1; - popup._endMarker = -1; + + if (!this._nativeView) { + popup.setAttribute("placespopup", "true"); } - else - popup.setAttribute("placespopup", "true"); + #ifdef XP_MACOSX // No context menu on mac. popup.setAttribute("context", "placesContext"); #endif element.appendChild(popup); element.className = "menu-iconic bookmark-item"; aPlacesNode._DOMElement = popup; @@ -349,50 +321,30 @@ PlacesViewBase.prototype = { aPlacesNode._DOMElement = element; return element; }, _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; }, _setLivemarkSiteURIMenuItem: function PVB__setLivemarkSiteURIMenuItem(aPopup) { let siteUrl = aPopup._placesNode._siteURI ? aPopup._placesNode._siteURI.spec : null; 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. aPopup._siteURIMenuitem = document.createElement("menuitem"); aPopup._siteURIMenuitem.className = "openlivemarksite-menuitem"; aPopup._siteURIMenuitem.setAttribute("targetURI", siteUrl); aPopup._siteURIMenuitem.setAttribute("oncommand", "openUILink(this.getAttribute('targetURI'), event);"); @@ -402,65 +354,58 @@ PlacesViewBase.prototype = { // Note: stopPropagation is needed to avoid serving middle-click // with BT_onClick that would open all items in tabs. aPopup._siteURIMenuitem.setAttribute("onclick", "checkForMiddleClick(this, event); event.stopPropagation();"); let label = 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); } }, /** * Add, update or remove the livemark status menuitem. * @param aPopup * The livemark container popup * @param aStatus * The livemark status */ _setLivemarkStatusMenuItem: function PVB_setLivemarkStatusMenuItem(aPopup, aStatus) { - let itemId = aPopup._placesNode.itemId; let statusMenuitem = aPopup._statusMenuitem; let stringId = ""; if (aStatus == Ci.mozILivemark.STATUS_LOADING) stringId = "bookmarksLivemarkLoading"; else if (aStatus == Ci.mozILivemark.STATUS_FAILED) stringId = "bookmarksLivemarkFailed"; if (stringId && !statusMenuitem) { // 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) { // Status has changed, update the cached status menuitem. statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId)); } else if (!stringId && statusMenuitem) { // The livemark has finished loading. aPopup.removeChild(aPopup._statusMenuitem); aPopup._statusMenuitem = null; - aPopup._startMarker--; } }, toggleCutNode: function PVB_toggleCutNode(aNode, aValue) { let elt = aNode._DOMElement; if (elt) { // We may get the popup for menus, but we need the menu itself. if (elt.localName == "menupopup") @@ -512,16 +457,22 @@ PlacesViewBase.prototype = { throw "aPlacesNode must have _DOMElement set"; // All livemarks have a feedURI, so use it as our indicator of a livemark // being modified. if (aAnno == PlacesUtils.LMANNO_FEEDURI) { 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( { id: aPlacesNode.itemId }, (function (aStatus, aLivemark) { if (Components.isSuccessCode(aStatus)) { // Set an expando on the node, controller will use it to build // its metadata. @@ -575,23 +526,18 @@ PlacesViewBase.prototype = { elt = elt.parentNode; if (parentElt._built) { parentElt.removeChild(elt); // Figure out if we need to show the "<Empty>" 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); } }, nodeReplaced: function PVB_nodeReplaced(aParentPlacesNode, aOldPlacesNode, aNewPlacesNode, aIndex) { let parentElt = aParentPlacesNode._DOMElement; if (!parentElt) throw "aParentPlacesNode node must have _DOMElement set"; @@ -615,18 +561,19 @@ PlacesViewBase.prototype = { } }, nodeHistoryDetailsChanged: function PVB_nodeHistoryDetailsChanged(aPlacesNode, aTime, aCount) { 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"); else child.removeAttribute("visited"); break; } } @@ -644,21 +591,21 @@ PlacesViewBase.prototype = { function PVB_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) { let parentElt = aParentPlacesNode._DOMElement; if (!parentElt) throw "aParentPlacesNode node must have _DOMElement set"; 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: function PBV_nodeMoved(aPlacesNode, aOldParentPlacesNode, aOldIndex, aNewParentPlacesNode, aNewIndex) { // Note: the current implementation of moveItem does not actually // use this notification when the item in question is moved from one @@ -679,17 +626,18 @@ PlacesViewBase.prototype = { let parentElt = aNewParentPlacesNode._DOMElement; if (!parentElt) throw "aNewParentPlacesNode node must have _DOMElement set"; 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]); } }, containerStateChanged: function PVB_containerStateChanged(aPlacesNode, aOldState, aNewState) { if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED || aNewState == Ci.nsINavHistoryContainerResultNode.STATE_CLOSED) { @@ -704,17 +652,19 @@ PlacesViewBase.prototype = { PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId }, (function (aStatus, aLivemark) { if (Components.isSuccessCode(aStatus)) { let shouldInvalidate = !aPlacesNode._feedURI; aPlacesNode._feedURI = aLivemark.feedURI; aPlacesNode._siteURI = aLivemark.siteURI; if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) { aLivemark.registerForUpdates(aPlacesNode, this); + // Prioritize the current livemark. aLivemark.reload(); + PlacesUtils.livemarks.reloadLivemarks(); if (shouldInvalidate) this.invalidateContainer(aPlacesNode); } else { aLivemark.unregisterForUpdates(aPlacesNode); } } }).bind(this) @@ -816,48 +766,83 @@ PlacesViewBase.prototype = { hasMultipleURIs = numURINodes > 1; } if (!hasMultipleURIs) { // We don't have to show any option. 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) { // Create a separator before options. 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"); aPopup._endOptOpenAllInTabs.className = "openintabs-menuitem"; aPopup._endOptOpenAllInTabs.setAttribute("oncommand", "PlacesUIUtils.openContainerNodeInTabs(this.parentNode._placesNode, event, " + "PlacesUIUtils.getViewForNode(this));"); aPopup._endOptOpenAllInTabs.setAttribute("onclick", "checkForMiddleClick(this, event); event.stopPropagation();"); 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; if (!popup._built) this._rebuildPopup(popup); this._mayAddCommandsItems(popup); } @@ -912,21 +897,16 @@ function PlacesToolbar(aPlace) { Services.telemetry.getHistogramById("FX_BOOKMARKS_TOOLBAR_INIT_MS") .add(Date.now() - startTime); } PlacesToolbar.prototype = { __proto__: PlacesViewBase.prototype, _cbEvents: ["dragstart", "dragover", "dragexit", "dragend", "drop", -#ifdef XP_UNIX -#ifndef XP_MACOSX - "mousedown", "mouseup", -#endif -#endif "mousemove", "mouseover", "mouseout"], QueryInterface: function PT_QueryInterface(aIID) { if (aIID.equals(Ci.nsIDOMEventListener) || aIID.equals(Ci.nsITimerCallback)) return this; return PlacesViewBase.prototype.QueryInterface.apply(this, arguments); @@ -1113,26 +1093,16 @@ PlacesToolbar.prototype = { this._onMouseOver(aEvent); break; case "mousemove": this._onMouseMove(aEvent); break; case "mouseout": this._onMouseOut(aEvent); break; -#ifdef XP_UNIX -#ifndef XP_MACOSX - case "mouseup": - this._onMouseUp(aEvent); - break; - case "mousedown": - this._onMouseDown(aEvent); - break; -#endif -#endif case "popupshowing": this._onPopupShowing(aEvent); break; case "popuphidden": this._onPopupHidden(aEvent); break; default: throw "Trying to handle unexpected event."; @@ -1550,24 +1520,16 @@ PlacesToolbar.prototype = { let draggedElt = aEvent.target; if (draggedElt.parentNode != this._rootElt || !draggedElt._placesNode) return; if (draggedElt.localName == "toolbarbutton" && draggedElt.getAttribute("type") == "menu") { // If the drag gesture on a container is toward down we open instead // of dragging. -#ifdef XP_UNIX -#ifndef XP_MACOSX - if (this._mouseDownTimer) { - this._mouseDownTimer.cancel(); - this._mouseDownTimer = null; - } -#endif -#endif let translateY = this._cachedMouseMoveEvent.clientY - aEvent.clientY; let translateX = this._cachedMouseMoveEvent.clientX - aEvent.clientX; if ((translateY) >= Math.abs(translateX/2)) { // Don't start the drag. aEvent.preventDefault(); // Open the menu. draggedElt.open = true; return; @@ -1730,57 +1692,16 @@ PlacesToolbar.prototype = { // Clear the dragover attribute if present, if we are dragging into a // folder in the hierachy of current opened popup we don't clear // this attribute on clearOverFolder. See Notify for closeTimer. if (parent.hasAttribute("dragover")) parent.removeAttribute("dragover"); } }, -#ifdef XP_UNIX -#ifndef XP_MACOSX - _onMouseDown: function PT__onMouseDown(aEvent) { - let target = aEvent.target; - if (aEvent.button == 0 && - target.localName == "toolbarbutton" && - target.getAttribute("type") == "menu") { - this._allowPopupShowing = false; - // On Linux we can open the popup only after a delay. - // Indeed as soon as the menupopup opens we are unable to start a - // drag aEvent. See bug 500081 for details. - this._mouseDownTimer = Cc["@mozilla.org/timer;1"]. - createInstance(Ci.nsITimer); - let callback = { - _self: this, - _target: target, - notify: function(timer) { - this._target.open = true; - this._mouseDownTimer = null; - } - }; - - this._mouseDownTimer.initWithCallback(callback, 300, - Ci.nsITimer.TYPE_ONE_SHOT); - } - }, - - _onMouseUp: function PT__onMouseUp(aEvent) { - if (aEvent.button != 0) - return; - - if (this._mouseDownTimer) { - // On a click (down/up), we should open the menu popup. - this._mouseDownTimer.cancel(); - this._mouseDownTimer = null; - aEvent.target.open = true; - } - }, -#endif -#endif - _onMouseMove: function PT__onMouseMove(aEvent) { // Used in dragStart to prevent dragging folders when dragging down. this._cachedMouseMoveEvent = aEvent; if (this._openedMenuButton == null || PlacesControllerDragHelper.getSession()) return; @@ -1803,18 +1724,16 @@ function PlacesMenu(aPopupShowingEvent, this._viewElt = this._rootElt.parentNode; // <menu> this._viewElt._placesView = this; this._addEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true); this._addEventListeners(window, ["unload"], false); #ifdef XP_MACOSX if (this._viewElt.parentNode.localName == "menubar") { this._nativeView = true; - this._rootElt._startMarker = -1; - this._rootElt._endMarker = -1; } #endif PlacesViewBase.call(this, aPlace); this._onPopupShowing(aPopupShowingEvent); } PlacesMenu.prototype = { @@ -1824,18 +1743,16 @@ PlacesMenu.prototype = { if (aIID.equals(Ci.nsIDOMEventListener)) return this; return PlacesViewBase.prototype.QueryInterface.apply(this, arguments); }, _removeChild: function PM_removeChild(aChild) { PlacesViewBase.prototype._removeChild.apply(this, arguments); - if (this._endMarker != -1) - this._endMarker--; }, uninit: function PM_uninit() { this._removeEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true); this._removeEventListeners(window, ["unload"], false); PlacesViewBase.prototype.uninit.apply(this, arguments);
--- a/browser/components/places/content/editBookmarkOverlay.xul +++ b/browser/components/places/content/editBookmarkOverlay.xul @@ -85,29 +85,27 @@ <row align="center" id="editBMPanel_feedLocationRow"> <label value="&editBookmarkOverlay.feedLocation.label;" class="editBMPanel_rowLabel" accesskey="&editBookmarkOverlay.feedLocation.accesskey;" control="editBMPanel_feedLocationField" observes="paneElementsBroadcaster"/> <textbox id="editBMPanel_feedLocationField" class="uri-element" - onblur="gEditItemOverlay.onFeedLocationFieldBlur();" observes="paneElementsBroadcaster"/> </row> <row align="center" id="editBMPanel_siteLocationRow"> <label value="&editBookmarkOverlay.siteLocation.label;" class="editBMPanel_rowLabel" accesskey="&editBookmarkOverlay.siteLocation.accesskey;" control="editBMPanel_siteLocationField" observes="paneElementsBroadcaster"/> <textbox id="editBMPanel_siteLocationField" class="uri-element" - onblur="gEditItemOverlay.onSiteLocationFieldBlur();" observes="paneElementsBroadcaster"/> </row> <row align="center" id="editBMPanel_folderRow"> <label value="&editBookmarkOverlay.folder.label;" class="editBMPanel_rowLabel" control="editBMPanel_folderMenuList" observes="paneElementsBroadcaster"/>
--- a/browser/components/places/content/menu.xml +++ b/browser/components/places/content/menu.xml @@ -67,38 +67,33 @@ "menupopup-drop-indicator-bar"); </field> <field name="_scrollBox"> document.getAnonymousElementByAttribute(this, "class", "popup-internal-box"); </field> - <!-- markers for start and end of valid places items --> - <field name="_startMarker">-1</field> - <field name="_endMarker">-1</field> - <!-- This is the view that manage the popup --> <field name="_rootView">PlacesUIUtils.getViewForNode(this);</field> <!-- Check if we should hide the drop indicator for the target --> <method name="_hideDropIndicator"> <parameter name="aEvent"/> <body><![CDATA[ - var target = aEvent.target; + let target = aEvent.target; - // in some view we have _startMarker and _endMarker, we should not - // draw the drop indicator outside of them - var betweenMarkers = true; - if (this._startMarker != -1 && - target.boxObject.y <= this.childNodes[this._startMarker].boxObject.y) - betweenMarkers = false; - if (this._endMarker != -1 && - target.boxObject.y >= 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); ]]></body> </method> <!-- This function returns information about where to drop when dragging over this popup insertion point -->
--- a/browser/components/places/content/treeView.js +++ b/browser/components/places/content/treeView.js @@ -891,17 +891,19 @@ PlacesTreeView.prototype = { PlacesUtils.livemarks.getLivemark({ id: aNode.itemId }, (function (aStatus, aLivemark) { if (Components.isSuccessCode(aStatus)) { let shouldInvalidate = !aNode._feedURI; aNode._feedURI = aLivemark.feedURI; if (aNewState == Components.interfaces.nsINavHistoryContainerResultNode.STATE_OPENED) { aLivemark.registerForUpdates(aNode, this); + // Prioritize the current livemark. aLivemark.reload(); + PlacesUtils.livemarks.reloadLivemarks(); if (shouldInvalidate) this.invalidateContainer(aNode); } else { aLivemark.unregisterForUpdates(aNode); } } }).bind(this)
--- a/browser/devtools/debugger/DebuggerUI.jsm +++ b/browser/devtools/debugger/DebuggerUI.jsm @@ -19,16 +19,17 @@ * Mozilla Foundation * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Dave Camp <dcamp@mozilla.com> (original author) * Panos Astithas <past@mozilla.com> * Victor Porof <vporof@mozilla.com> + * Mihai Sucan <mihai.sucan@gmail.com> * * 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 @@ -55,20 +56,48 @@ let EXPORTED_SYMBOLS = ["DebuggerUI"]; /** * Creates a pane that will host the debugger UI. */ function DebuggerPane(aTab) { this._tab = aTab; this._close = this.close.bind(this); this._debugTab = this.debugTab.bind(this); + this.breakpoints = {}; } DebuggerPane.prototype = { /** + * Skip editor breakpoint change events. + * + * This property tells the source editor event handler to skip handling of + * the BREAKPOINT_CHANGE events. This is used when the debugger adds/removes + * breakpoints from the editor. Typically, the BREAKPOINT_CHANGE event handler + * adds/removes events from the debugger, but when breakpoints are added from + * the public debugger API, we need to do things in reverse. + * + * This implementation relies on the fact that the source editor fires the + * BREAKPOINT_CHANGE events synchronously. + * + * @private + * @type boolean + */ + _skipEditorBreakpointChange: false, + + /** + * The list of breakpoints in the debugger as tracked by the current + * DebuggerPane instance. This an object where the values are BreakpointActor + * objects received from the client, while the keys are actor names, for + * example "conn0.breakpoint3". + * + * @type object + */ + breakpoints: null, + + /** * Creates and initializes the widgets contained in the debugger UI. */ create: function DP_create(gBrowser) { this._tab._scriptDebugger = this; this._nbox = gBrowser.getNotificationBox(this._tab.linkedBrowser); this._splitter = gBrowser.parentNode.ownerDocument.createElement("splitter"); this._splitter.setAttribute("class", "hud-splitter"); @@ -82,44 +111,251 @@ DebuggerPane.prototype = { this.frame.addEventListener("DOMContentLoaded", function initPane(aEvent) { if (aEvent.target != self.frame.contentDocument) { return; } self.frame.removeEventListener("DOMContentLoaded", initPane, true); // Initialize the source editor. self.frame.contentWindow.editor = self.editor = new SourceEditor(); + self.frame.contentWindow.updateEditorBreakpoints = + self._updateEditorBreakpoints.bind(self); let config = { mode: SourceEditor.MODES.JAVASCRIPT, showLineNumbers: true, - readOnly: true + readOnly: true, + showAnnotationRuler: true, + showOverviewRuler: true, }; let editorPlaceholder = self.frame.contentDocument.getElementById("editor"); self.editor.init(editorPlaceholder, config, self._onEditorLoad.bind(self)); }, true); this.frame.addEventListener("DebuggerClose", this._close, true); this.frame.setAttribute("src", "chrome://browser/content/debugger.xul"); }, /** * The load event handler for the source editor. This method does post-load * editor initialization. */ _onEditorLoad: function DP__onEditorLoad() { + this.editor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, + this._onEditorBreakpointChange.bind(this)); // Connect to the debugger server. this.connect(); }, /** + * Event handler for breakpoint changes that happen in the editor. This + * function syncs the breakpoint changes in the editor to those in the + * debugger. + * + * @private + * @param object aEvent + * The SourceEditor.EVENTS.BREAKPOINT_CHANGE event object. + */ + _onEditorBreakpointChange: function DP__onEditorBreakpointChange(aEvent) { + if (this._skipEditorBreakpointChange) { + return; + } + + aEvent.added.forEach(this._onEditorBreakpointAdd, this); + aEvent.removed.forEach(this._onEditorBreakpointRemove, this); + }, + + /** + * Retrieve the URL of the selected script in the debugger view. + * + * @private + * @return string + * The URL of the selected script. + */ + _selectedScript: function DP__selectedScript() { + return this.debuggerWindow ? + this.debuggerWindow.DebuggerView.Scripts.selected : null; + }, + + /** + * Event handler for new breakpoints that come from the editor. + * + * @private + * @param object aBreakpoint + * The breakpoint object coming from the editor. + */ + _onEditorBreakpointAdd: function DP__onEditorBreakpointAdd(aBreakpoint) { + let location = { + url: this._selectedScript(), + line: aBreakpoint.line + 1, + }; + + if (location.url) { + let callback = function (aClient, aError) { + if (aError) { + this._skipEditorBreakpointChange = true; + let result = this.editor.removeBreakpoint(aBreakpoint.line); + this._skipEditorBreakpointChange = false; + } + }.bind(this); + this.addBreakpoint(location, callback, true); + } + }, + + /** + * Event handler for breakpoints that are removed from the editor. + * + * @private + * @param object aBreakpoint + * The breakpoint object that was removed from the editor. + */ + _onEditorBreakpointRemove: function DP__onEditorBreakpointRemove(aBreakpoint) { + let url = this._selectedScript(); + let line = aBreakpoint.line + 1; + if (!url) { + return; + } + + let breakpoint = this.getBreakpoint(url, line); + if (breakpoint) { + this.removeBreakpoint(breakpoint, null, true); + } + }, + + /** + * Update the breakpoints in the editor view. This function takes the list of + * breakpoints in the debugger and adds them back into the editor view. This + * is invoked when the selected script is changed. + * + * @private + */ + _updateEditorBreakpoints: function DP__updateEditorBreakpoints() + { + let url = this._selectedScript(); + if (!url) { + return; + } + + this._skipEditorBreakpointChange = true; + for each (let breakpoint in this.breakpoints) { + if (breakpoint.location.url == url) { + this.editor.addBreakpoint(breakpoint.location.line - 1); + } + } + this._skipEditorBreakpointChange = false; + }, + + /** + * Add a breakpoint. + * + * @param object aLocation + * The location where you want the breakpoint. This object must have + * two properties: + * - url - the URL of the script. + * - line - the line number (starting from 1). + * @param function [aCallback] + * Optional function to invoke once the breakpoint is added. The + * callback is invoked with two arguments: + * - aBreakpointClient - the BreakpointActor client object, if the + * breakpoint has been added successfully. + * - aResponseError - if there was any error. + * @param boolean [aNoEditorUpdate=false] + * Tells if you want to skip editor updates. Typically the editor is + * updated to visually indicate that a breakpoint has been added. + */ + addBreakpoint: + function DP_addBreakpoint(aLocation, aCallback, aNoEditorUpdate) { + let breakpoint = this.getBreakpoint(aLocation.url, aLocation.line); + if (breakpoint) { + aCallback && aCallback(breakpoint); + return; + } + + this.activeThread.setBreakpoint(aLocation, function(aResponse, aBpClient) { + if (!aResponse.error) { + this.breakpoints[aBpClient.actor] = aBpClient; + + if (!aNoEditorUpdate) { + let url = this._selectedScript(); + if (url == aLocation.url) { + this._skipEditorBreakpointChange = true; + this.editor.addBreakpoint(aLocation.line - 1); + this._skipEditorBreakpointChange = false; + } + } + } + + aCallback && aCallback(aBpClient, aResponse.error); + }.bind(this)); + }, + + /** + * Remove a breakpoint. + * + * @param object aBreakpoint + * The breakpoint you want to remove. + * @param function [aCallback] + * Optional function to invoke once the breakpoint is removed. The + * callback is invoked with one argument: the breakpoint location + * object which holds the url and line properties. + * @param boolean [aNoEditorUpdate=false] + * Tells if you want to skip editor updates. Typically the editor is + * updated to visually indicate that a breakpoint has been removed. + */ + removeBreakpoint: + function DP_removeBreakpoint(aBreakpoint, aCallback, aNoEditorUpdate) { + if (!(aBreakpoint.actor in this.breakpoints)) { + aCallback && aCallback(aBreakpoint.location); + return; + } + + aBreakpoint.remove(function() { + delete this.breakpoints[aBreakpoint.actor]; + + if (!aNoEditorUpdate) { + let url = this._selectedScript(); + if (url == aBreakpoint.location.url) { + this._skipEditorBreakpointChange = true; + this.editor.removeBreakpoint(aBreakpoint.location.line - 1); + this._skipEditorBreakpointChange = false; + } + } + + aCallback && aCallback(aBreakpoint.location); + }.bind(this)); + }, + + /** + * Get the breakpoint object at the given location. + * + * @param string aUrl + * The URL of where the breakpoint is. + * @param number aLine + * The line number where the breakpoint is. + * @return object + * The BreakpointActor object. + */ + getBreakpoint: function DP_getBreakpoint(aUrl, aLine) { + for each (let breakpoint in this.breakpoints) { + if (breakpoint.location.url == aUrl && breakpoint.location.line == aLine) { + return breakpoint; + } + } + return null; + }, + + /** * Closes the debugger UI removing child nodes and event listeners. */ close: function DP_close() { + for each (let breakpoint in this.breakpoints) { + this.removeBreakpoint(breakpoint); + } + if (this._tab) { this._tab._scriptDebugger = null; this._tab = null; } if (this.frame) { DebuggerUIPreferences.height = this.frame.height; this.frame.removeEventListener("unload", this._close, true); @@ -187,17 +423,17 @@ DebuggerPane.prototype = { self.onConnected(self); } }); }); }); }, get debuggerWindow() { - return this.frame.contentWindow; + return this.frame ? this.frame.contentWindow : null; }, get debuggerClient() { return this._client; }, get activeThread() { try { @@ -335,16 +571,17 @@ DebuggerUI.prototype = { let doc = dbg.frame.contentDocument; let scripts = doc.getElementById("scripts"); let elt = scripts.getElementsByAttribute("value", aSourceUrl)[0]; let script = elt.getUserData("sourceScript"); script.loaded = true; script.text = aSourceText; script.contentType = aContentType; elt.setUserData("sourceScript", script, null); + dbg._updateEditorBreakpoints(); } }; /** * Various debugger UI preferences (currently just the pane height). */ let DebuggerUIPreferences = {
--- a/browser/devtools/debugger/debugger-view.js +++ b/browser/devtools/debugger/debugger-view.js @@ -17,16 +17,17 @@ * * 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): * Victor Porof <vporof@mozilla.com> (original author) + * Mihai Sucan <mihai.sucan@gmail.com> * * 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 @@ -194,17 +195,16 @@ DebuggerView.Stackframes = { * @param boolean aSelect * True if the frame should be selected, false otherwise. */ highlightFrame: function DVF_highlightFrame(aDepth, aSelect) { let frame = document.getElementById("stackframe-" + aDepth); // the list item wasn't found in the stackframe container if (!frame) { - dump("The frame list item wasn't found in the stackframes container."); return; } // add the 'selected' css class if the frame isn't already selected if (aSelect && !frame.classList.contains("selected")) { frame.classList.add("selected"); // remove the 'selected' css class if the frame is already selected @@ -351,17 +351,16 @@ DebuggerView.Properties = { // compute the id of the element if not specified aId = aId || (aName.toLowerCase().trim().replace(" ", "-") + "-scope"); // contains generic nodes and functionality let element = this._createPropertyElement(aName, aId, "scope", this._vars); // make sure the element was created successfully if (!element) { - dump("The debugger scope container wasn't created properly: " + aId); return null; } /** * @see DebuggerView.Properties._addVar */ element.addVar = this._addVar.bind(this, element); @@ -393,17 +392,16 @@ DebuggerView.Properties = { aId = aId || (aScope.id + "->" + aName + "-variable"); // contains generic nodes and functionality let element = this._createPropertyElement(aName, aId, "variable", aScope.querySelector(".details")); // make sure the element was created successfully if (!element) { - dump("The debugger variable container wasn't created properly: " + aId); return null; } /** * @see DebuggerView.Properties._setGrip */ element.setGrip = this._setGrip.bind(this, element); @@ -461,17 +459,16 @@ DebuggerView.Properties = { if (!aVar) { return null; } let info = aVar.querySelector(".info") || aVar.target.info; // make sure the info node exists if (!info) { - dump("Could not set the grip for the corresponding variable: " + aVar.id); return null; } info.textContent = this._propertyString(aGrip); info.classList.add(this._propertyColor(aGrip)); return aVar; }, @@ -564,17 +561,16 @@ DebuggerView.Properties = { aId = aId || (aVar.id + "->" + aProperty[0] + "-property"); // contains generic nodes and functionality let element = this._createPropertyElement(aName, aId, "property", aVar.querySelector(".details")); // make sure the element was created successfully if (!element) { - dump("The debugger property container wasn't created properly."); return null; } /** * @see DebuggerView.Properties._setGrip */ element.setGrip = this._setGrip.bind(this, element); @@ -705,21 +701,19 @@ DebuggerView.Properties = { * @param object aParent * The parent node which will contain the element. * @return object * The newly created html node representing the generic elem. */ _createPropertyElement: function DVP__createPropertyElement(aName, aId, aClass, aParent) { // make sure we don't duplicate anything and the parent exists if (document.getElementById(aId)) { - dump("Duplicating a property element id is not allowed."); return null; } if (!aParent) { - dump("A property element must have a valid parent node specified."); return null; } let element = document.createElement("div"); let arrow = document.createElement("span"); let name = document.createElement("span"); let title = document.createElement("div"); let details = document.createElement("div"); @@ -1122,16 +1116,25 @@ DebuggerView.Scripts = { for (let i = 0; i < this._scripts.itemCount; i++) { if (this._scripts.getItemAtIndex(i).value == aUrl) { this._scripts.selectedIndex = i; break; } } }, + /** + * Retrieve the URL of the selected script. + * @return string|null + */ + get selected() { + return this._scripts.selectedItem ? + this._scripts.selectedItem.value : null; + }, + /** * Adds a script to the scripts container. * If the script already exists (was previously added), null is returned. * Otherwise, the newly created element is returned. * * @param string aLabel * The simplified script location to be shown. * @param string aScript
--- a/browser/devtools/debugger/debugger.js +++ b/browser/devtools/debugger/debugger.js @@ -73,17 +73,18 @@ function startDebuggingTab(aClient, aTab { gClient = aClient; gClient.attachTab(aTabGrip.actor, function(aResponse, aTabClient) { if (aTabClient) { gTabClient = aTabClient; gClient.attachThread(aResponse.threadActor, function(aResponse, aThreadClient) { if (!aThreadClient) { - dump("Couldn't attach to thread: "+aResponse.error+"\n"); + Components.utils.reportError("Couldn't attach to thread: " + + aResponse.error); return; } ThreadState.connect(aThreadClient, function() { StackFrames.connect(aThreadClient, function() { SourceScripts.connect(aThreadClient, function() { aThreadClient.resume(); }); }); @@ -607,16 +608,17 @@ var SourceScripts = { if (!aScript.loaded) { // Notify the chrome code that we need to load a script file. var evt = document.createEvent("CustomEvent"); evt.initCustomEvent("Debugger:LoadSource", true, false, aScript.url); document.documentElement.dispatchEvent(evt); window.editor.setText(DebuggerView.getStr("loadingText")); } else { window.editor.setText(aScript.text); + window.updateEditorBreakpoints(); } } }; SourceScripts.onPaused = SourceScripts.onPaused.bind(SourceScripts); SourceScripts.onScripts = SourceScripts.onScripts.bind(SourceScripts); SourceScripts.onNewScript = SourceScripts.onNewScript.bind(SourceScripts); SourceScripts.onScriptsCleared = SourceScripts.onScriptsCleared.bind(SourceScripts);
--- a/browser/devtools/debugger/test/Makefile.in +++ b/browser/devtools/debugger/test/Makefile.in @@ -68,16 +68,17 @@ include $(topsrcdir)/config/rules.mk browser_dbg_stack-03.js \ browser_dbg_stack-04.js \ browser_dbg_location-changes.js \ browser_dbg_script-switching.js \ browser_dbg_pause-resume.js \ browser_dbg_update-editor-mode.js \ browser_dbg_select-line.js \ browser_dbg_clean-exit.js \ + browser_dbg_bug723069_editor-breakpoints.js \ head.js \ $(NULL) _BROWSER_TEST_PAGES = \ browser_dbg_tab1.html \ browser_dbg_tab2.html \ browser_dbg_debuggerstatement.html \ browser_dbg_stack.html \
new file mode 100644 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_bug723069_editor-breakpoints.js @@ -0,0 +1,274 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 723069: test the debugger breakpoint API and connection to the source + * editor. + */ + +const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html"; + +let gPane = null; +let gTab = null; +let gDebuggee = null; +let gDebugger = null; +let gScripts = null; +let gEditor = null; +let gBreakpoints = null; + +function test() +{ + let tempScope = {}; + Cu.import("resource:///modules/source-editor.jsm", tempScope); + let SourceEditor = tempScope.SourceEditor; + + debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { + gTab = aTab; + gDebuggee = aDebuggee; + gPane = aPane; + gDebugger = gPane.debuggerWindow; + + gPane.activeThread.addOneTimeListener("scriptsadded", function() { + Services.tm.currentThread.dispatch({ run: onScriptsAdded }, 0); + }); + gDebuggee.firstCall(); + }); + + function onScriptsAdded() + { + gScripts = gDebugger.DebuggerView.Scripts; + + is(gDebugger.StackFrames.activeThread.state, "paused", + "Should only be getting stack frames while paused."); + + is(gScripts._scripts.itemCount, 2, "Found the expected number of scripts."); + + gEditor = gDebugger.editor; + + isnot(gEditor.getText().indexOf("debugger"), -1, + "The correct script was loaded initially."); + isnot(gScripts.selected, gScripts.scriptLocations()[0], + "the correct sccript is selected"); + + gBreakpoints = gPane.breakpoints; + is(Object.keys(gBreakpoints), 0, "no breakpoints"); + ok(!gPane.getBreakpoint("foo", 3), "getBreakpoint('foo', 3) returns falsey"); + + is(gEditor.getBreakpoints().length, 0, "no breakpoints in the editor"); + + + info("add the first breakpoint"); + gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, + onEditorBreakpointAddFirst); + let location = {url: gScripts.selected, line: 6}; + executeSoon(function() { + gPane.addBreakpoint(location, onBreakpointAddFirst); + }); + } + + let breakpointsAdded = 0; + let breakpointsRemoved = 0; + let editorBreakpointChanges = 0; + + function onEditorBreakpointAddFirst(aEvent) + { + gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, + onEditorBreakpointAddFirst); + editorBreakpointChanges++; + + ok(aEvent, "breakpoint1 added to the editor"); + is(aEvent.added.length, 1, "one breakpoint added to the editor"); + is(aEvent.removed.length, 0, "no breakpoint was removed from the editor"); + is(aEvent.added[0].line, 5, "editor breakpoint line is correct"); + + is(gEditor.getBreakpoints().length, 1, + "editor.getBreakpoints().length is correct"); + } + + function onBreakpointAddFirst(aBreakpointClient, aResponseError) + { + breakpointsAdded++; + + ok(aBreakpointClient, "breakpoint1 added, client received"); + ok(!aResponseError, "breakpoint1 added without errors"); + is(aBreakpointClient.location.url, gScripts.selected, + "breakpoint1 client url is correct"); + is(aBreakpointClient.location.line, 6, + "breakpoint1 client line is correct"); + + executeSoon(function() { + ok(aBreakpointClient.actor in gBreakpoints, + "breakpoint1 client found in the list of debugger breakpoints"); + is(Object.keys(gBreakpoints).length, 1, + "the list of debugger breakpoints holds only one breakpoint"); + is(gPane.getBreakpoint(gScripts.selected, 6), aBreakpointClient, + "getBreakpoint(selectedScript, 2) returns the correct breakpoint"); + + info("remove the first breakpoint"); + gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, + onEditorBreakpointRemoveFirst); + gPane.removeBreakpoint(aBreakpointClient, onBreakpointRemoveFirst); + }); + } + + function onBreakpointRemoveFirst(aLocation) + { + breakpointsRemoved++; + + ok(aLocation, "breakpoint1 removed"); + is(aLocation.url, gScripts.selected, "breakpoint1 remove: url is correct"); + is(aLocation.line, 6, "breakpoint1 remove: line is correct"); + + executeSoon(testBreakpointAddBackground); + } + + function onEditorBreakpointRemoveFirst(aEvent) + { + gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, + onEditorBreakpointRemoveFirst); + editorBreakpointChanges++; + + ok(aEvent, "breakpoint1 removed from the editor"); + is(aEvent.added.length, 0, "no breakpoint was added to the editor"); + is(aEvent.removed.length, 1, "one breakpoint was removed from the editor"); + is(aEvent.removed[0].line, 5, "editor breakpoint line is correct"); + + is(gEditor.getBreakpoints().length, 0, "editor.getBreakpoints().length is correct"); + } + + function testBreakpointAddBackground() + { + info("add a breakpoint to the second script which is not selected"); + + is(Object.keys(gBreakpoints).length, 0, "no breakpoints in the debugger"); + ok(!gPane.getBreakpoint(gScripts.selected, 6), + "getBreakpoint(selectedScript, 6) returns no breakpoint"); + + let script0 = gScripts.scriptLocations()[0]; + isnot(script0, gScripts.selected, + "first script location is not the currently selected script"); + + let location = {url: script0, line: 5}; + gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, + onEditorBreakpointAddBackgroundTrap); + gPane.addBreakpoint(location, onBreakpointAddBackground); + } + + function onEditorBreakpointAddBackgroundTrap(aEvent) + { + // trap listener: no breakpoint must be added to the editor when a breakpoint + // is added to a script that is not currently selected. + gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, + onEditorBreakpointAddBackgroundTrap); + editorBreakpointChanges++; + ok(false, "breakpoint2 must not be added to the editor"); + } + + function onBreakpointAddBackground(aBreakpointClient, aResponseError) + { + breakpointsAdded++; + + ok(aBreakpointClient, "breakpoint2 added, client received"); + ok(!aResponseError, "breakpoint2 added without errors"); + is(aBreakpointClient.location.url, gScripts.scriptLocations()[0], + "breakpoint2 client url is correct"); + is(aBreakpointClient.location.line, 5, + "breakpoint2 client line is correct"); + + executeSoon(function() { + ok(aBreakpointClient.actor in gBreakpoints, + "breakpoint2 client found in the list of debugger breakpoints"); + is(Object.keys(gBreakpoints).length, 1, "one breakpoint in the debugger"); + is(gPane.getBreakpoint(gScripts.scriptLocations()[0], 5), aBreakpointClient, + "getBreakpoint(scriptLocations[0], 5) returns the correct breakpoint"); + + // remove the trap listener + gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, + onEditorBreakpointAddBackgroundTrap); + + gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, + onEditorBreakpointAddSwitch); + gEditor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, + onEditorTextChanged); + + info("switch to the second script"); + + gScripts._scripts.selectedIndex = 0; + gDebugger.SourceScripts.onChange({ target: gScripts._scripts }); + }); + } + + function onEditorBreakpointAddSwitch(aEvent) + { + gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, + onEditorBreakpointAddSwitch); + editorBreakpointChanges++; + + ok(aEvent, "breakpoint2 added to the editor"); + is(aEvent.added.length, 1, "one breakpoint added to the editor"); + is(aEvent.removed.length, 0, "no breakpoint was removed from the editor"); + is(aEvent.added[0].line, 4, "editor breakpoint line is correct"); + + is(gEditor.getBreakpoints().length, 1, + "editor.getBreakpoints().length is correct"); + } + + function onEditorTextChanged() + { + gEditor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED, + onEditorTextChanged); + + is(gEditor.getText().indexOf("debugger"), -1, + "The second script is no longer displayed."); + + isnot(gEditor.getText().indexOf("firstCall"), -1, + "The first script is displayed."); + + executeSoon(function() { + info("remove the second breakpoint using the mouse"); + + gEditor.addEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, + onEditorBreakpointRemoveSecond); + + let testWin = gEditor.editorElement.ownerDocument.defaultView; + EventUtils.synthesizeMouse(gEditor.editorElement, 10, 70, {}, testWin); + }); + + } + + function onEditorBreakpointRemoveSecond(aEvent) + { + gEditor.removeEventListener(SourceEditor.EVENTS.BREAKPOINT_CHANGE, + onEditorBreakpointRemoveSecond); + editorBreakpointChanges++; + + ok(aEvent, "breakpoint2 removed from the editor"); + is(aEvent.added.length, 0, "no breakpoint was added to the editor"); + is(aEvent.removed.length, 1, "one breakpoint was removed from the editor"); + is(aEvent.removed[0].line, 4, "editor breakpoint line is correct"); + + is(gEditor.getBreakpoints().length, 0, "editor.getBreakpoints().length is correct"); + + executeSoon(function() { + gDebugger.StackFrames.activeThread.resume(finish); + }); + } + + registerCleanupFunction(function() { + is(Object.keys(gBreakpoints).length, 0, "no breakpoint in the debugger"); + ok(!gPane.getBreakpoint(gScripts.scriptLocations()[0], 5), + "getBreakpoint(scriptLocations[0], 5) returns no breakpoint"); + + removeTab(gTab); + is(breakpointsAdded, 2, "correct number of breakpoints have been added"); + is(breakpointsRemoved, 1, "correct number of breakpoints have been removed"); + is(editorBreakpointChanges, 4, "correct number of editor breakpoint changes"); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; + gScripts = null; + gEditor = null; + gBreakpoints = null; + }); +}
--- a/browser/devtools/debugger/test/browser_dbg_clean-exit.js +++ b/browser/devtools/debugger/test/browser_dbg_clean-exit.js @@ -2,39 +2,41 @@ * Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ // Test that closing a tab with the debugger in a paused state exits cleanly. var gPane = null; var gTab = null; -var gDebuggee = null; var gDebugger = null; const DEBUGGER_TAB_URL = EXAMPLE_URL + "browser_dbg_debuggerstatement.html"; function test() { debug_tab_pane(DEBUGGER_TAB_URL, function(aTab, aDebuggee, aPane) { gTab = aTab; - gDebuggee = aDebuggee; gPane = aPane; gDebugger = gPane.debuggerWindow; testCleanExit(); }); } function testCleanExit() { gPane.activeThread.addOneTimeListener("framesadded", function() { Services.tm.currentThread.dispatch({ run: function() { is(gDebugger.StackFrames.activeThread.paused, true, "Should be paused after the debugger statement."); - gPane._client.addOneTimeListener("tabDetached", function () { - finish(); - }); - removeTab(gTab); + closeDebuggerAndFinish(gTab); }}, 0); }); gTab.linkedBrowser.contentWindow.wrappedJSObject.runDebuggerStatement(); } + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_location-changes.js +++ b/browser/devtools/debugger/test/browser_dbg_location-changes.js @@ -50,15 +50,22 @@ function testSimpleCall() { function testLocationChange() { gDebugger.StackFrames.activeThread.resume(function() { gPane._client.addOneTimeListener("tabNavigated", function(aEvent, aPacket) { ok(true, "tabNavigated event was fired."); gPane._client.addOneTimeListener("tabAttached", function(aEvent, aPacket) { ok(true, "Successfully reattached to the tab again."); - removeTab(gTab); - finish(); + closeDebuggerAndFinish(gTab); }); }); content.location = TAB1_URL; }); } + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_pause-resume.js +++ b/browser/devtools/debugger/test/browser_dbg_pause-resume.js @@ -1,23 +1,21 @@ /* vim:set ts=2 sw=2 sts=2 et: */ /* * Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ var gPane = null; var gTab = null; -var gDebuggee = null; var gDebugger = null; function test() { debug_tab_pane(STACK_URL, function(aTab, aDebuggee, aPane) { gTab = aTab; - gDebuggee = aDebuggee; gPane = aPane; gDebugger = gPane.debuggerWindow; testPause(); }); } function testPause() { @@ -58,17 +56,22 @@ function testResume() { is(gDebugger.StackFrames.activeThread.paused, false, "Should be paused after an interrupt request."); let button = gDebugger.document.getElementById("resume"); is(button.label, gDebugger.DebuggerView.getStr("pauseLabel"), "Button label should be pause when running."); - removeTab(gTab); - finish(); + closeDebuggerAndFinish(gTab); }}, 0); }); EventUtils.sendMouseEvent({ type: "click" }, gDebugger.document.getElementById("resume"), gDebugger); } + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-01.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-01.js @@ -129,14 +129,20 @@ function resumeAndFinish() { ok(gDebugger.DebuggerView.Scripts.containsLabel("x/script.js"), "Script (4) label is incorrect."); ok(gDebugger.DebuggerView.Scripts.containsLabel("x/y/script.js"), "Script (5) label is incorrect."); is(vs._scripts.itemCount, 6, "Got too many script items in the list!"); - - removeTab(gTab); - finish(); + closeDebuggerAndFinish(gTab); }); }); } + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-02.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-02.js @@ -111,21 +111,24 @@ function testSimpleCall() { EventUtils.sendMouseEvent({ type: "click" }, testScope.querySelector(".title"), gDebugger); ok(!testScope.expanded, "Clicking again the testScope tilte should collapse it."); - resumeAndFinish(); + gDebugger.StackFrames.activeThread.resume(function() { + closeDebuggerAndFinish(gTab); + }); }}, 0); }); gDebuggee.simpleCall(); } -function resumeAndFinish() { - gDebugger.StackFrames.activeThread.resume(function() { - removeTab(gTab); - finish(); - }); -} +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-03.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-03.js @@ -113,21 +113,24 @@ function testSimpleCall() { testScope.remove(); is(removeCallbackSender, testScope, "The removeCallback wasn't called as it should."); is(gDebugger.DebuggerView.Properties._vars.childNodes.length, 4, "The scope should have been removed from the parent container tree."); - resumeAndFinish(); + gDebugger.StackFrames.activeThread.resume(function() { + closeDebuggerAndFinish(gTab); + }); }}, 0); }); gDebuggee.simpleCall(); } -function resumeAndFinish() { - gDebugger.StackFrames.activeThread.resume(function() { - removeTab(gTab); - finish(); - }); -} +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-04.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-04.js @@ -68,21 +68,24 @@ function testSimpleCall() { is(testVar.querySelector(".details").childNodes.length, 0, "The var should remove all it's details container tree children."); testVar.remove(); is(testScope.querySelector(".details").childNodes.length, 0, "The var should have been removed from the parent container tree."); - resumeAndFinish(); + gDebugger.StackFrames.activeThread.resume(function() { + closeDebuggerAndFinish(gTab); + }); }}, 0); }); gDebuggee.simpleCall(); } -function resumeAndFinish() { - gDebugger.StackFrames.activeThread.resume(function() { - removeTab(gTab); - finish(); - }); -} +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-05.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-05.js @@ -76,21 +76,24 @@ function testSimpleCall() { is(testVar.querySelector(".details").childNodes.length, 0, "The var should remove all it's details container tree children."); testVar.remove(); is(testScope.querySelector(".details").childNodes.length, 0, "The var should have been removed from the parent container tree."); - resumeAndFinish(); + gDebugger.StackFrames.activeThread.resume(function() { + closeDebuggerAndFinish(gTab); + }); }}, 0); }); gDebuggee.simpleCall(); } -function resumeAndFinish() { - gDebugger.StackFrames.activeThread.resume(function() { - removeTab(gTab); - finish(); - }); -} +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-06.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-06.js @@ -112,22 +112,24 @@ function testSimpleCall() { "The grip information for the localVar3 wasn't set correctly."); is(localVar4.querySelector(".info").textContent, "null", "The grip information for the localVar4 wasn't set correctly."); is(localVar5.querySelector(".info").textContent, "[object Object]", "The grip information for the localVar5 wasn't set correctly."); - - resumeAndFinish(); + gDebugger.StackFrames.activeThread.resume(function() { + closeDebuggerAndFinish(gTab); + }); }}, 0); }); gDebuggee.simpleCall(); } -function resumeAndFinish() { - gDebugger.StackFrames.activeThread.resume(function() { - removeTab(gTab); - finish(); - }); -} +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-07.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-07.js @@ -5,24 +5,22 @@ /** * Make sure that the property view displays function parameters. */ const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html"; var gPane = null; var gTab = null; -var gDebuggee = null; var gDebugger = null; function test() { debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { gTab = aTab; - gDebuggee = aDebuggee; gPane = aPane; gDebugger = gPane.debuggerWindow; testFrameParameters(); }); } function testFrameParameters() @@ -82,15 +80,21 @@ function testFrameParameters() function resumeAndFinish() { gPane.activeThread.addOneTimeListener("framescleared", function() { Services.tm.currentThread.dispatch({ run: function() { var frames = gDebugger.DebuggerView.Stackframes._frames; is(frames.querySelectorAll(".dbg-stackframe").length, 0, "Should have no frames."); - removeTab(gTab); - finish(); + closeDebuggerAndFinish(gTab); }}, 0); }); gDebugger.StackFrames.activeThread.resume(); } + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-08.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-08.js @@ -5,24 +5,22 @@ /** * Make sure that the property view displays the properties of objects. */ const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html"; var gPane = null; var gTab = null; -var gDebuggee = null; var gDebugger = null; function test() { debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { gTab = aTab; - gDebuggee = aDebuggee; gPane = aPane; gDebugger = gPane.debuggerWindow; testFrameParameters(); }); } function testFrameParameters() @@ -98,15 +96,21 @@ function testFrameParameters() function resumeAndFinish() { gPane.activeThread.addOneTimeListener("framescleared", function() { Services.tm.currentThread.dispatch({ run: function() { var frames = gDebugger.DebuggerView.Stackframes._frames; is(frames.querySelectorAll(".dbg-stackframe").length, 0, "Should have no frames."); - removeTab(gTab); - finish(); + closeDebuggerAndFinish(gTab); }}, 0); }); gDebugger.StackFrames.activeThread.resume(); } + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_script-switching.js +++ b/browser/devtools/debugger/test/browser_dbg_script-switching.js @@ -98,11 +98,18 @@ function testSwitchPaused() function testSwitchRunning() { ok(gDebugger.editor.getText().search(/debugger/) != -1, "The second script is displayed again."); ok(gDebugger.editor.getText().search(/firstCall/) == -1, "The first script is no longer displayed."); + closeDebuggerAndFinish(gTab); +} + +registerCleanupFunction(function() { removeTab(gTab); - finish(); -} + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_select-line.js +++ b/browser/devtools/debugger/test/browser_dbg_select-line.js @@ -63,23 +63,39 @@ function testSelectLine() { // Yield control back to the event loop so that the debugger has a // chance to highlight the proper line. executeSoon(function(){ // getCaretPosition is 0-based. is(gDebugger.editor.getCaretPosition().line, 4, "The correct line is selected."); gDebugger.StackFrames.activeThread.resume(function() { - removeTab(gTab); - finish(); + closeDebuggerAndFinish(gTab); }); }); }); + // Scroll all the way down to ensure stackframe-3 is visible. + let stackframes = gDebugger.document.getElementById("stackframes"); + stackframes.scrollTop = stackframes.scrollHeight; + // Click the oldest stack frame. + let frames = gDebugger.DebuggerView.Stackframes._frames; + is(frames.querySelectorAll(".dbg-stackframe").length, 4, + "Should have four frames."); + let element = gDebugger.document.getElementById("stackframe-3"); + isnot(element, null, "Found the third stack frame."); EventUtils.synthesizeMouseAtCenter(element, {}, gDebugger); }); }}, 0); }); gDebuggee.firstCall(); } + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_stack-01.js +++ b/browser/devtools/debugger/test/browser_dbg_stack-01.js @@ -31,21 +31,24 @@ function testSimpleCall() { "Should only be getting stack frames while paused."); is(frames.querySelectorAll(".dbg-stackframe").length, 1, "Should have only one frame."); is(childNodes.length, frames.querySelectorAll(".dbg-stackframe").length, "All children should be frames."); - resumeAndFinish(); + gDebugger.StackFrames.activeThread.resume(function() { + closeDebuggerAndFinish(gTab); + }); }}, 0); }); gDebuggee.simpleCall(); } -function resumeAndFinish() { - gDebugger.StackFrames.activeThread.resume(function() { - removeTab(gTab); - finish(); - }); -} +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_stack-02.js +++ b/browser/devtools/debugger/test/browser_dbg_stack-02.js @@ -62,22 +62,24 @@ function testEvalCall() { gDebugger); ok(frames.querySelector("#stackframe-0").classList.contains("selected"), "First frame should be selected after click inside the first frame."); ok(!frames.querySelector("#stackframe-1").classList.contains("selected"), "Second frame should not be selected after click inside the first frame."); - resumeAndFinish(); + gDebugger.StackFrames.activeThread.resume(function() { + closeDebuggerAndFinish(gTab); + }); }}, 0); }); gDebuggee.evalCall(); } -function resumeAndFinish() { - gDebugger.StackFrames.activeThread.resume(function() { - removeTab(gTab); - finish(); - }); -} - +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_stack-03.js +++ b/browser/devtools/debugger/test/browser_dbg_stack-03.js @@ -42,27 +42,30 @@ function testRecurse() { is(frames.querySelectorAll(".dbg-stackframe").length, pageSize * 2, "Should now have twice the max limit of frames."); gPane.activeThread.addOneTimeListener("framesadded", function() { is(frames.querySelectorAll(".dbg-stackframe").length, recurseLimit, "Should have reached the recurse limit."); - resumeAndFinish(); + gDebugger.StackFrames.activeThread.resume(function() { + closeDebuggerAndFinish(gTab); + }); }); frames.scrollTop = frames.scrollHeight; }); frames.scrollTop = frames.scrollHeight; }}, 0); }); gDebuggee.recurse(); } -function resumeAndFinish() { - gDebugger.StackFrames.activeThread.resume(function() { - removeTab(gTab); - finish(); - }); -} +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_stack-04.js +++ b/browser/devtools/debugger/test/browser_dbg_stack-04.js @@ -43,18 +43,25 @@ function testEvalCallResume() { "Should have no frames after resume"); is(childNodes.length, 1, "Should only have one child."); is(frames.querySelectorAll(".empty").length, 1, "Should have the empty list explanation."); - removeTab(gTab); - finish(); + closeDebuggerAndFinish(gTab); }); gPane.activeThread.resume(); }}, 0); }); gDebuggee.evalCall(); } + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +});
--- a/browser/devtools/debugger/test/browser_dbg_update-editor-mode.js +++ b/browser/devtools/debugger/test/browser_dbg_update-editor-mode.js @@ -67,12 +67,20 @@ function testSwitchPaused() ok(gDebugger.editor.getText().search(/firstCall/) != -1, "The first script is displayed."); is(gDebugger.editor.getMode(), SourceEditor.MODES.JAVASCRIPT, "Found the expected editor mode."); gDebugger.StackFrames.activeThread.resume(function() { - removeTab(gTab); - finish(); + closeDebuggerAndFinish(gTab); }); } + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; + gScripts = null; +});
--- a/browser/devtools/debugger/test/head.js +++ b/browser/devtools/debugger/test/head.js @@ -44,16 +44,24 @@ function addTab(aURL, aOnload) return tab; } function removeTab(aTab) { gBrowser.removeTab(aTab); } +function closeDebuggerAndFinish(aTab) { + DebuggerUI.aWindow.addEventListener("Debugger:Shutdown", function cleanup() { + DebuggerUI.aWindow.removeEventListener("Debugger:Shutdown", cleanup, false); + finish(); + }, false); + DebuggerUI.getDebugger(aTab).close(); +} + function get_tab_actor_for_url(aClient, aURL, aCallback) { aClient.listTabs(function(aResponse) { for each (let tab in aResponse.tabs) { if (tab.url == aURL) { aCallback(tab); return; } }
--- a/browser/devtools/sourceeditor/source-editor-orion.jsm +++ b/browser/devtools/sourceeditor/source-editor-orion.jsm @@ -18,16 +18,17 @@ * The Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Mihai Sucan <mihai.sucan@gmail.com> (original author) * Kenny Heaton <kennyheaton@gmail.com> * Spyros Livathinos <livathinos.spyros@gmail.com> + * Allen Eubank <adeubank@gmail.com> * * 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 @@ -119,16 +120,28 @@ const DEFAULT_KEYBINDINGS = [ accel: true, shift: true, }, { action: "Unindent Lines", code: Ci.nsIDOMKeyEvent.DOM_VK_TAB, shift: true, }, + { + action: "Move Lines Up", + code: Ci.nsIDOMKeyEvent.DOM_VK_UP, + ctrl: Services.appinfo.OS == "Darwin", + alt: true, + }, + { + action: "Move Lines Down", + code: Ci.nsIDOMKeyEvent.DOM_VK_DOWN, + ctrl: Services.appinfo.OS == "Darwin", + alt: true, + }, ]; var EXPORTED_SYMBOLS = ["SourceEditor"]; /** * The SourceEditor object constructor. The SourceEditor component allows you to * provide users with an editor tailored to the specific needs of editing source * code, aimed primarily at web developers. @@ -362,26 +375,35 @@ SourceEditor.prototype = { "redo": [this.redo, this], "tab": [this._doTab, this], "Unindent Lines": [this._doUnindentLines, this], "enter": [this._doEnter, this], "Find...": [this.ui.find, this.ui], "Find Next Occurrence": [this.ui.findNext, this.ui], "Find Previous Occurrence": [this.ui.findPrevious, this.ui], "Goto Line...": [this.ui.gotoLine, this.ui], + "Move Lines Down": [this._moveLines, this], }; for (let name in actions) { let action = actions[name]; this._view.setAction(name, action[0].bind(action[1])); } + this._view.setAction("Move Lines Up", this._moveLines.bind(this, true)); + let keys = (config.keys || []).concat(DEFAULT_KEYBINDINGS); keys.forEach(function(aKey) { - let binding = new KeyBinding(aKey.code, aKey.accel, aKey.shift, aKey.alt); + // In Orion mod1 refers to Cmd on Macs and Ctrl on Windows and Linux. + // So, if ctrl is in aKey we use it on Windows and Linux, otherwise + // we use aKey.accel for mod1. + let mod1 = Services.appinfo.OS != "Darwin" && + "ctrl" in aKey ? aKey.ctrl : aKey.accel; + let binding = new KeyBinding(aKey.code, mod1, aKey.shift, aKey.alt, + aKey.ctrl); this._view.setKeyBinding(binding, aKey.action); if (aKey.callback) { this._view.setAction(aKey.action, aKey.callback); } }, this); this._initEventTarget(); @@ -574,16 +596,88 @@ SourceEditor.prototype = { } this.setText(this.getLineDelimiter() + prefix, selection.start, selection.end); return true; }, /** + * Move lines upwards or downwards, relative to the current caret location. + * + * @private + * @param boolean aLineAbove + * True if moving lines up, false to move lines down. + */ + _moveLines: function SE__moveLines(aLineAbove) + { + if (this.readOnly) { + return false; + } + + let model = this._model; + let selection = this.getSelection(); + let firstLine = model.getLineAtOffset(selection.start); + if (firstLine == 0 && aLineAbove) { + return true; + } + + let lastLine = model.getLineAtOffset(selection.end); + let firstLineStart = model.getLineStart(firstLine); + let lastLineStart = model.getLineStart(lastLine); + if (selection.start != selection.end && lastLineStart == selection.end) { + lastLine--; + } + if (!aLineAbove && (lastLine + 1) == this.getLineCount()) { + return true; + } + + let lastLineEnd = model.getLineEnd(lastLine, true); + let text = this.getText(firstLineStart, lastLineEnd); + + if (aLineAbove) { + let aboveLine = firstLine - 1; + let aboveLineStart = model.getLineStart(aboveLine); + + this.startCompoundChange(); + if (lastLine == (this.getLineCount() - 1)) { + let delimiterStart = model.getLineEnd(aboveLine); + let delimiterEnd = model.getLineEnd(aboveLine, true); + let lineDelimiter = this.getText(delimiterStart, delimiterEnd); + text += lineDelimiter; + this.setText("", firstLineStart - lineDelimiter.length, lastLineEnd); + } else { + this.setText("", firstLineStart, lastLineEnd); + } + this.setText(text, aboveLineStart, aboveLineStart); + this.endCompoundChange(); + this.setSelection(aboveLineStart, aboveLineStart + text.length); + } else { + let belowLine = lastLine + 1; + let belowLineEnd = model.getLineEnd(belowLine, true); + + let insertAt = belowLineEnd - lastLineEnd + firstLineStart; + let lineDelimiter = ""; + if (belowLine == this.getLineCount() - 1) { + let delimiterStart = model.getLineEnd(lastLine); + lineDelimiter = this.getText(delimiterStart, lastLineEnd); + text = lineDelimiter + text.substr(0, text.length - + lineDelimiter.length); + } + this.startCompoundChange(); + this.setText("", firstLineStart, lastLineEnd); + this.setText(text, insertAt, insertAt); + this.endCompoundChange(); + this.setSelection(insertAt + lineDelimiter.length, + insertAt + text.length); + } + return true; + }, + + /** * The Orion Selection event handler. The current caret line is * highlighted and for Linux users the selected text is copied into the X11 * PRIMARY buffer. * * @private * @param object aEvent * The Orion Selection event object. */
--- a/browser/devtools/sourceeditor/source-editor.jsm +++ b/browser/devtools/sourceeditor/source-editor.jsm @@ -187,16 +187,17 @@ SourceEditor.DEFAULTS = { highlightCurrentLine: true, /** * An array of objects that allows you to define custom editor keyboard * bindings. Each object can have: * - action - name of the editor action to invoke. * - code - keyCode for the shortcut. * - accel - boolean for the Accel key (Cmd on Macs, Ctrl on Linux/Windows). + * - ctrl - boolean for the Control key * - shift - boolean for the Shift key. * - alt - boolean for the Alt key. * - callback - optional function to invoke, if the action is not predefined * in the editor. * @type array */ keys: null,
--- a/browser/devtools/sourceeditor/test/Makefile.in +++ b/browser/devtools/sourceeditor/test/Makefile.in @@ -53,13 +53,14 @@ include $(topsrcdir)/config/rules.mk browser_bug684546_reset_undo.js \ browser_bug695035_middle_click_paste.js \ browser_bug687160_line_api.js \ browser_bug650345_find.js \ browser_bug703692_focus_blur.js \ browser_bug725388_mouse_events.js \ browser_bug707987_debugger_breakpoints.js \ browser_bug712982_line_ruler_click.js \ + browser_bug725618_moveLines_shortcut.js \ browser_bug700893_dirty_state.js \ head.js \ libs:: $(_BROWSER_TEST_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644 --- /dev/null +++ b/browser/devtools/sourceeditor/test/browser_bug725618_moveLines_shortcut.js @@ -0,0 +1,117 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +let tempScope = {}; +Cu.import("resource:///modules/source-editor.jsm", tempScope); +let SourceEditor = tempScope.SourceEditor; + +let editor; +let testWin; + +function test() +{ + waitForExplicitFinish(); + + const windowUrl = "data:application/vnd.mozilla.xul+xml,<?xml version='1.0'?>" + + "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" + + " title='test for bug 725618 - moveLines shortcut' width='300' height='500'>" + + "<box flex='1'/></window>"; + const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no"; + + testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null); + testWin.addEventListener("load", function onWindowLoad() { + testWin.removeEventListener("load", onWindowLoad, false); + waitForFocus(initEditor, testWin); + }, false); +} + +function initEditor() +{ + let box = testWin.document.querySelector("box"); + + let text = "target\nfoo\nbar" + let config = { + initialText: text, + }; + + editor = new SourceEditor(); + editor.init(box, config, editorLoaded); +} + +function editorLoaded() +{ + editor.focus(); + + editor.setCaretOffset(0); + + let modifiers = {altKey: true, ctrlKey: Services.appinfo.OS == "Darwin"}; + + EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin); + is(editor.getText(), "foo\ntarget\nbar", "Move lines down works"); + is(editor.getSelectedText(), "target\n", "selection is correct"); + + EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin); + is(editor.getText(), "foo\nbar\ntarget", "Move lines down works"); + is(editor.getSelectedText(), "target", "selection is correct"); + + EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin); + is(editor.getText(), "foo\nbar\ntarget", "Check for bottom of editor works"); + is(editor.getSelectedText(), "target", "selection is correct"); + + EventUtils.synthesizeKey("VK_UP", modifiers, testWin); + is(editor.getText(), "foo\ntarget\nbar", "Move lines up works"); + is(editor.getSelectedText(), "target\n", "selection is correct"); + + EventUtils.synthesizeKey("VK_UP", modifiers, testWin); + is(editor.getText(), "target\nfoo\nbar", "Move lines up works"); + is(editor.getSelectedText(), "target\n", "selection is correct"); + + EventUtils.synthesizeKey("VK_UP", modifiers, testWin); + is(editor.getText(), "target\nfoo\nbar", "Check for top of editor works"); + is(editor.getSelectedText(), "target\n", "selection is correct"); + + editor.setSelection(0, 10); + info("text within selection =" + editor.getSelectedText()); + + EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin); + is(editor.getText(), "bar\ntarget\nfoo", "Multiple line move down works"); + is(editor.getSelectedText(), "target\nfoo", "selection is correct"); + + EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin); + is(editor.getText(), "bar\ntarget\nfoo", + "Check for bottom of editor works with multiple line selection"); + is(editor.getSelectedText(), "target\nfoo", "selection is correct"); + + EventUtils.synthesizeKey("VK_UP", modifiers, testWin); + is(editor.getText(), "target\nfoo\nbar", "Multiple line move up works"); + is(editor.getSelectedText(), "target\nfoo\n", "selection is correct"); + + EventUtils.synthesizeKey("VK_UP", modifiers, testWin); + is(editor.getText(), "target\nfoo\nbar", + "Check for top of editor works with multiple line selection"); + is(editor.getSelectedText(), "target\nfoo\n", "selection is correct"); + + editor.readOnly = true; + + editor.setCaretOffset(0); + + EventUtils.synthesizeKey("VK_UP", modifiers, testWin); + is(editor.getText(), "target\nfoo\nbar", + "Check for readOnly mode works with move lines up"); + + EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin); + is(editor.getText(), "target\nfoo\nbar", + "Check for readOnly mode works with move lines down"); + + finish(); +} + +registerCleanupFunction(function() +{ + editor.destroy(); + testWin.close(); + testWin = editor = null; +});
--- a/browser/devtools/styleinspector/CssLogic.jsm +++ b/browser/devtools/styleinspector/CssLogic.jsm @@ -1193,36 +1193,51 @@ CssSheet.prototype = { * argument must point to the element. * @constructor */ function CssRule(aCssSheet, aDomRule, aElement) { this._cssSheet = aCssSheet; this._domRule = aDomRule; + let parentRule = aDomRule.parentRule; + if (parentRule && parentRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE) { + this.mediaText = parentRule.media.mediaText; + } + if (this._cssSheet) { // parse _domRule.selectorText on call to this.selectors this._selectors = null; this.line = this._cssSheet._cssLogic.domUtils.getRuleLine(this._domRule); this.source = this._cssSheet.shortSource + ":" + this.line; + if (this.mediaText) { + this.source += " @media " + this.mediaText; + } this.href = this._cssSheet.href; this.contentRule = this._cssSheet.contentSheet; } else if (aElement) { this._selectors = [ new CssSelector(this, "@element.style") ]; this.line = -1; this.source = CssLogic.l10n("rule.sourceElement"); this.href = "#"; this.contentRule = true; this.sourceElement = aElement; } } CssRule.prototype = { _passId: null, + mediaText: "", + + get isMediaRule() + { + return !!this.mediaText; + }, + /** * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter. * * @return {boolean} true if the parent stylesheet is allowed by the current * sourceFilter, or false otherwise. */ get sheetAllowed() {
--- a/browser/devtools/styleinspector/CssRuleView.jsm +++ b/browser/devtools/styleinspector/CssRuleView.jsm @@ -351,20 +351,30 @@ ElementStyle.prototype = { */ function Rule(aElementStyle, aOptions) { this.elementStyle = aElementStyle; this.domRule = aOptions.domRule || null; this.style = aOptions.style || this.domRule.style; this.selectorText = aOptions.selectorText || this.domRule.selectorText; this.inherited = aOptions.inherited || null; + + if (this.domRule) { + let parentRule = this.domRule.parentRule; + if (parentRule && parentRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE) { + this.mediaText = parentRule.media.mediaText; + } + } + this._getTextProperties(); } Rule.prototype = { + mediaText: "", + get title() { if (this._title) { return this._title; } this._title = CssLogic.shortSource(this.sheet); if (this.domRule) { this._title += ":" + this.ruleLine; @@ -375,17 +385,17 @@ Rule.prototype = { if (this.inherited.id) { eltText += "#" + this.inherited.id; } let args = [eltText, this._title]; this._title = CssLogic._strings.formatStringFromName("rule.inheritedSource", args, args.length); } - return this._title; + return this._title + (this.mediaText ? " @media " + this.mediaText : ""); }, /** * The rule's stylesheet. */ get sheet() { return this.domRule ? this.domRule.parentStyleSheet : null; @@ -717,18 +727,36 @@ CssRuleView.prototype = { } this._elementStyle = new ElementStyle(aElement, this.store); this._elementStyle.onChanged = function() { this._changed(); }.bind(this); this._createEditors(); + + // When creating a new property, we fake the normal property + // editor behavior (focusing a property's value after entering its + // name) by responding to the name's blur event, creating the + // value editor, and grabbing focus to the value editor. But if + // focus has already moved to another document, we won't be able + // to move focus to the new editor. + // Create a focusable item at the end of the editors to catch these + // cases. + this._focusBackstop = createChild(this.element, "div", { + tabindex: 0, + }); + this._backstopHandler = function() { + // If this item is actually focused long enough to get the focus + // event, allow focus to move on out of this document. + moveFocus(this.doc.defaultView, FOCUS_FORWARD); + }.bind(this); + this._focusBackstop.addEventListener("focus", this._backstopHandler, false); }, - + /** * Update the rules for the currently highlighted element. */ nodeChanged: function CssRuleView_nodeChanged() { this._clearRules(); this._elementStyle.populate(); this._createEditors(); @@ -747,16 +775,22 @@ CssRuleView.prototype = { /** * Clear the rule view. */ clear: function CssRuleView_clear() { this._clearRules(); this._viewedElement = null; this._elementStyle = null; + + if (this._focusBackstop) { + this._focusBackstop.removeEventListener("focus", this._backstopHandler, false); + this._backstopHandler = null; + this._focusBackstop = null; + } }, /** * Called when the user has made changes to the ElementStyle. * Emits an event that clients can listen to. */ _changed: function CssRuleView_changed() { @@ -830,17 +864,16 @@ RuleEditor.prototype = { let selectors = createChild(header, "span", { class: "ruleview-selector", textContent: this.rule.selectorText }); this.openBrace = createChild(header, "span", { class: "ruleview-ruleopen", - tabindex: "0", textContent: " {" }); this.openBrace.addEventListener("click", function() { this.newProperty(); }.bind(this), true); this.propertyList = createChild(code, "ul", {
--- a/browser/devtools/styleinspector/test/Makefile.in +++ b/browser/devtools/styleinspector/test/Makefile.in @@ -55,29 +55,33 @@ include $(topsrcdir)/config/rules.mk browser_bug_692400_element_style.js \ browser_csslogic_inherited.js \ browser_ruleview_editor.js \ browser_ruleview_editor_changedvalues.js \ browser_ruleview_inherit.js \ browser_ruleview_manipulation.js \ browser_ruleview_override.js \ browser_ruleview_ui.js \ + browser_ruleview_focus.js \ browser_bug705707_is_content_stylesheet.js \ + browser_bug722196_property_view_media_queries.js \ + browser_bug722196_rule_view_media_queries.js \ browser_bug_592743_specificity.js \ head.js \ $(NULL) _BROWSER_TEST_PAGES = \ browser_bug683672.html \ browser_bug705707_is_content_stylesheet.html \ browser_bug705707_is_content_stylesheet_imported.css \ browser_bug705707_is_content_stylesheet_imported2.css \ browser_bug705707_is_content_stylesheet_linked.css \ browser_bug705707_is_content_stylesheet_script.css \ browser_bug705707_is_content_stylesheet.xul \ browser_bug705707_is_content_stylesheet_xul.css \ + browser_bug722196_identify_media_queries.html \ $(NULL) libs:: $(_BROWSER_TEST_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir) libs:: $(_BROWSER_TEST_PAGES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644 --- /dev/null +++ b/browser/devtools/styleinspector/test/browser_bug722196_identify_media_queries.html @@ -0,0 +1,24 @@ +<html> +<head> + <title>test</title> + <script type="application/javascript;version=1.7"> + + </script> + <style> + div { + width: 1000px; + height: 100px; + background-color: #f00; + } + + @media screen and (min-width: 1px) { + div { + width: 200px; + } + } + </style> +</head> +<body> +<div></div> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/browser/devtools/styleinspector/test/browser_bug722196_property_view_media_queries.js @@ -0,0 +1,68 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that we correctly display appropriate media query titles in the +// property view. + +let doc; +let stylePanel; + +const TEST_URI = "http://example.com/browser/browser/devtools/styleinspector/" + + "test/browser_bug722196_identify_media_queries.html"; + +function test() +{ + waitForExplicitFinish(); + addTab(TEST_URI); + browser.addEventListener("load", docLoaded, true); +} + +function docLoaded() +{ + browser.removeEventListener("load", docLoaded, true); + doc = content.document; + stylePanel = new StyleInspector(window); + Services.obs.addObserver(checkSheets, "StyleInspector-opened", false); + stylePanel.createPanel(false, function() { + stylePanel.open(doc.body); + }); +} + +function checkSheets() +{ + Services.obs.removeObserver(checkSheets, "StyleInspector-opened", false); + + ok(stylePanel.isOpen(), "style inspector is open"); + + var div = doc.querySelector("div"); + ok(div, "captain, we have the div"); + + stylePanel.selectNode(div); + + let cssLogic = stylePanel.cssLogic; + cssLogic.processMatchedSelectors(); + + let _strings = Services.strings + .createBundle("chrome://browser/locale/devtools/styleinspector.properties"); + + let inline = _strings.GetStringFromName("rule.sourceInline"); + + let source1 = inline + ":8"; + let source2 = inline + ":15 @media screen and (min-width: 1px)"; + is(cssLogic._matchedRules[0][0].source, source1, + "rule.source gives correct output for rule 1"); + is(cssLogic._matchedRules[1][0].source, source2, + "rule.source gives correct output for rule 2"); + + Services.obs.addObserver(finishUp, "StyleInspector-closed", false); + stylePanel.close(); +} + +function finishUp() +{ + Services.obs.removeObserver(finishUp, "StyleInspector-closed", false); + doc = null; + gBrowser.removeCurrentTab(); + finish(); +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/styleinspector/test/browser_bug722196_rule_view_media_queries.js @@ -0,0 +1,55 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that we correctly display appropriate media query titles in the +// rule view. + +let tempScope = {}; +Cu.import("resource:///modules/devtools/CssRuleView.jsm", tempScope); +let _ElementStyle = tempScope._ElementStyle; +let doc; + +const TEST_URI = "http://example.com/browser/browser/devtools/styleinspector/" + + "test/browser_bug722196_identify_media_queries.html"; + +function test() +{ + waitForExplicitFinish(); + addTab(TEST_URI); + browser.addEventListener("load", docLoaded, true); +} + +function docLoaded() +{ + browser.removeEventListener("load", docLoaded, true); + doc = content.document; + checkSheets(); +} + +function checkSheets() +{ + var div = doc.querySelector("div"); + ok(div, "captain, we have the div"); + + let elementStyle = new _ElementStyle(div); + is(elementStyle.rules.length, 3, "Should have 3 rules."); + + let _strings = Services.strings + .createBundle("chrome://browser/locale/devtools/styleinspector.properties"); + + let inline = _strings.GetStringFromName("rule.sourceInline"); + + is(elementStyle.rules[0].title, inline, "check rule 0 title"); + is(elementStyle.rules[1].title, inline + + ":15 @media screen and (min-width: 1px)", "check rule 1 title"); + is(elementStyle.rules[2].title, inline + ":8", "check rule 2 title"); + finishUp(); +} + +function finishUp() +{ + doc = null; + gBrowser.removeCurrentTab(); + finish(); +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/styleinspector/test/browser_ruleview_focus.js @@ -0,0 +1,106 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that focus doesn't leave the style editor when adding a property +// (bug 719916) + +let doc; +let stylePanel; + +function waitForRuleView(aCallback) +{ + if (InspectorUI.ruleView) { + aCallback(); + return; + } + + let ruleViewFrame = InspectorUI.getToolIframe(InspectorUI.ruleViewObject); + ruleViewFrame.addEventListener("load", function(evt) { + ruleViewFrame.removeEventListener(evt.type, arguments.callee, true); + executeSoon(function() { + aCallback(); + }); + }, true); +} + +function waitForEditorFocus(aParent, aCallback) +{ + aParent.addEventListener("focus", function onFocus(evt) { + if (evt.target.inplaceEditor) { + aParent.removeEventListener("focus", onFocus, true); + let editor = evt.target.inplaceEditor; + executeSoon(function() { + aCallback(editor); + }); + } + }, true); +} + +function openRuleView() +{ + Services.obs.addObserver(function onOpened() { + Services.obs.removeObserver(onOpened, + InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false); + + // Highlight a node. + let node = content.document.getElementsByTagName("h1")[0]; + InspectorUI.inspectNode(node); + InspectorUI.stopInspecting(); + + // Open the rule view sidebar. + waitForRuleView(testFocus); + + InspectorUI.showSidebar(); + InspectorUI.ruleButton.click(); + + testFocus(); + }, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false); + InspectorUI.openInspectorUI(); +} + +function testFocus() +{ + let ruleViewFrame = InspectorUI.getToolIframe(InspectorUI.ruleViewObject); + let brace = ruleViewFrame.contentDocument.querySelectorAll(".ruleview-ruleclose")[0]; + waitForEditorFocus(brace.parentNode, function onNewElement(aEditor) { + aEditor.input.value = "color"; + waitForEditorFocus(brace.parentNode, function onEditingValue(aEditor) { + // If we actually get this focus we're ok. + ok(true, "We got focus."); + aEditor.input.value = "green"; + + // If we've retained focus, pressing return will start a new editor. + // If not, we'll wait here until we time out. + waitForEditorFocus(brace.parentNode, function onNewEditor(aEditor) { + aEditor.input.blur(); + finishTest(); + }); + EventUtils.sendKey("return"); + }); + EventUtils.sendKey("return"); + }); + + brace.focus(); +} + +function finishUp() +{ + doc = stylePanel = null; + gBrowser.removeCurrentTab(); + finish(); +} + +function test() +{ + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function(evt) { + gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true); + doc = content.document; + doc.title = "Rule View Test"; + waitForFocus(openRuleView, content); + }, true); + + content.location = "data:text/html,<h1>Some header text</h1>"; +}
--- a/browser/devtools/webconsole/GcliCommands.jsm +++ b/browser/devtools/webconsole/GcliCommands.jsm @@ -15,16 +15,17 @@ * * 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): * Joe Walker <jwalker@mozilla.com> (original author) + * Mihai Sucan <mihai.sucan@gmail.com> * * 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 @@ -155,18 +156,16 @@ gcli.addCommand({ ], exec: function(args, context) { let hud = HUDService.getHudReferenceById(context.environment.hudId); let StyleEditor = hud.gcliterm.document.defaultView.StyleEditor; StyleEditor.openChrome(args.resource.element, args.line); } }); -let breakpoints = []; - /** * 'break' command */ gcli.addCommand({ name: "break", description: gcli.lookup("breakDesc"), manual: gcli.lookup("breakManual") }); @@ -175,27 +174,35 @@ gcli.addCommand({ /** * 'break list' command */ gcli.addCommand({ name: "break list", description: gcli.lookup("breaklistDesc"), returnType: "html", exec: function(args, context) { - if (breakpoints.length === 0) { + let win = HUDService.currentContext(); + let dbg = win.DebuggerUI.getDebugger(win.gBrowser.selectedTab); + if (!dbg) { + return gcli.lookup("breakaddDebuggerStopped"); + } + let breakpoints = dbg.breakpoints; + + if (Object.keys(breakpoints).length === 0) { return gcli.lookup("breaklistNone"); } let reply = gcli.lookup("breaklistIntro"); reply += "<ol>"; - breakpoints.forEach(function(breakpoint) { + for each (let breakpoint in breakpoints) { let text = gcli.lookupFormat("breaklistLineEntry", - [breakpoint.file, breakpoint.line]); + [breakpoint.location.url, + breakpoint.location.line]); reply += "<li>" + text + "</li>"; - }); + }; reply += "</ol>"; return reply; } }); /** * 'break add' command @@ -243,24 +250,21 @@ gcli.addCommand({ args.type = "line"; let win = HUDService.currentContext(); let dbg = win.DebuggerUI.getDebugger(win.gBrowser.selectedTab); if (!dbg) { return gcli.lookup("breakaddDebuggerStopped"); } var promise = context.createPromise(); let position = { url: args.file, line: args.line }; - dbg.activeThread.setBreakpoint(position, function(aResponse, aBpClient) { - if (aResponse.error) { - promise.resolve(gcli.lookupFormat("breakaddFailed", - [ aResponse.error ])); + dbg.addBreakpoint(position, function(aBreakpoint, aError) { + if (aError) { + promise.resolve(gcli.lookupFormat("breakaddFailed", [aError])); return; } - args.client = aBpClient; - breakpoints.push(args); promise.resolve(gcli.lookup("breakaddAdded")); }); return promise; } }); /** @@ -270,28 +274,46 @@ gcli.addCommand({ name: "break del", description: gcli.lookup("breakdelDesc"), params: [ { name: "breakid", type: { name: "number", min: 0, - max: function() { return breakpoints.length - 1; } + max: function() { + let win = HUDService.currentContext(); + let dbg = win.DebuggerUI.getDebugger(win.gBrowser.selectedTab); + if (!dbg) { + return gcli.lookup("breakaddDebuggerStopped"); + } + return Object.keys(dbg.breakpoints).length - 1; + }, }, description: gcli.lookup("breakdelBreakidDesc") } ], returnType: "html", exec: function(args, context) { - let breakpoint = breakpoints.splice(args.breakid, 1)[0]; - var promise = context.createPromise(); + let win = HUDService.currentContext(); + let dbg = win.DebuggerUI.getDebugger(win.gBrowser.selectedTab); + if (!dbg) { + return gcli.lookup("breakaddDebuggerStopped"); + } + + let breakpoints = dbg.breakpoints; + let id = Object.keys(dbg.breakpoints)[args.breakid]; + if (!id || !(id in breakpoints)) { + return gcli.lookup("breakNotFound"); + } + + let promise = context.createPromise(); try { - breakpoint.client.remove(function(aResponse) { - promise.resolve(gcli.lookup("breakdelRemoved")); - }); + dbg.removeBreakpoint(breakpoints[id], function() { + promise.resolve(gcli.lookup("breakdelRemoved")); + }); } catch (ex) { // If the debugger has been closed already, don't scare the user. promise.resolve(gcli.lookup("breakdelRemoved")); } return promise; } });
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties +++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties @@ -250,16 +250,20 @@ breakdelDesc=Remove a breakpoint # LOCALIZATION NOTE (breakdelBreakidDesc) A very short string used to describe # the function of the index parameter in the 'break del' command. breakdelBreakidDesc=Index of breakpoint # LOCALIZATION NOTE (breakdelRemoved) Used in the output of the 'break del' # command to explain that a breakpoint was removed. breakdelRemoved=Breakpoint removed +# LOCALIZATION NOTE (breakNotFound) Used in the output of the 'break del' +# command to explain that the breakpoint was not found. +breakNotFound=Breakpoint was not found + # LOCALIZATION NOTE (consolecloseDesc) A very short description of the # 'console close' command. This string is designed to be shown in a menu # alongside the command name, which is why it should be as short as possible. consolecloseDesc=Close the console # LOCALIZATION NOTE (editDesc) A very short description of the 'edit' # command. See editManual for a fuller description of what it does. This # string is designed to be shown in a menu alongside the command name, which
--- a/browser/makefiles.sh +++ b/browser/makefiles.sh @@ -134,16 +134,17 @@ if [ "$ENABLE_TESTS" ]; then browser/components/certerror/test/Makefile browser/components/dirprovider/tests/Makefile browser/components/preferences/tests/Makefile browser/components/search/test/Makefile browser/components/sessionstore/test/Makefile browser/components/shell/test/Makefile browser/components/feeds/test/Makefile browser/components/feeds/test/chrome/Makefile + browser/components/migration/tests/Makefile browser/components/places/tests/Makefile browser/components/places/tests/chrome/Makefile browser/components/places/tests/browser/Makefile browser/components/privatebrowsing/test/Makefile browser/components/privatebrowsing/test/browser/Makefile browser/components/tabview/test/Makefile browser/components/test/Makefile browser/components/thumbnails/test/Makefile
--- a/browser/modules/NewTabUtils.jsm +++ b/browser/modules/NewTabUtils.jsm @@ -116,18 +116,16 @@ let Storage = { // Reset to normal DOM storage. this.currentStorage = this.domStorage; // When switching back from private browsing we need to reset the // grid and re-read its values from the underlying storage. We don't // want any data from private browsing to show up. PinnedLinks.resetCache(); BlockedLinks.resetCache(); - - Pages.update(); } }, QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]) }; /** @@ -183,37 +181,27 @@ PrivateBrowsingStorage.prototype = { */ let AllPages = { /** * The array containing all active pages. */ _pages: [], /** - * Tells whether we already added a preference observer. - */ - _observing: false, - - /** * Cached value that tells whether the New Tab Page feature is enabled. */ _enabled: null, /** * Adds a page to the internal list of pages. * @param aPage The page to register. */ register: function AllPages_register(aPage) { this._pages.push(aPage); - - // Add the preference observer if we haven't already. - if (!this._observing) { - this._observing = true; - Services.prefs.addObserver(PREF_NEWTAB_ENABLED, this, true); - } + this._addObserver(); }, /** * Removes a page from the internal list of pages. * @param aPage The page to unregister. */ unregister: function AllPages_unregister(aPage) { let index = this._pages.indexOf(aPage); @@ -234,16 +222,24 @@ let AllPages = { * Enables or disables the 'New Tab Page' feature. */ set enabled(aEnabled) { if (this.enabled != aEnabled) Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, !!aEnabled); }, /** + * Returns the number of registered New Tab Pages (i.e. the number of open + * about:newtab instances). + */ + get length() { + return this._pages.length; + }, + + /** * Updates all currently active pages but the given one. * @param aExceptPage The page to exclude from updating. */ update: function AllPages_update(aExceptPage) { this._pages.forEach(function (aPage) { if (aExceptPage != aPage) aPage.update(); }); @@ -259,16 +255,25 @@ let AllPages = { let args = Array.slice(arguments); this._pages.forEach(function (aPage) { aPage.observe.apply(aPage, args); }, this); }, + /** + * Adds a preference observer and turns itself into a no-op after the first + * invokation. + */ + _addObserver: function AllPages_addObserver() { + Services.prefs.addObserver(PREF_NEWTAB_ENABLED, this, true); + this._addObserver = function () {}; + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]) }; /** * Singleton that keeps track of all pinned links and their positions in the * grid. */ @@ -507,16 +512,18 @@ let Links = { if (this._links && !aForce) { executeCallbacks(); } else { this._provider.getLinks(function (aLinks) { this._links = aLinks; executeCallbacks(); }.bind(this)); + + this._addObserver(); } }, /** * Gets the current set of links contained in the grid. * @return The links in the grid. */ getLinks: function Links_getLinks() { @@ -539,17 +546,42 @@ let Links = { return pinnedLinks; }, /** * Resets the links cache. */ resetCache: function Links_resetCache() { this._links = []; - } + }, + + /** + * Implements the nsIObserver interface to get notified about browser history + * sanitization. + */ + observe: function Links_observe(aSubject, aTopic, aData) { + // Make sure to update open about:newtab instances. If there are no opened + // pages we can just wait for the next new tab to populate the cache again. + if (AllPages.length && AllPages.enabled) + this.populateCache(function () { AllPages.update() }, true); + else + this._links = null; + }, + + /** + * Adds a sanitization observer and turns itself into a no-op after the first + * invokation. + */ + _addObserver: function Links_addObserver() { + Services.obs.addObserver(this, "browser:purge-session-history", true); + this._addObserver = function () {}; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsISupportsWeakReference]) }; /** * Singleton that provides the public API of this JSM. */ let NewTabUtils = { /** * Resets the NewTabUtils module, its links and its storage.
--- a/browser/themes/gnomestripe/aboutCertError.css +++ b/browser/themes/gnomestripe/aboutCertError.css @@ -81,26 +81,29 @@ body[dir="rtl"] #errorPageContainer { #errorTitle { -moz-margin-start: 80px; } #errorLongContent { -moz-margin-start: 80px; } -#technicalContent > h2, #expertContent > h2 { - background : url("chrome://browser/skin/section_expanded.png") left 0 no-repeat; -} - -body[dir="rtl"] #technicalContent > h2, -body[dir="rtl"] #expertContent > h2 { - background-position: right 0; +.expander > button { + -moz-padding-start: 20px; + -moz-margin-start: -20px; + background: url("chrome://browser/skin/aboutCertError_sectionExpanded.png") left center no-repeat; + border: none; + font: inherit; + color: inherit; + cursor: pointer; } -#technicalContent[collapsed] > h2, -#expertContent[collapsed] > h2{ - background-image: url("chrome://browser/skin/section_collapsed.png"); +body[dir="rtl"] .expander > button { + background-position: right center; } -body[dir="rtl"] #technicalContent[collapsed] > h2, -body[dir="rtl"] #expertContent[collapsed] > h2 { - background-image: url("chrome://browser/skin/section_collapsed-rtl.png"); +.expander[collapsed] > button { + background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed.png"); } + +body[dir="rtl"] .expander[collapsed] > button { + background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed-rtl.png"); +}
rename from browser/themes/gnomestripe/section_collapsed-rtl.png rename to browser/themes/gnomestripe/aboutCertError_sectionCollapsed-rtl.png
rename from browser/themes/gnomestripe/section_collapsed.png rename to browser/themes/gnomestripe/aboutCertError_sectionCollapsed.png
rename from browser/themes/gnomestripe/section_expanded.png rename to browser/themes/gnomestripe/aboutCertError_sectionExpanded.png
--- a/browser/themes/gnomestripe/browser.css +++ b/browser/themes/gnomestripe/browser.css @@ -194,17 +194,17 @@ menuitem.bookmark-item { } /* Bookmarks toolbar */ #PlacesToolbarDropIndicator { list-style-image: url(chrome://browser/skin/places/toolbarDropMarker.png); } /* Bookmark items */ -.bookmark-item:not([container]) { +.bookmark-item { list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png"); } .bookmark-item[container] { list-style-image: url("moz-icon://stock/gtk-directory?size=menu"); } .bookmark-item[container][livemark] {
--- a/browser/themes/gnomestripe/devtools/csshtmltree.css +++ b/browser/themes/gnomestripe/devtools/csshtmltree.css @@ -141,16 +141,17 @@ cursor: pointer; } /* This rule is necessary because Templater.jsm breaks LTR TDs in RTL docs */ .rule-text { direction: ltr; padding: 0; -moz-padding-start: 20px; + vertical-align: text-bottom; } .bestmatch { color: black; } .matched { text-decoration: line-through; }
deleted file mode 100644 --- a/browser/themes/gnomestripe/fullscreen-video.css +++ /dev/null @@ -1,8 +0,0 @@ -#close { - position: absolute; - top: 0; - right: 0; - width: 32px; - height: 32px; - background: url(KUI-close.png) center center no-repeat; -}
--- a/browser/themes/gnomestripe/jar.mn +++ b/browser/themes/gnomestripe/jar.mn @@ -1,40 +1,39 @@ browser.jar: % skin browser classic/1.0 %skin/classic/browser/ % override chrome://global/skin/icons/warning-16.png moz-icon://stock/gtk-dialog-warning?size=menu skin/classic/browser/sanitizeDialog.css (sanitizeDialog.css) * skin/classic/browser/aboutPrivateBrowsing.css (aboutPrivateBrowsing.css) * skin/classic/browser/aboutSessionRestore.css (aboutSessionRestore.css) skin/classic/browser/aboutSessionRestore-window-icon.png - skin/classic/browser/aboutCertError.css (aboutCertError.css) + skin/classic/browser/aboutCertError.css + skin/classic/browser/aboutCertError_sectionCollapsed.png + skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png + skin/classic/browser/aboutCertError_sectionExpanded.png #ifdef MOZ_SERVICES_SYNC skin/classic/browser/aboutSyncTabs.css #endif skin/classic/browser/actionicon-tab.png * skin/classic/browser/browser.css (browser.css) * skin/classic/browser/engineManager.css (engineManager.css) - skin/classic/browser/fullscreen-video.css skin/classic/browser/Geolocation-16.png skin/classic/browser/Geolocation-64.png skin/classic/browser/Go-arrow.png skin/classic/browser/identity.png skin/classic/browser/Info.png skin/classic/browser/KUI-close.png skin/classic/browser/monitor.png skin/classic/browser/monitor_16-10.png * skin/classic/browser/pageInfo.css skin/classic/browser/pageInfo.png skin/classic/browser/page-livemarks.png skin/classic/browser/Privacy-16.png skin/classic/browser/Privacy-48.png skin/classic/browser/searchbar.css (searchbar.css) - skin/classic/browser/section_collapsed.png - skin/classic/browser/section_collapsed-rtl.png - skin/classic/browser/section_expanded.png skin/classic/browser/Secure.png skin/classic/browser/Security-broken.png skin/classic/browser/setDesktopBackground.css skin/classic/browser/Toolbar.png skin/classic/browser/Toolbar-small.png skin/classic/browser/urlbar-arrow.png skin/classic/browser/feeds/feedIcon.png (feeds/feedIcon.png) skin/classic/browser/feeds/feedIcon16.png (feeds/feedIcon16.png)
--- a/browser/themes/pinstripe/aboutCertError.css +++ b/browser/themes/pinstripe/aboutCertError.css @@ -81,26 +81,29 @@ body[dir="rtl"] #errorPageContainer { #errorTitle { -moz-margin-start: 80px; } #errorLongContent { -moz-margin-start: 80px; } -#technicalContent > h2, #expertContent > h2 { - background : url("chrome://browser/skin/section_expanded.png") left 0 no-repeat; -} - -body[dir="rtl"] #technicalContent > h2, -body[dir="rtl"] #expertContent > h2 { - background-position: right 0; +.expander > button { + -moz-padding-start: 20px; + -moz-margin-start: -20px; + background: url("chrome://browser/skin/aboutCertError_sectionExpanded.png") left center no-repeat; + border: none; + font: inherit; + color: inherit; + cursor: pointer; } -#technicalContent[collapsed] > h2, -#expertContent[collapsed] > h2{ - background-image: url("chrome://browser/skin/section_collapsed.png"); +body[dir="rtl"] .expander > button { + background-position: right center; } -body[dir="rtl"] #technicalContent[collapsed] > h2, -body[dir="rtl"] #expertContent[collapsed] > h2 { - background-image: url("chrome://browser/skin/section_collapsed-rtl.png"); +.expander[collapsed] > button { + background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed.png"); } + +body[dir="rtl"] .expander[collapsed] > button { + background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed-rtl.png"); +}
rename from browser/themes/pinstripe/section_collapsed-rtl.png rename to browser/themes/pinstripe/aboutCertError_sectionCollapsed-rtl.png
rename from browser/themes/pinstripe/section_collapsed.png rename to browser/themes/pinstripe/aboutCertError_sectionCollapsed.png
rename from browser/themes/pinstripe/section_expanded.png rename to browser/themes/pinstripe/aboutCertError_sectionExpanded.png
--- a/browser/themes/pinstripe/browser.css +++ b/browser/themes/pinstripe/browser.css @@ -224,25 +224,30 @@ toolbarbutton.bookmark-item > menupopup .bookmark-item > .toolbarbutton-icon[type="menu"] { -moz-margin-end: 5px; } .bookmark-item[container] { 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"); } .bookmark-item[query][tagContainer] { list-style-image: url("chrome://browser/skin/places/tag.png"); } .bookmark-item[query][dayContainer] { @@ -252,56 +257,49 @@ toolbarbutton.bookmark-item > menupopup .bookmark-item[query][hostContainer] { list-style-image: url("chrome://global/skin/tree/folder.png"); } .bookmark-item[query][hostContainer][open] { 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; } .bookmarks-toolbar-customize { max-width: 15em !important; list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important; } /* ----- BOOKMARK MENUS ----- */ .bookmark-item > .menu-iconic-left > .menu-iconic-icon { width: 16px; 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"); } #BMB_unsortedBookmarks { list-style-image: url("chrome://browser/skin/places/unfiledBookmarks.png"); }
--- a/browser/themes/pinstripe/devtools/csshtmltree.css +++ b/browser/themes/pinstripe/devtools/csshtmltree.css @@ -143,16 +143,17 @@ cursor: pointer; } /* This rule is necessary because Templater.jsm breaks LTR TDs in RTL docs */ .rule-text { direction: ltr; padding: 0; -moz-padding-start: 20px; + vertical-align: text-bottom; } .bestmatch { color: black; } .matched { text-decoration: line-through; }
deleted file mode 100644 --- a/browser/themes/pinstripe/fullscreen-video.css +++ /dev/null @@ -1,8 +0,0 @@ -#close { - position: absolute; - top: 0; - left: 0; - width: 32px; - height: 32px; - background: url(KUI-close.png) center center no-repeat; -}
--- a/browser/themes/pinstripe/jar.mn +++ b/browser/themes/pinstripe/jar.mn @@ -1,22 +1,24 @@ browser.jar: % skin browser classic/1.0 %skin/classic/browser/ skin/classic/browser/sanitizeDialog.css (sanitizeDialog.css) * skin/classic/browser/aboutPrivateBrowsing.css (aboutPrivateBrowsing.css) * skin/classic/browser/aboutSessionRestore.css (aboutSessionRestore.css) skin/classic/browser/aboutSessionRestore-window-icon.png - skin/classic/browser/aboutCertError.css (aboutCertError.css) + skin/classic/browser/aboutCertError.css + skin/classic/browser/aboutCertError_sectionCollapsed.png + skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png + skin/classic/browser/aboutCertError_sectionExpanded.png #ifdef MOZ_SERVICES_SYNC skin/classic/browser/aboutSyncTabs.css #endif skin/classic/browser/actionicon-tab.png * skin/classic/browser/browser.css (browser.css) * skin/classic/browser/engineManager.css (engineManager.css) - skin/classic/browser/fullscreen-video.css skin/classic/browser/Geolocation-16.png skin/classic/browser/Geolocation-64.png skin/classic/browser/home.png skin/classic/browser/hud-style-check-box-checked.png skin/classic/browser/hud-style-check-box-empty.png skin/classic/browser/hud-style-dropmarker-double-arrows.png skin/classic/browser/hud-style-expander-closed.png skin/classic/browser/hud-style-expander-open.png @@ -31,19 +33,16 @@ browser.jar: skin/classic/browser/page-livemarks.png skin/classic/browser/pageInfo.css skin/classic/browser/Privacy-16.png skin/classic/browser/Privacy-48.png skin/classic/browser/reload-stop-go.png skin/classic/browser/searchbar-dropmarker.png skin/classic/browser/searchbar.css skin/classic/browser/Search.png - skin/classic/browser/section_collapsed.png - skin/classic/browser/section_collapsed-rtl.png - skin/classic/browser/section_expanded.png skin/classic/browser/Secure-Glyph-White.png skin/classic/browser/keyhole-circle.png skin/classic/browser/Toolbar.png skin/classic/browser/toolbarbutton-dropmarker.png skin/classic/browser/urlbar-history-dropmarker.png skin/classic/browser/urlbar-arrow.png skin/classic/browser/urlbar-popup-blocked.png skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
--- a/browser/themes/winstripe/aboutCertError.css +++ b/browser/themes/winstripe/aboutCertError.css @@ -81,26 +81,29 @@ body[dir="rtl"] #errorPageContainer { #errorTitle { -moz-margin-start: 80px; } #errorLongContent { -moz-margin-start: 80px; } -#technicalContent > h2, #expertContent > h2 { - background : url("chrome://browser/skin/section_expanded.png") left center no-repeat; +.expander > button { + -moz-padding-start: 20px; + -moz-margin-start: -20px; + background: url("chrome://browser/skin/aboutCertError_sectionExpanded.png") left center no-repeat; + border: none; + font: inherit; + color: inherit; + cursor: pointer; } -body[dir="rtl"] #technicalContent > h2, -body[dir="rtl"] #expertContent > h2 { +body[dir="rtl"] .expander > button { background-position: right center; } -#technicalContent[collapsed] > h2, -#expertContent[collapsed] > h2{ - background-image: url("chrome://browser/skin/section_collapsed.png"); +.expander[collapsed] > button { + background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed.png"); } -body[dir="rtl"] #technicalContent[collapsed] > h2, -body[dir="rtl"] #expertContent[collapsed] > h2 { - background-image: url("chrome://browser/skin/section_collapsed-rtl.png"); +body[dir="rtl"] .expander[collapsed] > button { + background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed-rtl.png"); }
rename from browser/themes/winstripe/section_collapsed-rtl.png rename to browser/themes/winstripe/aboutCertError_sectionCollapsed-rtl.png
rename from browser/themes/winstripe/section_collapsed.png rename to browser/themes/winstripe/aboutCertError_sectionCollapsed.png
rename from browser/themes/winstripe/section_expanded.png rename to browser/themes/winstripe/aboutCertError_sectionExpanded.png
--- a/browser/themes/winstripe/devtools/csshtmltree.css +++ b/browser/themes/winstripe/devtools/csshtmltree.css @@ -141,16 +141,17 @@ cursor: pointer; } /* This rule is necessary because Templater.jsm breaks LTR TDs in RTL docs */ .rule-text { direction: ltr; padding: 0; -moz-padding-start: 20px; + vertical-align: text-bottom; } .bestmatch { color: black; } .matched { text-decoration: line-through; }
deleted file mode 100644 --- a/browser/themes/winstripe/fullscreen-video.css +++ /dev/null @@ -1,8 +0,0 @@ -#close { - position: absolute; - top: 0; - right: 0; - width: 32px; - height: 32px; - background: url(KUI-close.png) center center no-repeat; -}
--- a/browser/themes/winstripe/jar.mn +++ b/browser/themes/winstripe/jar.mn @@ -2,26 +2,28 @@ browser.jar: % skin browser classic/1.0 %skin/classic/browser/ os=WINNT osversion<6 % skin browser classic/1.0 %skin/classic/browser/ os!=WINNT # NOTE: If you add a new file here, you'll need to add it to the aero # section at the bottom of this file skin/classic/browser/sanitizeDialog.css (sanitizeDialog.css) * skin/classic/browser/aboutPrivateBrowsing.css (aboutPrivateBrowsing.css) * skin/classic/browser/aboutSessionRestore.css (aboutSessionRestore.css) skin/classic/browser/aboutSessionRestore-window-icon.png (preferences/application.png) - skin/classic/browser/aboutCertError.css (aboutCertError.css) + skin/classic/browser/aboutCertError.css + skin/classic/browser/aboutCertError_sectionCollapsed.png + skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png + skin/classic/browser/aboutCertError_sectionExpanded.png #ifdef MOZ_SERVICES_SYNC skin/classic/browser/aboutSyncTabs.css #endif skin/classic/browser/actionicon-tab.png skin/classic/browser/appmenu-icons.png skin/classic/browser/appmenu-dropmarker.png * skin/classic/browser/browser.css (browser.css) * skin/classic/browser/engineManager.css (engineManager.css) - skin/classic/browser/fullscreen-video.css skin/classic/browser/Geolocation-16.png skin/classic/browser/Geolocation-64.png skin/classic/browser/Info.png (Info.png) skin/classic/browser/identity.png (identity.png) skin/classic/browser/keyhole-forward-mask.svg skin/classic/browser/KUI-background.png skin/classic/browser/KUI-close.png skin/classic/browser/pageInfo.css @@ -33,19 +35,16 @@ browser.jar: skin/classic/browser/reload-stop-go.png skin/classic/browser/Secure24.png (Secure24.png) skin/classic/browser/Toolbar.png (Toolbar.png) skin/classic/browser/Toolbar-inverted.png skin/classic/browser/toolbarbutton-dropdown-arrow.png skin/classic/browser/toolbarbutton-dropdown-arrow-inverted.png * skin/classic/browser/searchbar.css (searchbar.css) skin/classic/browser/searchbar-dropdown-arrow.png - skin/classic/browser/section_collapsed.png - skin/classic/browser/section_collapsed-rtl.png - skin/classic/browser/section_expanded.png skin/classic/browser/setDesktopBackground.css skin/classic/browser/menu-back.png (menu-back.png) skin/classic/browser/menu-forward.png (menu-forward.png) skin/classic/browser/monitor.png skin/classic/browser/monitor_16-10.png skin/classic/browser/urlbar-arrow.png skin/classic/browser/urlbar-popup-blocked.png skin/classic/browser/urlbar-history-dropmarker.png @@ -172,26 +171,28 @@ browser.jar: #ifdef XP_WIN browser.jar: % skin browser classic/1.0 %skin/classic/aero/browser/ os=WINNT osversion>=6 skin/classic/aero/browser/sanitizeDialog.css (sanitizeDialog.css) * skin/classic/aero/browser/aboutPrivateBrowsing.css (aboutPrivateBrowsing.css) * skin/classic/aero/browser/aboutSessionRestore.css (aboutSessionRestore.css) skin/classic/aero/browser/aboutSessionRestore-window-icon.png (aboutSessionRestore-window-icon-aero.png) - skin/classic/aero/browser/aboutCertError.css (aboutCertError.css) + skin/classic/aero/browser/aboutCertError.css + skin/classic/aero/browser/aboutCertError_sectionCollapsed.png + skin/classic/aero/browser/aboutCertError_sectionCollapsed-rtl.png + skin/classic/aero/browser/aboutCertError_sectionExpanded.png #ifdef MOZ_SERVICES_SYNC skin/classic/aero/browser/aboutSyncTabs.css #endif skin/classic/aero/browser/actionicon-tab.png (actionicon-tab.png) skin/classic/aero/browser/appmenu-dropmarker.png skin/classic/aero/browser/appmenu-icons.png * skin/classic/aero/browser/browser.css (browser-aero.css) * skin/classic/aero/browser/engineManager.css (engineManager.css) - skin/classic/aero/browser/fullscreen-video.css skin/classic/aero/browser/Geolocation-16.png skin/classic/aero/browser/Geolocation-64.png skin/classic/aero/browser/Info.png (Info-aero.png) skin/classic/aero/browser/identity.png (identity-aero.png) skin/classic/aero/browser/keyhole-forward-mask.svg skin/classic/aero/browser/KUI-background.png skin/classic/aero/browser/KUI-close.png skin/classic/aero/browser/pageInfo.css @@ -203,19 +204,16 @@ browser.jar: skin/classic/aero/browser/reload-stop-go.png skin/classic/aero/browser/Secure24.png (Secure24-aero.png) skin/classic/aero/browser/Toolbar.png skin/classic/aero/browser/Toolbar-inverted.png skin/classic/aero/browser/toolbarbutton-dropdown-arrow.png skin/classic/aero/browser/toolbarbutton-dropdown-arrow-inverted.png * skin/classic/aero/browser/searchbar.css (searchbar.css) skin/classic/aero/browser/searchbar-dropdown-arrow.png (searchbar-dropdown-arrow-aero.png) - skin/classic/aero/browser/section_collapsed.png - skin/classic/aero/browser/section_collapsed-rtl.png - skin/classic/aero/browser/section_expanded.png skin/classic/aero/browser/setDesktopBackground.css skin/classic/aero/browser/menu-back.png (menu-back-aero.png) skin/classic/aero/browser/menu-forward.png (menu-forward-aero.png) skin/classic/aero/browser/monitor.png skin/classic/aero/browser/monitor_16-10.png skin/classic/aero/browser/urlbar-arrow.png skin/classic/aero/browser/urlbar-popup-blocked.png skin/classic/aero/browser/urlbar-history-dropmarker.png
--- a/build/autoconf/compiler-opts.m4 +++ b/build/autoconf/compiler-opts.m4 @@ -4,10 +4,81 @@ AC_DEFUN([MOZ_COMPILER_OPTS], [ if test "$CLANG_CXX"; then ## We disable return-type-c-linkage because jsval is defined as a C++ type but is ## returned by C functions. This is possible because we use knowledge about the ABI ## to typedef it to a C type with the same layout when the headers are included ## from C. _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wno-unknown-warning-option -Wno-return-type-c-linkage" fi + +if test "$GNU_CC"; then + CFLAGS="$CFLAGS -ffunction-sections -fdata-sections" + CXXFLAGS="$CXXFLAGS -ffunction-sections -fdata-sections" +fi + +dnl ======================================================== +dnl = Identical Code Folding +dnl ======================================================== + +MOZ_ARG_DISABLE_BOOL(icf, +[ --disable-icf Disable Identical Code Folding], + MOZ_DISABLE_ICF=1, + MOZ_DISABLE_ICF= ) + +if test "$GNU_CC" -a "$GCC_USE_GNU_LD" -a -z "$MOZ_DISABLE_ICF"; then + AC_CACHE_CHECK([whether the linker supports Identical Code Folding], + LD_SUPPORTS_ICF, + [echo 'int foo() {return 42;}' \ + 'int bar() {return 42;}' \ + 'int main() {return foo() - bar();}' > conftest.${ac_ext} + # If the linker supports ICF, foo and bar symbols will have + # the same address + if AC_TRY_COMMAND([${CC-cc} -o conftest${ac_exeext} $LDFLAGS -Wl,--icf=safe -ffunction-sections conftest.${ac_ext} $LIBS 1>&2]) && + test -s conftest${ac_exeext} && + objdump -t conftest${ac_exeext} | awk changequote(<<, >>)'{a[<<$>>6] = <<$>>1} END {if (a["foo"] && (a["foo"] != a["bar"])) { exit 1 }}'changequote([, ]); then + LD_SUPPORTS_ICF=yes + else + LD_SUPPORTS_ICF=no + fi + rm -rf conftest*]) + if test "$LD_SUPPORTS_ICF" = yes; then + _SAVE_LDFLAGS="$LDFLAGS -Wl,--icf=safe" + LDFLAGS="$LDFLAGS -Wl,--icf=safe -Wl,--print-icf-sections" + AC_TRY_LINK([], [], + [LD_PRINT_ICF_SECTIONS=-Wl,--print-icf-sections], + [LD_PRINT_ICF_SECTIONS=]) + AC_SUBST([LD_PRINT_ICF_SECTIONS]) + LDFLAGS="$_SAVE_LDFLAGS" + fi +fi + +dnl ======================================================== +dnl = Automatically remove dead symbols +dnl ======================================================== + +if test "$GNU_CC" -a "$GCC_USE_GNU_LD" -a -n "$MOZ_DEBUG_FLAGS"; then + dnl See bug 670659 + AC_CACHE_CHECK([whether removing dead symbols breaks debugging], + GC_SECTIONS_BREAKS_DEBUG_RANGES, + [echo 'int foo() {return 42;}' \ + 'int bar() {return 1;}' \ + 'int main() {return foo();}' > conftest.${ac_ext} + if AC_TRY_COMMAND([${CC-cc} -o conftest.${ac_objext} $CFLAGS $MOZ_DEBUG_FLAGS -c conftest.${ac_ext} 1>&2]) && + AC_TRY_COMMAND([${CC-cc} -o conftest${ac_exeext} $LDFLAGS $MOZ_DEBUG_FLAGS -Wl,--gc-sections conftest.${ac_objext} $LIBS 1>&2]) && + test -s conftest${ac_exeext} -a -s conftest.${ac_objext}; then + if test "`$PYTHON "$_topsrcdir"/build/autoconf/check_debug_ranges.py conftest.${ac_objext} conftest.${ac_ext}`" = \ + "`$PYTHON "$_topsrcdir"/build/autoconf/check_debug_ranges.py conftest${ac_exeext} conftest.${ac_ext}`"; then + GC_SECTIONS_BREAKS_DEBUG_RANGES=no + else + GC_SECTIONS_BREAKS_DEBUG_RANGES=yes + fi + else + dnl We really don't expect to get here, but just in case + GC_SECTIONS_BREAKS_DEBUG_RANGES="no, but it's broken in some other way" + fi + rm -rf conftest*]) + if test "$GC_SECTIONS_BREAKS_DEBUG_RANGES" = no; then + DSO_LDOPTS="$DSO_LDOPTS -Wl,--gc-sections" + fi +fi + ]) -
new file mode 100644 --- /dev/null +++ b/build/autoconf/expandlibs.m4 @@ -0,0 +1,56 @@ +AC_DEFUN([MOZ_EXPAND_LIBS], +[ +dnl ======================================================== +dnl = +dnl = Check what kind of list files are supported by the +dnl = linker +dnl = +dnl ======================================================== + +AC_CACHE_CHECK(what kind of list files are supported by the linker, + EXPAND_LIBS_LIST_STYLE, + [echo "int main() {return 0;}" > conftest.${ac_ext} + if AC_TRY_COMMAND(${CC-cc} -o conftest.${OBJ_SUFFIX} -c $CFLAGS $CPPFLAGS conftest.${ac_ext} 1>&5) && test -s conftest.${OBJ_SUFFIX}; then + echo "INPUT(conftest.${OBJ_SUFFIX})" > conftest.list + if AC_TRY_COMMAND(${CC-cc} -o conftest${ac_exeext} $LDFLAGS conftest.list $LIBS 1>&5) && test -s conftest${ac_exeext}; then + EXPAND_LIBS_LIST_STYLE=linkerscript + else + echo "conftest.${OBJ_SUFFIX}" > conftest.list + if AC_TRY_COMMAND(${CC-cc} -o conftest${ac_exeext} $LDFLAGS @conftest.list $LIBS 1>&5) && test -s conftest${ac_exeext}; then + EXPAND_LIBS_LIST_STYLE=list + else + EXPAND_LIBS_LIST_STYLE=none + fi + fi + else + dnl We really don't expect to get here, but just in case + AC_ERROR([couldn't compile a simple C file]) + fi + rm -rf conftest*]) + +LIBS_DESC_SUFFIX=desc +AC_SUBST(LIBS_DESC_SUFFIX) +AC_SUBST(EXPAND_LIBS_LIST_STYLE) + +if test "$GCC_USE_GNU_LD"; then + AC_CACHE_CHECK(what kind of ordering can be done with the linker, + EXPAND_LIBS_ORDER_STYLE, + [> conftest.order + _SAVE_LDFLAGS="$LDFLAGS" + LDFLAGS="${LDFLAGS} -Wl,--section-ordering-file,conftest.order" + AC_TRY_LINK([], [], + EXPAND_LIBS_ORDER_STYLE=section-ordering-file, + EXPAND_LIBS_ORDER_STYLE=) + LDFLAGS="$_SAVE_LDFLAGS" + if test -z "$EXPAND_LIBS_ORDER_STYLE"; then + if AC_TRY_COMMAND(${CC-cc} ${DSO_LDOPTS} ${LDFLAGS} -o ${DLL_PREFIX}conftest${DLL_SUFFIX} -Wl,--verbose 2> /dev/null | sed -n '/^===/,/^===/p' | grep '\.text'); then + EXPAND_LIBS_ORDER_STYLE=linkerscript + else + EXPAND_LIBS_ORDER_STYLE=none + fi + rm -f ${DLL_PREFIX}conftest${DLL_SUFFIX} + fi]) +fi +AC_SUBST(EXPAND_LIBS_ORDER_STYLE) + +])
--- a/build/automation.py.in +++ b/build/automation.py.in @@ -390,16 +390,17 @@ user_pref("camino.warn_when_closing", fa // Make url-classifier updates so rare that they won't affect tests user_pref("urlclassifier.updateinterval", 172800); // Point the url-classifier to the local testing server for fast failures user_pref("browser.safebrowsing.provider.0.gethashURL", "http://%(server)s/safebrowsing-dummy/gethash"); user_pref("browser.safebrowsing.provider.0.keyURL", "http://%(server)s/safebrowsing-dummy/newkey"); user_pref("browser.safebrowsing.provider.0.updateURL", "http://%(server)s/safebrowsing-dummy/update"); // Point update checks to the local testing server for fast failures user_pref("extensions.update.url", "http://%(server)s/extensions-dummy/updateURL"); +user_pref("extensions.update.background.url", "http://%(server)s/extensions-dummy/updateBackgroundURL"); user_pref("extensions.blocklist.url", "http://%(server)s/extensions-dummy/blocklistURL"); user_pref("extensions.hotfix.url", "http://%(server)s/extensions-dummy/hotfixURL"); // Make sure opening about:addons won't hit the network user_pref("extensions.webservice.discoverURL", "http://%(server)s/extensions-dummy/discoveryURL"); // Make sure AddonRepository won't hit the network user_pref("extensions.getAddons.maxResults", 0); user_pref("extensions.getAddons.get.url", "http://%(server)s/extensions-dummy/repositoryGetURL"); user_pref("extensions.getAddons.getWithPerformance.url", "http://%(server)s/extensions-dummy/repositoryGetWithPerformanceURL"); @@ -732,18 +733,21 @@ user_pref("camino.use_system_proxy_setti self.log.info("Failed to read image from %s", imgoutput) import base64 encoded = base64.b64encode(image) self.log.info("SCREENSHOT: data:image/png;base64,%s", encoded) 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: # ABRT will get picked up by Breakpad's signal handler os.kill(proc.pid, signal.SIGABRT) return elif self.IS_WIN32: # We should have a "crashinject" program in our utility path @@ -790,18 +794,21 @@ user_pref("camino.use_system_proxy_setti while line != "" and not didTimeout: if logger: logger.log(line) if "TEST-START" in line and "|" in line: self.lastTestSeen = line.split("|")[1].strip() 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): # Kill the application, but continue reading from stack fixer so as not to deadlock on stackFixerProcess.wait(). hitMaxTime = True self.log.info("TEST-UNEXPECTED-FAIL | %s | application ran for longer than allowed maximum time of %d seconds", self.lastTestSeen, int(maxTime)) self.killAndGetStack(proc, utilityPath, debuggerInfo) if didTimeout:
--- a/build/automationutils.py +++ b/build/automationutils.py @@ -454,17 +454,17 @@ def wrapCommand(cmd): return cmd class ShutdownLeakLogger(object): """ Parses the mochitest run log when running a debug build, assigns all leaked 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 = 120 def __init__(self, logger): self.logger = logger self.tests = [] self.leakedWindows = {} self.leakedDocShells = set() self.currentTest = None self.seenShutdown = False
--- a/build/mobile/robocop/Driver.java.in +++ b/build/mobile/robocop/Driver.java.in @@ -72,10 +72,10 @@ public interface Driver { void startCheckerboardRecording(); float stopCheckerboardRecording(); /** * Get a copy of the painted content region. * @return A 2-D array of pixels (indexed by y, then x). The pixels * are in ARGB-8888 format. */ - int[][] getPaintedSurface(); + PaintedSurface getPaintedSurface(); }
--- a/build/mobile/robocop/FennecNativeDriver.java.in +++ b/build/mobile/robocop/FennecNativeDriver.java.in @@ -43,16 +43,18 @@ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; 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; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.InvocationHandler; import java.lang.Long; @@ -286,17 +288,17 @@ public class FennecNativeDriver implemen for (View v : mSolo.getCurrentViews()) { if (v instanceof GLSurfaceView) { return (GLSurfaceView)v; } } return null; } - public int[][] getPaintedSurface() { + public PaintedSurface getPaintedSurface() { GLSurfaceView view = getSurfaceView(); if (view == null) { return null; } IntBuffer pixelBuffer; try { pixelBuffer = (IntBuffer)_getPixels.invoke(view); } catch (Exception e) { @@ -304,24 +306,44 @@ public class FennecNativeDriver implemen return null; } // now we need to (1) flip the image, because GL likes to do things up-side-down, // and (2) rearrange the bits from AGBR-8888 to ARGB-8888. 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; public int mScrollHeight=0; public int mPageHeight=10; class scrollHandler implements InvocationHandler { public scrollHandler(){};
--- a/build/mobile/robocop/Makefile.in +++ b/build/mobile/robocop/Makefile.in @@ -56,21 +56,25 @@ JAVAFILES = \ Driver.java \ Element.java \ FennecNativeActions.java \ FennecMochitestAssert.java \ FennecTalosAssert.java \ 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 \ parse_ids.py \ $(NULL) GARBAGE += \ AndroidManifest.xml \
new file mode 100644 --- /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; + } +} +
--- a/caps/idl/nsIPrincipal.idl +++ b/caps/idl/nsIPrincipal.idl @@ -47,17 +47,17 @@ struct JSPrincipals; %} interface nsIURI; interface nsIContentSecurityPolicy; [ptr] native JSContext(JSContext); [ptr] native JSPrincipals(JSPrincipals); -[scriptable, uuid(1f83b0e0-6b63-4bdc-a50a-b9afe256bd25)] +[scriptable, uuid(f8c4c89a-d726-421b-8415-3e34b241175b)] interface nsIPrincipal : nsISerializable { /** * Values of capabilities for each principal. Order is * significant: if an operation is performed on a set * of capabilities, the minimum is computed. */ const short ENABLE_DENIED = 1; @@ -92,22 +92,16 @@ interface nsIPrincipal : nsISerializable boolean equalsIgnoringDomain(in nsIPrincipal other); /** * Returns a hash value for the principal. */ [noscript] readonly attribute unsigned long hashValue; /** - * Returns the JS equivalent of the principal. - * @see JSPrincipals.h - */ - [noscript] JSPrincipals getJSPrincipals(in JSContext cx); - - /** * The domain security policy of the principal. */ // XXXcaa should this be here? The script security manager is the only // thing that should care about this. Wouldn't storing this data in one // of the hashtables in nsScriptSecurityManager be better? // XXXbz why is this writable? Who should have write access to this? What // happens if this principal is in our hashtable and we pass it out of the // security manager and someone writes to this field? Especially if they
--- a/caps/include/nsJSPrincipals.h +++ b/caps/include/nsJSPrincipals.h @@ -38,19 +38,52 @@ #ifndef nsJSPrincipals_h__ #define nsJSPrincipals_h__ #include "jsapi.h" #include "nsIPrincipal.h" class nsCString; -struct nsJSPrincipals : JSPrincipals +struct nsJSPrincipals : nsIPrincipal, JSPrincipals { - static nsresult Startup(); - nsJSPrincipals(); - nsresult Init(nsIPrincipal* aPrincipal, const nsCString& aCodebase); - ~nsJSPrincipals(void); + static JSBool Subsume(JSPrincipals *jsprin, JSPrincipals *other); + static void Destroy(JSPrincipals *jsprin); + static JSBool Transcode(JSXDRState *xdr, JSPrincipals **jsprinp); + + /* + * Get a weak reference to nsIPrincipal associated with the given JS + * principal. + */ + static nsJSPrincipals* get(JSPrincipals *principals) { + nsJSPrincipals *self = static_cast<nsJSPrincipals *>(principals); + MOZ_ASSERT_IF(self, self->debugToken == DEBUG_TOKEN); + return self; + } + + static nsJSPrincipals* get(nsIPrincipal *principal) { + nsJSPrincipals *self = static_cast<nsJSPrincipals *>(principal); + MOZ_ASSERT_IF(self, self->debugToken == DEBUG_TOKEN); + return self; + } - nsIPrincipal *nsIPrincipalPtr; // [WEAK] it owns us. + nsJSPrincipals() { + refcount = 0; + setDebugToken(DEBUG_TOKEN); + } + + virtual ~nsJSPrincipals() { + setDebugToken(0); + } + + /** + * Return a string that can be used as JS script filename in error reports. + */ + virtual void GetScriptLocation(nsACString &aStr) = 0; + +#ifdef DEBUG + virtual void dumpImpl() = 0; +#endif + + static const uint32_t DEBUG_TOKEN = 0x0bf41760; }; #endif /* nsJSPrincipals_h__ */
--- a/caps/include/nsNullPrincipal.h +++ b/caps/include/nsNullPrincipal.h @@ -54,33 +54,38 @@ class nsIURI; #define NS_NULLPRINCIPAL_CLASSNAME "nullprincipal" #define NS_NULLPRINCIPAL_CID \ { 0xdd156d62, 0xd26f, 0x4441, \ { 0x9c, 0xdb, 0xe8, 0xf0, 0x91, 0x07, 0xc2, 0x73 } } #define NS_NULLPRINCIPAL_CONTRACTID "@mozilla.org/nullprincipal;1" #define NS_NULLPRINCIPAL_SCHEME "moz-nullprincipal" -class nsNullPrincipal : public nsIPrincipal +class nsNullPrincipal : public nsJSPrincipals { public: nsNullPrincipal(); - // Our refcount is managed by mJSPrincipals. Use this macro to avoid an + // Our refcount is managed by nsJSPrincipals. Use this macro to avoid an // extra refcount member. // FIXME: bug 327245 -- I sorta wish there were a clean way to share the - // mJSPrincipals munging code between the various principal classes without + // nsJSPrincipals munging code between the various principal classes without // giving up the NS_DECL_NSIPRINCIPAL goodness. NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIPRINCIPAL NS_DECL_NSISERIALIZABLE nsresult Init(); -protected: + virtual void GetScriptLocation(nsACString &aStr) MOZ_OVERRIDE; + +#ifdef DEBUG + virtual void dumpImpl() MOZ_OVERRIDE; +#endif + + protected: virtual ~nsNullPrincipal(); - nsJSPrincipals mJSPrincipals; nsCOMPtr<nsIURI> mURI; }; #endif // nsNullPrincipal_h__
--- a/caps/include/nsPrincipal.h +++ b/caps/include/nsPrincipal.h @@ -46,26 +46,26 @@ #include "nsHashtable.h" #include "nsJSPrincipals.h" #include "nsTArray.h" #include "nsAutoPtr.h" class nsIObjectInputStream; class nsIObjectOutputStream; -class nsPrincipal : public nsIPrincipal +class nsPrincipal : public nsJSPrincipals { public: nsPrincipal(); protected: virtual ~nsPrincipal(); public: - // Our refcount is managed by mJSPrincipals. Use this macro to avoid + // Our refcount is managed by nsJSPrincipals. Use this macro to avoid // an extra refcount member. NS_DECL_ISUPPORTS_INHERITED public: NS_DECL_NSIPRINCIPAL NS_DECL_NSISERIALIZABLE // Either Init() or InitFromPersistent() must be called before @@ -95,18 +95,23 @@ public: enum AnnotationValue { AnnotationEnabled=1, AnnotationDisabled }; void SetURI(nsIURI *aURI); nsresult SetCapability(const char *capability, void **annotation, AnnotationValue value); static const char sInvalid[]; + virtual void GetScriptLocation(nsACString &aStr) MOZ_OVERRIDE; + +#ifdef DEBUG + virtual void dumpImpl() MOZ_OVERRIDE; +#endif + protected: - nsJSPrincipals mJSPrincipals; nsTArray< nsAutoPtr<nsHashtable> > mAnnotations; nsHashtable* mCapabilities; nsCString mPrefName; static PRInt32 sCapabilitiesOrdinal; // XXXcaa This is a semi-hack. The best solution here is to keep // a reference to an interface here, except there is no interface // that we can use yet.
--- a/caps/include/nsScriptSecurityManager.h +++ b/caps/include/nsScriptSecurityManager.h @@ -423,16 +423,19 @@ private: nsScriptSecurityManager(); virtual ~nsScriptSecurityManager(); static JSBool CheckObjectAccess(JSContext *cx, JSObject *obj, jsid id, JSAccessMode mode, jsval *vp); + static JSPrincipals * + ObjectPrincipalFinder(JSObject *obj); + // Decides, based on CSP, whether or not eval() and stuff can be executed. static JSBool ContentSecurityPolicyPermitsJSAction(JSContext *cx); // Returns null if a principal cannot be found; generally callers // should error out at that point. static nsIPrincipal* doGetObjectPrincipal(JSObject *obj
--- a/caps/include/nsSystemPrincipal.h +++ b/caps/include/nsSystemPrincipal.h @@ -45,30 +45,33 @@ #define NS_SYSTEMPRINCIPAL_CLASSNAME "systemprincipal" #define NS_SYSTEMPRINCIPAL_CID \ { 0x4a6212db, 0xaccb, 0x11d3, \ { 0xb7, 0x65, 0x0, 0x60, 0xb0, 0xb6, 0xce, 0xcb }} #define NS_SYSTEMPRINCIPAL_CONTRACTID "@mozilla.org/systemprincipal;1" -class nsSystemPrincipal : public nsIPrincipal +class nsSystemPrincipal : public nsJSPrincipals { public: - // Our refcount is managed by mJSPrincipals. Use this macro to avoid + // Our refcount is managed by nsJSPrincipals. Use this macro to avoid // an extra refcount member. NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIPRINCIPAL NS_DECL_NSISERIALIZABLE - nsresult Init(JSPrincipals **jsprin); + nsSystemPrincipal(); + + virtual void GetScriptLocation(nsACString &aStr) MOZ_OVERRIDE; - nsSystemPrincipal(); +#ifdef DEBUG + virtual void dumpImpl() MOZ_OVERRIDE; +#endif protected: virtual ~nsSystemPrincipal(void); - nsJSPrincipals mJSPrincipals; // XXX Probably unnecessary. See bug 143559. NS_DECL_OWNINGTHREAD }; #endif // nsSystemPrincipal_h__
--- a/caps/src/nsJSPrincipals.cpp +++ b/caps/src/nsJSPrincipals.cpp @@ -45,56 +45,54 @@ #include "nsCOMPtr.h" #include "jsapi.h" #include "jsxdrapi.h" #include "nsIJSRuntimeService.h" #include "nsIServiceManager.h" #include "nsMemory.h" #include "nsStringBuffer.h" -static JSBool -nsJSPrincipalsSubsume(JSPrincipals *jsprin, JSPrincipals *other) +// for mozilla::dom::workers::kJSPrincipalsDebugToken +#include "mozilla/dom/workers/Workers.h" + +/* static */ JSBool +nsJSPrincipals::Subsume(JSPrincipals *jsprin, JSPrincipals *other) { - nsJSPrincipals *nsjsprin = static_cast<nsJSPrincipals *>(jsprin); - nsJSPrincipals *nsother = static_cast<nsJSPrincipals *>(other); - bool result; - nsresult rv = nsjsprin->nsIPrincipalPtr->Subsumes(nsother->nsIPrincipalPtr, - &result); + nsresult rv = nsJSPrincipals::get(jsprin)->Subsumes(nsJSPrincipals::get(other), &result); return NS_SUCCEEDED(rv) && result; } -static void -nsDestroyJSPrincipals(JSContext *cx, struct JSPrincipals *jsprin) +/* static */ void +nsJSPrincipals::Destroy(JSPrincipals *jsprin) { - nsJSPrincipals *nsjsprin = static_cast<nsJSPrincipals *>(jsprin); + // The JS runtime can call this method during the last GC when + // nsScriptSecurityManager is destroyed. So we must not assume here that + // the security manager still exists. + + nsJSPrincipals *nsjsprin = nsJSPrincipals::get(jsprin); // We need to destroy the nsIPrincipal. We'll do this by adding // to the refcount and calling release - // Note that we don't want to use NS_IF_RELEASE because it will try - // to set nsjsprin->nsIPrincipalPtr to nsnull *after* nsjsprin has - // already been destroyed. #ifdef NS_BUILD_REFCNT_LOGGING // The refcount logging considers AddRef-to-1 to indicate creation, // so trick it into thinking it's otherwise, but balance the // Release() we do below. nsjsprin->refcount++; - nsjsprin->nsIPrincipalPtr->AddRef(); + nsjsprin->AddRef(); nsjsprin->refcount--; #else nsjsprin->refcount++; #endif - nsjsprin->nsIPrincipalPtr->Release(); - // The nsIPrincipal that we release owns the JSPrincipal struct, - // so we don't need to worry about "codebase" + nsjsprin->Release(); } -static JSBool -nsTranscodeJSPrincipals(JSXDRState *xdr, JSPrincipals **jsprinp) +/* static */ JSBool +nsJSPrincipals::Transcode(JSXDRState *xdr, JSPrincipals **jsprinp) { nsresult rv; if (xdr->mode == JSXDR_ENCODE) { nsIObjectOutputStream *stream = reinterpret_cast<nsIObjectOutputStream*>(xdr->userdata); // Flush xdr'ed data to the underlying object output stream. @@ -102,22 +100,17 @@ nsTranscodeJSPrincipals(JSXDRState *xdr, char *data = (char*) ::JS_XDRMemGetData(xdr, &size); rv = stream->Write32(size); if (NS_SUCCEEDED(rv)) { rv = stream->WriteBytes(data, size); if (NS_SUCCEEDED(rv)) { ::JS_XDRMemResetData(xdr); - // Require that GetJSPrincipals has been called already by the - // code that compiled the script that owns the principals. - nsJSPrincipals *nsjsprin = - static_cast<nsJSPrincipals*>(*jsprinp); - - rv = stream->WriteObject(nsjsprin->nsIPrincipalPtr, true); + rv = stream->WriteObject(nsJSPrincipals::get(*jsprinp), true); } } } else { NS_ASSERTION(JS_XDRMemDataLeft(xdr) == 0, "XDR out of sync?!"); nsIObjectInputStream *stream = reinterpret_cast<nsIObjectInputStream*>(xdr->userdata); nsCOMPtr<nsIPrincipal> prin; @@ -136,89 +129,43 @@ nsTranscodeJSPrincipals(JSXDRState *xdr, // Any decode-mode JSXDRState whose userdata points to an // nsIObjectInputStream instance must use nsMemory to Alloc // and Free its data buffer. Swap the new buffer we just // read for the old, exhausted data. olddata = (char*) ::JS_XDRMemGetData(xdr, &oldsize); nsMemory::Free(olddata); ::JS_XDRMemSetData(xdr, data, size); - prin->GetJSPrincipals(xdr->cx, jsprinp); + *jsprinp = nsJSPrincipals::get(prin); + JS_HoldPrincipals(*jsprinp); } } } } if (NS_FAILED(rv)) { ::JS_ReportError(xdr->cx, "can't %scode principals (failure code %x)", (xdr->mode == JSXDR_ENCODE) ? "en" : "de", (unsigned int) rv); return JS_FALSE; } return JS_TRUE; } -nsresult -nsJSPrincipals::Startup() -{ - nsCOMPtr<nsIJSRuntimeService> rtsvc = nsXPConnect::GetXPConnect(); - if (!rtsvc) - return NS_ERROR_FAILURE; - - JSRuntime *rt; - rtsvc->GetRuntime(&rt); - NS_ASSERTION(rt != nsnull, "no JSRuntime?!"); - - JSSecurityCallbacks *callbacks = JS_GetRuntimeSecurityCallbacks(rt); - NS_ASSERTION(callbacks, "Need a callbacks struct by now!"); +#ifdef DEBUG - NS_ASSERTION(!callbacks->principalsTranscoder, - "oops, JS_SetPrincipalsTranscoder wars!"); - - callbacks->principalsTranscoder = nsTranscodeJSPrincipals; - return NS_OK; -} - -nsJSPrincipals::nsJSPrincipals() +// Defined here so one can do principals->dump() in the debugger +JS_EXPORT_API(void) +JSPrincipals::dump() { - codebase = nsnull; - refcount = 0; - destroy = nsDestroyJSPrincipals; - subsume = nsJSPrincipalsSubsume; - nsIPrincipalPtr = nsnull; + if (debugToken == nsJSPrincipals::DEBUG_TOKEN) { + static_cast<nsJSPrincipals *>(this)->dumpImpl(); + } else if (debugToken == mozilla::dom::workers::kJSPrincipalsDebugToken) { + fprintf(stderr, "Web Worker principal singleton (%p)\n", this); + } else { + fprintf(stderr, + "!!! JSPrincipals (%p) is not nsJSPrincipals instance - bad token: " + "actual=0x%x expected=0x%x\n", + this, unsigned(debugToken), unsigned(nsJSPrincipals::DEBUG_TOKEN)); + } } -nsresult -nsJSPrincipals::Init(nsIPrincipal *aPrincipal, const nsCString& aCodebase) -{ - if (nsIPrincipalPtr) { - NS_ERROR("Init called twice!"); - return NS_ERROR_UNEXPECTED; - } - - nsIPrincipalPtr = aPrincipal; - nsStringBuffer* buf = nsStringBuffer::FromString(aCodebase); - char* data; - if (buf) { - buf->AddRef(); - data = static_cast<char*>(buf->Data()); - } else { - PRUint32 len = aCodebase.Length(); - buf = nsStringBuffer::Alloc(len + 1); // addrefs - if (!buf) { - return NS_ERROR_OUT_OF_MEMORY; - } - data = static_cast<char*>(buf->Data()); - memcpy(data, aCodebase.get(), len); - data[len] = '\0'; - } - - codebase = data; - - return NS_OK; -} - -nsJSPrincipals::~nsJSPrincipals() -{ - if (codebase) { - nsStringBuffer::FromData(codebase)->Release(); - } -} +#endif
--- a/caps/src/nsNullPrincipal.cpp +++ b/caps/src/nsNullPrincipal.cpp @@ -64,27 +64,27 @@ NS_IMPL_QUERY_INTERFACE2_CI(nsNullPrinci nsISerializable) NS_IMPL_CI_INTERFACE_GETTER2(nsNullPrincipal, nsIPrincipal, nsISerializable) NS_IMETHODIMP_(nsrefcnt) nsNullPrincipal::AddRef() { - NS_PRECONDITION(PRInt32(mJSPrincipals.refcount) >= 0, "illegal refcnt"); - nsrefcnt count = PR_ATOMIC_INCREMENT(&mJSPrincipals.refcount); + NS_PRECONDITION(PRInt32(refcount) >= 0, "illegal refcnt"); + nsrefcnt count = PR_ATOMIC_INCREMENT(&refcount); NS_LOG_ADDREF(this, count, "nsNullPrincipal", sizeof(*this)); return count; } NS_IMETHODIMP_(nsrefcnt) nsNullPrincipal::Release() { - NS_PRECONDITION(0 != mJSPrincipals.refcount, "dup release"); - nsrefcnt count = PR_ATOMIC_DECREMENT(&mJSPrincipals.refcount); + NS_PRECONDITION(0 != refcount, "dup release"); + nsrefcnt count = PR_ATOMIC_DECREMENT(&refcount); NS_LOG_RELEASE(this, count, "nsNullPrincipal"); if (count == 0) { delete this; } return count; } @@ -128,19 +128,34 @@ nsNullPrincipal::Init() if (str.Length() != prefixLen + suffixLen) { NS_WARNING("Out of memory allocating null-principal URI"); return NS_ERROR_OUT_OF_MEMORY; } mURI = new nsNullPrincipalURI(str); NS_ENSURE_TRUE(mURI, NS_ERROR_OUT_OF_MEMORY); - return mJSPrincipals.Init(this, str); + return NS_OK; +} + +void +nsNullPrincipal::GetScriptLocation(nsACString &aStr) +{ + mURI->GetSpec(aStr); } +#ifdef DEBUG +void nsNullPrincipal::dumpImpl() +{ + nsCAutoString str; + mURI->GetSpec(str); + fprintf(stderr, "nsNullPrincipal (%p) = %s\n", this, str.get()); +} +#endif + /** * nsIPrincipal implementation */ NS_IMETHODIMP nsNullPrincipal::GetPreferences(char** aPrefName, char** aID, char** aSubjectName, char** aGrantedList, char** aDeniedList, @@ -175,27 +190,16 @@ nsNullPrincipal::EqualsIgnoringDomain(ns NS_IMETHODIMP nsNullPrincipal::GetHashValue(PRUint32 *aResult) { *aResult = (NS_PTR_TO_INT32(this) >> 2); return NS_OK; } NS_IMETHODIMP -nsNullPrincipal::GetJSPrincipals(JSContext *cx, JSPrincipals **aJsprin) -{ - NS_PRECONDITION(mJSPrincipals.nsIPrincipalPtr, - "mJSPrincipals is uninitalized!"); - - JSPRINCIPALS_HOLD(cx, &mJSPrincipals); - *aJsprin = &mJSPrincipals; - return NS_OK; -} - -NS_IMETHODIMP nsNullPrincipal::GetSecurityPolicy(void** aSecurityPolicy) { // We don't actually do security policy caching. And it's not like anyone // can set a security policy for us anyway. *aSecurityPolicy = nsnull; return NS_OK; }
--- a/caps/src/nsPrincipal.cpp +++ b/caps/src/nsPrincipal.cpp @@ -88,28 +88,28 @@ NS_IMPL_QUERY_INTERFACE2_CI(nsPrincipal, nsISerializable) NS_IMPL_CI_INTERFACE_GETTER2(nsPrincipal, nsIPrincipal, nsISerializable) NS_IMETHODIMP_(nsrefcnt) nsPrincipal::AddRef() { - NS_PRECONDITION(PRInt32(mJSPrincipals.refcount) >= 0, "illegal refcnt"); + NS_PRECONDITION(PRInt32(refcount) >= 0, "illegal refcnt"); // XXXcaa does this need to be threadsafe? See bug 143559. - nsrefcnt count = PR_ATOMIC_INCREMENT(&mJSPrincipals.refcount); + nsrefcnt count = PR_ATOMIC_INCREMENT(&refcount); NS_LOG_ADDREF(this, count, "nsPrincipal", sizeof(*this)); return count; } NS_IMETHODIMP_(nsrefcnt) nsPrincipal::Release() { - NS_PRECONDITION(0 != mJSPrincipals.refcount, "dup release"); - nsrefcnt count = PR_ATOMIC_DECREMENT(&mJSPrincipals.refcount); + NS_PRECONDITION(0 != refcount, "dup release"); + nsrefcnt count = PR_ATOMIC_DECREMENT(&refcount); NS_LOG_RELEASE(this, count, "nsPrincipal"); if (count == 0) { delete this; } return count; } @@ -142,51 +142,46 @@ nsPrincipal::Init(const nsACString& aCer NS_ENSURE_STATE(!mInitialized); NS_ENSURE_ARG(!aCertFingerprint.IsEmpty() || aCodebase); // better have one of these. mInitialized = true; mCodebase = NS_TryToMakeImmutable(aCodebase); mCodebaseImmutable = URIIsImmutable(mCodebase); - nsresult rv; - if (!aCertFingerprint.IsEmpty()) { - rv = SetCertificate(aCertFingerprint, aSubjectName, aPrettyName, aCert); - if (NS_SUCCEEDED(rv)) { - rv = mJSPrincipals.Init(this, mCert->fingerprint); - } - } - else { - nsCAutoString spec; - rv = mCodebase->GetSpec(spec); - if (NS_SUCCEEDED(rv)) { - rv = mJSPrincipals.Init(this, spec); - } - } + if (aCertFingerprint.IsEmpty()) + return NS_OK; - NS_ASSERTION(NS_SUCCEEDED(rv), "nsPrincipal::Init() failed"); - - return rv; + return SetCertificate(aCertFingerprint, aSubjectName, aPrettyName, aCert); } nsPrincipal::~nsPrincipal(void) { SetSecurityPolicy(nsnull); delete mCapabilities; } -NS_IMETHODIMP -nsPrincipal::GetJSPrincipals(JSContext *cx, JSPrincipals **jsprin) +void +nsPrincipal::GetScriptLocation(nsACString &aStr) { - NS_PRECONDITION(mJSPrincipals.nsIPrincipalPtr, "mJSPrincipals is uninitialized!"); + if (mCert) { + aStr.Assign(mCert->fingerprint); + } else { + mCodebase->GetSpec(aStr); + } +} - JSPRINCIPALS_HOLD(cx, &mJSPrincipals); - *jsprin = &mJSPrincipals; - return NS_OK; +#ifdef DEBUG +void nsPrincipal::dumpImpl() +{ + nsCAutoString str; + GetScriptLocation(str); + fprintf(stderr, "nsPrincipal (%p) = %s\n", this, str.get()); } +#endif NS_IMETHODIMP nsPrincipal::GetOrigin(char **aOrigin) { *aOrigin = nsnull; nsCOMPtr<nsIURI> origin; if (mCodebase) { @@ -878,19 +873,16 @@ nsPrincipal::InitFromPersistent(const ch } NS_TryToSetImmutable(mCodebase); mCodebaseImmutable = URIIsImmutable(mCodebase); mTrusted = aTrusted; } - rv = mJSPrincipals.Init(this, aToken); - NS_ENSURE_SUCCESS(rv, rv); - //-- Save the preference name mPrefName = aPrefName; const char* ordinalBegin = PL_strpbrk(aPrefName, "1234567890"); if (ordinalBegin) { PRIntn n = atoi(ordinalBegin); if (sCapabilitiesOrdinal <= n) { sCapabilitiesOrdinal = n + 1;
--- a/caps/src/nsScriptSecurityManager.cpp +++ b/caps/src/nsScriptSecurityManager.cpp @@ -512,16 +512,23 @@ NS_IMPL_ISUPPORTS4(nsScriptSecurityManag nsIChannelEventSink, nsIObserver) /////////////////////////////////////////////////// // Methods implementing nsIScriptSecurityManager // /////////////////////////////////////////////////// ///////////////// Security Checks ///////////////// + +/* static */ JSPrincipals * +nsScriptSecurityManager::ObjectPrincipalFinder(JSObject *aObj) +{ + return nsJSPrincipals::get(doGetObjectPrincipal(aObj)); +} + JSBool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx) { // Get the security manager nsScriptSecurityManager *ssm = nsScriptSecurityManager::GetScriptSecurityManager(); NS_ASSERTION(ssm, "Failed to get security manager service"); @@ -532,17 +539,17 @@ nsScriptSecurityManager::ContentSecurity nsIPrincipal* subjectPrincipal = ssm->GetSubjectPrincipal(cx, &rv); NS_ASSERTION(NS_SUCCEEDED(rv), "CSP: Failed to get nsIPrincipal from js context"); if (NS_FAILED(rv)) return JS_FALSE; // Not just absence of principal, but failure. if (!subjectPrincipal) { // See bug 553448 for discussion of this case. - NS_ASSERTION(!JS_GetSecurityCallbacks(cx)->findObjectPrincipals, + NS_ASSERTION(!JS_GetSecurityCallbacks(js::GetRuntime(cx))->findObjectPrincipals, "CSP: Should have been able to find subject principal. " "Reluctantly granting access."); return JS_TRUE; } nsCOMPtr<nsIContentSecurityPolicy> csp; rv = subjectPrincipal->GetCsp(getter_AddRefs(csp)); NS_ASSERTION(NS_SUCCEEDED(rv), "CSP: Failed to get CSP from principal."); @@ -2174,21 +2181,17 @@ nsScriptSecurityManager::GetScriptPrinci return nsnull; } JSPrincipals *jsp = JS_GetScriptPrincipals(cx, script); if (!jsp) { *rv = NS_ERROR_FAILURE; NS_ERROR("Script compiled without principals!"); return nsnull; } - nsJSPrincipals *nsJSPrin = static_cast<nsJSPrincipals *>(jsp); - nsIPrincipal* result = nsJSPrin->nsIPrincipalPtr; - if (!result) - *rv = NS_ERROR_FAILURE; - return result; + return nsJSPrincipals::get(jsp); } // static nsIPrincipal* nsScriptSecurityManager::GetFunctionObjectPrincipal(JSContext *cx, JSObject *obj, JSStackFrame *fp, nsresult *rv) @@ -3325,17 +3328,16 @@ nsScriptSecurityManager::nsScriptSecurit mPolicyPrefsChanged(true) { NS_ASSERTION(sizeof(PRWord) == sizeof(void*), "PRWord and void* have different lengths on this platform. " "This may cause a security failure with the SecurityLevel union."); mPrincipals.Init(31); } - nsresult nsScriptSecurityManager::Init() { nsXPConnect* xpconnect = nsXPConnect::GetXPConnect(); if (!xpconnect) return NS_ERROR_FAILURE; NS_ADDREF(sXPConnect = xpconnect); NS_ADDREF(sJSContextStack = xpconnect); @@ -3360,45 +3362,40 @@ nsresult nsScriptSecurityManager::Init() rv = bundleService->CreateBundle("chrome://global/locale/security/caps.properties", &sStrBundle); NS_ENSURE_SUCCESS(rv, rv); // Create our system principal singleton nsRefPtr<nsSystemPrincipal> system = new nsSystemPrincipal(); NS_ENSURE_TRUE(system, NS_ERROR_OUT_OF_MEMORY); - JSPrincipals *jsprin; - rv = system->Init(&jsprin); - NS_ENSURE_SUCCESS(rv, rv); - mSystemPrincipal = system; //-- Register security check callback in the JS engine // Currently this is used to control access to function.caller nsCOMPtr<nsIJSRuntimeService> runtimeService = do_QueryInterface(sXPConnect, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = runtimeService->GetRuntime(&sRuntime); NS_ENSURE_SUCCESS(rv, rv); - static JSSecurityCallbacks securityCallbacks = { + static const JSSecurityCallbacks securityCallbacks = { CheckObjectAccess, - NULL, - NULL, + nsJSPrincipals::Subsume, + nsJSPrincipals::Transcode, + ObjectPrincipalFinder, ContentSecurityPolicyPermitsJSAction }; -#ifdef DEBUG - JSSecurityCallbacks *oldcallbacks = -#endif - JS_SetRuntimeSecurityCallbacks(sRuntime, &securityCallbacks); - NS_ASSERTION(!oldcallbacks, "Someone else set security callbacks!"); - - JS_SetTrustedPrincipals(sRuntime, jsprin); + MOZ_ASSERT(!JS_GetSecurityCallbacks(sRuntime)); + JS_SetSecurityCallbacks(sRuntime, &securityCallbacks); + JS_InitDestroyPrincipalsCallback(sRuntime, nsJSPrincipals::Destroy); + + JS_SetTrustedPrincipals(sRuntime, system); return NS_OK; } static nsScriptSecurityManager *gScriptSecMan = nsnull; jsid nsScriptSecurityManager::sEnabledID = JSID_VOID; @@ -3412,17 +3409,17 @@ nsScriptSecurityManager::~nsScriptSecuri delete mCapabilities; gScriptSecMan = nsnull; } void nsScriptSecurityManager::Shutdown() { if (sRuntime) { - JS_SetRuntimeSecurityCallbacks(sRuntime, NULL); + JS_SetSecurityCallbacks(sRuntime, NULL); JS_SetTrustedPrincipals(sRuntime, NULL); sRuntime = nsnull; } sEnabledID = JSID_VOID; NS_IF_RELEASE(sIOService); NS_IF_RELEASE(sXPConnect); NS_IF_RELEASE(sJSContextStack); @@ -3440,23 +3437,16 @@ nsScriptSecurityManager::GetScriptSecuri nsresult rv; rv = ssManager->Init(); NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to initialize nsScriptSecurityManager"); if (NS_FAILED(rv)) { delete ssManager; return nsnull; } - rv = nsJSPrincipals::Startup(); - if (NS_FAILED(rv)) { - NS_WARNING("can't initialize JS engine security protocol glue!"); - delete ssManager; - return nsnull; - } - rv = sXPConnect->SetDefaultSecurityManager(ssManager, nsIXPCSecurityManager::HOOK_ALL); if (NS_FAILED(rv)) { NS_WARNING("Failed to install xpconnect security manager!"); delete ssManager; return nsnull; }
--- a/caps/src/nsSystemPrincipal.cpp +++ b/caps/src/nsSystemPrincipal.cpp @@ -57,42 +57,55 @@ NS_IMPL_QUERY_INTERFACE2_CI(nsSystemPrin nsISerializable) NS_IMPL_CI_INTERFACE_GETTER2(nsSystemPrincipal, nsIPrincipal, nsISerializable) NS_IMETHODIMP_(nsrefcnt) nsSystemPrincipal::AddRef() { - NS_PRECONDITION(PRInt32(mJSPrincipals.refcount) >= 0, "illegal refcnt"); - nsrefcnt count = PR_ATOMIC_INCREMENT(&mJSPrincipals.refcount); + NS_PRECONDITION(PRInt32(refcount) >= 0, "illegal refcnt"); + nsrefcnt count = PR_ATOMIC_INCREMENT(&refcount); NS_LOG_ADDREF(this, count, "nsSystemPrincipal", sizeof(*this)); return count; } NS_IMETHODIMP_(nsrefcnt) nsSystemPrincipal::Release() { - NS_PRECONDITION(0 != mJSPrincipals.refcount, "dup release"); - nsrefcnt count = PR_ATOMIC_DECREMENT(&mJSPrincipals.refcount); + NS_PRECONDITION(0 != refcount, "dup release"); + nsrefcnt count = PR_ATOMIC_DECREMENT(&refcount); NS_LOG_RELEASE(this, count, "nsSystemPrincipal"); if (count == 0) { delete this; } return count; } +static const char SYSTEM_PRINCIPAL_SPEC[] = "[System Principal]"; + +void +nsSystemPrincipal::GetScriptLocation(nsACString &aStr) +{ + aStr.Assign(SYSTEM_PRINCIPAL_SPEC); +} + +#ifdef DEBUG +void nsSystemPrincipal::dumpImpl() +{ + fprintf(stderr, "nsSystemPrincipal (%p)\n", this); +} +#endif + /////////////////////////////////////// // Methods implementing nsIPrincipal // /////////////////////////////////////// -#define SYSTEM_PRINCIPAL_SPEC "[System Principal]" - NS_IMETHODIMP nsSystemPrincipal::GetPreferences(char** aPrefName, char** aID, char** aSubjectName, char** aGrantedList, char** aDeniedList, bool* aIsTrusted) { // The system principal should never be streamed out *aPrefName = nsnull; @@ -275,26 +288,16 @@ nsSystemPrincipal::GetSecurityPolicy(voi } NS_IMETHODIMP nsSystemPrincipal::SetSecurityPolicy(void* aSecurityPolicy) { return NS_OK; } -NS_IMETHODIMP -nsSystemPrincipal::GetJSPrincipals(JSContext *cx, JSPrincipals **jsprin) -{ - NS_PRECONDITION(mJSPrincipals.nsIPrincipalPtr, "mJSPrincipals is uninitialized!"); - - JSPRINCIPALS_HOLD(cx, &mJSPrincipals); - *jsprin = &mJSPrincipals; - return NS_OK; -} - ////////////////////////////////////////// // Methods implementing nsISerializable // ////////////////////////////////////////// NS_IMETHODIMP nsSystemPrincipal::Read(nsIObjectInputStream* aStream) { @@ -312,29 +315,11 @@ nsSystemPrincipal::Write(nsIObjectOutput ///////////////////////////////////////////// // Constructor, Destructor, initialization // ///////////////////////////////////////////// nsSystemPrincipal::nsSystemPrincipal() { } -nsresult -nsSystemPrincipal::Init(JSPrincipals **jsprin) -{ - // Use an nsCString so we only do the allocation once here and then - // share with nsJSPrincipals - nsCString str(SYSTEM_PRINCIPAL_SPEC); - if (!str.EqualsLiteral(SYSTEM_PRINCIPAL_SPEC)) { - NS_WARNING("Out of memory initializing system principal"); - return NS_ERROR_OUT_OF_MEMORY; - } - - nsresult rv = mJSPrincipals.Init(this, str); - NS_ENSURE_SUCCESS(rv, rv); - - *jsprin = &mJSPrincipals; - return NS_OK; -} - -nsSystemPrincipal::~nsSystemPrincipal(void) +nsSystemPrincipal::~nsSystemPrincipal() { }
--- a/config/autoconf.mk.in +++ b/config/autoconf.mk.in @@ -296,16 +296,20 @@ MOZ_GIO_CFLAGS = @MOZ_GIO_CFLAGS@ MOZ_GIO_LIBS = @MOZ_GIO_LIBS@ MOZ_NATIVE_NSPR = @MOZ_NATIVE_NSPR@ 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@ CROSS_COMPILE = @CROSS_COMPILE@ WCHAR_CFLAGS = @WCHAR_CFLAGS@ OS_CPPFLAGS = @CPPFLAGS@
--- a/config/config.mk +++ b/config/config.mk @@ -784,18 +784,22 @@ OPTIMIZE_JARS_CMD = $(PYTHON) $(call cor CREATE_PRECOMPLETE_CMD = $(PYTHON) $(call core_abspath,$(topsrcdir)/config/createprecomplete.py) EXPAND_LIBS = $(PYTHON) -I$(DEPTH)/config $(topsrcdir)/config/expandlibs.py EXPAND_LIBS_EXEC = $(PYTHON) $(topsrcdir)/config/pythonpath.py -I$(DEPTH)/config $(topsrcdir)/config/expandlibs_exec.py EXPAND_LIBS_GEN = $(PYTHON) $(topsrcdir)/config/pythonpath.py -I$(DEPTH)/config $(topsrcdir)/config/expandlibs_gen.py EXPAND_AR = $(EXPAND_LIBS_EXEC) --extract -- $(AR) EXPAND_CC = $(EXPAND_LIBS_EXEC) --uselist -- $(CC) EXPAND_CCC = $(EXPAND_LIBS_EXEC) --uselist -- $(CCC) -EXPAND_LD = $(EXPAND_LIBS_EXEC) --uselist $(if $(REORDER),--reorder $(REORDER))-- $(LD) -EXPAND_MKSHLIB = $(EXPAND_LIBS_EXEC) --uselist $(if $(REORDER),--reorder $(REORDER))-- $(MKSHLIB) +EXPAND_LD = $(EXPAND_LIBS_EXEC) --uselist -- $(LD) +EXPAND_MKSHLIB_ARGS = --uselist +ifdef SYMBOL_ORDER +EXPAND_MKSHLIB_ARGS += --symbol-order $(SYMBOL_ORDER) +endif +EXPAND_MKSHLIB = $(EXPAND_LIBS_EXEC) $(EXPAND_MKSHLIB_ARGS) -- $(MKSHLIB) ifdef STDCXX_COMPAT ifneq ($(OS_ARCH),Darwin) CHECK_STDCXX = objdump -p $(1) | grep -e 'GLIBCXX_3\.4\.\(9\|[1-9][0-9]\)' > /dev/null && echo "TEST-UNEXPECTED-FAIL | | We don't want these libstdc++ symbols to be used:" && objdump -T $(1) | grep -e 'GLIBCXX_3\.4\.\(9\|[1-9][0-9]\)' && exit 1 || exit 0 endif endif # autoconf.mk sets OBJ_SUFFIX to an error to avoid use before including
--- a/config/expandlibs_config.py.in +++ b/config/expandlibs_config.py.in @@ -49,8 +49,10 @@ AR_EXTRACT = "@AR_EXTRACT@".replace('$(A DLL_PREFIX = "@DLL_PREFIX@" LIB_PREFIX = "@LIB_PREFIX@" OBJ_SUFFIX = normalize_suffix("@OBJ_SUFFIX@") LIB_SUFFIX = normalize_suffix("@LIB_SUFFIX@") DLL_SUFFIX = normalize_suffix("@DLL_SUFFIX@") IMPORT_LIB_SUFFIX = normalize_suffix("@IMPORT_LIB_SUFFIX@") LIBS_DESC_SUFFIX = normalize_suffix("@LIBS_DESC_SUFFIX@") EXPAND_LIBS_LIST_STYLE = "@EXPAND_LIBS_LIST_STYLE@" +EXPAND_LIBS_ORDER_STYLE = "@EXPAND_LIBS_ORDER_STYLE@" +LD_PRINT_ICF_SECTIONS = "@LD_PRINT_ICF_SECTIONS@"
--- a/config/expandlibs_exec.py +++ b/config/expandlibs_exec.py @@ -44,29 +44,41 @@ from static libraries (or use those list With the --uselist argument (useful for e.g. $(CC)), it replaces all object files with a list file. This can be used to avoid limitations in the length of a command line. The kind of list file format used depends on the EXPAND_LIBS_LIST_STYLE variable: 'list' for MSVC style lists (@file.list) or 'linkerscript' for GNU ld linker scripts. See https://bugzilla.mozilla.org/show_bug.cgi?id=584474#c59 for more details. -With the --reorder argument, followed by a file name, it will reorder the -object files from the command line according to the order given in the file. -Implies --extract. +With the --symbol-order argument, followed by a file name, it will add the +relevant linker options to change the order in which the linker puts the +symbols appear in the resulting binary. Only works for ELF targets. ''' from __future__ import with_statement import sys import os from expandlibs import ExpandArgs, relativize, isObject import expandlibs_config as conf from optparse import OptionParser import subprocess import tempfile import shutil +import subprocess +import re + +# The are the insert points for a GNU ld linker script, assuming a more +# or less "standard" default linker script. This is not a dict because +# order is important. +SECTION_INSERT_BEFORE = [ + ('.text', '.fini'), + ('.rodata', '.rodata1'), + ('.data.rel.ro', '.dynamic'), + ('.data', '.data1'), +] class ExpandArgsMore(ExpandArgs): ''' Meant to be used as 'with ExpandArgsMore(args) as ...: ''' def __enter__(self): self.tmp = [] return self def __exit__(self, type, value, tb): @@ -114,61 +126,203 @@ class ExpandArgsMore(ExpandArgs): fd, tmp = tempfile.mkstemp(suffix=".list",dir=os.curdir) if conf.EXPAND_LIBS_LIST_STYLE == "linkerscript": content = ["INPUT(%s)\n" % obj for obj in objs] ref = tmp elif conf.EXPAND_LIBS_LIST_STYLE == "list": content = ["%s\n" % obj for obj in objs] ref = "@" + tmp else: + os.close(fd) os.remove(tmp) return self.tmp.append(tmp) f = os.fdopen(fd, "w") f.writelines(content) f.close() idx = self.index(objs[0]) newlist = self[0:idx] + [ref] + [item for item in self[idx:] if item not in objs] self[0:] = newlist - def reorder(self, order_list): - '''Given a list of file names without OBJ_SUFFIX, rearrange self - so that the object file names it contains are ordered according to - that list. - ''' - objs = [o for o in self if isObject(o)] - if not objs: return - idx = self.index(objs[0]) - # Keep everything before the first object, then the ordered objects, - # then any other objects, then any non-objects after the first object - objnames = dict([(os.path.splitext(os.path.basename(o))[0], o) for o in objs]) - self[0:] = self[0:idx] + [objnames[o] for o in order_list if o in objnames] + \ - [o for o in objs if os.path.splitext(os.path.basename(o))[0] not in order_list] + \ - [x for x in self[idx:] if not isObject(x)] + def _getFoldedSections(self): + '''Returns a dict about folded sections. + When section A and B are folded into section C, the dict contains: + { 'A': 'C', + 'B': 'C', + 'C': ['A', 'B'] }''' + if not conf.LD_PRINT_ICF_SECTIONS: + return {} + + proc = subprocess.Popen(self + [conf.LD_PRINT_ICF_SECTIONS], stdout = subprocess.PIPE, stderr = subprocess.PIPE) + (stdout, stderr) = proc.communicate() + result = {} + # gold's --print-icf-sections output looks like the following: + # ld: ICF folding section '.section' in file 'file.o'into '.section' in file 'file.o' + # In terms of words, chances are this will change in the future, + # especially considering "into" is misplaced. Splitting on quotes + # seems safer. + for l in stderr.split('\n'): + quoted = l.split("'") + if len(quoted) > 5 and quoted[1] != quoted[5]: + result[quoted[1]] = quoted[5] + if quoted[5] in result: + result[quoted[5]].append(quoted[1]) + else: + result[quoted[5]] = [quoted[1]] + return result + + def _getOrderedSections(self, ordered_symbols): + '''Given an ordered list of symbols, returns the corresponding list + of sections following the order.''' + if not conf.EXPAND_LIBS_ORDER_STYLE in ['linkerscript', 'section-ordering-file']: + raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE) + finder = SectionFinder([arg for arg in self if isObject(arg) or os.path.splitext(arg)[1] == conf.LIB_SUFFIX]) + folded = self._getFoldedSections() + sections = set() + ordered_sections = [] + for symbol in ordered_symbols: + symbol_sections = finder.getSections(symbol) + all_symbol_sections = [] + for section in symbol_sections: + if section in folded: + if isinstance(folded[section], str): + section = folded[section] + all_symbol_sections.append(section) + all_symbol_sections.extend(folded[section]) + else: + all_symbol_sections.append(section) + for section in all_symbol_sections: + if not section in sections: + ordered_sections.append(section) + sections.add(section) + return ordered_sections + + def orderSymbols(self, order): + '''Given a file containing a list of symbols, adds the appropriate + argument to make the linker put the symbols in that order.''' + with open(order) as file: + sections = self._getOrderedSections([l.strip() for l in file.readlines() if l.strip()]) + split_sections = {} + linked_sections = [s[0] for s in SECTION_INSERT_BEFORE] + for s in sections: + for linked_section in linked_sections: + if s.startswith(linked_section): + if linked_section in split_sections: + split_sections[linked_section].append(s) + else: + split_sections[linked_section] = [s] + break + content = [] + # Order is important + linked_sections = [s for s in linked_sections if s in split_sections] + if conf.EXPAND_LIBS_ORDER_STYLE == 'section-ordering-file': + option = '-Wl,--section-ordering-file,%s' + content = sections + for linked_section in linked_sections: + content.extend(split_sections[linked_section]) + content.append('%s.*' % linked_section) + content.append(linked_section) + + elif conf.EXPAND_LIBS_ORDER_STYLE == 'linkerscript': + option = '-Wl,-T,%s' + section_insert_before = dict(SECTION_INSERT_BEFORE) + for linked_section in linked_sections: + content.append('SECTIONS {') + content.append(' %s : {' % linked_section) + content.extend(' *(%s)' % s for s in split_sections[linked_section]) + content.append(' }') + content.append('}') + content.append('INSERT BEFORE %s' % section_insert_before[linked_section]) + else: + raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE) + + fd, tmp = tempfile.mkstemp(dir=os.curdir) + f = os.fdopen(fd, "w") + f.write('\n'.join(content)+'\n') + f.close() + self.tmp.append(tmp) + self.append(option % tmp) + +class SectionFinder(object): + '''Instances of this class allow to map symbol names to sections in + object files.''' + + def __init__(self, objs): + '''Creates an instance, given a list of object files.''' + if not conf.EXPAND_LIBS_ORDER_STYLE in ['linkerscript', 'section-ordering-file']: + raise Exception('EXPAND_LIBS_ORDER_STYLE "%s" is not supported' % conf.EXPAND_LIBS_ORDER_STYLE) + self.mapping = {} + for obj in objs: + if not isObject(obj) and os.path.splitext(obj)[1] != conf.LIB_SUFFIX: + raise Exception('%s is not an object nor a static library' % obj) + for symbol, section in SectionFinder._getSymbols(obj): + sym = SectionFinder._normalize(symbol) + if sym in self.mapping: + if not section in self.mapping[sym]: + self.mapping[sym].append(section) + else: + self.mapping[sym] = [section] + + def getSections(self, symbol): + '''Given a symbol, returns a list of sections containing it or the + corresponding thunks. When the given symbol is a thunk, returns the + list of sections containing its corresponding normal symbol and the + other thunks for that symbol.''' + sym = SectionFinder._normalize(symbol) + if sym in self.mapping: + return self.mapping[sym] + return [] + + @staticmethod + def _normalize(symbol): + '''For normal symbols, return the given symbol. For thunks, return + the corresponding normal symbol.''' + if re.match('^_ZThn[0-9]+_', symbol): + return re.sub('^_ZThn[0-9]+_', '_Z', symbol) + return symbol + + @staticmethod + def _getSymbols(obj): + '''Returns a list of (symbol, section) contained in the given object + file.''' + proc = subprocess.Popen(['objdump', '-t', obj], stdout = subprocess.PIPE, stderr = subprocess.PIPE) + (stdout, stderr) = proc.communicate() + syms = [] + for line in stdout.splitlines(): + # Each line has the following format: + # <addr> [lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol> + tmp = line.split(' ',1) + # This gives us ["<addr>", "[lgu!][w ][C ][W ][Ii ][dD ][FfO ] <section>\t<length> <symbol>"] + # We only need to consider cases where "<section>\t<length> <symbol>" is present, + # and where the [FfO] flag is either F (function) or O (object). + if len(tmp) > 1 and len(tmp[1]) > 6 and tmp[1][6] in ['O', 'F']: + tmp = tmp[1][8:].split() + # That gives us ["<section>","<length>", "<symbol>"] + syms.append((tmp[-1], tmp[0])) + return syms def main(): parser = OptionParser() parser.add_option("--extract", action="store_true", dest="extract", help="when a library has no descriptor file, extract it first, when possible") parser.add_option("--uselist", action="store_true", dest="uselist", help="use a list file for objects when executing a command") parser.add_option("--verbose", action="store_true", dest="verbose", help="display executed command and temporary files content") - parser.add_option("--reorder", dest="reorder", - help="reorder the objects according to the given list", metavar="FILE") + parser.add_option("--symbol-order", dest="symbol_order", metavar="FILE", + help="use the given list of symbols to order symbols in the resulting binary when using with a linker") (options, args) = parser.parse_args() with ExpandArgsMore(args) as args: - if options.extract or options.reorder: + if options.extract: args.extract() - if options.reorder: - with open(options.reorder) as file: - args.reorder([l.strip() for l in file.readlines()]) + if options.symbol_order: + args.orderSymbols(options.symbol_order) if options.uselist: args.makelist() if options.verbose: print >>sys.stderr, "Executing: " + " ".join(args) for tmp in [f for f in args.tmp if os.path.isfile(f)]: print >>sys.stderr, tmp + ":" with open(tmp) as file:
--- a/config/rules.mk +++ b/config/rules.mk @@ -143,16 +143,19 @@ testxpcsrcdir = $(topsrcdir)/testing/xpc # Execute all tests in the $(XPCSHELL_TESTS) directories. # See also testsuite-targets.mk 'xpcshell-tests' target for global execution. xpcshell-tests: $(PYTHON) -u $(topsrcdir)/config/pythonpath.py \ -I$(topsrcdir)/build \ $(testxpcsrcdir)/runxpcshelltests.py \ --symbols-path=$(DIST)/crashreporter-symbols \ --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)) xpcshell-tests-remote: DM_TRANS?=adb xpcshell-tests-remote: $(PYTHON) -u $(topsrcdir)/config/pythonpath.py \ -I$(topsrcdir)/build \
--- a/config/tests/unit-expandlibs.py +++ b/config/tests/unit-expandlibs.py @@ -33,17 +33,17 @@ config_unix = { 'LIBS_DESC_SUFFIX': '.desc', 'EXPAND_LIBS_LIST_STYLE': 'linkerscript', } config = sys.modules['expandlibs_config'] = imp.new_module('expandlibs_config') from expandlibs import LibDescriptor, ExpandArgs, relativize from expandlibs_gen import generate -from expandlibs_exec import ExpandArgsMore +from expandlibs_exec import ExpandArgsMore, SectionFinder def Lib(name): return config.LIB_PREFIX + name + config.LIB_SUFFIX def Obj(name): return name + config.OBJ_SUFFIX def Dll(name): @@ -262,33 +262,106 @@ class TestExpandArgsMore(TestExpandInit) tmp = args.tmp # Check that all temporary files are properly removed self.assertEqual(True, all([not os.path.exists(f) for f in tmp])) # Restore subprocess.call subprocess.call = subprocess_call - def test_reorder(self): - '''Test object reordering''' - # We don't care about AR_EXTRACT testing, which is done in test_extract - config.AR_EXTRACT = '' +class FakeProcess(object): + def __init__(self, out, err = ''): + self.out = out + self.err = err + + def communicate(self): + return (self.out, self.err) - # ExpandArgsMore does the same as ExpandArgs - with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args: - self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files) +OBJDUMPS = { +'foo.o': ''' +00000000 g F .text\t00000001 foo +00000000 g F .text._Z6foobarv\t00000001 _Z6foobarv +00000000 g F .text.hello\t00000001 hello +00000000 g F .text._ZThn4_6foobarv\t00000001 _ZThn4_6foobarv +''', +'bar.o': ''' +00000000 g F .text.hi\t00000001 hi +00000000 g F .text.hot._Z6barbazv\t00000001 .hidden _Z6barbazv +''', +} + +PRINT_ICF = ''' +ld: ICF folding section '.text.hello' in file 'foo.o'into '.text.hi' in file 'bar.o' +ld: ICF folding section '.foo' in file 'foo.o'into '.foo' in file 'bar.o' +''' + +class SubprocessPopen(object): + def __init__(self, test): + self.test = test + + def __call__(self, args, stdout = None, stderr = None): + self.test.assertEqual(stdout, subprocess.PIPE) + self.test.assertEqual(stderr, subprocess.PIPE) + if args[0] == 'objdump': + self.test.assertEqual(args[1], '-t') + self.test.assertTrue(args[2] in OBJDUMPS) + return FakeProcess(OBJDUMPS[args[2]]) + else: + return FakeProcess('', PRINT_ICF) - # Use an order containing object files from libraries - order_files = [self.libx_files[1], self.libx_files[0], self.liby_files[2], self.files[1]] - order = [os.path.splitext(os.path.basename(f))[0] for f in order_files] - args.reorder(order[:2] + ['unknown'] + order[2:]) +class TestSectionFinder(unittest.TestCase): + def test_getSections(self): + '''Test SectionFinder''' + # Divert subprocess.Popen + subprocess_popen = subprocess.Popen + subprocess.Popen = SubprocessPopen(self) + config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript' + config.OBJ_SUFFIX = '.o' + config.LIB_SUFFIX = '.a' + finder = SectionFinder(['foo.o', 'bar.o']) + self.assertEqual(finder.getSections('foobar'), []) + self.assertEqual(finder.getSections('_Z6barbazv'), ['.text.hot._Z6barbazv']) + self.assertEqual(finder.getSections('_Z6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv']) + self.assertEqual(finder.getSections('_ZThn4_6foobarv'), ['.text._Z6foobarv', '.text._ZThn4_6foobarv']) + subprocess.Popen = subprocess_popen - # self.files has objects at #1, #2, #4 - self.assertRelEqual(args[:3], ['foo', '-bar'] + self.files[:1]) - self.assertRelEqual(args[3:7], order_files) - self.assertRelEqual(args[7:9], [self.files[2], self.files[4]]) - self.assertRelEqual(args[9:11], self.liby_files[:2]) - self.assertRelEqual(args[11:12], [self.libx_files[2]]) - self.assertRelEqual(args[12:14], [self.files[3], self.files[5]]) - self.assertRelEqual(args[14:], [self.liby_files[3]]) +class TestSymbolOrder(unittest.TestCase): + def test_getOrderedSections(self): + '''Test ExpandMoreArgs' _getOrderedSections''' + # Divert subprocess.Popen + subprocess_popen = subprocess.Popen + subprocess.Popen = SubprocessPopen(self) + config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript' + config.OBJ_SUFFIX = '.o' + config.LIB_SUFFIX = '.a' + config.LD_PRINT_ICF_SECTIONS = '' + args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o']) + self.assertEqual(args._getOrderedSections(['_Z6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv']) + self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hot._Z6barbazv']) + subprocess.Popen = subprocess_popen + + def test_getFoldedSections(self): + '''Test ExpandMoreArgs' _getFoldedSections''' + # Divert subprocess.Popen + subprocess_popen = subprocess.Popen + subprocess.Popen = SubprocessPopen(self) + config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections' + args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o']) + self.assertEqual(args._getFoldedSections(), {'.text.hello': '.text.hi', '.text.hi': ['.text.hello']}) + subprocess.Popen = subprocess_popen + + def test_getOrderedSectionsWithICF(self): + '''Test ExpandMoreArgs' _getOrderedSections, with ICF''' + # Divert subprocess.Popen + subprocess_popen = subprocess.Popen + subprocess.Popen = SubprocessPopen(self) + config.EXPAND_LIBS_ORDER_STYLE = 'linkerscript' + config.OBJ_SUFFIX = '.o' + config.LIB_SUFFIX = '.a' + config.LD_PRINT_ICF_SECTIONS = '-Wl,--print-icf-sections' + args = ExpandArgsMore(['foo', '-bar', 'bar.o', 'foo.o']) + self.assertEqual(args._getOrderedSections(['hello', '_Z6barbazv']), ['.text.hi', '.text.hello', '.text.hot._Z6barbazv']) + self.assertEqual(args._getOrderedSections(['_ZThn4_6foobarv', 'hi', '_Z6barbazv']), ['.text._Z6foobarv', '.text._ZThn4_6foobarv', '.text.hi', '.text.hello', '.text.hot._Z6barbazv']) + subprocess.Popen = subprocess_popen + if __name__ == '__main__': unittest.main(testRunner=MozTestRunner())