diff --git a/accessible/src/base/nsAccessiblePivot.cpp b/accessible/src/base/nsAccessiblePivot.cpp index 43e1b6efb99..d0f712b599a 100644 --- a/accessible/src/base/nsAccessiblePivot.cpp +++ b/accessible/src/base/nsAccessiblePivot.cpp @@ -300,47 +300,114 @@ nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary, bool* aResult) *aResult = false; - int32_t oldStart = mStartOffset, oldEnd = mEndOffset; - HyperTextAccessible* text = mPosition->AsHyperText(); - Accessible* oldPosition = mPosition; - while (!text) { - oldPosition = mPosition; - mPosition = mPosition->Parent(); - text = mPosition->AsHyperText(); - } + int32_t tempStart = mStartOffset, tempEnd = mEndOffset; + Accessible* tempPosition = mPosition; + Accessible* root = GetActiveRoot(); + while (true) { + Accessible* curPosition = tempPosition; + HyperTextAccessible* text; + // Find the nearest text node using a preorder traversal starting from + // the current node. + if (!(text = tempPosition->AsHyperText())) { + text = SearchForText(tempPosition, false); + if (!text) + return NS_OK; + if (text != curPosition) + tempStart = tempEnd = -1; + tempPosition = text; + } - if (mEndOffset == -1) - mEndOffset = text != oldPosition ? text->GetChildOffset(oldPosition) : 0; + // If the search led to the parent of the node we started on (e.g. when + // starting on a text leaf), start the text movement from the end of that + // node, otherwise we just default to 0. + if (tempEnd == -1) + tempEnd = text == curPosition->Parent() ? + text->GetChildOffset(curPosition) : 0; - if (mEndOffset == text->CharacterCount()) + // If there's no more text on the current node, try to find the next text + // node; if there isn't one, bail out. + if (tempEnd == text->CharacterCount()) { + if (tempPosition == root) + return NS_OK; + + // If we're currently sitting on a link, try move to either the next + // sibling or the parent, whichever is closer to the current end + // offset. Otherwise, do a forward search for the next node to land on + // (we don't do this in the first case because we don't want to go to the + // subtree). + Accessible* sibling = tempPosition->NextSibling(); + if (tempPosition->IsLink()) { + if (sibling && sibling->IsLink()) { + tempStart = tempEnd = -1; + tempPosition = sibling; + } else { + tempStart = tempPosition->StartOffset(); + tempEnd = tempPosition->EndOffset(); + tempPosition = tempPosition->Parent(); + } + } else { + tempPosition = SearchForText(tempPosition, false); + if (!tempPosition) + return NS_OK; + tempStart = tempEnd = -1; + } + continue; + } + + AccessibleTextBoundary startBoundary, endBoundary; + switch (aBoundary) { + case CHAR_BOUNDARY: + startBoundary = nsIAccessibleText::BOUNDARY_CHAR; + endBoundary = nsIAccessibleText::BOUNDARY_CHAR; + break; + case WORD_BOUNDARY: + startBoundary = nsIAccessibleText::BOUNDARY_WORD_START; + endBoundary = nsIAccessibleText::BOUNDARY_WORD_END; + break; + default: + return NS_ERROR_INVALID_ARG; + } + + nsAutoString unusedText; + int32_t newStart = 0, newEnd = 0, currentEnd = tempEnd; + text->GetTextAtOffset(tempEnd, endBoundary, &newStart, &tempEnd, unusedText); + text->GetTextBeforeOffset(tempEnd, startBoundary, &newStart, &newEnd, + unusedText); + int32_t potentialStart = newEnd == tempEnd ? newStart : newEnd; + tempStart = potentialStart > tempStart ? potentialStart : currentEnd; + + // The offset range we've obtained might have embedded characters in it, + // limit the range to the start of the first occurrence of an embedded + // character. + Accessible* childAtOffset = nullptr; + for (int32_t i = tempStart; i < tempEnd; i++) { + childAtOffset = text->GetChildAtOffset(i); + if (childAtOffset && nsAccUtils::IsEmbeddedObject(childAtOffset)) { + tempEnd = i; + break; + } + } + // If there's an embedded character at the very start of the range, we + // instead want to traverse into it. So restart the movement with + // the child as the starting point. + if (childAtOffset && nsAccUtils::IsEmbeddedObject(childAtOffset) && + tempStart == childAtOffset->StartOffset()) { + tempPosition = childAtOffset; + tempStart = tempEnd = -1; + continue; + } + + *aResult = true; + + Accessible* startPosition = mPosition; + int32_t oldStart = mStartOffset, oldEnd = mEndOffset; + mPosition = tempPosition; + mStartOffset = tempStart; + mEndOffset = tempEnd; + NotifyOfPivotChange(startPosition, oldStart, oldEnd, + nsIAccessiblePivot::REASON_TEXT); return NS_OK; - - AccessibleTextBoundary startBoundary, endBoundary; - switch (aBoundary) { - case CHAR_BOUNDARY: - startBoundary = nsIAccessibleText::BOUNDARY_CHAR; - endBoundary = nsIAccessibleText::BOUNDARY_CHAR; - break; - case WORD_BOUNDARY: - startBoundary = nsIAccessibleText::BOUNDARY_WORD_START; - endBoundary = nsIAccessibleText::BOUNDARY_WORD_END; - break; - default: - return NS_ERROR_INVALID_ARG; } - - nsAutoString unusedText; - int32_t newStart = 0, newEnd = 0; - text->GetTextAtOffset(mEndOffset, endBoundary, &newStart, &mEndOffset, unusedText); - text->GetTextBeforeOffset(mEndOffset, startBoundary, &newStart, &newEnd, - unusedText); - mStartOffset = newEnd == mEndOffset ? newStart : newEnd; - - *aResult = true; - - NotifyOfPivotChange(mPosition, oldStart, oldEnd, - nsIAccessiblePivot::REASON_TEXT); - return NS_OK; } NS_IMETHODIMP @@ -350,52 +417,127 @@ nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary, bool* aResult) *aResult = false; - int32_t oldStart = mStartOffset, oldEnd = mEndOffset; - HyperTextAccessible* text = mPosition->AsHyperText(); - Accessible* oldPosition = mPosition; - while (!text) { - oldPosition = mPosition; - mPosition = mPosition->Parent(); - text = mPosition->AsHyperText(); - } + int32_t tempStart = mStartOffset, tempEnd = mEndOffset; + Accessible* tempPosition = mPosition; + Accessible* root = GetActiveRoot(); + while (true) { + Accessible* curPosition = tempPosition; + HyperTextAccessible* text; + // Find the nearest text node using a reverse preorder traversal starting + // from the current node. + if (!(text = tempPosition->AsHyperText())) { + text = SearchForText(tempPosition, true); + if (!text) + return NS_OK; + if (text != curPosition) + tempStart = tempEnd = -1; + tempPosition = text; + } - if (mStartOffset == -1) - mStartOffset = text != oldPosition ? text->GetChildOffset(oldPosition) : 0; + // If the search led to the parent of the node we started on (e.g. when + // starting on a text leaf), start the text movement from the end of that + // node, otherwise we just default to 0. + if (tempStart == -1) { + if (tempPosition != curPosition) + tempStart = text == curPosition->Parent() ? + text->GetChildOffset(curPosition) : text->CharacterCount(); + else + tempStart = 0; + } - if (mStartOffset == 0) + // If there's no more text on the current node, try to find the previous + // text node; if there isn't one, bail out. + if (tempStart == 0) { + if (tempPosition == root) + return NS_OK; + + // If we're currently sitting on a link, try move to either the previous + // sibling or the parent, whichever is closer to the current end + // offset. Otherwise, do a forward search for the next node to land on + // (we don't do this in the first case because we don't want to go to the + // subtree). + Accessible* sibling = tempPosition->PrevSibling(); + if (tempPosition->IsLink()) { + if (sibling && sibling->IsLink()) { + HyperTextAccessible* siblingText = sibling->AsHyperText(); + tempStart = tempEnd = siblingText ? + siblingText->CharacterCount() : -1; + tempPosition = sibling; + } else { + tempStart = tempPosition->StartOffset(); + tempEnd = tempPosition->EndOffset(); + tempPosition = tempPosition->Parent(); + } + } else { + HyperTextAccessible* tempText = SearchForText(tempPosition, true); + if (!tempText) + return NS_OK; + tempPosition = tempText; + tempStart = tempEnd = tempText->CharacterCount(); + } + continue; + } + + AccessibleTextBoundary startBoundary, endBoundary; + switch (aBoundary) { + case CHAR_BOUNDARY: + startBoundary = nsIAccessibleText::BOUNDARY_CHAR; + endBoundary = nsIAccessibleText::BOUNDARY_CHAR; + break; + case WORD_BOUNDARY: + startBoundary = nsIAccessibleText::BOUNDARY_WORD_START; + endBoundary = nsIAccessibleText::BOUNDARY_WORD_END; + break; + default: + return NS_ERROR_INVALID_ARG; + } + + nsAutoString unusedText; + int32_t newStart = 0, newEnd = 0, currentStart = tempStart, potentialEnd = 0; + text->GetTextBeforeOffset(tempStart, startBoundary, &newStart, &newEnd, + unusedText); + if (newStart < tempStart) + tempStart = newEnd >= currentStart ? newStart : newEnd; + else // XXX: In certain odd cases newStart is equal to tempStart + text->GetTextBeforeOffset(tempStart - 1, startBoundary, &newStart, + &tempStart, unusedText); + text->GetTextAtOffset(tempStart, endBoundary, &newStart, &potentialEnd, + unusedText); + tempEnd = potentialEnd < tempEnd ? potentialEnd : currentStart; + + // The offset range we've obtained might have embedded characters in it, + // limit the range to the start of the last occurrence of an embedded + // character. + Accessible* childAtOffset = nullptr; + for (int32_t i = tempEnd - 1; i >= tempStart; i--) { + childAtOffset = text->GetChildAtOffset(i); + if (childAtOffset && nsAccUtils::IsEmbeddedObject(childAtOffset)) { + tempStart = childAtOffset->EndOffset(); + break; + } + } + // If there's an embedded character at the very end of the range, we + // instead want to traverse into it. So restart the movement with + // the child as the starting point. + if (childAtOffset && nsAccUtils::IsEmbeddedObject(childAtOffset) && + tempEnd == childAtOffset->EndOffset()) { + tempPosition = childAtOffset; + tempStart = tempEnd = childAtOffset->AsHyperText()->CharacterCount(); + continue; + } + + *aResult = true; + + Accessible* startPosition = mPosition; + int32_t oldStart = mStartOffset, oldEnd = mEndOffset; + mPosition = tempPosition; + mStartOffset = tempStart; + mEndOffset = tempEnd; + + NotifyOfPivotChange(startPosition, oldStart, oldEnd, + nsIAccessiblePivot::REASON_TEXT); return NS_OK; - - AccessibleTextBoundary startBoundary, endBoundary; - switch (aBoundary) { - case CHAR_BOUNDARY: - startBoundary = nsIAccessibleText::BOUNDARY_CHAR; - endBoundary = nsIAccessibleText::BOUNDARY_CHAR; - break; - case WORD_BOUNDARY: - startBoundary = nsIAccessibleText::BOUNDARY_WORD_START; - endBoundary = nsIAccessibleText::BOUNDARY_WORD_END; - break; - default: - return NS_ERROR_INVALID_ARG; } - - nsAutoString unusedText; - int32_t newStart = 0, newEnd = 0; - text->GetTextBeforeOffset(mStartOffset, startBoundary, &newStart, &newEnd, - unusedText); - if (newStart < mStartOffset) - mStartOffset = newEnd == mStartOffset ? newStart : newEnd; - else // XXX: In certain odd cases newStart is equal to mStartOffset - text->GetTextBeforeOffset(mStartOffset - 1, startBoundary, &newStart, - &mStartOffset, unusedText); - text->GetTextAtOffset(mStartOffset, endBoundary, &newStart, &mEndOffset, - unusedText); - - *aResult = true; - - NotifyOfPivotChange(mPosition, oldStart, oldEnd, - nsIAccessiblePivot::REASON_TEXT); - return NS_OK; } NS_IMETHODIMP @@ -636,6 +778,48 @@ nsAccessiblePivot::SearchForward(Accessible* aAccessible, return nullptr; } +HyperTextAccessible* +nsAccessiblePivot::SearchForText(Accessible* aAccessible, bool aBackward) +{ + Accessible* root = GetActiveRoot(); + Accessible* accessible = aAccessible; + while (true) { + Accessible* child = nullptr; + + while ((child = (aBackward ? accessible->LastChild() : + accessible->FirstChild()))) { + accessible = child; + if (child->IsHyperText()) + return child->AsHyperText(); + } + + Accessible* sibling = nullptr; + Accessible* temp = accessible; + do { + if (temp == root) + break; + + if (temp != aAccessible && temp->IsHyperText()) + return temp->AsHyperText(); + + sibling = aBackward ? temp->PrevSibling() : temp->NextSibling(); + + if (sibling) + break; + } while ((temp = temp->Parent())); + + if (!sibling) + break; + + accessible = sibling; + if (accessible->IsHyperText()) + return accessible->AsHyperText(); + } + + return nullptr; +} + + bool nsAccessiblePivot::NotifyOfPivotChange(Accessible* aOldPosition, int32_t aOldStart, int32_t aOldEnd, diff --git a/accessible/src/base/nsAccessiblePivot.h b/accessible/src/base/nsAccessiblePivot.h index 0c578c72a42..e6d884df5cf 100644 --- a/accessible/src/base/nsAccessiblePivot.h +++ b/accessible/src/base/nsAccessiblePivot.h @@ -72,6 +72,12 @@ private: bool aSearchCurrent, nsresult* aResult); + /* + * Search in preorder for the first text accessible. + */ + mozilla::a11y::HyperTextAccessible* SearchForText(Accessible* aAccessible, + bool aBackward); + /* * Get the effective root for this pivot, either the true root or modal root. */ diff --git a/accessible/src/generic/HyperTextAccessible.cpp b/accessible/src/generic/HyperTextAccessible.cpp index eea26770480..a6d20d7abaa 100644 --- a/accessible/src/generic/HyperTextAccessible.cpp +++ b/accessible/src/generic/HyperTextAccessible.cpp @@ -593,14 +593,8 @@ HyperTextAccessible::DOMPointToHypertextOffset(nsINode* aNode, // addTextOffset, to put us after the embedded object char. We'll only treat the offset as // before the embedded object char if we end at the very beginning of the child. addTextOffset = addTextOffset > 0; - } - else { - // Start offset, inclusive - // Make sure the offset lands on the embedded object character in order to indicate - // the true inner offset is inside the subtree for that link - addTextOffset = - (nsAccUtils::TextLength(descendantAcc) == addTextOffset) ? 1 : 0; - } + } else + addTextOffset = 0; descendantAcc = parentAcc; } diff --git a/accessible/src/jsat/AccessFu.jsm b/accessible/src/jsat/AccessFu.jsm index 0edb9a24515..001a7882eb0 100644 --- a/accessible/src/jsat/AccessFu.jsm +++ b/accessible/src/jsat/AccessFu.jsm @@ -19,6 +19,8 @@ const ACCESSFU_DISABLE = 0; const ACCESSFU_ENABLE = 1; const ACCESSFU_AUTO = 2; +const SCREENREADER_SETTING = 'accessibility.screenreader'; + this.AccessFu = { /** * Initialize chrome-layer accessibility functionality. @@ -35,8 +37,15 @@ this.AccessFu = { Services.obs.addObserver(this, 'Accessibility:Settings', false); } catch (x) { // Not on Android - if (Utils.MozBuildApp === 'b2g') { - aWindow.addEventListener('ContentStart', this, false); + if (aWindow.navigator.mozSettings) { + let lock = aWindow.navigator.mozSettings.createLock(); + let req = lock.get(SCREENREADER_SETTING); + req.addEventListener('success', () => { + this._systemPref = req.result[SCREENREADER_SETTING]; + this._enableOrDisable(); + }); + aWindow.navigator.mozSettings.addObserver( + SCREENREADER_SETTING, this.handleEvent.bind(this)); } } @@ -56,10 +65,9 @@ this.AccessFu = { } if (Utils.MozBuildApp === 'mobile/android') { Services.obs.removeObserver(this, 'Accessibility:Settings'); - } else if (Utils.MozBuildApp === 'b2g') { - Utils.win.shell.contentBrowser.contentWindow.removeEventListener( - 'mozContentEvent', this); - Utils.win.removeEventListener('ContentStart', this); + } else if (Utils.win.navigator.mozSettings) { + Utils.win.navigator.mozSettings.removeObserver( + SCREENREADER_SETTING, this.handleEvent.bind(this)); } delete this._activatePref; Utils.uninit(); @@ -308,20 +316,6 @@ this.AccessFu = { handleEvent: function handleEvent(aEvent) { switch (aEvent.type) { - case 'ContentStart': - { - Utils.win.shell.contentBrowser.contentWindow.addEventListener( - 'mozContentEvent', this, false, true); - break; - } - case 'mozContentEvent': - { - if (aEvent.detail.type == 'accessibility-screenreader') { - this._systemPref = aEvent.detail.enabled; - this._enableOrDisable(); - } - break; - } case 'TabOpen': { let mm = Utils.getMessageManager(aEvent.target); @@ -351,6 +345,15 @@ this.AccessFu = { } break; } + default: + { + // A settings change, it does not have an event type + if (aEvent.settingName == SCREENREADER_SETTING) { + this._systemPref = aEvent.settingValue; + this._enableOrDisable(); + } + break; + } } }, diff --git a/accessible/tests/mochitest/events.js b/accessible/tests/mochitest/events.js index 112b1c482fb..264f168c19b 100644 --- a/accessible/tests/mochitest/events.js +++ b/accessible/tests/mochitest/events.js @@ -1541,10 +1541,12 @@ function moveToTextStart(aID) /** * Move the caret in text accessible. */ -function moveCaretToDOMPoint(aID, aNode, aOffset, aExpectedOffset, - aFocusTargetID) +function moveCaretToDOMPoint(aID, aDOMPointNodeID, aDOMPointOffset, + aExpectedOffset, aFocusTargetID, + aCheckFunc) { this.target = getAccessible(aID, [nsIAccessibleText]); + this.DOMPointNode = getNode(aDOMPointNodeID); this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null; this.focusNode = this.focus ? this.focus.DOMNode : null; @@ -1553,13 +1555,25 @@ function moveCaretToDOMPoint(aID, aNode, aOffset, aExpectedOffset, if (this.focusNode) this.focusNode.focus(); - window.getSelection().getRangeAt(0).setStart(aNode, aOffset); + var selection = this.DOMPointNode.ownerDocument.defaultView.getSelection(); + var selRange = selection.getRangeAt(0); + selRange.setStart(this.DOMPointNode, aDOMPointOffset); + selRange.collapse(true); + + selection.removeRange(selRange); + selection.addRange(selRange); } this.getID = function moveCaretToDOMPoint_getID() { return "Set caret on " + prettyName(aID) + " at point: " + - prettyName(aNode) + " node with offset " + aOffset; + prettyName(aDOMPointNodeID) + " node with offset " + aDOMPointOffset; + } + + this.finalCheck = function moveCaretToDOMPoint_finalCheck() + { + if (aCheckFunc) + aCheckFunc.call(); } this.eventSeq = [ diff --git a/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html b/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html index afcc0b92339..aba87bbd8a7 100644 --- a/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html +++ b/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html @@ -5,8 +5,25 @@ +
This is the very beginning.

- This is the test of text. + This is the test of text.

+
A multiword link is here. We will traverse
+
into, out, and between the subtrees.
+

Singularity.

+ + + + + + + + + +
Magicalunicorns
and wizardsreally exist.
+
Endless fun!
+

Objectsadjacentto eachother should be separate.

+
End!
diff --git a/accessible/tests/mochitest/pivot/test_virtualcursor_text.html b/accessible/tests/mochitest/pivot/test_virtualcursor_text.html index 4ef48490c6d..de42d7da71c 100644 --- a/accessible/tests/mochitest/pivot/test_virtualcursor_text.html +++ b/accessible/tests/mochitest/pivot/test_virtualcursor_text.html @@ -38,7 +38,6 @@ gQueue.push(new setVCPosInvoker(docAcc, null, null, getAccessible(doc.getElementById('paragraph-1')))); - gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,4], getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText))); gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', CHAR_BOUNDARY, [4,5], @@ -47,15 +46,170 @@ getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText))); gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [5,7], getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText))); - gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [8,9], - getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,3], + getAccessible(doc.getElementById('p1-link-1'), nsIAccessibleText))); gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [10,14], getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText))); - gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [8,9], - getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,3], + getAccessible(doc.getElementById('p1-link-1'), nsIAccessibleText))); gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [5,7], getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText))); + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById('section-1')))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,1], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,9], + getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [10,14], + getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [4,6], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [7,12], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,2], + getAccessible(doc.getElementById('s1-link-2'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [15,19], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [20,28], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,5], + getAccessible(doc.getElementById('section-2'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [6,10], + getAccessible(doc.getElementById('section-2'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,5], + getAccessible(doc.getElementById('section-2'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [20,28], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [15,19], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,2], + getAccessible(doc.getElementById('s1-link-2'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [7,12], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [4,6], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [10,14], + getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,9], + getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,1], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById('s1-link-1')))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', CHAR_BOUNDARY, [1,2], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', CHAR_BOUNDARY, [0,1], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', CHAR_BOUNDARY, [1,2], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', CHAR_BOUNDARY, [0,1], + getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', CHAR_BOUNDARY, [1,2], + getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [2,9], + getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [10,14], + getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', CHAR_BOUNDARY, [3,4], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', CHAR_BOUNDARY, [13,14], + getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText))); + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById('section-2')))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', CHAR_BOUNDARY, [27,28], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', CHAR_BOUNDARY, [0,1], + getAccessible(doc.getElementById('section-2'), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById('paragraph-2')))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,12], + getAccessible(doc.getElementById('paragraph-2'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,7], + getAccessible(doc.getElementById('cell-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,8], + getAccessible(doc.getElementById('cell-2'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,3], + getAccessible(doc.getElementById('cell-3'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [4,11], + getAccessible(doc.getElementById('cell-3'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,6], + getAccessible(doc.getElementById('cell-4'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [7,13], + getAccessible(doc.getElementById('cell-4'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,7], + getAccessible(doc.getElementById('section-3'), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById('section-3')))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,7], + getAccessible(doc.getElementById('section-3'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [7,13], + getAccessible(doc.getElementById('cell-4'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,6], + getAccessible(doc.getElementById('cell-4'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [4,11], + getAccessible(doc.getElementById('cell-3'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,3], + getAccessible(doc.getElementById('cell-3'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,8], + getAccessible(doc.getElementById('cell-2'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,7], + getAccessible(doc.getElementById('cell-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,12], + getAccessible(doc.getElementById('paragraph-2'), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById('paragraph-3')))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,7], + getAccessible(doc.getElementById('paragraph-3'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,8], + getAccessible(doc.getElementById('p3-link-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [8,10], + getAccessible(doc.getElementById('paragraph-3'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,4], + getAccessible(doc.getElementById('p3-link-2'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,5], + getAccessible(doc.getElementById('p3-link-3'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [14,20], + getAccessible(doc.getElementById('paragraph-3'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,5], + getAccessible(doc.getElementById('p3-link-3'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,4], + getAccessible(doc.getElementById('p3-link-2'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [8,10], + getAccessible(doc.getElementById('paragraph-3'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,8], + getAccessible(doc.getElementById('p3-link-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,7], + getAccessible(doc.getElementById('paragraph-3'), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById('s1-link-2')))); + // Start with the pivot in the middle of the paragraph + gQueue.push(new setVCPosInvoker(docAcc, "moveNext", ObjectTraversalRule, " will traverse")); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [15,19], + getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,2], + getAccessible(doc.getElementById('s1-link-2'), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById('end-block')))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,4], + getAccessible(doc.getElementById('end-block'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, null, false)); + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById('start-block')))); + gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,4], + getAccessible(doc.getElementById('start-block'), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, null, false)); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, null, false)); + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById('start-block')))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, null, false)); + gQueue.invoke(); } diff --git a/accessible/tests/mochitest/textcaret/test_general.html b/accessible/tests/mochitest/textcaret/test_general.html index c2b538d32af..69f83959f18 100644 --- a/accessible/tests/mochitest/textcaret/test_general.html +++ b/accessible/tests/mochitest/textcaret/test_general.html @@ -35,6 +35,29 @@ "Wrong caret offset for " + aID); } + function testCaretOffsets(aList) + { + for (var i = 0; i < aList.length; i++) + testCaretOffset(aList[0][0], aList[0][1]); + } + + function queueTraversalList(aList, aFocusNode) + { + for (var i = 0 ; i < aList.length; i++) { + var node = aList[i].DOMPoint[0]; + var nodeOffset = aList[i].DOMPoint[1]; + + var textAcc = aList[i].point[0]; + var textOffset = aList[i].point[1]; + var textList = aList[i].pointList; + var invoker = + new moveCaretToDOMPoint(textAcc, node, nodeOffset, textOffset, + ((i == 0) ? aFocusNode : null), + testCaretOffsets.bind(null, textList)) + gQueue.push(invoker); + } + } + /** * Do tests. */ @@ -48,7 +71,7 @@ turnCaretBrowsing(true); // test caret offsets - testCaretOffset(document, 14); + testCaretOffset(document, 16); testCaretOffset("textbox", -1); testCaretOffset("textarea", -1); testCaretOffset("p", -1); @@ -59,6 +82,49 @@ gQueue.push(new setCaretOffset("textbox", 1, "textbox")); gQueue.push(new setCaretOffset("link", 1, "link")); gQueue.push(new setCaretOffset("heading", 1, document)); + + // a*b*c + var p2Doc = getNode("p2_container").contentDocument; + var traversalList = [ + { // before 'a' + DOMPoint: [ getNode("p2", p2Doc).firstChild, 0 ], + point: [ getNode("p2", p2Doc), 0 ], + pointList: [ [ p2Doc, 0 ] ] + }, + { // after 'a' (before anchor) + DOMPoint: [ getNode("p2", p2Doc).firstChild, 1 ], + point: [ getNode("p2", p2Doc), 1 ], + pointList: [ [ p2Doc, 0 ] ] + }, + { // before 'b' (inside anchor) + DOMPoint: [ getNode("p2_a", p2Doc).firstChild, 0 ], + point: [ getNode("p2_a", p2Doc), 0 ], + pointList: [ + [ getNode("p2", p2Doc), 1 ], + [ p2Doc, 0 ] + ] + }, + { // after 'b' (inside anchor) + DOMPoint: [ getNode("p2_a", p2Doc).firstChild, 1 ], + point: [ getNode("p2_a", p2Doc), 1 ], + pointList: [ + [ getNode("p2", p2Doc), 1 ] , + [ p2Doc, 0 ] + ] + }, + { // before 'c' (after anchor) + DOMPoint: [ getNode("p2", p2Doc).lastChild, 0 ], + point: [ getNode("p2", p2Doc), 2 ], + pointList: [ [ p2Doc, 0 ] ] + }, + { // after 'c' + DOMPoint: [ getNode("p2", p2Doc).lastChild, 1 ], + point: [ getNode("p2", p2Doc), 3 ], + pointList: [ [ p2Doc, 0 ] ] + } + ]; + queueTraversalList(traversalList, getNode("p2", p2Doc)); + gQueue.onFinish = function() { turnCaretBrowsing(false); @@ -77,22 +143,27 @@ - Mozilla Bug 448744 + Bug 448744 - Mozilla Bug 524115 + Bug 524115 - Mozilla Bug 546068 + Bug 546068 + + + Bug 672717 - Mozilla Bug 725581 + Bug 725581

