Merge m-c to birch.

This commit is contained in:
Ryan VanderMeulen 2013-07-16 21:22:50 -04:00
commit 4054f59257
62 changed files with 1057 additions and 645 deletions

View File

@ -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:

View File

@ -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

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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);
});
});
}

View File

@ -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() {

View File

@ -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);

View File

@ -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

View File

@ -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);
}
}

View File

@ -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)))

View File

@ -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>

View File

@ -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:

View File

@ -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

View File

@ -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)) {

View File

@ -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)) {

View File

@ -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;
},

View File

@ -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);

View File

@ -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,

View File

@ -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();

View File

@ -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

View File

@ -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/

View File

@ -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()) {

View File

@ -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);

View File

@ -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); }

View File

@ -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;
}
}
}
}

View File

@ -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), "๑๑๐%");

View 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);

View File

@ -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 #

View File

@ -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. */

View File

@ -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 {

View File

@ -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";

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View 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>

View File

@ -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

View 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

View 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

View File

@ -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 \

View File

@ -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 -->

View File

@ -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);
}
}
}
}
}

View File

@ -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>

View File

@ -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"

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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.

View File

@ -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):
'''

View 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());
}

View File

@ -6,6 +6,7 @@
head =
tail =
[test_do_get_tempdir.js]
[test_execute_soon.js]
[test_get_file.js]
[test_get_idle.js]

View File

@ -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.

View File

@ -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()

View File

@ -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

View File

@ -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)"
}
}

View File

@ -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);
}

View File

@ -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();
},
};

View File

@ -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) {