Merge m-c to b2ginbound, a=merge

This commit is contained in:
Wes Kocher 2015-09-28 16:32:01 -07:00
commit 02a202b3f1
294 changed files with 6955 additions and 1858 deletions

View File

@ -738,6 +738,13 @@ DocAccessible::AttributeWillChange(nsIDocument* aDocument,
mStateBitWasOn = accessible->Unavailable();
}
void
DocAccessible::NativeAnonymousChildListChange(nsIDocument* aDocument,
nsIContent* aContent,
bool aIsRemove)
{
}
void
DocAccessible::AttributeChanged(nsIDocument* aDocument,
dom::Element* aElement,
@ -1357,6 +1364,11 @@ DocAccessible::ProcessInvalidationList()
continue;
}
if (!child->Parent()) {
NS_ERROR("The accessible is in document but doesn't have a parent");
continue;
}
// XXX: update context flags
{
Accessible* oldParent = child->Parent();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -468,6 +468,13 @@ loop.shared.actions = (function() {
// socialShareProviders: Array - Optional.
}),
/**
* Notifies if the user agent will handle the room or not.
*/
UserAgentHandlesRoom: Action.define("userAgentHandlesRoom", {
handlesRoom: Boolean
}),
/**
* Updates the Social API information when it is received.
* XXX: should move to some roomActions module - refs bug 1079284
@ -483,6 +490,16 @@ loop.shared.actions = (function() {
JoinRoom: Action.define("joinRoom", {
}),
/**
* A special action for metrics logging to define what type of join
* occurred when JoinRoom was activated.
* XXX: should move to some roomActions module - refs bug 1079284
*/
MetricsLogJoinRoom: Action.define("metricsLogJoinRoom", {
userAgentHandledRoom: Boolean
// ownRoom: Boolean - Optional. Expected if firefoxHandledRoom is true.
}),
/**
* Starts the process for the user to join the room.
* XXX: should move to some roomActions module - refs bug 1079284

View File

@ -132,6 +132,9 @@ loop.store.ActiveRoomStore = (function() {
videoMuted: false,
remoteVideoEnabled: false,
failureReason: undefined,
// Whether or not Firefox can handle this room in the conversation
// window, rather than us handling it in the standalone.
userAgentHandlesRoom: undefined,
// Tracks if the room has been used during this
// session. 'Used' means at least one call has been placed
// with it. Entering and leaving the room without seeing
@ -237,6 +240,7 @@ loop.store.ActiveRoomStore = (function() {
"roomFailure",
"retryAfterRoomFailure",
"updateRoomInfo",
"userAgentHandlesRoom",
"gotMediaPermission",
"joinRoom",
"joinedRoom",
@ -327,6 +331,9 @@ loop.store.ActiveRoomStore = (function() {
* This action is only used for the standalone UI.
*
* @param {sharedActions.FetchServerData} actionData
* @return {Promise} For testing purposes, returns a promise that is resolved
* once data is received from the server, and it is determined
* if Firefox handles the room or not.
*/
fetchServerData: function(actionData) {
if (actionData.windowType !== "room") {
@ -342,68 +349,144 @@ loop.store.ActiveRoomStore = (function() {
this._registerPostSetupActions();
this._getRoomDataForStandalone(actionData.cryptoKey);
var dataPromise = this._getRoomDataForStandalone(actionData.cryptoKey);
var userAgentHandlesPromise = this._promiseDetectUserAgentHandles();
return Promise.all([dataPromise, userAgentHandlesPromise]).then(function(results) {
results.forEach(function(result) {
this.dispatcher.dispatch(result);
}.bind(this));
}.bind(this));
},
/**
* Gets the room data for the standalone, decrypting it as necessary.
*
* @param {String} roomCryptoKey The crypto key associated to the room.
* @return {Promise} A promise that is resolved once the get
* and decryption is complete.
*/
_getRoomDataForStandalone: function(roomCryptoKey) {
this._mozLoop.rooms.get(this._storeState.roomToken, function(err, result) {
if (err) {
this.dispatchAction(new sharedActions.RoomFailure({
error: err,
failedJoinRequest: false
return new Promise(function(resolve, reject) {
this._mozLoop.rooms.get(this._storeState.roomToken, function(err, result) {
if (err) {
resolve(new sharedActions.RoomFailure({
error: err,
failedJoinRequest: false
}));
return;
}
var roomInfoData = new sharedActions.UpdateRoomInfo({
// If we've got this far, then we want to go to the ready state
// regardless of success of failure. This is because failures of
// crypto don't stop the user using the room, they just stop
// us putting up the information.
roomState: ROOM_STATES.READY,
roomUrl: result.roomUrl
});
if (!result.context && !result.roomName) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.NO_DATA;
resolve(roomInfoData);
return;
}
// This handles 'legacy', non-encrypted room names.
if (result.roomName && !result.context) {
roomInfoData.roomName = result.roomName;
resolve(roomInfoData);
return;
}
if (!crypto.isSupported()) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED;
resolve(roomInfoData);
return;
}
if (!roomCryptoKey) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.NO_CRYPTO_KEY;
resolve(roomInfoData);
return;
}
crypto.decryptBytes(roomCryptoKey, result.context.value)
.then(function(decryptedResult) {
var realResult = JSON.parse(decryptedResult);
roomInfoData.roomDescription = realResult.description;
roomInfoData.roomContextUrls = realResult.urls;
roomInfoData.roomName = realResult.roomName;
resolve(roomInfoData);
}, function(error) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.DECRYPT_FAILED;
resolve(roomInfoData);
});
}.bind(this));
}.bind(this));
},
/**
* If the user agent is Firefox, it sends a message to Firefox to see if
* the room can be handled within Firefox rather than the standalone UI.
*
* @return {Promise} A promise that is resolved once it has been determined
* if Firefox can handle the room.
*/
_promiseDetectUserAgentHandles: function() {
return new Promise(function(resolve, reject) {
function resolveWithNotHandlingResponse() {
resolve(new sharedActions.UserAgentHandlesRoom({
handlesRoom: false
}));
}
// If we're not Firefox, don't even try to see if it can be handled
// in the browser.
if (!loop.shared.utils.isFirefox(navigator.userAgent)) {
resolveWithNotHandlingResponse();
return;
}
var roomInfoData = new sharedActions.UpdateRoomInfo({
// If we've got this far, then we want to go to the ready state
// regardless of success of failure. This is because failures of
// crypto don't stop the user using the room, they just stop
// us putting up the information.
roomState: ROOM_STATES.READY,
roomUrl: result.roomUrl
});
// Set up a timer in case older versions of Firefox don't give us a response.
var timer = setTimeout(resolveWithNotHandlingResponse, 250);
var webChannelListenerFunc;
if (!result.context && !result.roomName) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.NO_DATA;
this.dispatcher.dispatch(roomInfoData);
return;
// Listen for the result.
function webChannelListener(e) {
if (e.detail.id !== "loop-link-clicker") {
return;
}
// Stop the default response.
clearTimeout(timer);
// Remove the listener.
window.removeEventListener("WebChannelMessageToContent", webChannelListenerFunc);
// Resolve with the details of if we're able to handle or not.
resolve(new sharedActions.UserAgentHandlesRoom({
handlesRoom: !!e.detail.message && e.detail.message.response
}));
}
// This handles 'legacy', non-encrypted room names.
if (result.roomName && !result.context) {
roomInfoData.roomName = result.roomName;
this.dispatcher.dispatch(roomInfoData);
return;
}
webChannelListenerFunc = webChannelListener.bind(this);
if (!crypto.isSupported()) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED;
this.dispatcher.dispatch(roomInfoData);
return;
}
window.addEventListener("WebChannelMessageToContent", webChannelListenerFunc);
if (!roomCryptoKey) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.NO_CRYPTO_KEY;
this.dispatcher.dispatch(roomInfoData);
return;
}
var dispatcher = this.dispatcher;
crypto.decryptBytes(roomCryptoKey, result.context.value)
.then(function(decryptedResult) {
var realResult = JSON.parse(decryptedResult);
roomInfoData.roomDescription = realResult.description;
roomInfoData.roomContextUrls = realResult.urls;
roomInfoData.roomName = realResult.roomName;
dispatcher.dispatch(roomInfoData);
}, function(error) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.DECRYPT_FAILED;
dispatcher.dispatch(roomInfoData);
});
// Now send a message to the chrome to see if it can handle this room.
window.dispatchEvent(new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "loop-link-clicker",
message: {
command: "checkWillOpenRoom",
roomToken: this._storeState.roomToken
}
}
}));
}.bind(this));
},
@ -426,6 +509,18 @@ loop.store.ActiveRoomStore = (function() {
this.setStoreState(newState);
},
/**
* Handles the userAgentHandlesRoom action. Updates the store's data with
* the new state.
*
* @param {sharedActions.userAgentHandlesRoom} actionData
*/
userAgentHandlesRoom: function(actionData) {
this.setStoreState({
userAgentHandlesRoom: actionData.handlesRoom
});
},
/**
* Handles the updateSocialShareInfo action. Updates the room data with new
* Social API info.
@ -477,14 +572,11 @@ loop.store.ActiveRoomStore = (function() {
},
/**
* Handles the action to join to a room.
* Checks that there are audio and video devices available, and joins the
* room if there are. If there aren't then it will dispatch a ConnectionFailure
* action with NO_MEDIA.
*/
joinRoom: function() {
// Reset the failure reason if necessary.
if (this.getStoreState().failureReason) {
this.setStoreState({failureReason: undefined});
}
_checkDevicesAndJoinRoom: function() {
// XXX Ideally we'd do this check before joining a room, but we're waiting
// for the UX for that. See bug 1166824. In the meantime this gives us
// additional information for analysis.
@ -501,6 +593,77 @@ loop.store.ActiveRoomStore = (function() {
}.bind(this));
},
/**
* Hands off the room join to Firefox.
*/
_handoffRoomJoin: function() {
var channelListener;
function handleRoomJoinResponse(e) {
if (e.detail.id !== "loop-link-clicker") {
return;
}
window.removeEventListener("WebChannelMessageToContent", channelListener);
if (!e.detail.message || !e.detail.message.response) {
// XXX Firefox didn't handle this, even though it said it could
// previously. We should add better user feedback here.
console.error("Firefox didn't handle room it said it could.");
} else {
this.dispatcher.dispatch(new sharedActions.JoinedRoom({
apiKey: "",
sessionToken: "",
sessionId: "",
expires: 0
}));
}
}
channelListener = handleRoomJoinResponse.bind(this);
window.addEventListener("WebChannelMessageToContent", channelListener);
// Now we're set up, dispatch an event.
window.dispatchEvent(new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "loop-link-clicker",
message: {
command: "openRoom",
roomToken: this._storeState.roomToken
}
}
}));
},
/**
* Handles the action to join to a room.
*/
joinRoom: function() {
// Reset the failure reason if necessary.
if (this.getStoreState().failureReason) {
this.setStoreState({ failureReason: undefined });
}
// If we're standalone and we know Firefox can handle the room, then hand
// it off.
if (this._storeState.standalone && this._storeState.userAgentHandlesRoom) {
this.dispatcher.dispatch(new sharedActions.MetricsLogJoinRoom({
userAgentHandledRoom: true,
ownRoom: true
}));
this._handoffRoomJoin();
return;
}
this.dispatcher.dispatch(new sharedActions.MetricsLogJoinRoom({
userAgentHandledRoom: false
}));
// Otherwise, we handle the room ourselves.
this._checkDevicesAndJoinRoom();
},
/**
* Handles the action that signifies when media permission has been
* granted and starts joining the room.
@ -540,6 +703,15 @@ loop.store.ActiveRoomStore = (function() {
* @param {sharedActions.JoinedRoom} actionData
*/
joinedRoom: function(actionData) {
// If we're standalone and firefox is handling, then just store the new
// state. No need to do anything else.
if (this._storeState.standalone && this._storeState.userAgentHandlesRoom) {
this.setStoreState({
roomState: ROOM_STATES.JOINED
});
return;
}
this.setStoreState({
apiKey: actionData.apiKey,
sessionToken: actionData.sessionToken,

View File

@ -25,6 +25,27 @@ body,
background: #000;
}
/* Logos */
.loop-logo-text {
background: url("../img/hello-logo-text.svg") no-repeat;
width: 200px;
height: 36px;
}
.loop-logo {
background: url("../shared/img/helloicon.svg") no-repeat;
width: 100px;
height: 100px;
}
.mozilla-logo {
background: url("../img/mozilla-logo.svg#logo") no-repeat;
background-size: contain;
width: 100px;
height: 30px;
}
.room-conversation-wrapper > .beta-logo {
position: fixed;
top: 0;
@ -43,7 +64,7 @@ body,
margin: 0 auto;
height: 30px;
background-size: contain;
background-image: url("../shared/img/mozilla-logo.png");
background-image: url("../img/mozilla-logo.svg#logo-white");
background-repeat: no-repeat;
}
@ -138,6 +159,45 @@ html[dir="rtl"] .rooms-footer .footer-logo {
line-height: 24px;
}
/**
* Handle in Firefox views
*/
.handle-user-agent-view-scroller {
height: 100%;
overflow: scroll;
}
.handle-user-agent-view {
margin: 2rem auto;
width: 500px;
}
.handle-user-agent-view > .info-panel {
padding-bottom: 40px;
font-size: 1.6rem;
}
.handle-user-agent-view > p,
.handle-user-agent-view > .info-panel > p {
margin-top: 0;
margin: 2rem auto;
}
.handle-user-agent-view > .info-panel > button {
width: 80%;
height: 4rem;
font-size: 1.6rem;
font-weight: bold;
}
.handle-user-agent-view > .info-panel > button.disabled {
background-color: #EBEBEB;
border-color: #EBEBEB;
color: #B2B0B3;
font-weight: normal;
}
/* Room wrapper layout */
.room-conversation-wrapper {

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 467.5 76"><path fill="#5D5F64" d="M263.4 28.2c-.2.4-.5.8-.8 1.2-.3.3-.7.6-1.2.8-.5.2-.9.3-1.4.3-.5 0-1-.1-1.4-.3-.5-.2-.8-.4-1.2-.8-.3-.3-.6-.7-.8-1.2-.2-.4-.3-.9-.3-1.4 0-.5.1-1 .3-1.4.2-.4.4-.8.8-1.2.3-.3.7-.6 1.2-.8.4-.2.9-.3 1.4-.3.5 0 1 .1 1.4.3.4.2.8.4 1.2.8.3.3.6.7.8 1.2.2.5.3.9.3 1.4 0 .5-.1 1-.3 1.4zm-.7-2.6c-.2-.4-.4-.7-.6-1-.2-.3-.6-.5-.9-.7-.4-.2-.7-.2-1.1-.2-.4 0-.8.1-1.1.2-.4.2-.7.4-.9.7-.3.3-.5.6-.6 1-.2.4-.2.8-.2 1.2 0 .4.1.8.2 1.2.2.4.4.7.6 1 .3.3.6.5.9.7.3.2.7.2 1.1.2.4 0 .8-.1 1.1-.2.4-.2.7-.4.9-.7.3-.3.5-.6.6-1 .1-.4.2-.8.2-1.2.1-.4 0-.8-.2-1.2zm-2.2 2.6c-.1-.3-.3-.5-.4-.6-.1-.1-.2-.3-.3-.4l-.1-.1h-.2v1.8h-.7v-4.1h1.3c.2 0 .4 0 .6.1.2.1.3.1.4.2.1.1.2.2.2.4 0 .1.1.3.1.5 0 .3-.1.6-.3.8-.2.2-.4.3-.8.3l.1.1.2.2c.1.1.1.2.2.2.1.1.1.2.2.3l.6 1h-.8l-.3-.7zm0-2.8c-.1-.1-.3-.2-.6-.2h-.4v1.3h.8c.1 0 .2-.1.2-.1.1-.1.2-.3.2-.5s0-.3-.2-.5zM35.6 13.9h-24v19h19.5v9.4H11.6v32.1H0V4.3h37.1l-1.5 9.6zM41.7 8.2c0-4.1 3.2-7.5 7.4-7.5 3.9 0 7.3 3.2 7.3 7.5 0 4.1-3.3 7.4-7.5 7.4-4.1 0-7.2-3.4-7.2-7.4zm1.6 66.2V24l11.2-2v52.4H43.3zM89.2 32.9c-1.1-.4-1.9-.7-3.1-.7-4.7 0-8.6 3.4-9.6 7.6v34.6H65.3V38.3c0-6.5-.7-10.6-1.8-13.8l10.2-2.6c1.2 2.3 1.9 5.3 1.9 8.1 4-5.6 8.1-8.2 13.1-8.2 1.6 0 2.6.2 3.9.8l-3.4 10.3zM103.3 51.8v.8c0 7.1 2.6 14.6 12.7 14.6 4.8 0 8.9-1.7 12.7-5.1l4.4 6.8c-5.4 4.6-11.5 6.8-18.4 6.8-14.6 0-23.7-10.4-23.7-26.8 0-9 1.9-15 6.4-20 4.2-4.8 9.2-6.9 15.7-6.9 5.1 0 9.7 1.3 14.1 5.3 4.5 4 6.7 10.3 6.7 22.3v2.3h-30.6zm9.8-21.4c-6.3 0-9.7 5-9.7 13.3h18.9c0-8.4-3.6-13.3-9.2-13.3zM165.5 10c-2.5-1.2-4-1.8-6.2-1.8-3.8 0-6.3 2.6-6.3 7.2v7.8h13.4l-2.8 7.7h-10.4v43.5h-11V30.9h-4.8v-7.7h5s-.3-2.8-.3-7.6C142.1 5 148.5 0 157.6 0c4.4 0 8 .9 11.5 2.9l-3.6 7.1zM210.1 49c0 16.5-8.8 26.7-22.7 26.7-13.9 0-22.6-10.4-22.6-26.8S173.6 22 187.2 22c14.6 0 22.9 10.8 22.9 27zm-32.9-.8c0 14.9 3.7 19.2 10.4 19.2 6.6 0 10.2-5.4 10.2-18.2 0-14.5-4-18.8-10.5-18.8-7 0-10.1 5.3-10.1 17.8z"/><path fill="#5D5F64" d="M243.6 74.4c-1.8-2.9-10.1-17.3-11.1-19.1-1.9 3.8-9.2 16.3-11.1 19.1h-14.1l19-27.8-14.8-22 12-2.4c2.3 3.8 6.9 11.8 9.3 16.6 1.4-3.3 6.6-13.6 8.1-15.7h13l-15.1 23.3 18.7 27.9h-13.9z"/><g fill="#5D5F64"><path d="M280.6 4.5h8.2v29.3h29.5V4.5h8.4v70.1h-8.4V40.7h-29.5v33.9h-8.2V4.5zM371.5 64.3l3.1 5.1c-4.5 4.1-10.6 6.3-17.2 6.3-14.1 0-22.6-10.2-22.6-27.1 0-8.6 1.8-14.1 6.1-19.2 4.1-4.8 9.1-7.1 15.2-7.1 5.5 0 10.3 1.9 13.8 5.5 4.4 4.5 5.6 9.3 5.8 21.5v1.1H344v1.2c0 4.8.6 8.5 2.3 11.1 2.9 4.4 7.6 6.2 12.7 6.2 4.9.2 8.9-1.3 12.5-4.6zM344 44.5h23.3c-.1-5.5-.8-8.9-2.3-11.3-1.7-2.8-5.3-4.5-9.2-4.5-7.3-.1-11.4 5.2-11.8 15.8zM393.5 64.4c0 4 .6 5.1 2.9 5.1.3 0 1-.2 1-.2l1.6 5.2c-2 .9-3 1.1-5.1 1.1-2.5 0-4.5-.7-6-2.1-1.6-1.4-2.5-3.6-2.5-7.3v-54c0-6.6-1.2-10.4-1.2-10.4l8-1.5s1.3 4.3 1.3 12.1v52zM413.9 64.4c0 4 .6 5.1 2.9 5.1.3 0 1-.2 1-.2l1.6 5.2c-2 .9-3 1.1-5.1 1.1-2.5 0-4.5-.7-6-2.1-1.6-1.4-2.5-3.6-2.5-7.3v-54c0-6.6-1.2-10.4-1.2-10.4l8-1.5s1.3 4.3 1.3 12.1v52zM445.3 22.2c8.5 0 14 3.9 17.5 8.9 3.2 4.6 4.7 10.6 4.7 18.9 0 17-9.1 26-21.9 26-14 0-22-10.3-22-27.1 0-16.6 8.3-26.7 21.7-26.7zm-.1 6.5c-4.5 0-8.7 2.1-10.4 5.5-1.6 3.2-2.5 7.3-2.5 13.3 0 7.2 1.2 13.5 3.2 16.7 1.8 3.1 5.9 5.1 10.3 5.1 5.3 0 9.3-2.8 11-7.7 1.1-3.2 1.5-6 1.5-11 0-7.2-.7-12-2.4-15.3-2-4.5-6.5-6.6-10.7-6.6z"/></g></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1 @@
<svg width="568" height="148" viewBox="0 0 568 148" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>Fill 1 Copy</title><style>use:not(:target) { display: none; } use { fill: #383838; } use[id$=&quot;-white&quot;] { fill: #fff; }</style><defs><path id="mozilla-logo" d="M23.39 42.294c1.72 2.656 2.478 5.026 3.44 10.024 6.728-6.568 15.025-10.024 24.08-10.024 8.18 0 14.89 2.656 20.085 8.077 1.4 1.36 2.75 3.113 3.913 4.833 9.038-9.257 17.132-12.91 27.976-12.91 7.722 0 15.042 2.31 19.51 6.156 5.583 4.823 7.353 10.622 7.353 24.124v70.244h-25.092V77.606c0-11.82-1.4-14.107-8.13-14.107-4.822 0-11.6 3.29-17.166 8.305v71.013H54.872V78.533c0-12.326-1.787-15.22-8.97-15.22-4.773 0-11.384 2.472-16.95 7.514v71.99H3.694V73.913c0-14.266-.978-20.43-3.693-25.277l23.39-6.34zm152.244 28.92c-1.772 5.228-2.715 12.168-2.715 22.016 0 11.357 1.162 19.89 3.27 24.89 2.327 5.406 8.146 8.104 13.12 8.104 11.197 0 15.986-10.025 15.986-33.372 0-13.324-1.737-22.025-5.193-26.476-2.48-3.255-6.51-5.194-11.164-5.194-6.19 0-11.216 3.844-13.306 10.032zm46.49-14.265c7.893 9.257 11.418 20.057 11.418 36.07 0 16.982-3.895 28.582-12.412 38.212-7.486 8.483-17.352 13.71-32.563 13.71-26.863 0-44.384-20.076-44.384-51.13 0-31.08 17.706-51.738 44.384-51.738 14.08 0 25.077 4.84 33.558 14.875zm94.588-12.935V61.77l-43.64 63.096h45.344l-6.19 17.952h-72.713v-16.012l46.46-64.645H243.39V44.016h73.322zm40.694-2.328v101.13h-25.853V45.75l25.853-4.063zm3.035-24.874c0 8.89-7.066 15.987-16.002 15.987-8.652 0-15.767-7.098-15.767-15.987 0-8.87 7.353-16.03 16.204-16.03 8.67 0 15.567 7.16 15.567 16.03zm44.048 8.89v76.98c0 17.006.203 19.292 1.755 21.99.977 1.753 3.068 2.698 5.227 2.698.927 0 1.483 0 2.883-.354l4.42 15.42c-4.42 1.73-9.832 2.693-15.43 2.693-11.03 0-19.9-5.197-22.97-13.477-1.94-5.024-2.36-8.127-2.36-22.208V35.7c0-12.918-.337-20.81-1.3-29.722L403.157 0c.926 5.397 1.33 11.77 1.33 25.702zm53.625 0v76.98c0 17.006.22 19.292 1.806 21.99.91 1.753 3 2.698 5.16 2.698.977 0 1.567 0 2.933-.354l4.402 15.42c-4.402 1.73-9.816 2.693-15.432 2.693-11.01 0-19.897-5.197-22.983-13.477-1.973-5.024-2.294-8.127-2.294-22.208V35.7c0-12.918-.387-20.81-1.383-29.722L456.73 0c1.064 5.397 1.383 11.77 1.383 25.702zm74.082 73.894c-17.875 0-24.148 3.254-24.148 15.076 0 7.688 4.89 12.9 11.45 12.9 4.806 0 9.664-2.513 13.492-6.746l.42-21.23h-1.215zM498.687 47.69c9.613-4.064 17.878-5.785 26.983-5.785 16.628 0 27.993 6.157 31.888 17.167 1.282 4.05 1.872 7.135 1.755 17.757L558.687 110v1.752c0 10.607 1.755 14.672 9.313 20.255l-13.73 15.85c-6.038-2.53-11.416-6.98-13.93-11.982-1.905 1.948-4.047 3.837-6.003 5.202-4.79 3.476-11.787 5.414-19.883 5.414-22.005 0-33.96-11.215-33.96-30.86 0-23.196 16.053-34.015 47.485-34.015 1.888 0 3.676 0 5.818.22v-4.03c0-11.02-2.142-14.69-11.653-14.69-8.196 0-17.926 4.03-28.517 11.19l-11.012-18.533c5.245-3.29 9.108-5.203 16.07-8.087z"/></defs><use id="logo" xlink:href="#mozilla-logo"/><use id="logo-white" xlink:href="#mozilla-logo"/></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -44,7 +44,7 @@ loop.store.StandaloneMetricsStore = (function() {
"connectedToSdkServers",
"connectionFailure",
"gotMediaPermission",
"joinRoom",
"metricsLogJoinRoom",
"joinedRoom",
"leaveRoom",
"mediaConnected",
@ -144,10 +144,20 @@ loop.store.StandaloneMetricsStore = (function() {
/**
* Handles the user clicking the join room button.
*
* @param {sharedActions.MetricsLogJoinRoom} actionData
*/
joinRoom: function() {
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Join the conversation");
metricsLogJoinRoom: function(actionData) {
var label;
if (actionData.userAgentHandledRoom) {
label = actionData.ownRoom ? "Joined own room in Firefox" :
"Joined in Firefox";
} else {
label = "Join the conversation";
}
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button, label);
},
/**

View File

@ -14,6 +14,103 @@ loop.standaloneRoomViews = (function(mozL10n) {
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views;
var ToSView = React.createClass({displayName: "ToSView",
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
_getContent: function() {
// We use this technique of static markup as it means we get
// just one overall string for L10n to define the structure of
// the whole item.
return mozL10n.get("legal_text_and_links", {
"clientShortname": mozL10n.get("clientShortname2"),
"terms_of_use_url": React.renderToStaticMarkup(
React.createElement("a", {href: loop.config.legalWebsiteUrl, rel: "noreferrer", target: "_blank"},
mozL10n.get("terms_of_use_link_text")
)
),
"privacy_notice_url": React.renderToStaticMarkup(
React.createElement("a", {href: loop.config.privacyWebsiteUrl, rel: "noreferrer", target: "_blank"},
mozL10n.get("privacy_notice_link_text")
)
)
});
},
recordClick: function(event) {
// Check for valid href, as this is clicking on the paragraph -
// so the user may be clicking on the text rather than the link.
if (event.target && event.target.href) {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: event.target.href
}));
}
},
render: function() {
return (
React.createElement("p", {
className: "terms-service",
dangerouslySetInnerHTML: {__html: this._getContent()},
onClick: this.recordClick})
);
}
});
var StandaloneHandleUserAgentView = React.createClass({displayName: "StandaloneHandleUserAgentView",
mixins: [
loop.store.StoreMixin("activeRoomStore")
],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
getInitialState: function() {
return this.getStoreState();
},
handleJoinButton: function() {
this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
},
render: function() {
var buttonMessage = this.state.roomState === ROOM_STATES.JOINED ?
mozL10n.get("rooms_room_joined_own_conversation_label") :
mozL10n.get("rooms_room_join_label");
var buttonClasses = React.addons.classSet({
btn: true,
"btn-info": true,
disabled: this.state.roomState === ROOM_STATES.JOINED
});
// The extra scroller div here is for providing a scroll view for shorter
// screens, as the common.css specifies overflow:hidden for the body which
// we need in some places.
return (
React.createElement("div", {className: "handle-user-agent-view-scroller"},
React.createElement("div", {className: "handle-user-agent-view"},
React.createElement("div", {className: "info-panel"},
React.createElement("p", {className: "loop-logo-text", title: mozL10n.get("clientShortname2") }),
React.createElement("p", {className: "roomName"}, this.state.roomName),
React.createElement("p", {className: "loop-logo"}),
React.createElement("button", {
className: buttonClasses,
onClick: this.handleJoinButton},
buttonMessage
)
),
React.createElement(ToSView, {
dispatcher: this.props.dispatcher}),
React.createElement("p", {className: "mozilla-logo"})
)
)
);
}
});
/**
* Handles display of failures, determining the correct messages and
* displaying the retry button at appropriate times.
@ -306,41 +403,12 @@ loop.standaloneRoomViews = (function(mozL10n) {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
_getContent: function() {
// We use this technique of static markup as it means we get
// just one overall string for L10n to define the structure of
// the whole item.
return mozL10n.get("legal_text_and_links", {
"clientShortname": mozL10n.get("clientShortname2"),
"terms_of_use_url": React.renderToStaticMarkup(
React.createElement("a", {href: loop.config.legalWebsiteUrl, rel: "noreferrer", target: "_blank"},
mozL10n.get("terms_of_use_link_text")
)
),
"privacy_notice_url": React.renderToStaticMarkup(
React.createElement("a", {href: loop.config.privacyWebsiteUrl, rel: "noreferrer", target: "_blank"},
mozL10n.get("privacy_notice_link_text")
)
)
});
},
recordClick: function(event) {
// Check for valid href, as this is clicking on the paragraph -
// so the user may be clicking on the text rather than the link.
if (event.target && event.target.href) {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: event.target.href
}));
}
},
render: function() {
return (
React.createElement("footer", {className: "rooms-footer"},
React.createElement("div", {className: "footer-logo"}),
React.createElement("p", {dangerouslySetInnerHTML: {__html: this._getContent()},
onClick: this.recordClick})
React.createElement(ToSView, {
dispatcher: this.props.dispatcher})
)
);
}
@ -596,11 +664,50 @@ loop.standaloneRoomViews = (function(mozL10n) {
}
});
var StandaloneRoomControllerView = React.createClass({displayName: "StandaloneRoomControllerView",
mixins: [
loop.store.StoreMixin("activeRoomStore")
],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
isFirefox: React.PropTypes.bool.isRequired
},
getInitialState: function() {
return this.getStoreState();
},
render: function() {
// If we don't know yet, don't display anything.
if (this.state.userAgentHandlesRoom === undefined) {
return null;
}
if (this.state.userAgentHandlesRoom) {
return (
React.createElement(StandaloneHandleUserAgentView, {
dispatcher: this.props.dispatcher})
);
}
return (
React.createElement(StandaloneRoomView, {
activeRoomStore: this.getStore(),
dispatcher: this.props.dispatcher,
isFirefox: this.props.isFirefox})
);
}
});
return {
StandaloneHandleUserAgentView: StandaloneHandleUserAgentView,
StandaloneRoomControllerView: StandaloneRoomControllerView,
StandaloneRoomFailureView: StandaloneRoomFailureView,
StandaloneRoomFooter: StandaloneRoomFooter,
StandaloneRoomHeader: StandaloneRoomHeader,
StandaloneRoomInfoArea: StandaloneRoomInfoArea,
StandaloneRoomView: StandaloneRoomView
StandaloneRoomView: StandaloneRoomView,
ToSView: ToSView
};
})(navigator.mozL10n);

View File

@ -14,6 +14,103 @@ loop.standaloneRoomViews = (function(mozL10n) {
var sharedUtils = loop.shared.utils;
var sharedViews = loop.shared.views;
var ToSView = React.createClass({
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
_getContent: function() {
// We use this technique of static markup as it means we get
// just one overall string for L10n to define the structure of
// the whole item.
return mozL10n.get("legal_text_and_links", {
"clientShortname": mozL10n.get("clientShortname2"),
"terms_of_use_url": React.renderToStaticMarkup(
<a href={loop.config.legalWebsiteUrl} rel="noreferrer" target="_blank">
{mozL10n.get("terms_of_use_link_text")}
</a>
),
"privacy_notice_url": React.renderToStaticMarkup(
<a href={loop.config.privacyWebsiteUrl} rel="noreferrer" target="_blank">
{mozL10n.get("privacy_notice_link_text")}
</a>
)
});
},
recordClick: function(event) {
// Check for valid href, as this is clicking on the paragraph -
// so the user may be clicking on the text rather than the link.
if (event.target && event.target.href) {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: event.target.href
}));
}
},
render: function() {
return (
<p
className="terms-service"
dangerouslySetInnerHTML={{__html: this._getContent()}}
onClick={this.recordClick}></p>
);
}
});
var StandaloneHandleUserAgentView = React.createClass({
mixins: [
loop.store.StoreMixin("activeRoomStore")
],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
getInitialState: function() {
return this.getStoreState();
},
handleJoinButton: function() {
this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
},
render: function() {
var buttonMessage = this.state.roomState === ROOM_STATES.JOINED ?
mozL10n.get("rooms_room_joined_own_conversation_label") :
mozL10n.get("rooms_room_join_label");
var buttonClasses = React.addons.classSet({
btn: true,
"btn-info": true,
disabled: this.state.roomState === ROOM_STATES.JOINED
});
// The extra scroller div here is for providing a scroll view for shorter
// screens, as the common.css specifies overflow:hidden for the body which
// we need in some places.
return (
<div className="handle-user-agent-view-scroller">
<div className="handle-user-agent-view">
<div className="info-panel">
<p className="loop-logo-text" title={ mozL10n.get("clientShortname2") }></p>
<p className="roomName">{ this.state.roomName }</p>
<p className="loop-logo" />
<button
className={buttonClasses}
onClick={this.handleJoinButton}>
{buttonMessage}
</button>
</div>
<ToSView
dispatcher={this.props.dispatcher} />
<p className="mozilla-logo" />
</div>
</div>
);
}
});
/**
* Handles display of failures, determining the correct messages and
* displaying the retry button at appropriate times.
@ -306,41 +403,12 @@ loop.standaloneRoomViews = (function(mozL10n) {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
_getContent: function() {
// We use this technique of static markup as it means we get
// just one overall string for L10n to define the structure of
// the whole item.
return mozL10n.get("legal_text_and_links", {
"clientShortname": mozL10n.get("clientShortname2"),
"terms_of_use_url": React.renderToStaticMarkup(
<a href={loop.config.legalWebsiteUrl} rel="noreferrer" target="_blank">
{mozL10n.get("terms_of_use_link_text")}
</a>
),
"privacy_notice_url": React.renderToStaticMarkup(
<a href={loop.config.privacyWebsiteUrl} rel="noreferrer" target="_blank">
{mozL10n.get("privacy_notice_link_text")}
</a>
)
});
},
recordClick: function(event) {
// Check for valid href, as this is clicking on the paragraph -
// so the user may be clicking on the text rather than the link.
if (event.target && event.target.href) {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: event.target.href
}));
}
},
render: function() {
return (
<footer className="rooms-footer">
<div className="footer-logo" />
<p dangerouslySetInnerHTML={{__html: this._getContent()}}
onClick={this.recordClick}></p>
<ToSView
dispatcher={this.props.dispatcher} />
</footer>
);
}
@ -596,11 +664,50 @@ loop.standaloneRoomViews = (function(mozL10n) {
}
});
var StandaloneRoomControllerView = React.createClass({
mixins: [
loop.store.StoreMixin("activeRoomStore")
],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
isFirefox: React.PropTypes.bool.isRequired
},
getInitialState: function() {
return this.getStoreState();
},
render: function() {
// If we don't know yet, don't display anything.
if (this.state.userAgentHandlesRoom === undefined) {
return null;
}
if (this.state.userAgentHandlesRoom) {
return (
<StandaloneHandleUserAgentView
dispatcher={this.props.dispatcher} />
);
}
return (
<StandaloneRoomView
activeRoomStore={this.getStore()}
dispatcher={this.props.dispatcher}
isFirefox={this.props.isFirefox} />
);
}
});
return {
StandaloneHandleUserAgentView: StandaloneHandleUserAgentView,
StandaloneRoomControllerView: StandaloneRoomControllerView,
StandaloneRoomFailureView: StandaloneRoomFailureView,
StandaloneRoomFooter: StandaloneRoomFooter,
StandaloneRoomHeader: StandaloneRoomHeader,
StandaloneRoomInfoArea: StandaloneRoomInfoArea,
StandaloneRoomView: StandaloneRoomView
StandaloneRoomView: StandaloneRoomView,
ToSView: ToSView
};
})(navigator.mozL10n);

View File

@ -153,7 +153,7 @@ loop.webapp = (function(_, OT, mozL10n) {
}
case "room": {
return (
React.createElement(loop.standaloneRoomViews.StandaloneRoomView, {
React.createElement(loop.standaloneRoomViews.StandaloneRoomControllerView, {
activeRoomStore: this.props.activeRoomStore,
dispatcher: this.props.dispatcher,
isFirefox: this.state.isFirefox})

View File

@ -153,7 +153,7 @@ loop.webapp = (function(_, OT, mozL10n) {
}
case "room": {
return (
<loop.standaloneRoomViews.StandaloneRoomView
<loop.standaloneRoomViews.StandaloneRoomControllerView
activeRoomStore={this.props.activeRoomStore}
dispatcher={this.props.dispatcher}
isFirefox={this.state.isFirefox} />

View File

@ -68,6 +68,7 @@ rooms_room_full_call_to_action_nonFx_label=Download {{brandShortname}} to start
rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} »
rooms_room_joined_label=Someone has joined the conversation!
rooms_room_join_label=Join the conversation
rooms_room_joined_own_conversation_label=Enjoy your conversation
rooms_display_name_guest=Guest
rooms_unavailable_notification_message=Sorry, you cannot join this conversation. The link may be expired or invalid.
rooms_media_denied_message=We could not get access to your microphone or camera. Please reload the page to try again.

View File

@ -6,6 +6,7 @@ describe("loop.store.ActiveRoomStore", function () {
var expect = chai.expect;
var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var REST_ERRNOS = loop.shared.utils.REST_ERRNOS;
var ROOM_STATES = loop.store.ROOM_STATES;
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
@ -434,20 +435,20 @@ describe("loop.store.ActiveRoomStore", function () {
sinon.assert.calledOnce(fakeMozLoop.rooms.get);
});
it("should dispatch an UpdateRoomInfo message with 'no data' failure if neither roomName nor context are supplied", function() {
it("should dispatch an UpdateRoomInfo message with failure if neither roomName nor context are supplied", function() {
fakeMozLoop.rooms.get.callsArgWith(1, null, {
roomUrl: "http://invalid"
});
store.fetchServerData(fetchServerAction);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo({
roomInfoFailure: ROOM_INFO_FAILURES.NO_DATA,
roomState: ROOM_STATES.READY,
roomUrl: "http://invalid"
}));
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo({
roomInfoFailure: ROOM_INFO_FAILURES.NO_DATA,
roomState: ROOM_STATES.READY,
roomUrl: "http://invalid"
}));
});
});
describe("mozLoop.rooms.get returns roomName as a separate field (no context)", function() {
@ -459,13 +460,13 @@ describe("loop.store.ActiveRoomStore", function () {
fakeMozLoop.rooms.get.callsArgWith(1, null, roomDetails);
store.fetchServerData(fetchServerAction);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomState: ROOM_STATES.READY
}, roomDetails)));
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomState: ROOM_STATES.READY
}, roomDetails)));
});
});
});
@ -491,25 +492,25 @@ describe("loop.store.ActiveRoomStore", function () {
it("should dispatch UpdateRoomInfo message with 'unsupported' failure if WebCrypto is unsupported", function() {
loop.crypto.isSupported.returns(false);
store.fetchServerData(fetchServerAction);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomInfoFailure: ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED,
roomState: ROOM_STATES.READY
}, expectedDetails)));
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomInfoFailure: ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED,
roomState: ROOM_STATES.READY
}, expectedDetails)));
});
});
it("should dispatch UpdateRoomInfo message with 'no crypto key' failure if there is no crypto key", function() {
store.fetchServerData(fetchServerAction);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomInfoFailure: ROOM_INFO_FAILURES.NO_CRYPTO_KEY,
roomState: ROOM_STATES.READY
}, expectedDetails)));
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomInfoFailure: ROOM_INFO_FAILURES.NO_CRYPTO_KEY,
roomState: ROOM_STATES.READY
}, expectedDetails)));
});
});
it("should dispatch UpdateRoomInfo message with 'decrypt failed' failure if decryption failed", function() {
@ -525,14 +526,14 @@ describe("loop.store.ActiveRoomStore", function () {
};
});
store.fetchServerData(fetchServerAction);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomInfoFailure: ROOM_INFO_FAILURES.DECRYPT_FAILED,
roomState: ROOM_STATES.READY
}, expectedDetails)));
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomInfoFailure: ROOM_INFO_FAILURES.DECRYPT_FAILED,
roomState: ROOM_STATES.READY
}, expectedDetails)));
});
});
it("should dispatch UpdateRoomInfo message with the context if decryption was successful", function() {
@ -558,18 +559,175 @@ describe("loop.store.ActiveRoomStore", function () {
};
});
store.fetchServerData(fetchServerAction);
return store.fetchServerData(fetchServerAction).then(function() {
var expectedData = _.extend({
roomContextUrls: roomContext.urls,
roomDescription: roomContext.description,
roomName: roomContext.roomName,
roomState: ROOM_STATES.READY
}, expectedDetails);
var expectedData = _.extend({
roomContextUrls: roomContext.urls,
roomDescription: roomContext.description,
roomName: roomContext.roomName,
roomState: ROOM_STATES.READY
}, expectedDetails);
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(expectedData));
});
});
});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(expectedData));
describe("User Agent Room Handling", function() {
var channelListener, roomDetails;
beforeEach(function() {
sandbox.stub(sharedUtils, "isFirefox").returns(true);
roomDetails = {
roomName: "fakeName",
roomUrl: "http://invalid"
};
fakeMozLoop.rooms.get.callsArgWith(1, null, roomDetails);
sandbox.stub(window, "addEventListener", function(eventName, listener) {
if (eventName === "WebChannelMessageToContent") {
channelListener = listener;
}
});
sandbox.stub(window, "removeEventListener", function(eventName, listener) {
if (eventName === "WebChannelMessageToContent" &&
listener === channelListener) {
channelListener = null;
}
});
});
it("should dispatch UserAgentHandlesRoom with false if the user agent is not Firefox", function() {
sharedUtils.isFirefox.returns(false);
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UserAgentHandlesRoom({
handlesRoom: false
}));
});
});
it("should dispatch with false after a timeout if there is no response from the channel", function() {
// When the dispatchEvent is called, we know the setup code has run, so
// advance the timer.
sandbox.stub(window, "dispatchEvent", function() {
sandbox.clock.tick(250);
});
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UserAgentHandlesRoom({
handlesRoom: false
}));
});
});
it("should not dispatch if a message is returned not for the link-clicker", function() {
// When the dispatchEvent is called, we know the setup code has run, so
// advance the timer.
sandbox.stub(window, "dispatchEvent", function() {
// We call the listener twice, but the first time with an invalid id.
// Hence we should only get the dispatch once.
channelListener({
detail: {
id: "invalid-id",
message: null
}
});
channelListener({
detail: {
id: "loop-link-clicker",
message: null
}
});
});
return store.fetchServerData(fetchServerAction).then(function() {
// Although this is only called once for the UserAgentHandlesRoom,
// it gets called twice due to the UpdateRoomInfo. Therefore,
// we test both results here.
sinon.assert.calledTwice(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UserAgentHandlesRoom({
handlesRoom: false
}));
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomState: ROOM_STATES.READY
}, roomDetails)));
});
});
it("should dispatch with false if the user agent does not understand the message", function() {
// When the dispatchEvent is called, we know the setup code has run, so
// advance the timer.
sandbox.stub(window, "dispatchEvent", function() {
channelListener({
detail: {
id: "loop-link-clicker",
message: null
}
});
});
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UserAgentHandlesRoom({
handlesRoom: false
}));
});
});
it("should dispatch with false if the user agent cannot handle the message", function() {
// When the dispatchEvent is called, we know the setup code has run, so
// advance the timer.
sandbox.stub(window, "dispatchEvent", function() {
channelListener({
detail: {
id: "loop-link-clicker",
message: {
response: false
}
}
});
});
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UserAgentHandlesRoom({
handlesRoom: false
}));
});
});
it("should dispatch with true if the user agent can handle the message", function() {
// When the dispatchEvent is called, we know the setup code has run, so
// advance the timer.
sandbox.stub(window, "dispatchEvent", function() {
channelListener({
detail: {
id: "loop-link-clicker",
message: {
response: true
}
}
});
});
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UserAgentHandlesRoom({
handlesRoom: true
}));
});
});
});
});
@ -624,6 +782,20 @@ describe("loop.store.ActiveRoomStore", function () {
});
});
describe("#userAgentHandlesRoom", function() {
it("should update the store state", function() {
store.setStoreState({
UserAgentHandlesRoom: false
});
store.userAgentHandlesRoom(new sharedActions.UserAgentHandlesRoom({
handlesRoom: true
}));
expect(store.getStoreState().userAgentHandlesRoom).eql(true);
});
});
describe("#updateSocialShareInfo", function() {
var fakeSocialShareInfo;
@ -659,32 +831,138 @@ describe("loop.store.ActiveRoomStore", function () {
expect(store.getStoreState().failureReason).eql(undefined);
});
it("should set the state to MEDIA_WAIT if media devices are present", function() {
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, true);
describe("Standalone Handles Room", function() {
it("should dispatch a MetricsLogJoinRoom action", function() {
store.joinRoom();
store.joinRoom();
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.MetricsLogJoinRoom({
userAgentHandledRoom: false
}));
});
expect(store.getStoreState().roomState).eql(ROOM_STATES.MEDIA_WAIT);
it("should set the state to MEDIA_WAIT if media devices are present", function() {
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, true);
store.joinRoom();
expect(store.getStoreState().roomState).eql(ROOM_STATES.MEDIA_WAIT);
});
it("should not set the state to MEDIA_WAIT if no media devices are present", function() {
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, false);
store.joinRoom();
expect(store.getStoreState().roomState).eql(ROOM_STATES.READY);
});
it("should dispatch `ConnectionFailure` if no media devices are present", function() {
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, false);
store.joinRoom();
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionFailure({
reason: FAILURE_DETAILS.NO_MEDIA
}));
});
});
it("should not set the state to MEDIA_WAIT if no media devices are present", function() {
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, false);
describe("Firefox Handles Room", function() {
var channelListener;
store.joinRoom();
beforeEach(function() {
store.setStoreState({
userAgentHandlesRoom: true,
roomToken: "fakeToken",
standalone: true
});
expect(store.getStoreState().roomState).eql(ROOM_STATES.READY);
});
sandbox.stub(window, "addEventListener", function(eventName, listener) {
if (eventName === "WebChannelMessageToContent") {
channelListener = listener;
}
});
sandbox.stub(window, "removeEventListener", function(eventName, listener) {
if (eventName === "WebChannelMessageToContent" &&
listener === channelListener) {
channelListener = null;
}
});
it("should dispatch `ConnectionFailure` if no media devices are present", function() {
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, false);
sandbox.stub(console, "error");
});
store.joinRoom();
it("should dispatch a MetricsLogJoinRoom action", function() {
store.joinRoom();
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionFailure({
reason: FAILURE_DETAILS.NO_MEDIA
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.MetricsLogJoinRoom({
userAgentHandledRoom: true,
ownRoom: true
}));
});
it("should dispatch an event to Firefox", function() {
sandbox.stub(window, "dispatchEvent");
store.joinRoom();
sinon.assert.calledOnce(window.dispatchEvent);
sinon.assert.calledWithExactly(window.dispatchEvent, new window.CustomEvent(
"WebChannelMessageToChrome", {
detail: {
id: "loop-link-clicker",
message: {
command: "openRoom",
roomToken: "fakeToken"
}
}
}));
});
it("should log an error if Firefox doesn't handle the room", function() {
// Start the join.
store.joinRoom();
// Pretend Firefox calls back.
channelListener({
detail: {
id: "loop-link-clicker",
message: null
}
});
sinon.assert.calledOnce(console.error);
});
it("should dispatch a JoinedRoom action if the room was successfully opened", function() {
// Start the join.
store.joinRoom();
// Pretend Firefox calls back.
channelListener({
detail: {
id: "loop-link-clicker",
message: {
response: true
}
}
});
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.JoinedRoom({
apiKey: "",
sessionToken: "",
sessionId: "",
expires: 0
}));
});
});
});
@ -762,6 +1040,17 @@ describe("loop.store.ActiveRoomStore", function () {
expect(store._storeState.roomState).eql(ROOM_STATES.JOINED);
});
it("should set the state to `JOINED` when Firefox handles the room", function() {
store.setStoreState({
userAgentHandlesRoom: true,
standalone: true
});
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
expect(store._storeState.roomState).eql(ROOM_STATES.JOINED);
});
it("should store the session and api values", function() {
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
@ -771,6 +1060,20 @@ describe("loop.store.ActiveRoomStore", function () {
expect(state.sessionId).eql(fakeJoinedData.sessionId);
});
it("should not store the session and api values when Firefox handles the room", function() {
store.setStoreState({
userAgentHandlesRoom: true,
standalone: true
});
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
var state = store.getStoreState();
expect(state.apiKey).eql(undefined);
expect(state.sessionToken).eql(undefined);
expect(state.sessionId).eql(undefined);
});
it("should start the session connection with the sdk", function() {
var actionData = new sharedActions.JoinedRoom(fakeJoinedData);

View File

@ -86,15 +86,6 @@ describe("loop.store.StandaloneMetricsStore", function() {
"Media granted");
});
it("should log an event on JoinRoom", function() {
store.joinRoom();
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Join the conversation");
});
it("should log an event on JoinedRoom", function() {
store.joinedRoom();
@ -150,6 +141,43 @@ describe("loop.store.StandaloneMetricsStore", function() {
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Retry failed room");
});
describe("MetricsLogJoinRoom", function() {
it("should log a 'Join the conversation' event if not joined by Firefox", function() {
store.metricsLogJoinRoom({
userAgentHandledRoom: false
});
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Join the conversation");
});
it("should log a 'Joined own room in Firefox' event if joining the own room in Firefox", function() {
store.metricsLogJoinRoom({
userAgentHandledRoom: true,
ownRoom: true
});
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Joined own room in Firefox");
});
it("should log a 'Joined in Firefox' event if joining a non-own room in Firefox", function() {
store.metricsLogJoinRoom({
userAgentHandledRoom: true,
ownRoom: false
});
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Joined in Firefox");
});
});
});
describe("Store Change Handlers", function() {

View File

@ -49,6 +49,8 @@ describe("loop.standaloneRoomViews", function() {
switch(key) {
case "standalone_title_with_room_name":
return args.roomName + " — " + args.clientShortname;
case "legal_text_and_links":
return args.terms_of_use_url + " " + args.privacy_notice_url;
default:
return key;
}
@ -66,6 +68,123 @@ describe("loop.standaloneRoomViews", function() {
view = null;
});
describe("TosView", function() {
var origConfig, node;
function mountTestComponent() {
return TestUtils.renderIntoDocument(
React.createElement(
loop.standaloneRoomViews.ToSView, {
dispatcher: dispatcher
}));
}
beforeEach(function() {
origConfig = loop.config;
loop.config = {
legalWebsiteUrl: "http://fakelegal/",
privacyWebsiteUrl: "http://fakeprivacy/"
};
view = mountTestComponent();
node = view.getDOMNode();
});
afterEach(function() {
loop.config = origConfig;
});
it("should dispatch a link click action when the ToS link is clicked", function() {
// [0] is the first link, the legal one.
var link = node.querySelectorAll("a")[0];
TestUtils.Simulate.click(node, { target: link });
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.RecordClick({
linkInfo: loop.config.legalWebsiteUrl
}));
});
it("should dispatch a link click action when the Privacy link is clicked", function() {
// [0] is the first link, the legal one.
var link = node.querySelectorAll("a")[1];
TestUtils.Simulate.click(node, { target: link });
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.RecordClick({
linkInfo: loop.config.privacyWebsiteUrl
}));
});
it("should not dispatch an action when the text is clicked", function() {
TestUtils.Simulate.click(node, { target: node });
sinon.assert.notCalled(dispatcher.dispatch);
});
});
describe("StandaloneHandleUserAgentView", function() {
function mountTestComponent() {
return TestUtils.renderIntoDocument(
React.createElement(
loop.standaloneRoomViews.StandaloneHandleUserAgentView, {
dispatcher: dispatcher
}));
}
it("should display a join room button if the state is not ROOM_JOINED", function() {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.READY
});
view = mountTestComponent();
var button = view.getDOMNode().querySelector(".info-panel > button");
expect(button.textContent).eql("rooms_room_join_label");
});
it("should dispatch a JoinRoom action when the join room button is clicked", function() {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.READY
});
view = mountTestComponent();
var button = view.getDOMNode().querySelector(".info-panel > button");
TestUtils.Simulate.click(button);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch, new sharedActions.JoinRoom());
});
it("should display a enjoy your conversation button if the state is ROOM_JOINED", function() {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.JOINED
});
view = mountTestComponent();
var button = view.getDOMNode().querySelector(".info-panel > button");
expect(button.textContent).eql("rooms_room_joined_own_conversation_label");
});
it("should disable the enjoy your conversation button if the state is ROOM_JOINED", function() {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.JOINED
});
view = mountTestComponent();
var button = view.getDOMNode().querySelector(".info-panel > button");
expect(button.classList.contains("disabled")).eql(true);
});
});
describe("StandaloneRoomHeader", function() {
function mountTestComponent() {
return TestUtils.renderIntoDocument(
@ -804,4 +923,47 @@ describe("loop.standaloneRoomViews", function() {
});
});
});
describe("StandaloneRoomControllerView", function() {
function mountTestComponent() {
return TestUtils.renderIntoDocument(
React.createElement(
loop.standaloneRoomViews.StandaloneRoomControllerView, {
dispatcher: dispatcher,
isFirefox: true
}));
}
it("should not display anything if it is not known if Firefox can handle the room", function() {
activeRoomStore.setStoreState({
userAgentHandlesRoom: undefined
});
view = mountTestComponent();
expect(view.getDOMNode()).eql(null);
});
it("should render StandaloneHandleUserAgentView if Firefox can handle the room", function() {
activeRoomStore.setStoreState({
userAgentHandlesRoom: true
});
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.standaloneRoomViews.StandaloneHandleUserAgentView);
});
it("should render StandaloneRoomView if Firefox cannot handle the room", function() {
activeRoomStore.setStoreState({
userAgentHandlesRoom: false
});
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.standaloneRoomViews.StandaloneRoomView);
});
});
});

View File

@ -119,14 +119,14 @@ describe("loop.webapp", function() {
loop.webapp.UnsupportedBrowserView);
});
it("should display the StandaloneRoomView for `room` window type",
it("should display the StandaloneRoomControllerView for `room` window type",
function() {
standaloneAppStore.setStoreState({windowType: "room", isFirefox: true});
var webappRootView = mountTestComponent();
TestUtils.findRenderedComponentWithType(webappRootView,
loop.standaloneRoomViews.StandaloneRoomView);
loop.standaloneRoomViews.StandaloneRoomControllerView);
});
it("should display the HomeView for `home` window type", function() {

View File

@ -36,6 +36,7 @@
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
var StandaloneRoomView = loop.standaloneRoomViews.StandaloneRoomView;
var StandaloneHandleUserAgentView = loop.standaloneRoomViews.StandaloneHandleUserAgentView;
// 3. Shared components
var ConversationToolbar = loop.shared.views.ConversationToolbar;
@ -1515,6 +1516,21 @@
)
),
React.createElement(Section, {name: "StandaloneHandleUserAgentView"},
React.createElement(FramedExample, {
cssClass: "standalone",
dashed: true,
height: 483,
summary: "Standalone Room Handle Join in Firefox",
width: 644},
React.createElement("div", {className: "standalone"},
React.createElement(StandaloneHandleUserAgentView, {
activeRoomStore: readyRoomStore,
dispatcher: dispatcher})
)
)
),
React.createElement(Section, {name: "StandaloneRoomView"},
React.createElement(FramedExample, {cssClass: "standalone",
dashed: true,

View File

@ -36,6 +36,7 @@
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
var StandaloneRoomView = loop.standaloneRoomViews.StandaloneRoomView;
var StandaloneHandleUserAgentView = loop.standaloneRoomViews.StandaloneHandleUserAgentView;
// 3. Shared components
var ConversationToolbar = loop.shared.views.ConversationToolbar;
@ -1515,6 +1516,21 @@
</FramedExample>
</Section>
<Section name="StandaloneHandleUserAgentView">
<FramedExample
cssClass="standalone"
dashed={true}
height={483}
summary="Standalone Room Handle Join in Firefox"
width={644} >
<div className="standalone">
<StandaloneHandleUserAgentView
activeRoomStore={readyRoomStore}
dispatcher={dispatcher} />
</div>
</FramedExample>
</Section>
<Section name="StandaloneRoomView">
<FramedExample cssClass="standalone"
dashed={true}

View File

@ -352,7 +352,7 @@ nsGNOMEShellService::GetShouldSkipCheckDefaultBrowser(bool* aResult)
if (NS_FAILED(rv)) {
return rv;
}
if (defaultBrowserCheckCount < 3) {
if (defaultBrowserCheckCount < 4) {
*aResult = false;
return prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT,
defaultBrowserCheckCount + 1);

View File

@ -130,7 +130,7 @@ nsMacShellService::GetShouldSkipCheckDefaultBrowser(bool* aResult)
if (NS_FAILED(rv)) {
return rv;
}
if (defaultBrowserCheckCount < 3) {
if (defaultBrowserCheckCount < 4) {
*aResult = false;
return prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT,
defaultBrowserCheckCount + 1);

View File

@ -1011,7 +1011,7 @@ nsWindowsShellService::GetShouldSkipCheckDefaultBrowser(bool* aResult)
if (NS_FAILED(rv)) {
return rv;
}
if (defaultBrowserCheckCount < 3) {
if (defaultBrowserCheckCount < 4) {
*aResult = false;
return prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT,
defaultBrowserCheckCount + 1);

View File

@ -285,4 +285,22 @@
:root[devtoolstheme="dark"] #titlebar-close {
list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-white);
}
/* ... and normal ones for the light theme on Windows 10 */
:root[devtoolstheme="light"] #titlebar-min {
list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize);
}
:root[devtoolstheme="light"] #titlebar-max {
list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize);
}
#main-window[devtoolstheme="light"][sizemode="maximized"] #titlebar-max {
list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore);
}
:root[devtoolstheme="light"] #titlebar-close {
list-style-image: url(chrome://browser/skin/caption-buttons.svg#close);
}
:root[devtoolstheme="light"] #titlebar-close:hover {
list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-white);
}
}