@@ -104,6 +175,8 @@

text
text

about mozilla
heading
+
diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index 04d50fdef88..6df7c1c7b91 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -135,6 +135,11 @@ pref("browser.search.suggest.enabled", true); // tell the search service that we don't really expose the "current engine" pref("browser.search.noCurrentEngine", true); +// Enable sparse localization by setting a few package locale overrides +pref("chrome.override_package.global", "b2g-l10n"); +pref("chrome.override_package.mozapps", "b2g-l10n"); +pref("chrome.override_package.passwordmgr", "b2g-l10n"); + // enable xul error pages pref("browser.xul.error_pages.enabled", true); diff --git a/b2g/locales/Makefile.in b/b2g/locales/Makefile.in index 8366e4340bd..b3b1f85b222 100644 --- a/b2g/locales/Makefile.in +++ b/b2g/locales/Makefile.in @@ -74,7 +74,6 @@ libs-%: # Tailored target to just add the chrome processing for multi-locale builds chrome-%: - @$(MAKE) -C ../../toolkit/locales chrome-$* @$(MAKE) chrome AB_CD=$* @$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales chrome AB_CD=$* diff --git a/b2g/locales/jar.mn b/b2g/locales/jar.mn index d9397c8c5e1..72a443723c5 100644 --- a/b2g/locales/jar.mn +++ b/b2g/locales/jar.mn @@ -13,3 +13,61 @@ * locale/@AB_CD@/b2g-l10n/netError.dtd (%chrome/overrides/netError.dtd) * locale/@AB_CD@/b2g-l10n/aboutCertError.dtd (%chrome/overrides/aboutCertError.dtd) * locale/@AB_CD@/b2g-l10n/appstrings.properties (%chrome/overrides/appstrings.properties) + + +# overrides for toolkit l10n, also for en-US +relativesrcdir toolkit/locales: + locale/@AB_CD@/b2g-l10n/overrides/about.dtd (%chrome/global/about.dtd) + locale/@AB_CD@/b2g-l10n/overrides/aboutAbout.dtd (%chrome/global/aboutAbout.dtd) + locale/@AB_CD@/b2g-l10n/overrides/aboutRights.dtd (%chrome/global/aboutRights.dtd) + locale/@AB_CD@/b2g-l10n/overrides/commonDialogs.properties (%chrome/global/commonDialogs.properties) + locale/@AB_CD@/b2g-l10n/overrides/handling/handling.properties (%chrome/mozapps/handling/handling.properties) + locale/@AB_CD@/b2g-l10n/overrides/intl.properties (%chrome/global/intl.properties) + locale/@AB_CD@/b2g-l10n/overrides/intl.css (%chrome/global/intl.css) + locale/@AB_CD@/b2g-l10n/overrides/passwordmgr.properties (%chrome/passwordmgr/passwordmgr.properties) + locale/@AB_CD@/b2g-l10n/overrides/search/search.properties (%chrome/search/search.properties) + locale/@AB_CD@/b2g-l10n/overrides/update/updates.properties (%chrome/mozapps/update/updates.properties) +# about:support + locale/@AB_CD@/b2g-l10n/overrides/global/aboutSupport.dtd (%chrome/global/aboutSupport.dtd) + locale/@AB_CD@/b2g-l10n/overrides/global/aboutSupport.properties (%chrome/global/aboutSupport.properties) +#about:crashes + locale/@AB_CD@/b2g-l10n/overrides/crashreporter/crashes.dtd (%crashreporter/crashes.dtd) + locale/@AB_CD@/b2g-l10n/overrides/crashreporter/crashes.properties (%crashreporter/crashes.properties) +#about:mozilla + locale/@AB_CD@/b2g-l10n/overrides/global/mozilla.dtd (%chrome/global/mozilla.dtd) +#about:telemetry + locale/@AB_CD@/b2g-l10n/overrides/global/aboutTelemetry.dtd (%chrome/global/aboutTelemetry.dtd) + locale/@AB_CD@/b2g-l10n/overrides/global/aboutTelemetry.properties (%chrome/global/aboutTelemetry.properties) + +% override chrome://global/locale/about.dtd chrome://b2g-l10n/locale/overrides/about.dtd +% override chrome://global/locale/aboutAbout.dtd chrome://b2g-l10n/locale/overrides/aboutAbout.dtd +% override chrome://global/locale/aboutRights.dtd chrome://b2g-l10n/locale/overrides/aboutRights.dtd +% override chrome://global/locale/commonDialogs.properties chrome://b2g-l10n/locale/overrides/commonDialogs.properties +% override chrome://mozapps/locale/handling/handling.properties chrome://b2g-l10n/locale/overrides/handling/handling.properties +% override chrome://global/locale/intl.properties chrome://b2g-l10n/locale/overrides/intl.properties +% override chrome://global/locale/intl.css chrome://b2g-l10n/locale/overrides/intl.css +% override chrome://passwordmgr/locale/passwordmgr.properties chrome://b2g-l10n/locale/overrides/passwordmgr/passwordmgr.properties +% override chrome://global/locale/search/search.properties chrome://b2g-l10n/locale/overrides/search/search.properties +% override chrome://mozapps/locale/update/updates.properties chrome://b2g-l10n/locale/overrides/update/updates.properties +% override chrome://global/locale/aboutSupport.dtd chrome://b2g-l10n/locale/overrides/global/aboutSupport.dtd +% override chrome://global/locale/aboutSupport.properties chrome://b2g-l10n/locale/overrides/global/aboutSupport.properties +% override chrome://global/locale/crashes.dtd chrome://b2g-l10n/locale/overrides/crashreporter/crashes.dtd +% override chrome://global/locale/crashes.properties chrome://b2g-l10n/locale/overrides/crashreporter/crashes.properties +% override chrome://global/locale/mozilla.dtd chrome://b2g-l10n/locale/overrides/global/mozilla.dtd +% override chrome://global/locale/aboutTelemetry.dtd chrome://b2g-l10n/locale/overrides/global/aboutTelemetry.dtd +% override chrome://global/locale/aboutTelemetry.properties chrome://b2g-l10n/locale/overrides/global/aboutTelemetry.properties + +# overrides for dom l10n, also for en-US +relativesrcdir dom/locales: + locale/@AB_CD@/b2g-l10n/overrides/charsetTitles.properties (%chrome/charsetTitles.properties) + locale/@AB_CD@/b2g-l10n/overrides/global.dtd (%chrome/global.dtd) + locale/@AB_CD@/b2g-l10n/overrides/AccessFu.properties (%chrome/accessibility/AccessFu.properties) + locale/@AB_CD@/b2g-l10n/overrides/dom/dom.properties (%chrome/dom/dom.properties) +#about:plugins + locale/@AB_CD@/b2g-l10n/overrides/plugins.properties (%chrome/plugins.properties) + +% override chrome://global/locale/charsetTitles.properties chrome://b2g-l10n/locale/overrides/charsetTitles.properties +% override chrome://global/locale/global.dtd chrome://b2g-l10n/locale/overrides/global.dtd +% override chrome://global/locale/AccessFu.properties chrome://b2g-l10n/locale/overrides/AccessFu.properties +% override chrome://global/locale/dom/dom.properties chrome://b2g-l10n/locale/overrides/dom/dom.properties +% override chrome://global/locale/plugins.properties chrome://b2g-l10n/locale/overrides/plugins.properties diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 8ab3b95ade3..552fc8a826c 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -1119,18 +1119,25 @@ var gBrowserInit = { // If the user manually opens the download manager before the timeout, the // downloads will start right away, and getting the service again won't hurt. setTimeout(function() { - let DownloadsCommon = - Cu.import("resource:///modules/DownloadsCommon.jsm", {}).DownloadsCommon; - if (DownloadsCommon.useJSTransfer) { - // Open the data link without initalizing nsIDownloadManager. - DownloadsCommon.initializeAllDataLinks(); - } else { - // Initalizing nsIDownloadManager will trigger the data link. - Services.downloads; + try { + let DownloadsCommon = + Cu.import("resource:///modules/DownloadsCommon.jsm", {}).DownloadsCommon; + if (DownloadsCommon.useJSTransfer) { + // Open the data link without initalizing nsIDownloadManager. + DownloadsCommon.initializeAllDataLinks(); + let DownloadsTaskbar = + Cu.import("resource:///modules/DownloadsTaskbar.jsm", {}).DownloadsTaskbar; + DownloadsTaskbar.registerIndicator(window); + } else { + // Initalizing nsIDownloadManager will trigger the data link. + Services.downloads; + let DownloadTaskbarProgress = + Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress; + DownloadTaskbarProgress.onBrowserWindowLoad(window); + } + } catch (ex) { + Cu.reportError(ex); } - let DownloadTaskbarProgress = - Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress; - DownloadTaskbarProgress.onBrowserWindowLoad(window); }, 10000); // The object handling the downloads indicator is also initialized here in the diff --git a/browser/components/downloads/src/DownloadsTaskbar.jsm b/browser/components/downloads/src/DownloadsTaskbar.jsm new file mode 100644 index 00000000000..18a9fd245b4 --- /dev/null +++ b/browser/components/downloads/src/DownloadsTaskbar.jsm @@ -0,0 +1,180 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */ +/* 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/. */ + +/** + * Handles the download progress indicator in the taskbar. + */ + +"use strict"; + +this.EXPORTED_SYMBOLS = [ + "DownloadsTaskbar", +]; + +//////////////////////////////////////////////////////////////////////////////// +//// Globals + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; +const Cr = Components.results; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Downloads", + "resource://gre/modules/Downloads.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", + "resource:///modules/RecentWindow.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyGetter(this, "gWinTaskbar", function () { + if (!("@mozilla.org/windows-taskbar;1" in Cc)) { + return null; + } + let winTaskbar = Cc["@mozilla.org/windows-taskbar;1"] + .getService(Ci.nsIWinTaskbar); + return winTaskbar.available && winTaskbar; +}); + +XPCOMUtils.defineLazyGetter(this, "gMacTaskbarProgress", function () { + return ("@mozilla.org/widget/macdocksupport;1" in Cc) && + Cc["@mozilla.org/widget/macdocksupport;1"] + .getService(Ci.nsITaskbarProgress); +}); + +//////////////////////////////////////////////////////////////////////////////// +//// DownloadsTaskbar + +/** + * Handles the download progress indicator in the taskbar. + */ +this.DownloadsTaskbar = { + /** + * Underlying DownloadSummary providing the aggregate download information, or + * null if the indicator has never been initialized. + */ + _summary: null, + + /** + * nsITaskbarProgress object to which download information is dispatched. + * This can be null if the indicator has never been initialized or if the + * indicator is currently hidden on Windows. + */ + _taskbarProgress: null, + + /** + * This method is called after a new browser window is opened, and ensures + * that the download progress indicator is displayed in the taskbar. + * + * On Windows, the indicator is attached to the first browser window that + * calls this method. When the window is closed, the indicator is moved to + * another browser window, if available, in no particular order. When there + * are no browser windows visible, the indicator is hidden. + * + * On Mac OS X, the indicator is initialized globally when this method is + * called for the first time. Subsequent calls have no effect. + * + * @param aBrowserWindow + * nsIDOMWindow object of the newly opened browser window to which the + * indicator may be attached. + */ + registerIndicator: function (aBrowserWindow) + { + if (!this._taskbarProgress) { + if (gMacTaskbarProgress) { + // On Mac OS X, we have to register the global indicator only once. + this._taskbarProgress = gMacTaskbarProgress; + // Free the XPCOM reference on shutdown, to prevent detecting a leak. + Services.obs.addObserver(() => { + this._taskbarProgress = null; + gMacTaskbarProgress = null; + }, "quit-application-granted", false); + } else if (gWinTaskbar) { + // On Windows, the indicator is currently hidden because we have no + // previous browser window, thus we should attach the indicator now. + this._attachIndicator(aBrowserWindow); + } else { + // The taskbar indicator is not available on this platform. + return; + } + } + + // Ensure that the DownloadSummary object will be created asynchronously. + if (!this._summary) { + Downloads.getSummary(Downloads.ALL).then(summary => { + // In case the method is re-entered, we simply ignore redundant + // invocations of the callback, instead of keeping separate state. + if (this._summary) { + return; + } + this._summary = summary; + return this._summary.addView(this); + }).then(null, Cu.reportError); + } + }, + + /** + * On Windows, attaches the taskbar indicator to the specified browser window. + */ + _attachIndicator: function (aWindow) + { + // Activate the indicator on the specified window. + let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIXULWindow).docShell; + this._taskbarProgress = gWinTaskbar.getTaskbarProgress(docShell); + + // If the DownloadSummary object has already been created, we should update + // the state of the new indicator, otherwise it will be updated as soon as + // the DownloadSummary view is registered. + if (this._summary) { + this.onSummaryChanged(); + } + + aWindow.addEventListener("unload", () => { + // Locate another browser window, excluding the one being closed. + let browserWindow = RecentWindow.getMostRecentBrowserWindow(); + if (browserWindow) { + // Move the progress indicator to the other browser window. + this._attachIndicator(browserWindow); + } else { + // The last browser window has been closed. We remove the reference to + // the taskbar progress object so that the indicator will be registered + // again on the next browser window that is opened. + this._taskbarProgress = null; + } + }, false); + }, + + ////////////////////////////////////////////////////////////////////////////// + //// DownloadSummary view + + onSummaryChanged: function () + { + // If the last browser window has been closed, we have no indicator anymore. + if (!this._taskbarProgress) { + return; + } + + if (this._summary.allHaveStopped || this._summary.progressTotalBytes == 0) { + this._taskbarProgress.setProgressState( + Ci.nsITaskbarProgress.STATE_NO_PROGRESS, 0, 0); + } else { + // For a brief moment before completion, some download components may + // report more transferred bytes than the total number of bytes. Thus, + // ensure that we never break the expectations of the progress indicator. + let progressCurrentBytes = Math.min(this._summary.progressTotalBytes, + this._summary.progressCurrentBytes); + this._taskbarProgress.setProgressState( + Ci.nsITaskbarProgress.STATE_NORMAL, + progressCurrentBytes, + this._summary.progressTotalBytes); + } + }, +}; diff --git a/browser/components/downloads/src/moz.build b/browser/components/downloads/src/moz.build index 2a3a4973dc5..f7b6c7e686c 100644 --- a/browser/components/downloads/src/moz.build +++ b/browser/components/downloads/src/moz.build @@ -13,5 +13,6 @@ EXTRA_COMPONENTS += [ EXTRA_JS_MODULES += [ 'DownloadsCommon.jsm', 'DownloadsLogger.jsm', + 'DownloadsTaskbar.jsm', ] diff --git a/browser/components/preferences/in-content/main.xul b/browser/components/preferences/in-content/main.xul index 1e3460772b6..f960ea71579 100644 --- a/browser/components/preferences/in-content/main.xul +++ b/browser/components/preferences/in-content/main.xul @@ -215,34 +215,27 @@ accesskey="&newWindowsAsTabs.accesskey;" preference="browser.link.open_newwindow" onsyncfrompreference="return gMainPane.readLinkTarget();" - onsynctopreference="return gMainPane.writeLinkTarget();" - class="indent"/> + onsynctopreference="return gMainPane.writeLinkTarget();"/> + preference="browser.tabs.warnOnClose"/> + preference="browser.tabs.warnOnOpen"/> + preference="browser.sessionstore.restore_on_demand"/> + preference="browser.tabs.loadInBackground"/> #ifdef XP_WIN + preference="browser.taskbar.previews.enable"/> #endif - diff --git a/browser/devtools/debugger/test/Makefile.in b/browser/devtools/debugger/test/Makefile.in index 275083ae5cd..de15fad5367 100644 --- a/browser/devtools/debugger/test/Makefile.in +++ b/browser/devtools/debugger/test/Makefile.in @@ -22,6 +22,7 @@ MOCHITEST_BROWSER_TESTS = \ browser_dbg_breakpoints-pane.js \ browser_dbg_chrome-debugging.js \ browser_dbg_clean-exit.js \ + browser_dbg_clean-exit-window.js \ browser_dbg_cmd-blackbox.js \ browser_dbg_cmd-break.js \ browser_dbg_cmd-dbg.js \ diff --git a/browser/devtools/debugger/test/browser_dbg_clean-exit-window.js b/browser/devtools/debugger/test/browser_dbg_clean-exit-window.js new file mode 100644 index 00000000000..aa0afc2acf0 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_clean-exit-window.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that closing a window with the debugger in a paused state exits cleanly. + */ + +let gDebuggee, gPanel, gDebugger, gWindow; + +const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html"; + +function test() { + addWindow(TAB_URL) + .then(win => initDebugger(TAB_URL, win)) + .then(([aTab, aDebuggee, aPanel, aWindow]) => { + gDebuggee = aDebuggee; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gWindow = aWindow; + + return testCleanExit(gWindow); + }) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); +} + +function testCleanExit(aWindow) { + let deferred = promise.defer(); + + gWindow = aWindow; + ok(!!gWindow, "Second window created."); + + gWindow.focus(); + + let topWindow = Services.wm.getMostRecentWindow("navigator:browser"); + is(topWindow, gWindow, + "The second window is on top."); + + let isActive = promise.defer(); + let isLoaded = promise.defer(); + + promise.all([isActive.promise, isLoaded.promise]).then(() => { + gWindow.BrowserChromeTest.runWhenReady(() => { + waitForSourceAndCaretAndScopes(gPanel, ".html", 16).then(() => { + is(gDebugger.gThreadClient.paused, true, + "Should be paused after the debugger statement."); + gWindow.close(); + deferred.resolve(); + finish(); + }); + + gDebuggee.runDebuggerStatement(); + }); + }); + + let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); + if (focusManager.activeWindow != gWindow) { + gWindow.addEventListener("activate", function onActivate(aEvent) { + if (aEvent.target != gWindow) { + return; + } + gWindow.removeEventListener("activate", onActivate, true); + isActive.resolve(); + }, true); + } else { + isActive.resolve(); + } + + let contentLocation = gWindow.content.location.href; + if (contentLocation != TAB_URL) { + gWindow.document.addEventListener("load", function onLoad(aEvent) { + if (aEvent.target.documentURI != TAB_URL) { + return; + } + gWindow.document.removeEventListener("load", onLoad, true); + isLoaded.resolve(); + }, true); + } else { + isLoaded.resolve(); + } + return deferred.promise; +} + +registerCleanupFunction(function() { + gWindow = null; + gDebuggee = null; + gPanel = null; + gDebugger = null; +}); diff --git a/browser/devtools/debugger/test/head.js b/browser/devtools/debugger/test/head.js index 2cfbc601d65..19c1582c831 100644 --- a/browser/devtools/debugger/test/head.js +++ b/browser/devtools/debugger/test/head.js @@ -416,18 +416,18 @@ function backspaceText(aElement, aTimes) { } } -function getTab(aTarget) { +function getTab(aTarget, aWindow) { if (aTarget instanceof XULElement) { return promise.resolve(aTarget); } else { - return addTab(aTarget); + return addTab(aTarget, aWindow); } } function initDebugger(aTarget, aWindow) { info("Initializing a debugger panel."); - return getTab(aTarget).then(aTab => { + return getTab(aTarget, aWindow).then(aTab => { info("Debugee tab added successfully: " + aTarget); let deferred = promise.defer(); @@ -445,7 +445,7 @@ function initDebugger(aTarget, aWindow) { info("Debugger client resumed successfully."); prepareDebugger(debuggerPanel); - deferred.resolve([aTab, debuggee, debuggerPanel]); + deferred.resolve([aTab, debuggee, debuggerPanel, aWindow]); }); }); diff --git a/browser/devtools/responsivedesign/responsivedesign.jsm b/browser/devtools/responsivedesign/responsivedesign.jsm index b7c0e90ba00..1052689bbf0 100644 --- a/browser/devtools/responsivedesign/responsivedesign.jsm +++ b/browser/devtools/responsivedesign/responsivedesign.jsm @@ -180,12 +180,6 @@ function ResponsiveUI(aWindow, aTab) this.buildUI(); this.checkMenus(); - this.docShell = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShell); - - this.docShell.deviceSizeIsPageSize = true; - try { if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) { this.rotate(); @@ -258,8 +252,6 @@ ResponsiveUI.prototype = { this.browser.removeEventListener("load", this.bound_onPageLoad, true); this.browser.removeEventListener("unload", this.bound_onPageUnload, true); - this.docShell.deviceSizeIsPageSize = false; - if (this._floatingScrollbars) switchToNativeScrollbars(this.tab); @@ -296,7 +288,6 @@ ResponsiveUI.prototype = { this.container.removeAttribute("responsivemode"); this.stack.removeAttribute("responsivemode"); - delete this.docShell; delete this.tab.__responsiveUI; if (this.touchEventHandler) this.touchEventHandler.stop(); diff --git a/browser/devtools/responsivedesign/test/Makefile.in b/browser/devtools/responsivedesign/test/Makefile.in index 6c7567bbca2..42f0be6518b 100644 --- a/browser/devtools/responsivedesign/test/Makefile.in +++ b/browser/devtools/responsivedesign/test/Makefile.in @@ -9,7 +9,6 @@ MOCHITEST_BROWSER_FILES := \ browser_responsive_cmd.js \ browser_responsivecomputedview.js \ browser_responsiveui_touch.js \ - browser_responsive_devicewidth.js \ touch.html \ head.js \ $(NULL) diff --git a/browser/devtools/responsivedesign/test/browser_responsive_devicewidth.js b/browser/devtools/responsivedesign/test/browser_responsive_devicewidth.js deleted file mode 100644 index 88437afde4c..00000000000 --- a/browser/devtools/responsivedesign/test/browser_responsive_devicewidth.js +++ /dev/null @@ -1,61 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. -http://creativecommons.org/publicdomain/zero/1.0/ */ - -function test() { - let instance; - let mgr = ResponsiveUI.ResponsiveUIManager; - - waitForExplicitFinish(); - - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function onload() { - gBrowser.selectedBrowser.removeEventListener("load", onload, true); - waitForFocus(startTest, content); - }, true); - - content.location = "data:text/html,mop"; - - function startTest() { - mgr.once("on", function() {executeSoon(onUIOpen)}); - document.getElementById("Tools:ResponsiveUI").doCommand(); - } - - function onUIOpen() { - instance = gBrowser.selectedTab.__responsiveUI; - instance.stack.setAttribute("notransition", "true"); - ok(instance, "instance of the module is attached to the tab."); - - let mql = content.matchMedia("(max-device-width:100px)") - - ok(!mql.matches, "media query doesn't match."); - - mql.addListener(onMediaChange); - instance.setSize(90, 500); - } - - function onMediaChange(mql) { - mql.removeListener(onMediaChange); - ok(mql.matches, "media query matches."); - ok(window.screen.width != content.screen.width, "screen.width is not the size of the screen."); - is(content.screen.width, 90, "screen.width is the width of the page."); - is(content.screen.height, 500, "screen.height is the height of the page."); - - - let docShell = content.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShell); - - mql.addListener(onMediaChange2); - docShell.deviceSizeIsPageSize = false; - } - - function onMediaChange2(mql) { - mql.removeListener(onMediaChange); - ok(!mql.matches, "media query has been re-evaluated."); - ok(window.screen.width == content.screen.width, "screen.width is not the size of the screen."); - instance.stack.removeAttribute("notransition"); - document.getElementById("Tools:ResponsiveUI").doCommand(); - gBrowser.removeCurrentTab(); - finish(); - } -} diff --git a/browser/devtools/styleinspector/computed-view.js b/browser/devtools/styleinspector/computed-view.js index ca34077a957..44c1ac0b9aa 100644 --- a/browser/devtools/styleinspector/computed-view.js +++ b/browser/devtools/styleinspector/computed-view.js @@ -668,16 +668,13 @@ PropertyView.prototype = { /** * Returns the className that should be assigned to the propertyView. - * * @return string */ get propertyHeaderClassName() { if (this.visible) { - this.tree._darkStripe = !this.tree._darkStripe; - let darkValue = this.tree._darkStripe ? - "property-view theme-bg-darker" : "property-view"; - return darkValue; + let isDark = this.tree._darkStripe = !this.tree._darkStripe; + return isDark ? "property-view theme-bg-darker" : "property-view"; } return "property-view-hidden"; }, @@ -690,49 +687,66 @@ PropertyView.prototype = { get propertyContentClassName() { if (this.visible) { - let darkValue = this.tree._darkStripe ? - "property-content theme-bg-darker" : "property-content"; - return darkValue; + let isDark = this.tree._darkStripe; + return isDark ? "property-content theme-bg-darker" : "property-content"; } return "property-content-hidden"; }, + /** + * Build the markup for on computed style + * @return Element + */ buildMain: function PropertyView_buildMain() { let doc = this.tree.styleDocument; + let onToggle = this.onStyleToggle.bind(this); + + // Build the container element this.element = doc.createElementNS(HTML_NS, "div"); this.element.setAttribute("class", this.propertyHeaderClassName); - this.matchedExpander = doc.createElementNS(HTML_NS, "div"); - this.matchedExpander.className = "expander theme-twisty"; - this.matchedExpander.setAttribute("tabindex", "0"); - this.matchedExpander.addEventListener("click", - this.matchedExpanderClick.bind(this), false); - this.matchedExpander.addEventListener("keydown", function(aEvent) { + // Make it keyboard navigable + this.element.setAttribute("tabindex", "0"); + this.element.addEventListener("keydown", function(aEvent) { let keyEvent = Ci.nsIDOMKeyEvent; if (aEvent.keyCode == keyEvent.DOM_VK_F1) { this.mdnLinkClick(); } if (aEvent.keyCode == keyEvent.DOM_VK_RETURN || aEvent.keyCode == keyEvent.DOM_VK_SPACE) { - this.matchedExpanderClick(aEvent); + onToggle(aEvent); } }.bind(this), false); + + // Build the twisty expand/collapse + this.matchedExpander = doc.createElementNS(HTML_NS, "div"); + this.matchedExpander.className = "expander theme-twisty"; + this.matchedExpander.addEventListener("click", onToggle, false); this.element.appendChild(this.matchedExpander); + // Build the style name element this.nameNode = doc.createElementNS(HTML_NS, "div"); - this.element.appendChild(this.nameNode); this.nameNode.setAttribute("class", "property-name theme-fg-color5"); + // Reset its tabindex attribute otherwise, if an ellipsis is applied + // it will be reachable via TABing + this.nameNode.setAttribute("tabindex", ""); this.nameNode.textContent = this.nameNode.title = this.name; - this.nameNode.addEventListener("click", function(aEvent) { - this.matchedExpander.focus(); - }.bind(this), false); + // Make it hand over the focus to the container + this.nameNode.addEventListener("click", () => this.element.focus(), false); + this.element.appendChild(this.nameNode); + // Build the style value element this.valueNode = doc.createElementNS(HTML_NS, "div"); - this.element.appendChild(this.valueNode); this.valueNode.setAttribute("class", "property-value theme-fg-color1"); + // Reset its tabindex attribute otherwise, if an ellipsis is applied + // it will be reachable via TABing + this.valueNode.setAttribute("tabindex", ""); this.valueNode.setAttribute("dir", "ltr"); this.valueNode.textContent = this.valueNode.title = this.value; + // Make it hand over the focus to the container + this.valueNode.addEventListener("click", () => this.element.focus(), false); + this.element.appendChild(this.valueNode); return this.element; }, @@ -836,7 +850,7 @@ PropertyView.prototype = { * @param {Event} aEvent Used to determine the class name of the targets click * event. */ - matchedExpanderClick: function PropertyView_matchedExpanderClick(aEvent) + onStyleToggle: function PropertyView_onStyleToggle(aEvent) { this.matchedExpanded = !this.matchedExpanded; this.refreshMatchedSelectors(); diff --git a/browser/devtools/styleinspector/test/Makefile.in b/browser/devtools/styleinspector/test/Makefile.in index 91346894eab..e10917f8af0 100644 --- a/browser/devtools/styleinspector/test/Makefile.in +++ b/browser/devtools/styleinspector/test/Makefile.in @@ -36,6 +36,7 @@ MOCHITEST_BROWSER_FILES = \ browser_bug894376_css_value_completion_existing_property_value_pair.js \ browser_ruleview_bug_902966_revert_value_on_ESC.js \ browser_ruleview_pseudoelement.js \ + browser_computedview_bug835808_keyboard_nav.js \ head.js \ $(NULL) diff --git a/browser/devtools/styleinspector/test/browser_computedview_bug835808_keyboard_nav.js b/browser/devtools/styleinspector/test/browser_computedview_bug835808_keyboard_nav.js new file mode 100644 index 00000000000..2a13bb2aad5 --- /dev/null +++ b/browser/devtools/styleinspector/test/browser_computedview_bug835808_keyboard_nav.js @@ -0,0 +1,94 @@ +/* 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 the style inspector works properly + +let doc, computedView, inspector; + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad(evt) { + gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true); + doc = content.document; + waitForFocus(createDocument, content); + }, true); + + content.location = "data:text/html,computed view context menu test"; +} + +function createDocument() +{ + doc.body.innerHTML = '
\n' + + '

