diff --git a/accessible/src/generic/HyperTextAccessible.cpp b/accessible/src/generic/HyperTextAccessible.cpp
index ea130f3b2627..f929eddf4ded 100644
--- a/accessible/src/generic/HyperTextAccessible.cpp
+++ b/accessible/src/generic/HyperTextAccessible.cpp
@@ -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:
diff --git a/accessible/src/generic/HyperTextAccessible.h b/accessible/src/generic/HyperTextAccessible.h
index 5f3bcda301ef..ffa7fd1140ce 100644
--- a/accessible/src/generic/HyperTextAccessible.h
+++ b/accessible/src/generic/HyperTextAccessible.h
@@ -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
diff --git a/accessible/src/jsat/AccessFu.jsm b/accessible/src/jsat/AccessFu.jsm
index a17283e1c989..10f740af2de2 100644
--- a/accessible/src/jsat/AccessFu.jsm
+++ b/accessible/src/jsat/AccessFu.jsm
@@ -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) {
diff --git a/accessible/src/jsat/Utils.jsm b/accessible/src/jsat/Utils.jsm
index 257a2e394c94..b9a924963dbe 100644
--- a/accessible/src/jsat/Utils.jsm
+++ b/accessible/src/jsat/Utils.jsm
@@ -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;
}
}
diff --git a/accessible/src/jsat/content-script.js b/accessible/src/jsat/content-script.js
index 34f53f315957..f1bcf76e7d12 100644
--- a/accessible/src/jsat/content-script.js
+++ b/accessible/src/jsat/content-script.js
@@ -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);
diff --git a/browser/devtools/webconsole/test/browser_console_iframe_messages.js b/browser/devtools/webconsole/test/browser_console_iframe_messages.js
index 3f6ed2db5c43..8131f928bf2c 100644
--- a/browser/devtools/webconsole/test/browser_console_iframe_messages.js
+++ b/browser/devtools/webconsole/test/browser_console_iframe_messages.js
@@ -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);
+ });
});
}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_613013_console_api_iframe.js b/browser/devtools/webconsole/test/browser_webconsole_bug_613013_console_api_iframe.js
index 978c15880319..7e0fe28e1d6a 100644
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_613013_console_api_iframe.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_613013_console_api_iframe.js
@@ -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() {
diff --git a/browser/devtools/webconsole/test/head.js b/browser/devtools/webconsole/test/head.js
index b1f5d622ea19..b7f1f4ce2cdc 100644
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -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);
diff --git a/browser/modules/SitePermissions.jsm b/browser/modules/SitePermissions.jsm
index 583be1897069..d26163a1405b 100644
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -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
diff --git a/content/events/src/nsEventStateManager.cpp b/content/events/src/nsEventStateManager.cpp
index 29d0b0d4ac9c..0ed1a4d8ab90 100644
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -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);
}
}
diff --git a/content/events/test/Makefile.in b/content/events/test/Makefile.in
index 66725c5bf3ab..f1fbc4f0cb62 100644
--- a/content/events/test/Makefile.in
+++ b/content/events/test/Makefile.in
@@ -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)))
diff --git a/content/events/test/test_dblclick_explicit_original_target.html b/content/events/test/test_dblclick_explicit_original_target.html
new file mode 100644
index 000000000000..8aa5f5d4c794
--- /dev/null
+++ b/content/events/test/test_dblclick_explicit_original_target.html
@@ -0,0 +1,33 @@
+
+
+
+ Test explicit original target of dblclick event
+
+
+
+
+
+Test explicit original target of dblclick event
+
+
+
+
+
+
+
+
diff --git a/dom/ipc/Blob.cpp b/dom/ipc/Blob.cpp
index 59093416ce29..399c508ced01 100644
--- a/dom/ipc/Blob.cpp
+++ b/dom/ipc/Blob.cpp
@@ -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::GetInternalStream(nsIInputStream** aStream)
}
template
-Blob::Blob(nsIDOMBlob* aBlob)
-: mBlob(aBlob), mRemoteBlob(nullptr), mOwnsBlob(true), mBlobIsFile(false)
+Blob::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 file = do_QueryInterface(aBlob);
@@ -1023,10 +1025,13 @@ Blob::Blob(nsIDOMBlob* aBlob)
}
template
-Blob::Blob(const ConstructorParamsType& aParams)
-: mBlob(nullptr), mRemoteBlob(nullptr), mOwnsBlob(false), mBlobIsFile(false)
+Blob::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::Blob(const ConstructorParamsType& aParams)
template
Blob*
-Blob::Create(const ConstructorParamsType& aParams)
+Blob::Create(ContentManager* aManager,
+ const ConstructorParamsType& aParams)
{
MOZ_ASSERT(NS_IsMainThread());
@@ -1059,7 +1065,7 @@ Blob::Create(const ConstructorParamsType& aParams)
case ChildBlobConstructorParams::TNormalBlobConstructorParams:
case ChildBlobConstructorParams::TFileBlobConstructorParams:
case ChildBlobConstructorParams::TMysteryBlobConstructorParams:
- return new Blob(aParams);
+ return new Blob(aManager, aParams);
case ChildBlobConstructorParams::TSlicedBlobConstructorParams: {
const SlicedBlobConstructorParams& params =
@@ -1074,7 +1080,7 @@ Blob::Create(const ConstructorParamsType& aParams)
getter_AddRefs(slice));
NS_ENSURE_SUCCESS(rv, nullptr);
- return new Blob(slice);
+ return new Blob(aManager, slice);
}
default:
diff --git a/dom/ipc/Blob.h b/dom/ipc/Blob.h
index 460ddd345327..78fa76c86cce 100644
--- a/dom/ipc/Blob.h
+++ b/dom/ipc/Blob.h
@@ -162,6 +162,7 @@ class Blob : public BlobTraits::BaseType
friend class RemoteBlob;
public:
+ typedef typename BlobTraits::ConcreteContentManagerType ContentManager;
typedef typename BlobTraits::ProtocolType ProtocolType;
typedef typename BlobTraits::StreamType StreamType;
typedef typename BlobTraits::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
CreateRemoteBlob(const ConstructorParamsType& aParams);
@@ -229,6 +235,8 @@ private:
virtual bool
RecvPBlobStreamConstructor(StreamType* aActor) MOZ_OVERRIDE;
+
+ nsRefPtr mManager;
};
} // namespace ipc
diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp
index 0464ddffca3a..08062664fd17 100644
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -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)) {
diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp
index caefdcbdbc98..dad5ca324610 100644
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -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)) {
diff --git a/dom/media/PeerConnection.js b/dom/media/PeerConnection.js
index d002e7eb1c38..8815c6576f42 100644
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -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;
},
diff --git a/gfx/layers/opengl/CompositingRenderTargetOGL.h b/gfx/layers/opengl/CompositingRenderTargetOGL.h
index ee41ad3d3e04..904fb79328ed 100644
--- a/gfx/layers/opengl/CompositingRenderTargetOGL.h
+++ b/gfx/layers/opengl/CompositingRenderTargetOGL.h
@@ -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);
diff --git a/gfx/layers/opengl/CompositorOGL.cpp b/gfx/layers/opengl/CompositorOGL.cpp
index a7de1dbf94bc..3ce748fdf421 100644
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -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,
diff --git a/image/src/VectorImage.cpp b/image/src/VectorImage.cpp
index 63260e621e68..575032d3f3ab 100644
--- a/image/src/VectorImage.cpp
+++ b/image/src/VectorImage.cpp
@@ -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 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 cb =
new SVGDrawingCallback(mSVGDocumentWrapper,
- nsIntRect(nsIntPoint(0, 0), scaledViewport),
+ nsIntRect(nsIntPoint(0, 0), aViewportSize),
+ scale,
aFlags);
- nsRefPtr drawable = new gfxCallbackDrawable(cb, scaledViewportGfx);
+ nsRefPtr 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();
diff --git a/intl/icu/source/i18n/decimfmt.cpp b/intl/icu/source/i18n/decimfmt.cpp
index 8e1cb0b6dbaa..f1cac6ab7642 100644
--- a/intl/icu/source/i18n/decimfmt.cpp
+++ b/intl/icu/source/i18n/decimfmt.cpp
@@ -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
diff --git a/intl/update-icu.sh b/intl/update-icu.sh
index 5d0f54d79e8c..152a4172ae3d 100755
--- a/intl/update-icu.sh
+++ b/intl/update-icu.sh
@@ -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
# E.g., for ICU 50.1.1: update-icu.sh http://source.icu-project.org/repos/icu/icu/tags/release-50-1-1/
diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp
index 07f20e872670..82b55afd808e 100644
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -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()) {
diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp
index 2d7d68bca6b5..c27cd21f7126 100644
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -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);
diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h
index 6a4bcdc84cb3..bdcbc96f161f 100644
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -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); }
diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h
index 37b34717b34d..350410c1899e 100644
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -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() && !obj->is() &&
+ 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;
- }
- }
}
}
diff --git a/js/src/tests/Intl/NumberFormat/format.js b/js/src/tests/Intl/NumberFormat/format.js
index 108adbb9dcec..32b51adc0edd 100644
--- a/js/src/tests/Intl/NumberFormat/format.js
+++ b/js/src/tests/Intl/NumberFormat/format.js
@@ -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), "๑๑๐%");
diff --git a/js/src/tests/Intl/NumberFormat/significantDigitsOfZero.js b/js/src/tests/Intl/NumberFormat/significantDigitsOfZero.js
new file mode 100644
index 000000000000..f2cd77cda359
--- /dev/null
+++ b/js/src/tests/Intl/NumberFormat/significantDigitsOfZero.js
@@ -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);
diff --git a/js/src/tests/jstests.list b/js/src/tests/jstests.list
index bd5eb8f030ae..5485bd6033e9 100644
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -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 #
diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp
index 8746271f15bb..0b83f5a0dad3 100644
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -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. */
diff --git a/js/src/vm/Shape.h b/js/src/vm/Shape.h
index b10a50f50d7a..d506c60470d5 100644
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -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(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(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(uintptr_t(shape) |
+ SHAPE_HAD_COLLISION(*spp));
+}
namespace js {
diff --git a/layout/inspector/tests/test_bug877690.html b/layout/inspector/tests/test_bug877690.html
index 773ea4f80a2a..6dc3f4b6ba9b 100644
--- a/layout/inspector/tests/test_bug877690.html
+++ b/layout/inspector/tests/test_bug877690.html
@@ -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";
diff --git a/layout/reftests/backgrounds/vector/empty/reftest.list b/layout/reftests/backgrounds/vector/empty/reftest.list
index 1a7af876cdca..d83e5c1da69c 100644
--- a/layout/reftests/backgrounds/vector/empty/reftest.list
+++ b/layout/reftests/backgrounds/vector/empty/reftest.list
@@ -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
diff --git a/layout/reftests/svg/as-image/background-scale-no-viewbox-1-ref.html b/layout/reftests/svg/as-image/background-scale-no-viewbox-1-ref.html
new file mode 100644
index 000000000000..e9ee74d29b10
--- /dev/null
+++ b/layout/reftests/svg/as-image/background-scale-no-viewbox-1-ref.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
diff --git a/layout/reftests/svg/as-image/background-scale-no-viewbox-1.html b/layout/reftests/svg/as-image/background-scale-no-viewbox-1.html
new file mode 100644
index 000000000000..e8ea278bb61e
--- /dev/null
+++ b/layout/reftests/svg/as-image/background-scale-no-viewbox-1.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/layout/reftests/svg/as-image/background-scale-with-viewbox-1-ref.html b/layout/reftests/svg/as-image/background-scale-with-viewbox-1-ref.html
new file mode 100644
index 000000000000..e9ee74d29b10
--- /dev/null
+++ b/layout/reftests/svg/as-image/background-scale-with-viewbox-1-ref.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
diff --git a/layout/reftests/svg/as-image/background-scale-with-viewbox-1.html b/layout/reftests/svg/as-image/background-scale-with-viewbox-1.html
new file mode 100644
index 000000000000..295aedf52020
--- /dev/null
+++ b/layout/reftests/svg/as-image/background-scale-with-viewbox-1.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/layout/reftests/svg/as-image/background-stretch-1-ref.html b/layout/reftests/svg/as-image/background-stretch-1-ref.html
new file mode 100644
index 000000000000..f84768e27c4b
--- /dev/null
+++ b/layout/reftests/svg/as-image/background-stretch-1-ref.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
diff --git a/layout/reftests/svg/as-image/background-stretch-1.html b/layout/reftests/svg/as-image/background-stretch-1.html
new file mode 100644
index 000000000000..0931cf3ccce9
--- /dev/null
+++ b/layout/reftests/svg/as-image/background-stretch-1.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/layout/reftests/svg/as-image/reftest.list b/layout/reftests/svg/as-image/reftest.list
index 7e0195d0f87d..0ba21dd7503b 100644
--- a/layout/reftests/svg/as-image/reftest.list
+++ b/layout/reftests/svg/as-image/reftest.list
@@ -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
diff --git a/layout/reftests/svg/as-image/white-rect-no-viewbox.svg b/layout/reftests/svg/as-image/white-rect-no-viewbox.svg
new file mode 100644
index 000000000000..76a7efd3dc80
--- /dev/null
+++ b/layout/reftests/svg/as-image/white-rect-no-viewbox.svg
@@ -0,0 +1,8 @@
+
+
+
diff --git a/layout/reftests/svg/as-image/white-rect-with-viewbox.svg b/layout/reftests/svg/as-image/white-rect-with-viewbox.svg
new file mode 100644
index 000000000000..6bb59d19a492
--- /dev/null
+++ b/layout/reftests/svg/as-image/white-rect-with-viewbox.svg
@@ -0,0 +1,11 @@
+
+
+
diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in
index 94b2f7db233e..d575b02da9bd 100644
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -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 \
diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd
index aeadc707ebf2..de7b3db80474 100644
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -68,12 +68,10 @@
-
-
diff --git a/mobile/android/base/preferences/SearchPreferenceCategory.java b/mobile/android/base/preferences/SearchPreferenceCategory.java
deleted file mode 100644
index 313787485912..000000000000
--- a/mobile/android/base/preferences/SearchPreferenceCategory.java
+++ /dev/null
@@ -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);
- }
- }
- }
- }
-}
diff --git a/mobile/android/base/resources/values/dimens.xml b/mobile/android/base/resources/values/dimens.xml
index 8d9670aa1b40..592d6278a904 100644
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -61,7 +61,6 @@
48dp
64dp
26dp
- 32dp
90dp
160dp
22sp
diff --git a/mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml b/mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml
index 48f10bdf1768..40977f940932 100644
--- a/mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml
+++ b/mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml
@@ -14,12 +14,6 @@
-
-
-
-
-
+
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/mobile/android/base/resources/xml/preferences_search.xml b/mobile/android/base/resources/xml/preferences_search.xml
deleted file mode 100644
index 721aefbfda00..000000000000
--- a/mobile/android/base/resources/xml/preferences_search.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in
index 428dc4e223ec..efa1ec131cfb 100644
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -80,12 +80,10 @@
&settings;
&settings_title;
&pref_category_customize;
- &pref_category_search;
&pref_category_display;
&pref_category_privacy_short;
&pref_category_vendor;
&pref_category_datareporting;
- &pref_category_installed_search_engines;
&pref_header_customize;
&pref_header_display;
diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp
index 60af7a572ba6..f2c9f225e8d7 100644
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -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 session;
// If we have an application cache, we check it first.
diff --git a/python/mozboot/mozboot/osx.py b/python/mozboot/mozboot/osx.py
index a4c917d32da6..8aa2d26b70b4 100644
--- a/python/mozboot/mozboot/osx.py
+++ b/python/mozboot/mozboot/osx.py
@@ -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):
'''
diff --git a/testing/xpcshell/example/unit/test_do_get_tempdir.js b/testing/xpcshell/example/unit/test_do_get_tempdir.js
new file mode 100644
index 000000000000..506a80b2eaec
--- /dev/null
+++ b/testing/xpcshell/example/unit/test_do_get_tempdir.js
@@ -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());
+}
diff --git a/testing/xpcshell/example/unit/xpcshell.ini b/testing/xpcshell/example/unit/xpcshell.ini
index 6a51f336491d..edd2c0446795 100644
--- a/testing/xpcshell/example/unit/xpcshell.ini
+++ b/testing/xpcshell/example/unit/xpcshell.ini
@@ -6,6 +6,7 @@
head =
tail =
+[test_do_get_tempdir.js]
[test_execute_soon.js]
[test_get_file.js]
[test_get_idle.js]
diff --git a/testing/xpcshell/head.js b/testing/xpcshell/head.js
index ed41443235bd..d0f4afd06a08 100644
--- a/testing/xpcshell/head.js
+++ b/testing/xpcshell/head.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.
diff --git a/testing/xpcshell/remotexpcshelltests.py b/testing/xpcshell/remotexpcshelltests.py
index 2b02e3ed4f29..17267abfab85 100644
--- a/testing/xpcshell/remotexpcshelltests.py
+++ b/testing/xpcshell/remotexpcshelltests.py
@@ -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()
diff --git a/testing/xpcshell/runxpcshelltests.py b/testing/xpcshell/runxpcshelltests.py
index 43371aea6456..7ef2af471758 100644
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -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
diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json
index d3518f3a663d..f348ac6c6d1a 100644
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -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)"
}
}
diff --git a/toolkit/components/thumbnails/BackgroundPageThumbs.jsm b/toolkit/components/thumbnails/BackgroundPageThumbs.jsm
index fcfc3973925d..bb3266cb6a6a 100644
--- a/toolkit/components/thumbnails/BackgroundPageThumbs.jsm
+++ b/toolkit/components/thumbnails/BackgroundPageThumbs.jsm
@@ -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);
+}
diff --git a/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js b/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js
index 7ef92e500c7a..c40b7316e7ed 100644
--- a/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js
+++ b/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js
@@ -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();
},
};
diff --git a/toolkit/components/thumbnails/test/browser_thumbnails_background.js b/toolkit/components/thumbnails/test/browser_thumbnails_background.js
index 8657033f012e..689b0c404b3c 100644
--- a/toolkit/components/thumbnails/test/browser_thumbnails_background.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_background.js
@@ -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) {