View File

@ -529,7 +529,8 @@ class Automation(object):
xrePath = None, certPath = None,
debuggerInfo = None, symbolsPath = None,
timeout = -1, maxTime = None, onLaunch = None,
detectShutdownLeaks = False, screenshotOnFail=False, testPath=None, bisectChunk=None):
detectShutdownLeaks = False, screenshotOnFail=False, testPath=None, bisectChunk=None,
valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None):
"""
Run the app, log the duration it took to execute, return the status code.
Kills the app if it runs for longer than |maxTime| seconds, or outputs nothing for |timeout| seconds.

View File

@ -191,15 +191,15 @@ public class FennecNativeActions implements Actions {
}
public void sendPreferencesGetEvent(int requestId, String[] prefNames) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesGetEvent(requestId, prefNames));
PrefsHelper.getPrefsById(requestId, prefNames, /* observe */ false);
}
public void sendPreferencesObserveEvent(int requestId, String[] prefNames) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesObserveEvent(requestId, prefNames));
PrefsHelper.getPrefsById(requestId, prefNames, /* observe */ true);
}
public void sendPreferencesRemoveObserversEvent(int requestId) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesRemoveObserversEvent(requestId));
PrefsHelper.removePrefsObserver(requestId);
}
class PaintExpecter implements RepeatedEventExpecter {

View File

@ -7641,7 +7641,6 @@ nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
aStatus == NS_ERROR_UNWANTED_URI ||
aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE ||
aStatus == NS_ERROR_REMOTE_XUL ||
aStatus == NS_ERROR_OFFLINE ||
aStatus == NS_ERROR_INTERCEPTION_FAILED ||
aStatus == NS_ERROR_OPAQUE_INTERCEPTION_DISABLED ||
aStatus == NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE ||

View File

@ -332,6 +332,13 @@ nsSHEntryShared::AttributeWillChange(nsIDocument* aDocument,
{
}
void
nsSHEntryShared::NativeAnonymousChildListChange(nsIDocument* aDocument,
nsIContent* aContent,
bool aIsRemove)
{
}
void
nsSHEntryShared::AttributeChanged(nsIDocument* aDocument,
dom::Element* aElement,

View File

@ -1631,6 +1631,9 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
}
nsNodeUtils::ParentChainChanged(this);
if (!hadParent && IsRootOfNativeAnonymousSubtree()) {
nsNodeUtils::NativeAnonymousChildListChange(this, false);
}
if (HasID()) {
AddToIdTable(DoGetID());
@ -1745,6 +1748,10 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
}
}
if (this->IsRootOfNativeAnonymousSubtree()) {
nsNodeUtils::NativeAnonymousChildListChange(this, true);
}
if (GetParent()) {
nsRefPtr<nsINode> p;
p.swap(mParent);

View File

@ -114,6 +114,38 @@ nsMutationReceiver::Disconnect(bool aRemoveFromObserver)
}
}
void
nsMutationReceiver::NativeAnonymousChildListChange(nsIDocument* aDocument,
nsIContent* aContent,
bool aIsRemove) {
if (!NativeAnonymousChildList()) {
return;
}
nsINode* parent = aContent->GetParentNode();
if (!parent ||
(!Subtree() && Target() != parent) ||
(Subtree() && RegisterTarget()->SubtreeRoot() != parent->SubtreeRoot())) {
return;
}
nsDOMMutationRecord* m =
Observer()->CurrentRecord(nsGkAtoms::nativeAnonymousChildList);
if (m->mTarget) {
return;
}
m->mTarget = parent;
if (aIsRemove) {
m->mRemovedNodes = new nsSimpleContentList(parent);
m->mRemovedNodes->AppendElement(aContent);
} else {
m->mAddedNodes = new nsSimpleContentList(parent);
m->mAddedNodes->AppendElement(aContent);
}
}
void
nsMutationReceiver::AttributeWillChange(nsIDocument* aDocument,
mozilla::dom::Element* aElement,
@ -586,6 +618,8 @@ nsDOMMutationObserver::Observe(nsINode& aTarget,
bool attributeOldValue =
aOptions.mAttributeOldValue.WasPassed() &&
aOptions.mAttributeOldValue.Value();
bool nativeAnonymousChildList = aOptions.mNativeAnonymousChildList &&
nsContentUtils::ThreadsafeIsCallerChrome();
bool characterDataOldValue =
aOptions.mCharacterDataOldValue.WasPassed() &&
aOptions.mCharacterDataOldValue.Value();
@ -605,7 +639,8 @@ nsDOMMutationObserver::Observe(nsINode& aTarget,
characterData = true;
}
if (!(childList || attributes || characterData || animations)) {
if (!(childList || attributes || characterData || animations ||
nativeAnonymousChildList)) {
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
return;
}
@ -655,6 +690,7 @@ nsDOMMutationObserver::Observe(nsINode& aTarget,
r->SetSubtree(subtree);
r->SetAttributeOldValue(attributeOldValue);
r->SetCharacterDataOldValue(characterDataOldValue);
r->SetNativeAnonymousChildList(nativeAnonymousChildList);
r->SetAttributeFilter(filters);
r->SetAllAttributes(allAttrs);
r->SetAnimations(animations);
@ -715,6 +751,7 @@ nsDOMMutationObserver::GetObservingInfo(
info.mSubtree = mr->Subtree();
info.mAttributeOldValue.Construct(mr->AttributeOldValue());
info.mCharacterDataOldValue.Construct(mr->CharacterDataOldValue());
info.mNativeAnonymousChildList = mr->NativeAnonymousChildList();
info.mAnimations.Construct(mr->Animations());
nsCOMArray<nsIAtom>& filters = mr->AttributeFilter();
if (filters.Count()) {

View File

@ -172,6 +172,16 @@ public:
mCharacterDataOldValue = aOldValue;
}
bool NativeAnonymousChildList()
{
return mParent ? mParent->NativeAnonymousChildList() : mNativeAnonymousChildList;
}
void SetNativeAnonymousChildList(bool aOldValue)
{
NS_ASSERTION(!mParent, "Shouldn't have parent");
mNativeAnonymousChildList = aOldValue;
}
bool Attributes() { return mParent ? mParent->Attributes() : mAttributes; }
void SetAttributes(bool aAttributes)
{
@ -298,6 +308,7 @@ private:
bool mChildList;
bool mCharacterData;
bool mCharacterDataOldValue;
bool mNativeAnonymousChildList;
bool mAttributes;
bool mAllAttributes;
bool mAttributeOldValue;
@ -362,6 +373,7 @@ public:
NS_DECL_ISUPPORTS
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
NS_DECL_NSIMUTATIONOBSERVER_NATIVEANONYMOUSCHILDLISTCHANGE
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED

View File

@ -514,6 +514,8 @@ nsGenericDOMDataNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
}
}
bool hadParent = !!GetParentNode();
// Set parent
if (aParent) {
if (!GetParent()) {
@ -548,6 +550,9 @@ nsGenericDOMDataNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
}
nsNodeUtils::ParentChainChanged(this);
if (!hadParent && IsRootOfNativeAnonymousSubtree()) {
nsNodeUtils::NativeAnonymousChildListChange(this, false);
}
UpdateEditableState(false);
@ -570,6 +575,9 @@ nsGenericDOMDataNode::UnbindFromTree(bool aDeep, bool aNullParent)
HasFlag(NODE_FORCE_XBL_BINDINGS) ? OwnerDoc() : GetComposedDoc();
if (aNullParent) {
if (this->IsRootOfNativeAnonymousSubtree()) {
nsNodeUtils::NativeAnonymousChildListChange(this, true);
}
if (GetParent()) {
NS_RELEASE(mParent);
} else {

View File

@ -633,6 +633,7 @@ GK_ATOM(_namespace, "namespace")
GK_ATOM(namespaceAlias, "namespace-alias")
GK_ATOM(namespaceUri, "namespace-uri")
GK_ATOM(NaN, "NaN")
GK_ATOM(nativeAnonymousChildList, "nativeAnonymousChildList")
GK_ATOM(nav, "nav")
GK_ATOM(negate, "negate")
GK_ATOM(never, "never")

View File

@ -22,8 +22,8 @@ class Element;
} // namespace mozilla
#define NS_IMUTATION_OBSERVER_IID \
{ 0xdd74f0cc, 0x2849, 0x4d05, \
{ 0x9c, 0xe3, 0xb0, 0x95, 0x3e, 0xc2, 0xfd, 0x44 } }
{ 0x6d674c17, 0x0fbc, 0x4633, \
{ 0x8f, 0x46, 0x73, 0x4e, 0x87, 0xeb, 0xf0, 0xc7 } }
/**
* Information details about a characterdata change. Basically, we
@ -200,6 +200,18 @@ public:
int32_t aModType,
const nsAttrValue* aOldValue) = 0;
/**
* Notification that the root of a native anonymous has been added
* or removed.
*
* @param aDocument Owner doc of aContent
* @param aContent Anonymous node that's been added or removed
* @param aIsRemove True if it's a removal, false if an addition
*/
virtual void NativeAnonymousChildListChange(nsIDocument* aDocument,
nsIContent* aContent,
bool aIsRemove) {}
/**
* Notification that an attribute of an element has been
* set to the value it already had.
@ -346,6 +358,11 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIMutationObserver, NS_IMUTATION_OBSERVER_IID)
int32_t aModType, \
const nsAttrValue* aNewValue) override;
#define NS_DECL_NSIMUTATIONOBSERVER_NATIVEANONYMOUSCHILDLISTCHANGE \
virtual void NativeAnonymousChildListChange(nsIDocument* aDocument, \
nsIContent* aContent, \
bool aIsRemove) override;
#define NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED \
virtual void AttributeChanged(nsIDocument* aDocument, \
mozilla::dom::Element* aElement, \
@ -383,6 +400,7 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIMutationObserver, NS_IMUTATION_OBSERVER_IID)
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE \
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED \
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE \
NS_DECL_NSIMUTATIONOBSERVER_NATIVEANONYMOUSCHILDLISTCHANGE \
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED \
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED \
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED \
@ -419,6 +437,12 @@ _class::AttributeWillChange(nsIDocument* aDocument, \
{ \
} \
void \
_class::NativeAnonymousChildListChange(nsIDocument* aDocument, \
nsIContent* aContent, \
bool aIsRemove) \
{ \
} \
void \
_class::AttributeChanged(nsIDocument* aDocument, \
mozilla::dom::Element* aElement, \
int32_t aNameSpaceID, \

View File

@ -167,6 +167,15 @@ nsNodeUtils::ContentAppended(nsIContent* aContainer,
aNewIndexInContainer));
}
void
nsNodeUtils::NativeAnonymousChildListChange(nsIContent* aContent,
bool aIsRemove)
{
nsIDocument* doc = aContent->OwnerDoc();
IMPL_MUTATION_NOTIFICATION(NativeAnonymousChildListChange, aContent,
(doc, aContent, aIsRemove));
}
void
nsNodeUtils::ContentInserted(nsINode* aContainer,
nsIContent* aChild,

View File

@ -96,6 +96,15 @@ public:
nsIContent* aFirstNewContent,
int32_t aNewIndexInContainer);
/**
* Send NativeAnonymousChildList notifications to nsIMutationObservers
* @param aContent Anonymous node that's been added or removed
* @param aIsRemove True if it's a removal, false if an addition
* @see nsIMutationObserver::NativeAnonymousChildListChange
*/
static void NativeAnonymousChildListChange(nsIContent* aContent,
bool aIsRemove);
/**
* Send ContentInserted notifications to nsIMutationObservers
* @param aContainer Node into which new child was inserted

View File

@ -65,6 +65,7 @@ skip-if = buildapp == 'mulet'
[test_cpows.xul]
skip-if = buildapp == 'mulet'
[test_document_register.xul]
[test_mutationobserver_anonymous.html]
[test_registerElement_content.xul]
[test_registerElement_ep.xul]
[test_domparsing.xul]

View File

@ -0,0 +1,251 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1034110
-->
<head>
<title>Test for Bug 1034110</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1034110">Mozilla Bug 1034110</a>
<style type="text/css">
#pseudo.before::before { content: "before"; }
#pseudo.after::after { content: "after"; }
</style>
<div id="pseudo"></div>
<video id="video"></video>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript;version=1.7">
/** Test for Bug 1034110 **/
SimpleTest.waitForExplicitFinish();
const {Cc, Ci, Cu} = SpecialPowers;
function getWalker(node) {
let walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"].createInstance(Ci.inIDeepTreeWalker);
walker.showAnonymousContent = true;
walker.init(node.ownerDocument, Ci.nsIDOMNodeFilter.SHOW_ALL);
walker.currentNode = node;
return walker;
}
function getFirstChild(parent) {
return SpecialPowers.unwrap(getWalker(parent).firstChild());
}
function getLastChild(parent) {
return SpecialPowers.unwrap(getWalker(parent).lastChild());
}
function assertSamePseudoElement(which, node1, node2) {
is(node1.nodeName, "_moz_generated_content_" + which,
"Correct pseudo element type");
is(node1, node2,
"Referencing the same ::after element");
}
window.onload = function () {
testOneAdded();
};
function testOneAdded() {
let parent = document.getElementById("pseudo");
var m = new MutationObserver(function(records, observer) {
is(records.length, 1, "Correct number of records");
is(records[0].type, "nativeAnonymousChildList", "Correct record type");
is(records[0].target, parent, "Correct target");
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
observer.disconnect();
testAddedAndRemoved();
});
m.observe(parent, { nativeAnonymousChildList: true});
parent.className = "before";
}
function testAddedAndRemoved() {
let parent = document.getElementById("pseudo");
let originalBeforeElement = getFirstChild(parent);
var m = new MutationObserver(function(records, observer) {
is(records.length, 2, "Correct number of records");
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
is(records[0].target, parent, "Correct target (1)");
is(records[1].target, parent, "Correct target (2)");
// Two records are sent - one for removed and one for added.
is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
is(records[0].removedNodes.length, 1, "Should have got removedNodes");
assertSamePseudoElement("before", records[0].removedNodes[0], originalBeforeElement);
is(records[1].addedNodes.length, 1, "Should have got addedNodes");
assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
observer.disconnect();
testRemoved();
});
m.observe(parent, { nativeAnonymousChildList: true});
parent.className = "after";
}
function testRemoved() {
let parent = document.getElementById("pseudo");
let originalAfterElement = getLastChild(parent);
var m = new MutationObserver(function(records, observer) {
is(records.length, 1, "Correct number of records");
is(records[0].type, "nativeAnonymousChildList", "Correct record type");
is(records[0].target, parent, "Correct target");
is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
is(records[0].removedNodes.length, 1, "Should have got removedNodes");
assertSamePseudoElement("after", records[0].removedNodes[0], originalAfterElement);
observer.disconnect();
testMultipleAdded();
});
m.observe(parent, { nativeAnonymousChildList: true });
parent.className = "";
}
function testMultipleAdded() {
let parent = document.getElementById("pseudo");
var m = new MutationObserver(function(records, observer) {
is(records.length, 2, "Correct number of records");
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
is(records[0].target, parent, "Correct target (1)");
is(records[1].target, parent, "Correct target (2)");
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
is(records[1].addedNodes.length, 1, "Should have got addedNodes");
assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
observer.disconnect();
testRemovedDueToDisplay();
});
m.observe(parent, { nativeAnonymousChildList: true });
parent.className = "before after";
}
function testRemovedDueToDisplay() {
let parent = document.getElementById("pseudo");
let originalBeforeElement = getFirstChild(parent);
let originalAfterElement = getLastChild(parent);
var m = new MutationObserver(function(records, observer) {
is(records.length, 2, "Correct number of records");
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
is(records[0].target, parent, "Correct target (1)");
is(records[1].target, parent, "Correct target (2)");
is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
is(records[0].removedNodes.length, 1, "Should have got removedNodes");
assertSamePseudoElement("before", records[0].removedNodes[0], originalBeforeElement);
is(records[1].addedNodes.length, 0, "Shouldn't have got addedNodes");
is(records[1].removedNodes.length, 1, "Should have got removedNodes");
assertSamePseudoElement("after", records[1].removedNodes[0], originalAfterElement);
observer.disconnect();
testAddedDueToDisplay();
});
m.observe(parent, { nativeAnonymousChildList: true });
parent.style.display = "none";
}
function testAddedDueToDisplay() {
let parent = document.getElementById("pseudo");
var m = new MutationObserver(function(records, observer) {
is(records.length, 2, "Correct number of records");
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
is(records[0].target, parent, "Correct target (1)");
is(records[1].target, parent, "Correct target (2)");
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
is(records[1].addedNodes.length, 1, "Should have got addedNodes");
assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
observer.disconnect();
testDifferentTargetNoSubtree();
});
m.observe(parent, { nativeAnonymousChildList: true });
parent.style.display = "block";
}
function testDifferentTargetNoSubtree() {
let parent = document.getElementById("pseudo");
var m = new MutationObserver(function(records, observer) {
ok(false,
"No mutation should fire when observing on a parent without subtree option.");
});
m.observe(document, { nativeAnonymousChildList: true });
parent.style.display = "none";
// Wait for the actual mutation to come through, making sure that
// the original observer never fires.
var m2 = new MutationObserver(function(records, observer) {
ok(!getFirstChild(parent), "Pseudo element has been removed, but no mutation");
ok(!getLastChild(parent), "Pseudo element has been removed, but no mutation");
observer.disconnect();
testSubtree();
});
m2.observe(parent, { nativeAnonymousChildList: true });
}
function testSubtree() {
let parent = document.getElementById("pseudo");
var m = new MutationObserver(function(records, observer) {
is(records.length, 2, "Correct number of records");
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
is(records[0].target, parent, "Correct target (1)");
is(records[1].target, parent, "Correct target (2)");
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
is(records[1].addedNodes.length, 1, "Should have got addedNodes");
assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
observer.disconnect();
SimpleTest.finish();
});
m.observe(document, { nativeAnonymousChildList: true, subtree: true });
parent.style.display = "block";
}
</script>
</pre>
</body>
</html>

View File

@ -909,6 +909,19 @@ function testAttributeRecordMerging4() {
m.disconnect();
div.innerHTML = "";
div.removeAttribute("foo");
then(testChromeOnly);
}
function testChromeOnly() {
// Content can't access nativeAnonymousChildList
try {
var mo = new M(function(records, observer) { });
mo.observe(div, { nativeAnonymousChildList: true });
ok(false, "Should have thrown when trying to observe with chrome-only init");
} catch (e) {
ok(true, "Throws when trying to observe with chrome-only init");
}
then();
}

View File

@ -409,7 +409,7 @@ public:
NS_IMETHOD GetItemId(nsAString& aId) final override {
nsString id;
GetItemId(id);
aId.Assign(aId);
aId.Assign(id);
return NS_OK;
}
NS_IMETHOD SetItemId(const nsAString& aId) final override {

View File

@ -169,6 +169,10 @@ ScreenManagerParent::RecvScreenForBrowser(const TabId& aTabId,
bool
ScreenManagerParent::ExtractScreenDetails(nsIScreen* aScreen, ScreenDetails &aDetails)
{
if (!aScreen) {
return false;
}
uint32_t id;
nsresult rv = aScreen->GetId(&id);
NS_ENSURE_SUCCESS(rv, false);

View File

@ -9,11 +9,11 @@
#include <stdint.h>
#include "MP4Demuxer.h"
#include "mp4_demuxer/Index.h"
#include "mp4_demuxer/MoofParser.h"
#include "mp4_demuxer/MP4Metadata.h"
#include "mp4_demuxer/ResourceStream.h"
#include "mp4_demuxer/BufferStream.h"
#include "mp4_demuxer/Index.h"
// Used for telemetry
#include "mozilla/Telemetry.h"
@ -30,6 +30,51 @@ PRLogModuleInfo* GetDemuxerLog() {
namespace mozilla {
class MP4TrackDemuxer : public MediaTrackDemuxer
{
public:
MP4TrackDemuxer(MP4Demuxer* aParent,
UniquePtr<TrackInfo>&& aInfo,
const nsTArray<mp4_demuxer::Index::Indice>& indices);
virtual UniquePtr<TrackInfo> GetInfo() const override;
virtual nsRefPtr<SeekPromise> Seek(media::TimeUnit aTime) override;
virtual nsRefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override;
virtual void Reset() override;
virtual nsresult GetNextRandomAccessPoint(media::TimeUnit* aTime) override;
nsRefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) override;
virtual media::TimeIntervals GetBuffered() override;
virtual void BreakCycles() override;
private:
friend class MP4Demuxer;
void NotifyDataArrived();
void UpdateSamples(nsTArray<nsRefPtr<MediaRawData>>& aSamples);
void EnsureUpToDateIndex();
void SetNextKeyFrameTime();
nsRefPtr<MP4Demuxer> mParent;
nsRefPtr<mp4_demuxer::ResourceStream> mStream;
UniquePtr<TrackInfo> mInfo;
// We do not actually need a monitor, however MoofParser (in mIndex) will
// assert if a monitor isn't held.
Monitor mMonitor;
nsRefPtr<mp4_demuxer::Index> mIndex;
UniquePtr<mp4_demuxer::SampleIterator> mIterator;
Maybe<media::TimeUnit> mNextKeyframeTime;
// Queued samples extracted by the demuxer, but not yet returned.
nsRefPtr<MediaRawData> mQueuedSample;
bool mNeedReIndex;
bool mNeedSPSForTelemetry;
};
// Returns true if no SPS was found and search for it should continue.
bool
AccumulateSPSTelemetry(const MediaByteBuffer* aExtradata)
@ -120,8 +165,15 @@ MP4Demuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber)
if (mMetadata->GetNumberTracks(aType) <= aTrackNumber) {
return nullptr;
}
nsRefPtr<MP4TrackDemuxer> e =
new MP4TrackDemuxer(this, aType, aTrackNumber);
UniquePtr<TrackInfo> info = mMetadata->GetTrackInfo(aType, aTrackNumber);
if (!info) {
return nullptr;
}
FallibleTArray<mp4_demuxer::Index::Indice> indices;
if (!mMetadata->ReadTrackIndex(indices, info->mTrackId)) {
return nullptr;
}
nsRefPtr<MP4TrackDemuxer> e = new MP4TrackDemuxer(this, Move(info), indices);
mDemuxers.AppendElement(e);
return e.forget();
@ -174,27 +226,20 @@ MP4Demuxer::GetCrypto()
}
MP4TrackDemuxer::MP4TrackDemuxer(MP4Demuxer* aParent,
TrackInfo::TrackType aType,
uint32_t aTrackNumber)
UniquePtr<TrackInfo>&& aInfo,
const nsTArray<mp4_demuxer::Index::Indice>& indices)
: mParent(aParent)
, mStream(new mp4_demuxer::ResourceStream(mParent->mResource))
, mNeedReIndex(true)
, mInfo(Move(aInfo))
, mMonitor("MP4TrackDemuxer")
{
mInfo = mParent->mMetadata->GetTrackInfo(aType, aTrackNumber);
MOZ_ASSERT(mInfo);
FallibleTArray<mp4_demuxer::Index::Indice> indices;
if (!mParent->mMetadata->ReadTrackIndex(indices, mInfo->mTrackId)) {
MOZ_ASSERT(false);
}
mIndex = new mp4_demuxer::Index(indices,
, mIndex(new mp4_demuxer::Index(indices,
mStream,
mInfo->mTrackId,
mInfo->IsAudio(),
&mMonitor);
mIterator = MakeUnique<mp4_demuxer::SampleIterator>(mIndex);
&mMonitor))
, mIterator(MakeUnique<mp4_demuxer::SampleIterator>(mIndex))
, mNeedReIndex(true)
{
EnsureUpToDateIndex(); // Force update of index
// Collect telemetry from h264 AVCC SPS.

View File

@ -13,7 +13,6 @@
#include "MediaResource.h"
namespace mp4_demuxer {
class Index;
class MP4Metadata;
class ResourceStream;
class SampleIterator;
@ -54,51 +53,6 @@ private:
nsTArray<nsRefPtr<MP4TrackDemuxer>> mDemuxers;
};
class MP4TrackDemuxer : public MediaTrackDemuxer
{
public:
MP4TrackDemuxer(MP4Demuxer* aParent,
TrackInfo::TrackType aType,
uint32_t aTrackNumber);
virtual UniquePtr<TrackInfo> GetInfo() const override;
virtual nsRefPtr<SeekPromise> Seek(media::TimeUnit aTime) override;
virtual nsRefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override;
virtual void Reset() override;
virtual nsresult GetNextRandomAccessPoint(media::TimeUnit* aTime) override;
nsRefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) override;
virtual media::TimeIntervals GetBuffered() override;
virtual void BreakCycles() override;
private:
friend class MP4Demuxer;
void NotifyDataArrived();
void UpdateSamples(nsTArray<nsRefPtr<MediaRawData>>& aSamples);
void EnsureUpToDateIndex();
void SetNextKeyFrameTime();
nsRefPtr<MP4Demuxer> mParent;
nsRefPtr<mp4_demuxer::Index> mIndex;
UniquePtr<mp4_demuxer::SampleIterator> mIterator;
UniquePtr<TrackInfo> mInfo;
nsRefPtr<mp4_demuxer::ResourceStream> mStream;
Maybe<media::TimeUnit> mNextKeyframeTime;
// Queued samples extracted by the demuxer, but not yet returned.
nsRefPtr<MediaRawData> mQueuedSample;
bool mNeedReIndex;
bool mNeedSPSForTelemetry;
// We do not actually need a monitor, however MoofParser will assert
// if a monitor isn't held.
Monitor mMonitor;
};
} // namespace mozilla
#endif

View File

@ -172,10 +172,6 @@ skip-if = (toolkit == 'gonk' && !debug) || android_version == '10' || android_ve
[test_stereoPanningWithGain.html]
[test_waveDecoder.html]
[test_waveShaper.html]
skip-if = os == 'win' && debug #Bug 1202564
[test_waveShaperNoCurve.html]
skip-if = os == 'win' && debug #Bug 1202565
[test_waveShaperPassThrough.html]
skip-if = os == 'win' && debug #Bug 1196084
[test_waveShaperInvalidLengthCurve.html]
skip-if = os == 'win' && debug #Bug 1202567

View File

@ -971,7 +971,7 @@ NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char*
NPError err = pluginInstanceInit(instanceData);
if (err != NPERR_NO_ERROR) {
NPN_ReleaseObject(scriptableObject);
free(instanceData);
delete instanceData;
return err;
}

View File

@ -105,7 +105,7 @@ PresentationResponderLoadingCallback::Init(nsIDocShell* aDocShell)
return rv;
}
if ((busyFlags & nsIDocShell::BUSY_FLAGS_NONE) ||
if ((busyFlags == nsIDocShell::BUSY_FLAGS_NONE) ||
(busyFlags & nsIDocShell::BUSY_FLAGS_PAGE_LOADING)) {
// The docshell has finished loading or is receiving data (|STATE_TRANSFERRING|
// has already been fired), so the page is ready for presentation use.

View File

@ -301,15 +301,15 @@ private:
// List of scopes having data, for optimization purposes only
nsTHashtable<nsCStringHashKey> mScopesHavingData;
StatementCache mWorkerStatements;
StatementCache mReaderStatements;
// Connection used by the worker thread for all read and write ops
nsCOMPtr<mozIStorageConnection> mWorkerConnection;
// Connection used only on the main thread for sync read operations
nsCOMPtr<mozIStorageConnection> mReaderConnection;
StatementCache mWorkerStatements;
StatementCache mReaderStatements;
// Time the first pending operation has been added to the pending operations
// list
PRIntervalTime mDirtyEpoch;

View File

@ -61,6 +61,8 @@ dictionary MutationObserverInit {
boolean attributeOldValue;
boolean characterDataOldValue;
// [ChromeOnly]
boolean nativeAnonymousChildList = false;
// [ChromeOnly]
boolean animations;
sequence<DOMString> attributeFilter;
};

View File

@ -2873,7 +2873,7 @@ nsHTMLEditRules::MoveBlock(nsIDOMNode *aLeftBlock, nsIDOMNode *aRightBlock, int3
// GetNodesFromPoint is the workhorse that figures out what we wnat to move.
nsresult res = GetNodesFromPoint(::DOMPoint(aRightBlock,aRightOffset),
EditAction::makeList, arrayOfNodes,
TouchContent::no);
TouchContent::yes);
NS_ENSURE_SUCCESS(res, res);
for (auto& curNode : arrayOfNodes) {
// get the node to act on
@ -5614,6 +5614,27 @@ nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode* aNode,
if (mHTMLEditor->IsVisBreak(nextNode->AsDOMNode())) {
break;
}
// Check for newlines in pre-formatted text nodes.
bool isPRE;
mHTMLEditor->IsPreformatted(nextNode->AsDOMNode(), &isPRE);
if (isPRE) {
nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(nextNode);
if (textNode) {
nsAutoString tempString;
textNode->GetData(tempString);
int32_t newlinePos = tempString.FindChar(nsCRT::LF);
if (newlinePos >= 0) {
if ((uint32_t)newlinePos + 1 == tempString.Length()) {
// No need for special processing if the newline is at the end.
break;
}
*outNode = nextNode->AsDOMNode();
*outOffset = newlinePos + 1;
return;
}
}
}
NS_ENSURE_TRUE(mHTMLEditor, /* void */);
nextNode = mHTMLEditor->GetNextHTMLNode(node, offset, true);
}
@ -5781,6 +5802,38 @@ nsHTMLEditRules::GetNodesForOperation(nsTArray<nsRefPtr<nsRange>>& aArrayOfRange
int32_t rangeCount = aArrayOfRanges.Length();
nsresult res = NS_OK;
if (aTouchContent == TouchContent::yes) {
// Split text nodes. This is necessary, since GetPromotedPoint() may return a
// range ending in a text node in case where part of a pre-formatted
// elements needs to be moved.
for (int32_t i = 0; i < rangeCount; i++) {
nsRefPtr<nsRange> r = aArrayOfRanges[i];
nsCOMPtr<nsIContent> endParent = do_QueryInterface(r->GetEndParent());
if (!mHTMLEditor->IsTextNode(endParent)) {
continue;
}
nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(endParent);
if (textNode) {
int32_t offset = r->EndOffset();
nsAutoString tempString;
textNode->GetData(tempString);
if (0 < offset && offset < (int32_t)(tempString.Length())) {
// Split the text node.
nsCOMPtr<nsIDOMNode> tempNode;
res = mHTMLEditor->SplitNode(endParent->AsDOMNode(), offset,
getter_AddRefs(tempNode));
NS_ENSURE_SUCCESS(res, res);
// Correct the range.
// The new end parent becomes the parent node of the text.
nsCOMPtr<nsIContent> newParent = endParent->GetParent();
r->SetEnd(newParent, newParent->IndexOf(endParent));
}
}
}
}
// Bust up any inlines that cross our range endpoints, but only if we are
// allowed to touch content.

View File

@ -130,6 +130,7 @@ skip-if = toolkit == 'android' || e10s
[test_bug757371.html]
[test_bug757771.html]
[test_bug767684.html]
[test_bug772796.html]
[test_bug773262.html]
[test_bug780035.html]
[test_bug787432.html]

View File

@ -0,0 +1,223 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=772796
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 772796</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style> .pre { white-space: pre } </style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=772796">Mozilla Bug 772796</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<div id="editable" contenteditable></div>
<pre id="test">
<script type="application/javascript">
var tests = [
/*00*/[ "<div>test</div><pre>foobar\nbaz</pre>", "<div>testfoobar\n</div><pre>baz</pre>" ],
/*01*/[ "<div>test</div><pre><b>foobar\nbaz</b></pre>", "<div>test<b>foobar\n</b></div><pre><b>baz</b></pre>" ],
/*02*/[ "<div>test</div><pre><b>foo</b>bar\nbaz</pre>", "<div>test<b>foo</b>bar\n</div><pre>baz</pre>" ],
/*03*/[ "<div>test</div><pre><b>foo</b>\nbar</pre>", "<div>test<b>foo</b>\n</div><pre>bar</pre>" ],
/*04*/[ "<div>test</div><pre><b>foo\n</b>bar\nbaz</pre>", "<div>test<b>foo\n</b></div><pre>bar\nbaz</pre>" ],
/* The <br> after the foobar is unfortunate but is behaviour that hasn't changed in bug 772796. */
/*05*/[ "<div>test</div><pre>foobar<br>baz</pre>", "<div>testfoobar<br></div><pre>baz</pre>" ],
/*06*/[ "<div>test</div><pre><b>foobar<br>baz</b></pre>", "<div>test<b>foobar</b><br></div><pre><b>baz</b></pre>" ],
/*
* Some tests with block elements.
* Tests 07, 09 and 11 don't use "MoveBlock", they use "JoinNodesSmart".
* Test 11 is a pain: <div>foo\bar</div> is be joined to "test", losing the visible line break.
*/
/*07*/[ "<div>test</div><pre><div>foobar</div>baz</pre>", "<div>testfoobar</div><pre>baz</pre>" ],
/*08*/[ "<div>test</div><pre>foobar<div>baz</div></pre>", "<div>testfoobar</div><pre><div>baz</div></pre>" ],
/*09*/[ "<div>test</div><pre><div>foobar</div>baz\nfred</pre>", "<div>testfoobar</div><pre>baz\nfred</pre>" ],
/*10*/[ "<div>test</div><pre>foobar<div>baz</div>\nfred</pre>", "<div>testfoobar</div><pre><div>baz</div>\nfred</pre>" ],
/*11*/[ "<div>test</div><pre><div>foo\nbar</div>baz\nfred</pre>", "<div>testfoo\nbar</div><pre>baz\nfred</pre>" ], // BAD
/*12*/[ "<div>test</div><pre>foo<div>bar</div>baz\nfred</pre>", "<div>testfoo</div><pre><div>bar</div>baz\nfred</pre>" ],
/*
* Repeating all tests above with the <pre> on a new line.
* We know that backspace doesn't work (bug 1190161). Third argument shows the current outcome.
*/
/*13-00*/[ "<div>test</div>\n<pre>foobar\nbaz</pre>", "<div>testfoobar\n</div><pre>baz</pre>",
"<div>test</div>foobar\n<pre>baz</pre>" ],
/*14-01*/[ "<div>test</div>\n<pre><b>foobar\nbaz</b></pre>", "<div>test<b>foobar\n</b></div><pre><b>baz</b></pre>",
"<div>test</div><b>foobar\n</b><pre><b>baz</b></pre>" ],
/*15-02*/[ "<div>test</div>\n<pre><b>foo</b>bar\nbaz</pre>", "<div>test<b>foo</b>bar\n</div><pre>baz</pre>",
"<div>test</div><b>foo</b>bar\n<pre>baz</pre>" ],
/*16-03*/[ "<div>test</div>\n<pre><b>foo</b>\nbar</pre>", "<div>test<b>foo</b>\n</div><pre>bar</pre>",
"<div>test</div><b>foo</b>\n<pre>bar</pre>" ],
/*17-04*/[ "<div>test</div>\n<pre><b>foo\n</b>bar\nbaz</pre>", "<div>test<b>foo\n</b></div><pre>bar\nbaz</pre>",
"<div>test</div><b>foo\n</b><pre>bar\nbaz</pre>" ],
/*18-05*/[ "<div>test</div>\n<pre>foobar<br>baz</pre>", "<div>testfoobar<br></div><pre>baz</pre>",
"<div>test</div>foobar<br><pre>baz</pre>" ],
/*19-06*/[ "<div>test</div>\n<pre><b>foobar<br>baz</b></pre>", "<div>test<b>foobar</b><br></div><pre><b>baz</b></pre>",
"<div>test</div><b>foobar</b><br><pre><b>baz</b></pre>" ],
/*20-07*/[ "<div>test</div>\n<pre><div>foobar</div>baz</pre>", "<div>testfoobar</div><pre>baz</pre>",
"<div>test</div>foobar<pre>baz</pre>" ],
/*21-08*/[ "<div>test</div>\n<pre>foobar<div>baz</div></pre>", "<div>testfoobar</div><pre><div>baz</div></pre>",
"<div>test</div>foobar<pre><div>baz</div></pre>" ],
/*22-09*/[ "<div>test</div>\n<pre><div>foobar</div>baz\nfred</pre>", "<div>testfoobar</div><pre>baz\nfred</pre>",
"<div>test</div>foobar<pre>baz\nfred</pre>" ],
/*23-10*/[ "<div>test</div>\n<pre>foobar<div>baz</div>\nfred</pre>", "<div>testfoobar</div><pre><div>baz</div>\nfred</pre>",
"<div>test</div>foobar<pre><div>baz</div>\nfred</pre>" ],
/*24-11*/[ "<div>test</div>\n<pre><div>foo\nbar</div>baz\nfred</pre>", "<div>testfoo\nbar</div><pre>baz\nfred</pre>", // BAD
"<div>test</div>foo\n<pre><div>bar</div>baz\nfred</pre>" ],
/*25-12*/[ "<div>test</div>\n<pre>foo<div>bar</div>baz\nfred</pre>", "<div>testfoo</div><pre><div>bar</div>baz\nfred</pre>",
"<div>test</div>foo<pre><div>bar</div>baz\nfred</pre>" ],
/* Some tests without <div>. These exercise the MoveBlock "right in left" */
/*26-00*/[ "test<pre>foobar\nbaz</pre>", "testfoobar\n<pre>baz</pre>" ],
/*27-01*/[ "test<pre><b>foobar\nbaz</b></pre>", "test<b>foobar\n</b><pre><b>baz</b></pre>" ],
/*28-02*/[ "test<pre><b>foo</b>bar\nbaz</pre>", "test<b>foo</b>bar\n<pre>baz</pre>" ],
/*29-03*/[ "test<pre><b>foo</b>\nbar</pre>", "test<b>foo</b>\n<pre>bar</pre>" ],
/*30-04*/[ "test<pre><b>foo\n</b>bar\nbaz</pre>", "test<b>foo\n</b><pre>bar\nbaz</pre>" ],
/*31-05*/[ "test<pre>foobar<br>baz</pre>", "testfoobar<br><pre>baz</pre>" ],
/*32-06*/[ "test<pre><b>foobar<br>baz</b></pre>", "test<b>foobar</b><br><pre><b>baz</b></pre>" ],
/*33-07*/[ "test<pre><div>foobar</div>baz</pre>", "testfoobar<pre>baz</pre>" ],
/*34-08*/[ "test<pre>foobar<div>baz</div></pre>", "testfoobar<pre><div>baz</div></pre>" ],
/*35-09*/[ "test<pre><div>foobar</div>baz\nfred</pre>", "testfoobar<pre>baz\nfred</pre>" ],
/*36-10*/[ "test<pre>foobar<div>baz</div>\nfred</pre>", "testfoobar<pre><div>baz</div>\nfred</pre>" ],
/*37-11*/[ "test<pre><div>foo\nbar</div>baz\nfred</pre>", "testfoo\n<pre><div>bar</div>baz\nfred</pre>" ],
/*38-12*/[ "test<pre>foo<div>bar</div>baz\nfred</pre>", "testfoo<pre><div>bar</div>baz\nfred</pre>" ],
/*
* Some tests with <span class="pre">. Again 07, 09 and 11 use "JoinNodesSmart".
* All these exercise MoveBlock "left in right". The "right" is the surrounding "contenteditable" div.
*/
/*39-00*/[ "<div>test</div><span class=\"pre\">foobar\nbaz</span>", "<div>test<span class=\"pre\">foobar\n</span></div><span class=\"pre\">baz</span>" ],
/*40-01*/[ "<div>test</div><span class=\"pre\"><b>foobar\nbaz</b></span>", "<div>test<span class=\"pre\"><b>foobar\n</b></span></div><span class=\"pre\"><b>baz</b></span>" ],
/*41-02*/[ "<div>test</div><span class=\"pre\"><b>foo</b>bar\nbaz</span>", "<div>test<span class=\"pre\"><b>foo</b>bar\n</span></div><span class=\"pre\">baz</span>" ],
/*42-03*/[ "<div>test</div><span class=\"pre\"><b>foo</b>\nbar</span>", "<div>test<span class=\"pre\"><b>foo</b>\n</span></div><span class=\"pre\">bar</span>" ],
/*43-04*/[ "<div>test</div><span class=\"pre\"><b>foo\n</b>bar\nbaz</span>", "<div>test<span class=\"pre\"><b>foo\n</b></span></div><span class=\"pre\">bar\nbaz</span>" ],
/*44-05*/[ "<div>test</div><span class=\"pre\">foobar<br>baz</span>", "<div>test<span class=\"pre\">foobar</span><br><span class=\"pre\"></span></div><span class=\"pre\">baz</span>" ],
/*45-06*/[ "<div>test</div><span class=\"pre\"><b>foobar<br>baz</b></span>", "<div>test<span class=\"pre\"><b>foobar</b></span><br><span class=\"pre\"></span></div><span class=\"pre\"><b>baz</b></span>" ],
/*46-07*/[ "<div>test</div><span class=\"pre\"><div>foobar</div>baz</span>", "<div>testfoobar</div><span class=\"pre\">baz</span>" ],
/*47-08*/[ "<div>test</div><span class=\"pre\">foobar<div>baz</div></span>", "<div>test<span class=\"pre\">foobar</span></div><span class=\"pre\"><div>baz</div></span>" ],
/*48-09*/[ "<div>test</div><span class=\"pre\"><div>foobar</div>baz\nfred</span>", "<div>testfoobar</div><span class=\"pre\">baz\nfred</span>" ],
/*49-10*/[ "<div>test</div><span class=\"pre\">foobar<div>baz</div>\nfred</span>", "<div>test<span class=\"pre\">foobar</span></div><span class=\"pre\"><div>baz</div>\nfred</span>" ],
/*50-11*/[ "<div>test</div><span class=\"pre\"><div>foo\nbar</div>baz\nfred</span>", "<div>testfoo\nbar</div><span class=\"pre\">baz\nfred</span>" ], // BAD
/*51-12*/[ "<div>test</div><span class=\"pre\">foo<div>bar</div>baz\nfred</span>", "<div>test<span class=\"pre\">foo</span></div><span class=\"pre\"><div>bar</div>baz\nfred</span>" ],
/* Some tests with <div class="pre">. */
/*
* The results are pretty ugly, since joining two <divs> sadly carrys the properties of the right to the left,
* but not in all cases: 07, 09, 11 are actually right. All cases use "JoinNodesSmart".
* Here we merely document the ugly behaviour. See bug 1191875 for more information.
*/
/*52-00*/[ "<div>test</div><div class=\"pre\">foobar\nbaz</div>", "<div class=\"pre\">testfoobar\nbaz</div>" ],
/*53-01*/[ "<div>test</div><div class=\"pre\"><b>foobar\nbaz</b></div>", "<div class=\"pre\">test<b>foobar\nbaz</b></div>" ],
/*54-02*/[ "<div>test</div><div class=\"pre\"><b>foo</b>bar\nbaz</div>", "<div class=\"pre\">test<b>foo</b>bar\nbaz</div>" ],
/*55-03*/[ "<div>test</div><div class=\"pre\"><b>foo</b>\nbar</div>", "<div class=\"pre\">test<b>foo</b>\nbar</div>" ],
/*56-04*/[ "<div>test</div><div class=\"pre\"><b>foo\n</b>bar\nbaz</div>", "<div class=\"pre\">test<b>foo\n</b>bar\nbaz</div>" ],
/*57-05*/[ "<div>test</div><div class=\"pre\">foobar<br>baz</div>", "<div class=\"pre\">testfoobar<br>baz</div>" ],
/*58-06*/[ "<div>test</div><div class=\"pre\"><b>foobar<br>baz</b></div>", "<div class=\"pre\">test<b>foobar<br>baz</b></div>" ],
/*59-07*/[ "<div>test</div><div class=\"pre\"><div>foobar</div>baz</div>", "<div>testfoobar</div><div class=\"pre\">baz</div>" ],
/*60-08*/[ "<div>test</div><div class=\"pre\">foobar<div>baz</div></div>", "<div class=\"pre\">testfoobar<div>baz</div></div>" ],
/*61-09*/[ "<div>test</div><div class=\"pre\"><div>foobar</div>baz\nfred</div>", "<div>testfoobar</div><div class=\"pre\">baz\nfred</div>" ],
/*62-10*/[ "<div>test</div><div class=\"pre\">foobar<div>baz</div>\nfred</div>", "<div class=\"pre\">testfoobar<div>baz</div>\nfred</div>" ],
/*63-11*/[ "<div>test</div><div class=\"pre\"><div>foo\nbar</div>baz\nfred</div>", "<div>testfoo\nbar</div><div class=\"pre\">baz\nfred</div>" ], // BAD
/*64-12*/[ "<div>test</div><div class=\"pre\">foo<div>bar</div>baz\nfred</div>", "<div class=\"pre\">testfoo<div>bar</div>baz\nfred</div>" ],
/* Some tests with lists. These exercise the MoveBlock "left in right". */
/*65*/[ "<ul><pre><li>test</li>foobar\nbaz</pre></ul>", "<ul><pre><li>testfoobar\n</li>baz</pre></ul>" ],
/*66*/[ "<ul><pre><li>test</li><b>foobar\nbaz</b></pre></ul>", "<ul><pre><li>test<b>foobar\n</b></li><b>baz</b></pre></ul>" ],
/*67*/[ "<ul><pre><li>test</li><b>foo</b>bar\nbaz</pre></ul>", "<ul><pre><li>test<b>foo</b>bar\n</li>baz</pre></ul>" ],
/*68*/[ "<ul><pre><li>test</li><b>foo</b>\nbar</pre></ul>", "<ul><pre><li>test<b>foo</b>\n</li>bar</pre></ul>" ],
/*69*/[ "<ul><pre><li>test</li><b>foo\n</b>bar\nbaz</pre></ul>", "<ul><pre><li>test<b>foo\n</b></li>bar\nbaz</pre></ul>" ],
/* Last not least, some simple edge case tests. */
/*70*/[ "<div>test</div><pre>foobar\n</pre>baz", "<div>testfoobar\n</div>baz" ],
/*71*/[ "<div>test</div><pre>\nfoo\nbar</pre>", "<div>testfoo\n</div><pre>bar</pre>" ],
/*72*/[ "<div>test</div><pre>\n\nfoo\nbar</pre>", "<div>test\n</div><pre>foo\nbar</pre>" ],
];
/** Test for Bug 772796 **/
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(function() {
var sel = window.getSelection();
var theEdit = document.getElementById("editable");
var testName;
var theDiv;
for (i = 0; i < tests.length; i++) {
testName = "test" + i.toString();
dump (testName+"\n");
dump (tests[i][0]+"\n");
/* Set up the selection. */
theEdit.innerHTML = "<div id=\"" + testName + "\">" + tests[i][0] + "</div>";
theDiv = document.getElementById(testName);
theDiv.focus();
sel.collapse(theDiv, 0);
synthesizeMouse(theDiv, 100, 2, {}); /* click behind and down */
/** First round: Forward delete. **/
synthesizeKey("VK_DELETE", {});
is(theDiv.innerHTML, tests[i][1], "delete(collapsed): inner HTML for " + testName);
/* Set up the selection. */
theEdit.innerHTML = "<div id=\"" + testName + "\">" + tests[i][0] + "</div>";
theDiv = document.getElementById(testName);
theDiv.focus();
sel.collapse(theDiv, 0);
synthesizeMouse(theDiv, 100, 2, {}); /* click behind and down */
/** Second round: Backspace. **/
synthesizeKey("VK_RIGHT", {});
synthesizeKey("VK_BACK_SPACE", {});
if (tests[i].length == 2) {
is(theDiv.innerHTML, tests[i][1], "backspace: inner HTML for " + testName);
} else {
todo_is(theDiv.innerHTML, tests[i][1], "backspace(should be): inner HTML for " + testName);
is(theDiv.innerHTML, tests[i][2], "backspace(currently is): inner HTML for " + testName);
}
/* Set up the selection. */
theEdit.innerHTML = "<div id=\"" + testName + "\">" + tests[i][0] + "</div>";
theDiv = document.getElementById(testName);
theDiv.focus();
sel.collapse(theDiv, 0);
synthesizeMouse(theDiv, 100, 2, {}); /* click behind and down */
/** Third round: Delete with non-collapsed selection. **/
if (i == 72) {
// This test doesn't work, since we can't select only one newline using the right arrow key.
continue;
}
synthesizeKey("VK_LEFT", {});
/* Strangely enough we need to hit "right arrow" three times to select two characters. */
synthesizeKey("VK_RIGHT", {shiftKey:true});
synthesizeKey("VK_RIGHT", {shiftKey:true});
synthesizeKey("VK_RIGHT", {shiftKey:true});
synthesizeKey("VK_DELETE", {});
/* We always expect to the delete the "tf" in "testfoo". */
var expected = tests[i][1].replace("testfoo", "tesoo")
.replace("test<b>foo", "tes<b>oo")
.replace("test<span class=\"pre\">foo", "tes<span class=\"pre\">oo")
.replace("test<span class=\"pre\"><b>foo", "tes<span class=\"pre\"><b>oo");
is(theDiv.innerHTML, expected, "delete(non-collapsed): inner HTML for " + testName);
}
SimpleTest.finish();
});
</script>
</pre>
</body>
</html>

View File

@ -16,6 +16,7 @@
#include "nsContentUtils.h"
#include "nsIScriptSecurityManager.h"
#include "nsJSPrincipals.h"
#include "nsIScriptError.h"
#include "jswrapper.h"
extern PRLogModuleInfo *MCD;
@ -106,7 +107,18 @@ nsresult EvaluateAdminConfigScript(const char *js_buffer, size_t length,
nsAutoCString script(js_buffer, length);
JS::RootedValue v(cx);
rv = xpc->EvalInSandboxObject(NS_ConvertUTF8toUTF16(script), filename, cx,
nsString convertedScript = NS_ConvertUTF8toUTF16(script);
if (convertedScript.Length() == 0) {
nsContentUtils::ReportToConsoleNonLocalized(
NS_LITERAL_STRING("Your AutoConfig file is ASCII. Please convert it to UTF-8."),
nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("autoconfig"),
nullptr);
/* If the length is 0, the conversion failed. Fallback to ASCII */
convertedScript = NS_ConvertASCIItoUTF16(script);
}
rv = xpc->EvalInSandboxObject(convertedScript, filename, cx,
autoconfigSb, JSVERSION_LATEST, &v);
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -7,7 +7,6 @@
include('/ipc/chromium/chromium-config.mozbuild')
SOURCES += [
'mozEnglishWordUtils.cpp',
'mozGenericWordUtils.cpp',
'mozInlineSpellChecker.cpp',
'mozInlineSpellWordUtil.cpp',
'mozPersonalDictionary.cpp',

View File

@ -1,40 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozGenericWordUtils.h"
NS_IMPL_ISUPPORTS(mozGenericWordUtils, mozISpellI18NUtil)
// do something sensible but generic ... eventually. For now whine.
mozGenericWordUtils::mozGenericWordUtils()
{
/* member initializers and constructor code */
}
mozGenericWordUtils::~mozGenericWordUtils()
{
/* destructor code */
}
NS_IMETHODIMP mozGenericWordUtils::GetLanguage(char16_t * *aLanguage)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP mozGenericWordUtils::GetRootForm(const char16_t *word, uint32_t type, char16_t ***words, uint32_t *count)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP mozGenericWordUtils::FromRootForm(const char16_t *word, const char16_t **iwords, uint32_t icount, char16_t ***owords, uint32_t *ocount)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP mozGenericWordUtils::FindNextWord(const char16_t *word, uint32_t length, uint32_t offset, int32_t *begin, int32_t *end)
{
return NS_ERROR_NOT_IMPLEMENTED;
}

View File

@ -1,24 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozGenericWordUtils_h__
#define mozGenericWordUtils_h__
#include "nsCOMPtr.h"
#include "mozISpellI18NUtil.h"
#include "nsCycleCollectionParticipant.h"
class mozGenericWordUtils : public mozISpellI18NUtil
{
protected:
virtual ~mozGenericWordUtils();
public:
NS_DECL_ISUPPORTS
NS_DECL_MOZISPELLI18NUTIL
mozGenericWordUtils();
};
#endif

View File

@ -5,36 +5,28 @@
#include "mozSpellI18NManager.h"
#include "mozEnglishWordUtils.h"
#include "mozGenericWordUtils.h"
#include "nsString.h"
#include "mozilla/nsRefPtr.h"
NS_IMPL_ISUPPORTS(mozSpellI18NManager, mozISpellI18NManager)
mozSpellI18NManager::mozSpellI18NManager()
{
/* member initializers and constructor code */
}
mozSpellI18NManager::~mozSpellI18NManager()
{
/* destructor code */
}
NS_IMETHODIMP mozSpellI18NManager::GetUtil(const char16_t *aLanguage, mozISpellI18NUtil **_retval)
{
if( nullptr == _retval) {
if (!_retval) {
return NS_ERROR_NULL_POINTER;
}
*_retval = nullptr;
nsAutoString lang;
lang.Assign(aLanguage);
if(lang.EqualsLiteral("en")){
*_retval = new mozEnglishWordUtils;
}
else{
*_retval = new mozEnglishWordUtils;
}
NS_IF_ADDREF(*_retval);
// XXX TODO Actually handle multiple languages.
nsRefPtr<mozEnglishWordUtils> utils = new mozEnglishWordUtils;
utils.forget(_retval);
return NS_OK;
}

80
gfx/2d/CriticalSection.h Normal file
View File

@ -0,0 +1,80 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MOZILLA_GFX_CRITICALSECTION_H_
#define MOZILLA_GFX_CRITICALSECTION_H_
#ifdef WIN32
#include <windows.h>
#else
#include <pthread.h>
#include "mozilla/DebugOnly.h"
#endif
namespace mozilla {
namespace gfx {
#ifdef WIN32
class CriticalSection {
public:
CriticalSection() { ::InitializeCriticalSection(&mCriticalSection); }
~CriticalSection() { ::DeleteCriticalSection(&mCriticalSection); }
void Enter() { ::EnterCriticalSection(&mCriticalSection); }
void Leave() { ::LeaveCriticalSection(&mCriticalSection); }
protected:
CRITICAL_SECTION mCriticalSection;
};
#else
// posix
class PosixCondvar;
class CriticalSection {
public:
CriticalSection() {
DebugOnly<int> err = pthread_mutex_init(&mMutex, nullptr);
MOZ_ASSERT(!err);
}
~CriticalSection() {
DebugOnly<int> err = pthread_mutex_destroy(&mMutex);
MOZ_ASSERT(!err);
}
void Enter() {
DebugOnly<int> err = pthread_mutex_lock(&mMutex);
MOZ_ASSERT(!err);
}
void Leave() {
DebugOnly<int> err = pthread_mutex_unlock(&mMutex);
MOZ_ASSERT(!err);
}
protected:
pthread_mutex_t mMutex;
friend class PosixCondVar;
};
#endif
/// RAII helper.
struct CriticalSectionAutoEnter {
explicit CriticalSectionAutoEnter(CriticalSection* aSection) : mSection(aSection) { mSection->Enter(); }
~CriticalSectionAutoEnter() { mSection->Leave(); }
protected:
CriticalSection* mSection;
};
} // namespace
} // namespace
#endif

View File

@ -6,6 +6,10 @@
#ifndef MOZILLA_GFX_DRAWCOMMAND_H_
#define MOZILLA_GFX_DRAWCOMMAND_H_
#define _USE_MATH_DEFINES
#include <math.h>
#include "2D.h"
#include "Filters.h"
#include <vector>
@ -31,7 +35,8 @@ enum class CommandType : int8_t {
PUSHCLIP,
PUSHCLIPRECT,
POPCLIP,
SETTRANSFORM
SETTRANSFORM,
FLUSH
};
class DrawingCommand
@ -39,7 +44,9 @@ class DrawingCommand
public:
virtual ~DrawingCommand() {}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix& aTransform) = 0;
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aTransform = nullptr) const = 0;
virtual bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const { return false; }
protected:
explicit DrawingCommand(CommandType aType)
@ -130,7 +137,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->DrawSurface(mSurface, mDest, mSource, mSurfOptions, mOptions);
}
@ -154,7 +161,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->DrawFilter(mFilter, mSourceRect, mDestPoint, mOptions);
}
@ -175,7 +182,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->ClearRect(mRect);
}
@ -197,11 +204,13 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix& aTransform)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aTransform) const
{
MOZ_ASSERT(!aTransform.HasNonIntegerTranslation());
MOZ_ASSERT(!aTransform || !aTransform->HasNonIntegerTranslation());
Point dest(Float(mDestination.x), Float(mDestination.y));
dest = aTransform * dest;
if (aTransform) {
dest = (*aTransform) * dest;
}
aDT->CopySurface(mSurface, mSourceRect, IntPoint(uint32_t(dest.x), uint32_t(dest.y)));
}
@ -224,11 +233,17 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->FillRect(mRect, mPattern, mOptions);
}
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
{
aDeviceRect = aTransform.TransformBounds(mRect);
return true;
}
private:
Rect mRect;
StoredPattern mPattern;
@ -248,9 +263,14 @@ public:
, mStrokeOptions(aStrokeOptions)
, mOptions(aOptions)
{
if (aStrokeOptions.mDashLength) {
mDashes.resize(aStrokeOptions.mDashLength);
mStrokeOptions.mDashPattern = &mDashes.front();
memcpy(&mDashes.front(), aStrokeOptions.mDashPattern, mStrokeOptions.mDashLength * sizeof(Float));
}
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->StrokeRect(mRect, mPattern, mStrokeOptions, mOptions);
}
@ -260,6 +280,7 @@ private:
StoredPattern mPattern;
StrokeOptions mStrokeOptions;
DrawOptions mOptions;
std::vector<Float> mDashes;
};
class StrokeLineCommand : public DrawingCommand
@ -279,7 +300,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->StrokeLine(mStart, mEnd, mPattern, mStrokeOptions, mOptions);
}
@ -305,17 +326,58 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->Fill(mPath, mPattern, mOptions);
}
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
{
aDeviceRect = mPath->GetBounds(aTransform);
return true;
}
private:
RefPtr<Path> mPath;
StoredPattern mPattern;
DrawOptions mOptions;
};
#ifndef M_SQRT2
#define M_SQRT2 1.41421356237309504880
#endif
#ifndef M_SQRT1_2
#define M_SQRT1_2 0.707106781186547524400844362104849039
#endif
// The logic for this comes from _cairo_stroke_style_max_distance_from_path
static Rect
PathExtentsToMaxStrokeExtents(const StrokeOptions &aStrokeOptions,
const Rect &aRect,
const Matrix &aTransform)
{
double styleExpansionFactor = 0.5f;
if (aStrokeOptions.mLineCap == CapStyle::SQUARE) {
styleExpansionFactor = M_SQRT1_2;
}
if (aStrokeOptions.mLineJoin == JoinStyle::MITER &&
styleExpansionFactor < M_SQRT2 * aStrokeOptions.mMiterLimit) {
styleExpansionFactor = M_SQRT2 * aStrokeOptions.mMiterLimit;
}
styleExpansionFactor *= aStrokeOptions.mLineWidth;
double dx = styleExpansionFactor * hypot(aTransform._11, aTransform._21);
double dy = styleExpansionFactor * hypot(aTransform._22, aTransform._12);
Rect result = aRect;
result.Inflate(dx, dy);
return result;
}
class StrokeCommand : public DrawingCommand
{
public:
@ -329,18 +391,30 @@ public:
, mStrokeOptions(aStrokeOptions)
, mOptions(aOptions)
{
if (aStrokeOptions.mDashLength) {
mDashes.resize(aStrokeOptions.mDashLength);
mStrokeOptions.mDashPattern = &mDashes.front();
memcpy(&mDashes.front(), aStrokeOptions.mDashPattern, mStrokeOptions.mDashLength * sizeof(Float));
}
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->Stroke(mPath, mPattern, mStrokeOptions, mOptions);
}
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
{
aDeviceRect = PathExtentsToMaxStrokeExtents(mStrokeOptions, mPath->GetBounds(aTransform), aTransform);
return true;
}
private:
RefPtr<Path> mPath;
StoredPattern mPattern;
StrokeOptions mStrokeOptions;
DrawOptions mOptions;
std::vector<Float> mDashes;
};
class FillGlyphsCommand : public DrawingCommand
@ -361,7 +435,7 @@ public:
memcpy(&mGlyphs.front(), aBuffer.mGlyphs, sizeof(Glyph) * aBuffer.mNumGlyphs);
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
GlyphBuffer buf;
buf.mNumGlyphs = mGlyphs.size();
@ -390,7 +464,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->Mask(mSource, mMask, mOptions);
}
@ -416,7 +490,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->MaskSurface(mSource, mMask, mOffset, mOptions);
}
@ -437,7 +511,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->PushClip(mPath);
}
@ -455,7 +529,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->PushClipRect(mRect);
}
@ -472,7 +546,7 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->PopClip();
}
@ -487,17 +561,33 @@ public:
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix& aMatrix)
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aMatrix) const
{
Matrix transform = mTransform;
transform *= aMatrix;
aDT->SetTransform(transform);
if (aMatrix) {
aDT->SetTransform(mTransform * (*aMatrix));
} else {
aDT->SetTransform(mTransform);
}
}
private:
Matrix mTransform;
};
class FlushCommand : public DrawingCommand
{
public:
explicit FlushCommand()
: DrawingCommand(CommandType::FLUSH)
{
}
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
{
aDT->Flush();
}
};
} // namespace gfx
} // namespace mozilla

View File

@ -188,7 +188,7 @@ DrawTargetCaptureImpl::ReplayToDrawTarget(DrawTarget* aDT, const Matrix& aTransf
uint8_t* current = start;
while (current < start + mDrawCommandStorage.size()) {
reinterpret_cast<DrawingCommand*>(current + sizeof(uint32_t))->ExecuteOnDT(aDT, aTransform);
reinterpret_cast<DrawingCommand*>(current + sizeof(uint32_t))->ExecuteOnDT(aDT, &aTransform);
current += *(uint32_t*)current;
}
}

120
gfx/2d/DrawingJob.cpp Normal file
View File

@ -0,0 +1,120 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DrawingJob.h"
#include "JobScheduler.h"
#include "mozilla/gfx/2D.h"
namespace mozilla {
namespace gfx {
DrawingJobBuilder::DrawingJobBuilder()
{}
DrawingJobBuilder::~DrawingJobBuilder()
{
MOZ_ASSERT(!mDrawTarget);
}
void
DrawingJob::Clear()
{
mCommandBuffer = nullptr;
mCursor = 0;
}
void
DrawingJobBuilder::BeginDrawingJob(DrawTarget* aTarget, IntPoint aOffset,
SyncObject* aStart)
{
MOZ_ASSERT(mCommandOffsets.empty());
MOZ_ASSERT(aTarget);
mDrawTarget = aTarget;
mOffset = aOffset;
mStart = aStart;
}
DrawingJob*
DrawingJobBuilder::EndDrawingJob(CommandBuffer* aCmdBuffer,
SyncObject* aCompletion,
WorkerThread* aPinToWorker)
{
MOZ_ASSERT(mDrawTarget);
DrawingJob* task = new DrawingJob(mDrawTarget, mOffset, mStart, aCompletion, aPinToWorker);
task->mCommandBuffer = aCmdBuffer;
task->mCommandOffsets = Move(mCommandOffsets);
mDrawTarget = nullptr;
mOffset = IntPoint();
mStart = nullptr;
return task;
}
DrawingJob::DrawingJob(DrawTarget* aTarget, IntPoint aOffset,
SyncObject* aStart, SyncObject* aCompletion,
WorkerThread* aPinToWorker)
: Job(aStart, aCompletion, aPinToWorker)
, mCommandBuffer(nullptr)
, mCursor(0)
, mDrawTarget(aTarget)
, mOffset(aOffset)
{
mCommandOffsets.reserve(64);
}
JobStatus
DrawingJob::Run()
{
while (mCursor < mCommandOffsets.size()) {
const DrawingCommand* cmd = mCommandBuffer->GetDrawingCommand(mCommandOffsets[mCursor]);
if (!cmd) {
return JobStatus::Error;
}
cmd->ExecuteOnDT(mDrawTarget);
++mCursor;
}
return JobStatus::Complete;
}
DrawingJob::~DrawingJob()
{
Clear();
}
const DrawingCommand*
CommandBuffer::GetDrawingCommand(ptrdiff_t aId)
{
return static_cast<DrawingCommand*>(mStorage.GetStorage(aId));
}
CommandBuffer::~CommandBuffer()
{
mStorage.ForEach([](void* item){
static_cast<DrawingCommand*>(item)->~DrawingCommand();
});
mStorage.Clear();
}
void
CommandBufferBuilder::BeginCommandBuffer(size_t aBufferSize)
{
MOZ_ASSERT(!mCommands);
mCommands = new CommandBuffer(aBufferSize);
}
already_AddRefed<CommandBuffer>
CommandBufferBuilder::EndCommandBuffer()
{
return mCommands.forget();
}
} // namespace
} // namespace

157
gfx/2d/DrawingJob.h Normal file
View File

@ -0,0 +1,157 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MOZILLA_GFX_COMMANDBUFFER_H_
#define MOZILLA_GFX_COMMANDBUFFER_H_
#include <stdint.h>
#include "mozilla/RefPtr.h"
#include "mozilla/Assertions.h"
#include "mozilla/gfx/Matrix.h"
#include "mozilla/gfx/JobScheduler.h"
#include "mozilla/gfx/IterableArena.h"
#include "DrawCommand.h"
namespace mozilla {
namespace gfx {
class DrawingCommand;
class PrintCommand;
class SignalCommand;
class DrawingJob;
class WaitCommand;
class SyncObject;
class MultiThreadedJobQueue;
class DrawTarget;
class DrawingJobBuilder;
class CommandBufferBuilder;
/// Contains a sequence of immutable drawing commands that are typically used by
/// several DrawingJobs.
///
/// CommandBuffer objects are built using CommandBufferBuilder.
class CommandBuffer : public external::AtomicRefCounted<CommandBuffer>
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(CommandBuffer)
~CommandBuffer();
const DrawingCommand* GetDrawingCommand(ptrdiff_t aId);
protected:
explicit CommandBuffer(size_t aSize = 256)
: mStorage(IterableArena::GROWABLE, aSize)
{}
IterableArena mStorage;
friend class CommandBufferBuilder;
};
/// Generates CommandBuffer objects.
///
/// The builder is a separate object to ensure that commands are not added to a
/// submitted CommandBuffer.
class CommandBufferBuilder
{
public:
void BeginCommandBuffer(size_t aBufferSize = 256);
already_AddRefed<CommandBuffer> EndCommandBuffer();
/// Build the CommandBuffer, command after command.
/// This must be used between BeginCommandBuffer and EndCommandBuffer.
template<typename T, typename... Args>
ptrdiff_t AddCommand(Args&&... aArgs)
{
static_assert(IsBaseOf<DrawingCommand, T>::value,
"T must derive from DrawingCommand");
return mCommands->mStorage.Alloc<T>(Forward<Args>(aArgs)...);
}
bool HasCommands() const { return !!mCommands; }
protected:
RefPtr<CommandBuffer> mCommands;
};
/// Stores multiple commands to be executed sequencially.
class DrawingJob : public Job {
public:
~DrawingJob();
virtual JobStatus Run() override;
protected:
DrawingJob(DrawTarget* aTarget,
IntPoint aOffset,
SyncObject* aStart,
SyncObject* aCompletion,
WorkerThread* aPinToWorker = nullptr);
/// Runs the tasks's destructors and resets the buffer.
void Clear();
std::vector<ptrdiff_t> mCommandOffsets;
RefPtr<CommandBuffer> mCommandBuffer;
uint32_t mCursor;
RefPtr<DrawTarget> mDrawTarget;
IntPoint mOffset;
friend class DrawingJobBuilder;
};
/// Generates DrawingJob objects.
///
/// The builder is a separate object to ensure that commands are not added to a
/// submitted DrawingJob.
class DrawingJobBuilder {
public:
DrawingJobBuilder();
~DrawingJobBuilder();
/// Allocates a DrawingJob.
///
/// call this method before starting to add commands.
void BeginDrawingJob(DrawTarget* aTarget, IntPoint aOffset,
SyncObject* aStart = nullptr);
/// Build the DrawingJob, command after command.
/// This must be used between BeginDrawingJob and EndDrawingJob.
void AddCommand(ptrdiff_t offset)
{
mCommandOffsets.push_back(offset);
}
/// Finalizes and returns the drawing task.
///
/// If aCompletion is not null, the sync object will be signaled after the
/// task buffer is destroyed (and after the destructor of the tasks have run).
/// In most cases this means after the completion of all tasks in the task buffer,
/// but also when the task buffer is destroyed due to an error.
DrawingJob* EndDrawingJob(CommandBuffer* aCmdBuffer,
SyncObject* aCompletion = nullptr,
WorkerThread* aPinToWorker = nullptr);
/// Returns true between BeginDrawingJob and EndDrawingJob, false otherwise.
bool HasDrawingJob() const { return !!mDrawTarget; }
protected:
std::vector<ptrdiff_t> mCommandOffsets;
RefPtr<DrawTarget> mDrawTarget;
IntPoint mOffset;
RefPtr<SyncObject> mStart;
};
} // namespace
} // namespace
#endif

193
gfx/2d/IterableArena.h Normal file
View File

@ -0,0 +1,193 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MOZILLA_GFX_ITERABLEARENA_H_
#define MOZILLA_GFX_ITERABLEARENA_H_
#include "mozilla/Move.h"
#include "mozilla/Assertions.h"
#include "mozilla/gfx/Logging.h"
#include <string.h>
#include <vector>
#include <stdint.h>
#include <stdio.h>
namespace mozilla {
namespace gfx {
/// A simple pool allocator for plain data structures.
///
/// Beware that the pool will not attempt to run the destructors. It is the
/// responsibility of the user of this class to either use objects with no
/// destructor or to manually call the allocated objects destructors.
/// If the pool is growable, its allocated objects must be safely moveable in
/// in memory (through memcpy).
class IterableArena {
protected:
struct Header
{
size_t mBlocSize;
};
public:
enum ArenaType {
FIXED_SIZE,
GROWABLE
};
IterableArena(ArenaType aType, size_t aStorageSize)
: mSize(aStorageSize)
, mCursor(0)
, mIsGrowable(aType == GROWABLE)
{
if (mSize == 0) {
mSize = 128;
}
mStorage = (uint8_t*)malloc(mSize);
if (mStorage == nullptr) {
gfxCriticalError() << "Not enough Memory allocate a memory pool of size " << aStorageSize;
MOZ_CRASH();
}
}
~IterableArena()
{
free(mStorage);
}
/// Constructs a new item in the pool and returns a positive offset in case of
/// success.
///
/// The offset never changes even if the storage is reallocated, so users
/// of this class should prefer storing offsets rather than direct pointers
/// to the allocated objects.
/// Alloc can cause the storage to be reallocated if the pool was initialized
/// with IterableArena::GROWABLE.
/// If for any reason the pool fails to allocate enough space for the new item
/// Alloc returns a negative offset and the object's constructor is not called.
template<typename T, typename... Args>
ptrdiff_t
Alloc(Args&&... aArgs)
{
void* storage = nullptr;
auto offset = AllocRaw(sizeof(T), &storage);
if (offset < 0) {
return offset;
}
new (storage) T(Forward<Args>(aArgs)...);
return offset;
}
ptrdiff_t AllocRaw(size_t aSize, void** aOutPtr = nullptr)
{
const size_t blocSize = AlignedSize(sizeof(Header) + aSize);
if (AlignedSize(mCursor + blocSize) > mSize) {
if (!mIsGrowable) {
return -1;
}
size_t newSize = mSize * 2;
while (AlignedSize(mCursor + blocSize) > newSize) {
newSize *= 2;
}
uint8_t* newStorage = (uint8_t*)realloc(mStorage, newSize);
if (!newStorage) {
gfxCriticalError() << "Not enough Memory to grow the memory pool, size: " << newSize;
return -1;
}
mStorage = newStorage;
mSize = newSize;
}
ptrdiff_t offset = mCursor;
GetHeader(offset)->mBlocSize = blocSize;
mCursor += blocSize;
if (aOutPtr) {
*aOutPtr = GetStorage(offset);
}
return offset;
}
/// Get access to an allocated item at a given offset (only use offsets returned
/// by Alloc or AllocRaw).
///
/// If the pool is growable, the returned pointer is only valid temporarily. The
/// underlying storage can be reallocated in Alloc or AllocRaw, so do not keep
/// these pointers around and store the offset instead.
void* GetStorage(ptrdiff_t offset = 0)
{
MOZ_ASSERT(offset >= 0);
MOZ_ASSERT(offset < mCursor);
return offset >= 0 ? mStorage + offset + sizeof(Header) : nullptr;
}
/// Clears the storage without running any destructor and without deallocating it.
void Clear()
{
mCursor = 0;
}
/// Iterate over the elements allocated in this pool.
///
/// Takes a lambda or function object accepting a void* as parameter.
template<typename Func>
void ForEach(Func cb)
{
Iterator it;
while (void* ptr = it.Next(this)) {
cb(ptr);
}
}
/// A simple iterator over an arena.
class Iterator {
public:
Iterator()
: mCursor(0)
{}
void* Next(IterableArena* aArena)
{
if (mCursor >= aArena->mCursor) {
return nullptr;
}
void* result = aArena->GetStorage(mCursor);
const size_t blocSize = aArena->GetHeader(mCursor)->mBlocSize;
MOZ_ASSERT(blocSize != 0);
mCursor += blocSize;
return result;
}
private:
ptrdiff_t mCursor;
};
protected:
Header* GetHeader(ptrdiff_t offset)
{
return (Header*) (mStorage + offset);
}
size_t AlignedSize(size_t aSize) const
{
const size_t alignment = sizeof(uintptr_t);
return aSize + (alignment - (aSize % alignment)) % alignment;
}
uint8_t* mStorage;
uint32_t mSize;
ptrdiff_t mCursor;
bool mIsGrowable;
friend class Iterator;
};
} // namespace
} // namespace
#endif

279
gfx/2d/JobScheduler.cpp Normal file
View File

@ -0,0 +1,279 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "JobScheduler.h"
namespace mozilla {
namespace gfx {
JobScheduler* JobScheduler::sSingleton = nullptr;
bool JobScheduler::Init(uint32_t aNumThreads, uint32_t aNumQueues)
{
MOZ_ASSERT(!sSingleton);
MOZ_ASSERT(aNumThreads >= aNumQueues);
sSingleton = new JobScheduler();
sSingleton->mNextQueue = 0;
for (uint32_t i = 0; i < aNumQueues; ++i) {
sSingleton->mDrawingQueues.push_back(new MultiThreadedJobQueue());
}
for (uint32_t i = 0; i < aNumThreads; ++i) {
sSingleton->mWorkerThreads.push_back(WorkerThread::Create(sSingleton->mDrawingQueues[i%aNumQueues]));
}
return true;
}
void JobScheduler::ShutDown()
{
MOZ_ASSERT(IsEnabled());
if (!IsEnabled()) {
return;
}
for (auto queue : sSingleton->mDrawingQueues) {
queue->ShutDown();
delete queue;
}
for (WorkerThread* thread : sSingleton->mWorkerThreads) {
// this will block until the thread is joined.
delete thread;
}
sSingleton->mWorkerThreads.clear();
delete sSingleton;
sSingleton = nullptr;
}
JobStatus
JobScheduler::ProcessJob(Job* aJob)
{
MOZ_ASSERT(aJob);
auto status = aJob->Run();
if (status == JobStatus::Error || status == JobStatus::Complete) {
delete aJob;
}
return status;
}
void
JobScheduler::SubmitJob(Job* aJob)
{
MOZ_ASSERT(aJob);
RefPtr<SyncObject> start = aJob->GetStartSync();
if (start && start->Register(aJob)) {
// The Job buffer starts with a non-signaled sync object, it
// is now registered in the list of task buffers waiting on the
// sync object, so we should not place it in the queue.
return;
}
GetQueueForJob(aJob)->SubmitJob(aJob);
}
MultiThreadedJobQueue*
JobScheduler::GetQueueForJob(Job* aJob)
{
return aJob->IsPinnedToAThread() ? aJob->GetWorkerThread()->GetJobQueue()
: GetDrawingQueue();
}
Job::Job(SyncObject* aStart, SyncObject* aCompletion, WorkerThread* aThread)
: mNextWaitingJob(nullptr)
, mStartSync(aStart)
, mCompletionSync(aCompletion)
, mPinToThread(aThread)
{
if (mStartSync) {
mStartSync->AddSubsequent(this);
}
if (mCompletionSync) {
mCompletionSync->AddPrerequisite(this);
}
}
Job::~Job()
{
if (mCompletionSync) {
//printf(" -- Job %p dtor completion %p\n", this, mCompletionSync);
mCompletionSync->Signal();
mCompletionSync = nullptr;
}
}
JobStatus
SetEventJob::Run()
{
mEvent->Set();
return JobStatus::Complete;
}
SetEventJob::SetEventJob(EventObject* aEvent,
SyncObject* aStart, SyncObject* aCompletion,
WorkerThread* aWorker)
: Job(aStart, aCompletion, aWorker)
, mEvent(aEvent)
{}
SetEventJob::~SetEventJob()
{}
SyncObject::SyncObject(uint32_t aNumPrerequisites)
: mSignals(aNumPrerequisites)
, mFirstWaitingJob(nullptr)
#ifdef DEBUG
, mNumPrerequisites(aNumPrerequisites)
, mAddedPrerequisites(0)
#endif
{}
SyncObject::~SyncObject()
{
MOZ_ASSERT(mFirstWaitingJob == nullptr);
}
bool
SyncObject::Register(Job* aJob)
{
MOZ_ASSERT(aJob);
// For now, ensure that when we schedule the first subsequent, we have already
// created all of the prerequisites. This is an arbitrary restriction because
// we specify the number of prerequisites in the constructor, but in the typical
// scenario, if the assertion FreezePrerequisite blows up here it probably means
// we got the initial nmber of prerequisites wrong. We can decide to remove
// this restriction if needed.
FreezePrerequisites();
int32_t signals = mSignals;
if (signals > 0) {
AddWaitingJob(aJob);
// Since Register and Signal can be called concurrently, it can happen that
// reading mSignals in Register happens before decrementing mSignals in Signal,
// but SubmitWaitingJobs happens before AddWaitingJob. This ordering means
// the SyncObject ends up in the signaled state with a task sitting in the
// waiting list. To prevent that we check mSignals a second time and submit
// again if signals reached zero in the mean time.
// We do this instead of holding a mutex around mSignals+mJobs to reduce
// lock contention.
int32_t signals2 = mSignals;
if (signals2 == 0) {
SubmitWaitingJobs();
}
return true;
}
return false;
}
void
SyncObject::Signal()
{
int32_t signals = --mSignals;
MOZ_ASSERT(signals >= 0);
if (signals == 0) {
SubmitWaitingJobs();
}
}
void
SyncObject::AddWaitingJob(Job* aJob)
{
// Push (using atomics) the task into the list of waiting tasks.
for (;;) {
Job* first = mFirstWaitingJob;
aJob->mNextWaitingJob = first;
if (mFirstWaitingJob.compareExchange(first, aJob)) {
break;
}
}
}
void SyncObject::SubmitWaitingJobs()
{
// Scheduling the tasks can cause code that modifies <this>'s reference
// count to run concurrently, and cause the caller of this function to
// be owned by another thread. We need to make sure the reference count
// does not reach 0 on another thread before the end of this method, so
// hold a strong ref to prevent that!
RefPtr<SyncObject> kungFuDeathGrip(this);
// First atomically swap mFirstWaitingJob and waitingJobs...
Job* waitingJobs = nullptr;
for (;;) {
waitingJobs = mFirstWaitingJob;
if (mFirstWaitingJob.compareExchange(waitingJobs, nullptr)) {
break;
}
}
// ... and submit all of the waiting tasks in waitingJob now that they belong
// to this thread.
while (waitingJobs) {
Job* next = waitingJobs->mNextWaitingJob;
waitingJobs->mNextWaitingJob = nullptr;
JobScheduler::GetQueueForJob(waitingJobs)->SubmitJob(waitingJobs);
waitingJobs = next;
}
}
bool
SyncObject::IsSignaled()
{
return mSignals == 0;
}
void
SyncObject::FreezePrerequisites()
{
MOZ_ASSERT(mAddedPrerequisites == mNumPrerequisites);
}
void
SyncObject::AddPrerequisite(Job* aJob)
{
MOZ_ASSERT(++mAddedPrerequisites <= mNumPrerequisites);
}
void
SyncObject::AddSubsequent(Job* aJob)
{
}
WorkerThread::WorkerThread(MultiThreadedJobQueue* aJobQueue)
: mQueue(aJobQueue)
{
aJobQueue->RegisterThread();
}
void
WorkerThread::Run()
{
SetName("gfx worker");
for (;;) {
Job* commands = nullptr;
if (!mQueue->WaitForJob(commands)) {
mQueue->UnregisterThread();
return;
}
JobStatus status = JobScheduler::ProcessJob(commands);
if (status == JobStatus::Error) {
// Don't try to handle errors for now, but that's open to discussions.
// I expect errors to be mostly OOM issues.
MOZ_CRASH();
}
}
}
} //namespace
} //namespace

250
gfx/2d/JobScheduler.h Normal file
View File

@ -0,0 +1,250 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MOZILLA_GFX_TASKSCHEDULER_H_
#define MOZILLA_GFX_TASKSCHEDULER_H_
#include "mozilla/RefPtr.h"
#include "mozilla/gfx/Types.h"
#ifdef WIN32
#include "mozilla/gfx/JobScheduler_win32.h"
#else
#include "mozilla/gfx/JobScheduler_posix.h"
#endif
#include <vector>
namespace mozilla {
namespace gfx {
class MultiThreadedJobQueue;
class SyncObject;
class WorkerThread;
class JobScheduler {
public:
/// Return one of the queues that the drawing worker threads pull from, chosen
/// pseudo-randomly.
static MultiThreadedJobQueue* GetDrawingQueue()
{
return sSingleton->mDrawingQueues[
sSingleton->mNextQueue++ % sSingleton->mDrawingQueues.size()
];
}
/// Return one of the queues that the drawing worker threads pull from with a
/// hash to choose the queue.
///
/// Calling this function several times with the same hash will yield the same queue.
static MultiThreadedJobQueue* GetDrawingQueue(uint32_t aHash)
{
return sSingleton->mDrawingQueues[
aHash % sSingleton->mDrawingQueues.size()
];
}
/// Return the task queue associated to the worker the task is pinned to if
/// the task is pinned to a worker, or a random queue.
static MultiThreadedJobQueue* GetQueueForJob(Job* aJob);
/// Initialize the task scheduler with aNumThreads worker threads for drawing
/// and aNumQueues task queues.
///
/// The number of threads must be superior or equal to the number of queues
/// (since for now a worker thread only pulls from one queue).
static bool Init(uint32_t aNumThreads, uint32_t aNumQueues);
/// Shut the scheduler down.
///
/// This will block until worker threads are joined and deleted.
static void ShutDown();
/// Returns true if there is a successfully initialized JobScheduler singleton.
static bool IsEnabled() { return !!sSingleton; }
/// Submit a task buffer to its associated queue.
///
/// The caller looses ownership of the task buffer.
static void SubmitJob(Job* aJobs);
/// Process commands until the command buffer needs to block on a sync object,
/// completes, yields, or encounters an error.
///
/// Can be used on any thread. Worker threads basically loop over this, but the
/// main thread can also dequeue pending task buffers and process them alongside
/// the worker threads if it is about to block until completion anyway.
///
/// The caller looses ownership of the task buffer.
static JobStatus ProcessJob(Job* aJobs);
protected:
static JobScheduler* sSingleton;
// queues of Job that are ready to be processed
std::vector<MultiThreadedJobQueue*> mDrawingQueues;
std::vector<WorkerThread*> mWorkerThreads;
Atomic<uint32_t> mNextQueue;
};
/// Jobs are not reference-counted because they don't have shared ownership.
/// The ownership of tasks can change when they are passed to certain methods
/// of JobScheduler and SyncObject. See the docuumentaion of these classes.
class Job {
public:
Job(SyncObject* aStart, SyncObject* aCompletion, WorkerThread* aThread = nullptr);
virtual ~Job();
virtual JobStatus Run() = 0;
/// For use in JobScheduler::SubmitJob. Don't use it anywhere else.
//already_AddRefed<SyncObject> GetAndResetStartSync();
SyncObject* GetStartSync() { return mStartSync; }
bool IsPinnedToAThread() const { return !!mPinToThread; }
WorkerThread* GetWorkerThread() { return mPinToThread; }
protected:
// An intrusive linked list of tasks waiting for a sync object to enter the
// signaled state. When the task is not waiting for a sync object, mNextWaitingJob
// should be null. This is only accessed from the thread that owns the task.
Job* mNextWaitingJob;
RefPtr<SyncObject> mStartSync;
RefPtr<SyncObject> mCompletionSync;
WorkerThread* mPinToThread;
friend class SyncObject;
};
class EventObject;
/// This task will set an EventObject.
///
/// Typically used as the final task, so that the main thread can block on the
/// corresponfing EventObject until all of the tasks are processed.
class SetEventJob : public Job
{
public:
explicit SetEventJob(EventObject* aEvent,
SyncObject* aStart, SyncObject* aCompletion = nullptr,
WorkerThread* aPinToWorker = nullptr);
~SetEventJob();
JobStatus Run() override;
EventObject* GetEvent() { return mEvent; }
protected:
RefPtr<EventObject> mEvent;
};
/// A synchronization object that can be used to express dependencies and ordering between
/// tasks.
///
/// Jobs can register to SyncObjects in order to asynchronously wait for a signal.
/// In practice, Job objects usually start with a sync object (startSyc) and end
/// with another one (completionSync).
/// a Job never gets processed before its startSync is in the signaled state, and
/// signals its completionSync as soon as it finishes. This is how dependencies
/// between tasks is expressed.
class SyncObject final : public external::AtomicRefCounted<SyncObject> {
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(SyncObject)
/// Create a synchronization object.
///
/// aNumPrerequisites represents the number of times the object must be signaled
/// before actually entering the signaled state (in other words, it means the
/// number of dependencies of this sync object).
///
/// Explicitly specifying the number of prerequisites when creating sync objects
/// makes it easy to start scheduling some of the prerequisite tasks while
/// creating the others, which is how we typically use the task scheduler.
/// Automatically determining the number of prerequisites using Job's constructor
/// brings the risk that the sync object enters the signaled state while we
/// are still adding prerequisites which is hard to fix without using muteces.
explicit SyncObject(uint32_t aNumPrerequisites = 1);
~SyncObject();
/// Attempt to register a task.
///
/// If the sync object is already in the signaled state, the buffer is *not*
/// registered and the sync object does not take ownership of the task.
/// If the object is not yet in the signaled state, it takes ownership of
/// the task and places it in a list of pending tasks.
/// Pending tasks will not be processed by the worker thread.
/// When the SyncObject reaches the signaled state, it places the pending
/// tasks back in the available buffer queue, so that they can be
/// scheduled again.
///
/// Returns true if the SyncOject is not already in the signaled state.
/// This means that if this method returns true, the SyncObject has taken
/// ownership of the Job.
bool Register(Job* aJob);
/// Signal the SyncObject.
///
/// This decrements an internal counter. The sync object reaches the signaled
/// state when the counter gets to zero.
void Signal();
/// Returns true if mSignals is equal to zero. In other words, returns true
/// if all prerequisite tasks have already signaled the sync object.
bool IsSignaled();
/// Asserts that the number of added prerequisites is equal to the number
/// specified in the constructor (does nothin in release builds).
void FreezePrerequisites();
private:
// Called by Job's constructor
void AddSubsequent(Job* aJob);
void AddPrerequisite(Job* aJob);
void AddWaitingJob(Job* aJob);
void SubmitWaitingJobs();
Atomic<int32_t> mSignals;
Atomic<Job*> mFirstWaitingJob;
#ifdef DEBUG
uint32_t mNumPrerequisites;
Atomic<uint32_t> mAddedPrerequisites;
#endif
friend class Job;
friend class JobScheduler;
};
/// Base class for worker threads.
class WorkerThread
{
public:
static WorkerThread* Create(MultiThreadedJobQueue* aJobQueue);
virtual ~WorkerThread() {}
void Run();
MultiThreadedJobQueue* GetJobQueue() { return mQueue; }
protected:
explicit WorkerThread(MultiThreadedJobQueue* aJobQueue);
virtual void SetName(const char* aName) {}
MultiThreadedJobQueue* mQueue;
};
} // namespace
} // namespace
#endif

View File

@ -0,0 +1,194 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "JobScheduler.h"
#include "mozilla/gfx/Logging.h"
using namespace std;
namespace mozilla {
namespace gfx {
void* ThreadCallback(void* threadData);
class WorkerThreadPosix : public WorkerThread {
public:
explicit WorkerThreadPosix(MultiThreadedJobQueue* aJobQueue)
: WorkerThread(aJobQueue)
{
pthread_create(&mThread, nullptr, ThreadCallback, static_cast<WorkerThread*>(this));
}
~WorkerThreadPosix()
{
pthread_join(mThread, nullptr);
}
virtual void SetName(const char*) override
{
// XXX - temporarily disabled, see bug 1209039
//
// // Call this from the thread itself because of Mac.
//#ifdef XP_MACOSX
// pthread_setname_np(aName);
//#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
// pthread_set_name_np(mThread, aName);
//#elif defined(__NetBSD__)
// pthread_setname_np(mThread, "%s", (void*)aName);
//#else
// pthread_setname_np(mThread, aName);
//#endif
}
protected:
pthread_t mThread;
};
void* ThreadCallback(void* threadData)
{
WorkerThread* thread = static_cast<WorkerThread*>(threadData);
thread->Run();
return nullptr;
}
WorkerThread*
WorkerThread::Create(MultiThreadedJobQueue* aJobQueue)
{
return new WorkerThreadPosix(aJobQueue);
}
MultiThreadedJobQueue::MultiThreadedJobQueue()
: mThreadsCount(0)
, mShuttingDown(false)
{}
MultiThreadedJobQueue::~MultiThreadedJobQueue()
{
MOZ_ASSERT(mJobs.empty());
}
bool
MultiThreadedJobQueue::WaitForJob(Job*& aOutJob)
{
return PopJob(aOutJob, BLOCKING);
}
bool
MultiThreadedJobQueue::PopJob(Job*& aOutJobs, AccessType aAccess)
{
for (;;) {
MutexAutoLock lock(&mMutex);
while (aAccess == BLOCKING && !mShuttingDown && mJobs.empty()) {
mAvailableCondvar.Wait(&mMutex);
}
if (mShuttingDown) {
return false;
}
if (mJobs.empty()) {
if (aAccess == NON_BLOCKING) {
return false;
}
continue;
}
Job* task = mJobs.front();
MOZ_ASSERT(task);
mJobs.pop_front();
aOutJobs = task;
return true;
}
}
void
MultiThreadedJobQueue::SubmitJob(Job* aJobs)
{
MOZ_ASSERT(aJobs);
MutexAutoLock lock(&mMutex);
mJobs.push_back(aJobs);
mAvailableCondvar.Broadcast();
}
size_t
MultiThreadedJobQueue::NumJobs()
{
MutexAutoLock lock(&mMutex);
return mJobs.size();
}
bool
MultiThreadedJobQueue::IsEmpty()
{
MutexAutoLock lock(&mMutex);
return mJobs.empty();
}
void
MultiThreadedJobQueue::ShutDown()
{
MutexAutoLock lock(&mMutex);
mShuttingDown = true;
while (mThreadsCount) {
mAvailableCondvar.Broadcast();
mShutdownCondvar.Wait(&mMutex);
}
}
void
MultiThreadedJobQueue::RegisterThread()
{
mThreadsCount += 1;
}
void
MultiThreadedJobQueue::UnregisterThread()
{
MutexAutoLock lock(&mMutex);
mThreadsCount -= 1;
if (mThreadsCount == 0) {
mShutdownCondvar.Broadcast();
}
}
EventObject::EventObject()
: mIsSet(false)
{}
EventObject::~EventObject()
{}
bool
EventObject::Peak()
{
MutexAutoLock lock(&mMutex);
return mIsSet;
}
void
EventObject::Set()
{
MutexAutoLock lock(&mMutex);
if (!mIsSet) {
mIsSet = true;
mCond.Broadcast();
}
}
void
EventObject::Wait()
{
MutexAutoLock lock(&mMutex);
if (mIsSet) {
return;
}
mCond.Wait(&mMutex);
}
} // namespce
} // namespce

144
gfx/2d/JobScheduler_posix.h Normal file
View File

@ -0,0 +1,144 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef WIN32
#ifndef MOZILLA_GFX_TASKSCHEDULER_POSIX_H_
#define MOZILLA_GFX_TASKSCHEDULER_POSIX_H_
#include <string>
#include <vector>
#include <list>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include "mozilla/RefPtr.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/gfx/CriticalSection.h"
namespace mozilla {
namespace gfx {
class Job;
class PosixCondVar;
class WorkerThread;
typedef mozilla::gfx::CriticalSection Mutex;
typedef mozilla::gfx::CriticalSectionAutoEnter MutexAutoLock;
// posix platforms only!
class PosixCondVar {
public:
PosixCondVar() {
DebugOnly<int> err = pthread_cond_init(&mCond, nullptr);
MOZ_ASSERT(!err);
}
~PosixCondVar() {
DebugOnly<int> err = pthread_cond_destroy(&mCond);
MOZ_ASSERT(!err);
}
void Wait(Mutex* aMutex) {
DebugOnly<int> err = pthread_cond_wait(&mCond, &aMutex->mMutex);
MOZ_ASSERT(!err);
}
void Broadcast() {
DebugOnly<int> err = pthread_cond_broadcast(&mCond);
MOZ_ASSERT(!err);
}
protected:
pthread_cond_t mCond;
};
/// A simple and naive multithreaded task queue
///
/// The public interface of this class must remain identical to its equivalent
/// in JobScheduler_win32.h
class MultiThreadedJobQueue {
public:
enum AccessType {
BLOCKING,
NON_BLOCKING
};
// Producer thread
MultiThreadedJobQueue();
// Producer thread
~MultiThreadedJobQueue();
// Worker threads
bool WaitForJob(Job*& aOutJob);
// Any thread
bool PopJob(Job*& aOutJob, AccessType aAccess);
// Any threads
void SubmitJob(Job* aJob);
// Producer thread
void ShutDown();
// Any thread
size_t NumJobs();
// Any thread
bool IsEmpty();
// Producer thread
void RegisterThread();
// Worker threads
void UnregisterThread();
protected:
std::list<Job*> mJobs;
Mutex mMutex;
PosixCondVar mAvailableCondvar;
PosixCondVar mShutdownCondvar;
int32_t mThreadsCount;
bool mShuttingDown;
friend class WorkerThread;
};
/// An object that a thread can synchronously wait on.
/// Usually set by a SetEventJob.
class EventObject : public external::AtomicRefCounted<EventObject>
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(EventObject)
EventObject();
~EventObject();
/// Synchronously wait until the event is set.
void Wait();
/// Return true if the event is set, without blocking.
bool Peak();
/// Set the event.
void Set();
protected:
Mutex mMutex;
PosixCondVar mCond;
bool mIsSet;
};
} // namespace
} // namespace
#include "JobScheduler.h"
#endif
#endif

View File

@ -0,0 +1,143 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "JobScheduler.h"
#include "mozilla/gfx/Logging.h"
using namespace std;
namespace mozilla {
namespace gfx {
DWORD __stdcall ThreadCallback(void* threadData);
class WorkerThreadWin32 : public WorkerThread {
public:
explicit WorkerThreadWin32(MultiThreadedJobQueue* aJobQueue)
: WorkerThread(aJobQueue)
{
mThread = ::CreateThread(nullptr, 0, ThreadCallback, static_cast<WorkerThread*>(this), 0, nullptr);
}
~WorkerThreadWin32()
{
::WaitForSingleObject(mThread, INFINITE);
::CloseHandle(mThread);
}
protected:
HANDLE mThread;
};
DWORD __stdcall ThreadCallback(void* threadData)
{
WorkerThread* thread = static_cast<WorkerThread*>(threadData);
thread->Run();
return 0;
}
WorkerThread*
WorkerThread::Create(MultiThreadedJobQueue* aJobQueue)
{
return new WorkerThreadWin32(aJobQueue);
}
bool
MultiThreadedJobQueue::PopJob(Job*& aOutJob, AccessType aAccess)
{
for (;;) {
while (aAccess == BLOCKING && mJobs.empty()) {
{
CriticalSectionAutoEnter lock(&mSection);
if (mShuttingDown) {
return false;
}
}
HANDLE handles[] = { mAvailableEvent, mShutdownEvent };
::WaitForMultipleObjects(2, handles, FALSE, INFINITE);
}
CriticalSectionAutoEnter lock(&mSection);
if (mShuttingDown) {
return false;
}
if (mJobs.empty()) {
if (aAccess == NON_BLOCKING) {
return false;
}
continue;
}
Job* task = mJobs.front();
MOZ_ASSERT(task);
mJobs.pop_front();
if (mJobs.empty()) {
::ResetEvent(mAvailableEvent);
}
aOutJob = task;
return true;
}
}
void
MultiThreadedJobQueue::SubmitJob(Job* aJob)
{
MOZ_ASSERT(aJob);
CriticalSectionAutoEnter lock(&mSection);
mJobs.push_back(aJob);
::SetEvent(mAvailableEvent);
}
void
MultiThreadedJobQueue::ShutDown()
{
{
CriticalSectionAutoEnter lock(&mSection);
mShuttingDown = true;
}
while (mThreadsCount) {
::SetEvent(mAvailableEvent);
::WaitForSingleObject(mShutdownEvent, INFINITE);
}
}
size_t
MultiThreadedJobQueue::NumJobs()
{
CriticalSectionAutoEnter lock(&mSection);
return mJobs.size();
}
bool
MultiThreadedJobQueue::IsEmpty()
{
CriticalSectionAutoEnter lock(&mSection);
return mJobs.empty();
}
void
MultiThreadedJobQueue::RegisterThread()
{
mThreadsCount += 1;
}
void
MultiThreadedJobQueue::UnregisterThread()
{
CriticalSectionAutoEnter lock(&mSection);
mThreadsCount -= 1;
if (mThreadsCount == 0) {
::SetEvent(mShutdownEvent);
}
}
} // namespace
} // namespace

View File

@ -0,0 +1,98 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef WIN32
#ifndef MOZILLA_GFX_TASKSCHEDULER_WIN32_H_
#define MOZILLA_GFX_TASKSCHEDULER_WIN32_H_
#include <windows.h>
#include <list>
#include "mozilla/RefPtr.h"
#include "mozilla/gfx/CriticalSection.h"
namespace mozilla {
namespace gfx {
class WorkerThread;
class Job;
// The public interface of this class must remain identical to its equivalent
// in JobScheduler_posix.h
class MultiThreadedJobQueue {
public:
enum AccessType {
BLOCKING,
NON_BLOCKING
};
MultiThreadedJobQueue()
: mThreadsCount(0)
, mShuttingDown(false)
{
mAvailableEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr);
mShutdownEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr);
}
~MultiThreadedJobQueue()
{
::CloseHandle(mAvailableEvent);
::CloseHandle(mShutdownEvent);
}
bool WaitForJob(Job*& aOutJob) { return PopJob(aOutJob, BLOCKING); }
bool PopJob(Job*& aOutJob, AccessType aAccess);
void SubmitJob(Job* aJob);
void ShutDown();
size_t NumJobs();
bool IsEmpty();
void RegisterThread();
void UnregisterThread();
protected:
std::list<Job*> mJobs;
CriticalSection mSection;
HANDLE mAvailableEvent;
HANDLE mShutdownEvent;
int32_t mThreadsCount;
bool mShuttingDown;
friend class WorkerThread;
};
// The public interface of this class must remain identical to its equivalent
// in JobScheduler_posix.h
class EventObject : public external::AtomicRefCounted<EventObject>
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(EventObject)
EventObject() { mEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr); }
~EventObject() { ::CloseHandle(mEvent); }
void Wait() { ::WaitForSingleObject(mEvent, INFINITE); }
bool Peak() { return ::WaitForSingleObject(mEvent, 0) == WAIT_OBJECT_0; }
void Set() { ::SetEvent(mEvent); }
protected:
// TODO: it's expensive to create events so we should try to reuse them
HANDLE mEvent;
};
} // namespace
} // namespace
#endif
#endif

View File

@ -289,6 +289,13 @@ struct GradientStop
Color color;
};
enum class JobStatus {
Complete,
Wait,
Yield,
Error
};
} // namespace gfx
} // namespace mozilla

View File

@ -20,11 +20,16 @@ EXPORTS.mozilla.gfx += [
'Blur.h',
'BorrowedContext.h',
'Coord.h',
'CriticalSection.h',
'DataSurfaceHelpers.h',
'DrawTargetTiled.h',
'Filters.h',
'Helpers.h',
'HelpersCairo.h',
'IterableArena.h',
'JobScheduler.h',
'JobScheduler_posix.h',
'JobScheduler_win32.h',
'Logging.h',
'Matrix.h',
'NumericTools.h',
@ -60,6 +65,7 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
'DrawTargetD2D1.cpp',
'ExtendInputEffectD2D1.cpp',
'FilterNodeD2D1.cpp',
'JobScheduler_win32.cpp',
'PathD2D.cpp',
'RadialGradientEffectD2D1.cpp',
'ScaledFontDWrite.cpp',
@ -70,6 +76,11 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
]
DEFINES['WIN32'] = True
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'windows':
SOURCES += [
'JobScheduler_posix.cpp',
]
if CONFIG['MOZ_ENABLE_SKIA']:
UNIFIED_SOURCES += [
'convolver.cpp',
@ -118,6 +129,7 @@ UNIFIED_SOURCES += [
'DataSourceSurface.cpp',
'DataSurfaceHelpers.cpp',
'DrawEventRecorder.cpp',
'DrawingJob.cpp',
'DrawTarget.cpp',
'DrawTargetCairo.cpp',
'DrawTargetCapture.cpp',
@ -129,6 +141,7 @@ UNIFIED_SOURCES += [
'FilterProcessing.cpp',
'FilterProcessingScalar.cpp',
'ImageScaling.cpp',
'JobScheduler.cpp',
'Matrix.cpp',
'Path.cpp',
'PathCairo.cpp',

View File

@ -7,16 +7,21 @@
#define GFX_LAYERSTYPES_H
#include <stdint.h> // for uint32_t
#include "mozilla/gfx/Point.h" // for IntPoint
#include "nsRegion.h"
#include "mozilla/TypedEnumBits.h"
#ifdef MOZ_WIDGET_GONK
#include <utils/RefBase.h>
#if ANDROID_VERSION >= 21
#include <utils/NativeHandle.h>
#endif
#endif
#include "mozilla/gfx/Point.h" // for IntPoint
#include "mozilla/TypedEnumBits.h"
#include "nsRegion.h"
#include <stdio.h> // FILE
#include "mozilla/Logging.h" // for PR_LOG
#ifndef MOZ_LAYERS_HAVE_LOG
# define MOZ_LAYERS_HAVE_LOG
#endif
@ -110,6 +115,14 @@ struct LayerRenderState {
void SetOverlayId(const int32_t& aId)
{ mOverlayId = aId; }
android::GraphicBuffer* GetGrallocBuffer() const
{ return mSurface.get(); }
#if ANDROID_VERSION >= 21
android::NativeHandle* GetSidebandStream() const
{ return mSidebandStream.get(); }
#endif
#endif
void SetOffset(const nsIntPoint& aOffset)
@ -133,6 +146,9 @@ struct LayerRenderState {
// size of mSurface
gfx::IntSize mSize;
TextureHost* mTexture;
#if ANDROID_VERSION >= 21
android::sp<android::NativeHandle> mSidebandStream;
#endif
#endif
};

View File

@ -417,13 +417,11 @@ ClientTiledPaintedLayer::RenderLayer()
}
if (!mContentClient) {
#if defined(MOZ_B2G) || defined(XP_MACOSX)
if (mCreationHint == LayerManager::NONE &&
SingleTiledContentClient::ClientSupportsLayerSize(layerSize, ClientManager())) {
SingleTiledContentClient::ClientSupportsLayerSize(layerSize, ClientManager()) &&
gfxPrefs::LayersSingleTileEnabled()) {
mContentClient = new SingleTiledContentClient(this, ClientManager());
} else
#endif
{
} else {
mContentClient = new MultiTiledContentClient(this, ClientManager());
}
@ -567,7 +565,6 @@ ClientTiledPaintedLayer::RenderLayer()
bool
ClientTiledPaintedLayer::IsOptimizedFor(LayerManager::PaintedLayerCreationHint aHint)
{
#if defined(MOZ_B2G) || defined(XP_MACOSX)
// The only creation hint is whether the layer is scrollable or not, and this
// is only respected on B2G and OSX, where it's used to determine whether to
// use a tiled content client or not.
@ -575,9 +572,6 @@ ClientTiledPaintedLayer::IsOptimizedFor(LayerManager::PaintedLayerCreationHint a
// large, scrollable layers, so we want the layer to be recreated in this
// situation.
return aHint == GetCreationHint();
#else
return true;
#endif
}
void

View File

@ -0,0 +1,188 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "mozilla/gfx/IterableArena.h"
#include <string>
using namespace mozilla;
using namespace mozilla::gfx;
#ifdef A
#undef A
#endif
#ifdef B
#undef B
#endif
// to avoid having symbols that collide easily like A and B in the global namespace
namespace test_arena {
class A;
class B;
class Base {
public:
virtual ~Base() {}
virtual A* AsA() { return nullptr; }
virtual B* AsB() { return nullptr; }
};
static int sDtorItemA = 0;
static int sDtorItemB = 0;
class A : public Base {
public:
virtual A* AsA() override { return this; }
explicit A(uint64_t val) : mVal(val) {}
~A() { ++sDtorItemA; }
uint64_t mVal;
};
class B : public Base {
public:
virtual B* AsB() override { return this; }
explicit B(const string& str) : mVal(str) {}
~B() { ++sDtorItemB; }
std::string mVal;
};
struct BigStruct {
uint64_t mVal;
uint8_t data[120];
explicit BigStruct(uint64_t val) : mVal(val) {}
};
void TestArenaAlloc(IterableArena::ArenaType aType)
{
sDtorItemA = 0;
sDtorItemB = 0;
IterableArena arena(aType, 256);
// An empty arena has no items to iterate over.
{
int iterations = 0;
arena.ForEach([&](void* item){
iterations++;
});
ASSERT_EQ(iterations, 0);
}
auto a1 = arena.Alloc<A>(42);
auto b1 = arena.Alloc<B>("Obladi oblada");
auto a2 = arena.Alloc<A>(1337);
auto b2 = arena.Alloc<B>("Yellow submarine");
auto b3 = arena.Alloc<B>("She's got a ticket to ride");
// Alloc returns a non-negative offset if the allocation succeeded.
ASSERT_TRUE(a1 >= 0);
ASSERT_TRUE(a2 >= 0);
ASSERT_TRUE(b1 >= 0);
ASSERT_TRUE(b2 >= 0);
ASSERT_TRUE(b3 >= 0);
ASSERT_TRUE(arena.GetStorage(a1) != nullptr);
ASSERT_TRUE(arena.GetStorage(a2) != nullptr);
ASSERT_TRUE(arena.GetStorage(b1) != nullptr);
ASSERT_TRUE(arena.GetStorage(b2) != nullptr);
ASSERT_TRUE(arena.GetStorage(b3) != nullptr);
ASSERT_TRUE(((Base*)arena.GetStorage(a1))->AsA() != nullptr);
ASSERT_TRUE(((Base*)arena.GetStorage(a2))->AsA() != nullptr);
ASSERT_TRUE(((Base*)arena.GetStorage(b1))->AsB() != nullptr);
ASSERT_TRUE(((Base*)arena.GetStorage(b2))->AsB() != nullptr);
ASSERT_TRUE(((Base*)arena.GetStorage(b3))->AsB() != nullptr);
ASSERT_EQ(((Base*)arena.GetStorage(a1))->AsA()->mVal, (uint64_t)42);
ASSERT_EQ(((Base*)arena.GetStorage(a2))->AsA()->mVal, (uint64_t)1337);
ASSERT_EQ(((Base*)arena.GetStorage(b1))->AsB()->mVal, std::string("Obladi oblada"));
ASSERT_EQ(((Base*)arena.GetStorage(b2))->AsB()->mVal, std::string("Yellow submarine"));
ASSERT_EQ(((Base*)arena.GetStorage(b3))->AsB()->mVal, std::string("She's got a ticket to ride"));
{
int iterations = 0;
arena.ForEach([&](void* item){
iterations++;
});
ASSERT_EQ(iterations, 5);
}
// Typically, running the destructors of the elements in the arena will is done
// manually like this:
arena.ForEach([](void* item){
((Base*)item)->~Base();
});
arena.Clear();
ASSERT_EQ(sDtorItemA, 2);
ASSERT_EQ(sDtorItemB, 3);
// An empty arena has no items to iterate over (we just cleared it).
{
int iterations = 0;
arena.ForEach([&](void* item){
iterations++;
});
ASSERT_EQ(iterations, 0);
}
}
void TestArenaLimit(IterableArena::ArenaType aType, bool aShouldReachLimit)
{
IterableArena arena(aType, 128);
// A non-growable arena should return a negative offset when running out
// of space, without crashing.
// We should not run out of space with a growable arena (unless the os is
// running out of memory but this isn't expected for this test).
bool reachedLimit = false;
for (int i = 0; i < 100; ++i) {
auto offset = arena.Alloc<A>(42);
if (offset < 0) {
reachedLimit = true;
break;
}
}
ASSERT_EQ(reachedLimit, aShouldReachLimit);
}
} // namespace test_arena
using namespace test_arena;
TEST(Moz2D, FixedArena) {
TestArenaAlloc(IterableArena::FIXED_SIZE);
TestArenaLimit(IterableArena::FIXED_SIZE, true);
}
TEST(Moz2D, GrowableArena) {
TestArenaAlloc(IterableArena::GROWABLE);
TestArenaLimit(IterableArena::GROWABLE, false);
IterableArena arena(IterableArena::GROWABLE, 16);
// sizeof(BigStruct) is more than twice the initial capacity, make sure that
// this doesn't blow everything up, since the arena doubles its storage size each
// time it grows (until it finds a size that fits).
auto a = arena.Alloc<BigStruct>(1);
auto b = arena.Alloc<BigStruct>(2);
auto c = arena.Alloc<BigStruct>(3);
// Offsets should also still point to the appropriate values after reallocation.
ASSERT_EQ(((BigStruct*)arena.GetStorage(a))->mVal, (uint64_t)1);
ASSERT_EQ(((BigStruct*)arena.GetStorage(b))->mVal, (uint64_t)2);
ASSERT_EQ(((BigStruct*)arena.GetStorage(c))->mVal, (uint64_t)3);
arena.Clear();
}

View File

@ -0,0 +1,247 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "mozilla/gfx/JobScheduler.h"
#ifndef WIN32
#include <pthread.h>
#include <sched.h>
#endif
#include <stdlib.h>
#include <time.h>
namespace test_scheduler {
using namespace mozilla::gfx;
using namespace mozilla;
// Artificially cause threads to yield randomly in an attempt to make racy
// things more apparent (if any).
void MaybeYieldThread()
{
#ifndef WIN32
if (rand() % 5 == 0) {
sched_yield();
}
#endif
}
/// Used by the TestCommand to check that tasks are processed in the right order.
struct SanityChecker {
std::vector<uint64_t> mAdvancements;
mozilla::gfx::CriticalSection mSection;
explicit SanityChecker(uint64_t aNumCmdBuffers)
{
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
mAdvancements.push_back(0);
}
}
virtual void Check(uint64_t aJobId, uint64_t aCmdId)
{
MaybeYieldThread();
CriticalSectionAutoEnter lock(&mSection);
ASSERT_EQ(mAdvancements[aJobId], aCmdId-1);
mAdvancements[aJobId] = aCmdId;
}
};
/// Run checks that are specific to TestSchulerJoin.
struct JoinTestSanityCheck : public SanityChecker {
bool mSpecialJobHasRun;
explicit JoinTestSanityCheck(uint64_t aNumCmdBuffers)
: SanityChecker(aNumCmdBuffers)
, mSpecialJobHasRun(false)
{}
virtual void Check(uint64_t aJobId, uint64_t aCmdId) override
{
// Job 0 is the special task executed when everything is joined after task 1
if (aCmdId == 0) {
ASSERT_FALSE(mSpecialJobHasRun);
mSpecialJobHasRun = true;
for (auto advancement : mAdvancements) {
// Because of the synchronization point (beforeFilter), all
// task buffers should have run task 1 when task 0 is run.
ASSERT_EQ(advancement, (uint32_t)1);
}
} else {
// This check does not apply to task 0.
SanityChecker::Check(aJobId, aCmdId);
}
if (aCmdId == 2) {
ASSERT_TRUE(mSpecialJobHasRun);
}
}
};
class TestJob : public Job
{
public:
TestJob(uint64_t aCmdId, uint64_t aJobId, SanityChecker* aChecker,
SyncObject* aStart, SyncObject* aCompletion)
: Job(aStart, aCompletion, nullptr)
, mCmdId(aCmdId)
, mCmdBufferId(aJobId)
, mSanityChecker(aChecker)
{}
JobStatus Run()
{
MaybeYieldThread();
mSanityChecker->Check(mCmdBufferId, mCmdId);
MaybeYieldThread();
return JobStatus::Complete;
}
uint64_t mCmdId;
uint64_t mCmdBufferId;
SanityChecker* mSanityChecker;
};
/// This test creates aNumCmdBuffers task buffers with sync objects set up
/// so that all tasks will join after command 5 before a task buffer runs
/// a special task (task 0) after which all task buffers fork again.
/// This simulates the kind of scenario where all tiles must join at
/// a certain point to execute, say, a filter, and fork again after the filter
/// has been processed.
/// The main thread is only blocked when waiting for the completion of the entire
/// task stream (it doesn't have to wait at the filter's sync points to orchestrate it).
void TestSchedulerJoin(uint32_t aNumThreads, uint32_t aNumCmdBuffers)
{
JoinTestSanityCheck check(aNumCmdBuffers);
RefPtr<SyncObject> beforeFilter = new SyncObject(aNumCmdBuffers);
RefPtr<SyncObject> afterFilter = new SyncObject();
RefPtr<SyncObject> completion = new SyncObject(aNumCmdBuffers);
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
Job* t1 = new TestJob(1, i, &check, nullptr, beforeFilter);
JobScheduler::SubmitJob(t1);
MaybeYieldThread();
}
beforeFilter->FreezePrerequisites();
// This task buffer is executed when all other tasks have joined after task 1
JobScheduler::SubmitJob(
new TestJob(0, 0, &check, beforeFilter, afterFilter)
);
afterFilter->FreezePrerequisites();
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
Job* t2 = new TestJob(2, i, &check, afterFilter, completion);
JobScheduler::SubmitJob(t2);
MaybeYieldThread();
}
completion->FreezePrerequisites();
RefPtr<EventObject> waitForCompletion = new EventObject();
auto evtJob = new SetEventJob(waitForCompletion, completion);
JobScheduler::SubmitJob(evtJob);
MaybeYieldThread();
waitForCompletion->Wait();
MaybeYieldThread();
for (auto advancement : check.mAdvancements) {
ASSERT_TRUE(advancement == 2);
}
}
/// This test creates several chains of 10 task, tasks of a given chain are executed
/// sequentially, and chains are exectuted in parallel.
/// This simulates the typical scenario where we want to process sequences of drawing
/// commands for several tiles in parallel.
void TestSchedulerChain(uint32_t aNumThreads, uint32_t aNumCmdBuffers)
{
SanityChecker check(aNumCmdBuffers);
RefPtr<SyncObject> completion = new SyncObject(aNumCmdBuffers);
uint32_t numJobs = 10;
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
std::vector<RefPtr<SyncObject>> syncs;
std::vector<Job*> tasks;
syncs.reserve(numJobs);
tasks.reserve(numJobs);
for (uint32_t t = 0; t < numJobs-1; ++t) {
syncs.push_back(new SyncObject());
tasks.push_back(new TestJob(t+1, i, &check, t == 0 ? nullptr
: syncs[t-1].get(),
syncs[t]));
syncs.back()->FreezePrerequisites();
}
tasks.push_back(new TestJob(numJobs, i, &check, syncs.back(), completion));
if (i % 2 == 0) {
// submit half of the tasks in order
for (Job* task : tasks) {
JobScheduler::SubmitJob(task);
MaybeYieldThread();
}
} else {
// ... and submit the other half in reverse order
for (int32_t reverse = numJobs-1; reverse >= 0; --reverse) {
JobScheduler::SubmitJob(tasks[reverse]);
MaybeYieldThread();
}
}
}
completion->FreezePrerequisites();
RefPtr<EventObject> waitForCompletion = new EventObject();
auto evtJob = new SetEventJob(waitForCompletion, completion);
JobScheduler::SubmitJob(evtJob);
MaybeYieldThread();
waitForCompletion->Wait();
for (auto advancement : check.mAdvancements) {
ASSERT_TRUE(advancement == numJobs);
}
}
} // namespace test_scheduler
TEST(Moz2D, JobScheduler_Join) {
srand(time(nullptr));
for (uint32_t threads = 1; threads < 8; ++threads) {
for (uint32_t queues = 1; queues < threads; ++queues) {
for (uint32_t buffers = 1; buffers < 100; buffers += 3) {
mozilla::gfx::JobScheduler::Init(threads, queues);
test_scheduler::TestSchedulerJoin(threads, buffers);
mozilla::gfx::JobScheduler::ShutDown();
}
}
}
}
TEST(Moz2D, JobScheduler_Chain) {
srand(time(nullptr));
for (uint32_t threads = 1; threads < 8; ++threads) {
for (uint32_t queues = 1; queues < threads; ++queues) {
for (uint32_t buffers = 1; buffers < 100; buffers += 3) {
mozilla::gfx::JobScheduler::Init(threads, queues);
test_scheduler::TestSchedulerChain(threads, buffers);
mozilla::gfx::JobScheduler::ShutDown();
}
}
}
}

View File

@ -8,11 +8,13 @@ UNIFIED_SOURCES += [
'gfxSurfaceRefCountTest.cpp',
# Disabled on suspicion of causing bug 904227
#'gfxWordCacheTest.cpp',
'TestArena.cpp',
'TestBufferRotation.cpp',
'TestColorNames.cpp',
'TestCompositor.cpp',
'TestGfxPrefs.cpp',
'TestGfxWidgets.cpp',
'TestJobScheduler.cpp',
'TestLayers.cpp',
'TestMoz2D.cpp',
'TestQcms.cpp',

View File

@ -4,5 +4,5 @@ fuzzy-if(winWidget,175,443) == 611498-1.html 611498-ref.html
skip-if(B2G) fuzzy-if(Android&&AndroidVersion>=15,8,1000) == 709477-1.html 709477-1-ref.html # bug 773482
skip-if(!asyncPan) == 1086723.html 1086723-ref.html
== 853889-1.html 853889-1-ref.html
== 1143303-1.svg pass.svg
fuzzy(100,30) == 1149923.html 1149923-ref.html # use fuzzy due to few distorted pixels caused by border-radius
skip-if(Android) == 1143303-1.svg pass.svg
fuzzy(100,30) == 1149923.html 1149923-ref.html # use fuzzy due to few distorted pixels caused by border-radius

View File

@ -100,13 +100,7 @@ gfxAndroidPlatform::gfxAndroidPlatform()
RegisterStrongMemoryReporter(new FreetypeReporter());
nsCOMPtr<nsIScreenManager> screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
nsCOMPtr<nsIScreen> screen;
screenMgr->GetPrimaryScreen(getter_AddRefs(screen));
mScreenDepth = 24;
screen->GetColorDepth(&mScreenDepth);
mOffscreenFormat = mScreenDepth == 16
mOffscreenFormat = GetScreenDepth() == 16
? gfxImageFormat::RGB16_565
: gfxImageFormat::RGB24;
@ -418,12 +412,6 @@ gfxAndroidPlatform::RequiresLinearZoom()
return gfxPlatform::RequiresLinearZoom();
}
int
gfxAndroidPlatform::GetScreenDepth() const
{
return mScreenDepth;
}
bool
gfxAndroidPlatform::UseAcceleratedSkiaCanvas()
{

View File

@ -80,8 +80,6 @@ public:
FT_Library GetFTLibrary();
virtual int GetScreenDepth() const;
virtual bool CanRenderContentToDataSurface() const override {
return true;
}

View File

@ -52,6 +52,7 @@
#include "gfxGraphiteShaper.h"
#include "gfx2DGlue.h"
#include "gfxGradientCache.h"
#include "gfxUtils.h" // for NextPowerOfTwo
#include "nsUnicodeRange.h"
#include "nsServiceManagerUtils.h"
@ -394,6 +395,7 @@ gfxPlatform::gfxPlatform()
, mAzureCanvasBackendCollector(this, &gfxPlatform::GetAzureBackendInfo)
, mApzSupportCollector(this, &gfxPlatform::GetApzSupportInfo)
, mCompositorBackend(layers::LayersBackend::LAYERS_NONE)
, mScreenDepth(0)
{
mAllowDownloadableFonts = UNINITIALIZED_VALUE;
mFallbackUsesCmaps = UNINITIALIZED_VALUE;
@ -514,6 +516,7 @@ gfxPlatform::Init()
InitLayersAccelerationPrefs();
InitLayersIPC();
gPlatform->PopulateScreenInfo();
gPlatform->ComputeTileSize();
nsresult rv;
@ -1024,45 +1027,49 @@ gfxPlatform::ComputeTileSize()
int32_t w = gfxPrefs::LayersTileWidth();
int32_t h = gfxPrefs::LayersTileHeight();
// TODO We may want to take the screen size into consideration here.
if (gfxPrefs::LayersTilesAdjust()) {
gfx::IntSize screenSize = GetScreenSize();
if (screenSize.width > 0) {
// FIXME: we should probably make sure this is within the max texture size,
// but I think everything should at least support 1024
w = h = std::max(std::min(NextPowerOfTwo(screenSize.width) / 2, 1024), 256);
}
#ifdef MOZ_WIDGET_GONK
int32_t format = android::PIXEL_FORMAT_RGBA_8888;
android::sp<android::GraphicBuffer> alloc =
new android::GraphicBuffer(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight(),
format,
android::GraphicBuffer::USAGE_SW_READ_OFTEN |
android::GraphicBuffer::USAGE_SW_WRITE_OFTEN |
android::GraphicBuffer::USAGE_HW_TEXTURE);
new android::GraphicBuffer(w, h, android::PIXEL_FORMAT_RGBA_8888,
android::GraphicBuffer::USAGE_SW_READ_OFTEN |
android::GraphicBuffer::USAGE_SW_WRITE_OFTEN |
android::GraphicBuffer::USAGE_HW_TEXTURE);
if (alloc.get()) {
w = alloc->getStride(); // We want the tiles to be gralloc stride aligned.
// No need to adjust the height here.
}
#endif
}
#ifdef XP_MACOSX
// Use double sized tiles for HiDPI screens.
nsCOMPtr<nsIScreenManager> screenManager =
do_GetService("@mozilla.org/gfx/screenmanager;1");
if (screenManager) {
nsCOMPtr<nsIScreen> primaryScreen;
screenManager->GetPrimaryScreen(getter_AddRefs(primaryScreen));
double scaleFactor = 1.0;
if (primaryScreen) {
primaryScreen->GetContentsScaleFactor(&scaleFactor);
}
if (scaleFactor > 1.0) {
w *= 2;
h *= 2;
}
}
#endif
SetTileSize(w, h);
}
void
gfxPlatform::PopulateScreenInfo()
{
nsCOMPtr<nsIScreenManager> manager = do_GetService("@mozilla.org/gfx/screenmanager;1");
MOZ_ASSERT(manager, "failed to get nsIScreenManager");
nsCOMPtr<nsIScreen> screen;
manager->GetPrimaryScreen(getter_AddRefs(screen));
if (!screen) {
// This can happen in xpcshell, for instance
return;
}
screen->GetColorDepth(&mScreenDepth);
int left, top;
screen->GetRect(&left, &top, &mScreenSize.width, &mScreenSize.height);
}
bool
gfxPlatform::SupportsAzureContentForDrawTarget(DrawTarget* aTarget)
{
@ -2093,13 +2100,6 @@ gfxPlatform::GetLog(eGfxLog aWhichLog)
return nullptr;
}
int
gfxPlatform::GetScreenDepth() const
{
NS_WARNING("GetScreenDepth not implemented on this platform -- returning 0!");
return 0;
}
mozilla::gfx::SurfaceFormat
gfxPlatform::Optimal2DFormatForContent(gfxContentType aContent)
{

View File

@ -568,7 +568,8 @@ public:
*/
static PRLogModuleInfo* GetLog(eGfxLog aWhichLog);
virtual int GetScreenDepth() const;
int GetScreenDepth() const { return mScreenDepth; }
mozilla::gfx::IntSize GetScreenSize() const { return mScreenSize; }
/**
* Return the layer debugging options to use browser-wide.
@ -777,6 +778,11 @@ private:
*/
void ComputeTileSize();
/**
* This uses nsIScreenManager to determine the screen size and color depth
*/
void PopulateScreenInfo();
nsRefPtr<gfxASurface> mScreenReferenceSurface;
nsTArray<uint32_t> mCJKPrefLangs;
nsCOMPtr<nsIObserver> mSRGBOverrideObserver;
@ -804,6 +810,9 @@ private:
// Backend that we are compositing with. NONE, if no compositor has been
// created yet.
mozilla::layers::LayersBackend mCompositorBackend;
int32_t mScreenDepth;
mozilla::gfx::IntSize mScreenSize;
};
#endif /* GFX_PLATFORM_H */

View File

@ -363,24 +363,6 @@ gfxPlatformGtk::GetOffscreenFormat()
return gfxImageFormat::RGB24;
}
static int sDepth = 0;
int
gfxPlatformGtk::GetScreenDepth() const
{
if (!sDepth) {
GdkScreen *screen = gdk_screen_get_default();
if (screen) {
sDepth = gdk_visual_get_depth(gdk_visual_get_system());
} else {
sDepth = 24;
}
}
return sDepth;
}
void
gfxPlatformGtk::GetPlatformCMSOutputProfile(void *&mem, size_t &size)
{

View File

@ -122,8 +122,6 @@ public:
virtual gfxImageFormat GetOffscreenFormat() override;
virtual int GetScreenDepth() const override;
bool SupportsApzWheelInput() const override {
return true;
}

View File

@ -355,6 +355,7 @@ private:
DECL_GFX_PREF(Live, "layers.transaction.warning-ms", LayerTransactionWarning, uint32_t, 200);
DECL_GFX_PREF(Once, "layers.uniformity-info", UniformityInfo, bool, false);
DECL_GFX_PREF(Once, "layers.use-image-offscreen-surfaces", UseImageOffscreenSurfaces, bool, false);
DECL_GFX_PREF(Live, "layers.single-tile.enabled", LayersSingleTileEnabled, bool, true);
DECL_GFX_PREF(Live, "layout.css.scroll-behavior.damping-ratio", ScrollBehaviorDampingRatio, float, 1.0f);
DECL_GFX_PREF(Live, "layout.css.scroll-behavior.enabled", ScrollBehaviorEnabled, bool, false);

View File

@ -53,8 +53,8 @@ gfxQtPlatform::gfxQtPlatform()
if (!sFontconfigUtils)
sFontconfigUtils = gfxFontconfigUtils::GetFontconfigUtils();
mScreenDepth = qApp->primaryScreen()->depth();
if (mScreenDepth == 16) {
int32_t depth = GetScreenDepth();
if (depth == 16) {
sOffscreenFormat = gfxImageFormat::RGB16_565;
}
uint32_t canvasMask = BackendTypeBit(BackendType::CAIRO) | BackendTypeBit(BackendType::SKIA);
@ -195,12 +195,6 @@ gfxQtPlatform::GetOffscreenFormat()
return sOffscreenFormat;
}
int
gfxQtPlatform::GetScreenDepth() const
{
return mScreenDepth;
}
already_AddRefed<ScaledFont>
gfxQtPlatform::GetScaledFontForFont(DrawTarget* aTarget, gfxFont* aFont)
{

View File

@ -82,8 +82,6 @@ public:
static Screen* GetXScreen(QWindow* aWindow = 0);
#endif
virtual int GetScreenDepth() const override;
bool AccelerateLayersByDefault() override {
return true;
}

View File

@ -1592,26 +1592,6 @@ gfxWindowsPlatform::IsOptimus()
return knowIsOptimus;
}
int
gfxWindowsPlatform::GetScreenDepth() const
{
// if the system doesn't have all displays with the same
// pixel format, just return 24 and move on with life.
if (!GetSystemMetrics(SM_SAMEDISPLAYFORMAT))
return 24;
HDC hdc = GetDC(nullptr);
if (!hdc)
return 24;
int depth = GetDeviceCaps(hdc, BITSPIXEL) *
GetDeviceCaps(hdc, PLANES);
ReleaseDC(nullptr, hdc);
return depth;
}
IDXGIAdapter1*
gfxWindowsPlatform::GetDXGIAdapter()
{

View File

@ -140,8 +140,6 @@ public:
RENDER_MODE_MAX
};
int GetScreenDepth() const;
RenderMode GetRenderMode() { return mRenderMode; }
void SetRenderMode(RenderMode rmode) { mRenderMode = rmode; }

View File

@ -5140,9 +5140,9 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
+ self.invokeRecvHandler(md, implicit=0)
+ [ Whitespace.NL ]
+ saveIdStmts
+ self.dtorEpilogue(md, md.actorDecl().var())
+ [ Whitespace.NL ]
+ self.makeReply(md, errfnRecv, routingId=idvar)
+ [ Whitespace.NL ]
+ self.dtorEpilogue(md, md.actorDecl().var())
+ [ Whitespace.NL,
StmtReturn(_Result.Processed) ])

View File

@ -0,0 +1,8 @@
try {
evalInWorker(`
function f() { f(); }
try { f(); } catch(e) {}
`);
} catch(e) {
assertEq(e.toString().includes("--no-threads"), true);
}

View File

@ -2165,6 +2165,19 @@ IonBuilder::inlineIsPossiblyWrappedTypedArray(CallInfo& callInfo)
return inlineIsTypedArrayHelper(callInfo, AllowWrappedTypedArrays);
}
static bool
IsTypedArrayObject(CompilerConstraintList* constraints, MDefinition* def)
{
MOZ_ASSERT(def->type() == MIRType_Object);
TemporaryTypeSet* types = def->resultTypeSet();
if (!types)
return false;
return types->forAllClasses(constraints, IsTypedArrayClass) ==
TemporaryTypeSet::ForAllResult::ALL_TRUE;
}
IonBuilder::InliningStatus
IonBuilder::inlineTypedArrayLength(CallInfo& callInfo)
{
@ -2175,8 +2188,10 @@ IonBuilder::inlineTypedArrayLength(CallInfo& callInfo)
if (getInlineReturnType() != MIRType_Int32)
return InliningStatus_NotInlined;
// We assume that when calling this function we always
// have a TypedArray. The native asserts that as well.
// Note that the argument we see here is not necessarily a typed array.
// If it's not, this call should be unreachable though.
if (!IsTypedArrayObject(constraints(), callInfo.getArg(0)))
return InliningStatus_NotInlined;
MInstruction* length = addTypedArrayLength(callInfo.getArg(0));
current->push(length);
@ -2210,19 +2225,10 @@ IonBuilder::inlineSetDisjointTypedElements(CallInfo& callInfo)
// Only attempt to optimize if |target| and |sourceTypedArray| are both
// definitely typed arrays. (The former always is. The latter is not,
// necessarily, because of wrappers.)
MDefinition* arrays[] = { target, sourceTypedArray };
for (MDefinition* def : arrays) {
TemporaryTypeSet* types = def->resultTypeSet();
if (!types)
return InliningStatus_NotInlined;
if (types->forAllClasses(constraints(), IsTypedArrayClass) !=
TemporaryTypeSet::ForAllResult::ALL_TRUE)
{
return InliningStatus_NotInlined;
}
if (!IsTypedArrayObject(constraints(), target) ||
!IsTypedArrayObject(constraints(), sourceTypedArray))
{
return InliningStatus_NotInlined;
}
auto sets = MSetDisjointTypedElements::New(alloc(), target, targetOffset, sourceTypedArray);

View File

@ -153,7 +153,7 @@ MacroAssembler::guardObjectType(Register obj, const TypeSet* types,
{
MOZ_ASSERT(!types->unknown());
MOZ_ASSERT(!types->hasType(TypeSet::AnyObjectType()));
MOZ_ASSERT(scratch != InvalidReg);
MOZ_ASSERT_IF(types->getObjectCount() > 0, scratch != InvalidReg);
// Note: this method elides read barriers on values read from type sets, as
// this may be called off the main thread during Ion compilation. This is
@ -215,7 +215,6 @@ MacroAssembler::guardObjectType(Register obj, const TypeSet* types,
lastBranch.emit(*this);
bind(&matched);
return;
}
template void MacroAssembler::guardTypeSet(const Address& address, const TypeSet* types,

View File

@ -2604,7 +2604,8 @@ EvalInWorker(JSContext* cx, unsigned argc, Value* vp)
return false;
PRThread* thread = PR_CreateThread(PR_USER_THREAD, WorkerMain, input,
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD,
gMaxStackSize + 128 * 1024);
if (!thread || !workerThreads.append(thread))
return false;
@ -6129,6 +6130,8 @@ SetWorkerRuntimeOptions(JSRuntime* rt)
if (*gZealStr)
rt->gc.parseAndSetZeal(gZealStr);
#endif
JS_SetNativeStackQuota(rt, gMaxStackSize);
}
static int

View File

@ -335,7 +335,7 @@ TemporaryTypeSet::TemporaryTypeSet(LifoAlloc* alloc, Type type)
}
bool
TypeSet::mightBeMIRType(jit::MIRType type)
TypeSet::mightBeMIRType(jit::MIRType type) const
{
if (unknown())
return true;

View File

@ -475,7 +475,7 @@ class TypeSet
}
/* Whether any values in this set might have the specified type. */
bool mightBeMIRType(jit::MIRType type);
bool mightBeMIRType(jit::MIRType type) const;
/*
* Get whether this type set is known to be a subset of other.

View File

@ -2094,9 +2094,6 @@ ContainerState::GetLayerCreationHint(const nsIFrame* aAnimatedGeometryRoot)
{
// Check whether the layer will be scrollable. This is used as a hint to
// influence whether tiled layers are used or not.
if (mParameters.mInLowPrecisionDisplayPort) {
return LayerManager::SCROLLABLE;
}
// Check whether there's any active scroll frame on the animated geometry
// root chain.

View File

@ -69,7 +69,7 @@ fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,4,90000) == rad
== radial-position-1a.html radial-position-1-ref.html
fuzzy-if(cocoaWidget,1,28) fuzzy-if(winWidget,1,18) == radial-position-1b.html radial-position-1-ref.html
fuzzy-if(cocoaWidget,4,22317) fuzzy-if(Android,8,771) == radial-shape-closest-corner-1a.html radial-shape-closest-corner-1-ref.html
fuzzy(1,238) fuzzy-if(cocoaWidget,4,22608) fuzzy-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\./.test(http.oscpu))&&d2d,1,336) fuzzy-if(Android,8,787) == radial-shape-closest-corner-1b.html radial-shape-closest-corner-1-ref.html
fuzzy(1,238) fuzzy-if(cocoaWidget,4,22608) fuzzy-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\./.test(http.oscpu))&&d2d,1,336) fuzzy-if(Android,8,787) fuzzy-if(B2G,1,242) == radial-shape-closest-corner-1b.html radial-shape-closest-corner-1-ref.html
fuzzy-if(azureQuartz,2,41171) fuzzy-if(Android,8,771) == radial-shape-closest-corner-1c.html radial-shape-closest-corner-1-ref.html
fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) fuzzy-if(Android,17,3880) == radial-shape-closest-side-1a.html radial-shape-closest-side-1-ref.html
fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) fuzzy-if(Android,17,3880) == radial-shape-closest-side-1b.html radial-shape-closest-side-1-ref.html

View File

@ -33,7 +33,7 @@ include svg-integration/reftest.list
== clip-02a.svg clip-02-ref.svg
== clip-02b.svg clip-02-ref.svg
== clipPath-advanced-01.svg pass.svg
fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.[12]/.test(http.oscpu),1,5) fuzzy-if(azureQuartz,1,6) fuzzy-if(OSX,1,2) == clipPath-and-shape-rendering-01.svg clipPath-and-shape-rendering-01-ref.svg # bug 614840
fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.[12]/.test(http.oscpu),1,5) fuzzy-if(azureQuartz,1,6) fuzzy-if(OSX,1,6) == clipPath-and-shape-rendering-01.svg clipPath-and-shape-rendering-01-ref.svg # bug 614840
== clipPath-and-transform-01.svg pass.svg
== clipPath-basic-01.svg pass.svg
== clipPath-basic-02.svg pass.svg

View File

@ -4,7 +4,7 @@ fuzzy-if(Android,16,244) skip-if(B2G||Mulet) HTTP(..) == marker-basic.html marke
skip-if(B2G||Mulet) HTTP(..) == marker-string.html marker-string-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
skip-if(Android||B2G) HTTP(..) == bidi-simple.html bidi-simple-ref.html # Fails on Android due to anti-aliasing
skip-if(!gtkWidget) fuzzy-if(gtkWidget,2,289) HTTP(..) == bidi-simple-scrolled.html bidi-simple-scrolled-ref.html # Fails on Windows and OSX due to anti-aliasing
skip-if(B2G||Mulet) fuzzy-if(Android&&AndroidVersion<15,9,2545) fuzzy-if(Android&&AndroidVersion>=15,24,4000) fuzzy-if(cocoaWidget,1,40) fuzzy-if(asyncPan&&!layersGPUAccelerated,102,1770) HTTP(..) == scroll-rounding.html scroll-rounding-ref.html # bug 760264 # Initial mulet triage: parity with B2G/B2G Desktop
skip-if(B2G||Mulet) fuzzy-if(Android&&AndroidVersion<15,206,41) fuzzy-if(Android&&AndroidVersion>=15,24,4000) fuzzy-if(cocoaWidget,1,40) fuzzy-if(asyncPan&&!layersGPUAccelerated,102,1770) HTTP(..) == scroll-rounding.html scroll-rounding-ref.html # bug 760264 # Initial mulet triage: parity with B2G/B2G Desktop
fuzzy-if(OSX==1008,1,1) HTTP(..) == anonymous-block.html anonymous-block-ref.html
skip-if(B2G||Mulet) HTTP(..) == false-marker-overlap.html false-marker-overlap-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
HTTP(..) == visibility-hidden.html visibility-hidden-ref.html

View File

@ -262,6 +262,15 @@ nsCSSCompressedDataBlock::MapRuleInfoInto(nsRuleData *aRuleData) const
nsCSSValue* target = aRuleData->ValueFor(iProp);
if (target->GetUnit() == eCSSUnit_Null) {
const nsCSSValue *val = ValueAtIndex(i);
// In order for variable resolution to have the right information
// about the stylesheet level of a value, that level needs to be
// stored on the token stream. We can't do that at creation time
// because the CSS parser (which creates the object) has no idea
// about the stylesheet level, so we do it here instead, where
// the rule walking will have just updated aRuleData.
if (val->GetUnit() == eCSSUnit_TokenStream) {
val->GetTokenStreamValue()->mLevel = aRuleData->mLevel;
}
MapSinglePropertyInto(iProp, val, target, aRuleData);
}
}

View File

@ -23,6 +23,7 @@
#include "nsPresContext.h"
#include "nsStyleUtil.h"
#include "nsDeviceContext.h"
#include "nsStyleSet.h"
using namespace mozilla;
@ -2537,6 +2538,7 @@ nsCSSValueGradient::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) con
nsCSSValueTokenStream::nsCSSValueTokenStream()
: mPropertyID(eCSSProperty_UNKNOWN)
, mShorthandPropertyID(eCSSProperty_UNKNOWN)
, mLevel(nsStyleSet::eSheetTypeCount)
{
MOZ_COUNT_CTOR(nsCSSValueTokenStream);
}

View File

@ -1541,6 +1541,7 @@ public:
// mozilla::CSSStyleSheet* mSheet;
uint32_t mLineNumber;
uint32_t mLineOffset;
uint16_t mLevel; // an nsStyleSet::sheetType
private:
nsCSSValueTokenStream(const nsCSSValueTokenStream& aOther) = delete;

Some files were not shown because too many files have changed in this diff Show More