Some header text

\n' + + '

hi.

\n' + + '

I am a test-case. This text exists ' + + 'solely to provide some things to ' + + 'highlight and count ' + + 'style list-items in the box at right. If you are reading this, ' + + 'you should go do something else instead. Maybe read a book. Or better ' + + 'yet, write some test-cases for another bit of code. ' + + 'some text

\n' + + '

more text

\n' + + '

even more text

' + + '
'; + doc.title = "Computed view keyboard navigation test"; + + openComputedView(startTests); +} + +function startTests(aInspector, aComputedView) +{ + computedView = aComputedView; + inspector = aInspector; + testTabThrougStyles(); +} + +function endTests() +{ + computedView = inspector = doc = null; + gBrowser.removeCurrentTab(); + finish(); +} + +function testTabThrougStyles() +{ + let span = doc.querySelector("span"); + + inspector.once("computed-view-refreshed", () => { + // Selecting the first computed style in the list + let firstStyle = computedView.styleDocument.querySelector(".property-view"); + ok(firstStyle, "First computed style found in panel"); + firstStyle.focus(); + + // Tab to select the 2nd style, press return + EventUtils.synthesizeKey("VK_TAB", {}); + EventUtils.synthesizeKey("VK_RETURN", {}); + inspector.once("computed-view-property-expanded", () => { + // Verify the 2nd style has been expanded + let secondStyleSelectors = computedView.styleDocument.querySelectorAll( + ".property-content .matchedselectors")[1]; + ok(secondStyleSelectors.childNodes.length > 0, "Matched selectors expanded"); + + // Tab back up and test the same thing, with space + EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}); + EventUtils.synthesizeKey("VK_SPACE", {}); + inspector.once("computed-view-property-expanded", () => { + // Verify the 1st style has been expanded too + let firstStyleSelectors = computedView.styleDocument.querySelectorAll( + ".property-content .matchedselectors")[0]; + ok(firstStyleSelectors.childNodes.length > 0, "Matched selectors expanded"); + + endTests(); + }); + }); + }); + + inspector.selection.setNode(span); +} diff --git a/browser/themes/linux/devtools/computedview.css b/browser/themes/linux/devtools/computedview.css index d9a2a9948b0..37ee4b9de03 100644 --- a/browser/themes/linux/devtools/computedview.css +++ b/browser/themes/linux/devtools/computedview.css @@ -45,6 +45,7 @@ body { overflow-x: hidden; text-overflow: ellipsis; white-space: nowrap; + outline: 0; } .property-value { @@ -58,6 +59,7 @@ body { background-size: 5px 8px; background-position: 2px center; padding-left: 10px; + outline: 0; } .other-property-value { diff --git a/browser/themes/osx/devtools/computedview.css b/browser/themes/osx/devtools/computedview.css index 227fb632b43..ea131ed0b85 100644 --- a/browser/themes/osx/devtools/computedview.css +++ b/browser/themes/osx/devtools/computedview.css @@ -63,6 +63,7 @@ body { overflow-x: hidden; text-overflow: ellipsis; white-space: nowrap; + outline: 0; } .property-value { @@ -76,6 +77,7 @@ body { background-size: 5px 8px; background-position: 2px center; padding-left: 10px; + outline: 0; } .other-property-value { diff --git a/browser/themes/windows/devtools/computedview.css b/browser/themes/windows/devtools/computedview.css index 227fb632b43..ea131ed0b85 100644 --- a/browser/themes/windows/devtools/computedview.css +++ b/browser/themes/windows/devtools/computedview.css @@ -63,6 +63,7 @@ body { overflow-x: hidden; text-overflow: ellipsis; white-space: nowrap; + outline: 0; } .property-value { @@ -76,6 +77,7 @@ body { background-size: 5px 8px; background-position: 2px center; padding-left: 10px; + outline: 0; } .other-property-value { diff --git a/build/automation.py.in b/build/automation.py.in index b6147464f59..8f3984dd0de 100644 --- a/build/automation.py.in +++ b/build/automation.py.in @@ -683,16 +683,15 @@ class Automation(object): def killAndGetStackNoScreenshot(self, processPID, utilityPath, debuggerInfo): """Kill the process, preferrably in a way that gets us a stack trace.""" if self.CRASHREPORTER and not debuggerInfo: - if self.UNIXISH: + if not self.IS_WIN32: # ABRT will get picked up by Breakpad's signal handler os.kill(processPID, signal.SIGABRT) return - elif self.IS_WIN32: + else: # We should have a "crashinject" program in our utility path crashinject = os.path.normpath(os.path.join(utilityPath, "crashinject.exe")) if os.path.exists(crashinject) and subprocess.Popen([crashinject, str(processPID)]).wait() == 0: return - #TODO: kill the process such that it triggers Breakpad on OS X (bug 525296) self.log.info("Can't trigger Breakpad, just killing process") self.killPid(processPID) diff --git a/content/html/content/src/HTMLInputElement.cpp b/content/html/content/src/HTMLInputElement.cpp index 731dceda681..dc5e9368cf5 100644 --- a/content/html/content/src/HTMLInputElement.cpp +++ b/content/html/content/src/HTMLInputElement.cpp @@ -7,7 +7,6 @@ #include "mozilla/DebugOnly.h" #include "mozilla/dom/Date.h" -#include "mozilla/dom/HTMLInputElementBinding.h" #include "nsAsyncDOMEvent.h" #include "nsAttrValueInlines.h" @@ -4504,6 +4503,11 @@ HTMLInputElement::SetSelectionRange(int32_t aSelectionStart, if (!aRv.Failed()) { aRv = textControlFrame->ScrollSelectionIntoView(); } + + nsContentUtils::DispatchTrustedEvent(OwnerDoc(), + static_cast(this), + NS_LITERAL_STRING("select"), true, + false); } } } @@ -4521,6 +4525,113 @@ HTMLInputElement::SetSelectionRange(int32_t aSelectionStart, return rv.ErrorCode(); } +void +HTMLInputElement::SetRangeText(const nsAString& aReplacement, ErrorResult& aRv) +{ + if (!SupportsSetRangeText()) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return; + } + + int32_t start, end; + aRv = GetSelectionRange(&start, &end); + if (aRv.Failed()) { + nsTextEditorState* state = GetEditorState(); + if (state && state->IsSelectionCached()) { + start = state->GetSelectionProperties().mStart; + end = state->GetSelectionProperties().mEnd; + aRv = NS_OK; + } + } + + SetRangeText(aReplacement, start, end, mozilla::dom::SelectionMode::Preserve, + aRv, start, end); +} + +void +HTMLInputElement::SetRangeText(const nsAString& aReplacement, uint32_t aStart, + uint32_t aEnd, const SelectionMode& aSelectMode, + ErrorResult& aRv, int32_t aSelectionStart, + int32_t aSelectionEnd) +{ + if (!SupportsSetRangeText()) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return; + } + + if (aStart > aEnd) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + nsAutoString value; + GetValueInternal(value); + uint32_t inputValueLength = value.Length(); + + if (aStart > inputValueLength) { + aStart = inputValueLength; + } + + if (aEnd > inputValueLength) { + aEnd = inputValueLength; + } + + if (aSelectionStart == -1 && aSelectionEnd == -1) { + aRv = GetSelectionRange(&aSelectionStart, &aSelectionEnd); + if (aRv.Failed()) { + nsTextEditorState* state = GetEditorState(); + if (state && state->IsSelectionCached()) { + aSelectionStart = state->GetSelectionProperties().mStart; + aSelectionEnd = state->GetSelectionProperties().mEnd; + aRv = NS_OK; + } + } + } + + if (aStart < aEnd) { + value.Replace(aStart, aEnd - aStart, aReplacement); + SetValueInternal(value, false, false); + } + + uint32_t newEnd = aStart + aReplacement.Length(); + int32_t delta = aReplacement.Length() - (aEnd - aStart); + + switch (aSelectMode) { + case mozilla::dom::SelectionMode::Select: + { + aSelectionStart = aStart; + aSelectionEnd = newEnd; + } + break; + case mozilla::dom::SelectionMode::Start: + { + aSelectionStart = aSelectionEnd = aStart; + } + break; + case mozilla::dom::SelectionMode::End: + { + aSelectionStart = aSelectionEnd = newEnd; + } + break; + case mozilla::dom::SelectionMode::Preserve: + { + if ((uint32_t)aSelectionStart > aEnd) + aSelectionStart += delta; + else if ((uint32_t)aSelectionStart > aStart) + aSelectionStart = aStart; + + if ((uint32_t)aSelectionEnd > aEnd) + aSelectionEnd += delta; + else if ((uint32_t)aSelectionEnd > aStart) + aSelectionEnd = newEnd; + } + break; + } + + Optional direction; + SetSelectionRange(aSelectionStart, aSelectionEnd, direction, aRv); +} + int32_t HTMLInputElement::GetSelectionStart(ErrorResult& aRv) { diff --git a/content/html/content/src/HTMLInputElement.h b/content/html/content/src/HTMLInputElement.h index c289c146d6d..27446398393 100644 --- a/content/html/content/src/HTMLInputElement.h +++ b/content/html/content/src/HTMLInputElement.h @@ -17,6 +17,7 @@ #include "nsCOMPtr.h" #include "nsIConstraintValidation.h" #include "mozilla/dom/HTMLFormElement.h" // for HasEverTriedInvalidSubmit() +#include "mozilla/dom/HTMLInputElementBinding.h" #include "nsIFilePicker.h" #include "nsIContentPrefService2.h" #include "mozilla/Decimal.h" @@ -641,6 +642,13 @@ public: const Optional< nsAString >& direction, ErrorResult& aRv); + void SetRangeText(const nsAString& aReplacement, ErrorResult& aRv); + + void SetRangeText(const nsAString& aReplacement, uint32_t aStart, + uint32_t aEnd, const SelectionMode& aSelectMode, + ErrorResult& aRv, int32_t aSelectionStart = -1, + int32_t aSelectionEnd = -1); + // XPCOM GetAlign() is OK void SetAlign(const nsAString& aValue, ErrorResult& aRv) { @@ -1239,6 +1247,15 @@ private: return MayFireChangeOnBlur(mType); } + /** + * Returns true if setRangeText can be called on element + */ + bool SupportsSetRangeText() const { + return mType == NS_FORM_INPUT_TEXT || mType == NS_FORM_INPUT_SEARCH || + mType == NS_FORM_INPUT_URL || mType == NS_FORM_INPUT_TEL || + mType == NS_FORM_INPUT_PASSWORD; + } + static bool MayFireChangeOnBlur(uint8_t aType) { return IsSingleLineTextControl(false, aType) || aType == NS_FORM_INPUT_RANGE; diff --git a/content/html/content/test/forms/Makefile.in b/content/html/content/test/forms/Makefile.in index 6119d2385e0..06359c028e1 100644 --- a/content/html/content/test/forms/Makefile.in +++ b/content/html/content/test/forms/Makefile.in @@ -5,6 +5,7 @@ MOCHITEST_FILES = \ save_restore_radio_groups.sjs \ test_save_restore_radio_groups.html \ + test_set_range_text.html \ test_change_event.html \ test_mozistextfield.html \ test_input_attributes_reflection.html \ diff --git a/content/html/content/test/forms/test_set_range_text.html b/content/html/content/test/forms/test_set_range_text.html new file mode 100644 index 00000000000..8ba01587f51 --- /dev/null +++ b/content/html/content/test/forms/test_set_range_text.html @@ -0,0 +1,183 @@ + + + + +Test for Bug 850364 + + + + + +Mozilla Bug 850364 +

