mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-31 22:25:30 +00:00
Merge m-c to birch.
This commit is contained in:
commit
4054f59257
@ -772,9 +772,9 @@ HyperTextAccessible::GetRelativeOffset(nsIPresShell* aPresShell,
|
||||
}
|
||||
|
||||
int32_t
|
||||
HyperTextAccessible::FindBoundary(int32_t aOffset, nsDirection aDirection,
|
||||
nsSelectionAmount aAmount,
|
||||
EWordMovementType aWordMovementType)
|
||||
HyperTextAccessible::FindOffset(int32_t aOffset, nsDirection aDirection,
|
||||
nsSelectionAmount aAmount,
|
||||
EWordMovementType aWordMovementType)
|
||||
{
|
||||
// Convert hypertext offset to frame-relative offset.
|
||||
int32_t offsetInFrame = aOffset, notUsedOffset = aOffset;
|
||||
@ -803,6 +803,81 @@ HyperTextAccessible::FindBoundary(int32_t aOffset, nsDirection aDirection,
|
||||
aWordMovementType);
|
||||
}
|
||||
|
||||
int32_t
|
||||
HyperTextAccessible::FindLineBoundary(int32_t aOffset,
|
||||
EWhichLineBoundary aWhichLineBoundary)
|
||||
{
|
||||
// Note: empty last line doesn't have own frame (a previous line contains '\n'
|
||||
// character instead) thus when it makes a difference we need to process this
|
||||
// case separately (otherwise operations are performed on previous line).
|
||||
switch (aWhichLineBoundary) {
|
||||
case ePrevLineBegin: {
|
||||
// Fetch a previous line and move to its start (as arrow up and home keys
|
||||
// were pressed).
|
||||
if (IsEmptyLastLineOffset(aOffset))
|
||||
return FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
|
||||
|
||||
int32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine);
|
||||
return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine);
|
||||
}
|
||||
|
||||
case ePrevLineEnd: {
|
||||
if (IsEmptyLastLineOffset(aOffset))
|
||||
return aOffset - 1;
|
||||
|
||||
// If offset is at first line then return 0 (first line start).
|
||||
int32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
|
||||
if (tmpOffset == 0)
|
||||
return 0;
|
||||
|
||||
// Otherwise move to end of previous line (as arrow up and end keys were
|
||||
// pressed).
|
||||
tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine);
|
||||
return FindOffset(tmpOffset, eDirNext, eSelectEndLine);
|
||||
}
|
||||
|
||||
case eThisLineBegin:
|
||||
if (IsEmptyLastLineOffset(aOffset))
|
||||
return aOffset;
|
||||
|
||||
// Move to begin of the current line (as home key was pressed).
|
||||
return FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
|
||||
|
||||
case eThisLineEnd:
|
||||
if (IsEmptyLastLineOffset(aOffset))
|
||||
return aOffset;
|
||||
|
||||
// Move to end of the current line (as end key was pressed).
|
||||
return FindOffset(aOffset, eDirNext, eSelectEndLine);
|
||||
|
||||
case eNextLineBegin: {
|
||||
if (IsEmptyLastLineOffset(aOffset))
|
||||
return aOffset;
|
||||
|
||||
// Move to begin of the next line if any (arrow down and home keys),
|
||||
// otherwise end of the current line (arrow down only).
|
||||
int32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine);
|
||||
if (tmpOffset == CharacterCount())
|
||||
return tmpOffset;
|
||||
|
||||
return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine);
|
||||
}
|
||||
|
||||
case eNextLineEnd: {
|
||||
if (IsEmptyLastLineOffset(aOffset))
|
||||
return aOffset;
|
||||
|
||||
// Move to next line end (as down arrow and end key were pressed).
|
||||
int32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine);
|
||||
if (tmpOffset != CharacterCount())
|
||||
return FindOffset(tmpOffset, eDirNext, eSelectEndLine);
|
||||
return tmpOffset;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
Gets the specified text relative to aBoundaryType, which means:
|
||||
BOUNDARY_CHAR The character before/at/after the offset is returned.
|
||||
@ -1029,54 +1104,21 @@ HyperTextAccessible::GetTextBeforeOffset(int32_t aOffset,
|
||||
return GetText(*aStartOffset, *aEndOffset, aText);
|
||||
}
|
||||
|
||||
case BOUNDARY_LINE_START: {
|
||||
case BOUNDARY_LINE_START:
|
||||
if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
|
||||
offset = AdjustCaretOffset(offset);
|
||||
|
||||
// If we are at last empty then home key and get the text (last empty line
|
||||
// doesn't have own frame).
|
||||
if (IsEmptyLastLineOffset(offset)) {
|
||||
*aStartOffset = FindLineBoundary(offset, eDirPrevious, eSelectBeginLine);
|
||||
*aEndOffset = offset;
|
||||
return GetText(*aStartOffset, *aEndOffset, aText);
|
||||
}
|
||||
|
||||
// Home key, up arrow, home key.
|
||||
*aEndOffset = FindLineBoundary(offset, eDirPrevious, eSelectBeginLine);
|
||||
*aStartOffset = FindLineBoundary(offset, eDirPrevious, eSelectLine);
|
||||
*aStartOffset = FindLineBoundary(*aStartOffset, eDirPrevious, eSelectBeginLine);
|
||||
|
||||
*aStartOffset = FindLineBoundary(offset, ePrevLineBegin);
|
||||
*aEndOffset = FindLineBoundary(offset, eThisLineBegin);
|
||||
return GetText(*aStartOffset, *aEndOffset, aText);
|
||||
}
|
||||
|
||||
case BOUNDARY_LINE_END: {
|
||||
case BOUNDARY_LINE_END:
|
||||
if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
|
||||
offset = AdjustCaretOffset(offset);
|
||||
|
||||
// Nothing if we are at first line.
|
||||
int32_t tmpOffset = FindLineBoundary(offset, eDirPrevious, eSelectBeginLine);
|
||||
if (tmpOffset == 0) {
|
||||
*aStartOffset = *aEndOffset = 0;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Up arrow, end key to find previous line endings.
|
||||
if (IsEmptyLastLineOffset(offset)) { // no own frame for a last line
|
||||
tmpOffset = FindLineBoundary(offset, eDirPrevious, eSelectLine);
|
||||
*aStartOffset = FindLineBoundary(tmpOffset, eDirNext, eSelectEndLine);
|
||||
*aEndOffset = offset - 1;
|
||||
return GetText(*aStartOffset, *aEndOffset, aText);
|
||||
}
|
||||
|
||||
tmpOffset = FindLineBoundary(offset, eDirPrevious, eSelectLine);
|
||||
*aEndOffset = FindLineBoundary(tmpOffset, eDirNext, eSelectEndLine);
|
||||
tmpOffset = FindLineBoundary(*aEndOffset, eDirPrevious, eSelectLine);
|
||||
*aStartOffset = FindLineBoundary(tmpOffset, eDirNext, eSelectEndLine);
|
||||
if (*aStartOffset == *aEndOffset) // we are at second line
|
||||
*aStartOffset = 0;
|
||||
|
||||
*aEndOffset = FindLineBoundary(offset, ePrevLineEnd);
|
||||
*aStartOffset = FindLineBoundary(*aEndOffset, ePrevLineEnd);
|
||||
return GetText(*aStartOffset, *aEndOffset, aText);
|
||||
}
|
||||
|
||||
case BOUNDARY_ATTRIBUTE_RANGE:
|
||||
return GetTextHelper(eGetBefore, aBoundaryType, aOffset,
|
||||
@ -1118,56 +1160,22 @@ HyperTextAccessible::GetTextAtOffset(int32_t aOffset,
|
||||
*aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
|
||||
return GetText(*aStartOffset, *aEndOffset, aText);
|
||||
|
||||
case BOUNDARY_LINE_START: {
|
||||
// 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 previous line.
|
||||
if (IsEmptyLastLineOffset(offset)) {
|
||||
*aStartOffset = *aEndOffset = offset;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
case BOUNDARY_LINE_START:
|
||||
if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
|
||||
offset = AdjustCaretOffset(offset);
|
||||
|
||||
// Start offset is begin of the current line (as the home key was
|
||||
// pressed). End offset is begin of the next line if any (arrow down and
|
||||
// home keys), otherwise end of the current line (arrow down only).
|
||||
*aStartOffset = FindLineBoundary(offset, eDirPrevious, eSelectBeginLine);
|
||||
*aEndOffset = FindLineBoundary(offset, eDirNext, eSelectLine);
|
||||
int32_t tmpOffset = FindLineBoundary(*aEndOffset, eDirPrevious, eSelectBeginLine);
|
||||
if (tmpOffset != *aStartOffset)
|
||||
*aEndOffset = tmpOffset;
|
||||
|
||||
*aStartOffset = FindLineBoundary(offset, eThisLineBegin);
|
||||
*aEndOffset = FindLineBoundary(offset, eNextLineBegin);
|
||||
return GetText(*aStartOffset, *aEndOffset, aText);
|
||||
}
|
||||
|
||||
case BOUNDARY_LINE_END: {
|
||||
// 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 = offset - 1;
|
||||
*aEndOffset = offset;
|
||||
aText.AssignLiteral("\n");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
case BOUNDARY_LINE_END:
|
||||
if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
|
||||
offset = AdjustCaretOffset(offset);
|
||||
|
||||
// In contrast to word end boundary we follow the spec here.
|
||||
// End offset is end of the current line (as the end key was pressed).
|
||||
// Start offset is end of the previous line if any (up arrow and end keys),
|
||||
// otherwise 0 offset (up arrow only).
|
||||
*aEndOffset = FindLineBoundary(offset, eDirNext, eSelectEndLine);
|
||||
int32_t tmpOffset = FindLineBoundary(offset, eDirPrevious, eSelectLine);
|
||||
*aStartOffset = FindLineBoundary(tmpOffset, eDirNext, eSelectEndLine);
|
||||
if (*aStartOffset == *aEndOffset)
|
||||
*aStartOffset = 0;
|
||||
|
||||
*aStartOffset = FindLineBoundary(offset, ePrevLineEnd);
|
||||
*aEndOffset = FindLineBoundary(offset, eThisLineEnd);
|
||||
return GetText(*aStartOffset, *aEndOffset, aText);
|
||||
}
|
||||
|
||||
case BOUNDARY_ATTRIBUTE_RANGE:
|
||||
return GetTextHelper(eGetAt, aBoundaryType, aOffset,
|
||||
@ -1223,36 +1231,16 @@ HyperTextAccessible::GetTextAfterOffset(int32_t aOffset,
|
||||
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();
|
||||
}
|
||||
*aStartOffset = FindLineBoundary(offset, eNextLineBegin);
|
||||
*aEndOffset = FindLineBoundary(*aStartOffset, eNextLineBegin);
|
||||
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);
|
||||
|
||||
*aStartOffset = FindLineBoundary(offset, eThisLineEnd);
|
||||
*aEndOffset = FindLineBoundary(offset, eNextLineEnd);
|
||||
return GetText(*aStartOffset, *aEndOffset, aText);
|
||||
|
||||
case BOUNDARY_ATTRIBUTE_RANGE:
|
||||
|
@ -302,24 +302,37 @@ protected:
|
||||
int32_t FindWordBoundary(int32_t aOffset, nsDirection aDirection,
|
||||
EWordMovementType aWordMovementType)
|
||||
{
|
||||
return FindBoundary(aOffset, aDirection, eSelectWord, aWordMovementType);
|
||||
return FindOffset(aOffset, aDirection, eSelectWord, aWordMovementType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an offset of the found line boundary.
|
||||
* Used to get begin/end of previous/this/next line. Note: end of line
|
||||
* is an offset right before '\n' character if any, the offset is right after
|
||||
* '\n' character is begin of line. In case of wrap word breaks these offsets
|
||||
* are equal.
|
||||
*/
|
||||
int32_t FindLineBoundary(int32_t aOffset, nsDirection aDirection,
|
||||
nsSelectionAmount aAmount)
|
||||
{
|
||||
return FindBoundary(aOffset, aDirection, aAmount, eDefaultBehavior);
|
||||
}
|
||||
enum EWhichLineBoundary {
|
||||
ePrevLineBegin,
|
||||
ePrevLineEnd,
|
||||
eThisLineBegin,
|
||||
eThisLineEnd,
|
||||
eNextLineBegin,
|
||||
eNextLineEnd
|
||||
};
|
||||
|
||||
/**
|
||||
* Return an offset of the found word or line boundary. Helper.
|
||||
* Return an offset for requested line boundary. See constants above.
|
||||
*/
|
||||
int32_t FindBoundary(int32_t aOffset, nsDirection aDirection,
|
||||
nsSelectionAmount aAmount,
|
||||
EWordMovementType aWordMovementType = eDefaultBehavior);
|
||||
int32_t FindLineBoundary(int32_t aOffset,
|
||||
EWhichLineBoundary aWhichLineBoundary);
|
||||
|
||||
/**
|
||||
* Return an offset corresponding to the given direction and selection amount
|
||||
* relative the given offset. A helper used to find word or line boundaries.
|
||||
*/
|
||||
int32_t FindOffset(int32_t aOffset, nsDirection aDirection,
|
||||
nsSelectionAmount aAmount,
|
||||
EWordMovementType aWordMovementType = eDefaultBehavior);
|
||||
|
||||
/*
|
||||
* This does the work for nsIAccessibleText::GetText[At|Before|After]Offset
|
||||
|
@ -274,9 +274,7 @@ this.AccessFu = {
|
||||
case 'Accessibility:Focus':
|
||||
this._focused = JSON.parse(aData);
|
||||
if (this._focused) {
|
||||
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
|
||||
mm.sendAsyncMessage('AccessFu:VirtualCursor',
|
||||
{action: 'whereIsIt', move: true});
|
||||
this.showCurrent(true);
|
||||
}
|
||||
break;
|
||||
case 'Accessibility:MoveCaret':
|
||||
@ -327,20 +325,24 @@ this.AccessFu = {
|
||||
case 'TabSelect':
|
||||
{
|
||||
if (this._focused) {
|
||||
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
|
||||
// We delay this for half a second so the awesomebar could close,
|
||||
// and we could use the current coordinates for the content item.
|
||||
// XXX TODO figure out how to avoid magic wait here.
|
||||
Utils.win.setTimeout(
|
||||
function () {
|
||||
mm.sendAsyncMessage('AccessFu:VirtualCursor', {action: 'whereIsIt'});
|
||||
}, 500);
|
||||
this.showCurrent(false);
|
||||
}.bind(this), 500);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
showCurrent: function showCurrent(aMove) {
|
||||
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
|
||||
mm.sendAsyncMessage('AccessFu:ShowCurrent', { move: aMove });
|
||||
},
|
||||
|
||||
announce: function announce(aAnnouncement) {
|
||||
this._output(Presentation.announce(aAnnouncement),
|
||||
Utils.CurrentBrowser);
|
||||
@ -632,8 +634,7 @@ var Input = {
|
||||
switch (gestureName) {
|
||||
case 'dwell1':
|
||||
case 'explore1':
|
||||
this.moveCursor('moveToPoint', 'SimpleTouch', 'gesture',
|
||||
aGesture.x, aGesture.y);
|
||||
this.moveToPoint('SimpleTouch', aGesture.x, aGesture.y);
|
||||
break;
|
||||
case 'doubletap1':
|
||||
this.activateCurrent();
|
||||
@ -754,12 +755,18 @@ var Input = {
|
||||
aEvent.stopPropagation();
|
||||
},
|
||||
|
||||
moveCursor: function moveCursor(aAction, aRule, aInputType, aX, aY) {
|
||||
moveToPoint: function moveToPoint(aRule, aX, aY) {
|
||||
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
|
||||
mm.sendAsyncMessage('AccessFu:VirtualCursor',
|
||||
mm.sendAsyncMessage('AccessFu:MoveToPoint', {rule: aRule,
|
||||
x: aX, y: aY,
|
||||
origin: 'top'});
|
||||
},
|
||||
|
||||
moveCursor: function moveCursor(aAction, aRule, aInputType) {
|
||||
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
|
||||
mm.sendAsyncMessage('AccessFu:MoveCursor',
|
||||
{action: aAction, rule: aRule,
|
||||
x: aX, y: aY, origin: 'top',
|
||||
inputType: aInputType});
|
||||
origin: 'top', inputType: aInputType});
|
||||
},
|
||||
|
||||
moveCaret: function moveCaret(aDetails) {
|
||||
|
@ -234,7 +234,8 @@ this.Utils = {
|
||||
|
||||
inHiddenSubtree: function inHiddenSubtree(aAccessible) {
|
||||
for (let acc=aAccessible; acc; acc=acc.parent) {
|
||||
if (JSON.parse(Utils.getAttributes(acc).hidden)) {
|
||||
let hidden = Utils.getAttributes(acc).hidden;
|
||||
if (hidden && JSON.parse(hidden)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -26,100 +26,136 @@ Logger.debug('content-script.js');
|
||||
|
||||
let eventManager = null;
|
||||
|
||||
function virtualCursorControl(aMessage) {
|
||||
if (Logger.logLevel >= Logger.DEBUG)
|
||||
Logger.debug(aMessage.name, JSON.stringify(aMessage.json));
|
||||
function moveCursor(aMessage) {
|
||||
if (Logger.logLevel >= Logger.DEBUG) {
|
||||
Logger.debug(aMessage.name, JSON.stringify(aMessage.json, null, ' '));
|
||||
}
|
||||
|
||||
try {
|
||||
let vc = Utils.getVirtualCursor(content.document);
|
||||
let origin = aMessage.json.origin;
|
||||
if (origin != 'child') {
|
||||
if (forwardMessage(vc, aMessage))
|
||||
return;
|
||||
}
|
||||
let vc = Utils.getVirtualCursor(content.document);
|
||||
let origin = aMessage.json.origin;
|
||||
let action = aMessage.json.action;
|
||||
let rule = TraversalRules[aMessage.json.rule];
|
||||
|
||||
let details = aMessage.json;
|
||||
let rule = TraversalRules[details.rule];
|
||||
let moved = 0;
|
||||
switch (details.action) {
|
||||
case 'moveFirst':
|
||||
case 'moveLast':
|
||||
moved = vc[details.action](rule);
|
||||
break;
|
||||
case 'moveNext':
|
||||
case 'movePrevious':
|
||||
try {
|
||||
if (origin == 'parent' && vc.position == null) {
|
||||
if (details.action == 'moveNext')
|
||||
moved = vc.moveFirst(rule);
|
||||
else
|
||||
moved = vc.moveLast(rule);
|
||||
} else {
|
||||
moved = vc[details.action](rule);
|
||||
function moveCursorInner() {
|
||||
try {
|
||||
if (origin == 'parent' &&
|
||||
!Utils.isAliveAndVisible(vc.position)) {
|
||||
// We have a bad position in this frame, move vc to last or first item.
|
||||
if (action == 'moveNext') {
|
||||
return vc.moveFirst(rule);
|
||||
} else if (action == 'movePrevious') {
|
||||
return vc.moveLast(rule);
|
||||
}
|
||||
} catch (x) {
|
||||
}
|
||||
|
||||
return vc[action](rule);
|
||||
} catch (x) {
|
||||
if (action == 'moveNext' || action == 'movePrevious') {
|
||||
// If we are trying to move next/prev put the vc on the focused item.
|
||||
let acc = Utils.AccRetrieval.
|
||||
getAccessibleFor(content.document.activeElement);
|
||||
moved = vc.moveNext(rule, acc, true);
|
||||
return vc.moveNext(rule, acc, true);
|
||||
} else {
|
||||
throw x;
|
||||
}
|
||||
break;
|
||||
case 'moveToPoint':
|
||||
if (!this._ppcp) {
|
||||
this._ppcp = Utils.getPixelsPerCSSPixel(content);
|
||||
}
|
||||
moved = vc.moveToPoint(rule,
|
||||
details.x * this._ppcp, details.y * this._ppcp,
|
||||
true);
|
||||
break;
|
||||
case 'whereIsIt':
|
||||
if (!forwardMessage(vc, aMessage)) {
|
||||
if (!vc.position && aMessage.json.move)
|
||||
vc.moveFirst(TraversalRules.Simple);
|
||||
else {
|
||||
sendAsyncMessage('AccessFu:Present', Presentation.pivotChanged(
|
||||
vc.position, null, Ci.nsIAccessiblePivot.REASON_NONE));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (moved == true) {
|
||||
forwardMessage(vc, aMessage);
|
||||
} else if (moved == false && details.action != 'moveToPoint') {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (origin != 'child' &&
|
||||
forwardToChild(aMessage, moveCursor, vc.position)) {
|
||||
// We successfully forwarded the move to the child document.
|
||||
return;
|
||||
}
|
||||
|
||||
if (moveCursorInner()) {
|
||||
// If we moved, try forwarding the message to the new position,
|
||||
// it may be a frame with a vc of its own.
|
||||
forwardToChild(aMessage, moveCursor, vc.position);
|
||||
} else {
|
||||
// If we did not move, we probably reached the end or start of the
|
||||
// document, go back to parent content and move us out of the iframe.
|
||||
if (origin == 'parent') {
|
||||
vc.position = null;
|
||||
}
|
||||
aMessage.json.origin = 'child';
|
||||
sendAsyncMessage('AccessFu:VirtualCursor', aMessage.json);
|
||||
forwardToParent(aMessage);
|
||||
}
|
||||
} catch (x) {
|
||||
Logger.logException(x, 'Failed to move virtual cursor');
|
||||
Logger.logException(x, 'Cursor move failed');
|
||||
}
|
||||
}
|
||||
|
||||
function forwardMessage(aVirtualCursor, aMessage) {
|
||||
try {
|
||||
let acc = aVirtualCursor.position;
|
||||
if (acc && acc.role == ROLE_INTERNAL_FRAME) {
|
||||
let mm = Utils.getMessageManager(acc.DOMNode);
|
||||
mm.addMessageListener(aMessage.name, virtualCursorControl);
|
||||
aMessage.json.origin = 'parent';
|
||||
if (Utils.isContentProcess) {
|
||||
// XXX: OOP content's screen offset is 0,
|
||||
// so we remove the real screen offset here.
|
||||
aMessage.json.x -= content.mozInnerScreenX;
|
||||
aMessage.json.y -= content.mozInnerScreenY;
|
||||
}
|
||||
mm.sendAsyncMessage(aMessage.name, aMessage.json);
|
||||
return true;
|
||||
}
|
||||
} catch (x) {
|
||||
// Frame may be hidden, we regard this case as false.
|
||||
function moveToPoint(aMessage) {
|
||||
if (Logger.logLevel >= Logger.DEBUG) {
|
||||
Logger.debug(aMessage.name, JSON.stringify(aMessage.json, null, ' '));
|
||||
}
|
||||
return false;
|
||||
|
||||
let vc = Utils.getVirtualCursor(content.document);
|
||||
let details = aMessage.json;
|
||||
let rule = TraversalRules[details.rule];
|
||||
|
||||
try {
|
||||
if (!this._ppcp) {
|
||||
this._ppcp = Utils.getPixelsPerCSSPixel(content);
|
||||
}
|
||||
vc.moveToPoint(rule, details.x * this._ppcp, details.y * this._ppcp, true);
|
||||
forwardToChild(aMessage, moveToPoint, vc.position);
|
||||
} catch (x) {
|
||||
Logger.logException(x, 'Failed move to point');
|
||||
}
|
||||
}
|
||||
|
||||
function showCurrent(aMessage) {
|
||||
if (Logger.logLevel >= Logger.DEBUG) {
|
||||
Logger.debug(aMessage.name, JSON.stringify(aMessage.json, null, ' '));
|
||||
}
|
||||
|
||||
let vc = Utils.getVirtualCursor(content.document);
|
||||
|
||||
if (!forwardToChild(vc, showCurrent, aMessage)) {
|
||||
if (!vc.position && aMessage.json.move) {
|
||||
vc.moveFirst(TraversalRules.Simple);
|
||||
} else {
|
||||
sendAsyncMessage('AccessFu:Present', Presentation.pivotChanged(
|
||||
vc.position, null, Ci.nsIAccessiblePivot.REASON_NONE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function forwardToParent(aMessage) {
|
||||
// XXX: This is a silly way to make a deep copy
|
||||
let newJSON = JSON.parse(JSON.stringify(aMessage.json));
|
||||
newJSON.origin = 'child';
|
||||
sendAsyncMessage(aMessage.name, newJSON);
|
||||
}
|
||||
|
||||
function forwardToChild(aMessage, aListener, aVCPosition) {
|
||||
let acc = aVCPosition || Utils.getVirtualCursor(content.document).position;
|
||||
|
||||
if (!Utils.isAliveAndVisible(acc) || acc.role != ROLE_INTERNAL_FRAME) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Logger.logLevel >= Logger.DEBUG) {
|
||||
Logger.debug('forwardToChild', Logger.accessibleToString(acc),
|
||||
aMessage.name, JSON.stringify(aMessage.json, null, ' '));
|
||||
}
|
||||
|
||||
let mm = Utils.getMessageManager(acc.DOMNode);
|
||||
mm.addMessageListener(aMessage.name, aListener);
|
||||
// XXX: This is a silly way to make a deep copy
|
||||
let newJSON = JSON.parse(JSON.stringify(aMessage.json));
|
||||
newJSON.origin = 'parent';
|
||||
if (Utils.isContentProcess) {
|
||||
// XXX: OOP content's screen offset is 0,
|
||||
// so we remove the real screen offset here.
|
||||
newJSON.x -= content.mozInnerScreenX;
|
||||
newJSON.y -= content.mozInnerScreenY;
|
||||
}
|
||||
mm.sendAsyncMessage(aMessage.name, newJSON);
|
||||
return true;
|
||||
}
|
||||
|
||||
function activateCurrent(aMessage) {
|
||||
@ -174,9 +210,10 @@ function activateCurrent(aMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
let vc = Utils.getVirtualCursor(content.document);
|
||||
if (!forwardMessage(vc, aMessage))
|
||||
activateAccessible(vc.position);
|
||||
let position = Utils.getVirtualCursor(content.document).position;
|
||||
if (!forwardToChild(aMessage, activateCurrent, position)) {
|
||||
activateAccessible(position);
|
||||
}
|
||||
}
|
||||
|
||||
function activateContextMenu(aMessage) {
|
||||
@ -188,9 +225,9 @@ function activateContextMenu(aMessage) {
|
||||
sendAsyncMessage('AccessFu:ActivateContextMenu', {x: x, y: y});
|
||||
}
|
||||
|
||||
let vc = Utils.getVirtualCursor(content.document);
|
||||
if (!forwardMessage(vc, aMessage))
|
||||
if (!forwardToChild(aMessage, activateContextMenu, vc.position)) {
|
||||
sendContextMenuCoordinates(vc.position);
|
||||
}
|
||||
}
|
||||
|
||||
function moveCaret(aMessage) {
|
||||
@ -315,15 +352,14 @@ function scroll(aMessage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aMessage.json.origin != 'child') {
|
||||
if (forwardMessage(vc, aMessage))
|
||||
return;
|
||||
if (aMessage.json.origin != 'child' &&
|
||||
forwardToChild(aMessage, scroll, vc.position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tryToScroll()) {
|
||||
// Failed to scroll anything in this document. Try in parent document.
|
||||
aMessage.json.origin = 'child';
|
||||
sendAsyncMessage('AccessFu:Scroll', aMessage.json);
|
||||
forwardToParent(aMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@ -334,7 +370,9 @@ addMessageListener(
|
||||
if (m.json.buildApp)
|
||||
Utils.MozBuildApp = m.json.buildApp;
|
||||
|
||||
addMessageListener('AccessFu:VirtualCursor', virtualCursorControl);
|
||||
addMessageListener('AccessFu:MoveToPoint', moveToPoint);
|
||||
addMessageListener('AccessFu:MoveCursor', moveCursor);
|
||||
addMessageListener('AccessFu:ShowCurrent', showCurrent);
|
||||
addMessageListener('AccessFu:Activate', activateCurrent);
|
||||
addMessageListener('AccessFu:ContextMenu', activateContextMenu);
|
||||
addMessageListener('AccessFu:Scroll', scroll);
|
||||
@ -351,7 +389,9 @@ addMessageListener(
|
||||
function(m) {
|
||||
Logger.debug('AccessFu:Stop');
|
||||
|
||||
removeMessageListener('AccessFu:VirtualCursor', virtualCursorControl);
|
||||
removeMessageListener('AccessFu:MoveToPoint', moveToPoint);
|
||||
removeMessageListener('AccessFu:MoveCursor', moveCursor);
|
||||
removeMessageListener('AccessFu:ShowCurrent', showCurrent);
|
||||
removeMessageListener('AccessFu:Activate', activateCurrent);
|
||||
removeMessageListener('AccessFu:ContextMenu', activateContextMenu);
|
||||
removeMessageListener('AccessFu:Scroll', scroll);
|
||||
|
@ -8,16 +8,56 @@
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-consoleiframes.html";
|
||||
|
||||
let expectedMessages = [
|
||||
{
|
||||
text: "main file",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
},
|
||||
{
|
||||
text: "blah",
|
||||
category: CATEGORY_JS,
|
||||
severity: SEVERITY_ERROR
|
||||
},
|
||||
{
|
||||
text: "iframe 2",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG
|
||||
},
|
||||
{
|
||||
text: "iframe 3",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG
|
||||
}
|
||||
];
|
||||
|
||||
// "iframe 1" console messages can be coalesced into one if they follow each
|
||||
// other in the sequence of messages (depending on timing). If they do not, then
|
||||
// they will be displayed in the console output independently, as separate
|
||||
// messages. This is why we need to match any of the following two rules.
|
||||
let expectedMessagesAny = [
|
||||
{
|
||||
name: "iframe 1 (count: 2)",
|
||||
text: "iframe 1",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
count: 2
|
||||
},
|
||||
{
|
||||
name: "iframe 1 (repeats: 2)",
|
||||
text: "iframe 1",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
repeats: 2
|
||||
},
|
||||
];
|
||||
|
||||
function test()
|
||||
{
|
||||
expectUncaughtException();
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
|
||||
// Test for cached nsIConsoleMessages.
|
||||
Services.console.logStringMessage("test1 for bug859756");
|
||||
|
||||
info("open web console");
|
||||
openConsole(null, consoleOpened);
|
||||
}, true);
|
||||
@ -29,31 +69,16 @@ function consoleOpened(hud)
|
||||
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
text: "main file",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
},
|
||||
{
|
||||
text: "blah",
|
||||
category: CATEGORY_JS,
|
||||
severity: SEVERITY_ERROR
|
||||
},
|
||||
{
|
||||
text: "iframe 1",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
count: 2
|
||||
},
|
||||
{
|
||||
text: "iframe 2",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG
|
||||
}
|
||||
],
|
||||
messages: expectedMessages,
|
||||
}).then(() => {
|
||||
closeConsole(null, onWebConsoleClose);
|
||||
info("first messages matched");
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: expectedMessagesAny,
|
||||
matchCondition: "any",
|
||||
}).then(() => {
|
||||
closeConsole(null, onWebConsoleClose);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -66,34 +91,17 @@ function onWebConsoleClose()
|
||||
function onBrowserConsoleOpen(hud)
|
||||
{
|
||||
ok(hud, "browser console opened");
|
||||
Services.console.logStringMessage("test2 for bug859756");
|
||||
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
text: "main file",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
},
|
||||
{
|
||||
text: "blah",
|
||||
category: CATEGORY_JS,
|
||||
severity: SEVERITY_ERROR
|
||||
},
|
||||
{
|
||||
text: "iframe 1",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
count: 2
|
||||
},
|
||||
{
|
||||
text: "iframe 2",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG
|
||||
}
|
||||
],
|
||||
messages: expectedMessages,
|
||||
}).then(() => {
|
||||
closeConsole(null, finishTest);
|
||||
info("first messages matched");
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: expectedMessagesAny,
|
||||
matchCondition: "any",
|
||||
}).then(() => {
|
||||
closeConsole(null, finishTest);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -35,15 +35,14 @@ function performTest() {
|
||||
Services.obs.removeObserver(TestObserver, "console-api-log-event");
|
||||
TestObserver = null;
|
||||
|
||||
waitForSuccess({
|
||||
name: "console.log() message",
|
||||
validatorFn: function()
|
||||
{
|
||||
return hud.outputNode.textContent.indexOf("foobarBug613013") > -1;
|
||||
},
|
||||
successFn: finishTest,
|
||||
failureFn: finishTest,
|
||||
});
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
text: "foobarBug613013",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
}],
|
||||
}).then(finishTest);
|
||||
}
|
||||
|
||||
function test() {
|
||||
|
@ -855,6 +855,12 @@ function getMessageElementText(aElement)
|
||||
* @param object aOptions
|
||||
* Options for what you want to wait for:
|
||||
* - webconsole: the webconsole instance you work with.
|
||||
* - matchCondition: "any" or "all". Default: "all". The promise
|
||||
* returned by this function resolves when all of the messages are
|
||||
* matched, if the |matchCondition| is "all". If you set the condition to
|
||||
* "any" then the promise is resolved by any message rule that matches,
|
||||
* irrespective of order - waiting for messages stops whenever any rule
|
||||
* matches.
|
||||
* - messages: an array of objects that tells which messages to wait for.
|
||||
* Properties:
|
||||
* - text: string or RegExp to match the textContent of each new
|
||||
@ -905,6 +911,7 @@ function waitForMessages(aOptions)
|
||||
let rulesMatched = 0;
|
||||
let listenerAdded = false;
|
||||
let deferred = promise.defer();
|
||||
aOptions.matchCondition = aOptions.matchCondition || "all";
|
||||
|
||||
function checkText(aRule, aText)
|
||||
{
|
||||
@ -1154,9 +1161,15 @@ function waitForMessages(aOptions)
|
||||
}
|
||||
}
|
||||
|
||||
function allRulesMatched()
|
||||
{
|
||||
return aOptions.matchCondition == "all" && rulesMatched == rules.length ||
|
||||
aOptions.matchCondition == "any" && rulesMatched > 0;
|
||||
}
|
||||
|
||||
function maybeDone()
|
||||
{
|
||||
if (rulesMatched == rules.length) {
|
||||
if (allRulesMatched()) {
|
||||
if (listenerAdded) {
|
||||
webconsole.ui.off("messages-added", onMessagesAdded);
|
||||
webconsole.ui.off("messages-updated", onMessagesAdded);
|
||||
@ -1169,7 +1182,7 @@ function waitForMessages(aOptions)
|
||||
}
|
||||
|
||||
function testCleanup() {
|
||||
if (rulesMatched == rules.length) {
|
||||
if (allRulesMatched()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1198,7 +1211,7 @@ function waitForMessages(aOptions)
|
||||
|
||||
executeSoon(() => {
|
||||
onMessagesAdded("messages-added", webconsole.outputNode.childNodes);
|
||||
if (rulesMatched != rules.length) {
|
||||
if (!allRulesMatched()) {
|
||||
listenerAdded = true;
|
||||
registerCleanupFunction(testCleanup);
|
||||
webconsole.ui.on("messages-added", onMessagesAdded);
|
||||
|
@ -27,7 +27,11 @@ this.SitePermissions = {
|
||||
/* Returns an array of all permission IDs.
|
||||
*/
|
||||
listPermissions: function () {
|
||||
return Object.keys(gPermissionObject);
|
||||
let array = Object.keys(gPermissionObject);
|
||||
array.sort((a, b) => {
|
||||
return this.getPermissionLabel(a).localeCompare(this.getPermissionLabel(b));
|
||||
});
|
||||
return array;
|
||||
},
|
||||
|
||||
/* Returns an array of permission states to be exposed to the user for a
|
||||
|
@ -4567,7 +4567,10 @@ nsEventStateManager::CheckForAndDispatchClick(nsPresContext* aPresContext,
|
||||
if (!mouseContent && !mCurrentTarget) {
|
||||
return NS_OK;
|
||||
}
|
||||
ret = presShell->HandleEventWithTarget(&event, mCurrentTarget,
|
||||
|
||||
// HandleEvent clears out mCurrentTarget which we might need again
|
||||
nsWeakFrame currentTarget = mCurrentTarget;
|
||||
ret = presShell->HandleEventWithTarget(&event, currentTarget,
|
||||
mouseContent, aStatus);
|
||||
if (NS_SUCCEEDED(ret) && aEvent->clickCount == 2) {
|
||||
//fire double click
|
||||
@ -4581,7 +4584,7 @@ nsEventStateManager::CheckForAndDispatchClick(nsPresContext* aPresContext,
|
||||
event2.button = aEvent->button;
|
||||
event2.inputSource = aEvent->inputSource;
|
||||
|
||||
ret = presShell->HandleEventWithTarget(&event2, mCurrentTarget,
|
||||
ret = presShell->HandleEventWithTarget(&event2, currentTarget,
|
||||
mouseContent, aStatus);
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ MOCHITEST_FILES = \
|
||||
test_focus_disabled.html \
|
||||
test_bug847597.html \
|
||||
test_bug855741.html \
|
||||
test_dblclick_explicit_original_target.html \
|
||||
$(NULL)
|
||||
|
||||
ifeq (,$(filter gonk,$(MOZ_WIDGET_TOOLKIT)))
|
||||
|
@ -0,0 +1,33 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test explicit original target of dblclick event</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<p id="display">Test explicit original target of dblclick event</p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(runTests);
|
||||
|
||||
function runTests()
|
||||
{
|
||||
synthesizeMouse(document.getElementById("display"), 5, 5, { clickCount: 2 });
|
||||
}
|
||||
|
||||
window.ondblclick = function(event) {
|
||||
is(event.explicitOriginalTarget.nodeType, Node.TEXT_NODE, "explicitOriginalTarget is a text node");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -840,7 +840,7 @@ private:
|
||||
typename ActorType::ConstructorParamsType params;
|
||||
ActorType::BaseType::SetBlobConstructorParams(params, normalParams);
|
||||
|
||||
ActorType* newActor = ActorType::Create(params);
|
||||
ActorType* newActor = ActorType::Create(mActor->Manager(), params);
|
||||
MOZ_ASSERT(newActor);
|
||||
|
||||
SlicedBlobConstructorParams slicedParams;
|
||||
@ -1011,11 +1011,13 @@ RemoteBlob<Child>::GetInternalStream(nsIInputStream** aStream)
|
||||
}
|
||||
|
||||
template <ActorFlavorEnum ActorFlavor>
|
||||
Blob<ActorFlavor>::Blob(nsIDOMBlob* aBlob)
|
||||
: mBlob(aBlob), mRemoteBlob(nullptr), mOwnsBlob(true), mBlobIsFile(false)
|
||||
Blob<ActorFlavor>::Blob(ContentManager* aManager, nsIDOMBlob* aBlob)
|
||||
: mBlob(aBlob), mRemoteBlob(nullptr), mOwnsBlob(true)
|
||||
, mBlobIsFile(false), mManager(aManager)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aBlob);
|
||||
MOZ_ASSERT(aManager);
|
||||
aBlob->AddRef();
|
||||
|
||||
nsCOMPtr<nsIDOMFile> file = do_QueryInterface(aBlob);
|
||||
@ -1023,10 +1025,13 @@ Blob<ActorFlavor>::Blob(nsIDOMBlob* aBlob)
|
||||
}
|
||||
|
||||
template <ActorFlavorEnum ActorFlavor>
|
||||
Blob<ActorFlavor>::Blob(const ConstructorParamsType& aParams)
|
||||
: mBlob(nullptr), mRemoteBlob(nullptr), mOwnsBlob(false), mBlobIsFile(false)
|
||||
Blob<ActorFlavor>::Blob(ContentManager* aManager,
|
||||
const ConstructorParamsType& aParams)
|
||||
: mBlob(nullptr), mRemoteBlob(nullptr), mOwnsBlob(false)
|
||||
, mBlobIsFile(false), mManager(aManager)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aManager);
|
||||
|
||||
ChildBlobConstructorParams::Type paramType =
|
||||
BaseType::GetBlobConstructorParams(aParams).type();
|
||||
@ -1048,7 +1053,8 @@ Blob<ActorFlavor>::Blob(const ConstructorParamsType& aParams)
|
||||
|
||||
template <ActorFlavorEnum ActorFlavor>
|
||||
Blob<ActorFlavor>*
|
||||
Blob<ActorFlavor>::Create(const ConstructorParamsType& aParams)
|
||||
Blob<ActorFlavor>::Create(ContentManager* aManager,
|
||||
const ConstructorParamsType& aParams)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
@ -1059,7 +1065,7 @@ Blob<ActorFlavor>::Create(const ConstructorParamsType& aParams)
|
||||
case ChildBlobConstructorParams::TNormalBlobConstructorParams:
|
||||
case ChildBlobConstructorParams::TFileBlobConstructorParams:
|
||||
case ChildBlobConstructorParams::TMysteryBlobConstructorParams:
|
||||
return new Blob<ActorFlavor>(aParams);
|
||||
return new Blob<ActorFlavor>(aManager, aParams);
|
||||
|
||||
case ChildBlobConstructorParams::TSlicedBlobConstructorParams: {
|
||||
const SlicedBlobConstructorParams& params =
|
||||
@ -1074,7 +1080,7 @@ Blob<ActorFlavor>::Create(const ConstructorParamsType& aParams)
|
||||
getter_AddRefs(slice));
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
return new Blob<ActorFlavor>(slice);
|
||||
return new Blob<ActorFlavor>(aManager, slice);
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -162,6 +162,7 @@ class Blob : public BlobTraits<ActorFlavor>::BaseType
|
||||
friend class RemoteBlob<ActorFlavor>;
|
||||
|
||||
public:
|
||||
typedef typename BlobTraits<ActorFlavor>::ConcreteContentManagerType ContentManager;
|
||||
typedef typename BlobTraits<ActorFlavor>::ProtocolType ProtocolType;
|
||||
typedef typename BlobTraits<ActorFlavor>::StreamType StreamType;
|
||||
typedef typename BlobTraits<ActorFlavor>::ConstructorParamsType
|
||||
@ -183,14 +184,14 @@ protected:
|
||||
public:
|
||||
// This create function is called on the sending side.
|
||||
static Blob*
|
||||
Create(nsIDOMBlob* aBlob)
|
||||
Create(ContentManager* aManager, nsIDOMBlob* aBlob)
|
||||
{
|
||||
return new Blob(aBlob);
|
||||
return new Blob(aManager, aBlob);
|
||||
}
|
||||
|
||||
// This create function is called on the receiving side.
|
||||
static Blob*
|
||||
Create(const ConstructorParamsType& aParams);
|
||||
Create(ContentManager* aManager, const ConstructorParamsType& aParams);
|
||||
|
||||
// Get the blob associated with this actor. This may always be called on the
|
||||
// sending side. It may also be called on the receiving side unless this is a
|
||||
@ -207,12 +208,17 @@ public:
|
||||
bool
|
||||
SetMysteryBlobInfo(const nsString& aContentType, uint64_t aLength);
|
||||
|
||||
ContentManager* Manager()
|
||||
{
|
||||
return mManager;
|
||||
}
|
||||
|
||||
private:
|
||||
// This constructor is called on the sending side.
|
||||
Blob(nsIDOMBlob* aBlob);
|
||||
Blob(ContentManager* aManager, nsIDOMBlob* aBlob);
|
||||
|
||||
// This constructor is called on the receiving side.
|
||||
Blob(const ConstructorParamsType& aParams);
|
||||
Blob(ContentManager* aManager, const ConstructorParamsType& aParams);
|
||||
|
||||
static already_AddRefed<RemoteBlobType>
|
||||
CreateRemoteBlob(const ConstructorParamsType& aParams);
|
||||
@ -229,6 +235,8 @@ private:
|
||||
|
||||
virtual bool
|
||||
RecvPBlobStreamConstructor(StreamType* aActor) MOZ_OVERRIDE;
|
||||
|
||||
nsRefPtr<ContentManager> mManager;
|
||||
};
|
||||
|
||||
} // namespace ipc
|
||||
|
@ -636,7 +636,7 @@ ContentChild::DeallocPBrowserChild(PBrowserChild* iframe)
|
||||
PBlobChild*
|
||||
ContentChild::AllocPBlobChild(const BlobConstructorParams& aParams)
|
||||
{
|
||||
return BlobChild::Create(aParams);
|
||||
return BlobChild::Create(this, aParams);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -723,7 +723,7 @@ ContentChild::GetOrCreateActorForBlob(nsIDOMBlob* aBlob)
|
||||
}
|
||||
}
|
||||
|
||||
BlobChild* actor = BlobChild::Create(aBlob);
|
||||
BlobChild* actor = BlobChild::Create(this, aBlob);
|
||||
NS_ENSURE_TRUE(actor, nullptr);
|
||||
|
||||
if (!SendPBlobConstructor(actor, params)) {
|
||||
|
@ -1692,7 +1692,7 @@ ContentParent::DeallocPDeviceStorageRequestParent(PDeviceStorageRequestParent* d
|
||||
PBlobParent*
|
||||
ContentParent::AllocPBlobParent(const BlobConstructorParams& aParams)
|
||||
{
|
||||
return BlobParent::Create(aParams);
|
||||
return BlobParent::Create(this, aParams);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -1772,7 +1772,7 @@ ContentParent::GetOrCreateActorForBlob(nsIDOMBlob* aBlob)
|
||||
}
|
||||
}
|
||||
|
||||
BlobParent* actor = BlobParent::Create(aBlob);
|
||||
BlobParent* actor = BlobParent::Create(this, aBlob);
|
||||
NS_ENSURE_TRUE(actor, nullptr);
|
||||
|
||||
if (!SendPBlobConstructor(actor, params)) {
|
||||
|
@ -849,8 +849,8 @@ RTCPeerConnection.prototype = {
|
||||
}
|
||||
|
||||
if (dict.maxRetransmitTime != undefined &&
|
||||
dict.maxRetransmitNum != undefined) {
|
||||
throw new Components.Exception("Both maxRetransmitTime and maxRetransmitNum cannot be provided");
|
||||
dict.maxRetransmits != undefined) {
|
||||
throw new Components.Exception("Both maxRetransmitTime and maxRetransmits cannot be provided");
|
||||
}
|
||||
let protocol;
|
||||
if (dict.protocol == undefined) {
|
||||
@ -863,7 +863,7 @@ RTCPeerConnection.prototype = {
|
||||
let type;
|
||||
if (dict.maxRetransmitTime != undefined) {
|
||||
type = Ci.IPeerConnection.kDataChannelPartialReliableTimed;
|
||||
} else if (dict.maxRetransmitNum != undefined) {
|
||||
} else if (dict.maxRetransmits != undefined) {
|
||||
type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit;
|
||||
} else {
|
||||
type = Ci.IPeerConnection.kDataChannelReliable;
|
||||
@ -871,9 +871,9 @@ RTCPeerConnection.prototype = {
|
||||
|
||||
// Synchronous since it doesn't block.
|
||||
let channel = this._getPC().createDataChannel(
|
||||
label, protocol, type, dict.outOfOrderAllowed, dict.maxRetransmitTime,
|
||||
dict.maxRetransmitNum, dict.preset ? true : false,
|
||||
dict.stream != undefined ? dict.stream : 0xFFFF
|
||||
label, protocol, type, !dict.ordered, dict.maxRetransmitTime,
|
||||
dict.maxRetransmits, dict.negotiated ? true : false,
|
||||
dict.id != undefined ? dict.id : 0xFFFF
|
||||
);
|
||||
return channel;
|
||||
},
|
||||
|
@ -185,9 +185,9 @@ private:
|
||||
GLenum result = mGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
|
||||
if (result != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
|
||||
nsAutoCString msg;
|
||||
msg.AppendPrintf("Framebuffer not complete -- error 0x%x, aFBOTextureTarget 0x%x, aRect.width %d, aRect.height %d",
|
||||
result, mInitParams.mFBOTextureTarget, mInitParams.mSize.width, mInitParams.mSize.height);
|
||||
NS_RUNTIMEABORT(msg.get());
|
||||
msg.AppendPrintf("Framebuffer not complete -- error 0x%x, aFBOTextureTarget 0x%x, mFBO %d, mTextureHandle %d, aRect.width %d, aRect.height %d",
|
||||
result, mInitParams.mFBOTextureTarget, mFBO, mTextureHandle, mInitParams.mSize.width, mInitParams.mSize.height);
|
||||
NS_ERROR(msg.get());
|
||||
}
|
||||
|
||||
mCompositor->PrepareViewport(mInitParams.mSize, mTransform);
|
||||
|
@ -896,6 +896,13 @@ CompositorOGL::CreateFBOWithTexture(const IntRect& aRect, SurfaceInitMode aInit,
|
||||
LOCAL_GL_UNSIGNED_BYTE,
|
||||
buf);
|
||||
}
|
||||
GLenum error = mGLContext->GetAndClearError();
|
||||
if (error != LOCAL_GL_NO_ERROR) {
|
||||
nsAutoCString msg;
|
||||
msg.AppendPrintf("Texture initialization failed! -- error 0x%x, Source %d, Source format %d, RGBA Compat %d",
|
||||
error, aSourceFrameBuffer, format, isFormatCompatibleWithRGBA);
|
||||
NS_ERROR(msg.get());
|
||||
}
|
||||
} else {
|
||||
mGLContext->fTexImage2D(mFBOTextureTarget,
|
||||
0,
|
||||
|
@ -230,9 +230,11 @@ class SVGDrawingCallback : public gfxDrawingCallback {
|
||||
public:
|
||||
SVGDrawingCallback(SVGDocumentWrapper* aSVGDocumentWrapper,
|
||||
const nsIntRect& aViewport,
|
||||
const gfxSize& aScale,
|
||||
uint32_t aImageFlags) :
|
||||
mSVGDocumentWrapper(aSVGDocumentWrapper),
|
||||
mViewport(aViewport),
|
||||
mScale(aScale),
|
||||
mImageFlags(aImageFlags)
|
||||
{}
|
||||
virtual bool operator()(gfxContext* aContext,
|
||||
@ -242,6 +244,7 @@ public:
|
||||
private:
|
||||
nsRefPtr<SVGDocumentWrapper> mSVGDocumentWrapper;
|
||||
const nsIntRect mViewport;
|
||||
const gfxSize mScale;
|
||||
uint32_t mImageFlags;
|
||||
};
|
||||
|
||||
@ -271,6 +274,7 @@ SVGDrawingCallback::operator()(gfxContext* aContext,
|
||||
|
||||
gfxContextMatrixAutoSaveRestore contextMatrixRestorer(aContext);
|
||||
aContext->Multiply(gfxMatrix(aTransform).Invert());
|
||||
aContext->Scale(1.0 / mScale.width, 1.0 / mScale.height);
|
||||
|
||||
nsPresContext* presContext = presShell->GetPresContext();
|
||||
MOZ_ASSERT(presContext, "pres shell w/out pres context");
|
||||
@ -698,18 +702,11 @@ VectorImage::Draw(gfxContext* aContext,
|
||||
// if we hit the tiling path. Unfortunately, the temporary surface isn't
|
||||
// created at the size at which we'll ultimately draw, causing fuzzy output.
|
||||
// To fix this we pre-apply the transform's scaling to the drawing parameters
|
||||
// and then remove the scaling from the transform, so the fact that temporary
|
||||
// and remove the scaling from the transform, so the fact that temporary
|
||||
// surfaces won't take the scaling into account doesn't matter. (Bug 600207.)
|
||||
gfxSize scale(aUserSpaceToImageSpace.ScaleFactors(true));
|
||||
gfxPoint translation(aUserSpaceToImageSpace.GetTranslation());
|
||||
|
||||
// Rescale everything.
|
||||
nsIntSize scaledViewport(aViewportSize.width / scale.width,
|
||||
aViewportSize.height / scale.height);
|
||||
gfxIntSize scaledViewportGfx(scaledViewport.width, scaledViewport.height);
|
||||
nsIntRect scaledSubimage(aSubimage);
|
||||
scaledSubimage.ScaleRoundOut(1.0 / scale.width, 1.0 / scale.height);
|
||||
|
||||
// Remove the scaling from the transform.
|
||||
gfxMatrix unscale;
|
||||
unscale.Translate(gfxPoint(translation.x / scale.width,
|
||||
@ -718,28 +715,30 @@ VectorImage::Draw(gfxContext* aContext,
|
||||
unscale.Translate(-translation);
|
||||
gfxMatrix unscaledTransform(aUserSpaceToImageSpace * unscale);
|
||||
|
||||
mSVGDocumentWrapper->UpdateViewportBounds(scaledViewport);
|
||||
mSVGDocumentWrapper->UpdateViewportBounds(aViewportSize);
|
||||
mSVGDocumentWrapper->FlushImageTransformInvalidation();
|
||||
|
||||
// Based on imgFrame::Draw
|
||||
gfxRect sourceRect = unscaledTransform.Transform(aFill);
|
||||
gfxRect imageRect(0, 0, scaledViewport.width, scaledViewport.height);
|
||||
gfxRect subimage(scaledSubimage.x, scaledSubimage.y,
|
||||
scaledSubimage.width, scaledSubimage.height);
|
||||
|
||||
// Rescale drawing parameters.
|
||||
gfxIntSize drawableSize(aViewportSize.width / scale.width,
|
||||
aViewportSize.height / scale.height);
|
||||
gfxRect drawableSourceRect = unscaledTransform.Transform(aFill);
|
||||
gfxRect drawableImageRect(0, 0, drawableSize.width, drawableSize.height);
|
||||
gfxRect drawableSubimage(aSubimage.x, aSubimage.y,
|
||||
aSubimage.width, aSubimage.height);
|
||||
drawableSubimage.ScaleRoundOut(1.0 / scale.width, 1.0 / scale.height);
|
||||
|
||||
nsRefPtr<gfxDrawingCallback> cb =
|
||||
new SVGDrawingCallback(mSVGDocumentWrapper,
|
||||
nsIntRect(nsIntPoint(0, 0), scaledViewport),
|
||||
nsIntRect(nsIntPoint(0, 0), aViewportSize),
|
||||
scale,
|
||||
aFlags);
|
||||
|
||||
nsRefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, scaledViewportGfx);
|
||||
nsRefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, drawableSize);
|
||||
|
||||
gfxUtils::DrawPixelSnapped(aContext, drawable,
|
||||
unscaledTransform,
|
||||
subimage, sourceRect, imageRect, aFill,
|
||||
gfxASurface::ImageFormatARGB32, aFilter,
|
||||
aFlags);
|
||||
gfxUtils::DrawPixelSnapped(aContext, drawable, unscaledTransform,
|
||||
drawableSubimage, drawableSourceRect,
|
||||
drawableImageRect, aFill,
|
||||
gfxASurface::ImageFormatARGB32, aFilter, aFlags);
|
||||
|
||||
MOZ_ASSERT(mRenderingObserver, "Should have a rendering observer by now");
|
||||
mRenderingObserver->ResumeHonoringInvalidations();
|
||||
|
@ -1714,6 +1714,14 @@ DecimalFormat::subformat(UnicodeString& appendTo,
|
||||
appendTo.append(*grouping);
|
||||
handler.addAttribute(kGroupingSeparatorField, currentLength, appendTo.length());
|
||||
}
|
||||
}
|
||||
|
||||
// This handles the special case of formatting 0. For zero only, we count the
|
||||
// zero to the left of the decimal point as one signficant digit. Ordinarily we
|
||||
// do not count any leading 0's as significant. If the number we are formatting
|
||||
// is not zero, then either sigCount or digits.getCount() will be non-zero.
|
||||
if (sigCount == 0 && digits.getCount() == 0) {
|
||||
sigCount = 1;
|
||||
}
|
||||
|
||||
// TODO(dlf): this looks like it was a bug, we marked the int field as ending
|
||||
|
@ -14,6 +14,15 @@
|
||||
# and reapply it after running update-icu.sh (additional updates may be needed).
|
||||
# If the bug has been addressed, please delete this warning.
|
||||
|
||||
# Warning
|
||||
# =======
|
||||
# The fix for ICU bug 10045 has been individually backported into this tree.
|
||||
# If you update ICU to a version that does not have this fix yet, obtain the
|
||||
# patch "Backported fix for formatting 0 with significant digits from ICU" from
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=853706
|
||||
# and reapply it after running update-icu.sh.
|
||||
# If you update ICU to a version that has the fix, please delete this warning.
|
||||
|
||||
# Usage: update-icu.sh <URL of ICU SVN with release>
|
||||
# E.g., for ICU 50.1.1: update-icu.sh http://source.icu-project.org/repos/icu/icu/tags/release-50-1-1/
|
||||
|
||||
|
@ -706,9 +706,6 @@ obj_create(JSContext *cx, unsigned argc, Value *vp)
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
/* Don't track types or array-ness for objects created here. */
|
||||
MarkTypeObjectUnknownProperties(cx, obj->type());
|
||||
|
||||
/* 15.2.3.5 step 4. */
|
||||
if (args.hasDefined(1)) {
|
||||
if (args[1].isPrimitive()) {
|
||||
|
@ -6288,12 +6288,6 @@ TypeObject::clearProperties()
|
||||
inline void
|
||||
TypeObject::sweep(FreeOp *fop)
|
||||
{
|
||||
/*
|
||||
* We may be regenerating existing type sets containing this object,
|
||||
* so reset contributions on each GC to avoid tripping the limit.
|
||||
*/
|
||||
contribution = 0;
|
||||
|
||||
if (singleton) {
|
||||
JS_ASSERT(!newScript);
|
||||
|
||||
|
@ -287,7 +287,7 @@ enum {
|
||||
TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE | TYPE_FLAG_STRING,
|
||||
|
||||
/* Mask/shift for the number of objects in objectSet */
|
||||
TYPE_FLAG_OBJECT_COUNT_MASK = 0xff00,
|
||||
TYPE_FLAG_OBJECT_COUNT_MASK = 0x1f00,
|
||||
TYPE_FLAG_OBJECT_COUNT_SHIFT = 8,
|
||||
TYPE_FLAG_OBJECT_COUNT_LIMIT =
|
||||
TYPE_FLAG_OBJECT_COUNT_MASK >> TYPE_FLAG_OBJECT_COUNT_SHIFT,
|
||||
@ -971,20 +971,6 @@ struct TypeObject : gc::Cell
|
||||
|
||||
static inline size_t offsetOfFlags() { return offsetof(TypeObject, flags); }
|
||||
|
||||
/*
|
||||
* Estimate of the contribution of this object to the type sets it appears in.
|
||||
* This is the sum of the sizes of those sets at the point when the object
|
||||
* was added.
|
||||
*
|
||||
* When the contribution exceeds the CONTRIBUTION_LIMIT, any type sets the
|
||||
* object is added to are instead marked as unknown. If we get to this point
|
||||
* we are probably not adding types which will let us do meaningful optimization
|
||||
* later, and we want to ensure in such cases that our time/space complexity
|
||||
* is linear, not worst-case cubic as it would otherwise be.
|
||||
*/
|
||||
uint32_t contribution;
|
||||
static const uint32_t CONTRIBUTION_LIMIT = 2000;
|
||||
|
||||
/*
|
||||
* If non-NULL, objects of this type have always been constructed using
|
||||
* 'new' on the specified script, which adds some number of properties to
|
||||
@ -1027,6 +1013,10 @@ struct TypeObject : gc::Cell
|
||||
/* If this is an interpreted function, the function object. */
|
||||
HeapPtrFunction interpretedFunction;
|
||||
|
||||
#if JS_BITS_PER_WORD == 32
|
||||
uint32_t padding;
|
||||
#endif
|
||||
|
||||
inline TypeObject(Class *clasp, TaggedProto proto, bool isFunction, bool unknown);
|
||||
|
||||
bool isFunction() { return !!(flags & OBJECT_FLAG_FUNCTION); }
|
||||
|
@ -970,9 +970,14 @@ TypeScript::MonitorAssign(JSContext *cx, HandleObject obj, jsid id)
|
||||
// But if we don't have too many properties yet, don't do anything. The
|
||||
// idea here is that normal object initialization should not trigger
|
||||
// deoptimization in most cases, while actual usage as a hashmap should.
|
||||
// Except for vanilla objects and arrays work around bug 894447 for now
|
||||
// by deoptimizing more eagerly. Since in those cases we often have a
|
||||
// pc-keyed TypeObject, this is ok.
|
||||
TypeObject* type = obj->type();
|
||||
if (type->getPropertyCount() < 8)
|
||||
if (!obj->is<JSObject>() && !obj->is<ArrayObject>() &&
|
||||
type->getPropertyCount() < 8) {
|
||||
return;
|
||||
}
|
||||
MarkTypeObjectUnknownProperties(cx, type);
|
||||
}
|
||||
}
|
||||
@ -1360,14 +1365,6 @@ TypeSet::addType(JSContext *cx, Type type)
|
||||
JS_ASSERT(!nobject->singleton);
|
||||
if (nobject->unknownProperties())
|
||||
goto unknownObject;
|
||||
if (objectCount > 1) {
|
||||
nobject->contribution += (objectCount - 1) * (objectCount - 1);
|
||||
if (nobject->contribution >= TypeObject::CONTRIBUTION_LIMIT) {
|
||||
InferSpew(ISpewOps, "limitUnknown: %sT%p%s",
|
||||
InferSpewColor(this), this, InferSpewColorReset());
|
||||
goto unknownObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ format = new Intl.NumberFormat("th-th-u-nu-thai",
|
||||
{style: "percent",
|
||||
minimumSignificantDigits: 2,
|
||||
maximumSignificantDigits: 2});
|
||||
assertEq(format.format(0), "๐.๐๐%");
|
||||
assertEq(format.format(0), "๐.๐%");
|
||||
assertEq(format.format(-0.01), "-๑.๐%");
|
||||
assertEq(format.format(1.10), "๑๑๐%");
|
||||
|
||||
|
36
js/src/tests/Intl/NumberFormat/significantDigitsOfZero.js
Normal file
36
js/src/tests/Intl/NumberFormat/significantDigitsOfZero.js
Normal file
@ -0,0 +1,36 @@
|
||||
// |reftest| skip-if(!this.hasOwnProperty("Intl"))
|
||||
// -- test that NumberFormat correctly formats 0 with various numbers of significant digits
|
||||
|
||||
/* 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/. */
|
||||
|
||||
var testData = [
|
||||
{minimumSignificantDigits: 1, maximumSignificantDigits: 1, expected: "0"},
|
||||
{minimumSignificantDigits: 1, maximumSignificantDigits: 2, expected: "0"},
|
||||
{minimumSignificantDigits: 1, maximumSignificantDigits: 3, expected: "0"},
|
||||
{minimumSignificantDigits: 1, maximumSignificantDigits: 4, expected: "0"},
|
||||
{minimumSignificantDigits: 1, maximumSignificantDigits: 5, expected: "0"},
|
||||
{minimumSignificantDigits: 2, maximumSignificantDigits: 2, expected: "0.0"},
|
||||
{minimumSignificantDigits: 2, maximumSignificantDigits: 3, expected: "0.0"},
|
||||
{minimumSignificantDigits: 2, maximumSignificantDigits: 4, expected: "0.0"},
|
||||
{minimumSignificantDigits: 2, maximumSignificantDigits: 5, expected: "0.0"},
|
||||
{minimumSignificantDigits: 3, maximumSignificantDigits: 3, expected: "0.00"},
|
||||
{minimumSignificantDigits: 3, maximumSignificantDigits: 4, expected: "0.00"},
|
||||
{minimumSignificantDigits: 3, maximumSignificantDigits: 5, expected: "0.00"},
|
||||
];
|
||||
|
||||
for (var i = 0; i < testData.length; i++) {
|
||||
var min = testData[i].minimumSignificantDigits;
|
||||
var max = testData[i].maximumSignificantDigits;
|
||||
var options = {minimumSignificantDigits: min, maximumSignificantDigits: max};
|
||||
var format = new Intl.NumberFormat("en-US", options);
|
||||
assertEq(format.format(0), testData[i].expected,
|
||||
"Wrong formatted string for 0 with " +
|
||||
"minimumSignificantDigits " + min +
|
||||
", maximumSignificantDigits " + max +
|
||||
": expected \"" + expected +
|
||||
"\", actual \"" + actual + "\"");
|
||||
}
|
||||
|
||||
reportCompare(true, true);
|
@ -38,7 +38,6 @@ skip script test262/ch10/10.4/10.4.3/10.4.3-1-106.js # bug 603201
|
||||
|
||||
skip script test262/intl402/ch10/10.1/10.1.1_13.js # bug 853704
|
||||
skip script test262/intl402/ch10/10.1/10.1.1_19_c.js # bug 853704
|
||||
skip script test262/intl402/ch11/11.3/11.3.2_TRP.js # bug 853706
|
||||
|
||||
#######################################################################
|
||||
# Tests disabled due to jstest limitations wrt imported test262 tests #
|
||||
|
@ -1288,10 +1288,6 @@ Interpret(JSContext *cx, RunState &state)
|
||||
InterruptEnabler interrupts(&switchMask, -1);
|
||||
|
||||
# define DO_OP() goto do_op
|
||||
# define DO_NEXT_OP(n) JS_BEGIN_MACRO \
|
||||
JS_ASSERT((n) == len); \
|
||||
goto advance_pc; \
|
||||
JS_END_MACRO
|
||||
|
||||
# define BEGIN_CASE(OP) case OP:
|
||||
# define END_CASE(OP) END_CASE_LEN(OP##_LENGTH)
|
||||
@ -1300,21 +1296,21 @@ Interpret(JSContext *cx, RunState &state)
|
||||
|
||||
/*
|
||||
* To share the code for all len == 1 cases we use the specialized label with
|
||||
* code that falls through to advance_pc: .
|
||||
* code that falls through to advanceAndDoOp: .
|
||||
*/
|
||||
# define END_CASE_LEN1 goto advance_pc_by_one;
|
||||
# define END_CASE_LEN2 len = 2; goto advance_pc;
|
||||
# define END_CASE_LEN3 len = 3; goto advance_pc;
|
||||
# define END_CASE_LEN4 len = 4; goto advance_pc;
|
||||
# define END_CASE_LEN5 len = 5; goto advance_pc;
|
||||
# define END_CASE_LEN6 len = 6; goto advance_pc;
|
||||
# define END_CASE_LEN7 len = 7; goto advance_pc;
|
||||
# define END_CASE_LEN8 len = 8; goto advance_pc;
|
||||
# define END_CASE_LEN9 len = 9; goto advance_pc;
|
||||
# define END_CASE_LEN10 len = 10; goto advance_pc;
|
||||
# define END_CASE_LEN11 len = 11; goto advance_pc;
|
||||
# define END_CASE_LEN12 len = 12; goto advance_pc;
|
||||
# define END_VARLEN_CASE goto advance_pc;
|
||||
# define END_CASE_LEN2 len = 2; goto advanceAndDoOp;
|
||||
# define END_CASE_LEN3 len = 3; goto advanceAndDoOp;
|
||||
# define END_CASE_LEN4 len = 4; goto advanceAndDoOp;
|
||||
# define END_CASE_LEN5 len = 5; goto advanceAndDoOp;
|
||||
# define END_CASE_LEN6 len = 6; goto advanceAndDoOp;
|
||||
# define END_CASE_LEN7 len = 7; goto advanceAndDoOp;
|
||||
# define END_CASE_LEN8 len = 8; goto advanceAndDoOp;
|
||||
# define END_CASE_LEN9 len = 9; goto advanceAndDoOp;
|
||||
# define END_CASE_LEN10 len = 10; goto advanceAndDoOp;
|
||||
# define END_CASE_LEN11 len = 11; goto advanceAndDoOp;
|
||||
# define END_CASE_LEN12 len = 12; goto advanceAndDoOp;
|
||||
# define END_VARLEN_CASE goto advanceAndDoOp;
|
||||
# define ADD_EMPTY_CASE(OP) BEGIN_CASE(OP)
|
||||
# define END_EMPTY_CASES goto advance_pc_by_one;
|
||||
|
||||
@ -1443,8 +1439,8 @@ Interpret(JSContext *cx, RunState &state)
|
||||
/*
|
||||
* It is important that "op" be initialized before calling DO_OP because
|
||||
* it is possible for "op" to be specially assigned during the normal
|
||||
* processing of an opcode while looping. We rely on DO_NEXT_OP to manage
|
||||
* "op" correctly in all other cases.
|
||||
* processing of an opcode while looping. We rely on |advanceAndDoOp:| to
|
||||
* manage "op" correctly in all other cases.
|
||||
*/
|
||||
JSOp op;
|
||||
int32_t len;
|
||||
@ -1453,13 +1449,13 @@ Interpret(JSContext *cx, RunState &state)
|
||||
if (rt->profilingScripts || cx->runtime()->debugHooks.interruptHook)
|
||||
interrupts.enable();
|
||||
|
||||
DO_NEXT_OP(len);
|
||||
goto advanceAndDoOp;
|
||||
|
||||
for (;;) {
|
||||
advance_pc_by_one:
|
||||
JS_ASSERT(js_CodeSpec[op].length == 1);
|
||||
len = 1;
|
||||
advance_pc:
|
||||
advanceAndDoOp:
|
||||
js::gc::MaybeVerifyBarriers(cx);
|
||||
regs.pc += len;
|
||||
op = (JSOp) *regs.pc;
|
||||
@ -1723,7 +1719,7 @@ BEGIN_CASE(JSOP_STOP)
|
||||
TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]);
|
||||
|
||||
len = JSOP_CALL_LENGTH;
|
||||
DO_NEXT_OP(len);
|
||||
goto advanceAndDoOp;
|
||||
}
|
||||
|
||||
/* Increment pc so that |sp - fp->slots == ReconstructStackDepth(pc)|. */
|
||||
@ -1773,7 +1769,7 @@ BEGIN_CASE(JSOP_OR)
|
||||
bool cond = ToBooleanOp(regs);
|
||||
if (cond == true) {
|
||||
len = GET_JUMP_OFFSET(regs.pc);
|
||||
DO_NEXT_OP(len);
|
||||
goto advanceAndDoOp;
|
||||
}
|
||||
}
|
||||
END_CASE(JSOP_OR)
|
||||
@ -1783,7 +1779,7 @@ BEGIN_CASE(JSOP_AND)
|
||||
bool cond = ToBooleanOp(regs);
|
||||
if (cond == false) {
|
||||
len = GET_JUMP_OFFSET(regs.pc);
|
||||
DO_NEXT_OP(len);
|
||||
goto advanceAndDoOp;
|
||||
}
|
||||
}
|
||||
END_CASE(JSOP_AND)
|
||||
@ -1806,7 +1802,7 @@ END_CASE(JSOP_AND)
|
||||
BRANCH(len); \
|
||||
} \
|
||||
len = 1 + JSOP_IFEQ_LENGTH; \
|
||||
DO_NEXT_OP(len); \
|
||||
goto advanceAndDoOp; \
|
||||
} \
|
||||
JS_END_MACRO
|
||||
|
||||
@ -2506,7 +2502,7 @@ BEGIN_CASE(JSOP_FUNCALL)
|
||||
TypeScript::Monitor(cx, script, regs.pc, newsp[-1]);
|
||||
regs.sp = newsp;
|
||||
len = JSOP_CALL_LENGTH;
|
||||
DO_NEXT_OP(len);
|
||||
goto advanceAndDoOp;
|
||||
}
|
||||
|
||||
InitialFrameFlags initial = construct ? INITIAL_CONSTRUCT : INITIAL_NONE;
|
||||
@ -2719,7 +2715,7 @@ BEGIN_CASE(JSOP_TABLESWITCH)
|
||||
double d;
|
||||
/* Don't use mozilla::DoubleIsInt32; treat -0 (double) as 0. */
|
||||
if (!rref.isDouble() || (d = rref.toDouble()) != (i = int32_t(rref.toDouble())))
|
||||
DO_NEXT_OP(len);
|
||||
goto advanceAndDoOp;
|
||||
}
|
||||
|
||||
pc2 += JUMP_OFFSET_LEN;
|
||||
@ -3249,7 +3245,7 @@ BEGIN_CASE(JSOP_LEAVEBLOCKEXPR)
|
||||
} else {
|
||||
/* Another op will pop; nothing to do here. */
|
||||
len = JSOP_LEAVEFORLETIN_LENGTH;
|
||||
DO_NEXT_OP(len);
|
||||
goto advanceAndDoOp;
|
||||
}
|
||||
}
|
||||
END_CASE(JSOP_LEAVEBLOCK)
|
||||
@ -3361,7 +3357,7 @@ END_CASE(JSOP_ARRAYPUSH)
|
||||
* catch block.
|
||||
*/
|
||||
len = 0;
|
||||
DO_NEXT_OP(len);
|
||||
goto advanceAndDoOp;
|
||||
|
||||
case JSTRY_FINALLY:
|
||||
/*
|
||||
@ -3372,7 +3368,7 @@ END_CASE(JSOP_ARRAYPUSH)
|
||||
PUSH_COPY(cx->getPendingException());
|
||||
cx->clearPendingException();
|
||||
len = 0;
|
||||
DO_NEXT_OP(len);
|
||||
goto advanceAndDoOp;
|
||||
|
||||
case JSTRY_ITER: {
|
||||
/* This is similar to JSOP_ENDITER in the interpreter loop. */
|
||||
|
@ -1044,28 +1044,64 @@ struct StackShape
|
||||
SkipRoot skip;
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
/* js::Shape pointer tag bit indicating a collision. */
|
||||
#define SHAPE_COLLISION (uintptr_t(1))
|
||||
#define SHAPE_REMOVED ((Shape *) SHAPE_COLLISION)
|
||||
#define SHAPE_REMOVED ((js::Shape *) SHAPE_COLLISION)
|
||||
|
||||
/* Macros to get and set shape pointer values and collision flags. */
|
||||
#define SHAPE_IS_FREE(shape) ((shape) == NULL)
|
||||
#define SHAPE_IS_REMOVED(shape) ((shape) == SHAPE_REMOVED)
|
||||
#define SHAPE_IS_LIVE(shape) ((shape) > SHAPE_REMOVED)
|
||||
#define SHAPE_FLAG_COLLISION(spp,shape) (*(spp) = (Shape *) \
|
||||
(uintptr_t(shape) | SHAPE_COLLISION))
|
||||
#define SHAPE_HAD_COLLISION(shape) (uintptr_t(shape) & SHAPE_COLLISION)
|
||||
#define SHAPE_FETCH(spp) SHAPE_CLEAR_COLLISION(*(spp))
|
||||
/* Functions to get and set shape pointer values and collision flags. */
|
||||
|
||||
#define SHAPE_CLEAR_COLLISION(shape) \
|
||||
((Shape *) (uintptr_t(shape) & ~SHAPE_COLLISION))
|
||||
inline bool
|
||||
SHAPE_IS_FREE(js::Shape *shape)
|
||||
{
|
||||
return shape == NULL;
|
||||
}
|
||||
|
||||
#define SHAPE_STORE_PRESERVING_COLLISION(spp, shape) \
|
||||
(*(spp) = (Shape *) (uintptr_t(shape) | SHAPE_HAD_COLLISION(*(spp))))
|
||||
inline bool
|
||||
SHAPE_IS_REMOVED(js::Shape *shape)
|
||||
{
|
||||
return shape == SHAPE_REMOVED;
|
||||
}
|
||||
|
||||
inline bool
|
||||
SHAPE_IS_LIVE(js::Shape *shape)
|
||||
{
|
||||
return shape > SHAPE_REMOVED;
|
||||
}
|
||||
|
||||
inline void
|
||||
SHAPE_FLAG_COLLISION(js::Shape **spp, js::Shape *shape)
|
||||
{
|
||||
*spp = reinterpret_cast<js::Shape*>(uintptr_t(shape) | SHAPE_COLLISION);
|
||||
}
|
||||
|
||||
inline bool
|
||||
SHAPE_HAD_COLLISION(js::Shape *shape)
|
||||
{
|
||||
return uintptr_t(shape) & SHAPE_COLLISION;
|
||||
}
|
||||
|
||||
inline js::Shape *
|
||||
SHAPE_CLEAR_COLLISION(js::Shape *shape)
|
||||
{
|
||||
return reinterpret_cast<js::Shape*>(uintptr_t(shape) & ~SHAPE_COLLISION);
|
||||
}
|
||||
|
||||
inline js::Shape *
|
||||
SHAPE_FETCH(js::Shape **spp)
|
||||
{
|
||||
return SHAPE_CLEAR_COLLISION(*spp);
|
||||
}
|
||||
|
||||
inline void
|
||||
SHAPE_STORE_PRESERVING_COLLISION(js::Shape **spp, js::Shape *shape)
|
||||
{
|
||||
*spp = reinterpret_cast<js::Shape*>(uintptr_t(shape) |
|
||||
SHAPE_HAD_COLLISION(*spp));
|
||||
}
|
||||
|
||||
namespace js {
|
||||
|
||||
|
@ -92,7 +92,7 @@ function do_test() {
|
||||
"repeat-x", "repeat-y", "fixed", "scroll", "center", "top", "bottom", "left", "right",
|
||||
"border-box", "padding-box", "content-box", "border-box", "padding-box", "content-box", "contain",
|
||||
"cover" ];
|
||||
ok(testValues(values, expected), "Shorthand proprety values.");
|
||||
ok(testValues(values, expected), "Shorthand property values.");
|
||||
|
||||
// test keywords only
|
||||
var prop = "border-top";
|
||||
|
@ -2,10 +2,12 @@
|
||||
== tall--contain--width.html ref-tall-empty.html
|
||||
== wide--contain--height.html ref-wide-empty.html
|
||||
== wide--contain--width.html ref-wide-empty.html
|
||||
== tall--cover--height.html ref-tall-lime.html
|
||||
== tall--cover--width.html ref-tall-lime.html
|
||||
== wide--cover--height.html ref-wide-lime.html
|
||||
== wide--cover--width.html ref-wide-lime.html
|
||||
|
||||
# These tests fail because of integer overflow; see bug 894555.
|
||||
fails == tall--cover--height.html ref-tall-lime.html
|
||||
fails == tall--cover--width.html ref-tall-lime.html
|
||||
fails == wide--cover--height.html ref-wide-lime.html
|
||||
fails == wide--cover--width.html ref-wide-lime.html
|
||||
|
||||
== zero-height-ratio-contain.html ref-tall-empty.html
|
||||
== zero-height-ratio-cover.html ref-tall-empty.html
|
||||
|
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
div {
|
||||
background-color: white;
|
||||
width: 12px;
|
||||
height: 60px;
|
||||
}
|
||||
body {
|
||||
background-color: black;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div></div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html reftest-zoom="2.0">
|
||||
<head>
|
||||
<style>
|
||||
div {
|
||||
background-color: yellow;
|
||||
background-image: url("white-rect-no-viewbox.svg");
|
||||
width: 6px;
|
||||
height: 30px;
|
||||
}
|
||||
body {
|
||||
background-color: black;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div></div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
div {
|
||||
background-color: white;
|
||||
width: 12px;
|
||||
height: 60px;
|
||||
}
|
||||
body {
|
||||
background-color: black;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div></div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html reftest-zoom="2.0">
|
||||
<head>
|
||||
<style>
|
||||
div {
|
||||
background-color: yellow;
|
||||
background-image: url("white-rect-with-viewbox.svg");
|
||||
width: 6px;
|
||||
height: 30px;
|
||||
}
|
||||
body {
|
||||
background-color: black;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div></div>
|
||||
</body>
|
||||
</html>
|
37
layout/reftests/svg/as-image/background-stretch-1-ref.html
Normal file
37
layout/reftests/svg/as-image/background-stretch-1-ref.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
div {
|
||||
background-color: yellow;
|
||||
height: 30px;
|
||||
}
|
||||
.container {
|
||||
width: 100px;
|
||||
}
|
||||
.stretch {
|
||||
width: 40px;
|
||||
background-color: white;
|
||||
display: inline-block;
|
||||
}
|
||||
.left-spacer {
|
||||
width: 30px;
|
||||
float: left;
|
||||
}
|
||||
.right-spacer {
|
||||
width: 30px;
|
||||
float: right;
|
||||
}
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="left-spacer"></div>
|
||||
<div class="stretch"></div>
|
||||
<div class="right-spacer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
22
layout/reftests/svg/as-image/background-stretch-1.html
Normal file
22
layout/reftests/svg/as-image/background-stretch-1.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
div {
|
||||
height: 30px;
|
||||
width: 100px;
|
||||
background-image: url("white-rect-with-viewbox.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 30px;
|
||||
background-size: 40px 30px;
|
||||
background-color: yellow;
|
||||
}
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div></div>
|
||||
</body>
|
||||
</html>
|
@ -16,6 +16,13 @@ skip-if(B2G) == background-simple-1.html lime100x100-ref.html # bug 773482
|
||||
== background-resize-3.html lime100x100-ref.html
|
||||
== background-resize-4.html lime100x100-ref.html
|
||||
|
||||
# Test for stretching background images by different amounts in each dimension
|
||||
== background-stretch-1.html background-stretch-1-ref.html
|
||||
|
||||
# Tests for scaling background images
|
||||
== background-scale-no-viewbox-1.html background-scale-no-viewbox-1-ref.html
|
||||
== background-scale-with-viewbox-1.html background-scale-with-viewbox-1-ref.html
|
||||
|
||||
# Tests with -moz-image-rect()
|
||||
skip-if(B2G) == background-image-rect-1svg.html lime100x100-ref.html # bug 773482
|
||||
== background-image-rect-1png.html lime100x100-ref.html
|
||||
|
8
layout/reftests/svg/as-image/white-rect-no-viewbox.svg
Normal file
8
layout/reftests/svg/as-image/white-rect-no-viewbox.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<svg version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
<rect width="6" height="30" style="fill:white;" />
|
||||
</svg>
|
After Width: | Height: | Size: 213 B |
11
layout/reftests/svg/as-image/white-rect-with-viewbox.svg
Normal file
11
layout/reftests/svg/as-image/white-rect-with-viewbox.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<svg version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="6px"
|
||||
height="30px"
|
||||
viewBox="0 0 6 30">
|
||||
|
||||
<rect width="6" height="30" style="fill:white;" />
|
||||
</svg>
|
After Width: | Height: | Size: 284 B |
@ -234,7 +234,6 @@ FENNEC_JAVA_FILES = \
|
||||
menu/MenuItemDefault.java \
|
||||
menu/MenuPanel.java \
|
||||
menu/MenuPopup.java \
|
||||
preferences/SearchPreferenceCategory.java \
|
||||
widget/AboutHome.java \
|
||||
widget/AboutHomeView.java \
|
||||
widget/AboutHomeSection.java \
|
||||
@ -311,7 +310,6 @@ FENNEC_PP_JAVA_FILES = \
|
||||
|
||||
FENNEC_PP_XML_FILES = \
|
||||
res/xml/preferences.xml \
|
||||
res/xml/preferences_customize.xml \
|
||||
res/xml/searchable.xml \
|
||||
$(NULL)
|
||||
|
||||
@ -575,15 +573,14 @@ RES_VALUES_V14 = \
|
||||
$(NULL)
|
||||
|
||||
RES_XML = \
|
||||
res/xml/preferences_customize.xml \
|
||||
res/xml/preferences_display.xml \
|
||||
res/xml/preferences_search.xml \
|
||||
res/xml/preferences_privacy.xml \
|
||||
res/xml/preferences_vendor.xml \
|
||||
$(SYNC_RES_XML) \
|
||||
$(NULL)
|
||||
|
||||
RES_XML_V11 = \
|
||||
res/xml-v11/preferences_customize.xml \
|
||||
res/xml-v11/preference_headers.xml \
|
||||
res/xml-v11/preferences_customize_tablet.xml \
|
||||
res/xml-v11/preferences.xml \
|
||||
|
@ -68,12 +68,10 @@
|
||||
<!ENTITY settings "Settings">
|
||||
<!ENTITY settings_title "Settings">
|
||||
<!ENTITY pref_category_customize "Customize">
|
||||
<!ENTITY pref_category_search "Search">
|
||||
<!ENTITY pref_category_display "Display">
|
||||
<!ENTITY pref_category_privacy_short "Privacy">
|
||||
<!ENTITY pref_category_vendor "&vendorShortName;">
|
||||
<!ENTITY pref_category_datareporting "Data choices">
|
||||
<!ENTITY pref_category_installed_search_engines "Installed search engines">
|
||||
|
||||
<!-- collected old strings - remove after determining final strings
|
||||
as part of Bug 877791 -->
|
||||
|
@ -1,89 +0,0 @@
|
||||
package org.mozilla.gecko.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.os.Build;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
|
||||
public class SearchPreferenceCategory extends PreferenceCategory implements GeckoEventListener {
|
||||
public static final String LOGTAG = "SearchPrefCategory";
|
||||
|
||||
private static int sIconSize;
|
||||
|
||||
public SearchPreferenceCategory(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init();
|
||||
}
|
||||
public SearchPreferenceCategory(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public SearchPreferenceCategory(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
sIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.searchpreferences_icon_size);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToActivity() {
|
||||
super.onAttachedToActivity();
|
||||
|
||||
// Request list of search engines from Gecko
|
||||
GeckoAppShell.registerEventListener("SearchEngines:Data", this);
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:Get", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(String event, final JSONObject data) {
|
||||
if (event.equals("SearchEngines:Data")) {
|
||||
JSONArray engines;
|
||||
try {
|
||||
engines = data.getJSONArray("searchEngines");
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Unable to decode search engine data from Gecko.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an element in this PreferenceCategory for each engine.
|
||||
for (int i = 0; i < engines.length(); i++) {
|
||||
try {
|
||||
JSONObject engineJSON = engines.getJSONObject(i);
|
||||
final String engineName = engineJSON.getString("name");
|
||||
|
||||
Preference engine = new Preference(getContext());
|
||||
engine.setTitle(engineName);
|
||||
engine.setKey(engineName);
|
||||
|
||||
// The setIcon feature is not available prior to API 11.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
String iconURI = engineJSON.getString("iconURI");
|
||||
Bitmap iconBitmap = BitmapUtils.getBitmapFromDataURI(iconURI);
|
||||
Bitmap scaledIconBitmap = Bitmap.createScaledBitmap(iconBitmap, sIconSize, sIconSize, false);
|
||||
BitmapDrawable drawable = new BitmapDrawable(scaledIconBitmap);
|
||||
engine.setIcon(drawable);
|
||||
}
|
||||
addPreference(engine);
|
||||
// TODO: Bug 892113 - Add event listener here for tapping on each element. Produce a dialog to provide options.
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "JSONException parsing engine at index " + i, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -61,7 +61,6 @@
|
||||
<dimen name="prompt_service_min_list_item_height">48dp</dimen>
|
||||
<dimen name="remote_tab_child_row_height">64dp</dimen>
|
||||
<dimen name="remote_tab_group_row_height">26dp</dimen>
|
||||
<dimen name="searchpreferences_icon_size">32dp</dimen>
|
||||
<dimen name="tab_thumbnail_height">90dp</dimen>
|
||||
<dimen name="tab_thumbnail_width">160dp</dimen>
|
||||
<dimen name="tabs_counter_size">22sp</dimen>
|
||||
|
@ -14,12 +14,6 @@
|
||||
<org.mozilla.gecko.SyncPreference android:title="@string/pref_sync"
|
||||
android:persistent="false" />
|
||||
|
||||
<PreferenceScreen android:title="@string/pref_category_search"
|
||||
android:fragment="org.mozilla.gecko.GeckoPreferenceFragment" >
|
||||
<extra android:name="resource"
|
||||
android:value="preferences_search"/>
|
||||
</PreferenceScreen>
|
||||
|
||||
<org.mozilla.gecko.AndroidImportPreference
|
||||
android:key="android.not_a_preference.import_android"
|
||||
gecko:entries="@array/pref_import_android_entries"
|
||||
@ -30,10 +24,10 @@
|
||||
android:negativeButtonText="@string/button_cancel"
|
||||
android:persistent="false" />
|
||||
|
||||
<CheckBoxPreference android:key="android.not_a_preference.restoreSession"
|
||||
android:title="@string/pref_restore_session"
|
||||
android:defaultValue="false"
|
||||
android:persistent="true" />
|
||||
<CheckBoxPreference android:key="browser.search.suggest.enabled"
|
||||
android:title="@string/pref_search_suggestions"
|
||||
android:defaultValue="true"
|
||||
android:persistent="false" />
|
||||
|
||||
<ListPreference android:key="app.update.autodownload"
|
||||
android:title="@string/pref_update_autodownload"
|
||||
|
@ -7,12 +7,6 @@
|
||||
xmlns:gecko="http://schemas.android.com/apk/res-auto"
|
||||
android:enabled="false">
|
||||
|
||||
<PreferenceScreen android:title="@string/pref_category_search"
|
||||
android:fragment="org.mozilla.gecko.GeckoPreferenceFragment" >
|
||||
<extra android:name="resource"
|
||||
android:value="preferences_search"/>
|
||||
</PreferenceScreen>
|
||||
|
||||
<org.mozilla.gecko.AndroidImportPreference
|
||||
android:key="android.not_a_preference.import_android"
|
||||
gecko:entries="@array/pref_import_android_entries"
|
||||
@ -23,6 +17,11 @@
|
||||
android:negativeButtonText="@string/button_cancel"
|
||||
android:persistent="false" />
|
||||
|
||||
<CheckBoxPreference android:key="browser.search.suggest.enabled"
|
||||
android:title="@string/pref_search_suggestions"
|
||||
android:defaultValue="true"
|
||||
android:persistent="false" />
|
||||
|
||||
<CheckBoxPreference android:key="android.not_a_preference.restoreSession"
|
||||
android:title="@string/pref_restore_session"
|
||||
android:defaultValue="false"
|
@ -1,42 +0,0 @@
|
||||
#filter substitution
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:gecko="http://schemas.android.com/apk/res-auto"
|
||||
android:enabled="false">
|
||||
<PreferenceScreen android:title="@string/pref_category_search" >
|
||||
<intent android:action="android.intent.action.VIEW"
|
||||
android:targetPackage="@ANDROID_PACKAGE_NAME@"
|
||||
android:targetClass="org.mozilla.gecko.GeckoPreferences" >
|
||||
<extra
|
||||
android:name="resource"
|
||||
android:value="preferences_search" />
|
||||
</intent>
|
||||
</PreferenceScreen>
|
||||
|
||||
<org.mozilla.gecko.AndroidImportPreference
|
||||
android:key="android.not_a_preference.import_android"
|
||||
gecko:entries="@array/pref_import_android_entries"
|
||||
gecko:entryKeys="@array/pref_import_android_keys"
|
||||
gecko:initialValues="@array/pref_import_android_values"
|
||||
android:title="@string/pref_import_android"
|
||||
android:positiveButtonText="@string/bookmarkhistory_button_import"
|
||||
android:negativeButtonText="@string/button_cancel"
|
||||
android:persistent="false" />
|
||||
|
||||
<CheckBoxPreference android:key="android.not_a_preference.restoreSession"
|
||||
android:title="@string/pref_restore_session"
|
||||
android:defaultValue="false"
|
||||
android:persistent="true" />
|
||||
|
||||
<ListPreference android:key="app.update.autodownload"
|
||||
android:title="@string/pref_update_autodownload"
|
||||
android:entries="@array/pref_update_autodownload_entries"
|
||||
android:entryValues="@array/pref_update_autodownload_values"
|
||||
android:persistent="false" />
|
||||
|
||||
</PreferenceScreen>
|
||||
|
@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:gecko="http://schemas.android.com/apk/res-auto"
|
||||
android:title="@string/pref_category_search"
|
||||
android:enabled="false">
|
||||
|
||||
<CheckBoxPreference android:key="browser.search.suggest.enabled"
|
||||
android:title="@string/pref_search_suggestions"
|
||||
android:defaultValue="false"
|
||||
android:persistent="false" />
|
||||
|
||||
<org.mozilla.gecko.preferences.SearchPreferenceCategory
|
||||
android:title="@string/pref_category_installed_search_engines"/>
|
||||
</PreferenceScreen>
|
@ -80,12 +80,10 @@
|
||||
<string name="settings">&settings;</string>
|
||||
<string name="settings_title">&settings_title;</string>
|
||||
<string name="pref_category_customize">&pref_category_customize;</string>
|
||||
<string name="pref_category_search">&pref_category_search;</string>
|
||||
<string name="pref_category_display">&pref_category_display;</string>
|
||||
<string name="pref_category_privacy_short">&pref_category_privacy_short;</string>
|
||||
<string name="pref_category_vendor">&pref_category_vendor;</string>
|
||||
<string name="pref_category_datareporting">&pref_category_datareporting;</string>
|
||||
<string name="pref_category_installed_search_engines">&pref_category_installed_search_engines;</string>
|
||||
|
||||
<string name="pref_header_customize">&pref_header_customize;</string>
|
||||
<string name="pref_header_display">&pref_header_display;</string>
|
||||
|
@ -2548,6 +2548,12 @@ nsHttpChannel::OpenCacheEntry(bool usingSSL)
|
||||
}
|
||||
}
|
||||
|
||||
if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
|
||||
mozilla::Telemetry::Accumulate(
|
||||
Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD,
|
||||
!!mApplicationCache);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsICacheSession> session;
|
||||
|
||||
// If we have an application cache, we check it first.
|
||||
|
@ -240,7 +240,7 @@ class OSXBootstrapper(BaseBootstrapper):
|
||||
self.run_as_root([self.port, '-v', 'install', MACPORTS_CLANG_PACKAGE])
|
||||
|
||||
self.run_as_root([self.port, 'select', '--set', 'python', 'python27'])
|
||||
self.run_as_root([self.port, 'select', '--set', 'clang', MACPORTS_CLANG_PACKAGE])
|
||||
self.run_as_root([self.port, 'select', '--set', 'clang', 'mp-' + MACPORTS_CLANG_PACKAGE])
|
||||
|
||||
def ensure_package_manager(self):
|
||||
'''
|
||||
|
16
testing/xpcshell/example/unit/test_do_get_tempdir.js
Normal file
16
testing/xpcshell/example/unit/test_do_get_tempdir.js
Normal file
@ -0,0 +1,16 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/* This tests that do_get_tempdir returns a directory that we can write to. */
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
function run_test() {
|
||||
let tmpd = do_get_tempdir();
|
||||
do_check_true(tmpd.exists());
|
||||
tmpd.append("testfile");
|
||||
tmpd.create(Ci.nsIFile.NORMAL_FILE_TYPE, 600);
|
||||
do_check_true(tmpd.exists());
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
head =
|
||||
tail =
|
||||
|
||||
[test_do_get_tempdir.js]
|
||||
[test_execute_soon.js]
|
||||
[test_get_file.js]
|
||||
[test_get_idle.js]
|
||||
|
@ -917,6 +917,23 @@ function do_register_cleanup(aFunction)
|
||||
_cleanupFunctions.push(aFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory for a temp dir, which is created by the
|
||||
* test harness. Every test gets its own temp dir.
|
||||
*
|
||||
* @return nsILocalFile of the temporary directory
|
||||
*/
|
||||
function do_get_tempdir() {
|
||||
let env = Components.classes["@mozilla.org/process/environment;1"]
|
||||
.getService(Components.interfaces.nsIEnvironment);
|
||||
// the python harness sets this in the environment for us
|
||||
let path = env.get("XPCSHELL_TEST_TEMP_DIR");
|
||||
let file = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
file.initWithPath(path);
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a directory with the profile service,
|
||||
* and return the directory as an nsILocalFile.
|
||||
|
@ -248,6 +248,17 @@ class XPCShellRemote(xpcshell.XPCShellTests, object):
|
||||
return ['-e', 'const _TEST_FILE = ["%s"];' %
|
||||
replaceBackSlashes(remoteName)]
|
||||
|
||||
def setupTempDir(self):
|
||||
# make sure the temp dir exists
|
||||
if self.device.dirExists(self.remoteTmpDir):
|
||||
self.device.removeDir(self.remoteTmpDir)
|
||||
self.device.mkDir(self.remoteTmpDir)
|
||||
|
||||
self.env["XPCSHELL_TEST_TEMP_DIR"] = self.remoteTmpDir
|
||||
if self.interactive:
|
||||
self.log.info("TEST-INFO | temp dir is %s" % self.remoteTmpDir)
|
||||
return self.remoteTmpDir
|
||||
|
||||
def setupProfileDir(self):
|
||||
self.device.removeDir(self.profileDir)
|
||||
self.device.mkDir(self.profileDir)
|
||||
@ -299,6 +310,7 @@ class XPCShellRemote(xpcshell.XPCShellTests, object):
|
||||
self.env["XPCSHELL_TEST_PROFILE_DIR"]=self.profileDir
|
||||
self.env["TMPDIR"]=self.remoteTmpDir
|
||||
self.env["HOME"]=self.profileDir
|
||||
self.setupTempDir()
|
||||
if self.options.setup:
|
||||
self.pushWrapper()
|
||||
|
||||
|
@ -312,6 +312,13 @@ class XPCShellTests(object):
|
||||
self.log.info("TEST-INFO | profile dir is %s" % profileDir)
|
||||
return profileDir
|
||||
|
||||
def setupTempDir(self):
|
||||
tempDir = mkdtemp()
|
||||
self.env["XPCSHELL_TEST_TEMP_DIR"] = tempDir
|
||||
if self.interactive:
|
||||
self.log.info("TEST-INFO | temp dir is %s" % tempDir)
|
||||
return tempDir
|
||||
|
||||
def setupLeakLogging(self):
|
||||
"""
|
||||
Enable leaks (only) detection to its own log file and set environment variables.
|
||||
@ -823,6 +830,39 @@ class XPCShellTests(object):
|
||||
|
||||
return self.failCount == 0
|
||||
|
||||
def print_stdout(self, stdout):
|
||||
"""Print stdout line-by-line to avoid overflowing buffers."""
|
||||
self.log.info(">>>>>>>")
|
||||
if (stdout):
|
||||
for line in stdout.splitlines():
|
||||
self.log.info(line)
|
||||
self.log.info("<<<<<<<")
|
||||
|
||||
def cleanupDir(self, directory, name, stdout, xunit_result):
|
||||
try:
|
||||
self.removeDir(directory)
|
||||
except Exception:
|
||||
self.log.info("TEST-INFO | Failed to remove directory: %s. Waiting." % directory)
|
||||
|
||||
# We suspect the filesystem may still be making changes. Wait a
|
||||
# little bit and try again.
|
||||
time.sleep(5)
|
||||
|
||||
try:
|
||||
self.removeDir(directory)
|
||||
except Exception:
|
||||
message = "TEST-UNEXPECTED-FAIL | %s | Failed to clean up directory: %s" % (name, sys.exc_info()[1])
|
||||
self.log.error(message)
|
||||
self.print_stdout(stdout)
|
||||
self.print_stdout(traceback.format_exc())
|
||||
|
||||
self.failCount += 1
|
||||
xunit_result["passed"] = False
|
||||
xunit_result["failure"] = {
|
||||
"type": "TEST-UNEXPECTED-FAIL",
|
||||
"message": message,
|
||||
"text": "%s\n%s" % (stdout, traceback.format_exc())
|
||||
}
|
||||
|
||||
def run_test(self, test, tests_root_dir=None, app_dir_key=None,
|
||||
interactive=False, verbose=False, pStdout=None, pStderr=None,
|
||||
@ -871,8 +911,10 @@ class XPCShellTests(object):
|
||||
head_files, tail_files = self.getHeadAndTailFiles(test)
|
||||
cmdH = self.buildCmdHead(head_files, tail_files, self.xpcsCmd)
|
||||
|
||||
# Create a temp dir that the JS harness can stick a profile in
|
||||
# Create a profile and a temp dir that the JS harness can stick
|
||||
# a profile and temporary data in
|
||||
self.profileDir = self.setupProfileDir()
|
||||
self.tempDir = self.setupTempDir()
|
||||
self.leakLogFile = self.setupLeakLogging()
|
||||
|
||||
# The test file will have to be loaded after the head files.
|
||||
@ -917,14 +959,6 @@ class XPCShellTests(object):
|
||||
if testTimer:
|
||||
testTimer.cancel()
|
||||
|
||||
def print_stdout(stdout):
|
||||
"""Print stdout line-by-line to avoid overflowing buffers."""
|
||||
self.log.info(">>>>>>>")
|
||||
if (stdout):
|
||||
for line in stdout.splitlines():
|
||||
self.log.info(line)
|
||||
self.log.info("<<<<<<<")
|
||||
|
||||
result = not ((self.getReturnCode(proc) != 0) or
|
||||
# if do_throw or do_check failed
|
||||
(stdout and re.search("^((parent|child): )?TEST-UNEXPECTED-",
|
||||
@ -943,7 +977,7 @@ class XPCShellTests(object):
|
||||
message = "%s | %s | test failed (with xpcshell return code: %d), see following log:" % (
|
||||
failureType, name, self.getReturnCode(proc))
|
||||
self.log.error(message)
|
||||
print_stdout(stdout)
|
||||
self.print_stdout(stdout)
|
||||
self.failCount += 1
|
||||
xunit_result["passed"] = False
|
||||
|
||||
@ -958,7 +992,7 @@ class XPCShellTests(object):
|
||||
xunit_result["time"] = now - startTime
|
||||
self.log.info("TEST-%s | %s | test passed (time: %.3fms)" % ("PASS" if expected else "KNOWN-FAIL", name, timeTaken))
|
||||
if verbose:
|
||||
print_stdout(stdout)
|
||||
self.print_stdout(stdout)
|
||||
|
||||
xunit_result["passed"] = True
|
||||
|
||||
@ -996,7 +1030,7 @@ class XPCShellTests(object):
|
||||
if proc and self.poll(proc) is None:
|
||||
message = "TEST-UNEXPECTED-FAIL | %s | Process still running after test!" % name
|
||||
self.log.error(message)
|
||||
print_stdout(stdout)
|
||||
self.print_stdout(stdout)
|
||||
self.failCount += 1
|
||||
xunit_result["passed"] = False
|
||||
xunit_result["failure"] = {
|
||||
@ -1010,30 +1044,9 @@ class XPCShellTests(object):
|
||||
# We don't want to delete the profile when running check-interactive
|
||||
# or check-one.
|
||||
if self.profileDir and not self.interactive and not self.singleFile:
|
||||
try:
|
||||
self.removeDir(self.profileDir)
|
||||
except Exception:
|
||||
self.log.info("TEST-INFO | Failed to remove profile directory. Waiting.")
|
||||
self.cleanupDir(self.profileDir, name, stdout, xunit_result)
|
||||
|
||||
# We suspect the filesystem may still be making changes. Wait a
|
||||
# little bit and try again.
|
||||
time.sleep(5)
|
||||
|
||||
try:
|
||||
self.removeDir(self.profileDir)
|
||||
except Exception:
|
||||
message = "TEST-UNEXPECTED-FAIL | %s | Failed to clean up the test profile directory: %s" % (name, sys.exc_info()[1])
|
||||
self.log.error(message)
|
||||
print_stdout(stdout)
|
||||
print_stdout(traceback.format_exc())
|
||||
|
||||
self.failCount += 1
|
||||
xunit_result["passed"] = False
|
||||
xunit_result["failure"] = {
|
||||
"type": "TEST-UNEXPECTED-FAIL",
|
||||
"message": message,
|
||||
"text": "%s\n%s" % (stdout, traceback.format_exc())
|
||||
}
|
||||
self.cleanupDir(self.tempDir, name, stdout, xunit_result)
|
||||
|
||||
if gotSIGINT:
|
||||
xunit_result["passed"] = False
|
||||
|
@ -1067,6 +1067,10 @@
|
||||
"n_values": 5,
|
||||
"description": "HTTP Offline Cache Hit, Reval, Failed-Reval, Miss"
|
||||
},
|
||||
"HTTP_OFFLINE_CACHE_DOCUMENT_LOAD": {
|
||||
"kind": "boolean",
|
||||
"description": "Rate of page load from offline cache"
|
||||
},
|
||||
"CACHE_DEVICE_SEARCH_2": {
|
||||
"kind": "exponential",
|
||||
"high": "10000",
|
||||
@ -3708,5 +3712,45 @@
|
||||
"kind": "enumerated",
|
||||
"n_values": 4,
|
||||
"description": "Accumulates type of content (mixed, mixed passive, unmixed) per page load"
|
||||
},
|
||||
"FX_THUMBNAILS_BG_QUEUE_SIZE_ON_CAPTURE": {
|
||||
"kind": "exponential",
|
||||
"high": 100,
|
||||
"n_buckets": 15,
|
||||
"extended_statistics_ok": true,
|
||||
"description": "BACKGROUND THUMBNAILS: Size of capture queue when a capture request is received"
|
||||
},
|
||||
"FX_THUMBNAILS_BG_CAPTURE_QUEUE_TIME_MS": {
|
||||
"kind": "exponential",
|
||||
"high": 300000,
|
||||
"n_buckets": 20,
|
||||
"extended_statistics_ok": true,
|
||||
"description": "BACKGROUND THUMBNAILS: Time the capture request spent in the queue before being serviced (ms)"
|
||||
},
|
||||
"FX_THUMBNAILS_BG_CAPTURE_SERVICE_TIME_MS": {
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 20,
|
||||
"extended_statistics_ok": true,
|
||||
"description": "BACKGROUND THUMBNAILS: Time the capture took once it started and successfully completed (ms)"
|
||||
},
|
||||
"FX_THUMBNAILS_BG_CAPTURE_DONE_REASON": {
|
||||
"kind": "enumerated",
|
||||
"n_values": 4,
|
||||
"description": "BACKGROUND THUMBNAILS: Reason the capture completed (see TEL_CAPTURE_DONE_* constants in BackgroundPageThumbs.jsm)"
|
||||
},
|
||||
"FX_THUMBNAILS_BG_CAPTURE_PAGE_LOAD_TIME_MS": {
|
||||
"kind": "exponential",
|
||||
"high": 60000,
|
||||
"n_buckets": 20,
|
||||
"extended_statistics_ok": true,
|
||||
"description": "BACKGROUND THUMBNAILS: Time the capture's page load took (ms)"
|
||||
},
|
||||
"FX_THUMBNAILS_BG_CAPTURE_CANVAS_DRAW_TIME_MS": {
|
||||
"kind": "exponential",
|
||||
"high": 500,
|
||||
"n_buckets": 15,
|
||||
"extended_statistics_ok": true,
|
||||
"description": "BACKGROUND THUMBNAILS: Time it took to draw the capture's window to canvas (ms)"
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,14 @@ const DEFAULT_CAPTURE_TIMEOUT = 30000; // ms
|
||||
const DESTROY_BROWSER_TIMEOUT = 60000; // ms
|
||||
const FRAME_SCRIPT_URL = "chrome://global/content/backgroundPageThumbsContent.js";
|
||||
|
||||
const TELEMETRY_HISTOGRAM_ID_PREFIX = "FX_THUMBNAILS_BG_";
|
||||
|
||||
// possible FX_THUMBNAILS_BG_CAPTURE_DONE_REASON telemetry values
|
||||
const TEL_CAPTURE_DONE_OK = 0;
|
||||
const TEL_CAPTURE_DONE_TIMEOUT = 1;
|
||||
const TEL_CAPTURE_DONE_PB_BEFORE_START = 2;
|
||||
const TEL_CAPTURE_DONE_PB_AFTER_START = 3;
|
||||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
@ -38,19 +46,11 @@ const BackgroundPageThumbs = {
|
||||
* the queue and started. Defaults to 30000 (30 seconds).
|
||||
*/
|
||||
capture: function (url, options={}) {
|
||||
if (isPrivateBrowsingActive()) {
|
||||
// There's only one, global private-browsing state shared by all private
|
||||
// windows and the thumbnail browser. Just as if you log into a site in
|
||||
// one private window you're logged in in all private windows, you're also
|
||||
// logged in in the thumbnail browser. A crude way to avoid capturing
|
||||
// sites in this situation is to refuse to capture at all when any private
|
||||
// windows are open. See bug 870179.
|
||||
if (options.onDone)
|
||||
Services.tm.mainThread.dispatch(options.onDone.bind(options, url), 0);
|
||||
return;
|
||||
}
|
||||
this._captureQueue = this._captureQueue || [];
|
||||
this._capturesByURL = this._capturesByURL || new Map();
|
||||
|
||||
tel("QUEUE_SIZE_ON_CAPTURE", this._captureQueue.length);
|
||||
|
||||
// We want to avoid duplicate captures for the same URL. If there is an
|
||||
// existing one, we just add the callback to that one and we are done.
|
||||
let existing = this._capturesByURL.get(url);
|
||||
@ -222,6 +222,7 @@ function Capture(url, captureCallback, options) {
|
||||
this.captureCallback = captureCallback;
|
||||
this.options = options;
|
||||
this.id = Capture.nextID++;
|
||||
this.creationDate = new Date();
|
||||
this.doneCallbacks = [];
|
||||
if (options.onDone)
|
||||
this.doneCallbacks.push(options.onDone);
|
||||
@ -239,11 +240,32 @@ Capture.prototype = {
|
||||
* @param messageManager The nsIMessageSender of the thumbnail browser.
|
||||
*/
|
||||
start: function (messageManager) {
|
||||
let timeout = typeof(this.options.timeout) == "number" ? this.options.timeout :
|
||||
this.startDate = new Date();
|
||||
tel("CAPTURE_QUEUE_TIME_MS", this.startDate - this.creationDate);
|
||||
|
||||
// The thumbnail browser uses private browsing mode and therefore shares
|
||||
// browsing state with private windows. To avoid capturing sites that the
|
||||
// user is logged into in private browsing windows, (1) observe window
|
||||
// openings, and if a private window is opened during capture, discard the
|
||||
// capture when it finishes, and (2) don't start the capture at all if a
|
||||
// private window is open already.
|
||||
Services.ww.registerNotification(this);
|
||||
if (isPrivateBrowsingActive()) {
|
||||
tel("CAPTURE_DONE_REASON", TEL_CAPTURE_DONE_PB_BEFORE_START);
|
||||
// Captures should always finish asyncly.
|
||||
schedule(() => this._done(null));
|
||||
return;
|
||||
}
|
||||
|
||||
// timeout timer
|
||||
let timeout = typeof(this.options.timeout) == "number" ?
|
||||
this.options.timeout :
|
||||
DEFAULT_CAPTURE_TIMEOUT;
|
||||
this._timeoutTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this._timeoutTimer.initWithCallback(this, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
this._timeoutTimer.initWithCallback(this, timeout,
|
||||
Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
|
||||
// didCapture registration
|
||||
this._msgMan = messageManager;
|
||||
this._msgMan.sendAsyncMessage("BackgroundPageThumbs:capture",
|
||||
{ id: this.id, url: this.url });
|
||||
@ -268,10 +290,14 @@ Capture.prototype = {
|
||||
delete this._msgMan;
|
||||
}
|
||||
delete this.captureCallback;
|
||||
Services.ww.unregisterNotification(this);
|
||||
},
|
||||
|
||||
// Called when the didCapture message is received.
|
||||
receiveMessage: function (msg) {
|
||||
tel("CAPTURE_DONE_REASON", TEL_CAPTURE_DONE_OK);
|
||||
tel("CAPTURE_SERVICE_TIME_MS", new Date() - this.startDate);
|
||||
|
||||
// A different timed-out capture may have finally successfully completed, so
|
||||
// discard messages that aren't meant for this capture.
|
||||
if (msg.json.id == this.id)
|
||||
@ -280,9 +306,17 @@ Capture.prototype = {
|
||||
|
||||
// Called when the timeout timer fires.
|
||||
notify: function () {
|
||||
tel("CAPTURE_DONE_REASON", TEL_CAPTURE_DONE_TIMEOUT);
|
||||
this._done(null);
|
||||
},
|
||||
|
||||
// Called when the window watcher notifies us.
|
||||
observe: function (subj, topic, data) {
|
||||
if (topic == "domwindowopened" &&
|
||||
PrivateBrowsingUtils.isWindowPrivate(subj))
|
||||
this._privateWinOpenedDuringCapture = true;
|
||||
},
|
||||
|
||||
_done: function (data) {
|
||||
// Note that _done will be called only once, by either receiveMessage or
|
||||
// notify, since it calls destroy, which cancels the timeout timer and
|
||||
@ -291,6 +325,13 @@ Capture.prototype = {
|
||||
this.captureCallback(this);
|
||||
this.destroy();
|
||||
|
||||
if (data && data.telemetry) {
|
||||
// Telemetry is currently disabled in the content process (bug 680508).
|
||||
for (let id in data.telemetry) {
|
||||
tel(id, data.telemetry[id]);
|
||||
}
|
||||
}
|
||||
|
||||
let callOnDones = function callOnDonesFn() {
|
||||
for (let callback of this.doneCallbacks) {
|
||||
try {
|
||||
@ -302,7 +343,9 @@ Capture.prototype = {
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
if (!data) {
|
||||
if (!data || this._privateWinOpenedDuringCapture) {
|
||||
if (this._privateWinOpenedDuringCapture)
|
||||
tel("CAPTURE_DONE_REASON", TEL_CAPTURE_DONE_PB_AFTER_START);
|
||||
callOnDones();
|
||||
return;
|
||||
}
|
||||
@ -343,3 +386,18 @@ function isPrivateBrowsingActive() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a value to one of this module's telemetry histograms.
|
||||
*
|
||||
* @param histogramID This is prefixed with this module's ID.
|
||||
* @param value The value to add.
|
||||
*/
|
||||
function tel(histogramID, value) {
|
||||
let id = TELEMETRY_HISTOGRAM_ID_PREFIX + histogramID;
|
||||
Services.telemetry.getHistogramById(id).add(value);
|
||||
}
|
||||
|
||||
function schedule(callback) {
|
||||
Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
}
|
||||
|
@ -43,11 +43,14 @@ const backgroundPageThumbsContent = {
|
||||
this._onLoad = function onLoad(event) {
|
||||
if (event.target != content.document)
|
||||
return;
|
||||
let pageLoadTime = new Date() - loadDate;
|
||||
removeEventListener("load", this._onLoad, true);
|
||||
delete this._onLoad;
|
||||
|
||||
let canvas = PageThumbs._createCanvas(content);
|
||||
let captureDate = new Date();
|
||||
PageThumbs._captureToCanvas(content, canvas);
|
||||
let captureTime = new Date() - captureDate;
|
||||
|
||||
let finalURL = this._webNav.currentURI.spec;
|
||||
let fileReader = Cc["@mozilla.org/files/filereader;1"].
|
||||
@ -57,6 +60,10 @@ const backgroundPageThumbsContent = {
|
||||
id: msg.json.id,
|
||||
imageData: fileReader.result,
|
||||
finalURL: finalURL,
|
||||
telemetry: {
|
||||
CAPTURE_PAGE_LOAD_TIME_MS: pageLoadTime,
|
||||
CAPTURE_CANVAS_DRAW_TIME_MS: captureTime,
|
||||
},
|
||||
});
|
||||
};
|
||||
canvas.toBlob(blob => fileReader.readAsArrayBuffer(blob));
|
||||
@ -70,6 +77,7 @@ const backgroundPageThumbsContent = {
|
||||
addEventListener("load", this._onLoad, true);
|
||||
this._webNav.loadURI(msg.json.url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
|
||||
null, null, null);
|
||||
let loadDate = new Date();
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -164,6 +164,42 @@ let tests = [
|
||||
win.close();
|
||||
},
|
||||
|
||||
function openPrivateWindowDuringCapture() {
|
||||
let url = "http://example.com/";
|
||||
let file = fileForURL(url);
|
||||
ok(!file.exists(), "Thumbnail file should not already exist.");
|
||||
|
||||
let deferred = imports.Promise.defer();
|
||||
|
||||
let waitCount = 0;
|
||||
function maybeFinish() {
|
||||
if (++waitCount == 2)
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
imports.BackgroundPageThumbs.capture(url, {
|
||||
onDone: function (capturedURL) {
|
||||
is(capturedURL, url, "Captured URL should be URL passed to capture.");
|
||||
ok(!file.exists(),
|
||||
"Thumbnail file should not exist because a private window " +
|
||||
"was opened during the capture.");
|
||||
maybeFinish();
|
||||
},
|
||||
});
|
||||
|
||||
// Opening the private window at this point relies on a couple of
|
||||
// implementation details: (1) The capture will start immediately and
|
||||
// synchronously (since at this point in the test, the service is
|
||||
// initialized and its queue is empty), and (2) when it starts the capture
|
||||
// registers with the window watcher.
|
||||
openPrivateWindow().then(function (win) {
|
||||
win.close();
|
||||
maybeFinish();
|
||||
});
|
||||
|
||||
yield deferred.promise;
|
||||
},
|
||||
|
||||
function noCookies() {
|
||||
// Visit the test page in the browser and tell it to set a cookie.
|
||||
let url = testPageURL({ setGreenCookie: true });
|
||||
@ -263,7 +299,7 @@ let tests = [
|
||||
imports.BackgroundPageThumbs.capture(url, {onDone: doneCallback});
|
||||
imports.BackgroundPageThumbs.capture(url, {onDone: doneCallback});
|
||||
yield deferred.promise;
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
function capture(url, options) {
|
||||
|
Loading…
Reference in New Issue
Block a user