Merge latest green birch changeset and mozilla-central
@ -415,6 +415,31 @@ nsAccessiblePivot::MovePivotInternal(Accessible* aPosition,
|
||||
return NotifyOfPivotChange(oldPosition, oldStart, oldEnd, aReason);
|
||||
}
|
||||
|
||||
Accessible*
|
||||
nsAccessiblePivot::AdjustStartPosition(Accessible* aAccessible,
|
||||
RuleCache& aCache,
|
||||
uint16_t* aFilterResult,
|
||||
nsresult* aResult)
|
||||
{
|
||||
Accessible* matched = aAccessible;
|
||||
*aResult = aCache.ApplyFilter(aAccessible, aFilterResult);
|
||||
|
||||
if (aAccessible != mRoot && aAccessible != mModalRoot) {
|
||||
for (Accessible* temp = aAccessible->Parent();
|
||||
temp && temp != mRoot && temp != mModalRoot; temp = temp->Parent()) {
|
||||
uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
|
||||
*aResult = aCache.ApplyFilter(temp, &filtered);
|
||||
NS_ENSURE_SUCCESS(*aResult, nullptr);
|
||||
if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) {
|
||||
*aFilterResult = filtered;
|
||||
matched = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
Accessible*
|
||||
nsAccessiblePivot::SearchBackward(Accessible* aAccessible,
|
||||
nsIAccessibleTraversalRule* aRule,
|
||||
@ -428,15 +453,13 @@ nsAccessiblePivot::SearchBackward(Accessible* aAccessible,
|
||||
return nullptr;
|
||||
|
||||
RuleCache cache(aRule);
|
||||
Accessible* accessible = aAccessible;
|
||||
|
||||
uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
|
||||
Accessible* accessible = AdjustStartPosition(aAccessible, cache,
|
||||
&filtered, aResult);
|
||||
NS_ENSURE_SUCCESS(*aResult, nullptr);
|
||||
|
||||
if (aSearchCurrent) {
|
||||
*aResult = cache.ApplyFilter(accessible, &filtered);
|
||||
NS_ENSURE_SUCCESS(*aResult, nullptr);
|
||||
if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
|
||||
return accessible;
|
||||
if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)) {
|
||||
return accessible;
|
||||
}
|
||||
|
||||
Accessible* root = GetActiveRoot();
|
||||
@ -492,7 +515,7 @@ nsAccessiblePivot::SearchForward(Accessible* aAccessible,
|
||||
RuleCache cache(aRule);
|
||||
|
||||
uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
|
||||
*aResult = cache.ApplyFilter(accessible, &filtered);
|
||||
accessible = AdjustStartPosition(accessible, cache, &filtered, aResult);
|
||||
NS_ENSURE_SUCCESS(*aResult, nullptr);
|
||||
if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH))
|
||||
return accessible;
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
class nsIAccessibleTraversalRule;
|
||||
class RuleCache;
|
||||
|
||||
/**
|
||||
* Class represents an accessible pivot.
|
||||
@ -90,6 +91,18 @@ private:
|
||||
*/
|
||||
bool MovePivotInternal(Accessible* aPosition, PivotMoveReason aReason);
|
||||
|
||||
/*
|
||||
* Get initial node we should start a search from with a given rule.
|
||||
*
|
||||
* When we do a move operation from one position to another,
|
||||
* the initial position can be inside of a subtree that is ignored by
|
||||
* the given rule. We need to step out of the ignored subtree and start
|
||||
* the search from there.
|
||||
*
|
||||
*/
|
||||
Accessible* AdjustStartPosition(Accessible* aAccessible, RuleCache& aCache,
|
||||
uint16_t* aFilterResult, nsresult* aResult);
|
||||
|
||||
/*
|
||||
* The root accessible.
|
||||
*/
|
||||
|
@ -1220,7 +1220,41 @@ HyperTextAccessible::GetTextAfterOffset(int32_t aOffset,
|
||||
return GetText(*aStartOffset, *aEndOffset, aText);
|
||||
|
||||
case BOUNDARY_LINE_START:
|
||||
if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
|
||||
offset = AdjustCaretOffset(offset);
|
||||
|
||||
// Down arrow, home key, down arrow, home key.
|
||||
*aStartOffset = FindLineBoundary(offset, eDirNext, eSelectLine);
|
||||
if (*aStartOffset != CharacterCount()) {
|
||||
*aStartOffset = FindLineBoundary(*aStartOffset, eDirPrevious, eSelectBeginLine);
|
||||
*aEndOffset = FindLineBoundary(*aStartOffset, eDirNext, eSelectLine);
|
||||
if (*aEndOffset != CharacterCount())
|
||||
*aEndOffset = FindLineBoundary(*aEndOffset, eDirPrevious, eSelectBeginLine);
|
||||
} else {
|
||||
*aEndOffset = CharacterCount();
|
||||
}
|
||||
return GetText(*aStartOffset, *aEndOffset, aText);
|
||||
|
||||
case BOUNDARY_LINE_END:
|
||||
if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
|
||||
offset = AdjustCaretOffset(offset);
|
||||
|
||||
// Empty last line doesn't have own frame (a previous line contains '\n'
|
||||
// character instead) thus we can't operate on last line separately
|
||||
// from the previous line.
|
||||
if (IsEmptyLastLineOffset(offset)) {
|
||||
*aStartOffset = *aEndOffset = offset;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// End key, down arrow, end key.
|
||||
*aStartOffset = FindLineBoundary(offset, eDirNext, eSelectEndLine);
|
||||
*aEndOffset = FindLineBoundary(*aStartOffset, eDirNext, eSelectLine);
|
||||
if (*aEndOffset != CharacterCount())
|
||||
*aEndOffset = FindLineBoundary(*aEndOffset, eDirNext, eSelectEndLine);
|
||||
|
||||
return GetText(*aStartOffset, *aEndOffset, aText);
|
||||
|
||||
case BOUNDARY_ATTRIBUTE_RANGE:
|
||||
return GetTextHelper(eGetAfter, aBoundaryType, aOffset,
|
||||
aStartOffset, aEndOffset, aText);
|
||||
|
@ -76,6 +76,21 @@ function VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMetho
|
||||
{
|
||||
this.__proto__ = new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc);
|
||||
|
||||
this.match = function VCChangedChecker_check(aEvent)
|
||||
{
|
||||
var event = null;
|
||||
try {
|
||||
event = aEvent.QueryInterface(nsIAccessibleVirtualCursorChangeEvent);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var expectedReason = VCChangedChecker.methodReasonMap[aPivotMoveMethod] ||
|
||||
nsIAccessiblePivot.REASON_NONE;
|
||||
|
||||
return event.reason == expectedReason;
|
||||
};
|
||||
|
||||
this.check = function VCChangedChecker_check(aEvent)
|
||||
{
|
||||
SimpleTest.info("VCChangedChecker_check");
|
||||
@ -87,11 +102,6 @@ function VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMetho
|
||||
SimpleTest.ok(false, "Does not support correct interface: " + e);
|
||||
}
|
||||
|
||||
SimpleTest.is(
|
||||
event.reason,
|
||||
VCChangedChecker.methodReasonMap[aPivotMoveMethod],
|
||||
'wrong move reason');
|
||||
|
||||
var position = aDocAcc.virtualCursor.position;
|
||||
var idMatches = position && position.DOMNode.id == aIdOrNameOrAcc;
|
||||
var nameMatches = position && position.name == aIdOrNameOrAcc;
|
||||
@ -196,9 +206,14 @@ function setVCPosInvoker(aDocAcc, aPivotMoveMethod, aRule, aIdOrNameOrAcc)
|
||||
{
|
||||
VCChangedChecker.
|
||||
storePreviousPosAndOffset(aDocAcc.virtualCursor);
|
||||
var moved = aDocAcc.virtualCursor[aPivotMoveMethod](aRule);
|
||||
SimpleTest.ok((expectMove && moved) || (!expectMove && !moved),
|
||||
"moved pivot");
|
||||
if (aPivotMoveMethod && aRule) {
|
||||
var moved = aDocAcc.virtualCursor[aPivotMoveMethod](aRule);
|
||||
SimpleTest.is(!!moved, !!expectMove,
|
||||
"moved pivot with " + aPivotMoveMethod +
|
||||
" to " + aIdOrNameOrAcc);
|
||||
} else {
|
||||
aDocAcc.virtualCursor.position = getAccessible(aIdOrNameOrAcc);
|
||||
}
|
||||
};
|
||||
|
||||
this.getID = function setVCPosInvoker_getID()
|
||||
@ -447,7 +462,7 @@ function removeVCRootInvoker(aRootNode)
|
||||
*/
|
||||
function dumpTraversalSequence(aPivot, aRule)
|
||||
{
|
||||
var sequence = []
|
||||
var sequence = [];
|
||||
if (aPivot.moveFirst(aRule)) {
|
||||
do {
|
||||
sequence.push("'" + prettyName(aPivot.position) + "'");
|
||||
|
@ -15,7 +15,7 @@
|
||||
<p id="paragraph-2" aria-hidden="">
|
||||
Sed accumsan luctus lacus, vitae mollis arcu tristique vulputate.</p>
|
||||
<p id="paragraph-3" aria-hidden="true">
|
||||
Maybe it was the other <i>George Michael</i>.
|
||||
<a href="#" id="hidden-link">Maybe</a> it was the other <i>George Michael</i>.
|
||||
You know, the <a href="#">singer-songwriter</a>.
|
||||
</p>
|
||||
<iframe
|
||||
|
@ -95,6 +95,12 @@
|
||||
gQueue.push(new setModalRootInvoker(docAcc, docAcc.parent,
|
||||
NS_ERROR_INVALID_ARG));
|
||||
|
||||
// Put cursor in an ignored subtree
|
||||
gQueue.push(new setVCPosInvoker(docAcc, null, null,
|
||||
getAccessible(doc.getElementById("hidden-link"))));
|
||||
// Next item shoud be outside of that subtree
|
||||
gQueue.push(new setVCPosInvoker(docAcc, "moveNext", ObjectTraversalRule, "An "));
|
||||
|
||||
gQueue.invoke();
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@
|
||||
["textarea"]);
|
||||
|
||||
testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "", 15, 15,
|
||||
"textarea", kOk, kTodo, kTodo);
|
||||
[ "textarea" ]);
|
||||
|
||||
testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "words", 10, 15,
|
||||
[ "textarea" ]);
|
||||
@ -72,10 +72,10 @@
|
||||
this.finalCheck = function moveToLastLineStart_finalCheck()
|
||||
{
|
||||
testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_START, "", 15, 15,
|
||||
"textarea", kTodo, kTodo, kOk);
|
||||
[ "textarea" ]);
|
||||
|
||||
testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "", 15, 15,
|
||||
"textarea", kTodo, kTodo, kOk);
|
||||
[ "textarea" ]);
|
||||
|
||||
testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "words", 10, 15,
|
||||
[ "textarea" ]);
|
||||
@ -104,10 +104,10 @@
|
||||
this.finalCheck = function moveToMiddleLineStart_finalCheck()
|
||||
{
|
||||
testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_START, "words", 10, 15,
|
||||
"textarea", kTodo, kTodo, kTodo);
|
||||
[ "textarea" ]);
|
||||
|
||||
testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "words", 10, 15,
|
||||
"textarea", kTodo, kTodo, kTodo);
|
||||
[ "textarea" ]);
|
||||
|
||||
testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "two ", 6, 10,
|
||||
[ "textarea" ]);
|
||||
@ -135,10 +135,10 @@
|
||||
this.finalCheck = function moveToMiddleLineEnd_finalCheck()
|
||||
{
|
||||
testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_START, "words", 10, 15,
|
||||
"textarea", kTodo, kTodo, kTodo);
|
||||
[ "textarea" ]);
|
||||
|
||||
testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "words", 10, 15,
|
||||
"textarea", kTodo, kTodo, kOk);
|
||||
[ "textarea" ]);
|
||||
|
||||
testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "two ", 6, 10,
|
||||
[ "textarea" ]);
|
||||
@ -166,10 +166,10 @@
|
||||
this.finalCheck = function moveToFirstLineStart_finalCheck()
|
||||
{
|
||||
testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_START, "two ", 6, 10,
|
||||
"textarea", kTodo, kTodo, kTodo);
|
||||
[ "textarea" ]);
|
||||
|
||||
testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "aword", 0, 5,
|
||||
"textarea", kOk, kOk, kOk);
|
||||
testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "\ntwo ", 5, 10,
|
||||
[ "textarea" ]);
|
||||
|
||||
testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "aword\n", 0, 6,
|
||||
[ "textarea" ]);
|
||||
@ -197,10 +197,10 @@
|
||||
this.finalCheck = function moveToFirstLineStart_finalCheck()
|
||||
{
|
||||
testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_START, "two ", 6, 10,
|
||||
"textarea", kTodo, kTodo, kTodo);
|
||||
[ "textarea" ]);
|
||||
|
||||
testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "\ntwo ", 5, 10,
|
||||
"textarea", kTodo, kTodo, kTodo);
|
||||
[ "textarea" ]);
|
||||
|
||||
testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "aword\n", 0, 6,
|
||||
[ "textarea" ]);
|
||||
@ -224,8 +224,6 @@
|
||||
var gQueue = null;
|
||||
function doTest()
|
||||
{
|
||||
SimpleTest.expectAssertions(1);
|
||||
|
||||
gQueue = new eventQueue();
|
||||
gQueue.push(new moveToLastLineEnd());
|
||||
gQueue.push(new moveToLastLineStart());
|
||||
|
@ -16,8 +16,6 @@
|
||||
|
||||
function doTest()
|
||||
{
|
||||
SimpleTest.expectAssertions(5);
|
||||
|
||||
// __o__n__e__w__o__r__d__\n
|
||||
// 0 1 2 3 4 5 6 7
|
||||
// __\n
|
||||
@ -34,74 +32,19 @@
|
||||
// getTextAfterOffset
|
||||
|
||||
// BOUNDARY_LINE_START
|
||||
testTextAfterOffset(0, BOUNDARY_LINE_START, "\n", 8, 9,
|
||||
"div", kTodo, kTodo, kTodo,
|
||||
"divbr", kTodo, kTodo, kTodo,
|
||||
"editable", kTodo, kTodo, kTodo,
|
||||
"editablebr", kTodo, kTodo, kTodo,
|
||||
"textarea", kTodo, kTodo, kTodo);
|
||||
testTextAfterOffset(7, BOUNDARY_LINE_START, "\n", 8, 9,
|
||||
"div", kOk, kTodo, kTodo,
|
||||
"divbr", kOk, kTodo, kTodo,
|
||||
"editable", kOk, kTodo, kTodo,
|
||||
"editablebr", kOk, kTodo, kTodo,
|
||||
"textarea", kOk, kTodo, kTodo);
|
||||
testTextAfterOffset(8, BOUNDARY_LINE_START, "two words\n", 9, 19,
|
||||
"div", kTodo, kTodo, kTodo,
|
||||
"divbr", kTodo, kTodo, kTodo,
|
||||
"editable", kTodo, kTodo, kTodo,
|
||||
"editablebr", kTodo, kTodo, kTodo,
|
||||
"textarea", kTodo, kTodo, kTodo);
|
||||
testTextAfterOffset(9, BOUNDARY_LINE_START, "", 19, 19,
|
||||
"div", kTodo, kTodo, kTodo,
|
||||
"divbr", kTodo, kTodo, kTodo,
|
||||
"editable", kTodo, kTodo, kTodo,
|
||||
"editablebr", kTodo, kTodo, kTodo,
|
||||
"textarea", kTodo, kTodo, kTodo);
|
||||
testTextAfterOffset(19, BOUNDARY_LINE_START, "", 19, 19,
|
||||
"div", kOk, kOk, kTodo,
|
||||
"divbr", kOk, kOk, kTodo,
|
||||
"editable", kOk, kOk, kTodo,
|
||||
"editablebr", kOk, kOk, kTodo,
|
||||
"textarea", kOk, kOk, kTodo);
|
||||
testTextAfterOffset(0, BOUNDARY_LINE_START, "\n", 8, 9, IDs);
|
||||
testTextAfterOffset(7, BOUNDARY_LINE_START, "\n", 8, 9, IDs);
|
||||
testTextAfterOffset(8, BOUNDARY_LINE_START, "two words\n", 9, 19, IDs);
|
||||
testTextAfterOffset(9, BOUNDARY_LINE_START, "", 19, 19, IDs);
|
||||
testTextAfterOffset(19, BOUNDARY_LINE_START, "", 19, 19, IDs);
|
||||
|
||||
// BOUNDARY_LINE_END
|
||||
testTextAfterOffset(0, BOUNDARY_LINE_END, "\n", 7, 8,
|
||||
"div", kTodo, kTodo, kTodo,
|
||||
"divbr", kTodo, kTodo, kTodo,
|
||||
"editable", kTodo, kTodo, kTodo,
|
||||
"editablebr", kTodo, kTodo, kTodo,
|
||||
"textarea", kTodo, kTodo, kTodo);
|
||||
testTextAfterOffset(7, BOUNDARY_LINE_END, "\n", 7, 8,
|
||||
"div", kOk, kOk, kOk,
|
||||
"divbr", kOk, kOk, kOk,
|
||||
"editable", kOk, kOk, kOk,
|
||||
"editablebr", kOk, kOk, kOk,
|
||||
"textarea", kOk, kOk, kOk);
|
||||
testTextAfterOffset(8, BOUNDARY_LINE_END, "\ntwo words", 8, 18,
|
||||
"div", kOk, kOk, kOk,
|
||||
"divbr", kOk, kOk, kOk,
|
||||
"editable", kOk, kOk, kOk,
|
||||
"editablebr", kOk, kOk, kOk,
|
||||
"textarea", kOk, kOk, kOk);
|
||||
testTextAfterOffset(9, BOUNDARY_LINE_END, "\n", 18, 19,
|
||||
"div", kTodo, kTodo, kTodo,
|
||||
"divbr", kTodo, kTodo, kTodo,
|
||||
"editable", kTodo, kTodo, kTodo,
|
||||
"editablebr", kTodo, kTodo, kTodo,
|
||||
"textarea", kTodo, kTodo, kTodo);
|
||||
testTextAfterOffset(18, BOUNDARY_LINE_END, "\n", 18, 19,
|
||||
"div", kOk, kOk, kOk,
|
||||
"divbr", kOk, kOk, kOk,
|
||||
"editable", kOk, kOk, kOk,
|
||||
"editablebr", kOk, kOk, kOk,
|
||||
"textarea", kOk, kOk, kOk);
|
||||
testTextAfterOffset(19, BOUNDARY_LINE_END, "", 19, 19,
|
||||
"div", kOk, kTodo, kTodo,
|
||||
"divbr", kOk, kTodo, kTodo,
|
||||
"editable", kOk, kTodo, kTodo,
|
||||
"editablebr", kOk, kTodo, kTodo,
|
||||
"textarea", kOk, kTodo, kTodo);
|
||||
testTextAfterOffset(0, BOUNDARY_LINE_END, "\n", 7, 8, IDs);
|
||||
testTextAfterOffset(7, BOUNDARY_LINE_END, "\n", 7, 8, IDs);
|
||||
testTextAfterOffset(8, BOUNDARY_LINE_END, "\ntwo words", 8, 18, IDs);
|
||||
testTextAfterOffset(9, BOUNDARY_LINE_END, "\n", 18, 19, IDs);
|
||||
testTextAfterOffset(18, BOUNDARY_LINE_END, "\n", 18, 19, IDs);
|
||||
testTextAfterOffset(19, BOUNDARY_LINE_END, "", 19, 19, IDs);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// getTextBeforeOffset
|
||||
@ -166,6 +109,11 @@
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=855732">
|
||||
Bug 855732
|
||||
</a>
|
||||
<a target="_blank"
|
||||
title=" getTextAfterOffset for line boundary on new rails"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=882292">
|
||||
Bug 882292
|
||||
</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test">
|
||||
|
@ -11,12 +11,6 @@
|
||||
<script type="application/javascript"
|
||||
src="../text.js"></script>
|
||||
<script type="application/javascript">
|
||||
if (navigator.platform.startsWith("Mac")) {
|
||||
SimpleTest.expectAssertions(0, 4);
|
||||
} else {
|
||||
SimpleTest.expectAssertions(4);
|
||||
}
|
||||
|
||||
function doTest()
|
||||
{
|
||||
// __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
|
||||
@ -29,48 +23,16 @@
|
||||
var regularIDs = [ "input", "div", "editable" ];
|
||||
|
||||
// BOUNDARY_LINE_START
|
||||
testTextAfterOffset(0, BOUNDARY_LINE_START, "", 15, 15,
|
||||
"input", kTodo, kTodo, kOk,
|
||||
"div", kTodo, kTodo, kOk,
|
||||
"editable", kTodo, kTodo, kOk,
|
||||
"textarea", kTodo, kTodo, kOk);
|
||||
testTextAfterOffset(1, BOUNDARY_LINE_START, "", 15, 15,
|
||||
"input", kTodo, kTodo, kOk,
|
||||
"div", kTodo, kTodo, kOk,
|
||||
"editable", kTodo, kTodo, kOk,
|
||||
"textarea", kTodo, kTodo, kOk);
|
||||
testTextAfterOffset(14, BOUNDARY_LINE_START, "", 15, 15,
|
||||
"input", kTodo, kTodo, kOk,
|
||||
"div", kTodo, kTodo, kOk,
|
||||
"editable", kTodo, kTodo, kOk,
|
||||
"textarea", kTodo, kTodo, kOk);
|
||||
testTextAfterOffset(15, BOUNDARY_LINE_START, "", 15, 15,
|
||||
"input", kOk, kOk, kOk,
|
||||
"div", kOk, kOk, kOk,
|
||||
"editable", kOk, kOk, kOk,
|
||||
"textarea", kOk, kOk, kOk);
|
||||
testTextAfterOffset(0, BOUNDARY_LINE_START, "", 15, 15, IDs);
|
||||
testTextAfterOffset(1, BOUNDARY_LINE_START, "", 15, 15, IDs);
|
||||
testTextAfterOffset(14, BOUNDARY_LINE_START, "", 15, 15, IDs);
|
||||
testTextAfterOffset(15, BOUNDARY_LINE_START, "", 15, 15, IDs);
|
||||
|
||||
// BOUNDARY_LINE_END
|
||||
testTextAfterOffset(0, BOUNDARY_LINE_END, "", 15, 15,
|
||||
"input", kTodo, kTodo, kOk,
|
||||
"div", kTodo, kTodo, kOk,
|
||||
"editable", kTodo, kTodo, kOk,
|
||||
"textarea", kTodo, kTodo, kOk);
|
||||
testTextAfterOffset(1, BOUNDARY_LINE_END, "", 15, 15,
|
||||
"input", kTodo, kTodo, kOk,
|
||||
"div", kTodo, kTodo, kOk,
|
||||
"editable", kTodo, kTodo, kOk,
|
||||
"textarea", kTodo, kTodo, kOk);
|
||||
testTextAfterOffset(14, BOUNDARY_LINE_END, "", 15, 15,
|
||||
"input", kTodo, kTodo, kOk,
|
||||
"div", kTodo, kTodo, kOk,
|
||||
"editable", kTodo, kTodo, kOk,
|
||||
"textarea", kTodo, kTodo, kOk);
|
||||
testTextAfterOffset(15, BOUNDARY_LINE_END, "", 15, 15,
|
||||
"input", kOk, kTodo, kTodo,
|
||||
"div", kOk, kTodo, kTodo,
|
||||
"editable", kOk, kTodo, kTodo,
|
||||
"textarea", kOk, kTodo, kTodo);
|
||||
testTextAfterOffset(0, BOUNDARY_LINE_END, "", 15, 15, IDs);
|
||||
testTextAfterOffset(1, BOUNDARY_LINE_END, "", 15, 15, IDs);
|
||||
testTextAfterOffset(14, BOUNDARY_LINE_END, "", 15, 15, IDs);
|
||||
testTextAfterOffset(15, BOUNDARY_LINE_END, "", 15, 15, IDs);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// getTextBeforeOffset
|
||||
@ -131,6 +93,11 @@
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=855732">
|
||||
Bug 855732
|
||||
</a>
|
||||
<a target="_blank"
|
||||
title=" getTextAfterOffset for line boundary on new rails"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=882292">
|
||||
Bug 882292
|
||||
</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test">
|
||||
|
@ -559,10 +559,13 @@ pref("javascript.options.mem.log", false);
|
||||
// Increase mark slice time from 10ms to 30ms
|
||||
pref("javascript.options.mem.gc_incremental_slice_ms", 30);
|
||||
|
||||
pref("javascript.options.mem.gc_high_frequency_heap_growth_max", 150);
|
||||
// Increase time to get more high frequency GC on benchmarks from 1000ms to 1500ms
|
||||
pref("javascript.options.mem.gc_high_frequency_time_limit_ms", 1500);
|
||||
|
||||
pref("javascript.options.mem.gc_high_frequency_heap_growth_max", 300);
|
||||
pref("javascript.options.mem.gc_high_frequency_heap_growth_min", 120);
|
||||
pref("javascript.options.mem.gc_high_frequency_high_limit_mb", 40);
|
||||
pref("javascript.options.mem.gc_high_frequency_low_limit_mb", 10);
|
||||
pref("javascript.options.mem.gc_high_frequency_low_limit_mb", 0);
|
||||
pref("javascript.options.mem.gc_low_frequency_heap_growth", 120);
|
||||
pref("javascript.options.mem.high_water_mark", 6);
|
||||
pref("javascript.options.mem.gc_allocation_threshold_mb", 1);
|
||||
|
@ -19,3 +19,6 @@ ifdef MOZ_MAINTENANCE_SERVICE
|
||||
$(MAKE) -C installer/windows maintenanceservice_installer
|
||||
endif
|
||||
endif
|
||||
|
||||
check::
|
||||
$(PYTHON) $(topsrcdir)/build/compare-mozconfig/compare-mozconfigs-wrapper.py
|
||||
|
@ -3668,10 +3668,8 @@ var XULBrowserWindow = {
|
||||
init: function () {
|
||||
this.throbberElement = document.getElementById("navigator-throbber");
|
||||
|
||||
// Initialize the security button's state and tooltip text. Remember to reset
|
||||
// _hostChanged, otherwise onSecurityChange will short circuit.
|
||||
// Initialize the security button's state and tooltip text.
|
||||
var securityUI = gBrowser.securityUI;
|
||||
this._hostChanged = true;
|
||||
this.onSecurityChange(null, null, securityUI.state);
|
||||
},
|
||||
|
||||
@ -3862,7 +3860,6 @@ var XULBrowserWindow = {
|
||||
|
||||
onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) {
|
||||
var location = aLocationURI ? aLocationURI.spec : "";
|
||||
this._hostChanged = true;
|
||||
|
||||
// Hide the form invalid popup.
|
||||
if (gFormSubmitObserver.panel) {
|
||||
@ -4004,37 +4001,18 @@ var XULBrowserWindow = {
|
||||
|
||||
// Properties used to cache security state used to update the UI
|
||||
_state: null,
|
||||
_hostChanged: false, // onLocationChange will flip this bit
|
||||
_lastLocation: null,
|
||||
|
||||
onSecurityChange: function (aWebProgress, aRequest, aState) {
|
||||
// Don't need to do anything if the data we use to update the UI hasn't
|
||||
// changed
|
||||
let uri = gBrowser.currentURI;
|
||||
let spec = uri.spec;
|
||||
if (this._state == aState &&
|
||||
!this._hostChanged) {
|
||||
#ifdef DEBUG
|
||||
try {
|
||||
var contentHost = gBrowser.contentWindow.location.host;
|
||||
if (this._host !== undefined && this._host != contentHost) {
|
||||
Components.utils.reportError(
|
||||
"ASSERTION: browser.js host is inconsistent. Content window has " +
|
||||
"<" + contentHost + "> but cached host is <" + this._host + ">.\n"
|
||||
);
|
||||
}
|
||||
} catch (ex) {}
|
||||
#endif
|
||||
this._lastLocation == spec)
|
||||
return;
|
||||
}
|
||||
this._state = aState;
|
||||
|
||||
#ifdef DEBUG
|
||||
try {
|
||||
this._host = gBrowser.contentWindow.location.host;
|
||||
} catch(ex) {
|
||||
this._host = null;
|
||||
}
|
||||
#endif
|
||||
|
||||
this._hostChanged = false;
|
||||
this._lastLocation = spec;
|
||||
|
||||
// aState is defined as a bitmask that may be extended in the future.
|
||||
// We filter out any unknown bits before testing for known values.
|
||||
@ -4063,7 +4041,6 @@ var XULBrowserWindow = {
|
||||
gURLBar.removeAttribute("level");
|
||||
}
|
||||
|
||||
let uri = gBrowser.currentURI;
|
||||
try {
|
||||
uri = Services.uriFixup.createExposableURI(uri);
|
||||
} catch (e) {}
|
||||
|
@ -87,7 +87,7 @@ function part7() {
|
||||
ok(!objLoadingContent.activated, "plugin should not be activated");
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(plugin, {}, gNewWindow.gBrowser.selectedBrowser.contentWindow);
|
||||
let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser).dismissed;
|
||||
let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser).dismissed && gNewWindow.PopupNotifications.panel.firstChild;
|
||||
waitForCondition(condition, part8, "waited too long for plugin to activate");
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,6 @@ function test() {
|
||||
|
||||
function checkIdentityMode(win) {
|
||||
let identityMode = win.document.getElementById("identity-box").className;
|
||||
is(identityMode, "unknownIdentity", "Identity should be chromeUI but is currently " +
|
||||
"shown as unknownIdentity for new windows.");
|
||||
is(identityMode, "chromeUI", "Identity state should be chromeUI for about:home in a new window");
|
||||
finish();
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 965 B After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 4.2 KiB |
@ -118,15 +118,20 @@
|
||||
SimpleTest.isnot(listItem, null, "Valid listItem found");
|
||||
|
||||
tagsSelector.ensureElementIsVisible(listItem);
|
||||
let visibleIndex = tagsSelector.getIndexOfFirstVisibleRow();
|
||||
let firstVisibleTag = tags[tagsSelector.getIndexOfFirstVisibleRow()];
|
||||
|
||||
SimpleTest.ok(listItem.checked, "Item is checked " + i);
|
||||
let selectedTag = listItem.label;
|
||||
|
||||
// Uncheck the tag.
|
||||
listItem.checked = false;
|
||||
SimpleTest.is(tagsSelector.getIndexOfFirstVisibleRow(),
|
||||
Math.max(visibleIndex -1, 0),
|
||||
|
||||
// Ensure the first visible tag is still visible in the list.
|
||||
let firstVisibleIndex = tagsSelector.getIndexOfFirstVisibleRow();
|
||||
let lastVisibleIndex = firstVisibleIndex + tagsSelector.getNumberOfVisibleRows() -1;
|
||||
let expectedTagIndex = tags.indexOf(firstVisibleTag);
|
||||
SimpleTest.ok(expectedTagIndex >= firstVisibleIndex &&
|
||||
expectedTagIndex <= lastVisibleIndex,
|
||||
"Scroll position is correct");
|
||||
|
||||
// The listbox is rebuilt, so we have to get the new element.
|
||||
|
90
browser/config/mozconfigs/whitelist
Normal file
@ -0,0 +1,90 @@
|
||||
# 'nightly' contains things that are in nightly mozconfigs and allowed to be missing from release builds.
|
||||
# Other keys in whitelist contain things are in that branches mozconfigs and allowed to be missing from nightly builds.
|
||||
whitelist = {
|
||||
'release': {},
|
||||
'nightly': {},
|
||||
}
|
||||
|
||||
all_platforms = ['win32', 'linux32', 'linux64', 'macosx-universal']
|
||||
|
||||
for platform in all_platforms:
|
||||
whitelist['nightly'][platform] = [
|
||||
'ac_add_options --enable-update-channel=nightly',
|
||||
'ac_add_options --enable-profiling',
|
||||
'mk_add_options CLIENT_PY_ARGS="--hg-options=\'--verbose --time\' --hgtool=../tools/buildfarm/utils/hgtool.py --skip-chatzilla --skip-comm --skip-inspector --skip-venkman --tinderbox-print"'
|
||||
]
|
||||
|
||||
for platform in ['linux32', 'linux64', 'macosx-universal']:
|
||||
whitelist['nightly'][platform] += [
|
||||
'ac_add_options --enable-codesighs',
|
||||
'mk_add_options MOZ_MAKE_FLAGS="-j4"',
|
||||
]
|
||||
|
||||
for platform in ['linux32', 'linux64', 'macosx-universal', 'win32']:
|
||||
whitelist['nightly'][platform] += ['ac_add_options --enable-signmar']
|
||||
whitelist['nightly'][platform] += ['ac_add_options --enable-js-diagnostics']
|
||||
|
||||
whitelist['nightly']['linux32'] += [
|
||||
'CXX=$REAL_CXX',
|
||||
'CXX="ccache $REAL_CXX"',
|
||||
'CC="ccache $REAL_CC"',
|
||||
'mk_add_options PROFILE_GEN_SCRIPT=@TOPSRCDIR@/build/profile_pageloader.pl',
|
||||
'ac_add_options --with-ccache=/usr/bin/ccache',
|
||||
'export MOZILLA_OFFICIAL=1',
|
||||
'export MOZ_TELEMETRY_REPORTING=1',
|
||||
"mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
|
||||
'STRIP_FLAGS="--strip-debug"',
|
||||
'ac_add_options --disable-elf-hack # --enable-elf-hack conflicts with --enable-profiling',
|
||||
]
|
||||
|
||||
whitelist['nightly']['linux64'] += [
|
||||
'export MOZILLA_OFFICIAL=1',
|
||||
'export MOZ_TELEMETRY_REPORTING=1',
|
||||
"mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
|
||||
'STRIP_FLAGS="--strip-debug"',
|
||||
'ac_add_options --with-ccache=/usr/bin/ccache',
|
||||
'ac_add_options --disable-elf-hack # --enable-elf-hack conflicts with --enable-profiling',
|
||||
]
|
||||
|
||||
whitelist['nightly']['macosx-universal'] += [
|
||||
'ac_add_options --with-macbundlename-prefix=Firefox',
|
||||
'mk_add_options MOZ_MAKE_FLAGS="-j12"',
|
||||
'ac_add_options --with-ccache',
|
||||
'ac_add_options --disable-install-strip',
|
||||
'ac_add_options --enable-instruments',
|
||||
'ac_add_options --enable-dtrace',
|
||||
]
|
||||
|
||||
whitelist['nightly']['win32'] += [
|
||||
'. $topsrcdir/configs/mozilla2/win32/include/choose-make-flags',
|
||||
'mk_add_options MOZ_MAKE_FLAGS=-j1',
|
||||
'if test "$IS_NIGHTLY" != ""; then',
|
||||
'ac_add_options --disable-auto-deps',
|
||||
'fi',
|
||||
'ac_add_options --enable-metro',
|
||||
]
|
||||
|
||||
for platform in all_platforms:
|
||||
whitelist['release'][platform] = [
|
||||
'ac_add_options --enable-update-channel=release',
|
||||
'ac_add_options --enable-official-branding',
|
||||
'mk_add_options MOZ_MAKE_FLAGS="-j4"',
|
||||
'export BUILDING_RELEASE=1',
|
||||
]
|
||||
whitelist['release']['win32'] += ['mk_add_options MOZ_PGO=1']
|
||||
whitelist['release']['linux32'] += [
|
||||
'export MOZILLA_OFFICIAL=1',
|
||||
'export MOZ_TELEMETRY_REPORTING=1',
|
||||
'mk_add_options MOZ_PGO=1',
|
||||
"mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
|
||||
]
|
||||
whitelist['release']['linux64'] += [
|
||||
'export MOZILLA_OFFICIAL=1',
|
||||
'export MOZ_TELEMETRY_REPORTING=1',
|
||||
'mk_add_options MOZ_PGO=1',
|
||||
"mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
|
||||
]
|
||||
|
||||
if __name__ == '__main__':
|
||||
import pprint
|
||||
pprint.pprint(whitelist)
|
@ -161,7 +161,7 @@ var ContextCommands = {
|
||||
|
||||
openLinkInNewTab: function cc_openLinkInNewTab() {
|
||||
Browser.addTab(ContextMenuUI.popupState.linkURL, false, Browser.selectedTab);
|
||||
ContextUI.peekTabs();
|
||||
ContextUI.peekTabs(kOpenInNewTabAnimationDelayMsec);
|
||||
},
|
||||
|
||||
copyLink: function cc_copyLink() {
|
||||
|
@ -10,12 +10,6 @@ const kContextUIDismissEvent = "MozContextUIDismiss";
|
||||
const kContextUITabsShowEvent = "MozContextUITabsShow";
|
||||
// add more as needed...
|
||||
|
||||
// delay for ContextUI's dismissTabsWithDelay
|
||||
const kHideContextAndTrayDelayMsec = 3000;
|
||||
|
||||
// delay when showing the tab bar briefly as a new tab opens
|
||||
const kNewTabAnimationDelayMsec = 1000;
|
||||
|
||||
/*
|
||||
* Manages context UI (navbar, tabbar, appbar) and track visibility. Also
|
||||
* tracks events that summon and hide the context UI.
|
||||
@ -160,35 +154,24 @@ var ContextUI = {
|
||||
/*
|
||||
* Briefly show the tab bar and then hide it. Fires context ui events.
|
||||
*/
|
||||
peekTabs: function peekTabs() {
|
||||
if (this.tabbarVisible) {
|
||||
setTimeout(function () {
|
||||
ContextUI.dismissTabsWithDelay(kNewTabAnimationDelayMsec);
|
||||
}, 0);
|
||||
} else {
|
||||
BrowserUI.setOnTabAnimationEnd(function () {
|
||||
ContextUI.dismissTabsWithDelay(kNewTabAnimationDelayMsec);
|
||||
});
|
||||
peekTabs: function peekTabs(aDelay) {
|
||||
if (!this.tabbarVisible)
|
||||
this.displayTabs();
|
||||
}
|
||||
|
||||
ContextUI.dismissTabsWithDelay(aDelay);
|
||||
},
|
||||
|
||||
/*
|
||||
* Dismiss tab bar after a delay. Fires context ui events.
|
||||
*/
|
||||
dismissTabsWithDelay: function (aDelay) {
|
||||
aDelay = aDelay || kHideContextAndTrayDelayMsec;
|
||||
aDelay = aDelay || kNewTabAnimationDelayMsec;
|
||||
this._clearDelayedTimeout();
|
||||
this._hidingId = setTimeout(function () {
|
||||
ContextUI.dismissTabs();
|
||||
}, aDelay);
|
||||
},
|
||||
|
||||
// Cancel any pending delayed dismiss
|
||||
cancelDismiss: function cancelDismiss() {
|
||||
this._clearDelayedTimeout();
|
||||
},
|
||||
|
||||
// Display the nav bar
|
||||
displayNavbar: function () {
|
||||
this._clearDelayedTimeout();
|
||||
@ -201,12 +184,6 @@ var ContextUI = {
|
||||
this._setIsExpanded(true);
|
||||
},
|
||||
|
||||
// Display the app bar
|
||||
displayContextAppbar: function () {
|
||||
this._clearDelayedTimeout();
|
||||
Elements.contextappbar.show();
|
||||
},
|
||||
|
||||
// Dismiss the navbar if visible.
|
||||
dismissNavbar: function dismissNavbar() {
|
||||
Elements.navbar.dismiss();
|
||||
@ -312,15 +289,9 @@ var ContextUI = {
|
||||
if (aEvent.button == 0 && this.isVisible)
|
||||
this.dismiss();
|
||||
break;
|
||||
case 'URLChanged':
|
||||
this.dismissTabs();
|
||||
break;
|
||||
case 'TabSelect':
|
||||
this.dismissTabs();
|
||||
break;
|
||||
|
||||
case 'ToolPanelShown':
|
||||
case 'ToolPanelHidden':
|
||||
case "ToolPanelShown":
|
||||
case "ToolPanelHidden":
|
||||
case "touchstart":
|
||||
case "AlertActive":
|
||||
this.dismiss();
|
||||
|
@ -84,8 +84,10 @@ const WebProgress = {
|
||||
let spec = aJson.location;
|
||||
let location = spec.split("#")[0]; // Ignore fragment identifier changes.
|
||||
|
||||
if (aTab == Browser.selectedTab)
|
||||
if (aTab == Browser.selectedTab) {
|
||||
BrowserUI.updateURI();
|
||||
BrowserUI.update();
|
||||
}
|
||||
|
||||
let locationHasChanged = (location != aTab.browser.lastLocation);
|
||||
if (locationHasChanged) {
|
||||
@ -123,22 +125,15 @@ const WebProgress = {
|
||||
_networkStart: function _networkStart(aJson, aTab) {
|
||||
aTab.startLoading();
|
||||
|
||||
if (aTab == Browser.selectedTab) {
|
||||
if (aTab == Browser.selectedTab)
|
||||
BrowserUI.update(TOOLBARSTATE_LOADING);
|
||||
|
||||
// We should at least show something in the URLBar until
|
||||
// the load has progressed further along
|
||||
if (aTab.browser.currentURI.spec == "about:blank")
|
||||
BrowserUI.updateURI({ captionOnly: true });
|
||||
}
|
||||
},
|
||||
|
||||
_networkStop: function _networkStop(aJson, aTab) {
|
||||
aTab.endLoading();
|
||||
|
||||
if (aTab == Browser.selectedTab) {
|
||||
if (aTab == Browser.selectedTab)
|
||||
BrowserUI.update(TOOLBARSTATE_LOADED);
|
||||
}
|
||||
},
|
||||
|
||||
_windowStart: function _windowStart(aJson, aTab) {
|
||||
@ -162,7 +157,7 @@ const WebProgress = {
|
||||
|
||||
// 'Whoosh' in
|
||||
this._progressCount = kProgressMarginStart;
|
||||
Elements.progress.style.width = this._progressCount + "%";
|
||||
Elements.progress.style.width = this._progressCount + "%";
|
||||
Elements.progress.removeAttribute("fade");
|
||||
|
||||
// Create a pulse timer to keep things moving even if we don't
|
||||
@ -204,7 +199,7 @@ const WebProgress = {
|
||||
_progressStop: function _progressStop(aJson, aTab) {
|
||||
this._progressActive = false;
|
||||
// 'Whoosh out' and fade
|
||||
Elements.progress.style.width = "100%";
|
||||
Elements.progress.style.width = "100%";
|
||||
Elements.progress.setAttribute("fade", true);
|
||||
},
|
||||
|
||||
@ -213,7 +208,7 @@ const WebProgress = {
|
||||
return;
|
||||
// Close out fade finished, reset
|
||||
if (data.propertyName == "opacity") {
|
||||
Elements.progress.style.width = "0px";
|
||||
Elements.progress.style.width = "0px";
|
||||
Elements.progressContainer.setAttribute("collapsed", true);
|
||||
}
|
||||
},
|
||||
|
@ -115,7 +115,7 @@ var Appbar = {
|
||||
}
|
||||
|
||||
var x = this.menuButton.getBoundingClientRect().left;
|
||||
var y = Elements.navbar.getBoundingClientRect().top;
|
||||
var y = Elements.toolbar.getBoundingClientRect().top;
|
||||
ContextMenuUI.showContextMenu({
|
||||
json: {
|
||||
types: typesArray,
|
||||
|
@ -1,473 +0,0 @@
|
||||
<?xml version="1.0" encoding="Windows-1252" ?>
|
||||
<!-- 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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE bindings [
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
||||
%browserDTD;
|
||||
]>
|
||||
|
||||
<bindings
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="autocomplete" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
|
||||
<implementation>
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
this.minResultsForPopup = 0;
|
||||
this.popup._input = this;
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<method name="openPopup">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.popup.openAutocompletePopup(this, null);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="closePopup">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.popup.closePopup(this, null);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="formatValue">
|
||||
<body>
|
||||
<![CDATA[
|
||||
BrowserUI.formatURI();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="trimValue">
|
||||
<parameter name="aURL"/>
|
||||
<body><![CDATA[
|
||||
return BrowserUI.trimURL(aURL);
|
||||
]]></body>
|
||||
</method>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<handler event="click" phase="capturing">
|
||||
<![CDATA[
|
||||
// If the urlbar is not already focused, focus it and select the contents.
|
||||
if (Elements.urlbarState.getAttribute("mode") != "edit") {
|
||||
BrowserUI._editURI(true);
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="dblclick" phase="capturing">
|
||||
<![CDATA[
|
||||
let selectAll = Services.prefs.getBoolPref("browser.urlbar.doubleClickSelectsAll");
|
||||
if (selectAll)
|
||||
this.select();
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="contextmenu" phase="capturing">
|
||||
<![CDATA[
|
||||
let box = this.inputField.parentNode;
|
||||
box.showContextMenu(this, event, true);
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="keypress" phase="capturing" keycode="VK_RETURN">
|
||||
<![CDATA[
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.popup.handleCompletion();
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="autocomplete-popup">
|
||||
<content orient="horizontal">
|
||||
<xul:vbox id="results-vbox" class="meta-section viewable-height" flex="1">
|
||||
<xul:label class="meta-section-title" value="&autocompleteResultsHeader.label;"/>
|
||||
<richgrid id="results-richgrid" deferlayout="true" anonid="results" seltype="single" flex="1"/>
|
||||
</xul:vbox>
|
||||
|
||||
<xul:vbox id="searches-vbox" class="meta-section viewable-height" flex="1">
|
||||
<xul:label class="meta-section-title" value="&autocompleteSearchesHeader.label;"/>
|
||||
<richgrid id="searches-richgrid" deferlayout="true" anonid="searches" seltype="single" flex="1"/>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
|
||||
<implementation implements="nsIAutoCompletePopup, nsIObserver">
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
Services.obs.addObserver(this, "browser-search-engine-modified", false);
|
||||
this.updateSearchEngines();
|
||||
this._results.controller = this;
|
||||
this._searches.controller = this;
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<destructor>
|
||||
<![CDATA[
|
||||
Services.obs.removeObserver(this, "browser-search-engine-modified");
|
||||
]]>
|
||||
</destructor>
|
||||
|
||||
<method name="handleItemClick">
|
||||
<parameter name="aItem"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (aItem.control == this._searches) {
|
||||
let engineName = aItem.getAttribute("value");
|
||||
BrowserUI.doOpenSearch(engineName);
|
||||
} else {
|
||||
let url = aItem.getAttribute("value");
|
||||
Browser.loadURI(url);
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- nsIAutocompleteInput -->
|
||||
|
||||
<field name="_input">null</field>
|
||||
<field name="_popupOpen">false</field>
|
||||
|
||||
<property name="overrideValue" readonly="true" onget="return null;"/>
|
||||
|
||||
<property name="selectedItem">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this._isGridBound(this._results) ? this._results.selectedItem : null;
|
||||
]]>
|
||||
</getter>
|
||||
<setter>
|
||||
<![CDATA[
|
||||
return this._isGridBound(this._results) ? this._results.selectedItem : null;
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
<property name="selectedIndex">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
|
||||
]]>
|
||||
</getter>
|
||||
<setter>
|
||||
<![CDATA[
|
||||
return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<property name="input" readonly="true" onget="return this._input;"/>
|
||||
<property name="popupOpen" readonly="true" onget="return this._popupOpen;"/>
|
||||
<property name="_matchCount" readonly="true" onget="return this.input.controller.matchCount;"/>
|
||||
|
||||
<method name="openAutocompletePopup">
|
||||
<parameter name="aInput"/>
|
||||
<parameter name="aElement"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this._popupOpen)
|
||||
return;
|
||||
|
||||
ContextUI.dismissContextAppbar();
|
||||
|
||||
this._input = aInput;
|
||||
this._popupOpen = true;
|
||||
this._grid = this._results;
|
||||
|
||||
this.clearSelection();
|
||||
this.invalidate();
|
||||
|
||||
this._results.arrangeItemsNow();
|
||||
this._searches.arrangeItemsNow();
|
||||
|
||||
this._fire("autocompletestart");
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="gridBoundCallback">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.updateResults();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="closePopup">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._popupOpen)
|
||||
return;
|
||||
|
||||
this.input.controller.stopSearch();
|
||||
this._popupOpen = false;
|
||||
this._fire("autocompleteend");
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="invalidate">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._popupOpen)
|
||||
return;
|
||||
|
||||
this.updateResults();
|
||||
this.updateSearchEngineSubtitles();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="selectBy">
|
||||
<parameter name="aReverse"/>
|
||||
<parameter name="aPage"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let handleOnSelect = this._handleOnSelect;
|
||||
this._handleOnSelect = false;
|
||||
|
||||
// TODO <jwilde>: Pressing page up/down should jump between rows,
|
||||
// not just items in the grid
|
||||
|
||||
// Move between grids if we're at the edge of one
|
||||
if ((this._grid.isSelectionAtEnd && !aReverse) ||
|
||||
(this._grid.isSelectionAtStart && aReverse)) {
|
||||
let index = aReverse ? this._otherGrid.itemCount - 1 : 0;
|
||||
this._otherGrid.selectedIndex = index;
|
||||
} else {
|
||||
this._grid.offsetSelection(aReverse ? -1 : 1);
|
||||
}
|
||||
|
||||
this._handleOnSelect = handleOnSelect;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- nsIObserver -->
|
||||
|
||||
<method name="observe">
|
||||
<parameter name="aSubject"/>
|
||||
<parameter name="aTopic"/>
|
||||
<parameter name="aData"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (aTopic != "browser-search-engine-modified")
|
||||
return;
|
||||
|
||||
switch (aData) {
|
||||
case "engine-added":
|
||||
case "engine-removed":
|
||||
case "engine-changed":
|
||||
this.updateSearchEngines();
|
||||
break;
|
||||
case "engine-current":
|
||||
// Not relevant
|
||||
break;
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Interface for updating various components of the popup. -->
|
||||
|
||||
<method name="updateResults">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._isGridBound(this._results))
|
||||
return;
|
||||
if (!this.input)
|
||||
return;
|
||||
|
||||
let controller = this.input.controller;
|
||||
let lastMatch = this._matchCount - 1;
|
||||
let iterCount = Math.max(this._results.itemCount, this._matchCount);
|
||||
|
||||
// Swap out existing items for new search hit results
|
||||
for (let i = 0; i < iterCount; i++) {
|
||||
if (i > lastMatch) {
|
||||
let lastItem = this._results.itemCount - 1;
|
||||
this._results.removeItemAt(lastItem, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = controller.getValueAt(i);
|
||||
let label = controller.getCommentAt(i) || value;
|
||||
let iconURI = controller.getImageAt(i);
|
||||
|
||||
let item = this._results.getItemAtIndex(i);
|
||||
if (item == null) {
|
||||
item = this._results.appendItem(label, value, true);
|
||||
item.setAttribute("autocomplete", "true");
|
||||
} else {
|
||||
item.setAttribute("label", label);
|
||||
item.setAttribute("value", value);
|
||||
}
|
||||
|
||||
item.setAttribute("iconURI", iconURI);
|
||||
}
|
||||
|
||||
this._results.arrangeItems();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="updateSearchEngines">
|
||||
<body><![CDATA[
|
||||
Services.search.init(this._onSearchServiceInit.bind(this));
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_onSearchServiceInit">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._isGridBound(this._searches))
|
||||
return;
|
||||
|
||||
this._engines = Services.search.getVisibleEngines();
|
||||
|
||||
while (this._searches.itemCount > 0)
|
||||
this._searches.removeItemAt(0, true);
|
||||
|
||||
this._engines.forEach(function (anEngine) {
|
||||
let item = this._searches.appendItem(anEngine.name, anEngine.name, true);
|
||||
item.setAttribute("autocomplete", "true");
|
||||
item.setAttribute("search", "true");
|
||||
|
||||
let iconURI = anEngine.iconURI ? anEngine.iconURI.spec : "";
|
||||
item.setAttribute("iconURI", iconURI);
|
||||
}.bind(this));
|
||||
|
||||
this._searches.arrangeItems();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="updateSearchEngineSubtitles">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._isGridBound(this._searches))
|
||||
return;
|
||||
|
||||
let searchString = this.input.controller.searchString;
|
||||
let label = Strings.browser.formatStringFromName("opensearch.search", [searchString], 1);
|
||||
|
||||
for (let i = 0, len = this._searches.itemCount; i < len; i++) {
|
||||
let item = this._searches.getItemAtIndex(i);
|
||||
item.setAttribute("label", label);
|
||||
item.refresh && item.refresh();
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Interface for handling actions across grids -->
|
||||
|
||||
<method name="handleCompletion">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this._grid == this._results) {
|
||||
this.input.controller.handleEnter(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._grid == this._searches) {
|
||||
let engine = this._engines[this._grid.selectedIndex];
|
||||
BrowserUI.doOpenSearch(engine.name);
|
||||
this.closePopup();
|
||||
return;
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="clearSelection">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this._isGridBound(this._results))
|
||||
this._results.clearSelection();
|
||||
|
||||
if (this._isGridBound(this._searches))
|
||||
this._searches.clearSelection();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Helpers -->
|
||||
|
||||
<field name="_engines">[]</field>
|
||||
<field name="_handleOnSelect">true</field>
|
||||
<field name="_grid">null</field>
|
||||
|
||||
<property name="_results" readonly="true" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'results');"/>
|
||||
<property name="_searches" readonly="true" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'searches');"/>
|
||||
|
||||
<property name="_otherGrid" readonly="true">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return (this._grid == this._results) ? this._searches : this._results;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<method name="_isGridBound">
|
||||
<parameter name="aGrid"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
return aGrid && aGrid.itemCount != undefined;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_fire">
|
||||
<parameter name="aName"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent(aName, true, false);
|
||||
this.dispatchEvent(event);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<handler event="select">
|
||||
<![CDATA[
|
||||
let grid = event.originalTarget;
|
||||
if (grid != this._grid) {
|
||||
if (this._grid.clearSelection)
|
||||
this._grid.clearSelection();
|
||||
this._grid = grid;
|
||||
}
|
||||
|
||||
if (this._handleOnSelect && this._results.selectedItem) {
|
||||
BrowserUI.goToURI(
|
||||
this._results.selectedItem.getAttribute("value"));
|
||||
this.closePopup();
|
||||
} else if (this._handleOnSelect) {
|
||||
this.handleCompletion();
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="contentgenerated">
|
||||
<![CDATA[
|
||||
let grid = event.originalTarget;
|
||||
if (grid == this._searches)
|
||||
this.updateSearchEngines();
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
</bindings>
|
@ -86,12 +86,20 @@
|
||||
|
||||
<method name="handleItemClick">
|
||||
<parameter name="aItem"/>
|
||||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if(!this.isBound)
|
||||
return;
|
||||
if (this.controller)
|
||||
this.controller.handleItemClick(aItem);
|
||||
|
||||
if ("single" == this.getAttribute("seltype")) {
|
||||
// we'll republish this as a selectionchange event on the grid
|
||||
aEvent.stopPropagation();
|
||||
this.selectItem(aItem);
|
||||
}
|
||||
|
||||
if (this.controller && this.controller.handleItemClick)
|
||||
this.controller.handleItemClick(aItem, aEvent);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
838
browser/metro/base/content/bindings/urlbar.xml
Normal file
@ -0,0 +1,838 @@
|
||||
<?xml version="1.0" encoding="Windows-1252" ?>
|
||||
<!-- 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/. -->
|
||||
|
||||
|
||||
<!DOCTYPE bindings [
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
||||
%browserDTD;
|
||||
]>
|
||||
|
||||
<bindings
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<binding id="urlbar" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
|
||||
<implementation implements="nsIObserver">
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
this._mayFormat = Services.prefs.getBoolPref("browser.urlbar.formatting.enabled");
|
||||
this._mayTrimURLs = Services.prefs.getBoolPref("browser.urlbar.trimURLs");
|
||||
this._maySelectAll = Services.prefs.getBoolPref("browser.urlbar.doubleClickSelectsAll");
|
||||
|
||||
Services.prefs.addObserver("browser.urlbar.formatting.enabled", this, false);
|
||||
Services.prefs.addObserver("browser.urlbar.trimURLs", this, false);
|
||||
Services.prefs.addObserver("browser.urlbar.doubleClickSelectsAll", this, false);
|
||||
|
||||
this.inputField.controllers.insertControllerAt(0, this._copyCutValueController);
|
||||
|
||||
this.minResultsForPopup = 0;
|
||||
this.popup._input = this;
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<destructor>
|
||||
<![CDATA[
|
||||
Services.prefs.removeObserver("browser.urlbar.formatting.enabled", this);
|
||||
Services.prefs.removeObserver("browser.urlbar.trimURLs", this);
|
||||
Services.prefs.removeObserver("browser.urlbar.doubleClickSelectsAll", this);
|
||||
]]>
|
||||
</destructor>
|
||||
|
||||
<field name="_mayFormat"/>
|
||||
<field name="_mayTrimURLs"/>
|
||||
<field name="_maySelectAll"/>
|
||||
<field name="_lastKnownGoodURL"/>
|
||||
|
||||
<method name="openPopup">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.popup.openAutocompletePopup(this, null);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="closePopup">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.popup.closePopup(this, null);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- URI Display: Domain Highlighting -->
|
||||
|
||||
<method name="_clearFormatting">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._mayFormat)
|
||||
return;
|
||||
|
||||
let controller = this.editor.selectionController;
|
||||
let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
|
||||
selection.removeAllRanges();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="formatValue">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._mayFormat || this.isEditing)
|
||||
return;
|
||||
|
||||
let controller = this.editor.selectionController;
|
||||
let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
|
||||
selection.removeAllRanges();
|
||||
|
||||
let textNode = this.editor.rootElement.firstChild;
|
||||
let value = textNode.textContent;
|
||||
|
||||
let protocol = value.match(/^[a-z\d.+\-]+:(?=[^\d])/);
|
||||
if (protocol &&
|
||||
["http:", "https:", "ftp:"].indexOf(protocol[0]) == -1)
|
||||
return;
|
||||
let matchedURL = value.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
|
||||
if (!matchedURL)
|
||||
return;
|
||||
|
||||
let [, preDomain, domain] = matchedURL;
|
||||
let baseDomain = domain;
|
||||
let subDomain = "";
|
||||
// getBaseDomainFromHost doesn't recognize IPv6 literals in brackets as IPs (bug 667159)
|
||||
if (domain[0] != "[") {
|
||||
try {
|
||||
baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
|
||||
if (!domain.endsWith(baseDomain)) {
|
||||
// getBaseDomainFromHost converts its resultant to ACE.
|
||||
let IDNService = Cc["@mozilla.org/network/idn-service;1"]
|
||||
.getService(Ci.nsIIDNService);
|
||||
baseDomain = IDNService.convertACEtoUTF8(baseDomain);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (baseDomain != domain) {
|
||||
subDomain = domain.slice(0, -baseDomain.length);
|
||||
}
|
||||
|
||||
let rangeLength = preDomain.length + subDomain.length;
|
||||
if (rangeLength) {
|
||||
let range = document.createRange();
|
||||
range.setStart(textNode, 0);
|
||||
range.setEnd(textNode, rangeLength);
|
||||
selection.addRange(range);
|
||||
}
|
||||
|
||||
let startRest = preDomain.length + domain.length;
|
||||
if (startRest < value.length) {
|
||||
let range = document.createRange();
|
||||
range.setStart(textNode, startRest);
|
||||
range.setEnd(textNode, value.length);
|
||||
selection.addRange(range);
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- URI Display: Scheme and Trailing Slash Triming -->
|
||||
|
||||
<method name="_trimURL">
|
||||
<parameter name="aURL"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
// This function must not modify the given URL such that calling
|
||||
// nsIURIFixup::createFixupURI with the rfdesult will produce a different URI.
|
||||
return aURL /* remove single trailing slash for http/https/ftp URLs */
|
||||
.replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1")
|
||||
/* remove http:// unless the host starts with "ftp\d*\." or contains "@" */
|
||||
.replace(/^http:\/\/((?!ftp\d*\.)[^\/@]+(?:\/|$))/, "$1");
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_getSelectedValueForClipboard">
|
||||
<body>
|
||||
<![CDATA[
|
||||
// Grab the actual input field's value, not our value, which could include moz-action:
|
||||
let inputVal = this.inputField.value;
|
||||
let selectedVal = inputVal.substring(this.selectionStart, this.electionEnd);
|
||||
|
||||
// If the selection doesn't start at the beginning or doesn't span the full domain or
|
||||
// the URL bar is modified, nothing else to do here.
|
||||
if (this.selectionStart > 0 || this.valueIsTyped)
|
||||
return selectedVal;
|
||||
|
||||
// The selection doesn't span the full domain if it doesn't contain a slash and is
|
||||
// followed by some character other than a slash.
|
||||
if (!selectedVal.contains("/")) {
|
||||
let remainder = inputVal.replace(selectedVal, "");
|
||||
if (remainder != "" && remainder[0] != "/")
|
||||
return selectedVal;
|
||||
}
|
||||
|
||||
let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
|
||||
|
||||
let uri;
|
||||
try {
|
||||
uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_USE_UTF8);
|
||||
} catch (e) {}
|
||||
if (!uri)
|
||||
return selectedVal;
|
||||
|
||||
// Only copy exposable URIs
|
||||
try {
|
||||
uri = uriFixup.createExposableURI(uri);
|
||||
} catch (ex) {}
|
||||
|
||||
// If the entire URL is selected, just use the actual loaded URI.
|
||||
if (inputVal == selectedVal) {
|
||||
// ... but only if isn't a javascript: or data: URI, since those
|
||||
// are hard to read when encoded
|
||||
if (!uri.schemeIs("javascript") && !uri.schemeIs("data")) {
|
||||
// Parentheses are known to confuse third-party applications (bug 458565).
|
||||
selectedVal = uri.spec.replace(/[()]/g, function (c) escape(c));
|
||||
}
|
||||
|
||||
return selectedVal;
|
||||
}
|
||||
|
||||
// Just the beginning of the URL is selected, check for a trimmed value
|
||||
let spec = uri.spec;
|
||||
let trimmedSpec = this._trimURL(spec);
|
||||
if (spec != trimmedSpec) {
|
||||
// Prepend the portion that trimURL removed from the beginning.
|
||||
// This assumes trimURL will only truncate the URL at
|
||||
// the beginning or end (or both).
|
||||
let trimmedSegments = spec.split(trimmedSpec);
|
||||
selectedVal = trimmedSegments[0] + selectedVal;
|
||||
}
|
||||
|
||||
return selectedVal;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<field name="_copyCutValueController">
|
||||
<![CDATA[
|
||||
({
|
||||
urlbar: this,
|
||||
doCommand: function(aCommand) {
|
||||
let urlbar = this.urlbar;
|
||||
let val = urlbar._getSelectedValueForClipboard();
|
||||
if (!val)
|
||||
return;
|
||||
|
||||
if (aCommand == "cmd_cut" && this.isCommandEnabled(aCommand)) {
|
||||
let start = urlbar.selectionStart;
|
||||
let end = urlbar.selectionEnd;
|
||||
urlbar.inputField.value = urlbar.inputField.value.substring(0, start) +
|
||||
urlbar.inputField.value.substring(end);
|
||||
urlbar.selectionStart = urlbar.selectionEnd = start;
|
||||
}
|
||||
|
||||
Cc["@mozilla.org/widget/clipboardhelper;1"]
|
||||
.getService(Ci.nsIClipboardHelper)
|
||||
.copyString(val, document);
|
||||
},
|
||||
|
||||
supportsCommand: function(aCommand) {
|
||||
switch (aCommand) {
|
||||
case "cmd_copy":
|
||||
case "cmd_cut":
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
isCommandEnabled: function(aCommand) {
|
||||
let urlbar = this.urlbar;
|
||||
return this.supportsCommand(aCommand) &&
|
||||
(aCommand != "cmd_cut" || !urlbar.readOnly) &&
|
||||
urlbar.selectionStart < urlbar.selectionEnd;
|
||||
},
|
||||
|
||||
onEvent: function(aEventName) {}
|
||||
})
|
||||
]]>
|
||||
</field>
|
||||
|
||||
<method name="trimValue">
|
||||
<parameter name="aURL"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
return (this._mayTrimURLs) ? this._trimURL(aURL) : aURL;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- URI Editing -->
|
||||
|
||||
<property name="isEditing" readonly="true">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return Elements.urlbarState.getAttribute("mode") == "edit";
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<method name="beginEditing">
|
||||
<parameter name="aShouldDismiss"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.isEditing)
|
||||
return;
|
||||
|
||||
Elements.urlbarState.setAttribute("mode", "edit");
|
||||
this._lastKnownGoodURL = this.value;
|
||||
|
||||
if (!this.focused)
|
||||
this.focus();
|
||||
|
||||
this._clearFormatting();
|
||||
this.select();
|
||||
|
||||
if (aShouldDismiss)
|
||||
ContextUI.dismissTabs();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="endEditing">
|
||||
<parameter name="aShouldRevert"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this.isEditing)
|
||||
return;
|
||||
|
||||
Elements.urlbarState.setAttribute("mode", "view");
|
||||
this.closePopup();
|
||||
this.formatValue();
|
||||
|
||||
if (this.focused)
|
||||
this.blur();
|
||||
|
||||
if (aShouldRevert)
|
||||
this.value = this._lastKnownGoodURL;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- URI Submission -->
|
||||
|
||||
<method name="_canonizeURL">
|
||||
<parameter name="aURL"/>
|
||||
<parameter name="aTriggeringEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!aURL)
|
||||
return "";
|
||||
|
||||
// Only add the suffix when the URL bar value isn't already "URL-like",
|
||||
// and only if we get a keyboard event, to match user expectations.
|
||||
if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(aURL)) {
|
||||
let accel = aTriggeringEvent.ctrlKey;
|
||||
let shift = aTriggeringEvent.shiftKey;
|
||||
let suffix = "";
|
||||
|
||||
switch (true) {
|
||||
case (accel && shift):
|
||||
suffix = ".org/";
|
||||
break;
|
||||
case (shift):
|
||||
suffix = ".net/";
|
||||
break;
|
||||
case (accel):
|
||||
try {
|
||||
suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
|
||||
if (suffix.charAt(suffix.length - 1) != "/")
|
||||
suffix += "/";
|
||||
} catch(e) {
|
||||
suffix = ".com/";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
// trim leading/trailing spaces (bug 233205)
|
||||
aURL = aURL.trim();
|
||||
|
||||
// Tack www. and suffix on. If user has appended directories, insert
|
||||
// suffix before them (bug 279035). Be careful not to get two slashes.
|
||||
let firstSlash = aURL.indexOf("/");
|
||||
if (firstSlash >= 0) {
|
||||
aURL = aURL.substring(0, firstSlash) + suffix + aURL.substring(firstSlash + 1);
|
||||
} else {
|
||||
aURL = aURL + suffix;
|
||||
}
|
||||
aURL = "http://www." + aURL;
|
||||
}
|
||||
}
|
||||
|
||||
return aURL;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="submitURL">
|
||||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
// If the address was typed in by a user, tidy it up
|
||||
if (aEvent instanceof KeyEvent)
|
||||
this.value = this._canonizeURL(this.value, aEvent);
|
||||
|
||||
this.endEditing();
|
||||
BrowserUI.goToURI(this.value);
|
||||
|
||||
return true;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="submitSearch">
|
||||
<parameter name="anEngineName"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.endEditing();
|
||||
BrowserUI.doOpenSearch(anEngineName);
|
||||
|
||||
return true;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- nsIObserver -->
|
||||
|
||||
<method name="observe">
|
||||
<parameter name="aSubject"/>
|
||||
<parameter name="aTopic"/>
|
||||
<parameter name="aData"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (aTopic != "nsPref:changed")
|
||||
return;
|
||||
|
||||
switch (aData) {
|
||||
case "browser.urlbar.formatting.enabled":
|
||||
this._mayFormat = Services.prefs.getBoolPref(aData);
|
||||
break;
|
||||
case "browser.urlbar.trimURLs":
|
||||
this._mayTrimURLs = Services.prefs.getBoolPref(aData);
|
||||
break;
|
||||
case "browser.urlbar.doubleClickSelectsAll":
|
||||
this._maySelectAll = Services.prefs.getBoolPref(aData);
|
||||
break;
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<!-- Entering editing mode -->
|
||||
|
||||
<handler event="input" phase="capturing">
|
||||
<![CDATA[
|
||||
// Ensures that paste-and-go actually brings the URL bar into editing mode
|
||||
// and displays the half-height autocomplete popup.
|
||||
this.beginEditing();
|
||||
this.openPopup();
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="click" phase="capturing">
|
||||
<![CDATA[
|
||||
this.beginEditing(true);
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<!-- Editing mode behaviors -->
|
||||
|
||||
<handler event="dblclick" phase="capturing">
|
||||
<![CDATA[
|
||||
if (this._maySelectAll) this.select();
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="contextmenu" phase="capturing">
|
||||
<![CDATA[
|
||||
let box = this.inputField.parentNode;
|
||||
box.showContextMenu(this, event, true);
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<!-- Leaving editing mode -->
|
||||
|
||||
<handler event="blur" phase="capturing">
|
||||
<![CDATA[
|
||||
this.endEditing();
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="keypress" phase="capturing" keycode="VK_RETURN">
|
||||
<![CDATA[
|
||||
if (this.popup.submitSelected())
|
||||
return;
|
||||
|
||||
if (this.submitURL(event))
|
||||
return;
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="urlbar-autocomplete">
|
||||
<content orient="horizontal">
|
||||
<xul:vbox id="results-vbox" flex="1">
|
||||
<xul:label class="meta-section-title" value="&autocompleteResultsHeader.label;"/>
|
||||
<richgrid id="results-richgrid" rows="3" deferlayout="true" anonid="results" seltype="single" flex="1"/>
|
||||
</xul:vbox>
|
||||
|
||||
<xul:vbox id="searches-vbox" flex="1">
|
||||
<xul:label class="meta-section-title" value="&autocompleteSearchesHeader.label;"/>
|
||||
<richgrid id="searches-richgrid" rows="3" deferlayout="true" anonid="searches" seltype="single" flex="1"/>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
|
||||
<implementation implements="nsIAutoCompletePopup, nsIObserver">
|
||||
<constructor>
|
||||
<![CDATA[
|
||||
this.hidden = true;
|
||||
|
||||
Services.obs.addObserver(this, "browser-search-engine-modified", false);
|
||||
|
||||
this._results.controller = this;
|
||||
this._searches.controller = this;
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<destructor>
|
||||
<![CDATA[
|
||||
Services.obs.removeObserver(this, "browser-search-engine-modified");
|
||||
]]>
|
||||
</destructor>
|
||||
|
||||
<!-- nsIAutocompleteInput -->
|
||||
|
||||
<field name="_input">null</field>
|
||||
|
||||
<property name="input" readonly="true" onget="return this._input;"/>
|
||||
<property name="matchCount" readonly="true" onget="return this.input.controller.matchCount;"/>
|
||||
<property name="popupOpen" readonly="true" onget="return !this.hidden"/>
|
||||
<property name="overrideValue" readonly="true" onget="return null;"/>
|
||||
|
||||
<property name="selectedItem">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this._isGridBound(this._results) ? this._results.selectedItem : null;
|
||||
]]>
|
||||
</getter>
|
||||
<setter>
|
||||
<![CDATA[
|
||||
return this._isGridBound(this._results) ? this._results.selectedItem : null;
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<property name="selectedIndex">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
|
||||
]]>
|
||||
</getter>
|
||||
<setter>
|
||||
<![CDATA[
|
||||
return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<method name="openAutocompletePopup">
|
||||
<parameter name="aInput"/>
|
||||
<parameter name="aElement"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.popupOpen)
|
||||
return;
|
||||
|
||||
ContextUI.dismissContextAppbar();
|
||||
|
||||
this._input = aInput;
|
||||
this._grid = this._results;
|
||||
|
||||
this.clearSelection();
|
||||
this.invalidate();
|
||||
|
||||
this._results.arrangeItemsNow();
|
||||
this._searches.arrangeItemsNow();
|
||||
|
||||
this.hidden = false;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="closePopup">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this.popupOpen)
|
||||
return;
|
||||
|
||||
this.input.controller.stopSearch();
|
||||
this.hidden = true;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Updating grid content -->
|
||||
|
||||
<field name="_grid">null</field>
|
||||
<field name="_results" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'results');</field>
|
||||
<field name="_searches" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'searches');</field>
|
||||
|
||||
<property name="_otherGrid" readonly="true">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
if (this._grid == null)
|
||||
return null;
|
||||
|
||||
return (this._grid == this._results) ? this._searches : this._results;
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
||||
<method name="_isGridBound">
|
||||
<parameter name="aGrid"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
return aGrid && aGrid.isBound;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="invalidate">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this.popupOpen)
|
||||
return;
|
||||
|
||||
this.updateResults();
|
||||
this.updateSearchEngineSubtitles();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Updating grid content: results -->
|
||||
|
||||
<method name="updateResults">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._isGridBound(this._results))
|
||||
return;
|
||||
|
||||
if (!this.input)
|
||||
return;
|
||||
|
||||
let controller = this.input.controller;
|
||||
let lastMatch = this.matchCount - 1;
|
||||
let iterCount = Math.max(this._results.itemCount, this.matchCount);
|
||||
|
||||
// Swap out existing items for new search hit results
|
||||
for (let i = 0; i < iterCount; i++) {
|
||||
if (i > lastMatch) {
|
||||
let lastItem = this._results.itemCount - 1;
|
||||
this._results.removeItemAt(lastItem, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = controller.getValueAt(i);
|
||||
let label = controller.getCommentAt(i) || value;
|
||||
let iconURI = controller.getImageAt(i);
|
||||
|
||||
let item = this._results.getItemAtIndex(i);
|
||||
if (item == null) {
|
||||
item = this._results.appendItem(label, value, true);
|
||||
item.setAttribute("autocomplete", "true");
|
||||
} else {
|
||||
item.setAttribute("label", label);
|
||||
item.setAttribute("value", value);
|
||||
}
|
||||
|
||||
item.setAttribute("iconURI", iconURI);
|
||||
}
|
||||
|
||||
this._results.arrangeItems();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Updating grid content: search engines -->
|
||||
|
||||
<field name="_engines">[]</field>
|
||||
|
||||
<method name="_initSearchEngines">
|
||||
<body>
|
||||
<![CDATA[
|
||||
Services.search.init(this.updateSearchEngineGrid.bind(this));
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="updateSearchEngineGrid">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._isGridBound(this._searches))
|
||||
return;
|
||||
|
||||
this._engines = Services.search.getVisibleEngines();
|
||||
|
||||
while (this._searches.itemCount > 0)
|
||||
this._searches.removeItemAt(0, true);
|
||||
|
||||
this._engines.forEach(function (anEngine) {
|
||||
let item = this._searches.appendItem(anEngine.name, anEngine.name, true);
|
||||
item.setAttribute("autocomplete", "true");
|
||||
item.setAttribute("search", "true");
|
||||
|
||||
let iconURI = anEngine.iconURI ? anEngine.iconURI.spec : "";
|
||||
item.setAttribute("iconURI", iconURI);
|
||||
}.bind(this));
|
||||
|
||||
this._searches.arrangeItems();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="updateSearchEngineSubtitles">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._isGridBound(this._searches))
|
||||
return;
|
||||
|
||||
let searchString = this.input.controller.searchString;
|
||||
let label = Strings.browser.formatStringFromName("opensearch.search", [searchString], 1);
|
||||
|
||||
for (let i = 0, len = this._searches.itemCount; i < len; i++) {
|
||||
let item = this._searches.getItemAtIndex(i);
|
||||
item.setAttribute("label", label);
|
||||
item.refresh && item.refresh();
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Selecting results -->
|
||||
|
||||
<method name="selectBy">
|
||||
<parameter name="aReverse"/>
|
||||
<parameter name="aPage"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (!this._isGridBound(this._results) ||
|
||||
!this._isGridBound(this._searches))
|
||||
return;
|
||||
|
||||
// Move between grids if we're at the edge of one.
|
||||
if ((this._grid.isSelectionAtEnd && !aReverse) ||
|
||||
(this._grid.isSelectionAtStart && aReverse)) {
|
||||
let index = !aReverse ? 0 : this._otherGrid.itemCount - 1;
|
||||
this._otherGrid.selectedIndex = index;
|
||||
} else {
|
||||
this._grid.offsetSelection(aReverse ? -1 : 1);
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="clearSelection">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this._isGridBound(this._results))
|
||||
this._results.clearSelection();
|
||||
|
||||
if (this._isGridBound(this._searches))
|
||||
this._searches.clearSelection();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- Submitting selected results -->
|
||||
|
||||
<method name="submitSelected">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this._isGridBound(this._results) &&
|
||||
this._results.selectedIndex >= 0) {
|
||||
let url = this.input.controller.getValueAt(this._results.selectedIndex);
|
||||
this.input.value = url;
|
||||
return this.input.submitURL();
|
||||
}
|
||||
|
||||
if (this._isGridBound(this._searches) &&
|
||||
this._searches.selectedIndex >= 0) {
|
||||
let engine = this._engines[this._searches.selectedIndex];
|
||||
return this.input.submitSearch(engine.name);
|
||||
}
|
||||
|
||||
return false;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="handleItemClick">
|
||||
<parameter name="aItem"/>
|
||||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.submitSelected();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- nsIObserver -->
|
||||
|
||||
<method name="observe">
|
||||
<parameter name="aSubject"/>
|
||||
<parameter name="aTopic"/>
|
||||
<parameter name="aData"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
switch (aTopic) {
|
||||
case "browser-search-engine-modified":
|
||||
this.updateSearchEngineGrid();
|
||||
break;
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<handler event="contentgenerated">
|
||||
<![CDATA[
|
||||
let grid = event.originalTarget;
|
||||
|
||||
if (grid == this._searches)
|
||||
this._initSearchEngines();
|
||||
|
||||
if (grid == this._results)
|
||||
this.updateResults();
|
||||
]]>
|
||||
</handler>
|
||||
|
||||
<handler event="select">
|
||||
<![CDATA[
|
||||
let grid = event.originalTarget;
|
||||
|
||||
// If a selection was made on a different grid,
|
||||
// remove selection from the current grid.
|
||||
if (grid != this._grid) {
|
||||
this._grid.clearSelection();
|
||||
this._grid = this._otherGrid;
|
||||
}
|
||||
]]>
|
||||
</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
</bindings>
|
@ -2,6 +2,7 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/PageThumbs.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm")
|
||||
@ -22,6 +23,13 @@ const kStartOverlayURI = "about:start";
|
||||
const debugServerStateChanged = "devtools.debugger.remote-enabled";
|
||||
const debugServerPortChanged = "devtools.debugger.remote-port";
|
||||
|
||||
// delay when showing the tab bar briefly after a new (empty) tab opens
|
||||
const kNewTabAnimationDelayMsec = 1000;
|
||||
// delay when showing the tab bar after opening a link on a new tab
|
||||
const kOpenInNewTabAnimationDelayMsec = 3000;
|
||||
// delay before closing tab bar after selecting another tab
|
||||
const kSelectTabAnimationDelayMsec = 500;
|
||||
|
||||
/**
|
||||
* Cache of commonly used elements.
|
||||
*/
|
||||
@ -74,9 +82,10 @@ var BrowserUI = {
|
||||
get _back() { return document.getElementById("cmd_back"); },
|
||||
get _forward() { return document.getElementById("cmd_forward"); },
|
||||
|
||||
lastKnownGoodURL: "", //used when the user wants to escape unfinished url entry
|
||||
init: function() {
|
||||
lastKnownGoodURL: "", // used when the user wants to escape unfinished url entry
|
||||
ready: false, // used for tests to determine when delayed initialization is done
|
||||
|
||||
init: function() {
|
||||
// start the debugger now so we can use it on the startup code as well
|
||||
if (Services.prefs.getBoolPref(debugServerStateChanged)) {
|
||||
this.runDebugServer();
|
||||
@ -100,11 +109,7 @@ var BrowserUI = {
|
||||
window.addEventListener("MozImprecisePointer", this, true);
|
||||
|
||||
Services.prefs.addObserver("browser.cache.disk_cache_ssl", this, false);
|
||||
Services.prefs.addObserver("browser.urlbar.formatting.enabled", this, false);
|
||||
Services.prefs.addObserver("browser.urlbar.trimURLs", this, false);
|
||||
Services.obs.addObserver(this, "metro_viewstate_changed", false);
|
||||
|
||||
this._edit.inputField.controllers.insertControllerAt(0, this._copyCutURIController);
|
||||
|
||||
// Init core UI modules
|
||||
ContextUI.init();
|
||||
@ -120,16 +125,17 @@ var BrowserUI = {
|
||||
|
||||
// We can delay some initialization until after startup. We wait until
|
||||
// the first page is shown, then dispatch a UIReadyDelayed event.
|
||||
messageManager.addMessageListener("pageshow", function() {
|
||||
messageManager.addMessageListener("pageshow", function onPageShow() {
|
||||
if (getBrowser().currentURI.spec == "about:blank")
|
||||
return;
|
||||
|
||||
messageManager.removeMessageListener("pageshow", arguments.callee, true);
|
||||
messageManager.removeMessageListener("pageshow", onPageShow);
|
||||
|
||||
setTimeout(function() {
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("UIReadyDelayed", true, false);
|
||||
window.dispatchEvent(event);
|
||||
BrowserUI.ready = true;
|
||||
}, 0);
|
||||
});
|
||||
|
||||
@ -139,9 +145,9 @@ var BrowserUI = {
|
||||
});
|
||||
|
||||
// Delay the panel UI and Sync initialization
|
||||
window.addEventListener("UIReadyDelayed", function(aEvent) {
|
||||
window.addEventListener("UIReadyDelayed", function delayedInit(aEvent) {
|
||||
Util.dumpLn("* delay load started...");
|
||||
window.removeEventListener(aEvent.type, arguments.callee, false);
|
||||
window.removeEventListener("UIReadyDelayed", delayedInit, false);
|
||||
|
||||
// Login Manager and Form History initialization
|
||||
Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
|
||||
@ -291,10 +297,22 @@ var BrowserUI = {
|
||||
* Navigation
|
||||
*/
|
||||
|
||||
/* Updates the overall state of the toolbar, but not the URL bar. */
|
||||
update: function(aState) {
|
||||
let uri = this.getDisplayURI(Browser.selectedBrowser);
|
||||
StartUI.update(uri);
|
||||
|
||||
this._updateButtons();
|
||||
this._updateToolbar();
|
||||
},
|
||||
|
||||
/* Updates the URL bar. */
|
||||
updateURI: function(aOptions) {
|
||||
let uri = this.getDisplayURI(Browser.selectedBrowser);
|
||||
let cleanURI = Util.isURLEmpty(uri) ? "" : uri;
|
||||
this._edit.value = cleanURI;
|
||||
},
|
||||
|
||||
getDisplayURI: function(browser) {
|
||||
let uri = browser.currentURI;
|
||||
let spec = uri.spec;
|
||||
@ -313,64 +331,18 @@ var BrowserUI = {
|
||||
return spec;
|
||||
},
|
||||
|
||||
/**
|
||||
* Some prefs that have consequences in both Metro and Desktop such as
|
||||
* app-update prefs, are automatically pulled from Desktop here.
|
||||
*/
|
||||
_pullDesktopControlledPrefs: function() {
|
||||
function pullDesktopControlledPrefType(prefType, prefFunc) {
|
||||
try {
|
||||
registry.create(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
|
||||
"Software\\Mozilla\\Firefox\\Metro\\Prefs\\" + prefType,
|
||||
Ci.nsIWindowsRegKey.ACCESS_ALL);
|
||||
for (let i = 0; i < registry.valueCount; i++) {
|
||||
let prefName = registry.getValueName(i);
|
||||
let prefValue = registry.readStringValue(prefName);
|
||||
if (prefType == Ci.nsIPrefBranch.PREF_BOOL) {
|
||||
prefValue = prefValue == "true";
|
||||
}
|
||||
Services.prefs[prefFunc](prefName, prefValue);
|
||||
}
|
||||
} catch (ex) {
|
||||
Util.dumpLn("Could not pull for prefType " + prefType + ": " + ex);
|
||||
} finally {
|
||||
registry.close();
|
||||
}
|
||||
}
|
||||
let registry = Cc["@mozilla.org/windows-registry-key;1"].
|
||||
createInstance(Ci.nsIWindowsRegKey);
|
||||
pullDesktopControlledPrefType(Ci.nsIPrefBranch.PREF_INT, "setIntPref");
|
||||
pullDesktopControlledPrefType(Ci.nsIPrefBranch.PREF_BOOL, "setBoolPref");
|
||||
pullDesktopControlledPrefType(Ci.nsIPrefBranch.PREF_STRING, "setCharPref");
|
||||
},
|
||||
|
||||
/* Set the location to the current content */
|
||||
updateURI: function(aOptions) {
|
||||
aOptions = aOptions || {};
|
||||
|
||||
let uri = this.getDisplayURI(Browser.selectedBrowser);
|
||||
let cleanURI = Util.isURLEmpty(uri) ? "" : uri;
|
||||
this._setURI(cleanURI);
|
||||
|
||||
if ("captionOnly" in aOptions && aOptions.captionOnly)
|
||||
return;
|
||||
|
||||
StartUI.update(uri);
|
||||
this._updateButtons();
|
||||
this._updateToolbar();
|
||||
},
|
||||
|
||||
goToURI: function(aURI) {
|
||||
aURI = aURI || this._edit.value;
|
||||
if (!aURI)
|
||||
return;
|
||||
|
||||
this._edit.value = aURI;
|
||||
|
||||
// Make sure we're online before attempting to load
|
||||
Util.forceOnline();
|
||||
|
||||
BrowserUI.showContent(aURI);
|
||||
content.focus();
|
||||
this._setURI(aURI);
|
||||
Browser.selectedBrowser.focus();
|
||||
|
||||
Task.spawn(function() {
|
||||
let postData = {};
|
||||
@ -387,75 +359,27 @@ var BrowserUI = {
|
||||
});
|
||||
},
|
||||
|
||||
handleUrlbarEnter: function handleUrlbarEnter(aEvent) {
|
||||
let url = this._edit.value;
|
||||
if (aEvent instanceof KeyEvent)
|
||||
url = this._canonizeURL(url, aEvent);
|
||||
this.goToURI(url);
|
||||
},
|
||||
|
||||
_canonizeURL: function _canonizeURL(aUrl, aTriggeringEvent) {
|
||||
if (!aUrl)
|
||||
return "";
|
||||
|
||||
// Only add the suffix when the URL bar value isn't already "URL-like",
|
||||
// and only if we get a keyboard event, to match user expectations.
|
||||
if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(aUrl)) {
|
||||
let accel = aTriggeringEvent.ctrlKey;
|
||||
let shift = aTriggeringEvent.shiftKey;
|
||||
let suffix = "";
|
||||
|
||||
switch (true) {
|
||||
case (accel && shift):
|
||||
suffix = ".org/";
|
||||
break;
|
||||
case (shift):
|
||||
suffix = ".net/";
|
||||
break;
|
||||
case (accel):
|
||||
try {
|
||||
suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
|
||||
if (suffix.charAt(suffix.length - 1) != "/")
|
||||
suffix += "/";
|
||||
} catch(e) {
|
||||
suffix = ".com/";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
// trim leading/trailing spaces (bug 233205)
|
||||
aUrl = aUrl.trim();
|
||||
|
||||
// Tack www. and suffix on. If user has appended directories, insert
|
||||
// suffix before them (bug 279035). Be careful not to get two slashes.
|
||||
let firstSlash = aUrl.indexOf("/");
|
||||
if (firstSlash >= 0) {
|
||||
aUrl = aUrl.substring(0, firstSlash) + suffix + aUrl.substring(firstSlash + 1);
|
||||
} else {
|
||||
aUrl = aUrl + suffix;
|
||||
}
|
||||
aUrl = "http://www." + aUrl;
|
||||
}
|
||||
}
|
||||
return aUrl;
|
||||
},
|
||||
|
||||
doOpenSearch: function doOpenSearch(aName) {
|
||||
// save the current value of the urlbar
|
||||
let searchValue = this._edit.value;
|
||||
let engine = Services.search.getEngineByName(aName);
|
||||
let submission = engine.getSubmission(searchValue, null);
|
||||
|
||||
this._edit.value = submission.uri.spec;
|
||||
|
||||
// Make sure we're online before attempting to load
|
||||
Util.forceOnline();
|
||||
|
||||
BrowserUI.showContent();
|
||||
Browser.selectedBrowser.focus();
|
||||
|
||||
let engine = Services.search.getEngineByName(aName);
|
||||
let submission = engine.getSubmission(searchValue, null);
|
||||
Browser.loadURI(submission.uri.spec, { postData: submission.postData });
|
||||
Task.spawn(function () {
|
||||
Browser.loadURI(submission.uri.spec, { postData: submission.postData });
|
||||
|
||||
// loadURI may open a new tab, so get the selectedBrowser afterward.
|
||||
Browser.selectedBrowser.userTypedValue = submission.uri.spec;
|
||||
this._titleChanged(Browser.selectedBrowser);
|
||||
// loadURI may open a new tab, so get the selectedBrowser afterward.
|
||||
Browser.selectedBrowser.userTypedValue = submission.uri.spec;
|
||||
BrowserUI._titleChanged(Browser.selectedBrowser);
|
||||
});
|
||||
},
|
||||
|
||||
/*********************************
|
||||
@ -465,21 +389,9 @@ var BrowserUI = {
|
||||
newTab: function newTab(aURI, aOwner) {
|
||||
aURI = aURI || kStartOverlayURI;
|
||||
let tab = Browser.addTab(aURI, true, aOwner);
|
||||
ContextUI.peekTabs();
|
||||
return tab;
|
||||
},
|
||||
|
||||
newOrSelectTab: function newOrSelectTab(aURI, aOwner) {
|
||||
let tabs = Browser.tabs;
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
if (tabs[i].browser.currentURI.spec == aURI) {
|
||||
Browser.selectedTab = tabs[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.newTab(aURI, aOwner);
|
||||
},
|
||||
|
||||
setOnTabAnimationEnd: function setOnTabAnimationEnd(aCallback) {
|
||||
Elements.tabs.addEventListener("animationend", function onAnimationEnd() {
|
||||
Elements.tabs.removeEventListener("animationend", onAnimationEnd);
|
||||
@ -502,9 +414,9 @@ var BrowserUI = {
|
||||
}
|
||||
|
||||
this.setOnTabAnimationEnd(function() {
|
||||
Browser.closeTab(tabToClose, { forceClose: true } );
|
||||
if (wasCollapsed)
|
||||
ContextUI.dismissTabsWithDelay(kNewTabAnimationDelayMsec);
|
||||
Browser.closeTab(tabToClose, { forceClose: true } );
|
||||
if (wasCollapsed)
|
||||
ContextUI.dismissTabsWithDelay(kNewTabAnimationDelayMsec);
|
||||
});
|
||||
},
|
||||
|
||||
@ -546,7 +458,7 @@ var BrowserUI = {
|
||||
|
||||
selectTabAndDismiss: function selectTabAndDismiss(aTab) {
|
||||
this.selectTab(aTab);
|
||||
ContextUI.dismissTabs();
|
||||
ContextUI.dismissTabsWithDelay(kSelectTabAnimationDelayMsec);
|
||||
},
|
||||
|
||||
selectTabAtIndex: function selectTabAtIndex(aIndex) {
|
||||
@ -621,12 +533,6 @@ var BrowserUI = {
|
||||
case "browser.cache.disk_cache_ssl":
|
||||
this._sslDiskCacheEnabled = Services.prefs.getBoolPref(aData);
|
||||
break;
|
||||
case "browser.urlbar.formatting.enabled":
|
||||
this._formattingEnabled = Services.prefs.getBoolPref(aData);
|
||||
break;
|
||||
case "browser.urlbar.trimURLs":
|
||||
this._mayTrimURLs = Services.prefs.getBoolPref(aData);
|
||||
break;
|
||||
case debugServerStateChanged:
|
||||
if (Services.prefs.getBoolPref(aData)) {
|
||||
this.runDebugServer();
|
||||
@ -641,7 +547,7 @@ var BrowserUI = {
|
||||
break;
|
||||
case "metro_viewstate_changed":
|
||||
this._adjustDOMforViewState();
|
||||
let autocomplete = document.getElementById("start-autocomplete");
|
||||
let autocomplete = document.getElementById("urlbar-autocomplete");
|
||||
if (aData == "snapped") {
|
||||
FlyoutPanelsUI.hide();
|
||||
// Order matters (need grids to get dimensions, etc), now
|
||||
@ -660,6 +566,37 @@ var BrowserUI = {
|
||||
* Internal utils
|
||||
*/
|
||||
|
||||
/**
|
||||
* Some prefs that have consequences in both Metro and Desktop such as
|
||||
* app-update prefs, are automatically pulled from Desktop here.
|
||||
*/
|
||||
_pullDesktopControlledPrefs: function() {
|
||||
function pullDesktopControlledPrefType(prefType, prefFunc) {
|
||||
try {
|
||||
registry.create(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
|
||||
"Software\\Mozilla\\Firefox\\Metro\\Prefs\\" + prefType,
|
||||
Ci.nsIWindowsRegKey.ACCESS_ALL);
|
||||
for (let i = 0; i < registry.valueCount; i++) {
|
||||
let prefName = registry.getValueName(i);
|
||||
let prefValue = registry.readStringValue(prefName);
|
||||
if (prefType == Ci.nsIPrefBranch.PREF_BOOL) {
|
||||
prefValue = prefValue == "true";
|
||||
}
|
||||
Services.prefs[prefFunc](prefName, prefValue);
|
||||
}
|
||||
} catch (ex) {
|
||||
Util.dumpLn("Could not pull for prefType " + prefType + ": " + ex);
|
||||
} finally {
|
||||
registry.close();
|
||||
}
|
||||
}
|
||||
let registry = Cc["@mozilla.org/windows-registry-key;1"].
|
||||
createInstance(Ci.nsIWindowsRegKey);
|
||||
pullDesktopControlledPrefType(Ci.nsIPrefBranch.PREF_INT, "setIntPref");
|
||||
pullDesktopControlledPrefType(Ci.nsIPrefBranch.PREF_BOOL, "setBoolPref");
|
||||
pullDesktopControlledPrefType(Ci.nsIPrefBranch.PREF_STRING, "setCharPref");
|
||||
},
|
||||
|
||||
_adjustDOMforViewState: function() {
|
||||
if (MetroUtils.immersive) {
|
||||
let currViewState = "";
|
||||
@ -727,204 +664,6 @@ var BrowserUI = {
|
||||
Elements.urlbarState.setAttribute("mode", "view");
|
||||
},
|
||||
|
||||
_trimURL: function _trimURL(aURL) {
|
||||
// This function must not modify the given URL such that calling
|
||||
// nsIURIFixup::createFixupURI with the result will produce a different URI.
|
||||
return aURL /* remove single trailing slash for http/https/ftp URLs */
|
||||
.replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1")
|
||||
/* remove http:// unless the host starts with "ftp\d*\." or contains "@" */
|
||||
.replace(/^http:\/\/((?!ftp\d*\.)[^\/@]+(?:\/|$))/, "$1");
|
||||
},
|
||||
|
||||
trimURL: function trimURL(aURL) {
|
||||
return this.mayTrimURLs ? this._trimURL(aURL) : aURL;
|
||||
},
|
||||
|
||||
_setURI: function _setURI(aURL) {
|
||||
this._edit.value = aURL;
|
||||
this.lastKnownGoodURL = aURL;
|
||||
},
|
||||
|
||||
_getSelectedURIForClipboard: function _getSelectedURIForClipboard() {
|
||||
// Grab the actual input field's value, not our value, which could include moz-action:
|
||||
let inputVal = this._edit.inputField.value;
|
||||
let selectedVal = inputVal.substring(this._edit.selectionStart, this._edit.electionEnd);
|
||||
|
||||
// If the selection doesn't start at the beginning or doesn't span the full domain or
|
||||
// the URL bar is modified, nothing else to do here.
|
||||
if (this._edit.selectionStart > 0 || this._edit.valueIsTyped)
|
||||
return selectedVal;
|
||||
// The selection doesn't span the full domain if it doesn't contain a slash and is
|
||||
// followed by some character other than a slash.
|
||||
if (!selectedVal.contains("/")) {
|
||||
let remainder = inputVal.replace(selectedVal, "");
|
||||
if (remainder != "" && remainder[0] != "/")
|
||||
return selectedVal;
|
||||
}
|
||||
|
||||
let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
|
||||
|
||||
let uri;
|
||||
try {
|
||||
uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_USE_UTF8);
|
||||
} catch (e) {}
|
||||
if (!uri)
|
||||
return selectedVal;
|
||||
|
||||
// Only copy exposable URIs
|
||||
try {
|
||||
uri = uriFixup.createExposableURI(uri);
|
||||
} catch (ex) {}
|
||||
|
||||
// If the entire URL is selected, just use the actual loaded URI.
|
||||
if (inputVal == selectedVal) {
|
||||
// ... but only if isn't a javascript: or data: URI, since those
|
||||
// are hard to read when encoded
|
||||
if (!uri.schemeIs("javascript") && !uri.schemeIs("data")) {
|
||||
// Parentheses are known to confuse third-party applications (bug 458565).
|
||||
selectedVal = uri.spec.replace(/[()]/g, function (c) escape(c));
|
||||
}
|
||||
|
||||
return selectedVal;
|
||||
}
|
||||
|
||||
// Just the beginning of the URL is selected, check for a trimmed value
|
||||
let spec = uri.spec;
|
||||
let trimmedSpec = this.trimURL(spec);
|
||||
if (spec != trimmedSpec) {
|
||||
// Prepend the portion that trimURL removed from the beginning.
|
||||
// This assumes trimURL will only truncate the URL at
|
||||
// the beginning or end (or both).
|
||||
let trimmedSegments = spec.split(trimmedSpec);
|
||||
selectedVal = trimmedSegments[0] + selectedVal;
|
||||
}
|
||||
|
||||
return selectedVal;
|
||||
},
|
||||
|
||||
_copyCutURIController: {
|
||||
doCommand: function(aCommand) {
|
||||
let urlbar = BrowserUI._edit;
|
||||
let val = BrowserUI._getSelectedURIForClipboard();
|
||||
if (!val)
|
||||
return;
|
||||
|
||||
if (aCommand == "cmd_cut" && this.isCommandEnabled(aCommand)) {
|
||||
let start = urlbar.selectionStart;
|
||||
let end = urlbar.selectionEnd;
|
||||
urlbar.inputField.value = urlbar.inputField.value.substring(0, start) +
|
||||
urlbar.inputField.value.substring(end);
|
||||
urlbar.selectionStart = urlbar.selectionEnd = start;
|
||||
}
|
||||
|
||||
Cc["@mozilla.org/widget/clipboardhelper;1"]
|
||||
.getService(Ci.nsIClipboardHelper)
|
||||
.copyString(val, document);
|
||||
},
|
||||
|
||||
supportsCommand: function(aCommand) {
|
||||
switch (aCommand) {
|
||||
case "cmd_copy":
|
||||
case "cmd_cut":
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
isCommandEnabled: function(aCommand) {
|
||||
let urlbar = BrowserUI._edit;
|
||||
return this.supportsCommand(aCommand) &&
|
||||
(aCommand != "cmd_cut" || !urlbar.readOnly) &&
|
||||
urlbar.selectionStart < urlbar.selectionEnd;
|
||||
},
|
||||
|
||||
onEvent: function(aEventName) {}
|
||||
},
|
||||
|
||||
_editURI: function _editURI(aShouldDismiss) {
|
||||
this._edit.focus();
|
||||
this._edit.select();
|
||||
|
||||
Elements.urlbarState.setAttribute("mode", "edit");
|
||||
StartUI.show();
|
||||
if (aShouldDismiss) {
|
||||
ContextUI.dismissTabs();
|
||||
}
|
||||
},
|
||||
|
||||
formatURI: function formatURI() {
|
||||
if (!this.formattingEnabled ||
|
||||
Elements.urlbarState.getAttribute("mode") == "edit")
|
||||
return;
|
||||
|
||||
let controller = this._edit.editor.selectionController;
|
||||
let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
|
||||
selection.removeAllRanges();
|
||||
|
||||
let textNode = this._edit.editor.rootElement.firstChild;
|
||||
let value = textNode.textContent;
|
||||
|
||||
let protocol = value.match(/^[a-z\d.+\-]+:(?=[^\d])/);
|
||||
if (protocol &&
|
||||
["http:", "https:", "ftp:"].indexOf(protocol[0]) == -1)
|
||||
return;
|
||||
let matchedURL = value.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
|
||||
if (!matchedURL)
|
||||
return;
|
||||
|
||||
let [, preDomain, domain] = matchedURL;
|
||||
let baseDomain = domain;
|
||||
let subDomain = "";
|
||||
// getBaseDomainFromHost doesn't recognize IPv6 literals in brackets as IPs (bug 667159)
|
||||
if (domain[0] != "[") {
|
||||
try {
|
||||
baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
|
||||
if (!domain.endsWith(baseDomain)) {
|
||||
// getBaseDomainFromHost converts its resultant to ACE.
|
||||
let IDNService = Cc["@mozilla.org/network/idn-service;1"]
|
||||
.getService(Ci.nsIIDNService);
|
||||
baseDomain = IDNService.convertACEtoUTF8(baseDomain);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (baseDomain != domain) {
|
||||
subDomain = domain.slice(0, -baseDomain.length);
|
||||
}
|
||||
|
||||
let rangeLength = preDomain.length + subDomain.length;
|
||||
if (rangeLength) {
|
||||
let range = document.createRange();
|
||||
range.setStart(textNode, 0);
|
||||
range.setEnd(textNode, rangeLength);
|
||||
selection.addRange(range);
|
||||
}
|
||||
|
||||
let startRest = preDomain.length + domain.length;
|
||||
if (startRest < value.length) {
|
||||
let range = document.createRange();
|
||||
range.setStart(textNode, startRest);
|
||||
range.setEnd(textNode, value.length);
|
||||
selection.addRange(range);
|
||||
}
|
||||
},
|
||||
|
||||
_clearURIFormatting: function _clearURIFormatting() {
|
||||
if (!this.formattingEnabled)
|
||||
return;
|
||||
|
||||
let controller = this._edit.editor.selectionController;
|
||||
let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
|
||||
selection.removeAllRanges();
|
||||
},
|
||||
|
||||
_urlbarBlurred: function _urlbarBlurred() {
|
||||
let state = Elements.urlbarState;
|
||||
if (state.getAttribute("mode") == "edit")
|
||||
state.removeAttribute("mode");
|
||||
this._updateToolbar();
|
||||
this.formatURI();
|
||||
},
|
||||
|
||||
_closeOrQuit: function _closeOrQuit() {
|
||||
// Close active dialog, if we have one. If not then close the application.
|
||||
if (!BrowserUI.isContentShowing()) {
|
||||
@ -985,10 +724,7 @@ var BrowserUI = {
|
||||
aEvent.preventDefault();
|
||||
|
||||
if (this._edit.popupOpen) {
|
||||
this._edit.value = this.lastKnownGoodURL;
|
||||
this._edit.closePopup();
|
||||
StartUI.hide();
|
||||
ContextUI.dismiss();
|
||||
this._edit.endEditing(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1205,24 +941,6 @@ var BrowserUI = {
|
||||
return this._sslDiskCacheEnabled;
|
||||
},
|
||||
|
||||
_formattingEnabled: null,
|
||||
|
||||
get formattingEnabled() {
|
||||
if (this._formattingEnabled === null) {
|
||||
this._formattingEnabled = Services.prefs.getBoolPref("browser.urlbar.formatting.enabled");
|
||||
}
|
||||
return this._formattingEnabled;
|
||||
},
|
||||
|
||||
_mayTrimURLs: null,
|
||||
|
||||
get mayTrimURLs() {
|
||||
if (this._mayTrimURLs === null) {
|
||||
this._mayTrimURLs = Services.prefs.getBoolPref("browser.urlbar.trimURLs");
|
||||
}
|
||||
return this._mayTrimURLs;
|
||||
},
|
||||
|
||||
supportsCommand : function(cmd) {
|
||||
var isSupported = false;
|
||||
switch (cmd) {
|
||||
@ -1302,7 +1020,7 @@ var BrowserUI = {
|
||||
break;
|
||||
case "cmd_openLocation":
|
||||
ContextUI.displayNavbar();
|
||||
this._editURI(true);
|
||||
this._edit.beginEditing(true);
|
||||
break;
|
||||
case "cmd_addBookmark":
|
||||
ContextUI.displayNavbar();
|
||||
@ -1332,7 +1050,8 @@ var BrowserUI = {
|
||||
break;
|
||||
case "cmd_newTab":
|
||||
this.newTab();
|
||||
this._editURI(false);
|
||||
this._edit.beginEditing(false);
|
||||
ContextUI.peekTabs(kNewTabAnimationDelayMsec);
|
||||
break;
|
||||
case "cmd_closeTab":
|
||||
this.closeTab();
|
||||
@ -1372,9 +1091,8 @@ var BrowserUI = {
|
||||
};
|
||||
|
||||
var StartUI = {
|
||||
get isVisible() { return this.isStartPageVisible || this.isFiltering; },
|
||||
get isVisible() { return this.isStartPageVisible; },
|
||||
get isStartPageVisible() { return Elements.windowState.hasAttribute("startpage"); },
|
||||
get isFiltering() { return Elements.windowState.hasAttribute("filtering"); },
|
||||
|
||||
get maxResultsPerSection() {
|
||||
return Services.prefs.getIntPref("browser.display.startUI.maxresults");
|
||||
@ -1389,8 +1107,6 @@ var StartUI = {
|
||||
],
|
||||
|
||||
init: function init() {
|
||||
Elements.startUI.addEventListener("autocompletestart", this, false);
|
||||
Elements.startUI.addEventListener("autocompleteend", this, false);
|
||||
Elements.startUI.addEventListener("contextmenu", this, false);
|
||||
Elements.startUI.addEventListener("click", this, false);
|
||||
Elements.startUI.addEventListener("MozMousePixelScroll", this, false);
|
||||
@ -1429,24 +1145,6 @@ var StartUI = {
|
||||
return true;
|
||||
},
|
||||
|
||||
/** Show the autocomplete popup */
|
||||
filter: function filter() {
|
||||
if (this.isFiltering)
|
||||
return;
|
||||
|
||||
BrowserUI._edit.openPopup();
|
||||
Elements.windowState.setAttribute("filtering", "true");
|
||||
},
|
||||
|
||||
/** Hide the autocomplete popup */
|
||||
unfilter: function unfilter() {
|
||||
if (!this.isFiltering)
|
||||
return;
|
||||
|
||||
BrowserUI._edit.closePopup();
|
||||
Elements.windowState.removeAttribute("filtering");
|
||||
},
|
||||
|
||||
/** Hide the Firefox start page */
|
||||
hide: function hide(aURI) {
|
||||
aURI = aURI || Browser.selectedBrowser.currentURI.spec;
|
||||
@ -1455,8 +1153,6 @@ var StartUI = {
|
||||
|
||||
Elements.contentShowing.removeAttribute("disabled");
|
||||
Elements.windowState.removeAttribute("startpage");
|
||||
|
||||
this.unfilter();
|
||||
return true;
|
||||
},
|
||||
|
||||
@ -1488,12 +1184,6 @@ var StartUI = {
|
||||
|
||||
handleEvent: function handleEvent(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "autocompletestart":
|
||||
this.filter();
|
||||
break;
|
||||
case "autocompleteend":
|
||||
this.unfilter();
|
||||
break;
|
||||
case "contextmenu":
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("MozEdgeUICompleted", true, false);
|
||||
|
@ -95,11 +95,11 @@ setting[type="menulist"] {
|
||||
}
|
||||
|
||||
#urlbar-edit {
|
||||
-moz-binding: url("chrome://browser/content/bindings/autocomplete.xml#autocomplete");
|
||||
-moz-binding: url("chrome://browser/content/bindings/urlbar.xml#urlbar");
|
||||
}
|
||||
|
||||
#start-autocomplete {
|
||||
-moz-binding: url("chrome://browser/content/bindings/autocomplete.xml#autocomplete-popup");
|
||||
#urlbar-autocomplete {
|
||||
-moz-binding: url("chrome://browser/content/bindings/urlbar.xml#urlbar-autocomplete");
|
||||
}
|
||||
|
||||
richgrid {
|
||||
|
@ -574,6 +574,7 @@ var Browser = {
|
||||
} else {
|
||||
// Update all of our UI to reflect the new tab's location
|
||||
BrowserUI.updateURI();
|
||||
BrowserUI.update();
|
||||
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("TabSelect", true, false);
|
||||
@ -1578,7 +1579,7 @@ Tab.prototype = {
|
||||
|
||||
// Ensure that history is initialized
|
||||
history.QueryInterface(Ci.nsISHistoryInternal);
|
||||
|
||||
|
||||
for (let i = 0, length = otherHistory.index; i <= length; i++)
|
||||
history.addEntry(otherHistory.getEntryAtIndex(i, false), true);
|
||||
},
|
||||
|
@ -179,7 +179,8 @@
|
||||
<vbox id="page">
|
||||
<vbox id="tray" class="tray-toolbar" observes="bcast_windowState" >
|
||||
<!-- Tabs -->
|
||||
<hbox id="tabs-container" observes="bcast_windowState">
|
||||
<!-- onclick handler to work around bug 837242 -->
|
||||
<hbox id="tabs-container" observes="bcast_windowState" onclick="void(0);">
|
||||
<box id="tabs" flex="1"
|
||||
observes="bcast_preciseInput"
|
||||
onselect="BrowserUI.selectTabAndDismiss(this);"
|
||||
@ -228,8 +229,6 @@
|
||||
onclick="PanelUI.show('remotetabs-container');" inputProcessing="true"/>
|
||||
</scrollbox>
|
||||
</hbox>
|
||||
<!-- Autocompletion interface -->
|
||||
<box id="start-autocomplete" observes="bcast_windowState"/>
|
||||
</hbox>
|
||||
</vbox> <!-- end tray -->
|
||||
|
||||
@ -256,44 +255,48 @@
|
||||
<hbox id="progress-control" />
|
||||
</hbox>
|
||||
|
||||
<!-- Main Toolbar -->
|
||||
<toolbar id="toolbar" observes="bcast_windowState" flex="1">
|
||||
<observes element="bcast_windowState" attribute="*"/>
|
||||
<observes element="bcast_urlbarState" attribute="*"/>
|
||||
<vbox id="toolbar-autocomplete" flex="1">
|
||||
<!-- Autocomplete -->
|
||||
<scrollbox flex="1">
|
||||
<hbox id="urlbar-autocomplete" observes="bcast_windowState"/>
|
||||
</scrollbox>
|
||||
|
||||
<toolbarbutton id="back-button" class="appbar-primary" command="cmd_back"/>
|
||||
<toolbarbutton id="forward-button" class="appbar-primary" command="cmd_forward"/>
|
||||
<!-- Main Toolbar -->
|
||||
<toolbar id="toolbar" observes="bcast_windowState" flex="1">
|
||||
<observes element="bcast_windowState" attribute="*"/>
|
||||
<observes element="bcast_urlbarState" attribute="*"/>
|
||||
|
||||
<hbox id="urlbar-container" flex="1" observes="bcast_urlbarState">
|
||||
<hbox id="urlbar" flex="1">
|
||||
<box id="identity-box" role="button">
|
||||
<hbox id="identity-box-inner" align="center" mousethrough="always">
|
||||
<image id="identity-icon"/>
|
||||
</hbox>
|
||||
</box>
|
||||
<toolbarbutton id="back-button" class="appbar-primary" command="cmd_back"/>
|
||||
<toolbarbutton id="forward-button" class="appbar-primary" command="cmd_forward"/>
|
||||
|
||||
<textbox id="urlbar-edit"
|
||||
type="url"
|
||||
class="uri-element"
|
||||
autocompletesearch="history"
|
||||
autocompletepopup="start-autocomplete"
|
||||
completeselectedindex="true"
|
||||
placeholder="&urlbar.emptytext;"
|
||||
flex="1"
|
||||
onpaste="this.focus();"
|
||||
ontextentered="BrowserUI.handleUrlbarEnter(param);"
|
||||
onblur="BrowserUI._urlbarBlurred();"/>
|
||||
<hbox id="urlbar-container" flex="1" observes="bcast_urlbarState">
|
||||
<hbox id="urlbar" flex="1">
|
||||
<box id="identity-box" role="button">
|
||||
<hbox id="identity-box-inner" align="center" mousethrough="always">
|
||||
<image id="identity-icon"/>
|
||||
</hbox>
|
||||
</box>
|
||||
|
||||
<textbox id="urlbar-edit"
|
||||
type="url"
|
||||
class="uri-element"
|
||||
autocompletesearch="history"
|
||||
autocompletepopup="urlbar-autocomplete"
|
||||
completeselectedindex="true"
|
||||
placeholder="&urlbar.emptytext;"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
|
||||
<toolbarbutton id="reload-button" oncommand="CommandUpdater.doCommand(event.shiftKey ? 'cmd_forceReload' : 'cmd_reload');"/>
|
||||
<toolbarbutton id="stop-button" command="cmd_stop"/>
|
||||
</hbox>
|
||||
|
||||
<toolbarbutton id="reload-button" oncommand="CommandUpdater.doCommand(event.shiftKey ? 'cmd_forceReload' : 'cmd_reload');"/>
|
||||
<toolbarbutton id="stop-button" command="cmd_stop"/>
|
||||
</hbox>
|
||||
|
||||
<toolbarbutton id="download-button" oncommand="Appbar.onDownloadButton()"/>
|
||||
<toolbarbutton id="star-button" class="appbar-primary" type="checkbox" oncommand="Appbar.onStarButton()"/>
|
||||
<toolbarbutton id="pin-button" class="appbar-primary" type="checkbox" oncommand="Appbar.onPinButton()"/>
|
||||
<toolbarbutton id="menu-button" class="appbar-primary" oncommand="Appbar.onMenuButton(event)"/>
|
||||
</toolbar>
|
||||
<toolbarbutton id="download-button" oncommand="Appbar.onDownloadButton()"/>
|
||||
<toolbarbutton id="star-button" class="appbar-primary" type="checkbox" oncommand="Appbar.onStarButton()"/>
|
||||
<toolbarbutton id="pin-button" class="appbar-primary" type="checkbox" oncommand="Appbar.onPinButton()"/>
|
||||
<toolbarbutton id="menu-button" class="appbar-primary" oncommand="Appbar.onMenuButton(event)"/>
|
||||
</toolbar>
|
||||
</vbox>
|
||||
</appbar>
|
||||
|
||||
<vbox id="panel-container" hidden="true" class="window-width window-height meta" observes="bcast_windowState">
|
||||
@ -353,7 +356,7 @@
|
||||
<button class="findbar-item previous-button" command="cmd_findPrevious"/>
|
||||
<button class="findbar-item next-button" command="cmd_findNext"/>
|
||||
<spacer flex="1"/>
|
||||
<button class="findbar-item close-button" command="cmd_findClose"/>
|
||||
<button id="findbar-close" class="findbar-item close-button" command="cmd_findClose"/>
|
||||
</appbar>
|
||||
|
||||
<!-- Context button bar -->
|
||||
|
@ -338,6 +338,7 @@ function MenuPopup(aPanel, aPopup) {
|
||||
this._panel = aPanel;
|
||||
this._popup = aPopup;
|
||||
this._wantTypeBehind = false;
|
||||
this._willReshowPopup = false;
|
||||
|
||||
window.addEventListener('MozAppbarShowing', this, false);
|
||||
}
|
||||
@ -346,9 +347,19 @@ MenuPopup.prototype = {
|
||||
get _commands() { return this._popup.childNodes[0]; },
|
||||
|
||||
show: function (aPositionOptions) {
|
||||
if (this._visible)
|
||||
return;
|
||||
if (this._visible) {
|
||||
this._willReshowPopup = true;
|
||||
let self = this;
|
||||
this._panel.addEventListener("transitionend", function () {
|
||||
self._show(aPositionOptions);
|
||||
self._panel.removeEventListener("transitionend", arguments.callee);
|
||||
});
|
||||
} else {
|
||||
this._show(aPositionOptions);
|
||||
}
|
||||
},
|
||||
|
||||
_show: function (aPositionOptions) {
|
||||
window.addEventListener("keypress", this, true);
|
||||
window.addEventListener("mousedown", this, true);
|
||||
Elements.stack.addEventListener("PopupChanged", this, false);
|
||||
@ -362,9 +373,12 @@ MenuPopup.prototype = {
|
||||
self._panel.removeEventListener("transitionend", arguments.callee);
|
||||
self._panel.removeAttribute("showingfrom");
|
||||
|
||||
let eventName = self._willReshowPopup ? "popupmoved" : "popupshown";
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("popupshown", true, false);
|
||||
document.dispatchEvent(event);
|
||||
event.initEvent(eventName, true, false);
|
||||
self._panel.dispatchEvent(event);
|
||||
|
||||
self._willReshowPopup = false;
|
||||
});
|
||||
|
||||
let popupFrom = !aPositionOptions.bottomAligned ? "above" : "below";
|
||||
@ -393,9 +407,11 @@ MenuPopup.prototype = {
|
||||
self._popup.style.maxWidth = "none";
|
||||
self._popup.style.maxHeight = "none";
|
||||
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("popuphidden", true, false);
|
||||
document.dispatchEvent(event);
|
||||
if (!self._willReshowPopup) {
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("popuphidden", true, false);
|
||||
self._panel.dispatchEvent(event);
|
||||
}
|
||||
});
|
||||
|
||||
this._panel.setAttribute("hiding", "true");
|
||||
|
@ -21,7 +21,7 @@ chrome.jar:
|
||||
content/bindings/dialog.xml (content/bindings/dialog.xml)
|
||||
content/bindings/arrowbox.xml (content/bindings/arrowbox.xml)
|
||||
content/bindings/grid.xml (content/bindings/grid.xml)
|
||||
content/bindings/autocomplete.xml (content/bindings/autocomplete.xml)
|
||||
content/bindings/urlbar.xml (content/bindings/urlbar.xml)
|
||||
content/bindings/appbar.xml (content/bindings/appbar.xml)
|
||||
content/bindings/flyoutpanel.xml (content/bindings/flyoutpanel.xml)
|
||||
content/bindings/selectionoverlay.xml (content/bindings/selectionoverlay.xml)
|
||||
|
@ -12,6 +12,7 @@ include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
BROWSER_TESTS = \
|
||||
head.js \
|
||||
browser_urlbar.js \
|
||||
browser_bookmarks.js \
|
||||
browser_canonizeURL.js \
|
||||
browser_context_menu_tests.js \
|
||||
@ -20,6 +21,8 @@ BROWSER_TESTS = \
|
||||
browser_context_menu_tests_03.html \
|
||||
browser_context_ui.js \
|
||||
browser_downloads.js \
|
||||
browser_findbar.js \
|
||||
browser_findbar.html \
|
||||
browser_history.js \
|
||||
browser_onscreen_keyboard.js \
|
||||
browser_onscreen_keyboard.html \
|
||||
@ -60,6 +63,7 @@ BROWSER_TEST_RESOURCES = \
|
||||
res/textblock01.html \
|
||||
res/textinput01.html \
|
||||
res/textarea01.html \
|
||||
res/testEngine.xml \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(BROWSER_TESTS)
|
||||
|
@ -16,6 +16,6 @@ function test() {
|
||||
[" example/a ", {ctrlKey: true}, "http://www.example.com/a"]
|
||||
];
|
||||
for (let [input, modifiers, result] of testcases) {
|
||||
is(BrowserUI._canonizeURL(input, modifiers), result, input + " -> " + result);
|
||||
is(BrowserUI._edit._canonizeURL(input, modifiers), result, input + " -> " + result);
|
||||
}
|
||||
}
|
||||
|
10
browser/metro/base/tests/mochitest/browser_findbar.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Find bar tests</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Find bar tests</title>
|
||||
</body>
|
||||
</html>
|
56
browser/metro/base/tests/mochitest/browser_findbar.js
Normal file
@ -0,0 +1,56 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
function test() {
|
||||
runTests();
|
||||
}
|
||||
|
||||
gTests.push({
|
||||
desc: "Access the find bar with the keyboard",
|
||||
run: function() {
|
||||
let tab = yield addTab(chromeRoot + "browser_findbar.html");
|
||||
yield waitForCondition(() => BrowserUI.ready);
|
||||
is(Elements.findbar.isShowing, false, "Find bar is hidden by default");
|
||||
|
||||
EventUtils.synthesizeKey("f", { accelKey: true });
|
||||
yield waitForEvent(Elements.findbar, "transitionend");
|
||||
is(Elements.findbar.isShowing, true, "Show find bar with Ctrl-F");
|
||||
|
||||
let textbox = document.getElementById("findbar-textbox");
|
||||
is(textbox.value, "", "Find bar is empty");
|
||||
|
||||
EventUtils.sendString("bar");
|
||||
is(textbox.value, "bar", "Type 'bar' into find bar");
|
||||
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", { accelKey: true });
|
||||
yield waitForEvent(Elements.findbar, "transitionend");
|
||||
is(Elements.findbar.isShowing, false, "Hide find bar with Esc");
|
||||
|
||||
Browser.closeTab(tab);
|
||||
}
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "Show and hide the find bar with mouse",
|
||||
run: function() {
|
||||
let tab = yield addTab(chromeRoot + "browser_findbar.html");
|
||||
yield waitForCondition(() => BrowserUI.ready);
|
||||
is(Elements.findbar.isShowing, false, "Find bar is hidden by default");
|
||||
|
||||
ContextUI.displayNavbar();
|
||||
EventUtils.sendMouseEvent({ type: "click" }, "menu-button");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, "context-findinpage");
|
||||
yield waitForEvent(Elements.findbar, "transitionend");
|
||||
is(Elements.findbar.isShowing, true, "Show find bar with menu item");
|
||||
|
||||
EventUtils.synthesizeMouse(document.getElementById("findbar-close"), 1, 1, {});
|
||||
yield waitForEvent(Elements.findbar, "transitionend");
|
||||
is(Elements.findbar.isShowing, false, "Hide find bar with close button");
|
||||
|
||||
Browser.closeTab(tab);
|
||||
}
|
||||
});
|
@ -56,7 +56,7 @@ gTests.push({
|
||||
var touchdrag = new TouchDragAndHold();
|
||||
yield touchdrag.start(gWindow, xpos, ypos, 900, ypos);
|
||||
yield waitForCondition(function () {
|
||||
return getTrimmedSelection(edit).toString() ==
|
||||
return getTrimmedSelection(edit).toString() ==
|
||||
"mochitests/content/metro/browser/metro/base/tests/mochitest/res/textblock01.html";
|
||||
}, kCommonWaitMs, kCommonPollMs);
|
||||
touchdrag.end();
|
||||
@ -69,6 +69,29 @@ gTests.push({
|
||||
},
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "bug 887120 - tap & hold to paste into urlbar",
|
||||
run: function bug887120_test() {
|
||||
gWindow = window;
|
||||
|
||||
yield showNavBar();
|
||||
let edit = document.getElementById("urlbar-edit");
|
||||
|
||||
SpecialPowers.clipboardCopyString("mozilla");
|
||||
sendContextMenuClickToElement(window, edit);
|
||||
yield waitForEvent(document, "popupshown");
|
||||
|
||||
ok(ContextMenuUI._menuPopup._visible, "is visible");
|
||||
let paste = document.getElementById("context-paste");
|
||||
ok(!paste.hidden, "paste item is visible");
|
||||
|
||||
sendElementTap(window, paste);
|
||||
ok(edit.popup.popupOpen, "bug: popup should be showing");
|
||||
|
||||
delete window.r;
|
||||
}
|
||||
});
|
||||
|
||||
function test() {
|
||||
if (!isLandscapeMode()) {
|
||||
todo(false, "browser_selection_tests need landscape mode to run.");
|
||||
|
232
browser/metro/base/tests/mochitest/browser_urlbar.js
Normal file
@ -0,0 +1,232 @@
|
||||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gEdit = null;
|
||||
|
||||
/*=============================================================================
|
||||
Search engine mocking utilities
|
||||
=============================================================================*/
|
||||
|
||||
var gEngine = null;
|
||||
|
||||
const kSearchEngineName = "Foo";
|
||||
const kSearchEngineURI = chromeRoot + "res/testEngine.xml";
|
||||
|
||||
/*
|
||||
* addMockSearchDefault - adds a mock search engine to the top of the engine list.
|
||||
*/
|
||||
function addMockSearchDefault(aTimeoutMs) {
|
||||
let deferred = Promise.defer();
|
||||
let timeoutMs = aTimeoutMs || kDefaultWait;
|
||||
let timerID = 0;
|
||||
|
||||
function engineAddObserver(aSubject, aTopic, aData) {
|
||||
if (aData != "engine-added")
|
||||
return;
|
||||
|
||||
gEngine = Services.search.getEngineByName(kSearchEngineName);
|
||||
Services.obs.removeObserver(engineAddObserver, "browser-search-engine-modified");
|
||||
clearTimeout(timerID);
|
||||
gEngine.hidden = false;
|
||||
ok(gEngine, "mock engine was added");
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
if (gEngine) {
|
||||
deferred.resolve();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
timerID = setTimeout(function ids_canceller() {
|
||||
Services.obs.removeObserver(engineAddObserver, "browser-search-engine-modified");
|
||||
deferred.reject(new Error("search add timeout"));
|
||||
}, timeoutMs);
|
||||
|
||||
Services.obs.addObserver(engineAddObserver, "browser-search-engine-modified", false);
|
||||
Services.search.addEngine(kSearchEngineURI, Ci.nsISearchEngine.DATA_XML,
|
||||
"data:image/x-icon,%00", false);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/*
|
||||
* removeMockSearchDefault - removes mock "Foo" search engine.
|
||||
*/
|
||||
|
||||
function removeMockSearchDefault(aTimeoutMs) {
|
||||
let deferred = Promise.defer();
|
||||
let timeoutMs = aTimeoutMs || kDefaultWait;
|
||||
let timerID = 0;
|
||||
|
||||
function engineRemoveObserver(aSubject, aTopic, aData) {
|
||||
if (aData != "engine-removed")
|
||||
return;
|
||||
|
||||
clearTimeout(timerID);
|
||||
gEngine = null;
|
||||
Services.obs.removeObserver(engineRemoveObserver, "browser-search-engine-modified");
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
if (!gEngine) {
|
||||
deferred.resolve();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
timerID = setTimeout(function ids_canceller() {
|
||||
Services.obs.removeObserver(engineRemoveObserver, "browser-search-engine-modified");
|
||||
deferred.reject(new Error("search remove timeout"));
|
||||
}, timeoutMs);
|
||||
|
||||
Services.obs.addObserver(engineRemoveObserver, "browser-search-engine-modified", false);
|
||||
Services.search.removeEngine(gEngine);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/*=============================================================================
|
||||
Test cases
|
||||
=============================================================================*/
|
||||
|
||||
function test() {
|
||||
runTests();
|
||||
}
|
||||
|
||||
|
||||
function setUp() {
|
||||
if (!gEdit)
|
||||
gEdit = document.getElementById("urlbar-edit");
|
||||
|
||||
yield addTab("about:start");
|
||||
yield showNavBar();
|
||||
yield waitForCondition(function () {
|
||||
return StartUI.isStartPageVisible;
|
||||
});
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
yield removeMockSearchDefault();
|
||||
Browser.closeTab(Browser.selectedTab, { forceClose: true });
|
||||
delete window.r;
|
||||
}
|
||||
|
||||
gTests.push({
|
||||
desc: "search engines update",
|
||||
setUp: setUp,
|
||||
tearDown: tearDown,
|
||||
run: function testSearchEngine() {
|
||||
// If the XBL hasn't initialized yet, open the popup so that it will.
|
||||
if (gEdit.popup._searches == undefined) {
|
||||
gEdit.openPopup();
|
||||
gEdit.closePopup();
|
||||
}
|
||||
|
||||
let numSearches = gEdit.popup._searches.itemCount;
|
||||
function getEngineItem() {
|
||||
return gEdit.popup._searches.querySelector("richgriditem[value="+kSearchEngineName+"]");
|
||||
}
|
||||
|
||||
yield addMockSearchDefault();
|
||||
ok(gEdit.popup._searches.itemCount == numSearches + 1, "added search engine count");
|
||||
ok(getEngineItem(), "added search engine item");
|
||||
|
||||
yield removeMockSearchDefault();
|
||||
ok(gEdit.popup._searches.itemCount == numSearches, "normal search engine count");
|
||||
ok(!getEngineItem(), "added search engine item");
|
||||
}
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "display autocomplete while typing, handle enter",
|
||||
setUp: setUp,
|
||||
tearDown: tearDown,
|
||||
run: function testUrlbarTyping() {
|
||||
sendElementTap(window, gEdit);
|
||||
ok(gEdit.isEditing, "focus urlbar: in editing mode");
|
||||
ok(!gEdit.popup.popupOpen, "focus urlbar: popup not open yet");
|
||||
|
||||
EventUtils.sendString("about:blank", window);
|
||||
let opened = yield waitForCondition(() => gEdit.popup.popupOpen);
|
||||
ok(opened, "type in urlbar: popup opens");
|
||||
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, window);
|
||||
let closed = yield waitForCondition(() => !gEdit.popup.popupOpen);
|
||||
ok(closed, "hit enter in urlbar: popup closes, page loads");
|
||||
ok(!gEdit.isEditing, "hit enter in urlbar: not in editing mode");
|
||||
}
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "display and select a search with keyboard",
|
||||
setUp: setUp,
|
||||
tearDown: tearDown,
|
||||
run: function testSearchKeyboard() {
|
||||
yield addMockSearchDefault();
|
||||
|
||||
sendElementTap(window, gEdit);
|
||||
ok(gEdit.isEditing, "focus urlbar: in editing mode");
|
||||
ok(!gEdit.popup.popupOpen, "focus urlbar: popup not open yet");
|
||||
|
||||
let search = "mozilla";
|
||||
EventUtils.sendString(search, window);
|
||||
yield waitForCondition(() => gEdit.popup.popupOpen);
|
||||
|
||||
// XXX We should probably change the keyboard selection behavior entirely,
|
||||
// given that it makes little to no sense, but that's a job for a later patch.
|
||||
|
||||
EventUtils.synthesizeKey("VK_DOWN", {}, window);
|
||||
is(gEdit.popup.selectedIndex, -1, "key select search: no result selected");
|
||||
is(gEdit.popup._searches.selectedIndex, 0, "key select search: first search selected");
|
||||
|
||||
let engines = Services.search.getVisibleEngines();
|
||||
for (let i = 0, max = engines.length - 1; i < max; i++) {
|
||||
is(gEdit.popup._searches.selectedIndex, i, "key select search: next index");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {}, window);
|
||||
}
|
||||
|
||||
let existingValue = gEdit.value;
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, window);
|
||||
|
||||
yield waitForCondition(() => gEdit.value != existingValue);
|
||||
|
||||
let closed = yield waitForCondition(() => !gEdit.popup.popupOpen);
|
||||
ok(closed, "hit enter in urlbar: popup closes, page loads");
|
||||
ok(!gEdit.isEditing, "hit enter in urlbar: not in editing mode");
|
||||
|
||||
let searchSubmission = gEngine.getSubmission(search, null);
|
||||
let trimmedSubmission = gEdit.trimValue(searchSubmission.uri.spec);
|
||||
is(gEdit.value, trimmedSubmission, "hit enter in urlbar: search conducted");
|
||||
|
||||
yield removeMockSearchDefault();
|
||||
}
|
||||
});
|
||||
|
||||
gTests.push({
|
||||
desc: "display and select a search with touch",
|
||||
setUp: setUp,
|
||||
tearDown: tearDown,
|
||||
run: function testUrlbarSearchesTouch() {
|
||||
yield addMockSearchDefault();
|
||||
|
||||
sendElementTap(window, gEdit);
|
||||
ok(gEdit.isEditing, "focus urlbar: in editing mode");
|
||||
ok(!gEdit.popup.popupOpen, "focus urlbar: popup not open yet");
|
||||
|
||||
let search = "mozilla";
|
||||
EventUtils.sendString(search, window);
|
||||
yield waitForCondition(() => gEdit.popup.popupOpen);
|
||||
|
||||
sendElementTap(window, gEdit.popup._searches.lastChild);
|
||||
|
||||
let closed = yield waitForCondition(() => !gEdit.popup.popupOpen);
|
||||
ok(closed, "tap search option: popup closes, page loads");
|
||||
ok(!gEdit.isEditing, "tap search option: not in editing mode");
|
||||
|
||||
let searchSubmission = gEngine.getSubmission(search, null);
|
||||
let trimmedSubmission = gEdit.trimValue(searchSubmission.uri.spec);
|
||||
is(gEdit.value, trimmedSubmission, "tap search option: search conducted");
|
||||
}
|
||||
});
|
||||
|
@ -708,8 +708,8 @@ TouchDragAndHold.prototype = {
|
||||
System utilities
|
||||
=============================================================================*/
|
||||
|
||||
/*
|
||||
* emptyClipboard - clear the windows clipbaord.
|
||||
/*
|
||||
* emptyClipboard - clear the windows clipboard.
|
||||
*/
|
||||
function emptyClipboard() {
|
||||
Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard)
|
||||
|
12
browser/metro/base/tests/mochitest/res/testEngine.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
|
||||
xmlns:moz="http://www.mozilla.org/2006/browser/search/">
|
||||
<ShortName>Foo</ShortName>
|
||||
<Description>Foo Search</Description>
|
||||
<InputEncoding>utf-8</InputEncoding>
|
||||
<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
|
||||
<Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?search">
|
||||
<Param name="test" value="{searchTerms}"/>
|
||||
</Url>
|
||||
<moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/</moz:SearchForm>
|
||||
<moz:Alias>fooalias</moz:Alias>
|
||||
</OpenSearchDescription>
|
@ -248,7 +248,7 @@ documenttab[selected] .documenttab-selection {
|
||||
|
||||
/* Toolbar ------------------------------------------------------------------ */
|
||||
|
||||
#toolbar {
|
||||
#toolbar-autocomplete {
|
||||
background-color: @panel_light_color@;
|
||||
}
|
||||
|
||||
@ -364,7 +364,7 @@ appbar {
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
appbar > toolbar {
|
||||
appbar toolbar {
|
||||
-moz-appearance: none;
|
||||
-moz-box-align: center;
|
||||
border: 0;
|
||||
@ -373,7 +373,7 @@ appbar > toolbar {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
appbar > toolbar > toolbarbutton {
|
||||
appbar toolbar > toolbarbutton {
|
||||
border: 0;
|
||||
margin: 0 @toolbar_horizontal_spacing@;
|
||||
padding: 0;
|
||||
|
@ -194,16 +194,17 @@ menulist {
|
||||
min-width: @touch_action_minwidth@; /* keep the button from being too narrow */
|
||||
border: 0 none;
|
||||
-moz-box-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.menu-popup richlistitem:not([disabled]):hover {
|
||||
background-color: #dedad0;
|
||||
background-color: #ccc;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.menu-popup richlistitem:not([disabled]):active {
|
||||
background-color: @selected_color@ !important;
|
||||
color: black;
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.menu-popup > richlistbox[left-hand="true"] > richlistitem {
|
||||
@ -214,7 +215,7 @@ menulist {
|
||||
padding-right: 50px;
|
||||
}
|
||||
|
||||
/* form select popup */
|
||||
/* Additional styles applied to popups for form <select> elements. */
|
||||
|
||||
#select-container {
|
||||
padding: 0;
|
||||
@ -233,7 +234,7 @@ menulist {
|
||||
}
|
||||
|
||||
/* listcell element doesn't have flex="1" so we need to force it */
|
||||
#select-commands .option-command > listcell {
|
||||
.option-command > listcell {
|
||||
-moz-box-flex: 1 !important;
|
||||
}
|
||||
|
||||
@ -244,8 +245,8 @@ menulist {
|
||||
}
|
||||
|
||||
.option-command.selected {
|
||||
background-color: @selected_color@ !important;
|
||||
color: black;
|
||||
background-color: #ff8000;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.option-command.optgroup {
|
||||
@ -255,12 +256,12 @@ menulist {
|
||||
}
|
||||
|
||||
.option-command:not([disabled]):hover {
|
||||
background-color: #dedad0;
|
||||
background-color: #f0f0f0;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.option-command:not([disabled]):active {
|
||||
background-color: @selected_color@ !important;
|
||||
background-color: #d3d3d3;
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
@ -91,11 +91,6 @@ toolbarbutton.bookmark-item[open="true"] {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
#PlacesToolbarItems > .bookmark-item:not([image]):not([label=""]):not([container]) > .toolbarbutton-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* Prevent [mode="icons"] from hiding the label */
|
||||
.bookmark-item > .toolbarbutton-text {
|
||||
display: -moz-box !important;
|
||||
|
@ -216,11 +216,6 @@ toolbarbutton.bookmark-item > menupopup {
|
||||
max-height: 16px;
|
||||
}
|
||||
|
||||
#PlacesToolbarItems > .bookmark-item:not([image]):not([label=""]):not([container]) > .toolbarbutton-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.bookmark-item > .toolbarbutton-icon[label]:not([label=""]),
|
||||
.bookmark-item > .toolbarbutton-icon[type="menu"] {
|
||||
-moz-margin-end: 5px;
|
||||
|
@ -33,17 +33,17 @@
|
||||
rgba(229,114,0,0));
|
||||
%else
|
||||
%if MOZ_UPDATE_CHANNEL == aurora
|
||||
color: hsl(214,90%,23%);
|
||||
background-image: linear-gradient(hsla(208,99%,37%,0),
|
||||
hsla(214,90%,23%,.5) 35%,
|
||||
hsla(214,90%,23%,.5) 65%,
|
||||
hsla(214,90%,23%,0));
|
||||
color: rgb(51,30,84);
|
||||
background-image: linear-gradient(rgba(51,30,84,0),
|
||||
rgba(51,30,84,.5) 35%,
|
||||
rgba(51,30,84,.5) 65%,
|
||||
rgba(51,30,84,0));
|
||||
%else
|
||||
color: hsl(211,33%,32%);
|
||||
background-image: linear-gradient(hsla(211,33%,32%,0),
|
||||
hsla(211,33%,32%,.5) 35%,
|
||||
hsla(211,33%,32%,.5) 65%,
|
||||
hsla(211,33%,32%,0));
|
||||
color: rgb(0,33,71);
|
||||
background-image: linear-gradient(rgba(0,33,71,0),
|
||||
rgba(0,33,71,.5) 35%,
|
||||
rgba(0,33,71,.5) 65%,
|
||||
rgba(0,33,71,0));
|
||||
%endif
|
||||
%endif
|
||||
}
|
||||
|
@ -90,6 +90,18 @@
|
||||
border-color: hsla(206,100%,60%,.65) hsla(206,100%,55%,.65) hsla(206,100%,50%,.65);
|
||||
}
|
||||
|
||||
#sidebar-header {
|
||||
-moz-appearance: none;
|
||||
color: black;
|
||||
background-color: #EEF3FA;
|
||||
border-bottom: none;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
#sidebar-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sidebar-splitter {
|
||||
border: 0;
|
||||
-moz-border-end: 1px solid #A9B7C9;
|
||||
|
@ -612,10 +612,6 @@ toolbarbutton.bookmark-item[open="true"] {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
#PlacesToolbarItems > .bookmark-item:not([image]):not([label=""]):not([container]) > .toolbarbutton-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Prevent [mode="icons"] from hiding the label */
|
||||
.bookmark-item > .toolbarbutton-text {
|
||||
display: -moz-box !important;
|
||||
|
@ -15,4 +15,8 @@
|
||||
background-color: transparent;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.sidebar-placesTreechildren::-moz-tree-cell-text(leaf, hover) {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
53
build/compare-mozconfig/compare-mozconfigs-wrapper.py
Normal file
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/python
|
||||
# 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/.
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from os import path
|
||||
from buildconfig import substs
|
||||
|
||||
def determine_platform():
|
||||
platform_mapping = {'WINNT': {'x86_64': 'win64',
|
||||
'i386': 'win32'},
|
||||
'Darwin': {'x86_64': 'macosx-universal',
|
||||
'i386':'macosx-universal'},
|
||||
'Linux': {'x86_64': 'linux64',
|
||||
'i386': 'linux32'}}
|
||||
|
||||
os_type = substs['OS_TARGET']
|
||||
cpu_type = substs['TARGET_CPU']
|
||||
return platform_mapping.get(os_type, {}).get(cpu_type, None)
|
||||
|
||||
def main():
|
||||
""" A wrapper script that calls compare-mozconfig.py
|
||||
based on the platform that the machine is building for"""
|
||||
platform = determine_platform()
|
||||
|
||||
if platform is not None:
|
||||
python_exe = substs['PYTHON']
|
||||
topsrcdir = substs['top_srcdir']
|
||||
|
||||
# construct paths and args for compare-mozconfig
|
||||
browser_dir = path.join(topsrcdir, 'browser')
|
||||
script_path = path.join(topsrcdir, 'build/compare-mozconfig/compare-mozconfigs.py')
|
||||
whitelist_path = path.join(browser_dir, 'config/mozconfigs/whitelist')
|
||||
beta_mozconfig_path = path.join(browser_dir, 'config/mozconfigs', platform, 'beta')
|
||||
release_mozconfig_path = path.join(browser_dir, 'config/mozconfigs', platform, 'release')
|
||||
nightly_mozconfig_path = path.join(browser_dir, 'config/mozconfigs', platform, 'nightly')
|
||||
|
||||
# compare beta vs nightly
|
||||
ret_code = subprocess.call([python_exe, script_path, '--whitelist',
|
||||
whitelist_path, '--no-download',
|
||||
platform + ',' + beta_mozconfig_path +
|
||||
',' + nightly_mozconfig_path])
|
||||
|
||||
if ret_code > 0:
|
||||
sys.exit(ret_code)
|
||||
|
||||
# compare release vs nightly
|
||||
ret_code = subprocess.call([python_exe, script_path, '--whitelist',
|
||||
whitelist_path, '--no-download',
|
||||
platform + ',' + release_mozconfig_path +
|
||||
',' + nightly_mozconfig_path])
|
68
build/compare-mozconfig/compare-mozconfigs.py
Normal file
@ -0,0 +1,68 @@
|
||||
#!/usr/bin/python
|
||||
import logging
|
||||
import os
|
||||
import site
|
||||
import sys
|
||||
import urllib2
|
||||
|
||||
site.addsitedir(os.path.join(os.path.dirname(__file__), "../../lib/python"))
|
||||
|
||||
from release.sanity import verify_mozconfigs
|
||||
from release.info import readConfig
|
||||
from util.hg import make_hg_url
|
||||
|
||||
FAILURE_CODE = 1
|
||||
SUCCESS_CODE = 0
|
||||
|
||||
def get_mozconfig(path, options):
|
||||
"""Consumes a path and returns a list of lines from
|
||||
the mozconfig file. If download is required, the path
|
||||
specified should be relative to the root of the hg
|
||||
repository e.g browser/config/mozconfigs/linux32/nightly"""
|
||||
if options.no_download:
|
||||
return open(path, 'r').readlines()
|
||||
else:
|
||||
url = make_hg_url(options.hghost, options.branch, 'http',
|
||||
options.revision, path)
|
||||
return urllib2.urlopen(url).readlines()
|
||||
|
||||
if __name__ == '__main__':
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser()
|
||||
|
||||
parser.add_option('--branch', dest='branch')
|
||||
parser.add_option('--revision', dest='revision')
|
||||
parser.add_option('--hghost', dest='hghost', default='hg.mozilla.org')
|
||||
parser.add_option('--whitelist', dest='whitelist')
|
||||
parser.add_option('--no-download', action='store_true', dest='no_download',
|
||||
default=False)
|
||||
options, args = parser.parse_args()
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
missing_args = options.branch is None or options.revision is None
|
||||
if not options.no_download and missing_args:
|
||||
logging.error('Not enough arguments to download mozconfigs')
|
||||
sys.exit(FAILURE_CODE)
|
||||
|
||||
mozconfig_whitelist = readConfig(options.whitelist, ['whitelist'])
|
||||
|
||||
for arg in args:
|
||||
platform, mozconfig_path, nightly_mozconfig_path = arg.split(',')
|
||||
|
||||
mozconfig_lines = get_mozconfig(mozconfig_path, options)
|
||||
nightly_mozconfig_lines = get_mozconfig(nightly_mozconfig_path, options)
|
||||
|
||||
mozconfig_pair = (mozconfig_path, mozconfig_lines)
|
||||
nightly_mozconfig_pair = (nightly_mozconfig_path,
|
||||
nightly_mozconfig_lines)
|
||||
|
||||
passed = verify_mozconfigs(mozconfig_pair, nightly_mozconfig_pair,
|
||||
platform, mozconfig_whitelist)
|
||||
|
||||
if passed:
|
||||
logging.info('Mozconfig check passed!')
|
||||
else:
|
||||
logging.error('Mozconfig check failed!')
|
||||
sys.exit(FAILURE_CODE)
|
||||
sys.exit(SUCCESS_CODE)
|
@ -19,8 +19,7 @@ toolkit/library
|
||||
security/manager
|
||||
security/dbm
|
||||
security/nss
|
||||
accessible/build
|
||||
accessible
|
||||
accessible
|
||||
dom
|
||||
content
|
||||
layout
|
||||
|
@ -131,6 +131,7 @@ def bootstrap(topsrcdir, mozilla_dir=None):
|
||||
os.makedirs(state_env_dir, mode=0o770)
|
||||
print('Please re-run mach.')
|
||||
sys.exit(1)
|
||||
state_dir = state_env_dir
|
||||
else:
|
||||
if not os.path.exists(state_user_dir):
|
||||
print(STATE_DIR_FIRST_RUN.format(userdir=state_user_dir))
|
||||
@ -146,6 +147,7 @@ def bootstrap(topsrcdir, mozilla_dir=None):
|
||||
os.mkdir(state_user_dir)
|
||||
print('Please re-run mach.')
|
||||
sys.exit(1)
|
||||
state_dir = state_user_dir
|
||||
|
||||
try:
|
||||
import mach.main
|
||||
@ -153,7 +155,12 @@ def bootstrap(topsrcdir, mozilla_dir=None):
|
||||
sys.path[0:0] = [os.path.join(mozilla_dir, path) for path in SEARCH_PATHS]
|
||||
import mach.main
|
||||
|
||||
def populate_context(context):
|
||||
context.state_dir = state_dir
|
||||
|
||||
mach = mach.main.Mach(topsrcdir)
|
||||
mach.populate_context_handler = populate_context
|
||||
|
||||
for category, meta in CATEGORIES.items():
|
||||
mach.define_category(category, meta['short'], meta['long'],
|
||||
meta['priority'])
|
||||
|
218
build/release/info.py
Normal file
@ -0,0 +1,218 @@
|
||||
from datetime import datetime
|
||||
import os
|
||||
from os import path
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from urllib2 import urlopen
|
||||
|
||||
from release.paths import makeCandidatesDir
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# If version has two parts with no trailing specifiers like "rc", we
|
||||
# consider it a "final" release for which we only create a _RELEASE tag.
|
||||
FINAL_RELEASE_REGEX = "^\d+\.\d+$"
|
||||
|
||||
|
||||
class ConfigError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def getBuildID(platform, product, version, buildNumber, nightlyDir='nightly',
|
||||
server='stage.mozilla.org'):
|
||||
infoTxt = makeCandidatesDir(product, version, buildNumber, nightlyDir,
|
||||
protocol='http', server=server) + \
|
||||
'%s_info.txt' % platform
|
||||
try:
|
||||
buildInfo = urlopen(infoTxt).read()
|
||||
except:
|
||||
log.error("Failed to retrieve %s" % infoTxt)
|
||||
raise
|
||||
|
||||
for line in buildInfo.splitlines():
|
||||
key, value = line.rstrip().split('=', 1)
|
||||
if key == 'buildID':
|
||||
return value
|
||||
|
||||
|
||||
def findOldBuildIDs(product, version, buildNumber, platforms,
|
||||
nightlyDir='nightly', server='stage.mozilla.org'):
|
||||
ids = {}
|
||||
if buildNumber <= 1:
|
||||
return ids
|
||||
for n in range(1, buildNumber):
|
||||
for platform in platforms:
|
||||
if platform not in ids:
|
||||
ids[platform] = []
|
||||
try:
|
||||
id = getBuildID(platform, product, version, n, nightlyDir,
|
||||
server)
|
||||
ids[platform].append(id)
|
||||
except Exception, e:
|
||||
log.error("Hit exception: %s" % e)
|
||||
return ids
|
||||
|
||||
|
||||
def getReleaseConfigName(product, branch, version=None, staging=False):
|
||||
# XXX: Horrible hack for bug 842741. Because Thunderbird release
|
||||
# and esr both build out of esr17 repositories we'll bump the wrong
|
||||
# config for release without this.
|
||||
if product == 'thunderbird' and 'esr17' in branch and version and 'esr' not in version:
|
||||
cfg = 'release-thunderbird-comm-release.py'
|
||||
else:
|
||||
cfg = 'release-%s-%s.py' % (product, branch)
|
||||
if staging:
|
||||
cfg = 'staging_%s' % cfg
|
||||
return cfg
|
||||
|
||||
|
||||
def readReleaseConfig(configfile, required=[]):
|
||||
return readConfig(configfile, keys=['releaseConfig'], required=required)
|
||||
|
||||
|
||||
def readBranchConfig(dir, localconfig, branch, required=[]):
|
||||
shutil.copy(localconfig, path.join(dir, "localconfig.py"))
|
||||
oldcwd = os.getcwd()
|
||||
os.chdir(dir)
|
||||
sys.path.append(".")
|
||||
try:
|
||||
return readConfig("config.py", keys=['BRANCHES', branch],
|
||||
required=required)
|
||||
finally:
|
||||
os.chdir(oldcwd)
|
||||
sys.path.remove(".")
|
||||
|
||||
|
||||
def readConfig(configfile, keys=[], required=[]):
|
||||
c = {}
|
||||
execfile(configfile, c)
|
||||
for k in keys:
|
||||
c = c[k]
|
||||
items = c.keys()
|
||||
err = False
|
||||
for key in required:
|
||||
if key not in items:
|
||||
err = True
|
||||
log.error("Required item `%s' missing from %s" % (key, c))
|
||||
if err:
|
||||
raise ConfigError("Missing at least one item in config, see above")
|
||||
return c
|
||||
|
||||
|
||||
def isFinalRelease(version):
|
||||
return bool(re.match(FINAL_RELEASE_REGEX, version))
|
||||
|
||||
|
||||
def getBaseTag(product, version):
|
||||
product = product.upper()
|
||||
version = version.replace('.', '_')
|
||||
return '%s_%s' % (product, version)
|
||||
|
||||
|
||||
def getTags(baseTag, buildNumber, buildTag=True):
|
||||
t = ['%s_RELEASE' % baseTag]
|
||||
if buildTag:
|
||||
t.append('%s_BUILD%d' % (baseTag, int(buildNumber)))
|
||||
return t
|
||||
|
||||
|
||||
def getRuntimeTag(tag):
|
||||
return "%s_RUNTIME" % tag
|
||||
|
||||
|
||||
def getReleaseTag(tag):
|
||||
return "%s_RELEASE" % tag
|
||||
|
||||
|
||||
def generateRelbranchName(version, prefix='GECKO'):
|
||||
return '%s%s_%s_RELBRANCH' % (
|
||||
prefix, version.replace('.', ''),
|
||||
datetime.now().strftime('%Y%m%d%H'))
|
||||
|
||||
|
||||
def getReleaseName(product, version, buildNumber):
|
||||
return '%s-%s-build%s' % (product.title(), version, str(buildNumber))
|
||||
|
||||
|
||||
def getRepoMatchingBranch(branch, sourceRepositories):
|
||||
for sr in sourceRepositories.values():
|
||||
if branch in sr['path']:
|
||||
return sr
|
||||
return None
|
||||
|
||||
|
||||
def fileInfo(filepath, product):
|
||||
"""Extract information about a release file. Returns a dictionary with the
|
||||
following keys set:
|
||||
'product', 'version', 'locale', 'platform', 'contents', 'format',
|
||||
'pathstyle'
|
||||
|
||||
'contents' is one of 'complete', 'installer'
|
||||
'format' is one of 'mar' or 'exe'
|
||||
'pathstyle' is either 'short' or 'long', and refers to if files are all in
|
||||
one directory, with the locale as part of the filename ('short' paths,
|
||||
firefox 3.0 style filenames), or if the locale names are part of the
|
||||
directory structure, but not the file name itself ('long' paths,
|
||||
firefox 3.5+ style filenames)
|
||||
"""
|
||||
try:
|
||||
# Mozilla 1.9.0 style (aka 'short') paths
|
||||
# e.g. firefox-3.0.12.en-US.win32.complete.mar
|
||||
filename = os.path.basename(filepath)
|
||||
m = re.match("^(%s)-([0-9.]+)\.([-a-zA-Z]+)\.(win32)\.(complete|installer)\.(mar|exe)$" % product, filename)
|
||||
if not m:
|
||||
raise ValueError("Could not parse: %s" % filename)
|
||||
return {'product': m.group(1),
|
||||
'version': m.group(2),
|
||||
'locale': m.group(3),
|
||||
'platform': m.group(4),
|
||||
'contents': m.group(5),
|
||||
'format': m.group(6),
|
||||
'pathstyle': 'short',
|
||||
'leading_path': '',
|
||||
}
|
||||
except:
|
||||
# Mozilla 1.9.1 and on style (aka 'long') paths
|
||||
# e.g. update/win32/en-US/firefox-3.5.1.complete.mar
|
||||
# win32/en-US/Firefox Setup 3.5.1.exe
|
||||
ret = {'pathstyle': 'long'}
|
||||
if filepath.endswith('.mar'):
|
||||
ret['format'] = 'mar'
|
||||
m = re.search("update/(win32|linux-i686|linux-x86_64|mac|mac64)/([-a-zA-Z]+)/(%s)-(\d+\.\d+(?:\.\d+)?(?:\w+(?:\d+)?)?)\.(complete)\.mar" % product, filepath)
|
||||
if not m:
|
||||
raise ValueError("Could not parse: %s" % filepath)
|
||||
ret['platform'] = m.group(1)
|
||||
ret['locale'] = m.group(2)
|
||||
ret['product'] = m.group(3)
|
||||
ret['version'] = m.group(4)
|
||||
ret['contents'] = m.group(5)
|
||||
ret['leading_path'] = ''
|
||||
elif filepath.endswith('.exe'):
|
||||
ret['format'] = 'exe'
|
||||
ret['contents'] = 'installer'
|
||||
# EUballot builds use a different enough style of path than others
|
||||
# that we can't catch them in the same regexp
|
||||
if filepath.find('win32-EUballot') != -1:
|
||||
ret['platform'] = 'win32'
|
||||
m = re.search("(win32-EUballot/)([-a-zA-Z]+)/((?i)%s) Setup (\d+\.\d+(?:\.\d+)?(?:\w+\d+)?(?:\ \w+\ \d+)?)\.exe" % product, filepath)
|
||||
if not m:
|
||||
raise ValueError("Could not parse: %s" % filepath)
|
||||
ret['leading_path'] = m.group(1)
|
||||
ret['locale'] = m.group(2)
|
||||
ret['product'] = m.group(3).lower()
|
||||
ret['version'] = m.group(4)
|
||||
else:
|
||||
m = re.search("(partner-repacks/[-a-zA-Z0-9_]+/|)(win32|mac|linux-i686)/([-a-zA-Z]+)/((?i)%s) Setup (\d+\.\d+(?:\.\d+)?(?:\w+(?:\d+)?)?(?:\ \w+\ \d+)?)\.exe" % product, filepath)
|
||||
if not m:
|
||||
raise ValueError("Could not parse: %s" % filepath)
|
||||
ret['leading_path'] = m.group(1)
|
||||
ret['platform'] = m.group(2)
|
||||
ret['locale'] = m.group(3)
|
||||
ret['product'] = m.group(4).lower()
|
||||
ret['version'] = m.group(5)
|
||||
else:
|
||||
raise ValueError("Unknown filetype for %s" % filepath)
|
||||
|
||||
return ret
|
124
build/release/sanity.py
Normal file
@ -0,0 +1,124 @@
|
||||
import difflib
|
||||
import logging
|
||||
import re
|
||||
import urllib2
|
||||
from util.commands import run_cmd, get_output
|
||||
from util.hg import get_repo_name, make_hg_url
|
||||
from subprocess import CalledProcessError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def check_buildbot():
|
||||
"""check if buildbot command works"""
|
||||
try:
|
||||
run_cmd(['buildbot', '--version'])
|
||||
except CalledProcessError:
|
||||
log.error("FAIL: buildbot command doesn't work", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
def find_version(contents, versionNumber):
|
||||
"""Given an open readable file-handle look for the occurrence
|
||||
of the version # in the file"""
|
||||
ret = re.search(re.compile(re.escape(versionNumber), re.DOTALL), contents)
|
||||
return ret
|
||||
|
||||
|
||||
def locale_diff(locales1, locales2):
|
||||
""" accepts two lists and diffs them both ways, returns any differences
|
||||
found """
|
||||
diff_list = [locale for locale in locales1 if not locale in locales2]
|
||||
diff_list.extend(locale for locale in locales2 if not locale in locales1)
|
||||
return diff_list
|
||||
|
||||
|
||||
def get_buildbot_username_param():
|
||||
cmd = ['buildbot', 'sendchange', '--help']
|
||||
output = get_output(cmd)
|
||||
if "-W, --who=" in output:
|
||||
return "--who"
|
||||
else:
|
||||
return "--username"
|
||||
|
||||
|
||||
def sendchange(branch, revision, username, master, products):
|
||||
"""Send the change to buildbot to kick off the release automation"""
|
||||
if isinstance(products, basestring):
|
||||
products = [products]
|
||||
cmd = [
|
||||
'buildbot',
|
||||
'sendchange',
|
||||
get_buildbot_username_param(),
|
||||
username,
|
||||
'--master',
|
||||
master,
|
||||
'--branch',
|
||||
branch,
|
||||
'-p',
|
||||
'products:%s' % ','.join(products),
|
||||
'-p',
|
||||
'script_repo_revision:%s' % revision,
|
||||
'release_build'
|
||||
]
|
||||
logging.info("Executing: %s" % cmd)
|
||||
run_cmd(cmd)
|
||||
|
||||
|
||||
def verify_mozconfigs(mozconfig_pair, nightly_mozconfig_pair, platform,
|
||||
mozconfigWhitelist={}):
|
||||
"""Compares mozconfig to nightly_mozconfig and compare to an optional
|
||||
whitelist of known differences. mozconfig_pair and nightly_mozconfig_pair
|
||||
are pairs containing the mozconfig's identifier and the list of lines in
|
||||
the mozconfig."""
|
||||
|
||||
# unpack the pairs to get the names, the names are just for
|
||||
# identifying the mozconfigs when logging the error messages
|
||||
mozconfig_name, mozconfig_lines = mozconfig_pair
|
||||
nightly_mozconfig_name, nightly_mozconfig_lines = nightly_mozconfig_pair
|
||||
|
||||
missing_args = mozconfig_lines == [] or nightly_mozconfig_lines == []
|
||||
if missing_args:
|
||||
log.info("Missing mozconfigs to compare for %s" % platform)
|
||||
return False
|
||||
|
||||
success = True
|
||||
|
||||
diffInstance = difflib.Differ()
|
||||
diff_result = diffInstance.compare(mozconfig_lines, nightly_mozconfig_lines)
|
||||
diffList = list(diff_result)
|
||||
|
||||
for line in diffList:
|
||||
clean_line = line[1:].strip()
|
||||
if (line[0] == '-' or line[0] == '+') and len(clean_line) > 1:
|
||||
# skip comment lines
|
||||
if clean_line.startswith('#'):
|
||||
continue
|
||||
# compare to whitelist
|
||||
message = ""
|
||||
if line[0] == '-':
|
||||
if platform in mozconfigWhitelist.get('release', {}):
|
||||
if clean_line in \
|
||||
mozconfigWhitelist['release'][platform]:
|
||||
continue
|
||||
elif line[0] == '+':
|
||||
if platform in mozconfigWhitelist.get('nightly', {}):
|
||||
if clean_line in \
|
||||
mozconfigWhitelist['nightly'][platform]:
|
||||
continue
|
||||
else:
|
||||
log.warning("%s not in %s %s!" % (
|
||||
clean_line, platform,
|
||||
mozconfigWhitelist['nightly'][platform]))
|
||||
else:
|
||||
log.error("Skipping line %s!" % line)
|
||||
continue
|
||||
message = "found in %s but not in %s: %s"
|
||||
if line[0] == '-':
|
||||
log.error(message % (mozconfig_name,
|
||||
nightly_mozconfig_name, clean_line))
|
||||
else:
|
||||
log.error(message % (nightly_mozconfig_name,
|
||||
mozconfig_name, clean_line))
|
||||
success = False
|
||||
return success
|
611
build/util/hg.py
Normal file
@ -0,0 +1,611 @@
|
||||
"""Functions for interacting with hg"""
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from urlparse import urlsplit
|
||||
from ConfigParser import RawConfigParser
|
||||
|
||||
from util.commands import run_cmd, get_output, remove_path
|
||||
from util.retry import retry
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DefaultShareBase:
|
||||
pass
|
||||
DefaultShareBase = DefaultShareBase()
|
||||
|
||||
|
||||
class HgUtilError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _make_absolute(repo):
|
||||
if repo.startswith("file://"):
|
||||
path = repo[len("file://"):]
|
||||
repo = "file://%s" % os.path.abspath(path)
|
||||
elif "://" not in repo:
|
||||
repo = os.path.abspath(repo)
|
||||
return repo
|
||||
|
||||
|
||||
def make_hg_url(hgHost, repoPath, protocol='https', revision=None,
|
||||
filename=None):
|
||||
"""construct a valid hg url from a base hg url (hg.mozilla.org),
|
||||
repoPath, revision and possible filename"""
|
||||
base = '%s://%s' % (protocol, hgHost)
|
||||
repo = '/'.join(p.strip('/') for p in [base, repoPath])
|
||||
if not filename:
|
||||
if not revision:
|
||||
return repo
|
||||
else:
|
||||
return '/'.join([p.strip('/') for p in [repo, 'rev', revision]])
|
||||
else:
|
||||
assert revision
|
||||
return '/'.join([p.strip('/') for p in [repo, 'raw-file', revision, filename]])
|
||||
|
||||
|
||||
def get_repo_name(repo):
|
||||
return repo.rstrip('/').split('/')[-1]
|
||||
|
||||
|
||||
def get_repo_path(repo):
|
||||
repo = _make_absolute(repo)
|
||||
if repo.startswith("/"):
|
||||
return repo.lstrip("/")
|
||||
else:
|
||||
return urlsplit(repo).path.lstrip("/")
|
||||
|
||||
|
||||
def get_revision(path):
|
||||
"""Returns which revision directory `path` currently has checked out."""
|
||||
return get_output(['hg', 'parent', '--template', '{node|short}'], cwd=path)
|
||||
|
||||
|
||||
def get_branch(path):
|
||||
return get_output(['hg', 'branch'], cwd=path).strip()
|
||||
|
||||
|
||||
def get_branches(path):
|
||||
branches = []
|
||||
for line in get_output(['hg', 'branches', '-c'], cwd=path).splitlines():
|
||||
branches.append(line.split()[0])
|
||||
return branches
|
||||
|
||||
|
||||
def is_hg_cset(rev):
|
||||
"""Retruns True if passed revision represents a valid HG revision
|
||||
(long or short(er) 40 bit hex)"""
|
||||
try:
|
||||
int(rev, 16)
|
||||
return True
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
|
||||
|
||||
def hg_ver():
|
||||
"""Returns the current version of hg, as a tuple of
|
||||
(major, minor, build)"""
|
||||
ver_string = get_output(['hg', '-q', 'version'])
|
||||
match = re.search("\(version ([0-9.]+)\)", ver_string)
|
||||
if match:
|
||||
bits = match.group(1).split(".")
|
||||
if len(bits) < 3:
|
||||
bits += (0,)
|
||||
ver = tuple(int(b) for b in bits)
|
||||
else:
|
||||
ver = (0, 0, 0)
|
||||
log.debug("Running hg version %s", ver)
|
||||
return ver
|
||||
|
||||
|
||||
def purge(dest):
|
||||
"""Purge the repository of all untracked and ignored files."""
|
||||
try:
|
||||
run_cmd(['hg', '--config', 'extensions.purge=', 'purge',
|
||||
'-a', '--all', dest], cwd=dest)
|
||||
except subprocess.CalledProcessError, e:
|
||||
log.debug('purge failed: %s' % e)
|
||||
raise
|
||||
|
||||
|
||||
def update(dest, branch=None, revision=None):
|
||||
"""Updates working copy `dest` to `branch` or `revision`. If neither is
|
||||
set then the working copy will be updated to the latest revision on the
|
||||
current branch. Local changes will be discarded."""
|
||||
# If we have a revision, switch to that
|
||||
if revision is not None:
|
||||
cmd = ['hg', 'update', '-C', '-r', revision]
|
||||
run_cmd(cmd, cwd=dest)
|
||||
else:
|
||||
# Check & switch branch
|
||||
local_branch = get_output(['hg', 'branch'], cwd=dest).strip()
|
||||
|
||||
cmd = ['hg', 'update', '-C']
|
||||
|
||||
# If this is different, checkout the other branch
|
||||
if branch and branch != local_branch:
|
||||
cmd.append(branch)
|
||||
|
||||
run_cmd(cmd, cwd=dest)
|
||||
return get_revision(dest)
|
||||
|
||||
|
||||
def clone(repo, dest, branch=None, revision=None, update_dest=True,
|
||||
clone_by_rev=False, mirrors=None, bundles=None):
|
||||
"""Clones hg repo and places it at `dest`, replacing whatever else is
|
||||
there. The working copy will be empty.
|
||||
|
||||
If `revision` is set, only the specified revision and its ancestors will
|
||||
be cloned.
|
||||
|
||||
If `update_dest` is set, then `dest` will be updated to `revision` if
|
||||
set, otherwise to `branch`, otherwise to the head of default.
|
||||
|
||||
If `mirrors` is set, will try and clone from the mirrors before
|
||||
cloning from `repo`.
|
||||
|
||||
If `bundles` is set, will try and download the bundle first and
|
||||
unbundle it. If successful, will pull in new revisions from mirrors or
|
||||
the master repo. If unbundling fails, will fall back to doing a regular
|
||||
clone from mirrors or the master repo.
|
||||
|
||||
Regardless of how the repository ends up being cloned, the 'default' path
|
||||
will point to `repo`.
|
||||
"""
|
||||
if os.path.exists(dest):
|
||||
remove_path(dest)
|
||||
|
||||
if bundles:
|
||||
log.info("Attempting to initialize clone with bundles")
|
||||
for bundle in bundles:
|
||||
if os.path.exists(dest):
|
||||
remove_path(dest)
|
||||
init(dest)
|
||||
log.info("Trying to use bundle %s", bundle)
|
||||
try:
|
||||
if not unbundle(bundle, dest):
|
||||
remove_path(dest)
|
||||
continue
|
||||
adjust_paths(dest, default=repo)
|
||||
# Now pull / update
|
||||
return pull(repo, dest, update_dest=update_dest,
|
||||
mirrors=mirrors, revision=revision, branch=branch)
|
||||
except Exception:
|
||||
remove_path(dest)
|
||||
log.exception("Problem unbundling/pulling from %s", bundle)
|
||||
continue
|
||||
else:
|
||||
log.info("Using bundles failed; falling back to clone")
|
||||
|
||||
if mirrors:
|
||||
log.info("Attempting to clone from mirrors")
|
||||
for mirror in mirrors:
|
||||
log.info("Cloning from %s", mirror)
|
||||
try:
|
||||
retval = clone(mirror, dest, branch, revision,
|
||||
update_dest=update_dest, clone_by_rev=clone_by_rev)
|
||||
adjust_paths(dest, default=repo)
|
||||
return retval
|
||||
except:
|
||||
log.exception("Problem cloning from mirror %s", mirror)
|
||||
continue
|
||||
else:
|
||||
log.info("Pulling from mirrors failed; falling back to %s", repo)
|
||||
# We may have a partial repo here; mercurial() copes with that
|
||||
# We need to make sure our paths are correct though
|
||||
if os.path.exists(os.path.join(dest, '.hg')):
|
||||
adjust_paths(dest, default=repo)
|
||||
return mercurial(repo, dest, branch, revision, autoPurge=True,
|
||||
update_dest=update_dest, clone_by_rev=clone_by_rev)
|
||||
|
||||
cmd = ['hg', 'clone']
|
||||
if not update_dest:
|
||||
cmd.append('-U')
|
||||
|
||||
if clone_by_rev:
|
||||
if revision:
|
||||
cmd.extend(['-r', revision])
|
||||
elif branch:
|
||||
# hg >= 1.6 supports -b branch for cloning
|
||||
ver = hg_ver()
|
||||
if ver >= (1, 6, 0):
|
||||
cmd.extend(['-b', branch])
|
||||
|
||||
cmd.extend([repo, dest])
|
||||
run_cmd(cmd)
|
||||
|
||||
if update_dest:
|
||||
return update(dest, branch, revision)
|
||||
|
||||
|
||||
def common_args(revision=None, branch=None, ssh_username=None, ssh_key=None):
|
||||
"""Fill in common hg arguments, encapsulating logic checks that depend on
|
||||
mercurial versions and provided arguments"""
|
||||
args = []
|
||||
if ssh_username or ssh_key:
|
||||
opt = ['-e', 'ssh']
|
||||
if ssh_username:
|
||||
opt[1] += ' -l %s' % ssh_username
|
||||
if ssh_key:
|
||||
opt[1] += ' -i %s' % ssh_key
|
||||
args.extend(opt)
|
||||
if revision:
|
||||
args.extend(['-r', revision])
|
||||
elif branch:
|
||||
if hg_ver() >= (1, 6, 0):
|
||||
args.extend(['-b', branch])
|
||||
return args
|
||||
|
||||
|
||||
def pull(repo, dest, update_dest=True, mirrors=None, **kwargs):
|
||||
"""Pulls changes from hg repo and places it in `dest`.
|
||||
|
||||
If `update_dest` is set, then `dest` will be updated to `revision` if
|
||||
set, otherwise to `branch`, otherwise to the head of default.
|
||||
|
||||
If `mirrors` is set, will try and pull from the mirrors first before
|
||||
`repo`."""
|
||||
|
||||
if mirrors:
|
||||
for mirror in mirrors:
|
||||
try:
|
||||
return pull(mirror, dest, update_dest=update_dest, **kwargs)
|
||||
except:
|
||||
log.exception("Problem pulling from mirror %s", mirror)
|
||||
continue
|
||||
else:
|
||||
log.info("Pulling from mirrors failed; falling back to %s", repo)
|
||||
|
||||
# Convert repo to an absolute path if it's a local repository
|
||||
repo = _make_absolute(repo)
|
||||
cmd = ['hg', 'pull']
|
||||
# Don't pass -r to "hg pull", except when it's a valid HG revision.
|
||||
# Pulling using tag names is dangerous: it uses the local .hgtags, so if
|
||||
# the tag has moved on the remote side you won't pull the new revision the
|
||||
# remote tag refers to.
|
||||
pull_kwargs = kwargs.copy()
|
||||
if 'revision' in pull_kwargs and \
|
||||
not is_hg_cset(pull_kwargs['revision']):
|
||||
del pull_kwargs['revision']
|
||||
|
||||
cmd.extend(common_args(**pull_kwargs))
|
||||
|
||||
cmd.append(repo)
|
||||
run_cmd(cmd, cwd=dest)
|
||||
|
||||
if update_dest:
|
||||
branch = None
|
||||
if 'branch' in kwargs and kwargs['branch']:
|
||||
branch = kwargs['branch']
|
||||
revision = None
|
||||
if 'revision' in kwargs and kwargs['revision']:
|
||||
revision = kwargs['revision']
|
||||
return update(dest, branch=branch, revision=revision)
|
||||
|
||||
# Defines the places of attributes in the tuples returned by `out'
|
||||
REVISION, BRANCH = 0, 1
|
||||
|
||||
|
||||
def out(src, remote, **kwargs):
|
||||
"""Check for outgoing changesets present in a repo"""
|
||||
cmd = ['hg', '-q', 'out', '--template', '{node} {branches}\n']
|
||||
cmd.extend(common_args(**kwargs))
|
||||
cmd.append(remote)
|
||||
if os.path.exists(src):
|
||||
try:
|
||||
revs = []
|
||||
for line in get_output(cmd, cwd=src).rstrip().split("\n"):
|
||||
try:
|
||||
rev, branch = line.split()
|
||||
# Mercurial displays no branch at all if the revision is on
|
||||
# "default"
|
||||
except ValueError:
|
||||
rev = line.rstrip()
|
||||
branch = "default"
|
||||
revs.append((rev, branch))
|
||||
return revs
|
||||
except subprocess.CalledProcessError, inst:
|
||||
# In some situations, some versions of Mercurial return "1"
|
||||
# if no changes are found, so we need to ignore this return code
|
||||
if inst.returncode == 1:
|
||||
return []
|
||||
raise
|
||||
|
||||
|
||||
def push(src, remote, push_new_branches=True, force=False, **kwargs):
|
||||
cmd = ['hg', 'push']
|
||||
cmd.extend(common_args(**kwargs))
|
||||
if force:
|
||||
cmd.append('-f')
|
||||
if push_new_branches:
|
||||
cmd.append('--new-branch')
|
||||
cmd.append(remote)
|
||||
run_cmd(cmd, cwd=src)
|
||||
|
||||
|
||||
def mercurial(repo, dest, branch=None, revision=None, update_dest=True,
|
||||
shareBase=DefaultShareBase, allowUnsharedLocalClones=False,
|
||||
clone_by_rev=False, mirrors=None, bundles=None, autoPurge=False):
|
||||
"""Makes sure that `dest` is has `revision` or `branch` checked out from
|
||||
`repo`.
|
||||
|
||||
Do what it takes to make that happen, including possibly clobbering
|
||||
dest.
|
||||
|
||||
If allowUnsharedLocalClones is True and we're trying to use the share
|
||||
extension but fail, then we will be able to clone from the shared repo to
|
||||
our destination. If this is False, the default, then if we don't have the
|
||||
share extension we will just clone from the remote repository.
|
||||
|
||||
If `clone_by_rev` is True, use 'hg clone -r <rev>' instead of 'hg clone'.
|
||||
This is slower, but useful when cloning repos with lots of heads.
|
||||
|
||||
If `mirrors` is set, will try and use the mirrors before `repo`.
|
||||
|
||||
If `bundles` is set, will try and download the bundle first and
|
||||
unbundle it instead of doing a full clone. If successful, will pull in
|
||||
new revisions from mirrors or the master repo. If unbundling fails, will
|
||||
fall back to doing a regular clone from mirrors or the master repo.
|
||||
"""
|
||||
dest = os.path.abspath(dest)
|
||||
if shareBase is DefaultShareBase:
|
||||
shareBase = os.environ.get("HG_SHARE_BASE_DIR", None)
|
||||
|
||||
log.info("Reporting hg version in use")
|
||||
cmd = ['hg', '-q', 'version']
|
||||
run_cmd(cmd, cwd='.')
|
||||
|
||||
if shareBase:
|
||||
# Check that 'hg share' works
|
||||
try:
|
||||
log.info("Checking if share extension works")
|
||||
output = get_output(['hg', 'help', 'share'], dont_log=True)
|
||||
if 'no commands defined' in output:
|
||||
# Share extension is enabled, but not functional
|
||||
log.info("Disabling sharing since share extension doesn't seem to work (1)")
|
||||
shareBase = None
|
||||
elif 'unknown command' in output:
|
||||
# Share extension is disabled
|
||||
log.info("Disabling sharing since share extension doesn't seem to work (2)")
|
||||
shareBase = None
|
||||
except subprocess.CalledProcessError:
|
||||
# The command failed, so disable sharing
|
||||
log.info("Disabling sharing since share extension doesn't seem to work (3)")
|
||||
shareBase = None
|
||||
|
||||
# Check that our default path is correct
|
||||
if os.path.exists(os.path.join(dest, '.hg')):
|
||||
hgpath = path(dest, "default")
|
||||
|
||||
# Make sure that our default path is correct
|
||||
if hgpath != _make_absolute(repo):
|
||||
log.info("hg path isn't correct (%s should be %s); clobbering",
|
||||
hgpath, _make_absolute(repo))
|
||||
remove_path(dest)
|
||||
|
||||
# If the working directory already exists and isn't using share we update
|
||||
# the working directory directly from the repo, ignoring the sharing
|
||||
# settings
|
||||
if os.path.exists(dest):
|
||||
if not os.path.exists(os.path.join(dest, ".hg")):
|
||||
log.warning("%s doesn't appear to be a valid hg directory; clobbering", dest)
|
||||
remove_path(dest)
|
||||
elif not os.path.exists(os.path.join(dest, ".hg", "sharedpath")):
|
||||
try:
|
||||
if autoPurge:
|
||||
purge(dest)
|
||||
return pull(repo, dest, update_dest=update_dest, branch=branch,
|
||||
revision=revision,
|
||||
mirrors=mirrors)
|
||||
except subprocess.CalledProcessError:
|
||||
log.warning("Error pulling changes into %s from %s; clobbering", dest, repo)
|
||||
log.debug("Exception:", exc_info=True)
|
||||
remove_path(dest)
|
||||
|
||||
# If that fails for any reason, and sharing is requested, we'll try to
|
||||
# update the shared repository, and then update the working directory from
|
||||
# that.
|
||||
if shareBase:
|
||||
sharedRepo = os.path.join(shareBase, get_repo_path(repo))
|
||||
dest_sharedPath = os.path.join(dest, '.hg', 'sharedpath')
|
||||
|
||||
if os.path.exists(sharedRepo):
|
||||
hgpath = path(sharedRepo, "default")
|
||||
|
||||
# Make sure that our default path is correct
|
||||
if hgpath != _make_absolute(repo):
|
||||
log.info("hg path isn't correct (%s should be %s); clobbering",
|
||||
hgpath, _make_absolute(repo))
|
||||
# we need to clobber both the shared checkout and the dest,
|
||||
# since hgrc needs to be in both places
|
||||
remove_path(sharedRepo)
|
||||
remove_path(dest)
|
||||
|
||||
if os.path.exists(dest_sharedPath):
|
||||
# Make sure that the sharedpath points to sharedRepo
|
||||
dest_sharedPath_data = os.path.normpath(
|
||||
open(dest_sharedPath).read())
|
||||
norm_sharedRepo = os.path.normpath(os.path.join(sharedRepo, '.hg'))
|
||||
if dest_sharedPath_data != norm_sharedRepo:
|
||||
# Clobber!
|
||||
log.info("We're currently shared from %s, but are being requested to pull from %s (%s); clobbering",
|
||||
dest_sharedPath_data, repo, norm_sharedRepo)
|
||||
remove_path(dest)
|
||||
|
||||
try:
|
||||
log.info("Updating shared repo")
|
||||
mercurial(repo, sharedRepo, branch=branch, revision=revision,
|
||||
update_dest=False, shareBase=None, clone_by_rev=clone_by_rev,
|
||||
mirrors=mirrors, bundles=bundles, autoPurge=False)
|
||||
if os.path.exists(dest):
|
||||
if autoPurge:
|
||||
purge(dest)
|
||||
return update(dest, branch=branch, revision=revision)
|
||||
|
||||
try:
|
||||
log.info("Trying to share %s to %s", sharedRepo, dest)
|
||||
return share(sharedRepo, dest, branch=branch, revision=revision)
|
||||
except subprocess.CalledProcessError:
|
||||
if not allowUnsharedLocalClones:
|
||||
# Re-raise the exception so it gets caught below.
|
||||
# We'll then clobber dest, and clone from original repo
|
||||
raise
|
||||
|
||||
log.warning("Error calling hg share from %s to %s;"
|
||||
"falling back to normal clone from shared repo",
|
||||
sharedRepo, dest)
|
||||
# Do a full local clone first, and then update to the
|
||||
# revision we want
|
||||
# This lets us use hardlinks for the local clone if the OS
|
||||
# supports it
|
||||
clone(sharedRepo, dest, update_dest=False,
|
||||
mirrors=mirrors, bundles=bundles)
|
||||
return update(dest, branch=branch, revision=revision)
|
||||
except subprocess.CalledProcessError:
|
||||
log.warning(
|
||||
"Error updating %s from sharedRepo (%s): ", dest, sharedRepo)
|
||||
log.debug("Exception:", exc_info=True)
|
||||
remove_path(dest)
|
||||
# end if shareBase
|
||||
|
||||
if not os.path.exists(os.path.dirname(dest)):
|
||||
os.makedirs(os.path.dirname(dest))
|
||||
|
||||
# Share isn't available or has failed, clone directly from the source
|
||||
return clone(repo, dest, branch, revision,
|
||||
update_dest=update_dest, mirrors=mirrors,
|
||||
bundles=bundles, clone_by_rev=clone_by_rev)
|
||||
|
||||
|
||||
def apply_and_push(localrepo, remote, changer, max_attempts=10,
|
||||
ssh_username=None, ssh_key=None, force=False):
|
||||
"""This function calls `changer' to make changes to the repo, and tries
|
||||
its hardest to get them to the origin repo. `changer' must be a
|
||||
callable object that receives two arguments: the directory of the local
|
||||
repository, and the attempt number. This function will push ALL
|
||||
changesets missing from remote."""
|
||||
assert callable(changer)
|
||||
branch = get_branch(localrepo)
|
||||
changer(localrepo, 1)
|
||||
for n in range(1, max_attempts + 1):
|
||||
new_revs = []
|
||||
try:
|
||||
new_revs = out(src=localrepo, remote=remote,
|
||||
ssh_username=ssh_username,
|
||||
ssh_key=ssh_key)
|
||||
if len(new_revs) < 1:
|
||||
raise HgUtilError("No revs to push")
|
||||
push(src=localrepo, remote=remote, ssh_username=ssh_username,
|
||||
ssh_key=ssh_key, force=force)
|
||||
return
|
||||
except subprocess.CalledProcessError, e:
|
||||
log.debug("Hit error when trying to push: %s" % str(e))
|
||||
if n == max_attempts:
|
||||
log.debug("Tried %d times, giving up" % max_attempts)
|
||||
for r in reversed(new_revs):
|
||||
run_cmd(['hg', '--config', 'extensions.mq=', 'strip', '-n',
|
||||
r[REVISION]], cwd=localrepo)
|
||||
raise HgUtilError("Failed to push")
|
||||
pull(remote, localrepo, update_dest=False,
|
||||
ssh_username=ssh_username, ssh_key=ssh_key)
|
||||
# After we successfully rebase or strip away heads the push is
|
||||
# is attempted again at the start of the loop
|
||||
try:
|
||||
run_cmd(['hg', '--config', 'ui.merge=internal:merge',
|
||||
'rebase'], cwd=localrepo)
|
||||
except subprocess.CalledProcessError, e:
|
||||
log.debug("Failed to rebase: %s" % str(e))
|
||||
update(localrepo, branch=branch)
|
||||
for r in reversed(new_revs):
|
||||
run_cmd(['hg', '--config', 'extensions.mq=', 'strip', '-n',
|
||||
r[REVISION]], cwd=localrepo)
|
||||
changer(localrepo, n + 1)
|
||||
|
||||
|
||||
def share(source, dest, branch=None, revision=None):
|
||||
"""Creates a new working directory in "dest" that shares history with
|
||||
"source" using Mercurial's share extension"""
|
||||
run_cmd(['hg', 'share', '-U', source, dest])
|
||||
return update(dest, branch=branch, revision=revision)
|
||||
|
||||
|
||||
def cleanOutgoingRevs(reponame, remote, username, sshKey):
|
||||
outgoingRevs = retry(out, kwargs=dict(src=reponame, remote=remote,
|
||||
ssh_username=username,
|
||||
ssh_key=sshKey))
|
||||
for r in reversed(outgoingRevs):
|
||||
run_cmd(['hg', '--config', 'extensions.mq=', 'strip', '-n',
|
||||
r[REVISION]], cwd=reponame)
|
||||
|
||||
|
||||
def path(src, name='default'):
|
||||
"""Returns the remote path associated with "name" """
|
||||
try:
|
||||
return get_output(['hg', 'path', name], cwd=src).strip()
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
|
||||
|
||||
def init(dest):
|
||||
"""Initializes an empty repo in `dest`"""
|
||||
run_cmd(['hg', 'init', dest])
|
||||
|
||||
|
||||
def unbundle(bundle, dest):
|
||||
"""Unbundles the bundle located at `bundle` into `dest`.
|
||||
|
||||
`bundle` can be a local file or remote url."""
|
||||
try:
|
||||
get_output(['hg', 'unbundle', bundle], cwd=dest, include_stderr=True)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def adjust_paths(dest, **paths):
|
||||
"""Adjusts paths in `dest`/.hg/hgrc so that names in `paths` are set to
|
||||
paths[name].
|
||||
|
||||
Note that any comments in the hgrc will be lost if changes are made to the
|
||||
file."""
|
||||
hgrc = os.path.join(dest, '.hg', 'hgrc')
|
||||
config = RawConfigParser()
|
||||
config.read(hgrc)
|
||||
|
||||
if not config.has_section('paths'):
|
||||
config.add_section('paths')
|
||||
|
||||
changed = False
|
||||
for path_name, path_value in paths.items():
|
||||
if (not config.has_option('paths', path_name) or
|
||||
config.get('paths', path_name) != path_value):
|
||||
changed = True
|
||||
config.set('paths', path_name, path_value)
|
||||
|
||||
if changed:
|
||||
config.write(open(hgrc, 'w'))
|
||||
|
||||
|
||||
def commit(dest, msg, user=None):
|
||||
cmd = ['hg', 'commit', '-m', msg]
|
||||
if user:
|
||||
cmd.extend(['-u', user])
|
||||
run_cmd(cmd, cwd=dest)
|
||||
return get_revision(dest)
|
||||
|
||||
|
||||
def tag(dest, tags, user=None, msg=None, rev=None, force=None):
|
||||
cmd = ['hg', 'tag']
|
||||
if user:
|
||||
cmd.extend(['-u', user])
|
||||
if msg:
|
||||
cmd.extend(['-m', msg])
|
||||
if rev:
|
||||
cmd.extend(['-r', rev])
|
||||
if force:
|
||||
cmd.append('-f')
|
||||
cmd.extend(tags)
|
||||
run_cmd(cmd, cwd=dest)
|
||||
return get_revision(dest)
|
@ -430,20 +430,6 @@ private:
|
||||
nsIPrincipal*
|
||||
GetSubjectPrincipal(JSContext* cx, nsresult* rv);
|
||||
|
||||
// Returns null if a principal cannot be found. Note that rv can be NS_OK
|
||||
// when this happens -- this means that there was no script. Callers MUST
|
||||
// pass in a non-null rv here.
|
||||
static nsIPrincipal*
|
||||
GetScriptPrincipal(JSScript* script, nsresult* rv);
|
||||
|
||||
// Returns null if a principal cannot be found. Note that rv can be NS_OK
|
||||
// when this happens -- this means that there was no script associated
|
||||
// with the function object, and no global object associated with the scope
|
||||
// of obj (the last object on its parent chain). Callers MUST pass in a
|
||||
// non-null rv here.
|
||||
static nsIPrincipal*
|
||||
GetFunctionObjectPrincipal(JSContext* cx, JS::Handle<JSObject*> obj, nsresult* rv);
|
||||
|
||||
/**
|
||||
* Check capability levels for an |aObj| that implements
|
||||
* nsISecurityCheckedComponent.
|
||||
|
@ -1584,25 +1584,7 @@ nsScriptSecurityManager::CheckFunctionAccess(JSContext *aCx, void *aFunObj,
|
||||
// This check is called for event handlers
|
||||
nsresult rv;
|
||||
JS::Rooted<JSObject*> rootedFunObj(aCx, static_cast<JSObject*>(aFunObj));
|
||||
nsIPrincipal* subject =
|
||||
GetFunctionObjectPrincipal(aCx, rootedFunObj, &rv);
|
||||
|
||||
// If subject is null, get a principal from the function object's scope.
|
||||
if (NS_SUCCEEDED(rv) && !subject)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
{
|
||||
JS_ASSERT(JS_ObjectIsFunction(aCx, rootedFunObj));
|
||||
JS::Rooted<JSFunction*> fun(aCx, JS_GetObjectFunction(rootedFunObj));
|
||||
JSScript *script = JS_GetFunctionScript(aCx, fun);
|
||||
|
||||
NS_ASSERTION(!script, "Null principal for non-native function!");
|
||||
}
|
||||
#endif
|
||||
|
||||
subject = doGetObjectPrincipal(rootedFunObj);
|
||||
}
|
||||
|
||||
nsIPrincipal* subject = doGetObjectPrincipal(rootedFunObj);
|
||||
if (!subject)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
@ -1929,77 +1911,6 @@ nsScriptSecurityManager::GetCodebasePrincipalInternal(nsIURI *aURI,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// static
|
||||
nsIPrincipal*
|
||||
nsScriptSecurityManager::GetScriptPrincipal(JSScript *script,
|
||||
nsresult* rv)
|
||||
{
|
||||
NS_PRECONDITION(rv, "Null out param");
|
||||
*rv = NS_OK;
|
||||
if (!script)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
JSPrincipals *jsp = JS_GetScriptPrincipals(script);
|
||||
if (!jsp) {
|
||||
*rv = NS_ERROR_FAILURE;
|
||||
NS_ERROR("Script compiled without principals!");
|
||||
return nullptr;
|
||||
}
|
||||
return nsJSPrincipals::get(jsp);
|
||||
}
|
||||
|
||||
// static
|
||||
nsIPrincipal*
|
||||
nsScriptSecurityManager::GetFunctionObjectPrincipal(JSContext *cx,
|
||||
JS::Handle<JSObject*> obj,
|
||||
nsresult *rv)
|
||||
{
|
||||
NS_PRECONDITION(rv, "Null out param");
|
||||
|
||||
*rv = NS_OK;
|
||||
|
||||
if (!JS_ObjectIsFunction(cx, obj))
|
||||
{
|
||||
// Protect against pseudo-functions (like SJOWs).
|
||||
nsIPrincipal *result = doGetObjectPrincipal(obj);
|
||||
if (!result)
|
||||
*rv = NS_ERROR_FAILURE;
|
||||
return result;
|
||||
}
|
||||
|
||||
JS::Rooted<JSFunction*> fun(cx, JS_GetObjectFunction(obj));
|
||||
JSScript *script = JS_GetFunctionScript(cx, fun);
|
||||
|
||||
if (!script)
|
||||
{
|
||||
// A native function: skip it in order to find its scripted caller.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!js::IsOriginalScriptFunction(fun))
|
||||
{
|
||||
// Here, obj is a cloned function object. In this case, the
|
||||
// clone's prototype may have been precompiled from brutally
|
||||
// shared chrome, or else it is a lambda or nested function.
|
||||
// The general case here is a function compiled against a
|
||||
// different scope than the one it is parented by at runtime,
|
||||
// hence the creation of a clone to carry the correct scope
|
||||
// chain linkage.
|
||||
//
|
||||
// Since principals follow scope, we must get the object
|
||||
// principal from the clone's scope chain. There are no
|
||||
// reliable principals compiled into the function itself.
|
||||
|
||||
nsIPrincipal *result = doGetObjectPrincipal(obj);
|
||||
if (!result)
|
||||
*rv = NS_ERROR_FAILURE;
|
||||
return result;
|
||||
}
|
||||
|
||||
return GetScriptPrincipal(script, rv);
|
||||
}
|
||||
|
||||
nsIPrincipal*
|
||||
nsScriptSecurityManager::GetSubjectPrincipal(JSContext *cx,
|
||||
nsresult* rv)
|
||||
|
@ -2116,6 +2116,10 @@ public:
|
||||
return mStyleSheetChangeEventsEnabled;
|
||||
}
|
||||
|
||||
void ObsoleteSheet(nsIURI *aSheetURI, mozilla::ErrorResult& rv);
|
||||
|
||||
void ObsoleteSheet(const nsAString& aSheetURI, mozilla::ErrorResult& rv);
|
||||
|
||||
virtual nsHTMLDocument* AsHTMLDocument() { return nullptr; }
|
||||
|
||||
virtual JSObject* WrapObject(JSContext *aCx,
|
||||
|
@ -9449,6 +9449,30 @@ nsDocument::CaretPositionFromPoint(float aX, float aY, nsISupports** aCaretPos)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsIDocument::ObsoleteSheet(nsIURI *aSheetURI, ErrorResult& rv)
|
||||
{
|
||||
nsresult res = CSSLoader()->ObsoleteSheet(aSheetURI);
|
||||
if (NS_FAILED(res)) {
|
||||
rv.Throw(res);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsIDocument::ObsoleteSheet(const nsAString& aSheetURI, ErrorResult& rv)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult res = NS_NewURI(getter_AddRefs(uri), aSheetURI);
|
||||
if (NS_FAILED(res)) {
|
||||
rv.Throw(res);
|
||||
return;
|
||||
}
|
||||
res = CSSLoader()->ObsoleteSheet(uri);
|
||||
if (NS_FAILED(res)) {
|
||||
rv.Throw(res);
|
||||
}
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// Singleton class to manage the list of fullscreen documents which are the
|
||||
|
@ -636,6 +636,8 @@ MOCHITEST_FILES_C= \
|
||||
file_CSP_bug885433_blocks.html \
|
||||
file_CSP_bug885433_blocks.html^headers^ \
|
||||
test_bug890580.html \
|
||||
test_declare_stylesheet_obsolete.html \
|
||||
variable_style_sheet.sjs \
|
||||
$(NULL)
|
||||
|
||||
# OOP tests don't work on Windows (bug 763081) or native-fennec
|
||||
|
94
content/base/test/test_declare_stylesheet_obsolete.html
Normal file
@ -0,0 +1,94 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=713564
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 713564</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
|
||||
<!-- Load the variable stylesheet, which changes the color of #content. -->
|
||||
<link rel="stylesheet" type="text/css" href="variable_style_sheet.sjs"/>
|
||||
|
||||
<script type="application/javascript">
|
||||
|
||||
function insertLinkToVarSSAndRun(callback) {
|
||||
var ss = document.createElement("link");
|
||||
ss.rel = "stylesheet";
|
||||
ss.type = "text/css";
|
||||
ss.href = "variable_style_sheet.sjs";
|
||||
document.getElementsByTagName("head")[0].appendChild(ss);
|
||||
ss.addEventListener("load", callback);
|
||||
}
|
||||
|
||||
/** Test for Bug 713564 **/
|
||||
|
||||
// Then you link to that sheet, remove the link from the DOM, insert a new link to
|
||||
// the same url and check that there was no new access, then call our new method,
|
||||
// insert _another_ <link> to the same url, and check that this time we hit the
|
||||
// server.
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function do_test() {
|
||||
var var_sheet = document.getElementsByTagName("link")[1];
|
||||
var head = document.getElementsByTagName("head")[0];
|
||||
var content = document.getElementById("content");
|
||||
var var_sheet_url = var_sheet.href;
|
||||
|
||||
var previousBgColor = window.getComputedStyle(content).
|
||||
getPropertyValue("background-color");
|
||||
var_sheet.parentNode.removeChild(var_sheet);
|
||||
insertLinkToVarSSAndRun(function() {
|
||||
is(window.getComputedStyle(content).getPropertyValue("background-color"),
|
||||
previousBgColor,
|
||||
"Sheet should still be the same.");
|
||||
|
||||
// Obsolete sheet
|
||||
try {
|
||||
SpecialPowers.wrap(document).obsoleteSheet(var_sheet_url);
|
||||
} catch (e) {
|
||||
ok(false, "obsoleteSheet should not raise an error on valid URL.");
|
||||
}
|
||||
insertLinkToVarSSAndRun(function() {
|
||||
isnot(window.getComputedStyle(content).getPropertyValue("background-color"),
|
||||
previousBgColor,
|
||||
"Sheet should change after obsoleted and reinserted.");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
// obsoleteSheet should throw with invalid input:
|
||||
try {
|
||||
SpecialPowers.wrap(document).obsoleteSheet("");
|
||||
ok(false, "obsoleteSheet should throw with empty string.");
|
||||
} catch (e) {
|
||||
ok(true, "obsoleteSheet throws with empty string.");
|
||||
}
|
||||
try {
|
||||
SpecialPowers.wrap(document).obsoleteSheet("foo");
|
||||
ok(false, "obsoleteSheet should throw with invalid URL.");
|
||||
} catch (e) {
|
||||
ok(true, "obsoleteSheet throws with invalid URL.");
|
||||
}
|
||||
try {
|
||||
SpecialPowers.wrap(document).obsoleteSheet("http://www.mozilla.org");
|
||||
ok(true, "obsoleteSheet should not throw with valid URL.");
|
||||
} catch (e) {
|
||||
ok(false, "obsoleteSheet throws with valid URL.");
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="do_test();">
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=713564">Mozilla Bug 713564</a>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
<br>
|
||||
<br>
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
19
content/base/test/variable_style_sheet.sjs
Normal file
@ -0,0 +1,19 @@
|
||||
function handleRequest(request, response)
|
||||
{
|
||||
response.setHeader("Cache-Control", "no-cache", false);
|
||||
response.setHeader("Content-Type", "text/css", false);
|
||||
|
||||
var accessCount;
|
||||
var state = getState("varSSAccessCount");
|
||||
if (!state) {
|
||||
setState("varSSAccessCount", "0");
|
||||
accessCount = 0;
|
||||
} else {
|
||||
setState("varSSAccessCount", (parseInt(state) + 1).toString());
|
||||
accessCount = parseInt(state) + 1;
|
||||
}
|
||||
|
||||
response.write("#content { background-color: rgb(" +
|
||||
(accessCount % 256) +
|
||||
", 0, 0); }");
|
||||
}
|
@ -14,9 +14,6 @@
|
||||
#include "nsWrapperCacheInlines.h"
|
||||
#include "mozilla/dom/HTMLPropertiesCollectionBinding.h"
|
||||
|
||||
DOMCI_DATA(HTMLPropertiesCollection, mozilla::dom::HTMLPropertiesCollection)
|
||||
DOMCI_DATA(PropertyNodeList, mozilla::dom::PropertyNodeList)
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
|
@ -357,12 +357,35 @@ nsXBLBinding::GenerateAnonymousContent()
|
||||
for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
|
||||
mDefaultInsertionPoint->AppendInsertedChild(child, bindingManager);
|
||||
}
|
||||
} else if (!mInsertionPoints.IsEmpty()) {
|
||||
} else {
|
||||
// It is odd to come into this code if mInsertionPoints is not empty, but
|
||||
// we need to make sure to do the compatibility hack below if the bound
|
||||
// node has any non <xul:template> or <xul:observer> children.
|
||||
ExplicitChildIterator iter(mBoundElement);
|
||||
for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
|
||||
XBLChildrenElement* point = FindInsertionPointForInternal(child);
|
||||
if (point) {
|
||||
point->AppendInsertedChild(child, bindingManager);
|
||||
} else {
|
||||
nsINodeInfo *ni = child->NodeInfo();
|
||||
if (ni->NamespaceID() != kNameSpaceID_XUL ||
|
||||
(!ni->Equals(nsGkAtoms::_template) &&
|
||||
!ni->Equals(nsGkAtoms::observer))) {
|
||||
// Compatibility hack. For some reason the original XBL
|
||||
// implementation dropped the content of a binding if any child of
|
||||
// the bound element didn't match any of the <children> in the
|
||||
// binding. This became a pseudo-API that we have to maintain.
|
||||
|
||||
// Undo InstallAnonymousContent
|
||||
UninstallAnonymousContent(doc, mContent);
|
||||
|
||||
// Clear out our children elements to avoid dangling references.
|
||||
ClearInsertionPoints();
|
||||
|
||||
// Pretend as though there was no content in the binding.
|
||||
mContent = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -147,10 +147,10 @@ $(binding_header_files): .BindingGen
|
||||
$(binding_cpp_files): .BindingGen
|
||||
|
||||
# $(binding_dependency_trackers) pick up additional dependencies via .pp files
|
||||
# The rule: just brings the tracker up to date, if it's out of date, so that
|
||||
# we'll know that we have to redo binding generation and flag this prerequisite
|
||||
# there as being newer than the bindinggen target.
|
||||
$(binding_dependency_trackers):
|
||||
# Just bring it up to date, if it's out of date, so that we'll know that
|
||||
# we have to redo binding generation and flag this prerequisite there as
|
||||
# being newer than the bindinggen target.
|
||||
@$(TOUCH) $@
|
||||
|
||||
$(globalgen_targets): ParserResults.pkl
|
||||
@ -179,34 +179,34 @@ $(CACHE_DIR)/.done:
|
||||
$(MKDIR) -p $(CACHE_DIR)
|
||||
@$(TOUCH) $@
|
||||
|
||||
# Running GlobalGen.py updates ParserResults.pkl as a side-effect
|
||||
ParserResults.pkl: $(globalgen_dependencies)
|
||||
# Running GlobalGen.py updates ParserResults.pkl as a side-effect
|
||||
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
|
||||
$(PLY_INCLUDE) -I$(srcdir)/parser \
|
||||
$(srcdir)/GlobalGen.py $(srcdir)/Bindings.conf . \
|
||||
--cachedir=$(CACHE_DIR) \
|
||||
$(all_webidl_files)
|
||||
$(PLY_INCLUDE) -I$(srcdir)/parser \
|
||||
$(srcdir)/GlobalGen.py $(srcdir)/Bindings.conf . \
|
||||
--cachedir=$(CACHE_DIR) \
|
||||
$(all_webidl_files)
|
||||
|
||||
# Make sure .deps actually exists, since we'll try to write to it from
|
||||
# BindingGen.py but we're typically running in the export phase, which is
|
||||
# before anyone has bothered creating .deps.
|
||||
# Then, pass our long lists through files to try to avoid blowing out the
|
||||
# command line.
|
||||
# Next, BindingGen.py will examine the changed dependency list to figure out
|
||||
# what it really needs to regenerate.
|
||||
# Finally, touch the .BindingGen file so that we don't have to keep redoing
|
||||
# all that until something else actually changes.
|
||||
.BindingGen: $(bindinggen_dependencies) $(binding_dependency_trackers)
|
||||
# Make sure .deps actually exists, since we'll try to write to it from
|
||||
# BindingGen.py but we're typically running in the export phase, which
|
||||
# is before anyone has bothered creating .deps.
|
||||
$(MKDIR) -p .deps
|
||||
# Pass our long lists through files to try to avoid blowing
|
||||
# out the command line
|
||||
echo $(all_webidl_files) > .all-webidl-file-list
|
||||
echo $? > .changed-dependency-list
|
||||
# BindingGen.py will examine the changed dependency list to figure out
|
||||
# what it really needs to regenerate.
|
||||
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
|
||||
$(PLY_INCLUDE) -I$(srcdir)/parser \
|
||||
$(srcdir)/BindingGen.py \
|
||||
$(srcdir)/Bindings.conf \
|
||||
$(CURDIR) \
|
||||
.all-webidl-file-list \
|
||||
.changed-dependency-list
|
||||
# Now touch the .BindingGen file so that we don't have to keep redoing
|
||||
# all that until something else actually changes.
|
||||
$(PLY_INCLUDE) -I$(srcdir)/parser \
|
||||
$(srcdir)/BindingGen.py \
|
||||
$(srcdir)/Bindings.conf \
|
||||
$(CURDIR) \
|
||||
.all-webidl-file-list \
|
||||
.changed-dependency-list
|
||||
@$(TOUCH) $@
|
||||
|
||||
GARBAGE += \
|
||||
|
@ -18,6 +18,7 @@
|
||||
interface StyleSheetList;
|
||||
interface WindowProxy;
|
||||
interface nsISupports;
|
||||
interface URI;
|
||||
|
||||
enum VisibilityState { "hidden", "visible" };
|
||||
|
||||
@ -316,6 +317,11 @@ partial interface Document {
|
||||
|
||||
[ChromeOnly]
|
||||
attribute boolean styleSheetChangeEventsEnabled;
|
||||
|
||||
[ChromeOnly, Throws]
|
||||
void obsoleteSheet(URI sheetURI);
|
||||
[ChromeOnly, Throws]
|
||||
void obsoleteSheet(DOMString sheetURI);
|
||||
};
|
||||
|
||||
// Extension to give chrome JS the ability to determine when a document was
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include "SkPath.h"
|
||||
|
||||
#define kMaxQuadSubdivide 5
|
||||
#define kMaxCubicSubdivide 4
|
||||
#define kMaxCubicSubdivide 7
|
||||
|
||||
static inline bool degenerate_vector(const SkVector& v) {
|
||||
return !SkPoint::CanNormalize(v.fX, v.fY);
|
||||
@ -304,12 +304,8 @@ DRAW_LINE:
|
||||
bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius,
|
||||
&normalBC, &unitNormalBC);
|
||||
#ifndef SK_IGNORE_CUBIC_STROKE_FIX
|
||||
if (subDivide <= 0) {
|
||||
if (degenerateBC) {
|
||||
goto DRAW_LINE;
|
||||
} else {
|
||||
goto DRAW_CUBIC;
|
||||
}
|
||||
if (--subDivide < 0) {
|
||||
goto DRAW_LINE;
|
||||
}
|
||||
#endif
|
||||
if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) ||
|
||||
@ -330,9 +326,6 @@ DRAW_LINE:
|
||||
// normals for CD
|
||||
this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide);
|
||||
} else {
|
||||
#ifndef SK_IGNORE_CUBIC_STROKE_FIX
|
||||
DRAW_CUBIC:
|
||||
#endif
|
||||
SkVector normalB, normalC;
|
||||
|
||||
// need normals to inset/outset the off-curve pts B and C
|
||||
|
@ -217,6 +217,14 @@ NS_GFX_(bool) NS_ColorNameToRGB(const nsAString& aColorName, nscolor* aResult)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns kColorNames, an array of all possible color names, and sets
|
||||
// *aSizeArray to the size of that array. Do NOT call free() on this array.
|
||||
NS_GFX_(const char * const *) NS_AllColorNames(size_t *aSizeArray)
|
||||
{
|
||||
*aSizeArray = ArrayLength(kColorNames);
|
||||
return kColorNames;
|
||||
}
|
||||
|
||||
// Macro to blend two colors
|
||||
//
|
||||
// equivalent to target = (bg*(255-fgalpha) + fg*fgalpha)/255
|
||||
|
@ -66,6 +66,10 @@ NS_GFX_(bool) NS_LooseHexToRGB(const nsString& aBuf, nscolor* aResult);
|
||||
// otherwise return false.
|
||||
NS_GFX_(bool) NS_ColorNameToRGB(const nsAString& aBuf, nscolor* aResult);
|
||||
|
||||
// Returns an array of all possible color names, and sets
|
||||
// *aSizeArray to the size of that array. Do NOT call |free()| on this array.
|
||||
NS_GFX_(const char * const *) NS_AllColorNames(size_t *aSizeArray);
|
||||
|
||||
// function to convert from HSL color space to RGB color space
|
||||
// the float parameters are all expected to be in the range 0-1
|
||||
NS_GFX_(nscolor) NS_HSL2RGB(float h, float s, float l);
|
||||
|
264
image/src/FrameAnimator.cpp
Normal file
@ -0,0 +1,264 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "FrameAnimator.h"
|
||||
|
||||
#include "imgIContainer.h"
|
||||
|
||||
using namespace mozilla::image;
|
||||
using namespace mozilla;
|
||||
|
||||
FrameAnimator::FrameAnimator(FrameBlender& aFrameBlender)
|
||||
: mCurrentAnimationFrameIndex(0)
|
||||
, mLoopCount(-1)
|
||||
, mFrameBlender(aFrameBlender)
|
||||
, mAnimationMode(imgIContainer::kNormalAnimMode)
|
||||
, mDoneDecoding(false)
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t
|
||||
FrameAnimator::GetSingleLoopTime() const
|
||||
{
|
||||
// If we aren't done decoding, we don't know the image's full play time.
|
||||
if (!mDoneDecoding) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If we're not looping, a single loop time has no meaning
|
||||
if (mAnimationMode != imgIContainer::kNormalAnimMode) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t looptime = 0;
|
||||
for (uint32_t i = 0; i < mFrameBlender.GetNumFrames(); ++i) {
|
||||
int32_t timeout = mFrameBlender.RawGetFrame(i)->GetTimeout();
|
||||
if (timeout > 0) {
|
||||
looptime += static_cast<uint32_t>(timeout);
|
||||
} else {
|
||||
// If we have a frame that never times out, we're probably in an error
|
||||
// case, but let's handle it more gracefully.
|
||||
NS_WARNING("Negative frame timeout - how did this happen?");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return looptime;
|
||||
}
|
||||
|
||||
TimeStamp
|
||||
FrameAnimator::GetCurrentImgFrameEndTime() const
|
||||
{
|
||||
imgFrame* currentFrame = mFrameBlender.RawGetFrame(mCurrentAnimationFrameIndex);
|
||||
TimeStamp currentFrameTime = mCurrentAnimationFrameTime;
|
||||
int64_t timeout = currentFrame->GetTimeout();
|
||||
|
||||
if (timeout < 0) {
|
||||
// We need to return a sentinel value in this case, because our logic
|
||||
// doesn't work correctly if we have a negative timeout value. The reason
|
||||
// this positive infinity was chosen was because it works with the loop in
|
||||
// RequestRefresh() below.
|
||||
return TimeStamp() +
|
||||
TimeDuration::FromMilliseconds(static_cast<double>(UINT64_MAX));
|
||||
}
|
||||
|
||||
TimeDuration durationOfTimeout =
|
||||
TimeDuration::FromMilliseconds(static_cast<double>(timeout));
|
||||
TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
|
||||
|
||||
return currentFrameEndTime;
|
||||
}
|
||||
|
||||
FrameAnimator::RefreshResult
|
||||
FrameAnimator::AdvanceFrame(TimeStamp aTime)
|
||||
{
|
||||
NS_ASSERTION(aTime <= TimeStamp::Now(),
|
||||
"Given time appears to be in the future");
|
||||
|
||||
uint32_t currentFrameIndex = mCurrentAnimationFrameIndex;
|
||||
uint32_t nextFrameIndex = currentFrameIndex + 1;
|
||||
uint32_t timeout = 0;
|
||||
|
||||
RefreshResult ret;
|
||||
|
||||
// If we're done decoding, we know we've got everything we're going to get.
|
||||
// If we aren't, we only display fully-downloaded frames; everything else
|
||||
// gets delayed.
|
||||
bool needToWait = !mDoneDecoding &&
|
||||
mFrameBlender.RawGetFrame(nextFrameIndex) &&
|
||||
!mFrameBlender.RawGetFrame(nextFrameIndex)->ImageComplete();
|
||||
|
||||
if (needToWait) {
|
||||
// Uh oh, the frame we want to show is currently being decoded (partial)
|
||||
// Wait until the next refresh driver tick and try again
|
||||
return ret;
|
||||
} else {
|
||||
// If we're done decoding the next frame, go ahead and display it now and
|
||||
// reinit with the next frame's delay time.
|
||||
if (mFrameBlender.GetNumFrames() == nextFrameIndex) {
|
||||
// End of Animation, unless we are looping forever
|
||||
|
||||
// If animation mode is "loop once", it's time to stop animating
|
||||
if (mAnimationMode == imgIContainer::kLoopOnceAnimMode || mLoopCount == 0) {
|
||||
ret.animationFinished = true;
|
||||
}
|
||||
|
||||
nextFrameIndex = 0;
|
||||
|
||||
if (mLoopCount > 0) {
|
||||
mLoopCount--;
|
||||
}
|
||||
|
||||
// If we're done, exit early.
|
||||
if (ret.animationFinished) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
timeout = mFrameBlender.GetFrame(nextFrameIndex)->GetTimeout();
|
||||
}
|
||||
|
||||
// Bad data
|
||||
if (!(timeout > 0)) {
|
||||
ret.animationFinished = true;
|
||||
ret.error = true;
|
||||
}
|
||||
|
||||
if (nextFrameIndex == 0) {
|
||||
ret.dirtyRect = mFirstFrameRefreshArea;
|
||||
} else {
|
||||
// Change frame
|
||||
if (!mFrameBlender.DoBlend(&ret.dirtyRect, currentFrameIndex, nextFrameIndex)) {
|
||||
// something went wrong, move on to next
|
||||
NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
|
||||
mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(true);
|
||||
mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();
|
||||
mCurrentAnimationFrameIndex = nextFrameIndex;
|
||||
|
||||
ret.error = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(false);
|
||||
}
|
||||
|
||||
mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();
|
||||
|
||||
// If we can get closer to the current time by a multiple of the image's loop
|
||||
// time, we should.
|
||||
uint32_t loopTime = GetSingleLoopTime();
|
||||
if (loopTime > 0) {
|
||||
TimeDuration delay = aTime - mCurrentAnimationFrameTime;
|
||||
if (delay.ToMilliseconds() > loopTime) {
|
||||
// Explicitly use integer division to get the floor of the number of
|
||||
// loops.
|
||||
uint32_t loops = static_cast<uint32_t>(delay.ToMilliseconds()) / loopTime;
|
||||
mCurrentAnimationFrameTime += TimeDuration::FromMilliseconds(loops * loopTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Set currentAnimationFrameIndex at the last possible moment
|
||||
mCurrentAnimationFrameIndex = nextFrameIndex;
|
||||
|
||||
// If we're here, we successfully advanced the frame.
|
||||
ret.frameAdvanced = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
FrameAnimator::RefreshResult
|
||||
FrameAnimator::RequestRefresh(const mozilla::TimeStamp& aTime)
|
||||
{
|
||||
// only advance the frame if the current time is greater than or
|
||||
// equal to the current frame's end time.
|
||||
TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime();
|
||||
|
||||
// By default, an empty RefreshResult.
|
||||
RefreshResult ret;
|
||||
|
||||
while (currentFrameEndTime <= aTime) {
|
||||
TimeStamp oldFrameEndTime = currentFrameEndTime;
|
||||
|
||||
RefreshResult frameRes = AdvanceFrame(aTime);
|
||||
|
||||
// Accumulate our result for returning to callers.
|
||||
ret.Accumulate(frameRes);
|
||||
|
||||
currentFrameEndTime = GetCurrentImgFrameEndTime();
|
||||
|
||||
// if we didn't advance a frame, and our frame end time didn't change,
|
||||
// then we need to break out of this loop & wait for the frame(s)
|
||||
// to finish downloading
|
||||
if (!frameRes.frameAdvanced && (currentFrameEndTime == oldFrameEndTime)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::ResetAnimation()
|
||||
{
|
||||
mCurrentAnimationFrameIndex = 0;
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::SetDoneDecoding(bool aDone)
|
||||
{
|
||||
mDoneDecoding = aDone;
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::SetAnimationMode(uint16_t aAnimationMode)
|
||||
{
|
||||
mAnimationMode = aAnimationMode;
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::InitAnimationFrameTimeIfNecessary()
|
||||
{
|
||||
if (mCurrentAnimationFrameTime.IsNull()) {
|
||||
mCurrentAnimationFrameTime = TimeStamp::Now();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::SetAnimationFrameTime(const TimeStamp& aTime)
|
||||
{
|
||||
mCurrentAnimationFrameTime = aTime;
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::SetFirstFrameRefreshArea(const nsIntRect& aRect)
|
||||
{
|
||||
mFirstFrameRefreshArea = aRect;
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::UnionFirstFrameRefreshArea(const nsIntRect& aRect)
|
||||
{
|
||||
mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, aRect);
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::SetLoopCount(int loopcount)
|
||||
{
|
||||
mLoopCount = loopcount;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
FrameAnimator::GetCurrentAnimationFrameIndex() const
|
||||
{
|
||||
return mCurrentAnimationFrameIndex;
|
||||
}
|
||||
|
||||
nsIntRect
|
||||
FrameAnimator::GetFirstFrameRefreshArea() const
|
||||
{
|
||||
return mFirstFrameRefreshArea;
|
||||
}
|
||||
|
||||
|
178
image/src/FrameAnimator.h
Normal file
@ -0,0 +1,178 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_imagelib_FrameAnimator_h_
|
||||
#define mozilla_imagelib_FrameAnimator_h_
|
||||
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "FrameBlender.h"
|
||||
#include "nsRect.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class FrameAnimator
|
||||
{
|
||||
public:
|
||||
FrameAnimator(FrameBlender& aBlender);
|
||||
|
||||
/**
|
||||
* Return value from RequestRefresh. Tells callers what happened in that call
|
||||
* to RequestRefresh.
|
||||
*/
|
||||
struct RefreshResult
|
||||
{
|
||||
// The dirty rectangle to be re-drawn after this RequestRefresh().
|
||||
nsIntRect dirtyRect;
|
||||
|
||||
// Whether any frame changed, and hence the dirty rect was set.
|
||||
bool frameAdvanced : 1;
|
||||
|
||||
// Whether the animation has finished playing.
|
||||
bool animationFinished : 1;
|
||||
|
||||
// Whether an error has occurred when trying to advance a frame. Note that
|
||||
// errors do not, on their own, end the animation.
|
||||
bool error : 1;
|
||||
|
||||
RefreshResult()
|
||||
: frameAdvanced(false)
|
||||
, animationFinished(false)
|
||||
, error(false)
|
||||
{}
|
||||
|
||||
void Accumulate(const RefreshResult& other)
|
||||
{
|
||||
frameAdvanced = frameAdvanced || other.frameAdvanced;
|
||||
animationFinished = animationFinished || other.animationFinished;
|
||||
error = error || other.error;
|
||||
dirtyRect = dirtyRect.Union(other.dirtyRect);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Re-evaluate what frame we're supposed to be on, and do whatever blending
|
||||
* is necessary to get us to that frame.
|
||||
*
|
||||
* Returns the result of that blending, including whether the current frame
|
||||
* changed and what the resulting dirty rectangle is.
|
||||
*/
|
||||
RefreshResult RequestRefresh(const mozilla::TimeStamp& aTime);
|
||||
|
||||
/**
|
||||
* Call when this image is finished decoding so we know that there aren't any
|
||||
* more frames coming.
|
||||
*/
|
||||
void SetDoneDecoding(bool aDone);
|
||||
|
||||
/**
|
||||
* Call when you need to re-start animating. Ensures we start from the first
|
||||
* frame.
|
||||
*/
|
||||
void ResetAnimation();
|
||||
|
||||
/**
|
||||
* Number of times to loop the image.
|
||||
* @note -1 means forever.
|
||||
*/
|
||||
void SetLoopCount(int32_t aLoopCount);
|
||||
|
||||
/**
|
||||
* The animation mode of the image.
|
||||
*
|
||||
* Constants defined in imgIContainer.idl.
|
||||
*/
|
||||
void SetAnimationMode(uint16_t aAnimationMode);
|
||||
|
||||
/**
|
||||
* Set the area to refresh when we loop around to the first frame.
|
||||
*/
|
||||
void SetFirstFrameRefreshArea(const nsIntRect& aRect);
|
||||
|
||||
/**
|
||||
* Union the area to refresh when we loop around to the first frame with this
|
||||
* rect.
|
||||
*/
|
||||
void UnionFirstFrameRefreshArea(const nsIntRect& aRect);
|
||||
|
||||
/**
|
||||
* If the animation frame time has not yet been set, set it to
|
||||
* TimeStamp::Now().
|
||||
*/
|
||||
void InitAnimationFrameTimeIfNecessary();
|
||||
|
||||
/**
|
||||
* Set the animation frame time to @aTime.
|
||||
*/
|
||||
void SetAnimationFrameTime(const TimeStamp& aTime);
|
||||
|
||||
/**
|
||||
* The current frame we're on, from 0 to (numFrames - 1).
|
||||
*/
|
||||
uint32_t GetCurrentAnimationFrameIndex() const;
|
||||
|
||||
/**
|
||||
* Get the area we refresh when we loop around to the first frame.
|
||||
*/
|
||||
nsIntRect GetFirstFrameRefreshArea() const;
|
||||
|
||||
private: // methods
|
||||
/**
|
||||
* Gets the length of a single loop of this image, in milliseconds.
|
||||
*
|
||||
* If this image is not finished decoding, is not animated, or it is animated
|
||||
* but does not loop, returns 0.
|
||||
*/
|
||||
uint32_t GetSingleLoopTime() const;
|
||||
|
||||
/**
|
||||
* Advances the animation. Typically, this will advance a single frame, but it
|
||||
* may advance multiple frames. This may happen if we have infrequently
|
||||
* "ticking" refresh drivers (e.g. in background tabs), or extremely short-
|
||||
* lived animation frames.
|
||||
*
|
||||
* @param aTime the time that the animation should advance to. This will
|
||||
* typically be <= TimeStamp::Now().
|
||||
*
|
||||
* @returns a RefreshResult that shows whether the frame was successfully
|
||||
* advanced, and its resulting dirty rect.
|
||||
*/
|
||||
RefreshResult AdvanceFrame(mozilla::TimeStamp aTime);
|
||||
|
||||
/**
|
||||
* Get the time the frame we're currently displaying is supposed to end.
|
||||
*
|
||||
* In the error case, returns an "infinity" timestamp.
|
||||
*/
|
||||
mozilla::TimeStamp GetCurrentImgFrameEndTime() const;
|
||||
|
||||
private: // data
|
||||
//! Area of the first frame that needs to be redrawn on subsequent loops.
|
||||
nsIntRect mFirstFrameRefreshArea;
|
||||
|
||||
//! the time that the animation advanced to the current frame
|
||||
TimeStamp mCurrentAnimationFrameTime;
|
||||
|
||||
//! The current frame index we're on. 0 to (numFrames - 1).
|
||||
uint32_t mCurrentAnimationFrameIndex;
|
||||
|
||||
//! number of loops remaining before animation stops (-1 no stop)
|
||||
int32_t mLoopCount;
|
||||
|
||||
//! All the frames of the image, shared with our owner
|
||||
FrameBlender& mFrameBlender;
|
||||
|
||||
//! The animation mode of this image. Constants defined in imgIContainer.
|
||||
uint16_t mAnimationMode;
|
||||
|
||||
//! Whether this image is done being decoded.
|
||||
bool mDoneDecoding;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* mozilla_imagelib_FrameAnimator_h_ */
|
@ -24,6 +24,7 @@
|
||||
#include "nsIThreadPool.h"
|
||||
#include "nsXPCOMCIDInternal.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "FrameAnimator.h"
|
||||
|
||||
#include "nsPNGDecoder.h"
|
||||
#include "nsGIFDecoder2.h"
|
||||
@ -390,7 +391,6 @@ RasterImage::RasterImage(imgStatusTracker* aStatusTracker,
|
||||
mFrameDecodeFlags(DECODE_FLAGS_DEFAULT),
|
||||
mMultipartDecodedFrame(nullptr),
|
||||
mAnim(nullptr),
|
||||
mLoopCount(-1),
|
||||
mLockCount(0),
|
||||
mDecodeCount(0),
|
||||
#ifdef DEBUG
|
||||
@ -528,135 +528,6 @@ RasterImage::Init(const char* aMimeType,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
RasterImage::GetSingleLoopTime() const
|
||||
{
|
||||
if (!mAnim) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If we aren't done decoding, we don't know the image's full play time.
|
||||
if (!mHasBeenDecoded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If we're not looping, a single loop time has no meaning
|
||||
if (mLoopCount == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t looptime = 0;
|
||||
for (uint32_t i = 0; i < GetNumFrames(); ++i) {
|
||||
int32_t timeout = mFrameBlender.RawGetFrame(i)->GetTimeout();
|
||||
if (timeout > 0) {
|
||||
looptime += static_cast<uint32_t>(timeout);
|
||||
} else {
|
||||
// If we have a frame that never times out, we're probably in an error
|
||||
// case, but let's handle it more gracefully.
|
||||
NS_WARNING("Negative frame timeout - how did this happen?");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return looptime;
|
||||
}
|
||||
|
||||
bool
|
||||
RasterImage::AdvanceFrame(TimeStamp aTime, nsIntRect* aDirtyRect)
|
||||
{
|
||||
NS_ASSERTION(aTime <= TimeStamp::Now(),
|
||||
"Given time appears to be in the future");
|
||||
|
||||
uint32_t currentFrameIndex = mAnim->currentAnimationFrameIndex;
|
||||
uint32_t nextFrameIndex = mAnim->currentAnimationFrameIndex + 1;
|
||||
uint32_t timeout = 0;
|
||||
|
||||
// Figure out if we have the next full frame. This is more complicated than
|
||||
// just checking GetNumFrames() because decoders append their frames
|
||||
// before they're filled in.
|
||||
NS_ABORT_IF_FALSE(mDecoder || nextFrameIndex <= GetNumFrames(),
|
||||
"How did we get 2 indices too far by incrementing?");
|
||||
|
||||
// If we don't have a decoder, we know we've got everything we're going to
|
||||
// get. If we do, we only display fully-downloaded frames; everything else
|
||||
// gets delayed.
|
||||
bool haveFullNextFrame = (mMultipart && mBytesDecoded == 0) || !mDecoder ||
|
||||
nextFrameIndex < mDecoder->GetCompleteFrameCount();
|
||||
|
||||
// If we're done decoding the next frame, go ahead and display it now and
|
||||
// reinit with the next frame's delay time.
|
||||
if (haveFullNextFrame) {
|
||||
if (GetNumFrames() == nextFrameIndex) {
|
||||
// End of Animation, unless we are looping forever
|
||||
|
||||
// If animation mode is "loop once", it's time to stop animating
|
||||
if (mAnimationMode == kLoopOnceAnimMode || mLoopCount == 0) {
|
||||
mAnimationFinished = true;
|
||||
EvaluateAnimation();
|
||||
}
|
||||
|
||||
nextFrameIndex = 0;
|
||||
|
||||
if (mLoopCount > 0) {
|
||||
mLoopCount--;
|
||||
}
|
||||
|
||||
if (!mAnimating) {
|
||||
// break out early if we are actually done animating
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
timeout = mFrameBlender.GetFrame(nextFrameIndex)->GetTimeout();
|
||||
|
||||
} else {
|
||||
// Uh oh, the frame we want to show is currently being decoded (partial)
|
||||
// Wait until the next refresh driver tick and try again
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(timeout > 0)) {
|
||||
mAnimationFinished = true;
|
||||
EvaluateAnimation();
|
||||
}
|
||||
|
||||
if (nextFrameIndex == 0) {
|
||||
*aDirtyRect = mAnim->firstFrameRefreshArea;
|
||||
} else {
|
||||
// Change frame
|
||||
if (!mFrameBlender.DoBlend(aDirtyRect, currentFrameIndex, nextFrameIndex)) {
|
||||
// something went wrong, move on to next
|
||||
NS_WARNING("RasterImage::AdvanceFrame(): Compositing of frame failed");
|
||||
mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(true);
|
||||
mAnim->currentAnimationFrameTime = GetCurrentImgFrameEndTime();
|
||||
mAnim->currentAnimationFrameIndex = nextFrameIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(false);
|
||||
}
|
||||
|
||||
mAnim->currentAnimationFrameTime = GetCurrentImgFrameEndTime();
|
||||
|
||||
// If we can get closer to the current time by a multiple of the image's loop
|
||||
// time, we should.
|
||||
uint32_t loopTime = GetSingleLoopTime();
|
||||
if (loopTime > 0) {
|
||||
TimeDuration delay = aTime - mAnim->currentAnimationFrameTime;
|
||||
if (delay.ToMilliseconds() > loopTime) {
|
||||
// Explicitly use integer division to get the floor of the number of
|
||||
// loops.
|
||||
uint32_t loops = static_cast<uint32_t>(delay.ToMilliseconds()) / loopTime;
|
||||
mAnim->currentAnimationFrameTime += TimeDuration::FromMilliseconds(loops * loopTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Set currentAnimationFrameIndex at the last possible moment
|
||||
mAnim->currentAnimationFrameIndex = nextFrameIndex;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
// [notxpcom] void requestRefresh ([const] in TimeStamp aTime);
|
||||
NS_IMETHODIMP_(void)
|
||||
@ -668,34 +539,12 @@ RasterImage::RequestRefresh(const mozilla::TimeStamp& aTime)
|
||||
|
||||
EvaluateAnimation();
|
||||
|
||||
// only advance the frame if the current time is greater than or
|
||||
// equal to the current frame's end time.
|
||||
TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime();
|
||||
bool frameAdvanced = false;
|
||||
|
||||
// The dirtyRect variable will contain an accumulation of the sub-rectangles
|
||||
// that are dirty for each frame we advance in AdvanceFrame().
|
||||
nsIntRect dirtyRect;
|
||||
|
||||
while (currentFrameEndTime <= aTime) {
|
||||
TimeStamp oldFrameEndTime = currentFrameEndTime;
|
||||
nsIntRect frameDirtyRect;
|
||||
bool didAdvance = AdvanceFrame(aTime, &frameDirtyRect);
|
||||
frameAdvanced = frameAdvanced || didAdvance;
|
||||
currentFrameEndTime = GetCurrentImgFrameEndTime();
|
||||
|
||||
// Accumulate the dirty area.
|
||||
dirtyRect = dirtyRect.Union(frameDirtyRect);
|
||||
|
||||
// if we didn't advance a frame, and our frame end time didn't change,
|
||||
// then we need to break out of this loop & wait for the frame(s)
|
||||
// to finish downloading
|
||||
if (!didAdvance && (currentFrameEndTime == oldFrameEndTime)) {
|
||||
break;
|
||||
}
|
||||
FrameAnimator::RefreshResult res;
|
||||
if (mAnim) {
|
||||
res = mAnim->RequestRefresh(aTime);
|
||||
}
|
||||
|
||||
if (frameAdvanced) {
|
||||
if (res.frameAdvanced) {
|
||||
// Notify listeners that our frame has actually changed, but do this only
|
||||
// once for all frames that we've now passed (if AdvanceFrame() was called
|
||||
// more than once).
|
||||
@ -708,7 +557,12 @@ RasterImage::RequestRefresh(const mozilla::TimeStamp& aTime)
|
||||
// Explicitly call this on mStatusTracker so we're sure to not interfere
|
||||
// with the decoding process
|
||||
if (mStatusTracker)
|
||||
mStatusTracker->FrameChanged(&dirtyRect);
|
||||
mStatusTracker->FrameChanged(&res.dirtyRect);
|
||||
}
|
||||
|
||||
if (res.animationFinished) {
|
||||
mAnimationFinished = true;
|
||||
EvaluateAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@ -833,34 +687,11 @@ uint32_t
|
||||
RasterImage::GetCurrentImgFrameIndex() const
|
||||
{
|
||||
if (mAnim)
|
||||
return mAnim->currentAnimationFrameIndex;
|
||||
return mAnim->GetCurrentAnimationFrameIndex();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
TimeStamp
|
||||
RasterImage::GetCurrentImgFrameEndTime() const
|
||||
{
|
||||
imgFrame* currentFrame = mFrameBlender.RawGetFrame(mAnim->currentAnimationFrameIndex);
|
||||
TimeStamp currentFrameTime = mAnim->currentAnimationFrameTime;
|
||||
int64_t timeout = currentFrame->GetTimeout();
|
||||
|
||||
if (timeout < 0) {
|
||||
// We need to return a sentinel value in this case, because our logic
|
||||
// doesn't work correctly if we have a negative timeout value. The reason
|
||||
// this positive infinity was chosen was because it works with the loop in
|
||||
// RequestRefresh() above.
|
||||
return TimeStamp() +
|
||||
TimeDuration::FromMilliseconds(static_cast<double>(UINT64_MAX));
|
||||
}
|
||||
|
||||
TimeDuration durationOfTimeout =
|
||||
TimeDuration::FromMilliseconds(static_cast<double>(timeout));
|
||||
TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
|
||||
|
||||
return currentFrameEndTime;
|
||||
}
|
||||
|
||||
imgFrame*
|
||||
RasterImage::GetCurrentImgFrame()
|
||||
{
|
||||
@ -1230,6 +1061,30 @@ RasterImage::OutOfProcessSizeOfDecoded() const
|
||||
NULL);
|
||||
}
|
||||
|
||||
void
|
||||
RasterImage::EnsureAnimExists()
|
||||
{
|
||||
if (!mAnim) {
|
||||
|
||||
// Create the animation context
|
||||
mAnim = new FrameAnimator(mFrameBlender);
|
||||
|
||||
// We don't support discarding animated images (See bug 414259).
|
||||
// Lock the image and throw away the key.
|
||||
//
|
||||
// Note that this is inefficient, since we could get rid of the source
|
||||
// data too. However, doing this is actually hard, because we're probably
|
||||
// calling ensureAnimExists mid-decode, and thus we're decoding out of
|
||||
// the source buffer. Since we're going to fix this anyway later, and
|
||||
// since we didn't kill the source data in the old world either, locking
|
||||
// is acceptable for the moment.
|
||||
LockImage();
|
||||
|
||||
// Notify our observers that we are starting animation.
|
||||
CurrentStatusTracker().RecordImageIsAnimated();
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
RasterImage::InternalAddFrameHelper(uint32_t framenum, imgFrame *aFrame,
|
||||
uint8_t **imageData, uint32_t *imageLength,
|
||||
@ -1310,15 +1165,13 @@ RasterImage::InternalAddFrame(uint32_t framenum,
|
||||
int32_t frameDisposalMethod = mFrameBlender.RawGetFrame(0)->GetFrameDisposalMethod();
|
||||
if (frameDisposalMethod == FrameBlender::kDisposeClear ||
|
||||
frameDisposalMethod == FrameBlender::kDisposeRestorePrevious)
|
||||
mAnim->firstFrameRefreshArea = mFrameBlender.RawGetFrame(0)->GetRect();
|
||||
mAnim->SetFirstFrameRefreshArea(mFrameBlender.RawGetFrame(0)->GetRect());
|
||||
}
|
||||
|
||||
// Calculate firstFrameRefreshArea
|
||||
// Some gifs are huge but only have a small area that they animate
|
||||
// We only need to refresh that small area when Frame 0 comes around again
|
||||
nsIntRect frameRect = frame->GetRect();
|
||||
mAnim->firstFrameRefreshArea.UnionRect(mAnim->firstFrameRefreshArea,
|
||||
frameRect);
|
||||
mAnim->UnionFirstFrameRefreshArea(frame->GetRect());
|
||||
|
||||
rv = InternalAddFrameHelper(framenum, frame.forget(), imageData, imageLength,
|
||||
paletteData, paletteLength, aRetFrame);
|
||||
@ -1541,9 +1394,22 @@ RasterImage::DecodingComplete()
|
||||
}
|
||||
}
|
||||
|
||||
if (mAnim) {
|
||||
mAnim->SetDoneDecoding(true);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
RasterImage::SetAnimationMode(uint16_t aAnimationMode)
|
||||
{
|
||||
if (mAnim) {
|
||||
mAnim->SetAnimationMode(aAnimationMode);
|
||||
}
|
||||
return SetAnimationModeInternal(aAnimationMode);
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
/* void StartAnimation () */
|
||||
nsresult
|
||||
@ -1565,9 +1431,7 @@ RasterImage::StartAnimation()
|
||||
|
||||
// We need to set the time that this initial frame was first displayed, as
|
||||
// this is used in AdvanceFrame().
|
||||
if (mAnim->currentAnimationFrameTime.IsNull()) {
|
||||
mAnim->currentAnimationFrameTime = TimeStamp::Now();
|
||||
}
|
||||
mAnim->InitAnimationFrameTimeIfNecessary();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
@ -1595,7 +1459,7 @@ RasterImage::ResetAnimation()
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
if (mAnimationMode == kDontAnimMode ||
|
||||
!mAnim || mAnim->currentAnimationFrameIndex == 0)
|
||||
!mAnim || mAnim->GetCurrentAnimationFrameIndex() == 0)
|
||||
return NS_OK;
|
||||
|
||||
mAnimationFinished = false;
|
||||
@ -1604,8 +1468,10 @@ RasterImage::ResetAnimation()
|
||||
StopAnimation();
|
||||
|
||||
mFrameBlender.ResetAnimation();
|
||||
if (mAnim) {
|
||||
mAnim->ResetAnimation();
|
||||
}
|
||||
|
||||
mAnim->currentAnimationFrameIndex = 0;
|
||||
UpdateImageContainer();
|
||||
|
||||
// Note - We probably want to kick off a redecode somewhere around here when
|
||||
@ -1613,7 +1479,8 @@ RasterImage::ResetAnimation()
|
||||
|
||||
// Update display if we were animating before
|
||||
if (mAnimating && mStatusTracker) {
|
||||
mStatusTracker->FrameChanged(&(mAnim->firstFrameRefreshArea));
|
||||
nsIntRect rect = mAnim->GetFirstFrameRefreshArea();
|
||||
mStatusTracker->FrameChanged(&rect);
|
||||
}
|
||||
|
||||
if (ShouldAnimate()) {
|
||||
@ -1635,7 +1502,7 @@ RasterImage::SetAnimationStartTime(const mozilla::TimeStamp& aTime)
|
||||
if (mError || mAnimating || !mAnim)
|
||||
return;
|
||||
|
||||
mAnim->currentAnimationFrameTime = aTime;
|
||||
mAnim->SetAnimationFrameTime(aTime);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(float)
|
||||
@ -1644,7 +1511,7 @@ RasterImage::GetFrameIndex(uint32_t aWhichFrame)
|
||||
MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE, "Invalid argument");
|
||||
return (aWhichFrame == FRAME_FIRST || !mAnim)
|
||||
? 0.0f
|
||||
: mAnim->currentAnimationFrameIndex;
|
||||
: mAnim->GetCurrentAnimationFrameIndex();
|
||||
}
|
||||
|
||||
void
|
||||
@ -1653,11 +1520,9 @@ RasterImage::SetLoopCount(int32_t aLoopCount)
|
||||
if (mError)
|
||||
return;
|
||||
|
||||
// -1 infinite
|
||||
// 0 no looping, one iteration
|
||||
// 1 one loop, two iterations
|
||||
// ...
|
||||
mLoopCount = aLoopCount;
|
||||
if (mAnim) {
|
||||
mAnim->SetLoopCount(aLoopCount);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -1929,6 +1794,10 @@ RasterImage::OnNewSourceData()
|
||||
mWantFullDecode = true;
|
||||
mDecodeRequest = nullptr;
|
||||
|
||||
if (mAnim) {
|
||||
mAnim->SetDoneDecoding(false);
|
||||
}
|
||||
|
||||
// We always need the size first.
|
||||
rv = InitDecoder(/* aDoSizeDecode = */ true);
|
||||
CONTAINER_ENSURE_SUCCESS(rv);
|
||||
|
@ -120,10 +120,6 @@ class nsIThreadPool;
|
||||
* @par
|
||||
* The mAnim structure has members only needed for animated images, so
|
||||
* it's not allocated until the second frame is added.
|
||||
*
|
||||
* @note
|
||||
* mAnimationMode and mLoopCount are not in the mAnim structure because
|
||||
* they have public setters.
|
||||
*/
|
||||
|
||||
class ScaleRequest;
|
||||
@ -137,6 +133,7 @@ class Image;
|
||||
namespace image {
|
||||
|
||||
class Decoder;
|
||||
class FrameAnimator;
|
||||
|
||||
class RasterImage : public ImageResource
|
||||
, public nsIProperties
|
||||
@ -324,20 +321,6 @@ private:
|
||||
|
||||
nsresult OnImageDataCompleteCore(nsIRequest* aRequest, nsISupports*, nsresult aStatus);
|
||||
|
||||
struct Anim
|
||||
{
|
||||
//! Area of the first frame that needs to be redrawn on subsequent loops.
|
||||
nsIntRect firstFrameRefreshArea;
|
||||
uint32_t currentAnimationFrameIndex; // 0 to numFrames-1
|
||||
|
||||
// the time that the animation advanced to the current frame
|
||||
TimeStamp currentAnimationFrameTime;
|
||||
|
||||
Anim() :
|
||||
currentAnimationFrameIndex(0)
|
||||
{}
|
||||
};
|
||||
|
||||
/**
|
||||
* Each RasterImage has a pointer to one or zero heap-allocated
|
||||
* DecodeRequests.
|
||||
@ -545,33 +528,6 @@ private:
|
||||
uint32_t aFlags,
|
||||
gfxImageSurface **_retval);
|
||||
|
||||
/**
|
||||
* Advances the animation. Typically, this will advance a single frame, but it
|
||||
* may advance multiple frames. This may happen if we have infrequently
|
||||
* "ticking" refresh drivers (e.g. in background tabs), or extremely short-
|
||||
* lived animation frames.
|
||||
*
|
||||
* @param aTime the time that the animation should advance to. This will
|
||||
* typically be <= TimeStamp::Now().
|
||||
*
|
||||
* @param [out] aDirtyRect a pointer to an nsIntRect which encapsulates the
|
||||
* area to be repainted after the frame is advanced.
|
||||
*
|
||||
* @returns true, if the frame was successfully advanced, false if it was not
|
||||
* able to be advanced (e.g. the frame to which we want to advance is
|
||||
* still decoding). Note: If false is returned, then aDirtyRect will
|
||||
* remain unmodified.
|
||||
*/
|
||||
bool AdvanceFrame(mozilla::TimeStamp aTime, nsIntRect* aDirtyRect);
|
||||
|
||||
/**
|
||||
* Gets the length of a single loop of this image, in milliseconds.
|
||||
*
|
||||
* If this image is not finished decoding, is not animated, or it is animated
|
||||
* but does not loop, returns 0.
|
||||
*/
|
||||
uint32_t GetSingleLoopTime() const;
|
||||
|
||||
/**
|
||||
* Deletes and nulls out the frame in mFrames[framenum].
|
||||
*
|
||||
@ -587,33 +543,11 @@ private:
|
||||
imgFrame* GetDrawableImgFrame(uint32_t framenum);
|
||||
imgFrame* GetCurrentImgFrame();
|
||||
uint32_t GetCurrentImgFrameIndex() const;
|
||||
mozilla::TimeStamp GetCurrentImgFrameEndTime() const;
|
||||
|
||||
size_t SizeOfDecodedWithComputedFallbackIfHeap(gfxASurface::MemoryLocation aLocation,
|
||||
mozilla::MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
inline void EnsureAnimExists()
|
||||
{
|
||||
if (!mAnim) {
|
||||
|
||||
// Create the animation context
|
||||
mAnim = new Anim();
|
||||
|
||||
// We don't support discarding animated images (See bug 414259).
|
||||
// Lock the image and throw away the key.
|
||||
//
|
||||
// Note that this is inefficient, since we could get rid of the source
|
||||
// data too. However, doing this is actually hard, because we're probably
|
||||
// calling ensureAnimExists mid-decode, and thus we're decoding out of
|
||||
// the source buffer. Since we're going to fix this anyway later, and
|
||||
// since we didn't kill the source data in the old world either, locking
|
||||
// is acceptable for the moment.
|
||||
LockImage();
|
||||
|
||||
// Notify our observers that we are starting animation.
|
||||
CurrentStatusTracker().RecordImageIsAnimated();
|
||||
}
|
||||
}
|
||||
void EnsureAnimExists();
|
||||
|
||||
nsresult InternalAddFrameHelper(uint32_t framenum, imgFrame *frame,
|
||||
uint8_t **imageData, uint32_t *imageLength,
|
||||
@ -671,10 +605,7 @@ private: // data
|
||||
// IMPORTANT: if you use mAnim in a method, call EnsureImageIsDecoded() first to ensure
|
||||
// that the frames actually exist (they may have been discarded to save memory, or
|
||||
// we maybe decoding on draw).
|
||||
RasterImage::Anim* mAnim;
|
||||
|
||||
//! # loops remaining before animation stops (-1 no stop)
|
||||
int32_t mLoopCount;
|
||||
FrameAnimator* mAnim;
|
||||
|
||||
// Discard members
|
||||
uint32_t mLockCount;
|
||||
@ -791,10 +722,6 @@ inline NS_IMETHODIMP RasterImage::GetAnimationMode(uint16_t *aAnimationMode) {
|
||||
return GetAnimationModeInternal(aAnimationMode);
|
||||
}
|
||||
|
||||
inline NS_IMETHODIMP RasterImage::SetAnimationMode(uint16_t aAnimationMode) {
|
||||
return SetAnimationModeInternal(aAnimationMode);
|
||||
}
|
||||
|
||||
// Asynchronous Decode Requestor
|
||||
//
|
||||
// We use this class when someone calls requestDecode() from within a decode
|
||||
|
@ -509,7 +509,7 @@ nsresult imgFrame::ImageUpdated(const nsIntRect &aUpdateRect)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool imgFrame::GetIsDirty()
|
||||
bool imgFrame::GetIsDirty() const
|
||||
{
|
||||
MutexAutoLock lock(mDirtyMutex);
|
||||
return mDirty;
|
||||
@ -796,8 +796,11 @@ void imgFrame::SetBlendMethod(int32_t aBlendMethod)
|
||||
mBlendMethod = (int8_t)aBlendMethod;
|
||||
}
|
||||
|
||||
// This can be called from any thread.
|
||||
bool imgFrame::ImageComplete() const
|
||||
{
|
||||
MutexAutoLock lock(mDirtyMutex);
|
||||
|
||||
return mDecoded.IsEqualInterior(nsIntRect(mOffset, mSize));
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ public:
|
||||
uint32_t aImageFlags = imgIContainer::FLAG_NONE);
|
||||
|
||||
nsresult ImageUpdated(const nsIntRect &aUpdateRect);
|
||||
bool GetIsDirty();
|
||||
bool GetIsDirty() const;
|
||||
|
||||
nsIntRect GetRect() const;
|
||||
gfxASurface::gfxImageFormat GetFormat() const;
|
||||
@ -152,7 +152,7 @@ private: // data
|
||||
|
||||
nsIntRect mDecoded;
|
||||
|
||||
mozilla::Mutex mDirtyMutex;
|
||||
mutable mozilla::Mutex mDirtyMutex;
|
||||
|
||||
// The palette and image data for images that are paletted, since Cairo
|
||||
// doesn't support these images.
|
||||
|
@ -17,6 +17,7 @@ CPP_SOURCES += [
|
||||
'ClippedImage.cpp',
|
||||
'Decoder.cpp',
|
||||
'DiscardTracker.cpp',
|
||||
'FrameAnimator.cpp',
|
||||
'FrameBlender.cpp',
|
||||
'FrameSequence.cpp',
|
||||
'FrozenImage.cpp',
|
||||
|
@ -210,8 +210,6 @@ class Vector : private AllocPolicy
|
||||
bool growStorageBy(size_t incr);
|
||||
bool convertToHeapStorage(size_t newCap);
|
||||
|
||||
template <bool InitNewElems> inline bool growByImpl(size_t inc);
|
||||
|
||||
/* magic constants */
|
||||
|
||||
static const int sMaxInlineBytes = 1024;
|
||||
@ -756,9 +754,8 @@ Vector<T,N,AP>::shrinkBy(size_t incr)
|
||||
}
|
||||
|
||||
template <class T, size_t N, class AP>
|
||||
template <bool InitNewElems>
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
Vector<T,N,AP>::growByImpl(size_t incr)
|
||||
Vector<T,N,AP>::growBy(size_t incr)
|
||||
{
|
||||
REENTRANCY_GUARD_ET_AL;
|
||||
if (incr > mCapacity - mLength && !growStorageBy(incr))
|
||||
@ -766,8 +763,7 @@ Vector<T,N,AP>::growByImpl(size_t incr)
|
||||
|
||||
MOZ_ASSERT(mLength + incr <= mCapacity);
|
||||
T *newend = endNoCheck() + incr;
|
||||
if (InitNewElems)
|
||||
Impl::initialize(endNoCheck(), newend);
|
||||
Impl::initialize(endNoCheck(), newend);
|
||||
mLength += incr;
|
||||
#ifdef DEBUG
|
||||
if (mLength > mReserved)
|
||||
@ -776,18 +772,21 @@ Vector<T,N,AP>::growByImpl(size_t incr)
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T, size_t N, class AP>
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
Vector<T,N,AP>::growBy(size_t incr)
|
||||
{
|
||||
return growByImpl<true>(incr);
|
||||
}
|
||||
|
||||
template <class T, size_t N, class AP>
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
Vector<T,N,AP>::growByUninitialized(size_t incr)
|
||||
{
|
||||
return growByImpl<false>(incr);
|
||||
REENTRANCY_GUARD_ET_AL;
|
||||
if (incr > mCapacity - mLength && !growStorageBy(incr))
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(mLength + incr <= mCapacity);
|
||||
mLength += incr;
|
||||
#ifdef DEBUG
|
||||
if (mLength > mReserved)
|
||||
mReserved = mLength;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T, size_t N, class AP>
|
||||
|
@ -604,6 +604,7 @@ if test -n "$_WIN32_MSVC"; then
|
||||
# Common to all MSVC environments:
|
||||
|
||||
AC_DEFINE(HAVE_LOCALECONV)
|
||||
AC_DEFINE(HAVE_HYPOT)
|
||||
fi
|
||||
|
||||
fi # COMPILE_ENVIRONMENT
|
||||
@ -2752,6 +2753,12 @@ AC_CHECK_FUNCS([fchmod flockfile getc_unlocked _getc_nolock getpagesize \
|
||||
lchown localtime_r lstat64 memmove random rint sbrk snprintf \
|
||||
stat64 statvfs statvfs64 strerror strtok_r truncate64])
|
||||
|
||||
|
||||
dnl Checks for math functions.
|
||||
dnl ========================================================
|
||||
AC_CHECK_FUNCS(log2 log1p expm1 sqrt1pm1 acosh asinh atanh hypot trunc cbrt)
|
||||
|
||||
|
||||
dnl check for wcrtomb/mbrtowc
|
||||
dnl =======================================================================
|
||||
if test -z "$MACOS_DEPLOYMENT_TARGET" || test "$MACOS_DEPLOYMENT_TARGET" -ge "100300"; then
|
||||
|
@ -358,10 +358,9 @@ frontend::CompileScript(JSContext *cx, HandleObject scopeChain,
|
||||
}
|
||||
|
||||
bool
|
||||
frontend::CompileLazyFunction(JSContext *cx, HandleFunction fun, LazyScript *lazy,
|
||||
const jschar *chars, size_t length)
|
||||
frontend::CompileLazyFunction(JSContext *cx, LazyScript *lazy, const jschar *chars, size_t length)
|
||||
{
|
||||
JS_ASSERT(cx->compartment() == fun->compartment());
|
||||
JS_ASSERT(cx->compartment() == lazy->function()->compartment());
|
||||
|
||||
CompileOptions options(cx, lazy->version());
|
||||
options.setPrincipals(cx->compartment()->principals)
|
||||
@ -377,6 +376,7 @@ frontend::CompileLazyFunction(JSContext *cx, HandleFunction fun, LazyScript *laz
|
||||
|
||||
uint32_t staticLevel = lazy->staticLevel(cx);
|
||||
|
||||
Rooted<JSFunction*> fun(cx, lazy->function());
|
||||
ParseNode *pn = parser.standaloneLazyFunction(fun, staticLevel, lazy->strict());
|
||||
if (!pn)
|
||||
return false;
|
||||
|
@ -26,8 +26,7 @@ CompileScript(JSContext *cx, HandleObject scopeChain, HandleScript evalCaller,
|
||||
SourceCompressionToken *extraSct = NULL);
|
||||
|
||||
bool
|
||||
CompileLazyFunction(JSContext *cx, HandleFunction fun, LazyScript *lazy,
|
||||
const jschar *chars, size_t length);
|
||||
CompileLazyFunction(JSContext *cx, LazyScript *lazy, const jschar *chars, size_t length);
|
||||
|
||||
bool
|
||||
CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, CompileOptions options,
|
||||
|
@ -2046,11 +2046,6 @@ EmitElemOperands(ExclusiveContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *
|
||||
right = pn->pn_right;
|
||||
}
|
||||
|
||||
if (op == JSOP_GETELEM && left->isKind(PNK_NAME) && right->isKind(PNK_NUMBER)) {
|
||||
if (!BindNameToSlot(cx, bce, left))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EmitTree(cx, bce, left))
|
||||
return false;
|
||||
|
||||
@ -2731,65 +2726,90 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
|
||||
if (Emit1(cx, bce, JSOP_POP) < 0)
|
||||
return false;
|
||||
}
|
||||
} else if (emitOption == PushInitialValues) {
|
||||
// The lhs is a simple name so the to-be-destructured value is
|
||||
// its initial value and there is nothing to do.
|
||||
JS_ASSERT(pn->getOp() == JSOP_GETLOCAL);
|
||||
JS_ASSERT(pn->pn_dflags & PND_BOUND);
|
||||
} else {
|
||||
if (emitOption == PushInitialValues) {
|
||||
/*
|
||||
* The lhs is a simple name so the to-be-destructured value is
|
||||
* its initial value and there is nothing to do.
|
||||
*/
|
||||
JS_ASSERT(pn->getOp() == JSOP_GETLOCAL);
|
||||
JS_ASSERT(pn->pn_dflags & PND_BOUND);
|
||||
return true;
|
||||
}
|
||||
// All paths below must pop after assigning to the lhs.
|
||||
|
||||
/* All paths below must pop after assigning to the lhs. */
|
||||
|
||||
if (pn->isKind(PNK_NAME)) {
|
||||
switch (pn->getKind()) {
|
||||
case PNK_NAME:
|
||||
if (!BindNameToSlot(cx, bce, pn))
|
||||
return false;
|
||||
|
||||
/* Allow 'const [x,y] = o', make 'const x,y; [x,y] = o' a nop. */
|
||||
// Allow 'const [x,y] = o', make 'const x,y; [x,y] = o' a nop.
|
||||
if (pn->isConst() && !pn->isDefn())
|
||||
return Emit1(cx, bce, JSOP_POP) >= 0;
|
||||
}
|
||||
|
||||
switch (pn->getOp()) {
|
||||
case JSOP_SETNAME:
|
||||
case JSOP_SETGNAME:
|
||||
/*
|
||||
* NB: pn is a PN_NAME node, not a PN_BINARY. Nevertheless,
|
||||
* we want to emit JSOP_ENUMELEM, which has format JOF_ELEM.
|
||||
* So here and for JSOP_ENUMCONSTELEM, we use EmitElemOp.
|
||||
*/
|
||||
switch (pn->getOp()) {
|
||||
case JSOP_SETNAME:
|
||||
case JSOP_SETGNAME:
|
||||
// This is like ordinary assignment, but with one difference.
|
||||
//
|
||||
// In `a = b`, we first determine a binding for `a` (using
|
||||
// JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`, then
|
||||
// a JSOP_SETNAME instruction.
|
||||
//
|
||||
// In `[a] = [b]`, per spec, `b` is evaluated first, then we
|
||||
// determine a binding for `a`. Then we need to do assignment--
|
||||
// but the operands are on the stack in the wrong order for
|
||||
// JSOP_SETPROP, so we use JSOP_ENUMELEM instead.
|
||||
//
|
||||
// EmitElemOp ordinarily works with PNK_ELEM nodes, naturally,
|
||||
// but it has special code to handle PNK_NAME nodes in this one
|
||||
// case.
|
||||
if (!EmitElemOp(cx, pn, JSOP_ENUMELEM, bce))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case JSOP_SETCONST:
|
||||
// As above.
|
||||
if (!EmitElemOp(cx, pn, JSOP_ENUMCONSTELEM, bce))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case JSOP_SETLOCAL:
|
||||
case JSOP_SETARG:
|
||||
if (!EmitVarOp(cx, pn, pn->getOp(), bce))
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_POP) < 0)
|
||||
return false;
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_ASSUME_UNREACHABLE("EmitDestructuringLHS: bad name op");
|
||||
}
|
||||
break;
|
||||
|
||||
case PNK_DOT:
|
||||
case PNK_ELEM:
|
||||
// See the (PNK_NAME, JSOP_SETNAME) case above.
|
||||
//
|
||||
// In `a.x = b`, `a` is evaluated first, then `b`, then a
|
||||
// JSOP_SETPROP instruction.
|
||||
//
|
||||
// In `[a.x] = [b]`, per spec, `b` is evaluated before `a`. Then we
|
||||
// need a property set -- but the operands are on the stack in the
|
||||
// wrong order for JSOP_SETPROP, so we use JSOP_ENUMELEM instead.
|
||||
//
|
||||
// EmitElemOp has special code to handle PNK_DOT nodes in this one
|
||||
// case.
|
||||
if (!EmitElemOp(cx, pn, JSOP_ENUMELEM, bce))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case JSOP_SETCONST:
|
||||
if (!EmitElemOp(cx, pn, JSOP_ENUMCONSTELEM, bce))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case JSOP_SETLOCAL:
|
||||
case JSOP_SETARG:
|
||||
if (!EmitVarOp(cx, pn, pn->getOp(), bce))
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_POP) < 0)
|
||||
return false;
|
||||
break;
|
||||
|
||||
case JSOP_CALL:
|
||||
case JSOP_EVAL:
|
||||
case JSOP_FUNCALL:
|
||||
case JSOP_FUNAPPLY:
|
||||
case PNK_CALL:
|
||||
JS_ASSERT(pn->pn_xflags & PNX_SETCALL);
|
||||
if (!EmitTree(cx, bce, pn))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* We just emitted JSOP_SETCALL which will always throw.
|
||||
* Pop the call return value and the RHS.
|
||||
*/
|
||||
// Pop the call return value and the RHS, presumably for the
|
||||
// benefit of bytecode analysis. (The interpreter will never reach
|
||||
// these instructions since we just emitted JSOP_SETCALL, which
|
||||
// always throws. It's possible no analyses actually depend on this
|
||||
// either.)
|
||||
if (Emit1(cx, bce, JSOP_POP) < 0)
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_POP) < 0)
|
||||
@ -2797,16 +2817,7 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
if (!EmitTree(cx, bce, pn))
|
||||
return false;
|
||||
if (!EmitElemOpBase(cx, bce, JSOP_ENUMELEM))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
case JSOP_ENUMELEM:
|
||||
JS_ASSERT(0);
|
||||
MOZ_ASSUME_UNREACHABLE("EmitDestructuringLHS: bad lhs kind");
|
||||
}
|
||||
}
|
||||
|
||||
@ -5681,6 +5692,7 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
||||
bool restIsDefn = false;
|
||||
if (fun->hasRest()) {
|
||||
JS_ASSERT(!bce->sc->asFunctionBox()->argumentsHasLocalBinding());
|
||||
|
||||
// Defaults with a rest parameter need special handling. The
|
||||
// rest parameter needs to be undefined while defaults are being
|
||||
// processed. To do this, we create the rest argument and let it
|
||||
@ -5694,6 +5706,7 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
||||
if (Emit1(cx, bce, JSOP_REST) < 0)
|
||||
return false;
|
||||
CheckTypeSet(cx, bce, JSOP_REST);
|
||||
|
||||
// Only set the rest parameter if it's not aliased by a nested
|
||||
// function in the body.
|
||||
if (restIsDefn) {
|
||||
|
@ -86,17 +86,13 @@ class FullParseHandler
|
||||
void prepareNodeForMutation(ParseNode *pn) { return allocator.prepareNodeForMutation(pn); }
|
||||
const Token ¤tToken() { return tokenStream.currentToken(); }
|
||||
|
||||
ParseNode *newName(PropertyName *name, InBlockBool inBlock, uint32_t blockid,
|
||||
const TokenPos &pos)
|
||||
{
|
||||
return new_<NameNode>(PNK_NAME, JSOP_NAME, name, inBlock, blockid, pos);
|
||||
ParseNode *newName(PropertyName *name, uint32_t blockid, const TokenPos &pos) {
|
||||
return new_<NameNode>(PNK_NAME, JSOP_NAME, name, blockid, pos);
|
||||
}
|
||||
|
||||
Definition *newPlaceholder(JSAtom *atom, InBlockBool inBlock, uint32_t blockid,
|
||||
const TokenPos &pos)
|
||||
{
|
||||
Definition *newPlaceholder(JSAtom *atom, uint32_t blockid, const TokenPos &pos) {
|
||||
Definition *dn =
|
||||
(Definition *) new_<NameNode>(PNK_NAME, JSOP_NOP, atom, inBlock, blockid, pos);
|
||||
(Definition *) new_<NameNode>(PNK_NAME, JSOP_NOP, atom, blockid, pos);
|
||||
if (!dn)
|
||||
return NULL;
|
||||
dn->setDefn(true);
|
||||
|
@ -630,7 +630,6 @@ class ParseNode
|
||||
#define PND_LET 0x01 /* let (block-scoped) binding */
|
||||
#define PND_CONST 0x02 /* const binding (orthogonal to let) */
|
||||
#define PND_ASSIGNED 0x04 /* set if ever LHS of assignment */
|
||||
#define PND_BLOCKCHILD 0x08 /* use or def is direct block child */
|
||||
#define PND_PLACEHOLDER 0x10 /* placeholder definition for lexdep */
|
||||
#define PND_BOUND 0x20 /* bound to a stack or global slot */
|
||||
#define PND_DEOPTIMIZED 0x40 /* former pn_used name node, pn_lexdef
|
||||
@ -713,7 +712,6 @@ class ParseNode
|
||||
|
||||
bool isLet() const { return test(PND_LET); }
|
||||
bool isConst() const { return test(PND_CONST); }
|
||||
bool isBlockChild() const { return test(PND_BLOCKCHILD); }
|
||||
bool isPlaceholder() const { return test(PND_PLACEHOLDER); }
|
||||
bool isDeoptimized() const { return test(PND_DEOPTIMIZED); }
|
||||
bool isAssigned() const { return test(PND_ASSIGNED); }
|
||||
@ -976,21 +974,16 @@ struct CodeNode : public ParseNode
|
||||
#endif
|
||||
};
|
||||
|
||||
enum InBlockBool {
|
||||
NotInBlock = false,
|
||||
InBlock = true
|
||||
};
|
||||
|
||||
struct NameNode : public ParseNode
|
||||
{
|
||||
NameNode(ParseNodeKind kind, JSOp op, JSAtom *atom, InBlockBool inBlock, uint32_t blockid,
|
||||
NameNode(ParseNodeKind kind, JSOp op, JSAtom *atom, uint32_t blockid,
|
||||
const TokenPos &pos)
|
||||
: ParseNode(kind, op, PN_NAME, pos)
|
||||
{
|
||||
pn_atom = atom;
|
||||
pn_expr = NULL;
|
||||
pn_cookie.makeFree();
|
||||
pn_dflags = inBlock ? PND_BLOCKCHILD : 0;
|
||||
pn_dflags = 0;
|
||||
pn_blockid = blockid;
|
||||
JS_ASSERT(pn_blockid == blockid); // check for bitfield overflow
|
||||
}
|
||||
|
@ -782,14 +782,14 @@ Parser<ParseHandler>::checkFinalReturn(Node pn)
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that it is permitted to assign to lhs. Strict mode code may not
|
||||
* assign to 'eval' or 'arguments'.
|
||||
* Check that assigning to lhs is permitted. Assigning to 'eval' or
|
||||
* 'arguments' is banned in strict mode and in destructuring assignment.
|
||||
*/
|
||||
template <typename ParseHandler>
|
||||
bool
|
||||
Parser<ParseHandler>::checkStrictAssignment(Node lhs)
|
||||
Parser<ParseHandler>::checkStrictAssignment(Node lhs, AssignmentFlavor flavor)
|
||||
{
|
||||
if (!pc->sc->needStrictChecks())
|
||||
if (!pc->sc->needStrictChecks() && flavor != KeyedDestructuringAssignment)
|
||||
return true;
|
||||
|
||||
JSAtom *atom = handler.isName(lhs);
|
||||
@ -798,12 +798,20 @@ Parser<ParseHandler>::checkStrictAssignment(Node lhs)
|
||||
|
||||
if (atom == context->names().eval || atom == context->names().arguments) {
|
||||
JSAutoByteString name;
|
||||
if (!AtomToPrintableString(context, atom, &name) ||
|
||||
!report(ParseStrictError, pc->sc->strict, lhs,
|
||||
JSMSG_DEPRECATED_ASSIGN, name.ptr()))
|
||||
{
|
||||
if (!AtomToPrintableString(context, atom, &name))
|
||||
return false;
|
||||
|
||||
ParseReportKind kind;
|
||||
unsigned errnum;
|
||||
if (pc->sc->strict || flavor != KeyedDestructuringAssignment) {
|
||||
kind = ParseStrictError;
|
||||
errnum = JSMSG_BAD_STRICT_ASSIGN;
|
||||
} else {
|
||||
kind = ParseError;
|
||||
errnum = JSMSG_BAD_DESTRUCT_ASSIGN;
|
||||
}
|
||||
if (!report(kind, pc->sc->strict, lhs, errnum, name.ptr()))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -816,7 +824,7 @@ Parser<ParseHandler>::checkStrictAssignment(Node lhs)
|
||||
*/
|
||||
template <typename ParseHandler>
|
||||
bool
|
||||
Parser<ParseHandler>::checkStrictBinding(HandlePropertyName name, Node pn)
|
||||
Parser<ParseHandler>::checkStrictBinding(PropertyName *name, Node pn)
|
||||
{
|
||||
if (!pc->sc->needStrictChecks())
|
||||
return true;
|
||||
@ -1254,7 +1262,7 @@ Parser<ParseHandler>::getOrCreateLexicalDependency(ParseContext<ParseHandler> *p
|
||||
if (p)
|
||||
return p.value().get<ParseHandler>();
|
||||
|
||||
DefinitionNode dn = handler.newPlaceholder(atom, pc->inBlock(), pc->blockid(), pos());
|
||||
DefinitionNode dn = handler.newPlaceholder(atom, pc->blockid(), pos());
|
||||
if (!dn)
|
||||
return ParseHandler::nullDefinition();
|
||||
DefinitionSingle def = DefinitionSingle::new_<ParseHandler>(dn);
|
||||
@ -1297,8 +1305,7 @@ ConvertDefinitionToNamedLambdaUse(TokenStream &ts, ParseContext<FullParseHandler
|
||||
*/
|
||||
template <>
|
||||
bool
|
||||
Parser<FullParseHandler>::leaveFunction(ParseNode *fn, HandlePropertyName funName,
|
||||
ParseContext<FullParseHandler> *outerpc,
|
||||
Parser<FullParseHandler>::leaveFunction(ParseNode *fn, ParseContext<FullParseHandler> *outerpc,
|
||||
FunctionSyntaxKind kind)
|
||||
{
|
||||
outerpc->blockidGen = pc->blockidGen;
|
||||
@ -1306,9 +1313,6 @@ Parser<FullParseHandler>::leaveFunction(ParseNode *fn, HandlePropertyName funNam
|
||||
FunctionBox *funbox = fn->pn_funbox;
|
||||
JS_ASSERT(funbox == pc->sc->asFunctionBox());
|
||||
|
||||
if (!outerpc->topStmt || outerpc->topStmt->type == STMT_BLOCK)
|
||||
fn->pn_dflags |= PND_BLOCKCHILD;
|
||||
|
||||
/* Propagate unresolved lexical names up to outerpc->lexdeps. */
|
||||
if (pc->lexdeps->count()) {
|
||||
for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront()) {
|
||||
@ -1316,7 +1320,7 @@ Parser<FullParseHandler>::leaveFunction(ParseNode *fn, HandlePropertyName funNam
|
||||
Definition *dn = r.front().value().get<FullParseHandler>();
|
||||
JS_ASSERT(dn->isPlaceholder());
|
||||
|
||||
if (atom == funName && kind == Expression) {
|
||||
if (atom == funbox->function()->name() && kind == Expression) {
|
||||
if (!ConvertDefinitionToNamedLambdaUse(tokenStream, pc, funbox, dn))
|
||||
return false;
|
||||
continue;
|
||||
@ -1400,8 +1404,7 @@ Parser<FullParseHandler>::leaveFunction(ParseNode *fn, HandlePropertyName funNam
|
||||
|
||||
template <>
|
||||
bool
|
||||
Parser<SyntaxParseHandler>::leaveFunction(Node fn, HandlePropertyName funName,
|
||||
ParseContext<SyntaxParseHandler> *outerpc,
|
||||
Parser<SyntaxParseHandler>::leaveFunction(Node fn, ParseContext<SyntaxParseHandler> *outerpc,
|
||||
FunctionSyntaxKind kind)
|
||||
{
|
||||
outerpc->blockidGen = pc->blockidGen;
|
||||
@ -1910,7 +1913,7 @@ Parser<SyntaxParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
|
||||
template <typename ParseHandler>
|
||||
typename ParseHandler::Node
|
||||
Parser<ParseHandler>::functionDef(HandlePropertyName funName, const TokenStream::Position &start,
|
||||
size_t startOffset, FunctionType type, FunctionSyntaxKind kind)
|
||||
FunctionType type, FunctionSyntaxKind kind)
|
||||
{
|
||||
JS_ASSERT_IF(kind == Statement, funName);
|
||||
|
||||
@ -1935,7 +1938,7 @@ Parser<ParseHandler>::functionDef(HandlePropertyName funName, const TokenStream:
|
||||
// directive, we backup and reparse it as strict.
|
||||
bool initiallyStrict = pc->sc->strict;
|
||||
bool becameStrict;
|
||||
if (!functionArgsAndBody(pn, fun, funName, startOffset, type, kind, initiallyStrict,
|
||||
if (!functionArgsAndBody(pn, fun, type, kind, initiallyStrict,
|
||||
&becameStrict))
|
||||
{
|
||||
if (initiallyStrict || !becameStrict || tokenStream.hadError())
|
||||
@ -1945,8 +1948,11 @@ Parser<ParseHandler>::functionDef(HandlePropertyName funName, const TokenStream:
|
||||
tokenStream.seek(start);
|
||||
if (funName && tokenStream.getToken() == TOK_ERROR)
|
||||
return null();
|
||||
|
||||
// functionArgsAndBody may have already set pn->pn_body before failing.
|
||||
handler.setFunctionBody(pn, null());
|
||||
if (!functionArgsAndBody(pn, fun, funName, startOffset, type, kind, true))
|
||||
|
||||
if (!functionArgsAndBody(pn, fun, type, kind, true))
|
||||
return null();
|
||||
}
|
||||
|
||||
@ -2017,7 +2023,8 @@ Parser<SyntaxParseHandler>::finishFunctionDefinition(Node pn, FunctionBox *funbo
|
||||
size_t numFreeVariables = pc->lexdeps->count();
|
||||
size_t numInnerFunctions = pc->innerFunctions.length();
|
||||
|
||||
LazyScript *lazy = LazyScript::Create(context, numFreeVariables, numInnerFunctions, versionNumber(),
|
||||
RootedFunction fun(context, funbox->function());
|
||||
LazyScript *lazy = LazyScript::Create(context, fun, numFreeVariables, numInnerFunctions, versionNumber(),
|
||||
funbox->bufStart, funbox->bufEnd,
|
||||
funbox->startLine, funbox->startColumn);
|
||||
if (!lazy)
|
||||
@ -2039,16 +2046,14 @@ Parser<SyntaxParseHandler>::finishFunctionDefinition(Node pn, FunctionBox *funbo
|
||||
lazy->setUsesArgumentsAndApply();
|
||||
PropagateTransitiveParseFlags(funbox, lazy);
|
||||
|
||||
funbox->object->as<JSFunction>().initLazyScript(lazy);
|
||||
fun->initLazyScript(lazy);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool
|
||||
Parser<FullParseHandler>::functionArgsAndBody(ParseNode *pn, HandleFunction fun,
|
||||
HandlePropertyName funName,
|
||||
size_t startOffset, FunctionType type,
|
||||
FunctionSyntaxKind kind,
|
||||
FunctionType type, FunctionSyntaxKind kind,
|
||||
bool strict, bool *becameStrict)
|
||||
{
|
||||
if (becameStrict)
|
||||
@ -2078,7 +2083,7 @@ Parser<FullParseHandler>::functionArgsAndBody(ParseNode *pn, HandleFunction fun,
|
||||
return false;
|
||||
|
||||
if (!parser->functionArgsAndBodyGeneric(SyntaxParseHandler::NodeGeneric,
|
||||
fun, funName, type, kind, strict, becameStrict))
|
||||
fun, type, kind, becameStrict))
|
||||
{
|
||||
if (parser->hadAbortedSyntaxParse()) {
|
||||
// Try again with a full parse.
|
||||
@ -2111,10 +2116,10 @@ Parser<FullParseHandler>::functionArgsAndBody(ParseNode *pn, HandleFunction fun,
|
||||
if (!funpc.init())
|
||||
return false;
|
||||
|
||||
if (!functionArgsAndBodyGeneric(pn, fun, funName, type, kind, strict, becameStrict))
|
||||
if (!functionArgsAndBodyGeneric(pn, fun, type, kind, becameStrict))
|
||||
return false;
|
||||
|
||||
if (!leaveFunction(pn, funName, outerpc, kind))
|
||||
if (!leaveFunction(pn, outerpc, kind))
|
||||
return false;
|
||||
|
||||
pn->pn_blockid = outerpc->blockid();
|
||||
@ -2132,9 +2137,7 @@ Parser<FullParseHandler>::functionArgsAndBody(ParseNode *pn, HandleFunction fun,
|
||||
template <>
|
||||
bool
|
||||
Parser<SyntaxParseHandler>::functionArgsAndBody(Node pn, HandleFunction fun,
|
||||
HandlePropertyName funName,
|
||||
size_t startOffset, FunctionType type,
|
||||
FunctionSyntaxKind kind,
|
||||
FunctionType type, FunctionSyntaxKind kind,
|
||||
bool strict, bool *becameStrict)
|
||||
{
|
||||
if (becameStrict)
|
||||
@ -2152,10 +2155,10 @@ Parser<SyntaxParseHandler>::functionArgsAndBody(Node pn, HandleFunction fun,
|
||||
if (!funpc.init())
|
||||
return false;
|
||||
|
||||
if (!functionArgsAndBodyGeneric(pn, fun, funName, type, kind, strict, becameStrict))
|
||||
if (!functionArgsAndBodyGeneric(pn, fun, type, kind, becameStrict))
|
||||
return false;
|
||||
|
||||
if (!leaveFunction(pn, funName, outerpc, kind))
|
||||
if (!leaveFunction(pn, outerpc, kind))
|
||||
return false;
|
||||
|
||||
// This is a lazy function inner to another lazy function. Remember the
|
||||
@ -2183,13 +2186,11 @@ Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, unsigned st
|
||||
if (!funpc.init())
|
||||
return null();
|
||||
|
||||
RootedPropertyName funName(context, fun->atom() ? fun->atom()->asPropertyName() : NULL);
|
||||
|
||||
if (!functionArgsAndBodyGeneric(pn, fun, funName, Normal, Statement, strict, NULL))
|
||||
if (!functionArgsAndBodyGeneric(pn, fun, Normal, Statement, NULL))
|
||||
return null();
|
||||
|
||||
if (fun->isNamedLambda()) {
|
||||
if (AtomDefnPtr p = pc->lexdeps->lookup(funName)) {
|
||||
if (AtomDefnPtr p = pc->lexdeps->lookup(fun->name())) {
|
||||
Definition *dn = p.value().get<FullParseHandler>();
|
||||
if (!ConvertDefinitionToNamedLambdaUse(tokenStream, pc, funbox, dn))
|
||||
return NULL;
|
||||
@ -2206,10 +2207,8 @@ Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, unsigned st
|
||||
|
||||
template <typename ParseHandler>
|
||||
bool
|
||||
Parser<ParseHandler>::functionArgsAndBodyGeneric(Node pn, HandleFunction fun,
|
||||
HandlePropertyName funName, FunctionType type,
|
||||
FunctionSyntaxKind kind,
|
||||
bool strict, bool *becameStrict)
|
||||
Parser<ParseHandler>::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, FunctionType type,
|
||||
FunctionSyntaxKind kind, bool *becameStrict)
|
||||
{
|
||||
// Given a properly initialized parse context, try to parse an actual
|
||||
// function without concern for conversion to strict mode, use of lazy
|
||||
@ -2265,7 +2264,7 @@ Parser<ParseHandler>::functionArgsAndBodyGeneric(Node pn, HandleFunction fun,
|
||||
if (!yieldGuard.empty() && !yieldGuard.ref().checkValidBody(body, JSMSG_YIELD_IN_ARROW))
|
||||
return false;
|
||||
|
||||
if (funName && !checkStrictBinding(funName, pn))
|
||||
if (fun->name() && !checkStrictBinding(fun->name(), pn))
|
||||
return false;
|
||||
|
||||
#if JS_HAS_EXPR_CLOSURES
|
||||
@ -2356,7 +2355,7 @@ Parser<ParseHandler>::functionStmt()
|
||||
!report(ParseStrictError, pc->sc->strict, null(), JSMSG_STRICT_FUNCTION_STATEMENT))
|
||||
return null();
|
||||
|
||||
return functionDef(name, start, tokenStream.positionToOffset(start), Normal, Statement);
|
||||
return functionDef(name, start, Normal, Statement);
|
||||
}
|
||||
|
||||
template <typename ParseHandler>
|
||||
@ -2371,7 +2370,7 @@ Parser<ParseHandler>::functionExpr()
|
||||
name = tokenStream.currentToken().name();
|
||||
else
|
||||
tokenStream.ungetToken();
|
||||
return functionDef(name, start, tokenStream.positionToOffset(start), Normal, Expression);
|
||||
return functionDef(name, start, Normal, Expression);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2919,57 +2918,6 @@ Parser<FullParseHandler>::bindDestructuringVar(BindData<FullParseHandler> *data,
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Here, we are destructuring {... P: Q, ...} = R, where P is any id, Q is any
|
||||
* LHS expression except a destructuring initialiser, and R is on the stack.
|
||||
* Because R is already evaluated, the usual LHS-specialized bytecodes won't
|
||||
* work. After pushing R[P] we need to evaluate Q's "reference base" QB and
|
||||
* then push its property name QN. At this point the stack looks like
|
||||
*
|
||||
* [... R, R[P], QB, QN]
|
||||
*
|
||||
* We need to set QB[QN] = R[P]. This is a job for JSOP_ENUMELEM, which takes
|
||||
* its operands with left-hand side above right-hand side:
|
||||
*
|
||||
* [rval, lval, xval]
|
||||
*
|
||||
* and pops all three values, setting lval[xval] = rval. But we cannot select
|
||||
* JSOP_ENUMELEM yet, because the LHS may turn out to be an arg or local var,
|
||||
* which can be optimized further. So we select JSOP_SETNAME.
|
||||
*/
|
||||
template <>
|
||||
bool
|
||||
Parser<FullParseHandler>::bindDestructuringLHS(ParseNode *pn)
|
||||
{
|
||||
switch (pn->getKind()) {
|
||||
case PNK_NAME:
|
||||
pn->markAsAssigned();
|
||||
/* FALL THROUGH */
|
||||
|
||||
case PNK_DOT:
|
||||
case PNK_ELEM:
|
||||
/*
|
||||
* We may be called on a name node that has already been specialized,
|
||||
* in the very weird and ECMA-262-required "for (var [x] = i in o) ..."
|
||||
* case. See bug 558633.
|
||||
*/
|
||||
if (!(js_CodeSpec[pn->getOp()].format & JOF_SET))
|
||||
pn->setOp(JSOP_SETNAME);
|
||||
break;
|
||||
|
||||
case PNK_CALL:
|
||||
if (!makeSetCall(pn, JSMSG_BAD_LEFTSIDE_OF_ASS))
|
||||
return false;
|
||||
break;
|
||||
|
||||
default:
|
||||
report(ParseError, false, pn, JSMSG_BAD_LEFTSIDE_OF_ASS);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Destructuring patterns can appear in two kinds of contexts:
|
||||
*
|
||||
@ -3038,7 +2986,7 @@ Parser<FullParseHandler>::checkDestructuring(BindData<FullParseHandler> *data,
|
||||
}
|
||||
ok = bindDestructuringVar(data, pn);
|
||||
} else {
|
||||
ok = bindDestructuringLHS(pn);
|
||||
ok = checkAndMarkAsAssignmentLhs(pn, KeyedDestructuringAssignment);
|
||||
}
|
||||
}
|
||||
if (!ok)
|
||||
@ -3072,7 +3020,7 @@ Parser<FullParseHandler>::checkDestructuring(BindData<FullParseHandler> *data,
|
||||
if (!noteNameUse(name, pn))
|
||||
return false;
|
||||
}
|
||||
ok = bindDestructuringLHS(pn);
|
||||
ok = checkAndMarkAsAssignmentLhs(pn, KeyedDestructuringAssignment);
|
||||
}
|
||||
if (!ok)
|
||||
return false;
|
||||
@ -3954,7 +3902,7 @@ Parser<FullParseHandler>::forStatement()
|
||||
pn2 = pn1;
|
||||
pn1 = NULL;
|
||||
|
||||
if (!setAssignmentLhsOps(pn2, true))
|
||||
if (!checkAndMarkAsAssignmentLhs(pn2, PlainAssignment))
|
||||
return null();
|
||||
}
|
||||
|
||||
@ -4179,7 +4127,7 @@ Parser<SyntaxParseHandler>::forStatement()
|
||||
return null();
|
||||
}
|
||||
|
||||
if (!isForDecl && !setAssignmentLhsOps(lhsNode, true))
|
||||
if (!isForDecl && !checkAndMarkAsAssignmentLhs(lhsNode, PlainAssignment))
|
||||
return null();
|
||||
|
||||
if (!expr())
|
||||
@ -5086,25 +5034,34 @@ Parser<ParseHandler>::condExpr1()
|
||||
|
||||
template <>
|
||||
bool
|
||||
Parser<FullParseHandler>::setAssignmentLhsOps(ParseNode *pn, bool isPlainAssignment)
|
||||
Parser<FullParseHandler>::checkAndMarkAsAssignmentLhs(ParseNode *pn, AssignmentFlavor flavor)
|
||||
{
|
||||
switch (pn->getKind()) {
|
||||
case PNK_NAME:
|
||||
if (!checkStrictAssignment(pn))
|
||||
if (!checkStrictAssignment(pn, flavor))
|
||||
return false;
|
||||
pn->setOp(pn->isOp(JSOP_GETLOCAL) ? JSOP_SETLOCAL : JSOP_SETNAME);
|
||||
if (flavor == KeyedDestructuringAssignment) {
|
||||
/*
|
||||
* We may be called on a name node that has already been
|
||||
* specialized, in the very weird "for (var [x] = i in o) ..."
|
||||
* case. See bug 558633.
|
||||
*/
|
||||
if (!(js_CodeSpec[pn->getOp()].format & JOF_SET))
|
||||
pn->setOp(JSOP_SETNAME);
|
||||
} else {
|
||||
pn->setOp(pn->isOp(JSOP_GETLOCAL) ? JSOP_SETLOCAL : JSOP_SETNAME);
|
||||
}
|
||||
pn->markAsAssigned();
|
||||
break;
|
||||
|
||||
case PNK_DOT:
|
||||
pn->setOp(JSOP_SETPROP);
|
||||
break;
|
||||
case PNK_ELEM:
|
||||
pn->setOp(JSOP_SETELEM);
|
||||
break;
|
||||
|
||||
#if JS_HAS_DESTRUCTURING
|
||||
case PNK_ARRAY:
|
||||
case PNK_OBJECT:
|
||||
if (!isPlainAssignment) {
|
||||
if (flavor == CompoundAssignment) {
|
||||
report(ParseError, false, null(), JSMSG_BAD_DESTRUCT_ASS);
|
||||
return false;
|
||||
}
|
||||
@ -5112,12 +5069,14 @@ Parser<FullParseHandler>::setAssignmentLhsOps(ParseNode *pn, bool isPlainAssignm
|
||||
return false;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case PNK_CALL:
|
||||
if (!makeSetCall(pn, JSMSG_BAD_LEFTSIDE_OF_ASS))
|
||||
return false;
|
||||
break;
|
||||
|
||||
default:
|
||||
report(ParseError, false, null(), JSMSG_BAD_LEFTSIDE_OF_ASS);
|
||||
report(ParseError, false, pn, JSMSG_BAD_LEFTSIDE_OF_ASS);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -5125,7 +5084,7 @@ Parser<FullParseHandler>::setAssignmentLhsOps(ParseNode *pn, bool isPlainAssignm
|
||||
|
||||
template <>
|
||||
bool
|
||||
Parser<SyntaxParseHandler>::setAssignmentLhsOps(Node pn, bool isPlainAssignment)
|
||||
Parser<SyntaxParseHandler>::checkAndMarkAsAssignmentLhs(Node pn, AssignmentFlavor flavor)
|
||||
{
|
||||
/* Full syntax checking of valid assignment LHS terms requires a parse tree. */
|
||||
if (pn != SyntaxParseHandler::NodeName &&
|
||||
@ -5134,7 +5093,7 @@ Parser<SyntaxParseHandler>::setAssignmentLhsOps(Node pn, bool isPlainAssignment)
|
||||
{
|
||||
return abortIfSyntaxParser();
|
||||
}
|
||||
return checkStrictAssignment(pn);
|
||||
return checkStrictAssignment(pn, flavor);
|
||||
}
|
||||
|
||||
template <typename ParseHandler>
|
||||
@ -5202,10 +5161,9 @@ Parser<ParseHandler>::assignExpr()
|
||||
|
||||
if (tokenStream.getToken() == TOK_ERROR)
|
||||
return null();
|
||||
size_t offset = pos().begin;
|
||||
tokenStream.ungetToken();
|
||||
|
||||
return functionDef(NullPtr(), start, offset, Normal, Arrow);
|
||||
return functionDef(NullPtr(), start, Normal, Arrow);
|
||||
}
|
||||
|
||||
default:
|
||||
@ -5214,7 +5172,8 @@ Parser<ParseHandler>::assignExpr()
|
||||
return lhs;
|
||||
}
|
||||
|
||||
if (!setAssignmentLhsOps(lhs, kind == PNK_ASSIGN))
|
||||
AssignmentFlavor flavor = kind == PNK_ASSIGN ? PlainAssignment : CompoundAssignment;
|
||||
if (!checkAndMarkAsAssignmentLhs(lhs, flavor))
|
||||
return null();
|
||||
|
||||
Node rhs = assignExpr();
|
||||
@ -5224,59 +5183,48 @@ Parser<ParseHandler>::assignExpr()
|
||||
return handler.newBinaryOrAppend(kind, lhs, rhs, pc, op);
|
||||
}
|
||||
|
||||
template <> bool
|
||||
Parser<FullParseHandler>::setLvalKid(ParseNode *pn, ParseNode *kid, const char *name)
|
||||
{
|
||||
if (!kid->isKind(PNK_NAME) &&
|
||||
!kid->isKind(PNK_DOT) &&
|
||||
(!kid->isKind(PNK_CALL) ||
|
||||
(!kid->isOp(JSOP_CALL) && !kid->isOp(JSOP_EVAL) &&
|
||||
!kid->isOp(JSOP_FUNCALL) && !kid->isOp(JSOP_FUNAPPLY))) &&
|
||||
!kid->isKind(PNK_ELEM))
|
||||
{
|
||||
report(ParseError, false, null(), JSMSG_BAD_OPERAND, name);
|
||||
return false;
|
||||
}
|
||||
if (!checkStrictAssignment(kid))
|
||||
return false;
|
||||
pn->pn_kid = kid;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char incop_name_str[][10] = {"increment", "decrement"};
|
||||
|
||||
template <>
|
||||
bool
|
||||
Parser<FullParseHandler>::setIncOpKid(ParseNode *pn, ParseNode *kid, TokenKind tt, bool preorder)
|
||||
Parser<FullParseHandler>::checkAndMarkAsIncOperand(ParseNode *kid, TokenKind tt, bool preorder)
|
||||
{
|
||||
if (!setLvalKid(pn, kid, incop_name_str[tt == TOK_DEC]))
|
||||
// Check.
|
||||
if (!kid->isKind(PNK_NAME) &&
|
||||
!kid->isKind(PNK_DOT) &&
|
||||
!kid->isKind(PNK_ELEM) &&
|
||||
!(kid->isKind(PNK_CALL) &&
|
||||
(kid->isOp(JSOP_CALL) ||
|
||||
kid->isOp(JSOP_EVAL) ||
|
||||
kid->isOp(JSOP_FUNCALL) ||
|
||||
kid->isOp(JSOP_FUNAPPLY))))
|
||||
{
|
||||
report(ParseError, false, null(), JSMSG_BAD_OPERAND, incop_name_str[tt == TOK_DEC]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checkStrictAssignment(kid, IncDecAssignment))
|
||||
return false;
|
||||
|
||||
switch (kid->getKind()) {
|
||||
case PNK_NAME:
|
||||
// Mark.
|
||||
if (kid->isKind(PNK_NAME)) {
|
||||
kid->markAsAssigned();
|
||||
break;
|
||||
|
||||
case PNK_CALL:
|
||||
} else if (kid->isKind(PNK_CALL)) {
|
||||
if (!makeSetCall(kid, JSMSG_BAD_INCOP_OPERAND))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case PNK_DOT:
|
||||
case PNK_ELEM:
|
||||
break;
|
||||
|
||||
default:
|
||||
JS_ASSERT(0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool
|
||||
Parser<SyntaxParseHandler>::setIncOpKid(Node pn, Node kid, TokenKind tt, bool preorder)
|
||||
Parser<SyntaxParseHandler>::checkAndMarkAsIncOperand(Node kid, TokenKind tt, bool preorder)
|
||||
{
|
||||
return setAssignmentLhsOps(kid, false);
|
||||
// To the extent of what we support in syntax-parse mode, the rules for
|
||||
// inc/dec operands are the same as for assignment. There are differences,
|
||||
// such as destructuring; but if we hit any of those cases, we'll abort and
|
||||
// reparse in full mode.
|
||||
return checkAndMarkAsAssignmentLhs(kid, IncDecAssignment);
|
||||
}
|
||||
|
||||
template <typename ParseHandler>
|
||||
@ -5320,15 +5268,12 @@ Parser<ParseHandler>::unaryExpr()
|
||||
pn2 = memberExpr(tt2, true);
|
||||
if (!pn2)
|
||||
return null();
|
||||
pn = handler.newUnary((tt == TOK_INC) ? PNK_PREINCREMENT : PNK_PREDECREMENT,
|
||||
JSOP_NOP,
|
||||
begin,
|
||||
pn2);
|
||||
if (!pn)
|
||||
if (!checkAndMarkAsIncOperand(pn2, tt, true))
|
||||
return null();
|
||||
if (!setIncOpKid(pn, pn2, tt, true))
|
||||
return null();
|
||||
break;
|
||||
return handler.newUnary((tt == TOK_INC) ? PNK_PREINCREMENT : PNK_PREDECREMENT,
|
||||
JSOP_NOP,
|
||||
begin,
|
||||
pn2);
|
||||
}
|
||||
|
||||
case TOK_DELETE: {
|
||||
@ -5359,19 +5304,16 @@ Parser<ParseHandler>::unaryExpr()
|
||||
tt = tokenStream.peekTokenSameLine(TSF_OPERAND);
|
||||
if (tt == TOK_INC || tt == TOK_DEC) {
|
||||
tokenStream.consumeKnownToken(tt);
|
||||
pn2 = handler.newUnary((tt == TOK_INC) ? PNK_POSTINCREMENT : PNK_POSTDECREMENT,
|
||||
JSOP_NOP,
|
||||
begin,
|
||||
pn);
|
||||
if (!pn2)
|
||||
if (!checkAndMarkAsIncOperand(pn, tt, false))
|
||||
return null();
|
||||
if (!setIncOpKid(pn2, pn, tt, false))
|
||||
return null();
|
||||
pn = pn2;
|
||||
return handler.newUnary((tt == TOK_INC) ? PNK_POSTINCREMENT : PNK_POSTDECREMENT,
|
||||
JSOP_NOP,
|
||||
begin,
|
||||
pn);
|
||||
}
|
||||
break;
|
||||
return pn;
|
||||
}
|
||||
return pn;
|
||||
MOZ_ASSUME_UNREACHABLE("unaryExpr");
|
||||
}
|
||||
|
||||
/*
|
||||
@ -5645,8 +5587,8 @@ CompExprTransplanter::transplant(ParseNode *pn)
|
||||
* generator) a use of a new placeholder in the generator's
|
||||
* lexdeps.
|
||||
*/
|
||||
Definition *dn2 = parser->handler.newPlaceholder(
|
||||
atom, parser->pc->inBlock(), parser->pc->blockid(), parser->pos());
|
||||
Definition *dn2 = parser->handler.newPlaceholder(atom, parser->pc->blockid(),
|
||||
parser->pos());
|
||||
if (!dn2)
|
||||
return false;
|
||||
dn2->pn_pos = root->pn_pos;
|
||||
@ -6074,8 +6016,7 @@ Parser<FullParseHandler>::generatorExpr(ParseNode *kid)
|
||||
genfn->pn_pos.begin = body->pn_pos.begin = kid->pn_pos.begin;
|
||||
genfn->pn_pos.end = body->pn_pos.end = pos().end;
|
||||
|
||||
RootedPropertyName funName(context);
|
||||
if (!leaveFunction(genfn, funName, outerpc))
|
||||
if (!leaveFunction(genfn, outerpc))
|
||||
return null();
|
||||
}
|
||||
|
||||
@ -6270,27 +6211,11 @@ Parser<ParseHandler>::memberExpr(TokenKind tt, bool allowCallSyntax)
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template <typename ParseHandler>
|
||||
typename ParseHandler::Node
|
||||
Parser<ParseHandler>::bracketedExpr()
|
||||
{
|
||||
/*
|
||||
* Always accept the 'in' operator in a parenthesized expression,
|
||||
* where it's unambiguous, even if we might be parsing the init of a
|
||||
* for statement.
|
||||
*/
|
||||
bool oldParsingForInit = pc->parsingForInit;
|
||||
pc->parsingForInit = false;
|
||||
Node pn = expr();
|
||||
pc->parsingForInit = oldParsingForInit;
|
||||
return pn;
|
||||
}
|
||||
|
||||
template <typename ParseHandler>
|
||||
typename ParseHandler::Node
|
||||
Parser<ParseHandler>::newName(PropertyName *name)
|
||||
{
|
||||
return handler.newName(name, pc->inBlock(), pc->blockid(), pos());
|
||||
return handler.newName(name, pc->blockid(), pos());
|
||||
}
|
||||
|
||||
template <typename ParseHandler>
|
||||
@ -6573,8 +6498,7 @@ Parser<ParseHandler>::primaryExpr(TokenKind tt)
|
||||
Rooted<PropertyName*> funName(context, NULL);
|
||||
TokenStream::Position start(keepAtoms);
|
||||
tokenStream.tell(&start);
|
||||
pn2 = functionDef(funName, start, tokenStream.positionToOffset(start),
|
||||
op == JSOP_INITPROP_GETTER ? Getter : Setter,
|
||||
pn2 = functionDef(funName, start, op == JSOP_INITPROP_GETTER ? Getter : Setter,
|
||||
Expression);
|
||||
if (!pn2)
|
||||
return null();
|
||||
@ -6800,7 +6724,16 @@ Parser<ParseHandler>::parenExpr(bool *genexp)
|
||||
|
||||
GenexpGuard<ParseHandler> guard(this);
|
||||
|
||||
Node pn = bracketedExpr();
|
||||
/*
|
||||
* Always accept the 'in' operator in a parenthesized expression,
|
||||
* where it's unambiguous, even if we might be parsing the init of a
|
||||
* for statement.
|
||||
*/
|
||||
bool oldParsingForInit = pc->parsingForInit;
|
||||
pc->parsingForInit = false;
|
||||
Node pn = expr();
|
||||
pc->parsingForInit = oldParsingForInit;
|
||||
|
||||
if (!pn)
|
||||
return null();
|
||||
guard.endBody();
|
||||
|
@ -272,7 +272,6 @@ struct ParseContext : public GenericParseContext
|
||||
return decls_.init() && lexdeps.ensureMap(sc->context);
|
||||
}
|
||||
|
||||
InBlockBool inBlock() const { return InBlockBool(!topStmt || topStmt->type == STMT_BLOCK); }
|
||||
unsigned blockid() { return topStmt ? topStmt->blockid : bodyid; }
|
||||
|
||||
// True if we are at the topmost level of a entire script or function body.
|
||||
@ -416,9 +415,8 @@ class Parser : private AutoGCRooter, public StrictModeGetter
|
||||
enum FunctionBodyType { StatementListBody, ExpressionBody };
|
||||
Node functionBody(FunctionSyntaxKind kind, FunctionBodyType type);
|
||||
|
||||
bool functionArgsAndBodyGeneric(Node pn, HandleFunction fun,
|
||||
HandlePropertyName funName, FunctionType type,
|
||||
FunctionSyntaxKind kind, bool strict, bool *becameStrict);
|
||||
bool functionArgsAndBodyGeneric(Node pn, HandleFunction fun, FunctionType type,
|
||||
FunctionSyntaxKind kind, bool *becameStrict);
|
||||
|
||||
virtual bool strictMode() { return pc->sc->strict; }
|
||||
|
||||
@ -486,9 +484,9 @@ class Parser : private AutoGCRooter, public StrictModeGetter
|
||||
bool functionArguments(FunctionSyntaxKind kind, Node *list, Node funcpn, bool &hasRest);
|
||||
|
||||
Node functionDef(HandlePropertyName name, const TokenStream::Position &start,
|
||||
size_t startOffset, FunctionType type, FunctionSyntaxKind kind);
|
||||
bool functionArgsAndBody(Node pn, HandleFunction fun, HandlePropertyName funName,
|
||||
size_t startOffset, FunctionType type, FunctionSyntaxKind kind,
|
||||
FunctionType type, FunctionSyntaxKind kind);
|
||||
bool functionArgsAndBody(Node pn, HandleFunction fun,
|
||||
FunctionType type, FunctionSyntaxKind kind,
|
||||
bool strict, bool *becameStrict = NULL);
|
||||
|
||||
Node unaryOpExpr(ParseNodeKind kind, JSOp op, uint32_t begin);
|
||||
@ -500,7 +498,6 @@ class Parser : private AutoGCRooter, public StrictModeGetter
|
||||
bool arrayInitializerComprehensionTail(Node pn);
|
||||
Node generatorExpr(Node kid);
|
||||
bool argumentList(Node listNode);
|
||||
Node bracketedExpr();
|
||||
Node letBlock(LetContext letContext);
|
||||
Node destructuringExpr(BindData<ParseHandler> *data, TokenKind tt);
|
||||
|
||||
@ -514,7 +511,14 @@ class Parser : private AutoGCRooter, public StrictModeGetter
|
||||
#endif
|
||||
}
|
||||
|
||||
bool setAssignmentLhsOps(Node pn, bool isPlainAssignment);
|
||||
enum AssignmentFlavor {
|
||||
PlainAssignment,
|
||||
CompoundAssignment,
|
||||
KeyedDestructuringAssignment,
|
||||
IncDecAssignment
|
||||
};
|
||||
|
||||
bool checkAndMarkAsAssignmentLhs(Node pn, AssignmentFlavor flavor);
|
||||
bool matchInOrOf(bool *isForOfp);
|
||||
|
||||
bool checkFunctionArguments();
|
||||
@ -526,10 +530,9 @@ class Parser : private AutoGCRooter, public StrictModeGetter
|
||||
|
||||
bool isValidForStatementLHS(Node pn1, JSVersion version,
|
||||
bool forDecl, bool forEach, bool forOf);
|
||||
bool setLvalKid(Node pn, Node kid, const char *name);
|
||||
bool setIncOpKid(Node pn, Node kid, TokenKind tt, bool preorder);
|
||||
bool checkStrictAssignment(Node lhs);
|
||||
bool checkStrictBinding(HandlePropertyName name, Node pn);
|
||||
bool checkAndMarkAsIncOperand(Node kid, TokenKind tt, bool preorder);
|
||||
bool checkStrictAssignment(Node lhs, AssignmentFlavor flavor);
|
||||
bool checkStrictBinding(PropertyName *name, Node pn);
|
||||
bool defineArg(Node funcpn, HandlePropertyName name,
|
||||
bool disallowDuplicateArgs = false, Node *duplicatedArg = NULL);
|
||||
Node pushLexicalScope(StmtInfoPC *stmt);
|
||||
@ -568,8 +571,7 @@ class Parser : private AutoGCRooter, public StrictModeGetter
|
||||
bool checkFinalReturn(Node pn);
|
||||
DefinitionNode getOrCreateLexicalDependency(ParseContext<ParseHandler> *pc, JSAtom *atom);
|
||||
|
||||
bool leaveFunction(Node fn, HandlePropertyName funName,
|
||||
ParseContext<ParseHandler> *outerpc,
|
||||
bool leaveFunction(Node fn, ParseContext<ParseHandler> *outerpc,
|
||||
FunctionSyntaxKind kind = Expression);
|
||||
|
||||
TokenPos pos() const { return tokenStream.currentToken().pos; }
|
||||
@ -583,11 +585,11 @@ class Parser : private AutoGCRooter, public StrictModeGetter
|
||||
|
||||
template <>
|
||||
bool
|
||||
Parser<FullParseHandler>::setAssignmentLhsOps(ParseNode *pn, bool isPlainAssignment);
|
||||
Parser<FullParseHandler>::checkAndMarkAsAssignmentLhs(ParseNode *pn, AssignmentFlavor flavor);
|
||||
|
||||
template <>
|
||||
bool
|
||||
Parser<SyntaxParseHandler>::setAssignmentLhsOps(Node pn, bool isPlainAssignment);
|
||||
Parser<SyntaxParseHandler>::checkAndMarkAsAssignmentLhs(Node pn, AssignmentFlavor flavor);
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
@ -53,14 +53,12 @@ class SyntaxParseHandler
|
||||
|
||||
void trace(JSTracer *trc) {}
|
||||
|
||||
Node newName(PropertyName *name, InBlockBool inBlock, uint32_t blockid, const TokenPos &pos) {
|
||||
Node newName(PropertyName *name, uint32_t blockid, const TokenPos &pos) {
|
||||
lastAtom = name;
|
||||
return NodeName;
|
||||
}
|
||||
|
||||
DefinitionNode newPlaceholder(JSAtom *atom, InBlockBool inBlock, uint32_t blockid,
|
||||
const TokenPos &pos)
|
||||
{
|
||||
DefinitionNode newPlaceholder(JSAtom *atom, uint32_t blockid, const TokenPos &pos) {
|
||||
return Definition::PLACEHOLDER;
|
||||
}
|
||||
|
||||
|
@ -132,6 +132,11 @@ class EncapsulatedPtr
|
||||
|
||||
~EncapsulatedPtr() { pre(); }
|
||||
|
||||
void init(T *v) {
|
||||
JS_ASSERT(!IsPoisonedPtr<T>(v));
|
||||
this->value = v;
|
||||
}
|
||||
|
||||
/* Use to set the pointer to NULL. */
|
||||
void clear() {
|
||||
pre();
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
using namespace js;
|
||||
using namespace js::gc;
|
||||
using mozilla::ReentrancyGuard;
|
||||
|
||||
/*** SlotEdge ***/
|
||||
|
||||
@ -147,6 +148,7 @@ template <typename T>
|
||||
void
|
||||
StoreBuffer::MonoTypeBuffer<T>::mark(JSTracer *trc)
|
||||
{
|
||||
ReentrancyGuard g(*this);
|
||||
compact();
|
||||
T *cursor = base;
|
||||
while (cursor != pos) {
|
||||
@ -240,6 +242,8 @@ StoreBuffer::GenericBuffer::clear()
|
||||
void
|
||||
StoreBuffer::GenericBuffer::mark(JSTracer *trc)
|
||||
{
|
||||
ReentrancyGuard g(*this);
|
||||
|
||||
uint8_t *p = base;
|
||||
while (p < pos) {
|
||||
unsigned size = *((unsigned *)p);
|
||||
|
@ -13,6 +13,8 @@
|
||||
# error "Generational GC requires exact rooting."
|
||||
#endif
|
||||
|
||||
#include "mozilla/ReentrancyGuard.h"
|
||||
|
||||
#include "jsalloc.h"
|
||||
#include "jsgc.h"
|
||||
#include "jsobj.h"
|
||||
@ -77,6 +79,7 @@ class StoreBuffer
|
||||
class MonoTypeBuffer
|
||||
{
|
||||
friend class StoreBuffer;
|
||||
friend class mozilla::ReentrancyGuard;
|
||||
|
||||
StoreBuffer *owner;
|
||||
|
||||
@ -97,8 +100,10 @@ class StoreBuffer
|
||||
*/
|
||||
EdgeSet duplicates;
|
||||
|
||||
bool entered;
|
||||
|
||||
MonoTypeBuffer(StoreBuffer *owner)
|
||||
: owner(owner), base(NULL), pos(NULL), top(NULL)
|
||||
: owner(owner), base(NULL), pos(NULL), top(NULL), entered(false)
|
||||
{
|
||||
duplicates.init();
|
||||
}
|
||||
@ -125,6 +130,7 @@ class StoreBuffer
|
||||
|
||||
/* Add one item to the buffer. */
|
||||
void put(const T &v) {
|
||||
mozilla::ReentrancyGuard g(*this);
|
||||
JS_ASSERT(!owner->inParallelSection());
|
||||
|
||||
/* Check if we have been enabled. */
|
||||
@ -180,6 +186,7 @@ class StoreBuffer
|
||||
class GenericBuffer
|
||||
{
|
||||
friend class StoreBuffer;
|
||||
friend class mozilla::ReentrancyGuard;
|
||||
|
||||
StoreBuffer *owner;
|
||||
|
||||
@ -187,8 +194,10 @@ class StoreBuffer
|
||||
uint8_t *pos; /* Pointer to current buffer position. */
|
||||
uint8_t *top; /* Pointer to one past the last entry. */
|
||||
|
||||
bool entered;
|
||||
|
||||
GenericBuffer(StoreBuffer *owner)
|
||||
: owner(owner)
|
||||
: owner(owner), base(NULL), pos(NULL), top(NULL), entered(false)
|
||||
{}
|
||||
|
||||
GenericBuffer &operator=(const GenericBuffer& other) MOZ_DELETE;
|
||||
@ -202,6 +211,7 @@ class StoreBuffer
|
||||
|
||||
template <typename T>
|
||||
void put(const T &t) {
|
||||
mozilla::ReentrancyGuard g(*this);
|
||||
JS_ASSERT(!owner->inParallelSection());
|
||||
|
||||
/* Ensure T is derived from BufferableRef. */
|
||||
|
@ -3571,6 +3571,45 @@ CodeGenerator::visitMathFunctionD(LMathFunctionD *ins)
|
||||
case MMathFunction::ACos:
|
||||
funptr = JS_FUNC_TO_DATA_PTR(void *, js::math_acos_impl);
|
||||
break;
|
||||
case MMathFunction::Log10:
|
||||
funptr = JS_FUNC_TO_DATA_PTR(void *, js::math_log10_impl);
|
||||
break;
|
||||
case MMathFunction::Log2:
|
||||
funptr = JS_FUNC_TO_DATA_PTR(void *, js::math_log2_impl);
|
||||
break;
|
||||
case MMathFunction::Log1P:
|
||||
funptr = JS_FUNC_TO_DATA_PTR(void *, js::math_log1p_impl);
|
||||
break;
|
||||
case MMathFunction::ExpM1:
|
||||
funptr = JS_FUNC_TO_DATA_PTR(void *, js::math_expm1_impl);
|
||||
break;
|
||||
case MMathFunction::CosH:
|
||||
funptr = JS_FUNC_TO_DATA_PTR(void *, js::math_cosh_impl);
|
||||
break;
|
||||
case MMathFunction::SinH:
|
||||
funptr = JS_FUNC_TO_DATA_PTR(void *, js::math_sinh_impl);
|
||||
break;
|
||||
case MMathFunction::TanH:
|
||||
funptr = JS_FUNC_TO_DATA_PTR(void *, js::math_tanh_impl);
|
||||
break;
|
||||
case MMathFunction::ACosH:
|
||||
funptr = JS_FUNC_TO_DATA_PTR(void *, js::math_acosh_impl);
|
||||
break;
|
||||
case MMathFunction::ASinH:
|
||||
funptr = JS_FUNC_TO_DATA_PTR(void *, js::math_asinh_impl);
|
||||
break;
|
||||
case MMathFunction::ATanH:
|
||||
funptr = JS_FUNC_TO_DATA_PTR(void *, js::math_atanh_impl);
|
||||
break;
|
||||
case MMathFunction::Sign:
|
||||
funptr = JS_FUNC_TO_DATA_PTR(void *, js::math_sign_impl);
|
||||
break;
|
||||
case MMathFunction::Trunc:
|
||||
funptr = JS_FUNC_TO_DATA_PTR(void *, js::math_trunc_impl);
|
||||
break;
|
||||
case MMathFunction::Cbrt:
|
||||
funptr = JS_FUNC_TO_DATA_PTR(void *, js::math_cbrt_impl);
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSUME_UNREACHABLE("Unknown math function");
|
||||
}
|
||||
|
@ -7903,13 +7903,8 @@ IonBuilder::getPropTryCache(bool *emitted, HandlePropertyName name, HandleId id,
|
||||
return true;
|
||||
}
|
||||
|
||||
MIRType rvalType = MIRTypeFromValueType(types->getKnownTypeTag());
|
||||
if (barrier || IsNullOrUndefined(rvalType) || accessGetter)
|
||||
rvalType = MIRType_Value;
|
||||
|
||||
current->pop();
|
||||
MGetPropertyCache *load = MGetPropertyCache::New(obj, name);
|
||||
load->setResultType(rvalType);
|
||||
|
||||
// Try to mark the cache as idempotent. We only do this if JM is enabled
|
||||
// (its ICs are used to mark property reads as likely non-idempotent) or
|
||||
@ -7944,6 +7939,21 @@ IonBuilder::getPropTryCache(bool *emitted, HandlePropertyName name, HandleId id,
|
||||
if (accessGetter)
|
||||
barrier = true;
|
||||
|
||||
// ParallelGetPropertyIC cannot safely call TypeScript::Monitor to ensure
|
||||
// that the observed type set contains undefined. To account for possible
|
||||
// missing properties, which property types do not track, we must always
|
||||
// insert a type barrier.
|
||||
if (info().executionMode() == ParallelExecution &&
|
||||
!types->hasType(types::Type::UndefinedType()))
|
||||
{
|
||||
barrier = true;
|
||||
}
|
||||
|
||||
MIRType rvalType = MIRTypeFromValueType(types->getKnownTypeTag());
|
||||
if (barrier || IsNullOrUndefined(rvalType))
|
||||
rvalType = MIRType_Value;
|
||||
load->setResultType(rvalType);
|
||||
|
||||
if (!pushTypeBarrier(load, types, barrier))
|
||||
return false;
|
||||
|
||||
|
@ -72,6 +72,32 @@ IonBuilder::inlineNativeCall(CallInfo &callInfo, JSNative native)
|
||||
return inlineMathFunction(callInfo, MMathFunction::ASin);
|
||||
if (native == js::math_acos)
|
||||
return inlineMathFunction(callInfo, MMathFunction::ACos);
|
||||
if (native == js::math_log10)
|
||||
return inlineMathFunction(callInfo, MMathFunction::Log10);
|
||||
if (native == js::math_log2)
|
||||
return inlineMathFunction(callInfo, MMathFunction::Log2);
|
||||
if (native == js::math_log1p)
|
||||
return inlineMathFunction(callInfo, MMathFunction::Log1P);
|
||||
if (native == js::math_expm1)
|
||||
return inlineMathFunction(callInfo, MMathFunction::ExpM1);
|
||||
if (native == js::math_cosh)
|
||||
return inlineMathFunction(callInfo, MMathFunction::CosH);
|
||||
if (native == js::math_sin)
|
||||
return inlineMathFunction(callInfo, MMathFunction::SinH);
|
||||
if (native == js::math_tan)
|
||||
return inlineMathFunction(callInfo, MMathFunction::TanH);
|
||||
if (native == js::math_acosh)
|
||||
return inlineMathFunction(callInfo, MMathFunction::ACosH);
|
||||
if (native == js::math_asin)
|
||||
return inlineMathFunction(callInfo, MMathFunction::ASinH);
|
||||
if (native == js::math_atan)
|
||||
return inlineMathFunction(callInfo, MMathFunction::ATanH);
|
||||
if (native == js::math_sign)
|
||||
return inlineMathFunction(callInfo, MMathFunction::Sign);
|
||||
if (native == js::math_trunc)
|
||||
return inlineMathFunction(callInfo, MMathFunction::Trunc);
|
||||
if (native == js::math_cbrt)
|
||||
return inlineMathFunction(callInfo, MMathFunction::Cbrt);
|
||||
|
||||
// String natives.
|
||||
if (native == js_String)
|
||||
|
@ -1581,7 +1581,6 @@ MCompare::infer(JSContext *cx, BaselineInspector *inspector, jsbytecode *pc)
|
||||
bool relationalEq = !(looseEq || strictEq);
|
||||
|
||||
// Comparisons on unsigned integers may be treated as UInt32.
|
||||
MDefinition *newlhs, *newrhs;
|
||||
if (tryUseUnsignedOperands()) {
|
||||
compareType_ = Compare_UInt32;
|
||||
return;
|
||||
|
@ -3206,7 +3206,20 @@ class MMathFunction
|
||||
Tan,
|
||||
ACos,
|
||||
ASin,
|
||||
ATan
|
||||
ATan,
|
||||
Log10,
|
||||
Log2,
|
||||
Log1P,
|
||||
ExpM1,
|
||||
CosH,
|
||||
SinH,
|
||||
TanH,
|
||||
ACosH,
|
||||
ASinH,
|
||||
ATanH,
|
||||
Sign,
|
||||
Trunc,
|
||||
Cbrt
|
||||
};
|
||||
|
||||
private:
|
||||
|
@ -895,6 +895,9 @@ MAbs::computeRange()
|
||||
|
||||
Range other(getOperand(0));
|
||||
setRange(Range::abs(&other));
|
||||
|
||||
if (implicitTruncate_ && !range()->isInt32())
|
||||
setRange(new Range(INT32_MIN, INT32_MAX));
|
||||
}
|
||||
|
||||
void
|
||||
@ -917,6 +920,9 @@ MAdd::computeRange()
|
||||
Range right(getOperand(1));
|
||||
Range *next = Range::add(&left, &right);
|
||||
setRange(next);
|
||||
|
||||
if (isTruncated() && !range()->isInt32())
|
||||
setRange(new Range(INT32_MIN, INT32_MAX));
|
||||
}
|
||||
|
||||
void
|
||||
@ -928,6 +934,9 @@ MSub::computeRange()
|
||||
Range right(getOperand(1));
|
||||
Range *next = Range::sub(&left, &right);
|
||||
setRange(next);
|
||||
|
||||
if (isTruncated() && !range()->isInt32())
|
||||
setRange(new Range(INT32_MIN, INT32_MAX));
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -341,6 +341,11 @@ MacroAssemblerARM::ma_movPatchable(Imm32 imm_, Register dest,
|
||||
Assembler::Condition c, RelocStyle rs, Instruction *i)
|
||||
{
|
||||
int32_t imm = imm_.value;
|
||||
if (i) {
|
||||
// If the access goes through an iterator (which this doesn't) then
|
||||
// all issues pertaining to reading guard instructions.
|
||||
i = InstructionIterator(i).cur();
|
||||
}
|
||||
switch(rs) {
|
||||
case L_MOVWT:
|
||||
as_movw(dest, Imm16(imm & 0xffff), c, i);
|
||||
|
14
js/src/jit-test/tests/asm.js/testBug893364.js
Normal file
@ -0,0 +1,14 @@
|
||||
function m()
|
||||
{
|
||||
"use asm";
|
||||
function f()
|
||||
{
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
x = (((0x77777777 - 0xcccccccc) | 0) % -1) | 0;
|
||||
y = (((0x7FFFFFFF + 0x7FFFFFFF) | 0) % -1) | 0;
|
||||
return (x+y)|0;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
assertEq(m()(), 0)
|