+
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+ + diff --git a/content/html/document/src/Makefile.in b/content/html/document/src/Makefile.in deleted file mode 100644 index c9b334ae06c..00000000000 --- a/content/html/document/src/Makefile.in +++ /dev/null @@ -1,16 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -include $(topsrcdir)/config/rules.mk - -LOCAL_INCLUDES += \ - -I$(srcdir)/../../../base/src \ - -I$(srcdir)/../../../events/src \ - -I$(srcdir)/../../content/src \ - -I$(topsrcdir)/layout/style \ - -I$(topsrcdir)/dom/base \ - -I$(topsrcdir)/caps/include \ - -I$(topsrcdir)/xpcom/ds \ - $(NULL) diff --git a/content/html/document/src/moz.build b/content/html/document/src/moz.build index 7aa28a3c192..5515a31fd5f 100644 --- a/content/html/document/src/moz.build +++ b/content/html/document/src/moz.build @@ -33,3 +33,12 @@ LIBXUL_LIBRARY = True MSVC_ENABLE_PGO = True +LOCAL_INCLUDES += [ + '../../content/src', + '/caps/include', + '/content/base/src', + '/content/events/src', + '/dom/base', + '/layout/style', + '/xpcom/ds', +] diff --git a/content/media/AudioNodeEngine.cpp b/content/media/AudioNodeEngine.cpp index e87e1abf921..b0a0fa78699 100644 --- a/content/media/AudioNodeEngine.cpp +++ b/content/media/AudioNodeEngine.cpp @@ -125,6 +125,19 @@ BufferComplexMultiply(const float* aInput, } } +float +AudioBufferPeakValue(const float *aInput, uint32_t aSize) +{ + float max = 0.0f; + for (uint32_t i = 0; i < aSize; i++) { + float mag = fabs(aInput[i]); + if (mag > max) { + max = mag; + } + } + return max; +} + void AudioBlockCopyChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE], const float aScale[WEBAUDIO_BLOCK_SIZE], diff --git a/content/media/AudioNodeEngine.h b/content/media/AudioNodeEngine.h index ca0614882ee..ad51fc87d47 100644 --- a/content/media/AudioNodeEngine.h +++ b/content/media/AudioNodeEngine.h @@ -134,6 +134,11 @@ void BufferComplexMultiply(const float* aInput, float* aOutput, uint32_t aSize); +/** + * Vector maximum element magnitude ( max(abs(aInput)) ). + */ +float AudioBufferPeakValue(const float* aInput, uint32_t aSize); + /** * In place gain. aScale == 1.0f should be optimized. */ diff --git a/content/media/mediasource/Makefile.in b/content/media/mediasource/Makefile.in deleted file mode 100644 index 6fbe8159b2d..00000000000 --- a/content/media/mediasource/Makefile.in +++ /dev/null @@ -1,3 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/content/media/plugins/Makefile.in b/content/media/plugins/Makefile.in deleted file mode 100644 index 9e99f02e7a4..00000000000 --- a/content/media/plugins/Makefile.in +++ /dev/null @@ -1,10 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this file, -# You can obtain one at http://mozilla.org/MPL/2.0/. - -include $(topsrcdir)/config/rules.mk - -INCLUDES += \ - -I$(srcdir)/../../base/src \ - -I$(srcdir)/../../html/content/src \ - $(NULL) diff --git a/content/media/plugins/moz.build b/content/media/plugins/moz.build index 729551df317..9ce356cff8a 100644 --- a/content/media/plugins/moz.build +++ b/content/media/plugins/moz.build @@ -25,3 +25,7 @@ LIBRARY_NAME = 'gkconmediaplugins_s' LIBXUL_LIBRARY = True +LOCAL_INCLUDES += [ + '/content/base/src', + '/content/html/content/src', + ] diff --git a/content/media/raw/Makefile.in b/content/media/raw/Makefile.in deleted file mode 100644 index 3a768f87d4c..00000000000 --- a/content/media/raw/Makefile.in +++ /dev/null @@ -1,11 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -include $(topsrcdir)/config/rules.mk - -INCLUDES += \ - -I$(srcdir)/../../base/src \ - -I$(srcdir)/../../html/content/src \ - $(NULL) diff --git a/content/media/raw/moz.build b/content/media/raw/moz.build index 7c8e2b0d528..a98ad0a7c36 100644 --- a/content/media/raw/moz.build +++ b/content/media/raw/moz.build @@ -23,3 +23,7 @@ FAIL_ON_WARNINGS = True LIBXUL_LIBRARY = True +LOCAL_INCLUDES += [ + '/content/base/src', + '/content/html/content/src', +] diff --git a/content/media/webaudio/AudioContext.cpp b/content/media/webaudio/AudioContext.cpp index 0056bfa7312..e308ba495b2 100644 --- a/content/media/webaudio/AudioContext.cpp +++ b/content/media/webaudio/AudioContext.cpp @@ -396,8 +396,11 @@ AudioContext::CreatePeriodicWave(const Float32Array& aRealData, } nsRefPtr periodicWave = - new PeriodicWave(this, aRealData.Data(), aRealData.Length(), - aImagData.Data(), aImagData.Length()); + new PeriodicWave(this, aRealData.Data(), aImagData.Data(), + aImagData.Length(), aRv); + if (aRv.Failed()) { + return nullptr; + } return periodicWave.forget(); } diff --git a/content/media/webaudio/FFTBlock.h b/content/media/webaudio/FFTBlock.h index b3b1993cc05..baa5440aaa9 100644 --- a/content/media/webaudio/FFTBlock.h +++ b/content/media/webaudio/FFTBlock.h @@ -39,11 +39,14 @@ public: CreateInterpolatedBlock(const FFTBlock& block0, const FFTBlock& block1, double interp); + // Transform FFTSize() points of aData and store the result internally. void PerformFFT(const float* aData) { EnsureFFT(); kiss_fftr(mFFT, aData, mOutputBuffer.Elements()); } + // Inverse-transform internal data and store the resulting FFTSize() + // points in aData. void PerformInverseFFT(float* aData) { EnsureIFFT(); @@ -52,6 +55,27 @@ public: aData[i] /= mFFTSize; } } + // Inverse-transform the FFTSize()/2+1 points of data in each + // of aRealDataIn and aImagDataIn and store the resulting + // FFTSize() points in aRealDataOut. + void PerformInverseFFT(float* aRealDataIn, + float *aImagDataIn, + float *aRealDataOut) + { + EnsureIFFT(); + const uint32_t inputSize = mFFTSize / 2 + 1; + nsTArray inputBuffer; + inputBuffer.SetLength(inputSize); + for (uint32_t i = 0; i < inputSize; ++i) { + inputBuffer[i].r = aRealDataIn[i]; + inputBuffer[i].i = aImagDataIn[i]; + } + kiss_fftri(mIFFT, inputBuffer.Elements(), aRealDataOut); + for (uint32_t i = 0; i < mFFTSize; ++i) { + aRealDataOut[i] /= mFFTSize; + } + } + void Multiply(const FFTBlock& aFrame) { BufferComplexMultiply(reinterpret_cast(mOutputBuffer.Elements()), diff --git a/content/media/webaudio/OscillatorNode.cpp b/content/media/webaudio/OscillatorNode.cpp index efd80c4a863..1e68533f8d2 100644 --- a/content/media/webaudio/OscillatorNode.cpp +++ b/content/media/webaudio/OscillatorNode.cpp @@ -9,6 +9,7 @@ #include "AudioNodeStream.h" #include "AudioDestinationNode.h" #include "WebAudioUtils.h" +#include "blink/PeriodicWave.h" namespace mozilla { namespace dom { @@ -36,6 +37,39 @@ NS_INTERFACE_MAP_END_INHERITING(AudioNode) NS_IMPL_ADDREF_INHERITED(OscillatorNode, AudioNode) NS_IMPL_RELEASE_INHERITED(OscillatorNode, AudioNode) +static const float sLeak = 0.995f; + +class DCBlocker +{ +public: + // These are sane defauts when the initial mPhase is zero + DCBlocker(float aLastInput = 0.0f, + float aLastOutput = 0.0f, + float aPole = 0.995) + :mLastInput(aLastInput), + mLastOutput(aLastOutput), + mPole(aPole) + { + MOZ_ASSERT(aPole > 0); + } + + inline float Process(float aInput) + { + float out; + + out = mLastOutput * mPole + aInput - mLastInput; + mLastOutput = out; + mLastInput = aInput; + + return out; + } +private: + float mLastInput; + float mLastOutput; + float mPole; +}; + + class OscillatorNodeEngine : public AudioNodeEngine { public: @@ -50,6 +84,17 @@ public: , mDetune(0.f) , mType(OscillatorType::Sine) , mPhase(0.) + , mFinalFrequency(0.0) + , mNumberOfHarmonics(0) + , mSignalPeriod(0.0) + , mAmplitudeAtZero(0.0) + , mPhaseIncrement(0.0) + , mSquare(0.0) + , mTriangle(0.0) + , mSaw(0.0) + , mPhaseWrap(0.0) + , mRecomputeFrequency(true) + , mCustomLength(0) { } @@ -70,6 +115,7 @@ public: const AudioParamTimeline& aValue, TrackRate aSampleRate) MOZ_OVERRIDE { + mRecomputeFrequency = true; switch (aIndex) { case FREQUENCY: MOZ_ASSERT(mSource && mDestination); @@ -85,6 +131,7 @@ public: NS_ERROR("Bad OscillatorNodeEngine TimelineParameter"); } } + virtual void SetStreamTimeParameter(uint32_t aIndex, TrackTicks aParam) { switch (aIndex) { @@ -94,29 +141,127 @@ public: NS_ERROR("Bad OscillatorNodeEngine StreamTimeParameter"); } } + virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam) { switch (aIndex) { - case TYPE: mType = static_cast(aParam); break; - default: - NS_ERROR("Bad OscillatorNodeEngine Int32Parameter"); + case TYPE: + // Set the new type. + mType = static_cast(aParam); + if (mType != OscillatorType::Custom) { + // Forget any previous custom data. + mCustomLength = 0; + mCustom = nullptr; + mPeriodicWave = nullptr; + } + // Update BLIT integrators with the new initial conditions. + switch (mType) { + case OscillatorType::Sine: + mPhase = 0.0; + break; + case OscillatorType::Square: + mPhase = 0.0; + // Initial integration condition is -0.5, because our + // square has 50% duty cycle. + mSquare = -0.5; + break; + case OscillatorType::Triangle: + // Initial mPhase and related integration condition so the + // triangle is in the middle of the first upward slope. + // XXX actually do the maths and put the right number here. + mPhase = (float)(M_PI / 2); + mSquare = 0.5; + mTriangle = 0.0; + break; + case OscillatorType::Sawtooth: + // Initial mPhase so the oscillator starts at the + // middle of the ramp, per spec. + mPhase = (float)(M_PI / 2); + // mSaw = 0 when mPhase = pi/2. + mSaw = 0.0; + break; + case OscillatorType::Custom: + // Custom waveforms don't use BLIT. + break; + default: + NS_ERROR("Bad OscillatorNodeEngine type parameter."); + } + // End type switch. + break; + case PERIODICWAVE: + MOZ_ASSERT(aParam >= 0, "negative custom array length"); + mCustomLength = static_cast(aParam); + break; + default: + NS_ERROR("Bad OscillatorNodeEngine Int32Parameter."); + } + // End index switch. + } + + virtual void SetBuffer(already_AddRefed aBuffer) + { + MOZ_ASSERT(mCustomLength, "Custom buffer sent before length"); + mCustom = aBuffer; + MOZ_ASSERT(mCustom->GetChannels() == 2, + "PeriodicWave should have sent two channels"); + mPeriodicWave = WebCore::PeriodicWave::create(mSource->SampleRate(), + mCustom->GetData(0), mCustom->GetData(1), mCustomLength); + } + + void IncrementPhase() + { + mPhase += mPhaseIncrement; + if (mPhase > mPhaseWrap) { + mPhase -= mPhaseWrap; } } - double ComputeFrequency(TrackTicks ticks, size_t count) + // Square and triangle are using a bipolar band-limited impulse train, saw is + // using a normal band-limited impulse train. + bool UsesBipolarBLIT() { + return mType == OscillatorType::Square || mType == OscillatorType::Triangle; + } + + void UpdateFrequencyIfNeeded(TrackTicks ticks, size_t count) { double frequency, detune; - if (mFrequency.HasSimpleValue()) { + + bool simpleFrequency = mFrequency.HasSimpleValue(); + bool simpleDetune = mDetune.HasSimpleValue(); + + // Shortcut if frequency-related AudioParam are not automated, and we + // already have computed the frequency information and related parameters. + if (simpleFrequency && simpleDetune && !mRecomputeFrequency) { + return; + } + + if (simpleFrequency) { frequency = mFrequency.GetValue(); } else { frequency = mFrequency.GetValueAtTime(ticks, count); } - if (mDetune.HasSimpleValue()) { + if (simpleDetune) { detune = mDetune.GetValue(); } else { detune = mDetune.GetValueAtTime(ticks, count); } - return frequency * pow(2., detune / 1200.); + + mFinalFrequency = frequency * pow(2., detune / 1200.); + mRecomputeFrequency = false; + + // When using bipolar BLIT, we divide the signal period by two, because we + // are using two BLIT out of phase. + mSignalPeriod = UsesBipolarBLIT() ? 0.5 * mSource->SampleRate() / mFinalFrequency + : mSource->SampleRate() / mFinalFrequency; + // Wrap the phase accordingly: + mPhaseWrap = UsesBipolarBLIT() || mType == OscillatorType::Sine ? 2 * M_PI + : M_PI; + // Even number of harmonics for bipolar blit, odd otherwise. + mNumberOfHarmonics = UsesBipolarBLIT() ? 2 * floor(0.5 * mSignalPeriod) + : 2 * floor(0.5 * mSignalPeriod) + 1; + mPhaseIncrement = mType == OscillatorType::Sine ? 2 * M_PI / mSignalPeriod + : M_PI / mSignalPeriod; + mAmplitudeAtZero = mNumberOfHarmonics / mSignalPeriod; } void FillBounds(float* output, TrackTicks ticks, @@ -141,95 +286,138 @@ public: } } - void ComputeSine(AudioChunk *aOutput) + float BipolarBLIT() { - AllocateAudioBlock(1, aOutput); - float* output = static_cast(const_cast(aOutput->mChannelData[0])); + float blit; + float denom = sin(mPhase); - TrackTicks ticks = mSource->GetCurrentPosition(); - uint32_t start, end; - FillBounds(output, ticks, start, end); - - double rate = 2.*M_PI / mSource->SampleRate(); - double phase = mPhase; - for (uint32_t i = start; i < end; ++i) { - phase += ComputeFrequency(ticks, i) * rate; - output[i] = sin(phase); - } - mPhase = phase; - while (mPhase > 2.0*M_PI) { - // Rescale to avoid precision reductions on long runs. - mPhase -= 2.0*M_PI; - } - } - - void ComputeSquare(AudioChunk *aOutput) - { - AllocateAudioBlock(1, aOutput); - float* output = static_cast(const_cast(aOutput->mChannelData[0])); - - TrackTicks ticks = mSource->GetCurrentPosition(); - uint32_t start, end; - FillBounds(output, ticks, start, end); - - double rate = 1.0 / mSource->SampleRate(); - double phase = mPhase; - for (uint32_t i = start; i < end; ++i) { - phase += ComputeFrequency(ticks, i) * rate; - if (phase > 1.0) { - phase -= 1.0; - } - output[i] = phase < 0.5 ? 1.0 : -1.0; - } - mPhase = phase; - } - - void ComputeSawtooth(AudioChunk *aOutput) - { - AllocateAudioBlock(1, aOutput); - float* output = static_cast(const_cast(aOutput->mChannelData[0])); - - TrackTicks ticks = mSource->GetCurrentPosition(); - uint32_t start, end; - FillBounds(output, ticks, start, end); - - double rate = 1.0 / mSource->SampleRate(); - double phase = mPhase; - for (uint32_t i = start; i < end; ++i) { - phase += ComputeFrequency(ticks, i) * rate; - if (phase > 1.0) { - phase -= 1.0; - } - output[i] = phase < 0.5 ? 2.0*phase : 2.0*(phase - 1.0); - } - mPhase = phase; - } - - void ComputeTriangle(AudioChunk *aOutput) - { - AllocateAudioBlock(1, aOutput); - float* output = static_cast(const_cast(aOutput->mChannelData[0])); - - TrackTicks ticks = mSource->GetCurrentPosition(); - uint32_t start, end; - FillBounds(output, ticks, start, end); - - double rate = 1.0 / mSource->SampleRate(); - double phase = mPhase; - for (uint32_t i = start; i < end; ++i) { - phase += ComputeFrequency(ticks, i) * rate; - if (phase > 1.0) { - phase -= 1.0; - } - if (phase < 0.25) { - output[i] = 4.0*phase; - } else if (phase < 0.75) { - output[i] = 1.0 - 4.0*(phase - 0.25); + if (fabs(denom) < std::numeric_limits::epsilon()) { + if (mPhase < 0.1f || mPhase > 2 * M_PI - 0.1f) { + blit = mAmplitudeAtZero; } else { - output[i] = 4.0*(phase - 0.75) - 1.0; + blit = -mAmplitudeAtZero; } + } else { + blit = sin(mNumberOfHarmonics * mPhase); + blit /= mSignalPeriod * denom; + } + return blit; + } + + float UnipolarBLIT() + { + float blit; + float denom = sin(mPhase); + + if (fabs(denom) <= std::numeric_limits::epsilon()) { + blit = mAmplitudeAtZero; + } else { + blit = sin(mNumberOfHarmonics * mPhase); + blit /= mSignalPeriod * denom; + } + + return blit; + } + + void ComputeSine(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd) + { + for (uint32_t i = aStart; i < aEnd; ++i) { + UpdateFrequencyIfNeeded(ticks, i); + + aOutput[i] = sin(mPhase); + + IncrementPhase(); + } + } + + void ComputeSquare(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd) + { + for (uint32_t i = aStart; i < aEnd; ++i) { + UpdateFrequencyIfNeeded(ticks, i); + // Integration to get us a square. It turns out we can have a + // pure integrator here. + mSquare += BipolarBLIT(); + aOutput[i] = mSquare; + // maybe we want to apply a gain, the wg has not decided yet + aOutput[i] *= 1.5; + IncrementPhase(); + } + } + + void ComputeSawtooth(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd) + { + float dcoffset; + for (uint32_t i = aStart; i < aEnd; ++i) { + UpdateFrequencyIfNeeded(ticks, i); + // DC offset so the Saw does not ramp up to infinity when integrating. + dcoffset = mFinalFrequency / mSource->SampleRate(); + // Integrate and offset so we get mAmplitudeAtZero sawtooth. We have a + // very low frequency component somewhere here, but I'm not sure where. + mSaw += UnipolarBLIT() - dcoffset; + // reverse the saw so we are spec compliant + aOutput[i] = -mSaw * 1.5; + + IncrementPhase(); + } + } + + void ComputeTriangle(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd) + { + for (uint32_t i = aStart; i < aEnd; ++i) { + UpdateFrequencyIfNeeded(ticks, i); + // Integrate to get a square + mSquare += BipolarBLIT(); + // Leaky integrate to get a triangle. We get too much dc offset if we don't + // leaky integrate here. + // C6 = k0 / period + // (period is samplingrate / frequency, k0 = (PI/2)/(2*PI)) = 0.25 + float C6 = 0.25 / (mSource->SampleRate() / mFinalFrequency); + mTriangle = mTriangle * sLeak + mSquare + C6; + // DC Block, and scale back to [-1.0; 1.0] + aOutput[i] = mDCBlocker.Process(mTriangle) / (mSignalPeriod/2) * 1.5; + + IncrementPhase(); + } + } + + void ComputeCustom(float* aOutput, + TrackTicks ticks, + uint32_t aStart, + uint32_t aEnd) + { + MOZ_ASSERT(mPeriodicWave, "No custom waveform data"); + + uint32_t periodicWaveSize = mPeriodicWave->periodicWaveSize(); + float* higherWaveData = nullptr; + float* lowerWaveData = nullptr; + float tableInterpolationFactor; + float rate = 1.0 / mSource->SampleRate(); + + for (uint32_t i = aStart; i < aEnd; ++i) { + UpdateFrequencyIfNeeded(ticks, i); + mPeriodicWave->waveDataForFundamentalFrequency(mFinalFrequency, + lowerWaveData, + higherWaveData, + tableInterpolationFactor); + // mPhase runs 0..periodicWaveSize here instead of 0..2*M_PI. + mPhase += periodicWaveSize * mFinalFrequency * rate; + if (mPhase >= periodicWaveSize) { + mPhase -= periodicWaveSize; + } + // Bilinear interpolation between adjacent samples in each table. + uint32_t j1 = floor(mPhase); + uint32_t j2 = j1 + 1; + if (j2 >= periodicWaveSize) { + j2 -= periodicWaveSize; + } + float sampleInterpolationFactor = mPhase - j1; + float lower = sampleInterpolationFactor * lowerWaveData[j1] + + (1 - sampleInterpolationFactor) * lowerWaveData[j2]; + float higher = sampleInterpolationFactor * higherWaveData[j1] + + (1 - sampleInterpolationFactor) * higherWaveData[j2]; + aOutput[i] = tableInterpolationFactor * lower + + (1 - tableInterpolationFactor) * higher; } - mPhase = phase; } void ComputeSilence(AudioChunk *aOutput) @@ -261,25 +449,38 @@ public: *aFinished = true; return; } + + AllocateAudioBlock(1, aOutput); + float* output = static_cast( + const_cast(aOutput->mChannelData[0])); + + uint32_t start, end; + FillBounds(output, ticks, start, end); + // Synthesize the correct waveform. - switch (mType) { + switch(mType) { case OscillatorType::Sine: - ComputeSine(aOutput); + ComputeSine(output, ticks, start, end); break; case OscillatorType::Square: - ComputeSquare(aOutput); - break; - case OscillatorType::Sawtooth: - ComputeSawtooth(aOutput); + ComputeSquare(output, ticks, start, end); break; case OscillatorType::Triangle: - ComputeTriangle(aOutput); + ComputeTriangle(output, ticks, start, end); + break; + case OscillatorType::Sawtooth: + ComputeSawtooth(output, ticks, start, end); + break; + case OscillatorType::Custom: + ComputeCustom(output, ticks, start, end); break; default: ComputeSilence(aOutput); - } + }; + } + DCBlocker mDCBlocker; AudioNodeStream* mSource; AudioNodeStream* mDestination; TrackTicks mStart; @@ -287,7 +488,20 @@ public: AudioParamTimeline mFrequency; AudioParamTimeline mDetune; OscillatorType mType; - double mPhase; + float mPhase; + float mFinalFrequency; + uint32_t mNumberOfHarmonics; + float mSignalPeriod; + float mAmplitudeAtZero; + float mPhaseIncrement; + float mSquare; + float mTriangle; + float mSaw; + float mPhaseWrap; + bool mRecomputeFrequency; + nsRefPtr mCustom; + uint32_t mCustomLength; + nsAutoPtr mPeriodicWave; }; OscillatorNode::OscillatorNode(AudioContext* aContext) @@ -338,10 +552,25 @@ OscillatorNode::SendDetuneToStream(AudioNode* aNode) void OscillatorNode::SendTypeToStream() { - SendInt32ParameterToStream(OscillatorNodeEngine::TYPE, static_cast(mType)); if (mType == OscillatorType::Custom) { - // TODO: Send the custom wave table somehow + // The engine assumes we'll send the custom data before updating the type. + SendPeriodicWaveToStream(); } + SendInt32ParameterToStream(OscillatorNodeEngine::TYPE, static_cast(mType)); +} + +void OscillatorNode::SendPeriodicWaveToStream() +{ + NS_ASSERTION(mType == OscillatorType::Custom, + "Sending custom waveform to engine thread with non-custom type"); + AudioNodeStream* ns = static_cast(mStream.get()); + MOZ_ASSERT(ns, "Missing node stream."); + MOZ_ASSERT(mPeriodicWave, "Send called without PeriodicWave object."); + SendInt32ParameterToStream(OscillatorNodeEngine::PERIODICWAVE, + mPeriodicWave->DataLength()); + nsRefPtr data = + mPeriodicWave->GetThreadSharedBuffer(); + ns->SetBuffer(data.forget()); } void diff --git a/content/media/webaudio/OscillatorNode.h b/content/media/webaudio/OscillatorNode.h index ca19c931fcb..8059b9c485a 100644 --- a/content/media/webaudio/OscillatorNode.h +++ b/content/media/webaudio/OscillatorNode.h @@ -77,9 +77,10 @@ public: // Shut up the compiler warning break; } - if (aType == OscillatorType::Custom) { - aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + // ::Custom can only be set by setPeriodicWave(). + // https://github.com/WebAudio/web-audio-api/issues/105 for exception. + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } mType = aType; @@ -108,6 +109,7 @@ public: void SetPeriodicWave(PeriodicWave& aPeriodicWave) { mPeriodicWave = &aPeriodicWave; + // SendTypeToStream will call SendPeriodicWaveToStream for us. mType = OscillatorType::Custom; SendTypeToStream(); } @@ -120,6 +122,7 @@ private: static void SendFrequencyToStream(AudioNode* aNode); static void SendDetuneToStream(AudioNode* aNode); void SendTypeToStream(); + void SendPeriodicWaveToStream(); private: OscillatorType mType; diff --git a/content/media/webaudio/PeriodicWave.cpp b/content/media/webaudio/PeriodicWave.cpp index c156e5b8f2c..76c6202c451 100644 --- a/content/media/webaudio/PeriodicWave.cpp +++ b/content/media/webaudio/PeriodicWave.cpp @@ -18,13 +18,30 @@ NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(PeriodicWave, Release) PeriodicWave::PeriodicWave(AudioContext* aContext, const float* aRealData, - uint32_t aRealDataLength, const float* aImagData, - uint32_t aImagDataLength) + const uint32_t aLength, + ErrorResult& aRv) : mContext(aContext) { MOZ_ASSERT(aContext); SetIsDOMBinding(); + + // Caller should have checked this and thrown. + MOZ_ASSERT(aLength > 0); + MOZ_ASSERT(aLength <= 4096); + mLength = aLength; + + // Copy coefficient data. The two arrays share an allocation. + mCoefficients = new ThreadSharedFloatArrayBufferList(2); + float* buffer = static_cast(malloc(aLength*sizeof(float)*2)); + if (buffer == nullptr) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + PodCopy(buffer, aRealData, aLength); + mCoefficients->SetData(0, buffer, buffer); + PodCopy(buffer+aLength, aImagData, aLength); + mCoefficients->SetData(1, nullptr, buffer+aLength); } JSObject* @@ -33,6 +50,6 @@ PeriodicWave::WrapObject(JSContext* aCx, JS::Handle aScope) return PeriodicWaveBinding::Wrap(aCx, aScope, this); } -} -} +} // namespace dom +} // namespace mozilla diff --git a/content/media/webaudio/PeriodicWave.h b/content/media/webaudio/PeriodicWave.h index c980f3ac970..8acb517cf13 100644 --- a/content/media/webaudio/PeriodicWave.h +++ b/content/media/webaudio/PeriodicWave.h @@ -12,6 +12,7 @@ #include "mozilla/Attributes.h" #include "EnableWebAudioCheck.h" #include "AudioContext.h" +#include "AudioNodeEngine.h" #include "nsAutoPtr.h" namespace mozilla { @@ -24,9 +25,9 @@ class PeriodicWave MOZ_FINAL : public nsWrapperCache, public: PeriodicWave(AudioContext* aContext, const float* aRealData, - uint32_t aRealDataLength, const float* aImagData, - uint32_t aImagDataLength); + const uint32_t aLength, + ErrorResult& aRv); NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PeriodicWave) NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(PeriodicWave) @@ -39,12 +40,23 @@ public: virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aScope) MOZ_OVERRIDE; + uint32_t DataLength() const + { + return mLength; + } + + ThreadSharedFloatArrayBufferList* GetThreadSharedBuffer() const + { + return mCoefficients; + } + private: nsRefPtr mContext; + nsRefPtr mCoefficients; + uint32_t mLength; }; } } #endif - diff --git a/content/media/webaudio/blink/PeriodicWave.cpp b/content/media/webaudio/blink/PeriodicWave.cpp new file mode 100644 index 00000000000..eadeea9d237 --- /dev/null +++ b/content/media/webaudio/blink/PeriodicWave.cpp @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "PeriodicWave.h" +#include +#include +#include "mozilla/FFTBlock.h" + +const unsigned PeriodicWaveSize = 4096; // This must be a power of two. +const unsigned NumberOfRanges = 36; // There should be 3 * log2(PeriodicWaveSize) 1/3 octave ranges. +const float CentsPerRange = 1200 / 3; // 1/3 Octave. + +using namespace mozilla; +using mozilla::dom::OscillatorType; + +namespace WebCore { + +PeriodicWave* PeriodicWave::create(float sampleRate, + const float* real, + const float* imag, + size_t numberOfComponents) +{ + bool isGood = real && imag && numberOfComponents > 0 && + numberOfComponents <= PeriodicWaveSize; + MOZ_ASSERT(isGood); + if (isGood) { + PeriodicWave* periodicWave = new PeriodicWave(sampleRate); + periodicWave->createBandLimitedTables(real, imag, numberOfComponents); + return periodicWave; + } + return 0; +} + +PeriodicWave* PeriodicWave::createSine(float sampleRate) +{ + PeriodicWave* periodicWave = new PeriodicWave(sampleRate); + periodicWave->generateBasicWaveform(OscillatorType::Sine); + return periodicWave; +} + +PeriodicWave* PeriodicWave::createSquare(float sampleRate) +{ + PeriodicWave* periodicWave = new PeriodicWave(sampleRate); + periodicWave->generateBasicWaveform(OscillatorType::Square); + return periodicWave; +} + +PeriodicWave* PeriodicWave::createSawtooth(float sampleRate) +{ + PeriodicWave* periodicWave = new PeriodicWave(sampleRate); + periodicWave->generateBasicWaveform(OscillatorType::Sawtooth); + return periodicWave; +} + +PeriodicWave* PeriodicWave::createTriangle(float sampleRate) +{ + PeriodicWave* periodicWave = new PeriodicWave(sampleRate); + periodicWave->generateBasicWaveform(OscillatorType::Triangle); + return periodicWave; +} + +PeriodicWave::PeriodicWave(float sampleRate) + : m_sampleRate(sampleRate) + , m_periodicWaveSize(PeriodicWaveSize) + , m_numberOfRanges(NumberOfRanges) + , m_centsPerRange(CentsPerRange) +{ + float nyquist = 0.5 * m_sampleRate; + m_lowestFundamentalFrequency = nyquist / maxNumberOfPartials(); + m_rateScale = m_periodicWaveSize / m_sampleRate; +} + +void PeriodicWave::waveDataForFundamentalFrequency(float fundamentalFrequency, float* &lowerWaveData, float* &higherWaveData, float& tableInterpolationFactor) +{ + // Negative frequencies are allowed, in which case we alias + // to the positive frequency. + fundamentalFrequency = fabsf(fundamentalFrequency); + + // Calculate the pitch range. + float ratio = fundamentalFrequency > 0 ? fundamentalFrequency / m_lowestFundamentalFrequency : 0.5; + float centsAboveLowestFrequency = logf(ratio)/logf(2.0f) * 1200; + + // Add one to round-up to the next range just in time to truncate + // partials before aliasing occurs. + float pitchRange = 1 + centsAboveLowestFrequency / m_centsPerRange; + + pitchRange = std::max(pitchRange, 0.0f); + pitchRange = std::min(pitchRange, static_cast(m_numberOfRanges - 1)); + + // The words "lower" and "higher" refer to the table data having + // the lower and higher numbers of partials. It's a little confusing + // since the range index gets larger the more partials we cull out. + // So the lower table data will have a larger range index. + unsigned rangeIndex1 = static_cast(pitchRange); + unsigned rangeIndex2 = rangeIndex1 < m_numberOfRanges - 1 ? rangeIndex1 + 1 : rangeIndex1; + + lowerWaveData = m_bandLimitedTables[rangeIndex2]->Elements(); + higherWaveData = m_bandLimitedTables[rangeIndex1]->Elements(); + + // Ranges from 0 -> 1 to interpolate between lower -> higher. + tableInterpolationFactor = pitchRange - rangeIndex1; +} + +unsigned PeriodicWave::maxNumberOfPartials() const +{ + return m_periodicWaveSize / 2; +} + +unsigned PeriodicWave::numberOfPartialsForRange(unsigned rangeIndex) const +{ + // Number of cents below nyquist where we cull partials. + float centsToCull = rangeIndex * m_centsPerRange; + + // A value from 0 -> 1 representing what fraction of the partials to keep. + float cullingScale = pow(2, -centsToCull / 1200); + + // The very top range will have all the partials culled. + unsigned numberOfPartials = cullingScale * maxNumberOfPartials(); + + return numberOfPartials; +} + +// Convert into time-domain wave buffers. +// One table is created for each range for non-aliasing playback +// at different playback rates. Thus, higher ranges have more +// high-frequency partials culled out. +void PeriodicWave::createBandLimitedTables(const float* realData, const float* imagData, unsigned numberOfComponents) +{ + float normalizationScale = 1; + + unsigned fftSize = m_periodicWaveSize; + unsigned halfSize = fftSize / 2 + 1; + unsigned i; + + numberOfComponents = std::min(numberOfComponents, halfSize); + + m_bandLimitedTables.SetCapacity(m_numberOfRanges); + + for (unsigned rangeIndex = 0; rangeIndex < m_numberOfRanges; ++rangeIndex) { + // This FFTBlock is used to cull partials (represented by frequency bins). + FFTBlock frame(fftSize); + float* realP = new float[halfSize]; + float* imagP = new float[halfSize]; + + // Copy from loaded frequency data and scale. + float scale = fftSize; + AudioBufferCopyWithScale(realData, scale, realP, numberOfComponents); + AudioBufferCopyWithScale(imagData, scale, imagP, numberOfComponents); + + // If fewer components were provided than 1/2 FFT size, + // then clear the remaining bins. + for (i = numberOfComponents; i < halfSize; ++i) { + realP[i] = 0; + imagP[i] = 0; + } + + // Generate complex conjugate because of the way the + // inverse FFT is defined. + float minusOne = -1; + AudioBufferInPlaceScale(imagP, 1, minusOne, halfSize); + + // Find the starting bin where we should start culling. + // We need to clear out the highest frequencies to band-limit + // the waveform. + unsigned numberOfPartials = numberOfPartialsForRange(rangeIndex); + + // Cull the aliasing partials for this pitch range. + for (i = numberOfPartials + 1; i < halfSize; ++i) { + realP[i] = 0; + imagP[i] = 0; + } + // Clear nyquist if necessary. + if (numberOfPartials < halfSize) + realP[halfSize-1] = 0; + + // Clear any DC-offset. + realP[0] = 0; + + // Clear values which have no effect. + imagP[0] = 0; + imagP[halfSize-1] = 0; + + // Create the band-limited table. + AudioFloatArray* table = new AudioFloatArray(m_periodicWaveSize); + m_bandLimitedTables.AppendElement(table); + + // Apply an inverse FFT to generate the time-domain table data. + float* data = m_bandLimitedTables[rangeIndex]->Elements(); + frame.PerformInverseFFT(realP, imagP, data); + + // For the first range (which has the highest power), calculate + // its peak value then compute normalization scale. + if (!rangeIndex) { + float maxValue; + maxValue = AudioBufferPeakValue(data, m_periodicWaveSize); + + if (maxValue) + normalizationScale = 1.0f / maxValue; + } + + // Apply normalization scale. + AudioBufferInPlaceScale(data, 1, normalizationScale, m_periodicWaveSize); + } +} + +void PeriodicWave::generateBasicWaveform(OscillatorType shape) +{ + const float piFloat = M_PI; + unsigned fftSize = periodicWaveSize(); + unsigned halfSize = fftSize / 2 + 1; + + AudioFloatArray real(halfSize); + AudioFloatArray imag(halfSize); + float* realP = real.Elements(); + float* imagP = imag.Elements(); + + // Clear DC and Nyquist. + realP[0] = 0; + imagP[0] = 0; + realP[halfSize-1] = 0; + imagP[halfSize-1] = 0; + + for (unsigned n = 1; n < halfSize; ++n) { + float omega = 2 * piFloat * n; + float invOmega = 1 / omega; + + // Fourier coefficients according to standard definition. + float a; // Coefficient for cos(). + float b; // Coefficient for sin(). + + // Calculate Fourier coefficients depending on the shape. + // Note that the overall scaling (magnitude) of the waveforms + // is normalized in createBandLimitedTables(). + switch (shape) { + case OscillatorType::Sine: + // Standard sine wave function. + a = 0; + b = (n == 1) ? 1 : 0; + break; + case OscillatorType::Square: + // Square-shaped waveform with the first half its maximum value + // and the second half its minimum value. + a = 0; + b = invOmega * ((n & 1) ? 2 : 0); + break; + case OscillatorType::Sawtooth: + // Sawtooth-shaped waveform with the first half ramping from + // zero to maximum and the second half from minimum to zero. + a = 0; + b = -invOmega * cos(0.5 * omega); + break; + case OscillatorType::Triangle: + // Triangle-shaped waveform going from its maximum value to + // its minimum value then back to the maximum value. + a = (4 - 4 * cos(0.5 * omega)) / (n * n * piFloat * piFloat); + b = 0; + break; + default: + NS_NOTREACHED("invalid oscillator type"); + a = 0; + b = 0; + break; + } + + realP[n] = a; + imagP[n] = b; + } + + createBandLimitedTables(realP, imagP, halfSize); +} + +} // namespace WebCore diff --git a/content/media/webaudio/blink/PeriodicWave.h b/content/media/webaudio/blink/PeriodicWave.h new file mode 100644 index 00000000000..9949e8bea78 --- /dev/null +++ b/content/media/webaudio/blink/PeriodicWave.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PeriodicWave_h +#define PeriodicWave_h + +#include "mozilla/dom/OscillatorNodeBinding.h" +#include +#include + +namespace WebCore { + +typedef nsTArray AudioFloatArray; + +class PeriodicWave { +public: + static PeriodicWave* createSine(float sampleRate); + static PeriodicWave* createSquare(float sampleRate); + static PeriodicWave* createSawtooth(float sampleRate); + static PeriodicWave* createTriangle(float sampleRate); + + // Creates an arbitrary periodic wave given the frequency components + // (Fourier coefficients). + static PeriodicWave* create(float sampleRate, + const float* real, + const float* imag, + size_t numberOfComponents); + + // Returns pointers to the lower and higher wave data for the pitch range + // containing the given fundamental frequency. These two tables are in + // adjacent "pitch" ranges where the higher table will have the maximum + // number of partials which won't alias when played back at this + // fundamental frequency. The lower wave is the next range containing fewer + // partials than the higher wave. Interpolation between these two tables + // can be made according to tableInterpolationFactor. Where values + // from 0 -> 1 interpolate between lower -> higher. + void waveDataForFundamentalFrequency(float, float* &lowerWaveData, float* &higherWaveData, float& tableInterpolationFactor); + + // Returns the scalar multiplier to the oscillator frequency to calculate + // wave buffer phase increment. + float rateScale() const { return m_rateScale; } + + unsigned periodicWaveSize() const { return m_periodicWaveSize; } + float sampleRate() const { return m_sampleRate; } + +private: + explicit PeriodicWave(float sampleRate); + + void generateBasicWaveform(mozilla::dom::OscillatorType); + + float m_sampleRate; + unsigned m_periodicWaveSize; + unsigned m_numberOfRanges; + float m_centsPerRange; + + // The lowest frequency (in Hertz) where playback will include all of the + // partials. Playing back lower than this frequency will gradually lose + // more high-frequency information. + // This frequency is quite low (~10Hz @ // 44.1KHz) + float m_lowestFundamentalFrequency; + + float m_rateScale; + + unsigned numberOfRanges() const { return m_numberOfRanges; } + + // Maximum possible number of partials (before culling). + unsigned maxNumberOfPartials() const; + + unsigned numberOfPartialsForRange(unsigned rangeIndex) const; + + // Creates tables based on numberOfComponents Fourier coefficients. + void createBandLimitedTables(const float* real, const float* imag, unsigned numberOfComponents); + nsTArray > m_bandLimitedTables; +}; + +} // namespace WebCore + +#endif // PeriodicWave_h diff --git a/content/media/webaudio/blink/moz.build b/content/media/webaudio/blink/moz.build index fc0e3e3a18f..4168bc8cf2e 100644 --- a/content/media/webaudio/blink/moz.build +++ b/content/media/webaudio/blink/moz.build @@ -17,6 +17,7 @@ CPP_SOURCES += [ 'HRTFElevation.cpp', 'HRTFKernel.cpp', 'HRTFPanner.cpp', + 'PeriodicWave.cpp', 'Reverb.cpp', 'ReverbAccumulationBuffer.cpp', 'ReverbConvolver.cpp', diff --git a/content/media/webaudio/test/test_oscillatorNode.html b/content/media/webaudio/test/test_oscillatorNode.html index f8d6a0412fb..4f15e5613ee 100644 --- a/content/media/webaudio/test/test_oscillatorNode.html +++ b/content/media/webaudio/test/test_oscillatorNode.html @@ -22,10 +22,10 @@ addLoadEvent(function() { is(osc.type, "sine", "Correct default type"); expectException(function() { osc.type = "custom"; - }, DOMException.NOT_SUPPORTED_ERR); + }, DOMException.INVALID_STATE_ERR); expectException(function() { osc.type = osc.CUSTOM; - }, DOMException.NOT_SUPPORTED_ERR); + }, DOMException.INVALID_STATE_ERR); is(osc.type, "sine", "Cannot set the type to custom"); is(osc.frequency.value, 440, "Correct default frequency value"); is(osc.detune.value, 0, "Correct default detine value"); @@ -43,6 +43,12 @@ addLoadEvent(function() { osc.type = types[i]; } + // Verify setPeriodicWave() + var real = new Float32Array([1.0, 0.5, 0.25, 0.125]); + var imag = new Float32Array([1.0, 0.7, -1.0, 0.5]); + osc.setPeriodicWave(context.createPeriodicWave(real, imag)); + is(osc.type, "custom", "Failed to set custom waveform"); + SimpleTest.finish(); }); diff --git a/content/smil/Makefile.in b/content/smil/Makefile.in deleted file mode 100644 index e127ed31561..00000000000 --- a/content/smil/Makefile.in +++ /dev/null @@ -1,10 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -LOCAL_INCLUDES += \ - -I$(srcdir)/../base/src \ - -I$(srcdir)/../../layout/style \ - -I$(srcdir)/../events/src \ - $(NULL) diff --git a/content/smil/moz.build b/content/smil/moz.build index 0e4c6f74569..2892d20f10f 100644 --- a/content/smil/moz.build +++ b/content/smil/moz.build @@ -67,3 +67,8 @@ LIBXUL_LIBRARY = True LIBRARY_NAME = 'gkconsmil_s' +LOCAL_INCLUDES += [ + '../base/src', + '../events/src', + '/layout/style', +] diff --git a/content/svg/document/src/Makefile.in b/content/svg/document/src/Makefile.in deleted file mode 100644 index fe72b0d45a8..00000000000 --- a/content/svg/document/src/Makefile.in +++ /dev/null @@ -1,17 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -include $(topsrcdir)/config/rules.mk - -INCLUDES += \ - -I$(srcdir) \ - -I$(srcdir)/../../../xml/document/src \ - -I$(srcdir)/../../../html/document/src \ - -I$(srcdir)/../../../../layout/style \ - -I$(srcdir)/../../../base/src \ - -I$(srcdir)/../../../events/src \ - -I$(topsrcdir)/xpcom/ds \ - -I$(topsrcdir)/content/svg/content/src \ - $(NULL) diff --git a/content/svg/document/src/moz.build b/content/svg/document/src/moz.build index d0723654f0f..b5b5f8264f8 100644 --- a/content/svg/document/src/moz.build +++ b/content/svg/document/src/moz.build @@ -20,3 +20,12 @@ FAIL_ON_WARNINGS = True LIBXUL_LIBRARY = True +LOCAL_INCLUDES += [ + '/content/base/src', + '/content/events/src', + '/content/html/document/src', + '/content/svg/content/src', + '/content/xml/document/src', + '/layout/style', + '/xpcom/ds', +] diff --git a/content/xbl/src/Makefile.in b/content/xbl/src/Makefile.in deleted file mode 100644 index a0f13f47b16..00000000000 --- a/content/xbl/src/Makefile.in +++ /dev/null @@ -1,18 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -include $(topsrcdir)/config/rules.mk - -LOCAL_INCLUDES = \ - -I$(srcdir)/../../base/src \ - -I$(srcdir)/../../html/document/src \ - -I$(srcdir)/../../xml/document/src \ - -I$(srcdir)/../../xul/content/src \ - -I$(srcdir)/../../xul/document/src \ - -I$(srcdir)/../../events/src \ - -I$(srcdir)/../../../layout/style \ - -I$(srcdir)/../../../dom/base \ - -I$(topsrcdir)/xpcom/ds \ - $(NULL) diff --git a/content/xbl/src/moz.build b/content/xbl/src/moz.build index e3cfed1451f..71a1f0537e4 100644 --- a/content/xbl/src/moz.build +++ b/content/xbl/src/moz.build @@ -44,3 +44,14 @@ MSVC_ENABLE_PGO = True LIBRARY_NAME = 'gkconxbl_s' +LOCAL_INCLUDES += [ + '/content/base/src', + '/content/events/src', + '/content/html/document/src', + '/content/xml/document/src', + '/content/xul/content/src', + '/content/xul/document/src', + '/dom/base', + '/layout/style', + '/xpcom/ds', +] diff --git a/content/xml/document/src/Makefile.in b/content/xml/document/src/Makefile.in deleted file mode 100644 index 0bdbcd70a1e..00000000000 --- a/content/xml/document/src/Makefile.in +++ /dev/null @@ -1,18 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -include $(topsrcdir)/config/rules.mk - -LOCAL_INCLUDES = \ - -I$(srcdir) \ - -I$(srcdir)/../../../html/document/src \ - -I$(srcdir)/../../../../layout/style \ - -I$(srcdir)/../../../base/src \ - -I$(srcdir)/../../../xul/content/src \ - -I$(srcdir)/../../../events/src \ - -I$(srcdir)/../../../../dom/base \ - -I$(srcdir)/../../../../caps/include \ - -I$(topsrcdir)/xpcom/ds \ - $(NULL) diff --git a/content/xml/document/src/moz.build b/content/xml/document/src/moz.build index 105fe79cff1..d7770082265 100644 --- a/content/xml/document/src/moz.build +++ b/content/xml/document/src/moz.build @@ -25,3 +25,13 @@ LIBXUL_LIBRARY = True MSVC_ENABLE_PGO = True +LOCAL_INCLUDES += [ + '/caps/include', + '/content/base/src', + '/content/events/src', + '/content/html/document/src', + '/content/xul/content/src', + '/dom/base', + '/layout/style', + '/xpcom/ds', + ] diff --git a/content/xslt/src/base/Makefile.in b/content/xslt/src/base/Makefile.in deleted file mode 100644 index c967747abe0..00000000000 --- a/content/xslt/src/base/Makefile.in +++ /dev/null @@ -1,13 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -include $(topsrcdir)/config/rules.mk - -INCLUDES += \ - -I$(srcdir)/../../public \ - -I$(srcdir) \ - -I$(srcdir)/../xml \ - -I$(srcdir)/../xpath \ - -I$(srcdir)/../xslt \ - $(NULL) diff --git a/content/xslt/src/base/moz.build b/content/xslt/src/base/moz.build index 073bd662fe4..23f8719f328 100644 --- a/content/xslt/src/base/moz.build +++ b/content/xslt/src/base/moz.build @@ -20,3 +20,9 @@ FAIL_ON_WARNINGS = True LIBXUL_LIBRARY = True +LOCAL_INCLUDES += [ + '../../public', + '../xml', + '../xpath', + '../xslt', +] diff --git a/content/xslt/src/xml/Makefile.in b/content/xslt/src/xml/Makefile.in deleted file mode 100644 index 225e6c8041d..00000000000 --- a/content/xslt/src/xml/Makefile.in +++ /dev/null @@ -1,13 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -include $(topsrcdir)/config/rules.mk - -INCLUDES += \ - -I$(srcdir)/../base \ - -I$(srcdir) \ - -I$(srcdir)/../xpath \ - -I$(srcdir)/../xslt \ - -I$(srcdir)/../../../base/src \ - $(NULL) diff --git a/content/xslt/src/xml/moz.build b/content/xslt/src/xml/moz.build index 471e2db7c0f..d1d85e57190 100644 --- a/content/xslt/src/xml/moz.build +++ b/content/xslt/src/xml/moz.build @@ -17,3 +17,9 @@ FAIL_ON_WARNINGS = True LIBXUL_LIBRARY = True +LOCAL_INCLUDES += [ + '../base', + '../xpath', + '../xslt', + '/content/base/src', +] diff --git a/content/xslt/src/xpath/Makefile.in b/content/xslt/src/xpath/Makefile.in deleted file mode 100644 index 462cf326fc8..00000000000 --- a/content/xslt/src/xpath/Makefile.in +++ /dev/null @@ -1,12 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -include $(topsrcdir)/config/rules.mk - -INCLUDES += \ - -I$(srcdir)/../base \ - -I$(srcdir)/../xml \ - -I$(srcdir) \ - -I$(srcdir)/../xslt \ - $(NULL) diff --git a/content/xslt/src/xpath/moz.build b/content/xslt/src/xpath/moz.build index 23e60da2f68..33422fb1d47 100644 --- a/content/xslt/src/xpath/moz.build +++ b/content/xslt/src/xpath/moz.build @@ -57,3 +57,8 @@ FAIL_ON_WARNINGS = True LIBXUL_LIBRARY = True +LOCAL_INCLUDES += [ + '../base', + '../xml', + '../xslt', +] diff --git a/content/xslt/src/xslt/Makefile.in b/content/xslt/src/xslt/Makefile.in deleted file mode 100644 index 2388cc5d542..00000000000 --- a/content/xslt/src/xslt/Makefile.in +++ /dev/null @@ -1,18 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# For nsDependentJSString -LOCAL_INCLUDES += \ - -I$(topsrcdir)/dom/base \ - $(NULL) - -include $(topsrcdir)/config/rules.mk - -INCLUDES += \ - -I$(srcdir) \ - -I$(srcdir)/../base \ - -I$(srcdir)/../xml \ - -I$(srcdir)/../xpath \ - -I$(srcdir)/../../../base/src \ - $(NULL) diff --git a/content/xslt/src/xslt/moz.build b/content/xslt/src/xslt/moz.build index c86961f4263..5ecc48c310b 100644 --- a/content/xslt/src/xslt/moz.build +++ b/content/xslt/src/xslt/moz.build @@ -50,3 +50,12 @@ FAIL_ON_WARNINGS = True LIBXUL_LIBRARY = True +# For nsDependentJSString +LOCAL_INCLUDES += ["/dom/base"] + +LOCAL_INCLUDES += [ + '../base', + '../xml', + '../xpath', + '/content/base/src', +] diff --git a/content/xul/content/src/Makefile.in b/content/xul/content/src/Makefile.in deleted file mode 100644 index 385bcdf8e44..00000000000 --- a/content/xul/content/src/Makefile.in +++ /dev/null @@ -1,21 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -include $(topsrcdir)/config/rules.mk - -LOCAL_INCLUDES = \ - -I$(srcdir)/../../document/src \ - -I$(srcdir)/../../templates/src \ - -I$(srcdir)/../../../xml/content/src \ - -I$(srcdir)/../../../base/src \ - -I$(srcdir)/../../../xml/document/src \ - -I$(srcdir)/../../../../layout/generic \ - -I$(srcdir)/../../../../layout/style \ - -I$(srcdir)/../../../../layout/xul/base/src \ - -I$(srcdir)/../../../html/content/src \ - -I$(srcdir)/../../../events/src \ - -I$(srcdir)/../../../xbl/src \ - -I$(topsrcdir)/xpcom/ds \ - $(NULL) diff --git a/content/xul/content/src/moz.build b/content/xul/content/src/moz.build index 2c209b0cf72..e34d55b3f07 100644 --- a/content/xul/content/src/moz.build +++ b/content/xul/content/src/moz.build @@ -19,3 +19,17 @@ if CONFIG['MOZ_XUL']: FAIL_ON_WARNINGS = True +LOCAL_INCLUDES += [ + '../../document/src', + '../../templates/src', + '/content/base/src', + '/content/events/src', + '/content/html/content/src', + '/content/xbl/src', + '/content/xml/content/src', + '/content/xml/document/src', + '/layout/generic', + '/layout/style', + '/layout/xul/base/src', + '/xpcom/ds', +] diff --git a/content/xul/document/src/Makefile.in b/content/xul/document/src/Makefile.in deleted file mode 100644 index f956dda749a..00000000000 --- a/content/xul/document/src/Makefile.in +++ /dev/null @@ -1,20 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -include $(topsrcdir)/config/rules.mk - -LOCAL_INCLUDES = -I$(srcdir)/../../../base/src \ - -I$(srcdir)/../../content/src \ - -I$(srcdir)/../../templates/src \ - -I$(srcdir)/../../../../layout/base \ - -I$(srcdir)/../../../../layout/generic \ - -I$(srcdir)/../../../../layout/style \ - -I$(srcdir)/../../../../layout/xul/base/src \ - -I$(srcdir)/../../../xml/document/src \ - -I$(srcdir)/../../../xbl/src \ - -I$(srcdir)/../../../events/src \ - -I$(topsrcdir)/xpcom/ds \ - -I$(topsrcdir)/dom/base \ - $(NULL) diff --git a/content/xul/document/src/moz.build b/content/xul/document/src/moz.build index 04924010927..d8fb48e9034 100644 --- a/content/xul/document/src/moz.build +++ b/content/xul/document/src/moz.build @@ -27,3 +27,17 @@ LIBXUL_LIBRARY = True MSVC_ENABLE_PGO = True +LOCAL_INCLUDES += [ + '/content/base/src', + '/content/events/src', + '/content/xbl/src', + '/content/xml/document/src', + '/content/xul/content/src', + '/content/xul/templates/src', + '/dom/base', + '/layout/base', + '/layout/generic', + '/layout/style', + '/layout/xul/base/src', + '/xpcom/ds', +] diff --git a/content/xul/templates/src/Makefile.in b/content/xul/templates/src/Makefile.in deleted file mode 100644 index 390009456a5..00000000000 --- a/content/xul/templates/src/Makefile.in +++ /dev/null @@ -1,12 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -include $(topsrcdir)/config/rules.mk - -LOCAL_INCLUDES = -I$(srcdir)/../../../base/src \ - -I$(srcdir)/../../content/src \ - -I$(srcdir)/../../../../dom/base \ - -I$(srcdir)/../../../../layout/xul/tree/ \ - $(NULL) diff --git a/content/xul/templates/src/moz.build b/content/xul/templates/src/moz.build index 9317155aba5..a9f05d4a819 100644 --- a/content/xul/templates/src/moz.build +++ b/content/xul/templates/src/moz.build @@ -41,3 +41,9 @@ LIBXUL_LIBRARY = True MSVC_ENABLE_PGO = True +LOCAL_INCLUDES += [ + '../../content/src', + '/content/base/src', + '/dom/base', + '/layout/xul/tree/', +] diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 5d94f93dbb3..d2845d5bd06 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -750,7 +750,6 @@ nsDocShell::nsDocShell(): mIsAppTab(false), mUseGlobalHistory(false), mInPrivateBrowsing(false), - mDeviceSizeIsPageSize(false), mFiredUnloadEvent(false), mEODForCurrentDocument(false), mURIResultedInDocument(false), @@ -3933,27 +3932,6 @@ nsDocShell::GetCurrentSHEntry(nsISHEntry** aEntry, bool* aOSHE) return NS_OK; } -NS_IMETHODIMP -nsDocShell::SetDeviceSizeIsPageSize(bool aValue) -{ - if (mDeviceSizeIsPageSize != aValue) { - mDeviceSizeIsPageSize = aValue; - nsRefPtr presContext; - GetPresContext(getter_AddRefs(presContext)); - if (presContext) { - presContext->MediaFeatureValuesChanged(presContext->eAlwaysRebuildStyle); - } - } - return NS_OK; -} - -NS_IMETHODIMP -nsDocShell::GetDeviceSizeIsPageSize(bool* aValue) -{ - *aValue = mDeviceSizeIsPageSize; - return NS_OK; -} - void nsDocShell::ClearFrameHistory(nsISHEntry* aEntry) { diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index 4b9aa2458ca..d6eb1d14ba1 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -809,7 +809,6 @@ protected: bool mIsAppTab; bool mUseGlobalHistory; bool mInPrivateBrowsing; - bool mDeviceSizeIsPageSize; // This boolean is set to true right before we fire pagehide and generally // unset when we embed a new content viewer. While it's true no navigation diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl index 32683fc1de7..0447f39733f 100644 --- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -916,12 +916,4 @@ interface nsIDocShell : nsIDocShellTreeItem */ boolean isCommandEnabled(in string command); void doCommand(in string command); - - /** - * If deviceSizeIsPageSize is set to true, device-width/height media queries - * will be calculated from the page size, not the device size. - * - * Used by the Responsive Design View. - */ - [infallible] attribute boolean deviceSizeIsPageSize; }; diff --git a/docshell/build/Makefile.in b/docshell/build/Makefile.in deleted file mode 100644 index 4b2b8c1c3b7..00000000000 --- a/docshell/build/Makefile.in +++ /dev/null @@ -1,28 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -EXPORT_LIBRARY = 1 -SHARED_LIBRARY_LIBS= \ - ../base/$(LIB_PREFIX)basedocshell_s.$(LIB_SUFFIX) \ - $(DEPTH)/uriloader/base/$(LIB_PREFIX)uriloaderbase_s.$(LIB_SUFFIX) \ - $(DEPTH)/uriloader/exthandler/$(LIB_PREFIX)exthandler_s.$(LIB_SUFFIX) \ - $(DEPTH)/uriloader/prefetch/$(LIB_PREFIX)prefetch_s.$(LIB_SUFFIX) \ - ../shistory/src/$(LIB_PREFIX)shistory_s.$(LIB_SUFFIX) \ - $(NULL) - -include $(topsrcdir)/config/rules.mk - -LOCAL_INCLUDES = \ - -I$(srcdir) \ - -I$(srcdir)/../base \ - -I$(srcdir)/../shistory/src \ - -I$(topsrcdir)/uriloader/base \ - -I$(topsrcdir)/uriloader/prefetch \ - -I$(topsrcdir)/uriloader/exthandler \ - $(NULL) - -ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) -LOCAL_INCLUDES += -I$(topsrcdir)/uriloader/exthandler/mac -endif diff --git a/docshell/build/moz.build b/docshell/build/moz.build index 3d9c5030fca..f3b92d23ed0 100644 --- a/docshell/build/moz.build +++ b/docshell/build/moz.build @@ -18,3 +18,13 @@ LIBRARY_NAME = 'docshell' LIBXUL_LIBRARY = True +LOCAL_INCLUDES += [ + '../base', + '../shistory/src/', + '/uriloader/base', + '/uriloader/exthandler', + '/uriloader/prefetch', + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + LOCAL_INCLUDES += ['/uriloader/exthandler/mac'] diff --git a/docshell/shistory/src/Makefile.in b/docshell/shistory/src/Makefile.in deleted file mode 100644 index 4d38db3ec62..00000000000 --- a/docshell/shistory/src/Makefile.in +++ /dev/null @@ -1,8 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -include $(topsrcdir)/config/rules.mk - -LOCAL_INCLUDES += -I$(srcdir)/../../base diff --git a/docshell/shistory/src/moz.build b/docshell/shistory/src/moz.build index 4ca6d43c5a8..c6c3baed257 100644 --- a/docshell/shistory/src/moz.build +++ b/docshell/shistory/src/moz.build @@ -25,3 +25,6 @@ LIBXUL_LIBRARY = True MSVC_ENABLE_PGO = True +LOCAL_INCLUDES += [ + '/docshell/base', +] diff --git a/docshell/test/navigation/NavigationUtils.js b/docshell/test/navigation/NavigationUtils.js index cf422ad2f94..72c19da61a8 100644 --- a/docshell/test/navigation/NavigationUtils.js +++ b/docshell/test/navigation/NavigationUtils.js @@ -93,6 +93,10 @@ function isInaccessible(wnd, message) { } } +function getSubframe(win, i) { + return SpecialPowers.unwrap(SpecialPowers.wrap(win)[i]); +} + /////////////////////////////////////////////////////////////////////////// // Functions that require UniversalXPConnect privilege /////////////////////////////////////////////////////////////////////////// diff --git a/docshell/test/navigation/test_bug13871.html b/docshell/test/navigation/test_bug13871.html index 9ff0b9bb553..f0d08428c57 100644 --- a/docshell/test/navigation/test_bug13871.html +++ b/docshell/test/navigation/test_bug13871.html @@ -9,17 +9,18 @@ iframe { width: 90%; height: 50px; } + + diff --git a/layout/reftests/position-sticky/inline-3-ref.html b/layout/reftests/position-sticky/inline-3-ref.html new file mode 100644 index 00000000000..ace95403b31 --- /dev/null +++ b/layout/reftests/position-sticky/inline-3-ref.html @@ -0,0 +1,36 @@ + + + + + + + + + +
+
+
+ + diff --git a/layout/reftests/position-sticky/inline-3.html b/layout/reftests/position-sticky/inline-3.html new file mode 100644 index 00000000000..0a28e74e424 --- /dev/null +++ b/layout/reftests/position-sticky/inline-3.html @@ -0,0 +1,42 @@ + + + + + CSS Test: Sticky Positioning - inline with margins + + + + + + + +
+
+
+
+
+ + diff --git a/layout/reftests/position-sticky/reftest.list b/layout/reftests/position-sticky/reftest.list index 0d7fed3963b..9abd7ae355d 100644 --- a/layout/reftests/position-sticky/reftest.list +++ b/layout/reftests/position-sticky/reftest.list @@ -41,3 +41,8 @@ fuzzy-if(Android,4,1) == containing-block-1.html containing-block-1-ref.html == overconstrained-1.html overconstrained-1-ref.html == overconstrained-2.html overconstrained-2-ref.html == overconstrained-3.html overconstrained-3-ref.html +== inline-1.html inline-1-ref.html +== inline-2.html inline-2-ref.html +fails == inline-3.html inline-3-ref.html # bug 916302 +fails == column-contain-1a.html column-contain-1-ref.html +== column-contain-1b.html column-contain-1-ref.html diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp index 883436f7a10..7feaeb3966d 100644 --- a/layout/style/nsMediaFeatures.cpp +++ b/layout/style/nsMediaFeatures.cpp @@ -111,19 +111,14 @@ static nsSize GetDeviceSize(nsPresContext* aPresContext) { nsSize size; - - if (aPresContext->IsDeviceSizePageSize()) { - size = GetSize(aPresContext); - } else if (aPresContext->IsRootPaginatedDocument()) { + if (aPresContext->IsRootPaginatedDocument()) // We want the page size, including unprintable areas and margins. // XXX The spec actually says we want the "page sheet size", but // how is that different? size = aPresContext->GetPageSize(); - } else { + else GetDeviceContextFor(aPresContext)-> GetDeviceSurfaceDimensions(size.width, size.height); - } - return size; } diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java index a35ecc06bea..b9639a32b6c 100644 --- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -2257,7 +2257,7 @@ public class GeckoAppShell sEventDispatcher.registerEventListener(event, listener); } - static EventDispatcher getEventDispatcher() { + public static EventDispatcher getEventDispatcher() { return sEventDispatcher; } diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 2a93655da6c..8becefd39a9 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -220,14 +220,12 @@ FENNEC_JAVA_FILES = \ home/BookmarkThumbnailView.java \ home/BrowserSearch.java \ home/HistoryPage.java \ - home/HomeCursorLoaderCallbacks.java \ home/HomeFragment.java \ home/HomeListView.java \ home/HomePager.java \ home/HomePagerTabStrip.java \ home/HomeBanner.java \ home/FadedTextView.java \ - home/FaviconsLoader.java \ home/LastTabsPage.java \ home/MostRecentPage.java \ home/MostVisitedPage.java \ diff --git a/mobile/android/base/android-services-files.mk b/mobile/android/base/android-services-files.mk index f883ba29f38..59880c8c512 100644 --- a/mobile/android/base/android-services-files.mk +++ b/mobile/android/base/android-services-files.mk @@ -25,6 +25,7 @@ SYNC_JAVA_FILES := \ background/bagheera/BagheeraRequestDelegate.java \ background/bagheera/BoundedByteArrayEntity.java \ background/bagheera/DeflateHelper.java \ + background/common/DateUtils.java \ background/common/log/Logger.java \ background/common/log/writers/AndroidLevelCachingLogWriter.java \ background/common/log/writers/AndroidLogWriter.java \ diff --git a/mobile/android/base/animation/PropertyAnimator.java b/mobile/android/base/animation/PropertyAnimator.java index 5eb51516642..c163b08ff2f 100644 --- a/mobile/android/base/animation/PropertyAnimator.java +++ b/mobile/android/base/animation/PropertyAnimator.java @@ -160,12 +160,17 @@ public class PropertyAnimator implements Runnable { } // Try to start animation after any on-going layout round - // in the current view tree. - if (treeObserver != null && treeObserver.isAlive()) { + // in the current view tree. OnPreDrawListener seems broken + // on pre-Honeycomb devices, start animation immediatelly + // in this case. + if (Build.VERSION.SDK_INT >= 11 && treeObserver != null && treeObserver.isAlive()) { treeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { - treeObserver.removeOnPreDrawListener(this); + if (treeObserver.isAlive()) { + treeObserver.removeOnPreDrawListener(this); + } + mFramePoster.postFirstAnimationFrame(); return true; } diff --git a/mobile/android/base/background/common/DateUtils.java b/mobile/android/base/background/common/DateUtils.java new file mode 100644 index 00000000000..05412224d74 --- /dev/null +++ b/mobile/android/base/background/common/DateUtils.java @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.background.common; + +import java.util.Calendar; +import java.util.Formatter; +import java.util.TimeZone; + +public class DateUtils { + private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); + + public static final class DateFormatter { + private final Calendar calendar; + private final Formatter formatter; + private final StringBuilder builder; + + public DateFormatter() { + this.calendar = Calendar.getInstance(UTC); + this.builder = new StringBuilder(); // So we can reset it. + this.formatter = new Formatter(this.builder, null); + } + + public String getDateString(long time) { + calendar.setTimeInMillis(time); + builder.setLength(0); + return formatter.format("%04d-%02d-%02d", + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH) + 1, // 0-indexed. + calendar.get(Calendar.DAY_OF_MONTH)) + .toString(); + } + + public String getDateStringForDay(long day) { + return getDateString(GlobalConstants.MILLISECONDS_PER_DAY * day); + } + } + + public static int getDay(final long time) { + return (int) Math.floor(time / GlobalConstants.MILLISECONDS_PER_DAY); + } +} diff --git a/mobile/android/base/background/common/GlobalConstants.java.in b/mobile/android/base/background/common/GlobalConstants.java.in index a58ff6bf97b..206052a636e 100644 --- a/mobile/android/base/background/common/GlobalConstants.java.in +++ b/mobile/android/base/background/common/GlobalConstants.java.in @@ -47,4 +47,8 @@ public class GlobalConstants { public static String GECKO_PREFERENCES_CLASS = "org.mozilla.gecko.GeckoPreferences"; public static String GECKO_BROADCAST_ANNOUNCEMENTS_PREF_METHOD = "broadcastAnnouncementsPref"; public static String GECKO_BROADCAST_HEALTHREPORT_UPLOAD_PREF_METHOD = "broadcastHealthReportUploadPref"; + + // Common time values. + public static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; + public static final long MILLISECONDS_PER_SIX_MONTHS = 180 * MILLISECONDS_PER_DAY; } diff --git a/mobile/android/base/background/healthreport/EnvironmentBuilder.java b/mobile/android/base/background/healthreport/EnvironmentBuilder.java index 9d223d54af2..cb53a7afce0 100644 --- a/mobile/android/base/background/healthreport/EnvironmentBuilder.java +++ b/mobile/android/base/background/healthreport/EnvironmentBuilder.java @@ -9,6 +9,7 @@ import java.util.Iterator; import org.json.JSONObject; import org.mozilla.gecko.AppConstants; import org.mozilla.gecko.SysInfo; +import org.mozilla.gecko.background.common.GlobalConstants; import org.mozilla.gecko.background.common.log.Logger; import android.content.ContentProvider; @@ -77,7 +78,7 @@ public class EnvironmentBuilder { e.sysName = SysInfo.getName(); e.sysVersion = SysInfo.getReleaseVersion(); - e.profileCreation = (int) (info.getProfileCreationTime() / HealthReportConstants.MILLISECONDS_PER_DAY); + e.profileCreation = (int) (info.getProfileCreationTime() / GlobalConstants.MILLISECONDS_PER_DAY); // Corresponds to Gecko pref "extensions.blocklist.enabled". e.isBlocklistEnabled = (info.isBlocklistEnabled() ? 1 : 0); diff --git a/mobile/android/base/background/healthreport/HealthReportConstants.java.in b/mobile/android/base/background/healthreport/HealthReportConstants.java.in index cc6f9c44eb2..d2814278fa5 100644 --- a/mobile/android/base/background/healthreport/HealthReportConstants.java.in +++ b/mobile/android/base/background/healthreport/HealthReportConstants.java.in @@ -5,6 +5,8 @@ package org.mozilla.gecko.background.healthreport; +import org.mozilla.gecko.background.common.GlobalConstants; + public class HealthReportConstants { public static final String HEALTH_AUTHORITY = "@ANDROID_PACKAGE_NAME@.health"; public static final String GLOBAL_LOG_TAG = "GeckoHealth"; @@ -15,9 +17,6 @@ public class HealthReportConstants { */ public static final long EARLIEST_LAST_PING = 1367500000000L; - public static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; - public static final long MILLISECONDS_PER_SIX_MONTHS = 180 * MILLISECONDS_PER_DAY; - // Not `final` so we have the option to turn this on at runtime with a magic addon. public static boolean UPLOAD_FEATURE_DISABLED = false; @@ -29,15 +28,15 @@ public class HealthReportConstants { // intent is scheduled to be called by the Android Alarm Manager, not how // frequently we actually submit. public static final String PREF_SUBMISSION_INTENT_INTERVAL_MSEC = "healthreport_submission_intent_interval_msec"; - public static final long DEFAULT_SUBMISSION_INTENT_INTERVAL_MSEC = MILLISECONDS_PER_DAY / 24; + public static final long DEFAULT_SUBMISSION_INTENT_INTERVAL_MSEC = GlobalConstants.MILLISECONDS_PER_DAY / 24; public static final String ACTION_HEALTHREPORT_UPLOAD_PREF = "@ANDROID_PACKAGE_NAME@.HEALTHREPORT_UPLOAD_PREF"; public static final String PREF_MINIMUM_TIME_BETWEEN_UPLOADS = "healthreport_time_between_uploads"; - public static final long DEFAULT_MINIMUM_TIME_BETWEEN_UPLOADS = MILLISECONDS_PER_DAY; + public static final long DEFAULT_MINIMUM_TIME_BETWEEN_UPLOADS = GlobalConstants.MILLISECONDS_PER_DAY; public static final String PREF_MINIMUM_TIME_BEFORE_FIRST_SUBMISSION = "healthreport_time_before_first_submission"; - public static final long DEFAULT_MINIMUM_TIME_BEFORE_FIRST_SUBMISSION = MILLISECONDS_PER_DAY; + public static final long DEFAULT_MINIMUM_TIME_BEFORE_FIRST_SUBMISSION = GlobalConstants.MILLISECONDS_PER_DAY; public static final String PREF_MINIMUM_TIME_AFTER_FAILURE = "healthreport_time_after_failure"; public static final long DEFAULT_MINIMUM_TIME_AFTER_FAILURE = DEFAULT_SUBMISSION_INTENT_INTERVAL_MSEC; diff --git a/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java b/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java index 9ac4117e2dc..eb6227b8096 100644 --- a/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java +++ b/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java @@ -12,6 +12,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; import org.json.JSONObject; +import org.mozilla.gecko.background.common.DateUtils; import org.mozilla.gecko.background.common.log.Logger; import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields.FieldSpec; @@ -1029,7 +1030,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage { @Override public int getDay(long time) { - return HealthReportUtils.getDay(time); + return DateUtils.getDay(time); } @Override diff --git a/mobile/android/base/background/healthreport/HealthReportGenerator.java b/mobile/android/base/background/healthreport/HealthReportGenerator.java index d57e150522f..426a1c336a9 100644 --- a/mobile/android/base/background/healthreport/HealthReportGenerator.java +++ b/mobile/android/base/background/healthreport/HealthReportGenerator.java @@ -10,6 +10,7 @@ import java.util.Set; import org.json.JSONException; import org.json.JSONObject; +import org.mozilla.gecko.background.common.DateUtils.DateFormatter; import org.mozilla.gecko.background.common.log.Logger; import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field; @@ -22,9 +23,11 @@ public class HealthReportGenerator { private static final String LOG_TAG = "GeckoHealthGen"; private final HealthReportStorage storage; + private final DateFormatter dateFormatter; public HealthReportGenerator(HealthReportStorage storage) { this.storage = storage; + this.dateFormatter = new DateFormatter(); } @SuppressWarnings("static-method") @@ -76,10 +79,10 @@ public class HealthReportGenerator { JSONObject document = new JSONObject(); if (lastPingTime >= HealthReportConstants.EARLIEST_LAST_PING) { - document.put("lastPingDate", HealthReportUtils.getDateString(lastPingTime)); + document.put("lastPingDate", dateFormatter.getDateString(lastPingTime)); } - document.put("thisPingDate", HealthReportUtils.getDateString(now())); + document.put("thisPingDate", dateFormatter.getDateString(now())); document.put("version", PAYLOAD_VERSION); document.put("environments", getEnvironmentsJSON(currentEnvironment, envs)); @@ -147,7 +150,7 @@ public class HealthReportGenerator { if (dateChanged) { if (dateObject != null) { - days.put(HealthReportUtils.getDateStringForDay(lastDate), dateObject); + days.put(dateFormatter.getDateStringForDay(lastDate), dateObject); } dateObject = new JSONObject(); lastDate = cDate; @@ -179,7 +182,7 @@ public class HealthReportGenerator { cursor.moveToNext(); continue; } - days.put(HealthReportUtils.getDateStringForDay(lastDate), dateObject); + days.put(dateFormatter.getDateStringForDay(lastDate), dateObject); } finally { cursor.close(); } diff --git a/mobile/android/base/background/healthreport/HealthReportUtils.java b/mobile/android/base/background/healthreport/HealthReportUtils.java index e3c849a74f0..4c72b52d73a 100644 --- a/mobile/android/base/background/healthreport/HealthReportUtils.java +++ b/mobile/android/base/background/healthreport/HealthReportUtils.java @@ -4,13 +4,10 @@ package org.mozilla.gecko.background.healthreport; -import java.text.SimpleDateFormat; import java.util.HashSet; import java.util.Iterator; -import java.util.Locale; import java.util.Set; import java.util.SortedSet; -import java.util.TimeZone; import java.util.TreeSet; import java.util.UUID; @@ -25,24 +22,10 @@ import android.net.Uri; public class HealthReportUtils { public static final String LOG_TAG = HealthReportUtils.class.getSimpleName(); - public static int getDay(final long time) { - return (int) Math.floor(time / HealthReportConstants.MILLISECONDS_PER_DAY); - } - public static String getEnvironmentHash(final String input) { return DigestUtils.shaHex(input); } - public static String getDateStringForDay(long day) { - return getDateString(HealthReportConstants.MILLISECONDS_PER_DAY * day); - } - - public static String getDateString(long time) { - final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.US); - format.setTimeZone(TimeZone.getTimeZone("UTC")); - return format.format(time); - } - /** * Take an environment URI (one that identifies an environment) and produce an * event URI. diff --git a/mobile/android/base/background/healthreport/upload/AndroidSubmissionClient.java b/mobile/android/base/background/healthreport/upload/AndroidSubmissionClient.java index efce5e68271..addd248ba39 100644 --- a/mobile/android/base/background/healthreport/upload/AndroidSubmissionClient.java +++ b/mobile/android/base/background/healthreport/upload/AndroidSubmissionClient.java @@ -10,6 +10,7 @@ import java.util.Collection; import org.json.JSONObject; import org.mozilla.gecko.background.bagheera.BagheeraClient; import org.mozilla.gecko.background.bagheera.BagheeraRequestDelegate; +import org.mozilla.gecko.background.common.GlobalConstants; import org.mozilla.gecko.background.common.log.Logger; import org.mozilla.gecko.background.healthreport.EnvironmentBuilder; import org.mozilla.gecko.background.healthreport.HealthReportConstants; @@ -103,7 +104,7 @@ public class AndroidSubmissionClient implements SubmissionClient { return; } - long since = localTime - HealthReportConstants.MILLISECONDS_PER_SIX_MONTHS; + long since = localTime - GlobalConstants.MILLISECONDS_PER_SIX_MONTHS; long last = Math.max(getLastUploadLocalTime(), HealthReportConstants.EARLIEST_LAST_PING); if (!storage.hasEventSince(last)) { diff --git a/mobile/android/base/health/BrowserHealthReporter.java b/mobile/android/base/health/BrowserHealthReporter.java index 4b2c754fbb2..0a2309fe7fc 100644 --- a/mobile/android/base/health/BrowserHealthReporter.java +++ b/mobile/android/base/health/BrowserHealthReporter.java @@ -14,6 +14,7 @@ import org.mozilla.gecko.GeckoEvent; import org.mozilla.gecko.GeckoProfile; import org.mozilla.gecko.background.healthreport.EnvironmentBuilder; +import org.mozilla.gecko.background.common.GlobalConstants; import org.mozilla.gecko.background.healthreport.HealthReportConstants; import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage; import org.mozilla.gecko.background.healthreport.HealthReportGenerator; @@ -124,7 +125,7 @@ public class BrowserHealthReporter implements GeckoEventListener { GeckoProfile profile = GeckoAppShell.getGeckoInterface().getProfile(); String profilePath = profile.getDir().getAbsolutePath(); - long since = System.currentTimeMillis() - HealthReportConstants.MILLISECONDS_PER_SIX_MONTHS; + long since = System.currentTimeMillis() - GlobalConstants.MILLISECONDS_PER_SIX_MONTHS; long lastPingTime = Math.max(getLastUploadLocalTime(), HealthReportConstants.EARLIEST_LAST_PING); return generateReport(since, lastPingTime, profilePath); diff --git a/mobile/android/base/home/BookmarksPage.java b/mobile/android/base/home/BookmarksPage.java index f3c28e4cb40..238a7e95385 100644 --- a/mobile/android/base/home/BookmarksPage.java +++ b/mobile/android/base/home/BookmarksPage.java @@ -179,7 +179,7 @@ public class BookmarksPage extends HomeFragment { BrowserDB.invalidateCachedState(); // Create callbacks before the initial loader is started. - mLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager()); + mLoaderCallbacks = new CursorLoaderCallbacks(); mThumbnailsLoaderCallbacks = new ThumbnailsLoaderCallbacks(); loadIfVisible(); } @@ -453,11 +453,7 @@ public class BookmarksPage extends HomeFragment { /** * Loader callbacks for the LoaderManager of this fragment. */ - private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks { - public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) { - super(context, loaderManager); - } - + private class CursorLoaderCallbacks implements LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { switch(id) { @@ -472,11 +468,9 @@ public class BookmarksPage extends HomeFragment { case LOADER_ID_TOP_BOOKMARKS: { return new TopBookmarksLoader(getActivity()); } - - default: { - return super.onCreateLoader(id, args); - } } + + return null; } @Override @@ -485,7 +479,6 @@ public class BookmarksPage extends HomeFragment { switch(loaderId) { case LOADER_ID_BOOKMARKS_LIST: { mListAdapter.swapCursor(c); - loadFavicons(c); mList.setHeaderDividersEnabled(c != null && c.getCount() > 0); break; } @@ -509,11 +502,6 @@ public class BookmarksPage extends HomeFragment { } break; } - - default: { - super.onLoadFinished(loader, c); - break; - } } } @@ -534,18 +522,8 @@ public class BookmarksPage extends HomeFragment { break; } } - - default: { - super.onLoaderReset(loader); - break; - } } } - - @Override - public void onFaviconsLoaded() { - mListAdapter.notifyDataSetChanged(); - } } /** diff --git a/mobile/android/base/home/BrowserSearch.java b/mobile/android/base/home/BrowserSearch.java index d641ab14578..b7a512f3bdf 100644 --- a/mobile/android/base/home/BrowserSearch.java +++ b/mobile/android/base/home/BrowserSearch.java @@ -104,7 +104,7 @@ public class BrowserSearch extends HomeFragment // Whether search suggestions are enabled or not private boolean mSuggestionsEnabled; - // Callbacks used for the search and favicon cursor loaders + // Callbacks used for the search loader private CursorLoaderCallbacks mCursorLoaderCallbacks; // Callbacks used for the search suggestion loader @@ -286,17 +286,15 @@ public class BrowserSearch extends HomeFragment public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - final Activity activity = getActivity(); - // Intialize the search adapter - mAdapter = new SearchAdapter(activity); + mAdapter = new SearchAdapter(getActivity()); mList.setAdapter(mAdapter); // Only create an instance when we need it mSuggestionLoaderCallbacks = null; // Create callbacks before the initial loader is started - mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager()); + mCursorLoaderCallbacks = new CursorLoaderCallbacks(); loadIfVisible(); } @@ -772,49 +770,26 @@ public class BrowserSearch extends HomeFragment } } - private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks { - public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) { - super(context, loaderManager); - } - + private class CursorLoaderCallbacks implements LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { - if (id == LOADER_ID_SEARCH) { - return SearchLoader.createInstance(getActivity(), args); - } else { - return super.onCreateLoader(id, args); - } + return SearchLoader.createInstance(getActivity(), args); } @Override public void onLoadFinished(Loader loader, Cursor c) { - if (loader.getId() == LOADER_ID_SEARCH) { - mAdapter.swapCursor(c); + mAdapter.swapCursor(c); - // We should handle autocompletion based on the search term - // associated with the currently loader that has just provided - // the results. - SearchCursorLoader searchLoader = (SearchCursorLoader) loader; - handleAutocomplete(searchLoader.getSearchTerm(), c); - - loadFavicons(c); - } else { - super.onLoadFinished(loader, c); - } + // We should handle autocompletion based on the search term + // associated with the currently loader that has just provided + // the results. + SearchCursorLoader searchLoader = (SearchCursorLoader) loader; + handleAutocomplete(searchLoader.getSearchTerm(), c); } @Override public void onLoaderReset(Loader loader) { - if (loader.getId() == LOADER_ID_SEARCH) { - mAdapter.swapCursor(null); - } else { - super.onLoaderReset(loader); - } - } - - @Override - public void onFaviconsLoaded() { - mAdapter.notifyDataSetChanged(); + mAdapter.swapCursor(null); } } diff --git a/mobile/android/base/home/FaviconsLoader.java b/mobile/android/base/home/FaviconsLoader.java deleted file mode 100644 index b5da2d7b029..00000000000 --- a/mobile/android/base/home/FaviconsLoader.java +++ /dev/null @@ -1,115 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.home; - -import org.mozilla.gecko.favicons.Favicons; -import org.mozilla.gecko.db.BrowserDB; -import org.mozilla.gecko.db.BrowserDB.URLColumns; -import org.mozilla.gecko.gfx.BitmapUtils; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.Loader; - -import java.util.ArrayList; - -/** - * Encapsulates the implementation of the favicons cursorloader. - */ -class FaviconsLoader { - // Argument containing list of urls for the favicons loader - private static final String FAVICONS_LOADER_URLS_ARG = "urls"; - - private FaviconsLoader() { - } - - private static ArrayList getUrlsWithoutFavicon(Cursor c) { - ArrayList urls = new ArrayList(); - - if (c == null || !c.moveToFirst()) { - return urls; - } - - do { - final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL)); - - // We only want to load favicons from DB if they are not in the - // memory cache yet. The url is null for bookmark folders. - if (url == null || Favicons.getFaviconFromMemCache(url) != null) { - continue; - } - - urls.add(url); - } while (c.moveToNext()); - - return urls; - } - - private static void storeFaviconsInMemCache(Cursor c) { - if (c == null || !c.moveToFirst()) { - return; - } - - do { - final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL)); - final byte[] b = c.getBlob(c.getColumnIndexOrThrow(URLColumns.FAVICON)); - - if (b == null) { - continue; - } - - Bitmap favicon = BitmapUtils.decodeByteArray(b); - if (favicon == null) { - continue; - } - - favicon = Favicons.scaleImage(favicon); - Favicons.putFaviconInMemCache(url, favicon); - } while (c.moveToNext()); - } - - public static void restartFromCursor(LoaderManager manager, int loaderId, - LoaderCallbacks callbacks, Cursor c) { - // If there urls without in-memory favicons, trigger a new loader - // to load the images from disk to memory. - ArrayList urls = getUrlsWithoutFavicon(c); - if (urls.size() > 0) { - Bundle args = new Bundle(); - args.putStringArrayList(FAVICONS_LOADER_URLS_ARG, urls); - - manager.restartLoader(loaderId, args, callbacks); - } - } - - public static Loader createInstance(Context context, Bundle args) { - final ArrayList urls = args.getStringArrayList(FAVICONS_LOADER_URLS_ARG); - return new FaviconsCursorLoader(context, urls); - } - - private static class FaviconsCursorLoader extends SimpleCursorLoader { - private final ArrayList mUrls; - - public FaviconsCursorLoader(Context context, ArrayList urls) { - super(context); - mUrls = urls; - } - - @Override - public Cursor loadCursor() { - final ContentResolver cr = getContext().getContentResolver(); - - Cursor c = BrowserDB.getFaviconsForUrls(cr, mUrls); - storeFaviconsInMemCache(c); - - return c; - } - } -} diff --git a/mobile/android/base/home/HomeBanner.java b/mobile/android/base/home/HomeBanner.java index cc4fc7cc94b..b4adfb95326 100644 --- a/mobile/android/base/home/HomeBanner.java +++ b/mobile/android/base/home/HomeBanner.java @@ -5,16 +5,31 @@ package org.mozilla.gecko.home; +import org.mozilla.gecko.GeckoAppShell; +import org.mozilla.gecko.GeckoEvent; import org.mozilla.gecko.R; +import org.mozilla.gecko.gfx.BitmapUtils; +import org.mozilla.gecko.util.GeckoEventListener; +import org.mozilla.gecko.util.ThreadUtils; + +import org.json.JSONException; +import org.json.JSONObject; import android.content.Context; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; import android.util.AttributeSet; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageButton; +import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.TextView; -public class HomeBanner extends LinearLayout { +public class HomeBanner extends LinearLayout + implements GeckoEventListener { + private static final String LOGTAG = "GeckoHomeBanner"; public HomeBanner(Context context) { this(context, null); @@ -43,9 +58,78 @@ public class HomeBanner extends LinearLayout { HomeBanner.this.setVisibility(View.GONE); } }); + + setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // Send the current message id back to JS. + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Click", (String) getTag())); + } + }); + + GeckoAppShell.getEventDispatcher().registerEventListener("HomeBanner:Data", this); + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Get", null)); } + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + GeckoAppShell.getEventDispatcher().unregisterEventListener("HomeBanner:Data", this); + } + public boolean isDismissed() { return (getVisibility() == View.GONE); } + + @Override + public void handleMessage(String event, JSONObject message) { + try { + // Store the current message id to pass back to JS in the view's OnClickListener. + setTag(message.getString("id")); + + final String text = message.getString("text"); + final TextView textView = (TextView) findViewById(R.id.text); + + // Update the banner message on the UI thread. + ThreadUtils.postToUiThread(new Runnable() { + @Override + public void run() { + textView.setText(text); + setVisibility(View.VISIBLE); + } + }); + } catch (JSONException e) { + Log.e(LOGTAG, "Exception handling " + event + " message", e); + return; + } + + final String iconURI = message.optString("iconURI"); + final ImageView iconView = (ImageView) findViewById(R.id.icon); + + if (TextUtils.isEmpty(iconURI)) { + // Hide the image view if we don't have an icon to show. + iconView.setVisibility(View.GONE); + return; + } + + BitmapUtils.getDrawable(getContext(), iconURI, new BitmapUtils.BitmapLoader() { + @Override + public void onBitmapFound(final Drawable d) { + // Bail if getDrawable doesn't find anything. + if (d == null) { + iconView.setVisibility(View.GONE); + return; + } + + // Update the banner icon on the UI thread. + ThreadUtils.postToUiThread(new Runnable() { + @Override + public void run() { + iconView.setImageDrawable(d); + } + }); + } + }); + } } diff --git a/mobile/android/base/home/HomeCursorLoaderCallbacks.java b/mobile/android/base/home/HomeCursorLoaderCallbacks.java deleted file mode 100644 index 1ff2d732f6c..00000000000 --- a/mobile/android/base/home/HomeCursorLoaderCallbacks.java +++ /dev/null @@ -1,58 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.home; - -import android.content.Context; -import android.database.Cursor; -import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.Loader; - -/** - * Cursor loader callbacks that takes care loading favicons into memory. - */ -abstract class HomeCursorLoaderCallbacks implements LoaderCallbacks { - - // Cursor loader ID for favicons query - private static final int LOADER_ID_FAVICONS = 100; - - private final Context mContext; - private final LoaderManager mLoaderManager; - - public HomeCursorLoaderCallbacks(Context context, LoaderManager loaderManager) { - mContext = context; - mLoaderManager = loaderManager; - } - - public void loadFavicons(Cursor cursor) { - FaviconsLoader.restartFromCursor(mLoaderManager, LOADER_ID_FAVICONS, this, cursor); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - if (id == LOADER_ID_FAVICONS) { - return FaviconsLoader.createInstance(mContext, args); - } - - return null; - } - - @Override - public void onLoadFinished(Loader loader, Cursor c) { - if (loader.getId() == LOADER_ID_FAVICONS) { - onFaviconsLoaded(); - } - } - - @Override - public void onLoaderReset(Loader loader) { - // Do nothing by default. - } - - // Callback for favicons loaded in memory. - public abstract void onFaviconsLoaded(); -} diff --git a/mobile/android/base/home/LastTabsPage.java b/mobile/android/base/home/LastTabsPage.java index 3a08eea2952..87b80b1445d 100644 --- a/mobile/android/base/home/LastTabsPage.java +++ b/mobile/android/base/home/LastTabsPage.java @@ -142,14 +142,12 @@ public class LastTabsPage extends HomeFragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - final Activity activity = getActivity(); - // Intialize adapter - mAdapter = new LastTabsAdapter(activity); + mAdapter = new LastTabsAdapter(getActivity()); mList.setAdapter(mAdapter); // Create callbacks before the initial loader is started - mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager()); + mCursorLoaderCallbacks = new CursorLoaderCallbacks(); loadIfVisible(); } @@ -262,43 +260,21 @@ public class LastTabsPage extends HomeFragment { } } - private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks { - public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) { - super(context, loaderManager); - } - + private class CursorLoaderCallbacks implements LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { - if (id == LOADER_ID_LAST_TABS) { - return new LastTabsCursorLoader(getActivity()); - } else { - return super.onCreateLoader(id, args); - } + return new LastTabsCursorLoader(getActivity()); } @Override public void onLoadFinished(Loader loader, Cursor c) { - if (loader.getId() == LOADER_ID_LAST_TABS) { - mAdapter.swapCursor(c); - updateUiFromCursor(c); - loadFavicons(c); - } else { - super.onLoadFinished(loader, c); - } + mAdapter.swapCursor(c); + updateUiFromCursor(c); } @Override public void onLoaderReset(Loader loader) { - if (loader.getId() == LOADER_ID_LAST_TABS) { - mAdapter.swapCursor(null); - } else { - super.onLoaderReset(loader); - } - } - - @Override - public void onFaviconsLoaded() { - mAdapter.notifyDataSetChanged(); + mAdapter.swapCursor(null); } } } diff --git a/mobile/android/base/home/MostRecentPage.java b/mobile/android/base/home/MostRecentPage.java index fe262e9d4e1..0d79ca37106 100644 --- a/mobile/android/base/home/MostRecentPage.java +++ b/mobile/android/base/home/MostRecentPage.java @@ -17,6 +17,7 @@ import android.content.Context; import android.database.Cursor; import android.os.Bundle; import android.support.v4.app.LoaderManager; +import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; import android.util.SparseArray; import android.view.LayoutInflater; @@ -127,14 +128,12 @@ public class MostRecentPage extends HomeFragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - final Activity activity = getActivity(); - // Intialize adapter - mAdapter = new MostRecentAdapter(activity); + mAdapter = new MostRecentAdapter(getActivity()); mList.setAdapter(mAdapter); // Create callbacks before the initial loader is started - mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager()); + mCursorLoaderCallbacks = new CursorLoaderCallbacks(); loadIfVisible(); } @@ -360,43 +359,21 @@ public class MostRecentPage extends HomeFragment { } } - private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks { - public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) { - super(context, loaderManager); - } - + private class CursorLoaderCallbacks implements LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { - if (id == LOADER_ID_HISTORY) { - return new MostRecentCursorLoader(getActivity()); - } else { - return super.onCreateLoader(id, args); - } + return new MostRecentCursorLoader(getActivity()); } @Override public void onLoadFinished(Loader loader, Cursor c) { - if (loader.getId() == LOADER_ID_HISTORY) { - mAdapter.swapCursor(c); - updateUiFromCursor(c); - loadFavicons(c); - } else { - super.onLoadFinished(loader, c); - } + mAdapter.swapCursor(c); + updateUiFromCursor(c); } @Override public void onLoaderReset(Loader loader) { - if (loader.getId() == LOADER_ID_HISTORY) { - mAdapter.swapCursor(null); - } else { - super.onLoaderReset(loader); - } - } - - @Override - public void onFaviconsLoaded() { - mAdapter.notifyDataSetChanged(); + mAdapter.swapCursor(null); } } } diff --git a/mobile/android/base/home/MostVisitedPage.java b/mobile/android/base/home/MostVisitedPage.java index fff8e9ec0af..9315049aa13 100644 --- a/mobile/android/base/home/MostVisitedPage.java +++ b/mobile/android/base/home/MostVisitedPage.java @@ -16,6 +16,7 @@ import android.content.Context; import android.database.Cursor; import android.os.Bundle; import android.support.v4.app.LoaderManager; +import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; import android.view.LayoutInflater; @@ -129,14 +130,12 @@ public class MostVisitedPage extends HomeFragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - final Activity activity = getActivity(); - // Intialize the search adapter - mAdapter = new VisitedAdapter(activity, null); + mAdapter = new VisitedAdapter(getActivity(), null); mList.setAdapter(mAdapter); // Create callbacks before the initial loader is started - mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager()); + mCursorLoaderCallbacks = new CursorLoaderCallbacks(); loadIfVisible(); } @@ -206,43 +205,21 @@ public class MostVisitedPage extends HomeFragment { } } - private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks { - public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) { - super(context, loaderManager); - } - + private class CursorLoaderCallbacks implements LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { - if (id == LOADER_ID_FRECENCY) { - return new FrecencyCursorLoader(getActivity()); - } else { - return super.onCreateLoader(id, args); - } + return new FrecencyCursorLoader(getActivity()); } @Override public void onLoadFinished(Loader loader, Cursor c) { - if (loader.getId() == LOADER_ID_FRECENCY) { - mAdapter.swapCursor(c); - updateUiFromCursor(c); - loadFavicons(c); - } else { - super.onLoadFinished(loader, c); - } + mAdapter.swapCursor(c); + updateUiFromCursor(c); } @Override public void onLoaderReset(Loader loader) { - if (loader.getId() == LOADER_ID_FRECENCY) { - mAdapter.swapCursor(null); - } else { - super.onLoaderReset(loader); - } - } - - @Override - public void onFaviconsLoaded() { - mAdapter.notifyDataSetChanged(); + mAdapter.swapCursor(null); } } } diff --git a/mobile/android/base/home/PinBookmarkDialog.java b/mobile/android/base/home/PinBookmarkDialog.java index 3e20ea80578..90b0ab9082a 100644 --- a/mobile/android/base/home/PinBookmarkDialog.java +++ b/mobile/android/base/home/PinBookmarkDialog.java @@ -14,6 +14,7 @@ import android.database.Cursor; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v4.app.LoaderManager; +import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; import android.text.Editable; @@ -38,9 +39,6 @@ class PinBookmarkDialog extends DialogFragment { // Cursor loader ID for search query private static final int LOADER_ID_SEARCH = 0; - // Cursor loader ID for favicons query - private static final int LOADER_ID_FAVICONS = 1; - // Holds the current search term to use in the query private String mSearchTerm; @@ -53,7 +51,7 @@ class PinBookmarkDialog extends DialogFragment { // Search results private ListView mList; - // Callbacks used for the search and favicon cursor loaders + // Callbacks used for the search loader private CursorLoaderCallbacks mLoaderCallbacks; // Bookmark selected listener @@ -125,15 +123,14 @@ class PinBookmarkDialog extends DialogFragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - final Activity activity = getActivity(); final LoaderManager manager = getLoaderManager(); // Initialize the search adapter - mAdapter = new SearchAdapter(activity); + mAdapter = new SearchAdapter(getActivity()); mList.setAdapter(mAdapter); // Create callbacks before the initial loader is started - mLoaderCallbacks = new CursorLoaderCallbacks(activity, manager); + mLoaderCallbacks = new CursorLoaderCallbacks(); // Reconnect to the loader only if present manager.initLoader(LOADER_ID_SEARCH, null, mLoaderCallbacks); @@ -179,42 +176,20 @@ class PinBookmarkDialog extends DialogFragment { } } - private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks { - public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) { - super(context, loaderManager); - } - + private class CursorLoaderCallbacks implements LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { - if (id == LOADER_ID_SEARCH) { - return SearchLoader.createInstance(getActivity(), args); - } else { - return super.onCreateLoader(id, args); - } + return SearchLoader.createInstance(getActivity(), args); } @Override public void onLoadFinished(Loader loader, Cursor c) { - if (loader.getId() == LOADER_ID_SEARCH) { - mAdapter.swapCursor(c); - loadFavicons(c); - } else { - super.onLoadFinished(loader, c); - } + mAdapter.swapCursor(c); } @Override public void onLoaderReset(Loader loader) { - if (loader.getId() == LOADER_ID_SEARCH) { - mAdapter.swapCursor(null); - } else { - super.onLoaderReset(loader); - } - } - - @Override - public void onFaviconsLoaded() { - mAdapter.notifyDataSetChanged(); + mAdapter.swapCursor(null); } } } diff --git a/mobile/android/base/home/ReadingListPage.java b/mobile/android/base/home/ReadingListPage.java index 62fc9a558a6..4bf43467d35 100644 --- a/mobile/android/base/home/ReadingListPage.java +++ b/mobile/android/base/home/ReadingListPage.java @@ -219,33 +219,18 @@ public class ReadingListPage extends HomeFragment { private class CursorLoaderCallbacks implements LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { - switch(id) { - case LOADER_ID_READING_LIST: - return new ReadingListLoader(getActivity()); - } - return null; + return new ReadingListLoader(getActivity()); } @Override public void onLoadFinished(Loader loader, Cursor c) { - final int loaderId = loader.getId(); - switch(loaderId) { - case LOADER_ID_READING_LIST: - mAdapter.swapCursor(c); - break; - } - - updateUiFromCursor(c); + mAdapter.swapCursor(c); + updateUiFromCursor(c); } @Override public void onLoaderReset(Loader loader) { - final int loaderId = loader.getId(); - switch(loaderId) { - case LOADER_ID_READING_LIST: - mAdapter.swapCursor(null); - break; - } + mAdapter.swapCursor(null); } } } diff --git a/mobile/android/base/home/TwoLinePageRow.java b/mobile/android/base/home/TwoLinePageRow.java index a82b1d797d0..c70ae2b693f 100644 --- a/mobile/android/base/home/TwoLinePageRow.java +++ b/mobile/android/base/home/TwoLinePageRow.java @@ -10,14 +10,19 @@ import org.mozilla.gecko.R; import org.mozilla.gecko.Tab; import org.mozilla.gecko.Tabs; import org.mozilla.gecko.db.BrowserContract.Combined; +import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.db.BrowserDB.URLColumns; import org.mozilla.gecko.gfx.BitmapUtils; import org.mozilla.gecko.util.ThreadUtils; +import org.mozilla.gecko.util.UiAsyncTask; import org.mozilla.gecko.widget.FaviconView; +import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; +import android.os.AsyncTask; +import android.os.Build; import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; @@ -40,6 +45,8 @@ public class TwoLinePageRow extends LinearLayout // The URL for the page corresponding to this view. private String mPageUrl; + private LoadFaviconTask mLoadFaviconTask; + public TwoLinePageRow(Context context) { this(context, null); } @@ -74,6 +81,8 @@ public class TwoLinePageRow extends LinearLayout Tabs.unregisterOnTabsChangedListener(TwoLinePageRow.this); } }); + + cancelLoadFaviconTask(); } @Override @@ -130,6 +139,16 @@ public class TwoLinePageRow extends LinearLayout updateDisplayedUrl(); } + /** + * Cancels any pending favicon loading task associated with this view. + */ + private void cancelLoadFaviconTask() { + if (mLoadFaviconTask != null) { + mLoadFaviconTask.cancel(true); + mLoadFaviconTask = null; + } + } + /** * Replaces the page URL with "Switch to tab" if there is already a tab open with that URL. */ @@ -163,24 +182,33 @@ public class TwoLinePageRow extends LinearLayout // bar view - this is the equivalent of getDisplayTitle() in Tab.java setTitle(TextUtils.isEmpty(title) ? url : title); + // No need to do extra work if the URL associated with this view + // hasn't changed. + if (TextUtils.equals(mPageUrl, url)) { + return; + } + updateDisplayedUrl(url); + cancelLoadFaviconTask(); - int faviconIndex = cursor.getColumnIndex(URLColumns.FAVICON); - if (faviconIndex != -1) { - byte[] b = cursor.getBlob(faviconIndex); - - Bitmap favicon = null; - if (b != null) { - Bitmap bitmap = BitmapUtils.decodeByteArray(b); - if (bitmap != null) { - favicon = Favicons.scaleImage(bitmap); - } - } - + // First, try to find the favicon in the memory cache. If it's not + // cached yet, try to load it from the database, off main thread. + final Bitmap favicon = Favicons.getFaviconFromMemCache(url); + if (favicon != null) { setFaviconWithUrl(favicon, url); } else { - // If favicons is not on the cursor, try to fetch it from the memory cache - setFaviconWithUrl(Favicons.getFaviconFromMemCache(url), url); + // Show blank image until the new favicon finishes loading + mFavicon.clearImage(); + + mLoadFaviconTask = new LoadFaviconTask(url); + + // Try to use a thread pool instead of serial execution of tasks + // to add more throughput to the favicon loading routines. + if (Build.VERSION.SDK_INT >= 11) { + mLoadFaviconTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + mLoadFaviconTask.execute(); + } } // Don't show bookmark/reading list icon, if not needed. @@ -213,4 +241,37 @@ public class TwoLinePageRow extends LinearLayout setBookmarkIcon(NO_ICON); } } + + private class LoadFaviconTask extends AsyncTask { + private final String mUrl; + + public LoadFaviconTask(String url) { + mUrl = url; + } + + @Override + public Bitmap doInBackground(Void... params) { + Bitmap favicon = Favicons.getFaviconFromMemCache(mUrl); + if (favicon == null) { + final ContentResolver cr = getContext().getContentResolver(); + + final Bitmap faviconFromDb = BrowserDB.getFaviconForUrl(cr, mUrl); + if (faviconFromDb != null) { + favicon = Favicons.scaleImage(faviconFromDb); + Favicons.putFaviconInMemCache(mUrl, favicon); + } + } + + return favicon; + } + + @Override + public void onPostExecute(Bitmap favicon) { + if (TextUtils.equals(mPageUrl, mUrl)) { + setFaviconWithUrl(favicon, mUrl); + } + + mLoadFaviconTask = null; + } + } } diff --git a/mobile/android/base/resources/layout/home_banner.xml b/mobile/android/base/resources/layout/home_banner.xml index e8a54586d38..cc2e34b1cd9 100644 --- a/mobile/android/base/resources/layout/home_banner.xml +++ b/mobile/android/base/resources/layout/home_banner.xml @@ -9,13 +9,13 @@ android:layout_width="48dip" android:layout_height="48dip" android:layout_marginLeft="10dp" - android:layout_marginRight="10dp" android:scaleType="centerInside"/> -#include "plstr.h" #include "nsNetUtil.h" -#include "nsStringGlue.h" #include "nsISiteSecurityService.h" -#include "nsIPermissionManager.h" #define EXPECT_SUCCESS(rv, ...) \ PR_BEGIN_MACRO \ @@ -40,8 +35,7 @@ bool TestSuccess(const char* hdr, bool extraTokens, uint64_t expectedMaxAge, bool expectedIncludeSubdomains, - nsISiteSecurityService* sss, - nsIPermissionManager* pm) + nsISiteSecurityService* sss) { nsCOMPtr dummyUri; nsresult rv = NS_NewURI(getter_AddRefs(dummyUri), "https://foo.com/bar.html"); @@ -68,8 +62,7 @@ TestSuccess(const char* hdr, bool extraTokens, } bool TestFailure(const char* hdr, - nsISiteSecurityService* sss, - nsIPermissionManager* pm) + nsISiteSecurityService* sss) { nsCOMPtr dummyUri; nsresult rv = NS_NewURI(getter_AddRefs(dummyUri), "https://foo.com/bar.html"); @@ -102,10 +95,6 @@ main(int32_t argc, char *argv[]) sss = do_GetService("@mozilla.org/ssservice;1", &rv); NS_ENSURE_SUCCESS(rv, -1); - nsCOMPtr pm; - pm = do_GetService("@mozilla.org/permissionmanager;1", &rv); - NS_ENSURE_SUCCESS(rv, -1); - int rv0, rv1; nsTArray rvs(24); @@ -114,42 +103,42 @@ main(int32_t argc, char *argv[]) printf("*** Attempting to parse valid STS headers ...\n"); // SHOULD SUCCEED: - rvs.AppendElement(TestSuccess("max-age=100", false, 100, false, sss, pm)); - rvs.AppendElement(TestSuccess("max-age =100", false, 100, false, sss, pm)); - rvs.AppendElement(TestSuccess(" max-age=100", false, 100, false, sss, pm)); - rvs.AppendElement(TestSuccess("max-age = 100 ", false, 100, false, sss, pm)); - rvs.AppendElement(TestSuccess("max-age = \"100\" ", false, 100, false, sss, pm)); - rvs.AppendElement(TestSuccess("max-age=\"100\"", false, 100, false, sss, pm)); - rvs.AppendElement(TestSuccess(" max-age =\"100\" ", false, 100, false, sss, pm)); - rvs.AppendElement(TestSuccess("\tmax-age\t=\t\"100\"\t", false, 100, false, sss, pm)); - rvs.AppendElement(TestSuccess("max-age = 100 ", false, 100, false, sss, pm)); + rvs.AppendElement(TestSuccess("max-age=100", false, 100, false, sss)); + rvs.AppendElement(TestSuccess("max-age =100", false, 100, false, sss)); + rvs.AppendElement(TestSuccess(" max-age=100", false, 100, false, sss)); + rvs.AppendElement(TestSuccess("max-age = 100 ", false, 100, false, sss)); + rvs.AppendElement(TestSuccess("max-age = \"100\" ", false, 100, false, sss)); + rvs.AppendElement(TestSuccess("max-age=\"100\"", false, 100, false, sss)); + rvs.AppendElement(TestSuccess(" max-age =\"100\" ", false, 100, false, sss)); + rvs.AppendElement(TestSuccess("\tmax-age\t=\t\"100\"\t", false, 100, false, sss)); + rvs.AppendElement(TestSuccess("max-age = 100 ", false, 100, false, sss)); - rvs.AppendElement(TestSuccess("maX-aGe=100", false, 100, false, sss, pm)); - rvs.AppendElement(TestSuccess("MAX-age =100", false, 100, false, sss, pm)); - rvs.AppendElement(TestSuccess("max-AGE=100", false, 100, false, sss, pm)); - rvs.AppendElement(TestSuccess("Max-Age = 100 ", false, 100, false, sss, pm)); - rvs.AppendElement(TestSuccess("MAX-AGE = 100 ", false, 100, false, sss, pm)); + rvs.AppendElement(TestSuccess("maX-aGe=100", false, 100, false, sss)); + rvs.AppendElement(TestSuccess("MAX-age =100", false, 100, false, sss)); + rvs.AppendElement(TestSuccess("max-AGE=100", false, 100, false, sss)); + rvs.AppendElement(TestSuccess("Max-Age = 100 ", false, 100, false, sss)); + rvs.AppendElement(TestSuccess("MAX-AGE = 100 ", false, 100, false, sss)); - rvs.AppendElement(TestSuccess("max-age=100;includeSubdomains", false, 100, true, sss, pm)); - rvs.AppendElement(TestSuccess("max-age=100\t; includeSubdomains", false, 100, true, sss, pm)); - rvs.AppendElement(TestSuccess(" max-age=100; includeSubdomains", false, 100, true, sss, pm)); - rvs.AppendElement(TestSuccess("max-age = 100 ; includeSubdomains", false, 100, true, sss, pm)); - rvs.AppendElement(TestSuccess("max-age = 100 ; includeSubdomains", false, 100, true, sss, pm)); + rvs.AppendElement(TestSuccess("max-age=100;includeSubdomains", false, 100, true, sss)); + rvs.AppendElement(TestSuccess("max-age=100\t; includeSubdomains", false, 100, true, sss)); + rvs.AppendElement(TestSuccess(" max-age=100; includeSubdomains", false, 100, true, sss)); + rvs.AppendElement(TestSuccess("max-age = 100 ; includeSubdomains", false, 100, true, sss)); + rvs.AppendElement(TestSuccess("max-age = 100 ; includeSubdomains", false, 100, true, sss)); - rvs.AppendElement(TestSuccess("maX-aGe=100; includeSUBDOMAINS", false, 100, true, sss, pm)); - rvs.AppendElement(TestSuccess("MAX-age =100; includeSubDomains", false, 100, true, sss, pm)); - rvs.AppendElement(TestSuccess("max-AGE=100; iNcLuDeSuBdoMaInS", false, 100, true, sss, pm)); - rvs.AppendElement(TestSuccess("Max-Age = 100; includesubdomains ", false, 100, true, sss, pm)); - rvs.AppendElement(TestSuccess("INCLUDESUBDOMAINS;MaX-AgE = 100 ", false, 100, true, sss, pm)); + rvs.AppendElement(TestSuccess("maX-aGe=100; includeSUBDOMAINS", false, 100, true, sss)); + rvs.AppendElement(TestSuccess("MAX-age =100; includeSubDomains", false, 100, true, sss)); + rvs.AppendElement(TestSuccess("max-AGE=100; iNcLuDeSuBdoMaInS", false, 100, true, sss)); + rvs.AppendElement(TestSuccess("Max-Age = 100; includesubdomains ", false, 100, true, sss)); + rvs.AppendElement(TestSuccess("INCLUDESUBDOMAINS;MaX-AgE = 100 ", false, 100, true, sss)); // Turns out, the actual directive is entirely optional (hence the // trailing semicolon) - rvs.AppendElement(TestSuccess("max-age=100;includeSubdomains;", true, 100, true, sss, pm)); + rvs.AppendElement(TestSuccess("max-age=100;includeSubdomains;", true, 100, true, sss)); // these are weird tests, but are testing that some extended syntax is // still allowed (but it is ignored) - rvs.AppendElement(TestSuccess("max-age=100 ; includesubdomainsSomeStuff", true, 100, false, sss, pm)); - rvs.AppendElement(TestSuccess("\r\n\t\t \tcompletelyUnrelated = foobar; max-age= 34520103 \t \t; alsoUnrelated;asIsThis;\tincludeSubdomains\t\t \t", true, 34520103, true, sss, pm)); - rvs.AppendElement(TestSuccess("max-age=100; unrelated=\"quoted \\\"thingy\\\"\"", true, 100, false, sss, pm)); + rvs.AppendElement(TestSuccess("max-age=100 ; includesubdomainsSomeStuff", true, 100, false, sss)); + rvs.AppendElement(TestSuccess("\r\n\t\t \tcompletelyUnrelated = foobar; max-age= 34520103 \t \t; alsoUnrelated;asIsThis;\tincludeSubdomains\t\t \t", true, 34520103, true, sss)); + rvs.AppendElement(TestSuccess("max-age=100; unrelated=\"quoted \\\"thingy\\\"\"", true, 100, false, sss)); rv0 = rvs.Contains(false) ? 1 : 0; if (rv0 == 0) @@ -160,37 +149,37 @@ main(int32_t argc, char *argv[]) // SHOULD FAIL: printf("*** Attempting to parse invalid STS headers (should not parse)...\n"); // invalid max-ages - rvs.AppendElement(TestFailure("max-age", sss, pm)); - rvs.AppendElement(TestFailure("max-age ", sss, pm)); - rvs.AppendElement(TestFailure("max-age=p", sss, pm)); - rvs.AppendElement(TestFailure("max-age=*1p2", sss, pm)); - rvs.AppendElement(TestFailure("max-age=.20032", sss, pm)); - rvs.AppendElement(TestFailure("max-age=!20032", sss, pm)); - rvs.AppendElement(TestFailure("max-age==20032", sss, pm)); + rvs.AppendElement(TestFailure("max-age", sss)); + rvs.AppendElement(TestFailure("max-age ", sss)); + rvs.AppendElement(TestFailure("max-age=p", sss)); + rvs.AppendElement(TestFailure("max-age=*1p2", sss)); + rvs.AppendElement(TestFailure("max-age=.20032", sss)); + rvs.AppendElement(TestFailure("max-age=!20032", sss)); + rvs.AppendElement(TestFailure("max-age==20032", sss)); // invalid headers - rvs.AppendElement(TestFailure("foobar", sss, pm)); - rvs.AppendElement(TestFailure("maxage=100", sss, pm)); - rvs.AppendElement(TestFailure("maxa-ge=100", sss, pm)); - rvs.AppendElement(TestFailure("max-ag=100", sss, pm)); - rvs.AppendElement(TestFailure("includesubdomains", sss, pm)); - rvs.AppendElement(TestFailure(";", sss, pm)); - rvs.AppendElement(TestFailure("max-age=\"100", sss, pm)); + rvs.AppendElement(TestFailure("foobar", sss)); + rvs.AppendElement(TestFailure("maxage=100", sss)); + rvs.AppendElement(TestFailure("maxa-ge=100", sss)); + rvs.AppendElement(TestFailure("max-ag=100", sss)); + rvs.AppendElement(TestFailure("includesubdomains", sss)); + rvs.AppendElement(TestFailure(";", sss)); + rvs.AppendElement(TestFailure("max-age=\"100", sss)); // The max-age directive here doesn't conform to the spec, so it MUST // be ignored. Consequently, the REQUIRED max-age directive is not // present in this header, and so it is invalid. - rvs.AppendElement(TestFailure("max-age=100, max-age=200; includeSubdomains", sss, pm)); - rvs.AppendElement(TestFailure("max-age=100 includesubdomains", sss, pm)); - rvs.AppendElement(TestFailure("max-age=100 bar foo", sss, pm)); - rvs.AppendElement(TestFailure("max-age=100randomstuffhere", sss, pm)); + rvs.AppendElement(TestFailure("max-age=100, max-age=200; includeSubdomains", sss)); + rvs.AppendElement(TestFailure("max-age=100 includesubdomains", sss)); + rvs.AppendElement(TestFailure("max-age=100 bar foo", sss)); + rvs.AppendElement(TestFailure("max-age=100randomstuffhere", sss)); // All directives MUST appear only once in an STS header field. - rvs.AppendElement(TestFailure("max-age=100; max-age=200", sss, pm)); - rvs.AppendElement(TestFailure("includeSubdomains; max-age=200; includeSubdomains", sss, pm)); - rvs.AppendElement(TestFailure("max-age=200; includeSubdomains; includeSubdomains", sss, pm)); + rvs.AppendElement(TestFailure("max-age=100; max-age=200", sss)); + rvs.AppendElement(TestFailure("includeSubdomains; max-age=200; includeSubdomains", sss)); + rvs.AppendElement(TestFailure("max-age=200; includeSubdomains; includeSubdomains", sss)); // The includeSubdomains directive is valueless. - rvs.AppendElement(TestFailure("max-age=100; includeSubdomains=unexpected", sss, pm)); + rvs.AppendElement(TestFailure("max-age=100; includeSubdomains=unexpected", sss)); // LWS must have at least one space or horizontal tab - rvs.AppendElement(TestFailure("\r\nmax-age=200", sss, pm)); + rvs.AppendElement(TestFailure("\r\nmax-age=200", sss)); rv1 = rvs.Contains(false) ? 1 : 0; if (rv1 == 0) diff --git a/testing/talos/talos.json b/testing/talos/talos.json index e5c3bcce39c..0b7488b249b 100644 --- a/testing/talos/talos.json +++ b/testing/talos/talos.json @@ -1,11 +1,11 @@ { "talos.zip": { - "url": "http://talos-bundles.pvt.build.mozilla.org/zips/talos.ca2229a32cb6.zip", + "url": "http://talos-bundles.pvt.build.mozilla.org/zips/talos.e11c30b59cef.zip", "path": "" }, "global": { "talos_repo": "http://hg.mozilla.org/build/talos", - "talos_revision": "655c5140970c" + "talos_revision": "e11c30b59cef" }, "suites": { "chromez": { @@ -52,5 +52,43 @@ "C:/slave/talos-data/talos/xperf.config" ] } + }, + "mobile-suites": { + "remote-ts": { + "tests": ["ts_paint"] + }, + "remote-tsvgx": { + "tests": ["tsvgx"], + "talos_options": [ + "--noChrome" + ] + }, + "remote-tcanvasmark": { + "tests": ["tcanvasmark"], + "talos_options": [ + "--noChrome" + ] + }, + "remote-trobopan": { + "tests": ["trobopan"], + "talos_options": [ + "--fennecIDs", "../fennec_ids.txt" + ] + }, + "remote-troboprovider": { + "tests": ["tprovider"], + "talos_options": [ + "--fennecIDs", "../fennec_ids.txt" + ] + }, + "remote-trobocheck2": { + "tests": ["tcheck2"], + "talos_options": [ + "--fennecIDs", "../fennec_ids.txt" + ] + }, + "remote-tp4m_nochrome": { + "tests": ["tp4m"] + } } } diff --git a/toolkit/components/aboutmemory/content/aboutMemory.js b/toolkit/components/aboutmemory/content/aboutMemory.js index 53b76fed419..809eb4ae953 100644 --- a/toolkit/components/aboutmemory/content/aboutMemory.js +++ b/toolkit/components/aboutmemory/content/aboutMemory.js @@ -483,6 +483,9 @@ function updateAboutMemoryFromReporters() } // Increment this if the JSON format changes. +// +// If/when this changes to 2, the beLenient() function and its use can be +// removed. var gCurrentFileFormatVersion = 1; /** @@ -958,8 +961,29 @@ function getPCollsByProcess(aProcessReports) "non-sentence explicit description"); } else { - assertInput(gSentenceRegExp.test(aDescription), - "non-sentence other description"); + const kLenientPrefixes = + ['rss/', 'pss/', 'size/', 'swap/', 'compartments/', 'ghost-windows/']; + let beLenient = function(aUnsafePath) { + for (let i = 0; i < kLenientPrefixes.length; i++) { + if (aUnsafePath.startsWith(kLenientPrefixes[i])) { + return true; + } + } + return false; + } + + // In general, non-explicit reports should have a description that is a + // complete sentence. However, we want to be able to read old saved + // reports, so we are lenient in a couple of situations where we used to + // allow non-sentence descriptions: + // - smaps reports (which were removed in bug 912165); + // - compartment and ghost-window reports (which had empty descriptions + // prior to bug 911641). + if (!beLenient(aUnsafePath)) { + assertInput(gSentenceRegExp.test(aDescription), + "non-sentence other description: " + aUnsafePath + ", " + + aDescription); + } } assert(aPresence === undefined || @@ -1737,7 +1761,9 @@ function appendTreeElements(aP, aRoot, aProcess, aPadText) tIsInvalid = true; let unsafePath = aUnsafeNames.join("/"); gUnsafePathsWithInvalidValuesForThisProcess.push(unsafePath); - reportAssertionFailure("Invalid value for " + flipBackslashes(unsafePath)); + reportAssertionFailure("Invalid value (" + aT._amount + " / " + + aRoot._amount + ") for " + + flipBackslashes(unsafePath)); } // For non-leaf nodes, the entire sub-tree is put within a span so it can diff --git a/toolkit/components/aboutmemory/tests/Makefile.in b/toolkit/components/aboutmemory/tests/Makefile.in index 0f2d2dd6cb4..df8f3b55c32 100644 --- a/toolkit/components/aboutmemory/tests/Makefile.in +++ b/toolkit/components/aboutmemory/tests/Makefile.in @@ -17,6 +17,8 @@ MOCHITEST_CHROME_FILES = \ ifndef MOZ_ASAN MOCHITEST_CHROME_FILES += \ + remote.xul \ test_memoryReporters.xul \ + test_memoryReporters2.xul \ $(NULL) endif diff --git a/toolkit/components/aboutmemory/tests/memory-reports-good.json b/toolkit/components/aboutmemory/tests/memory-reports-good.json index 8c5c2412a54..f3ec6ff4b00 100644 --- a/toolkit/components/aboutmemory/tests/memory-reports-good.json +++ b/toolkit/components/aboutmemory/tests/memory-reports-good.json @@ -7,6 +7,13 @@ {"process": "Main Process (pid NNN)", "path": "other/a", "kind": 2, "units": 0, "amount": 209715, "description": "Other a."}, {"process": "Main Process (pid NNN)", "path": "explicit/a/b", "kind": 1, "units": 0, "amount": 52428800, "description": "A b."}, + {"process": "Main Process (pid NNN)", "path": "size/a", "kind": 1, "units": 0, "amount": 1024, "description": "non-sentence"}, + {"process": "Main Process (pid NNN)", "path": "rss/a", "kind": 1, "units": 0, "amount": 1024, "description": "non-sentence"}, + {"process": "Main Process (pid NNN)", "path": "pss/a", "kind": 1, "units": 0, "amount": 1024, "description": "non-sentence"}, + {"process": "Main Process (pid NNN)", "path": "swap/a", "kind": 1, "units": 0, "amount": 1024, "description": "non-sentence"}, + {"process": "Main Process (pid NNN)", "path": "compartments/system/a", "kind": 1, "units": 0, "amount": 1024, "description": ""}, + {"process": "Main Process (pid NNN)", "path": "ghost-windows/a", "kind": 1, "units": 0, "amount": 1024, "description": ""}, + {"process": "Explicit-only process", "path": "explicit/a/b", "kind": 1, "units": 0, "amount": 100000, "description": "A b."}, {"process": "Other-only process", "path": "a/b", "kind": 1, "units": 0, "amount": 100000, "description": "A b."}, diff --git a/toolkit/components/aboutmemory/tests/remote.xul b/toolkit/components/aboutmemory/tests/remote.xul new file mode 100644 index 00000000000..7d691013055 --- /dev/null +++ b/toolkit/components/aboutmemory/tests/remote.xul @@ -0,0 +1,12 @@ + + + + + + +

Remote browser

+ + +
diff --git a/toolkit/components/aboutmemory/tests/test_aboutmemory3.xul b/toolkit/components/aboutmemory/tests/test_aboutmemory3.xul index 38c55f7d858..a99a1c175e8 100644 --- a/toolkit/components/aboutmemory/tests/test_aboutmemory3.xul +++ b/toolkit/components/aboutmemory/tests/test_aboutmemory3.xul @@ -213,10 +213,28 @@ Explicit Allocations\n\ \n\ Other Measurements\n\ \n\ +1,024 B (100.0%) -- compartments\n\ +└──1,024 B (100.0%) ── system/a\n\ +\n\ +1,024 B (100.0%) -- ghost-windows\n\ +└──1,024 B (100.0%) ── a\n\ +\n\ 314,572 B (100.0%) -- other\n\ ├──209,715 B (66.67%) ── a\n\ └──104,857 B (33.33%) ── b\n\ \n\ +1,024 B (100.0%) -- pss\n\ +└──1,024 B (100.0%) ── a\n\ +\n\ +1,024 B (100.0%) -- rss\n\ +└──1,024 B (100.0%) ── a\n\ +\n\ +1,024 B (100.0%) -- size\n\ +└──1,024 B (100.0%) ── a\n\ +\n\ +1,024 B (100.0%) -- swap\n\ +└──1,024 B (100.0%) ── a\n\ +\n\ 262,144,000 B ── heap-allocated\n\ \n\ Other-only process\n\ @@ -309,12 +327,13 @@ Other Measurements\n\ // This loads a pre-existing file that is valid. { filename: "memory-reports-good.json", expected: expectedGood, dumpFirst: false }, - // This dumps to a file and then reads it back in. The output is the same as the first test. + // This dumps to a file and then reads it back in. { filename: "memory-reports-dumped.json.gz", expected: expectedGood2, dumpFirst: true }, // This loads a pre-existing file that is invalid. { filename: "memory-reports-bad.json", expected: expectedBad, dumpFirst: false }, + // This loads a pre-existing diff file. { filename: "memory-reports-diff1.json", filename2: "memory-reports-diff2.json", expected: expectedDiff, dumpFirst: false } ]; diff --git a/toolkit/components/aboutmemory/tests/test_aboutmemory4.xul b/toolkit/components/aboutmemory/tests/test_aboutmemory4.xul index 22ce18777e4..000711a36ae 100644 --- a/toolkit/components/aboutmemory/tests/test_aboutmemory4.xul +++ b/toolkit/components/aboutmemory/tests/test_aboutmemory4.xul @@ -108,10 +108,28 @@ Explicit Allocations\n\ \n\ Other Measurements\n\ \n\ +0.00 MB (100.0%) -- compartments\n\ +└──0.00 MB (100.0%) ── system/a\n\ +\n\ +0.00 MB (100.0%) -- ghost-windows\n\ +└──0.00 MB (100.0%) ── a\n\ +\n\ 0.30 MB (100.0%) -- other\n\ ├──0.20 MB (66.67%) ── a\n\ └──0.10 MB (33.33%) ── b\n\ \n\ +0.00 MB (100.0%) -- pss\n\ +└──0.00 MB (100.0%) ── a\n\ +\n\ +0.00 MB (100.0%) -- rss\n\ +└──0.00 MB (100.0%) ── a\n\ +\n\ +0.00 MB (100.0%) -- size\n\ +└──0.00 MB (100.0%) ── a\n\ +\n\ +0.00 MB (100.0%) -- swap\n\ +└──0.00 MB (100.0%) ── a\n\ +\n\ 250.00 MB ── heap-allocated\n\ \n\ Other-only process\n\ diff --git a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul index 10f11b060d0..c83fd09e3fd 100644 --- a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul +++ b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul @@ -1,9 +1,10 @@ - - + diff --git a/toolkit/components/social/MessagePortWorker.js b/toolkit/components/social/MessagePortWorker.js index 29f889cd5ae..80d495472b5 100644 --- a/toolkit/components/social/MessagePortWorker.js +++ b/toolkit/components/social/MessagePortWorker.js @@ -19,7 +19,9 @@ WorkerPort.prototype = { }, _onerror: function fw_WorkerPort_onerror(err) { - throw new Error("Port " + this + " handler failed: " + err); + // We throw an object that "derives" from the exception, but with + // a more detailed message. + throw {message: "Port " + this + " handler failed: " + err.message, __proto__: err}; } } diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/exception_handler.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/exception_handler.cc index 98b8e069bc1..a7e5725cf76 100644 --- a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/exception_handler.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/exception_handler.cc @@ -626,7 +626,6 @@ bool ExceptionHandler::InstallHandler() { if (gProtectedData.handler != NULL) { return false; } -#if TARGET_OS_IPHONE if (!IsOutOfProcess()) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); @@ -646,7 +645,6 @@ bool ExceptionHandler::InstallHandler() { mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ); #endif } -#endif try { #if USE_PROTECTED_ALLOCATIONS diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_generator.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_generator.cc index 85c53ebbb8f..809e684fa0d 100644 --- a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_generator.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_generator.cc @@ -787,9 +787,22 @@ bool MinidumpGenerator::GetThreadState(thread_act_t target_thread, *count = final_size; return true; } +#endif +#ifdef HAS_X86_SUPPORT + case CPU_TYPE_I386: + case CPU_TYPE_X86_64: { + size_t state_size = cpu_type_ == CPU_TYPE_I386 ? + sizeof(i386_thread_state_t) : sizeof(x86_thread_state64_t); + size_t final_size = + std::min(static_cast(*count), state_size); + memcpy(state, &task_context_->uc_mcontext->__ss, final_size); + *count = final_size; + return true; + } #endif } } + thread_state_flavor_t flavor; switch (cpu_type_) { #ifdef HAS_ARM_SUPPORT diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/tests/exception_handler_test.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/exception_handler_test.cc index d108d432185..e19944d0871 100644 --- a/toolkit/crashreporter/google-breakpad/src/client/mac/tests/exception_handler_test.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/exception_handler_test.cc @@ -122,10 +122,46 @@ void ExceptionHandlerTest::InProcessCrash(bool aborting) { char minidump_file[PATH_MAX]; ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); ASSERT_NE(0, nbytes); - // Ensure that minidump file exists and is > 0 bytes. - struct stat st; - ASSERT_EQ(0, stat(minidump_file, &st)); - ASSERT_LT(0, st.st_size); + + Minidump minidump(minidump_file); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + ASSERT_TRUE(exception); + + const MDRawExceptionStream* raw_exception = exception->exception(); + ASSERT_TRUE(raw_exception); + + if (aborting) { + EXPECT_EQ(MD_EXCEPTION_MAC_SOFTWARE, + raw_exception->exception_record.exception_code); + EXPECT_EQ(MD_EXCEPTION_CODE_MAC_ABORT, + raw_exception->exception_record.exception_flags); + } else { + EXPECT_EQ(MD_EXCEPTION_MAC_BAD_ACCESS, + raw_exception->exception_record.exception_code); +#if defined(__x86_64__) + EXPECT_EQ(MD_EXCEPTION_CODE_MAC_INVALID_ADDRESS, + raw_exception->exception_record.exception_flags); +#elif defined(__i386__) + EXPECT_EQ(MD_EXCEPTION_CODE_MAC_PROTECTION_FAILURE, + raw_exception->exception_record.exception_flags); +#endif + } + + const MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + uint64_t instruction_pointer; + ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); + + // Ideally would like to sanity check that abort() is on the stack + // but that's hard. + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(memory_list); + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + EXPECT_TRUE(region); // Child process should have exited with a zero status. int ret; @@ -138,11 +174,9 @@ TEST_F(ExceptionHandlerTest, InProcess) { InProcessCrash(false); } -#if TARGET_OS_IPHONE TEST_F(ExceptionHandlerTest, InProcessAbort) { InProcessCrash(true); } -#endif static bool DumpNameMDCallback(const char *dump_dir, const char *file_name, void *context, bool success) { diff --git a/toolkit/crashreporter/test/CrashTestUtils.jsm b/toolkit/crashreporter/test/CrashTestUtils.jsm index 9ba544473f8..21a12275116 100644 --- a/toolkit/crashreporter/test/CrashTestUtils.jsm +++ b/toolkit/crashreporter/test/CrashTestUtils.jsm @@ -1,4 +1,5 @@ -// XXXkhuey this needs a license header. +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ this.EXPORTED_SYMBOLS = ["CrashTestUtils"]; @@ -16,6 +17,7 @@ this.CrashTestUtils = { CRASH_RUNTIMEABORT: 2, CRASH_OOM: 3, CRASH_MOZ_CRASH: 4, + CRASH_ABORT: 5, // Constants for dumpHasStream() // From google_breakpad/common/minidump_format.h diff --git a/toolkit/crashreporter/test/nsTestCrasher.cpp b/toolkit/crashreporter/test/nsTestCrasher.cpp index 72f04688544..6287999a561 100644 --- a/toolkit/crashreporter/test/nsTestCrasher.cpp +++ b/toolkit/crashreporter/test/nsTestCrasher.cpp @@ -46,6 +46,7 @@ const int16_t CRASH_PURE_VIRTUAL_CALL = 1; const int16_t CRASH_RUNTIMEABORT = 2; const int16_t CRASH_OOM = 3; const int16_t CRASH_MOZ_CRASH = 4; +const int16_t CRASH_ABORT = 5; extern "C" NS_EXPORT void Crash(int16_t how) @@ -76,6 +77,10 @@ void Crash(int16_t how) MOZ_CRASH(); break; } + case CRASH_ABORT: { + abort(); + break; + } default: break; } diff --git a/toolkit/crashreporter/test/unit/test_crash_abort.js b/toolkit/crashreporter/test/unit/test_crash_abort.js new file mode 100644 index 00000000000..67008346f8d --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_abort.js @@ -0,0 +1,16 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() +{ + // Try crashing with an abort(). + do_crash(function() { + crashType = CrashTestUtils.CRASH_ABORT; + crashReporter.annotateCrashReport("TestKey", "TestValue"); + }, + function(mdump, extra) { + do_check_eq(extra.TestKey, "TestValue"); + }, + // process will exit with a zero exit status + true); +} diff --git a/toolkit/crashreporter/test/unit/xpcshell.ini b/toolkit/crashreporter/test/unit/xpcshell.ini index b055441dca6..8b2771fa42a 100644 --- a/toolkit/crashreporter/test/unit/xpcshell.ini +++ b/toolkit/crashreporter/test/unit/xpcshell.ini @@ -8,6 +8,9 @@ tail = [test_crash_oom.js] skip-if = os == 'win' && debug +[test_crash_abort.js] +skip-if = os == 'win' + [test_crashreporter.js] [test_crashreporter_crash.js] [test_crashreporter_crash_profile_lock.js] diff --git a/toolkit/devtools/server/actors/script.js b/toolkit/devtools/server/actors/script.js index 1d9b3692b23..0ae4530dcf6 100644 --- a/toolkit/devtools/server/actors/script.js +++ b/toolkit/devtools/server/actors/script.js @@ -288,9 +288,17 @@ EventLoopStack.prototype = { * The URL of the debuggee who pushed the event loop on top of the stack. */ get lastPausedUrl() { - return this.size > 0 - ? this._inspector.lastNestRequestor.url - : null; + let url = null; + if (this.size > 0) { + try { + url = this._inspector.lastNestRequestor.url + } catch (e) { + // The tab's URL getter may throw if the tab is destroyed by the time + // this code runs, but we don't really care at this point. + dumpn(e); + } + } + return url; }, /** @@ -936,7 +944,7 @@ ThreadActor.prototype = { // In case of multiple nested event loops (due to multiple debuggers open in // different tabs or multiple debugger clients connected to the same tab) // only allow resumption in a LIFO order. - if (this._nestedEventLoops.size + if (this._nestedEventLoops.size && this._nestedEventLoops.lastPausedUrl && this._nestedEventLoops.lastPausedUrl !== this._hooks.url) { return { error: "wrongOrder", diff --git a/toolkit/library/Makefile.in b/toolkit/library/Makefile.in index 817044c1a31..e76907f93c1 100644 --- a/toolkit/library/Makefile.in +++ b/toolkit/library/Makefile.in @@ -147,7 +147,6 @@ COMPONENT_LIBS += \ mediasniffer \ gkgfx \ gklayout \ - docshell \ embedcomponents \ webbrwsr \ nsappshell \ @@ -168,6 +167,15 @@ COMPONENT_LIBS += \ diskspacewatcher \ $(NULL) +SHARED_LIBRARY_LIBS += \ + $(DEPTH)/docshell/base/$(LIB_PREFIX)basedocshell_s.$(LIB_SUFFIX) \ + $(DEPTH)/uriloader/base/$(LIB_PREFIX)uriloaderbase_s.$(LIB_SUFFIX) \ + $(DEPTH)/uriloader/exthandler/$(LIB_PREFIX)exthandler_s.$(LIB_SUFFIX) \ + $(DEPTH)/uriloader/prefetch/$(LIB_PREFIX)prefetch_s.$(LIB_SUFFIX) \ + $(DEPTH)/docshell/shistory/src/$(LIB_PREFIX)shistory_s.$(LIB_SUFFIX) \ + $(DEPTH)/docshell/build/$(LIB_PREFIX)docshell.$(LIB_SUFFIX) \ + $(NULL) + ifeq (gonk,$(MOZ_WIDGET_TOOLKIT)) COMPONENT_LIBS += \ domwifi_s \ diff --git a/widget/cocoa/nsLookAndFeel.mm b/widget/cocoa/nsLookAndFeel.mm index 938224f881f..2fd0ff2e451 100644 --- a/widget/cocoa/nsLookAndFeel.mm +++ b/widget/cocoa/nsLookAndFeel.mm @@ -249,7 +249,7 @@ nsLookAndFeel::NativeGetColor(ColorID aID, nscolor &aColor) aColor = GetColorFromNSColor([NSColor selectedMenuItemTextColor]); break; case eColorID__moz_mac_disabledtoolbartext: - aColor = NS_RGB(0x3F,0x3F,0x3F); + aColor = GetColorFromNSColor([NSColor disabledControlTextColor]); break; case eColorID__moz_mac_menuselect: aColor = GetColorFromNSColor([NSColor alternateSelectedControlColor]); diff --git a/widget/tests/test_platform_colors.xul b/widget/tests/test_platform_colors.xul index c6777648de9..5a4bc40c7c6 100644 --- a/widget/tests/test_platform_colors.xul +++ b/widget/tests/test_platform_colors.xul @@ -75,7 +75,7 @@ var colors = { "-moz-mac-menushadow": ["rgb(163, 163, 163)"], "-moz-mac-menutextdisable": ["rgb(152, 152, 152)", "rgb(136, 136, 136)"], "-moz-mac-menutextselect": ["rgb(255, 255, 255)"], - "-moz-mac-disabledtoolbartext": ["rgb(63, 63, 63)"], + "-moz-mac-disabledtoolbartext": ["rgb(127, 127, 127)"], "-moz-mac-secondaryhighlight": ["rgb(212, 212, 212)"], "-moz-menuhover": ["rgb(115, 132, 153)", "rgb(127, 127, 127)", "rgb(56, 117, 215)", "rgb(255, 193, 31)", "rgb(243, 70, 72)", "rgb(255, 138, 34)", "rgb(102, 197, 71)", "rgb(140, 78, 184)"], "-moz-menuhovertext": ["rgb(255, 255, 255)", "rgb(255, 254, 254)", "rgb(254, 255, 254)"], diff --git a/xpcom/ds/Makefile.in b/xpcom/ds/Makefile.in deleted file mode 100644 index 992f4970bc1..00000000000 --- a/xpcom/ds/Makefile.in +++ /dev/null @@ -1,10 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -MOZILLA_INTERNAL_API = 1 - -include $(topsrcdir)/config/rules.mk - -LOCAL_INCLUDES += -I$(srcdir)/../io diff --git a/xpcom/ds/moz.build b/xpcom/ds/moz.build index 4f29be69263..4a4103f4790 100644 --- a/xpcom/ds/moz.build +++ b/xpcom/ds/moz.build @@ -119,3 +119,6 @@ LIBXUL_LIBRARY = True MSVC_ENABLE_PGO = True +LOCAL_INCLUDES += [ + '../io', +] diff --git a/xpcom/reflect/xptcall/src/Makefile.in b/xpcom/reflect/xptcall/src/Makefile.in deleted file mode 100644 index c2a1da78b5f..00000000000 --- a/xpcom/reflect/xptcall/src/Makefile.in +++ /dev/null @@ -1,16 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -MOZILLA_INTERNAL_API = 1 - -# we don't want the shared lib, but we want to force the creation of a static lib. -FORCE_STATIC_LIB = 1 - - -include $(topsrcdir)/config/rules.mk - -DEFINES += -DIMPL_LIBXUL - -LOCAL_INCLUDES += -I$(srcdir)/../../xptinfo/src diff --git a/xpcom/reflect/xptcall/src/moz.build b/xpcom/reflect/xptcall/src/moz.build index 4b47db7ed1a..ffd7e4c54d1 100644 --- a/xpcom/reflect/xptcall/src/moz.build +++ b/xpcom/reflect/xptcall/src/moz.build @@ -16,3 +16,8 @@ LIBRARY_NAME = 'xptcall' MSVC_ENABLE_PGO = True +LIBXUL_LIBRARY = True + +LOCAL_INCLUDES += [ + '/xpcom/reflect/xptinfo/src', +] diff --git a/xpcom/reflect/xptinfo/public/Makefile.in b/xpcom/reflect/xptinfo/public/Makefile.in deleted file mode 100644 index c14c389e2be..00000000000 --- a/xpcom/reflect/xptinfo/public/Makefile.in +++ /dev/null @@ -1,8 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -include $(topsrcdir)/config/rules.mk - -CFLAGS += -DEXPORT_XPCI_API diff --git a/xpcom/reflect/xptinfo/src/Makefile.in b/xpcom/reflect/xptinfo/src/Makefile.in deleted file mode 100644 index e8221baf221..00000000000 --- a/xpcom/reflect/xptinfo/src/Makefile.in +++ /dev/null @@ -1,17 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -MOZILLA_INTERNAL_API = 1 - -# we don't want the shared lib, but we want to force the creation of a static lib. -FORCE_STATIC_LIB = 1 - - -include $(topsrcdir)/config/rules.mk - -# For nsManifestLineReader class. -LOCAL_INCLUDES = -I$(srcdir)/../../../ds - -DEFINES += -DIMPL_LIBXUL diff --git a/xpcom/reflect/xptinfo/src/moz.build b/xpcom/reflect/xptinfo/src/moz.build index f257aab15a1..b2104cdf857 100644 --- a/xpcom/reflect/xptinfo/src/moz.build +++ b/xpcom/reflect/xptinfo/src/moz.build @@ -17,3 +17,9 @@ LIBRARY_NAME = 'xptinfo' MSVC_ENABLE_PGO = True +LIBXUL_LIBRARY = True + +# For nsManifestLineReader class. +LOCAL_INCLUDES += [ + '/xpcom/ds', +] diff --git a/xpcom/threads/Makefile.in b/xpcom/threads/Makefile.in deleted file mode 100644 index cfb6a17d9f9..00000000000 --- a/xpcom/threads/Makefile.in +++ /dev/null @@ -1,8 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -MOZILLA_INTERNAL_API = 1 -LOCAL_INCLUDES = -I$(srcdir)/../components -LOCAL_INCLUDES = -I$(srcdir)/../build diff --git a/xpcom/threads/moz.build b/xpcom/threads/moz.build index c31d1f03cc9..e621f886f9c 100644 --- a/xpcom/threads/moz.build +++ b/xpcom/threads/moz.build @@ -55,3 +55,6 @@ LIBXUL_LIBRARY = True MSVC_ENABLE_PGO = True +LOCAL_INCLUDES += [ + '../build', +]