mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge m-c to b2g-inbound. a=merge
This commit is contained in:
commit
22f4c1f579
@ -60,7 +60,6 @@ DEFAULT_NO_CONNECTIONS_PREFS = {
|
||||
'browser.safebrowsing.enabled' : False,
|
||||
'browser.safebrowsing.updateURL': 'http://localhost/safebrowsing-dummy/update',
|
||||
'browser.safebrowsing.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.safebrowsing.reportURL': 'http://localhost/safebrowsing-dummy/report',
|
||||
'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport',
|
||||
'browser.selfsupport.url': 'https://localhost/selfsupport-dummy',
|
||||
'browser.trackingprotection.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
|
||||
|
@ -17,7 +17,6 @@
|
||||
"browser.safebrowsing.enabled": false,
|
||||
"browser.safebrowsing.updateURL": "http://localhost/safebrowsing-dummy/update",
|
||||
"browser.safebrowsing.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
|
||||
"browser.safebrowsing.reportURL": "http://localhost/safebrowsing-dummy/report",
|
||||
"browser.safebrowsing.malware.reportURL": "http://localhost/safebrowsing-dummy/malwarereport",
|
||||
"browser.selfsupport.url": "https://localhost/selfsupport-dummy",
|
||||
"browser.trackingprotection.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
|
||||
|
@ -360,12 +360,9 @@ pref("browser.safebrowsing.malware.enabled", false);
|
||||
pref("browser.safebrowsing.debug", false);
|
||||
pref("browser.safebrowsing.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
|
||||
pref("browser.safebrowsing.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
|
||||
pref("browser.safebrowsing.reportURL", "https://safebrowsing.google.com/safebrowsing/report?");
|
||||
pref("browser.safebrowsing.reportGenericURL", "http://%LOCALE%.phish-generic.mozilla.com/?hl=%LOCALE%");
|
||||
pref("browser.safebrowsing.reportErrorURL", "http://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%");
|
||||
pref("browser.safebrowsing.reportPhishURL", "http://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%");
|
||||
pref("browser.safebrowsing.reportMalwareURL", "http://%LOCALE%.malware-report.mozilla.com/?hl=%LOCALE%");
|
||||
pref("browser.safebrowsing.reportMalwareErrorURL", "http://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%");
|
||||
pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
|
||||
|
||||
pref("browser.safebrowsing.id", "Firefox");
|
||||
|
@ -981,13 +981,9 @@ pref("browser.safebrowsing.debug", false);
|
||||
|
||||
pref("browser.safebrowsing.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
|
||||
pref("browser.safebrowsing.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
|
||||
pref("browser.safebrowsing.reportURL", "https://safebrowsing.google.com/safebrowsing/report?");
|
||||
pref("browser.safebrowsing.reportGenericURL", "http://%LOCALE%.phish-generic.mozilla.com/?hl=%LOCALE%");
|
||||
pref("browser.safebrowsing.reportErrorURL", "http://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%");
|
||||
pref("browser.safebrowsing.reportPhishURL", "http://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%");
|
||||
pref("browser.safebrowsing.reportMalwareURL", "http://%LOCALE%.malware-report.mozilla.com/?hl=%LOCALE%");
|
||||
pref("browser.safebrowsing.reportMalwareErrorURL", "http://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%");
|
||||
|
||||
pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.malware.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
|
||||
|
||||
pref("browser.safebrowsing.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
|
||||
|
@ -36,17 +36,7 @@ var gSafeBrowsing = {
|
||||
* @return String the report phishing URL.
|
||||
*/
|
||||
getReportURL: function(name) {
|
||||
var reportUrl = SafeBrowsing.getReportURL(name);
|
||||
|
||||
var pageUri = gBrowser.currentURI.clone();
|
||||
|
||||
// Remove the query to avoid including potentially sensitive data
|
||||
if (pageUri instanceof Ci.nsIURL)
|
||||
pageUri.query = '';
|
||||
|
||||
reportUrl += "&url=" + encodeURIComponent(pageUri.asciiSpec);
|
||||
|
||||
return reportUrl;
|
||||
return SafeBrowsing.getReportURL(name, gBrowser.currentURI);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -3019,7 +3019,7 @@ let BrowserOnClick = {
|
||||
label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"),
|
||||
accessKey: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.accessKey"),
|
||||
callback: function() {
|
||||
openUILinkIn(gSafeBrowsing.getReportURL('MalwareError'), 'tab');
|
||||
openUILinkIn(gSafeBrowsing.getReportURL('MalwareMistake'), 'tab');
|
||||
}
|
||||
};
|
||||
} else if (reason === 'phishing') {
|
||||
@ -3028,7 +3028,7 @@ let BrowserOnClick = {
|
||||
label: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.label"),
|
||||
accessKey: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.accessKey"),
|
||||
callback: function() {
|
||||
openUILinkIn(gSafeBrowsing.getReportURL('Error'), 'tab');
|
||||
openUILinkIn(gSafeBrowsing.getReportURL('PhishMistake'), 'tab');
|
||||
}
|
||||
};
|
||||
} else if (reason === 'unwanted') {
|
||||
|
@ -29,7 +29,7 @@
|
||||
accesskey="&reportPhishSiteMenu.accesskey;"
|
||||
insertbefore="aboutSeparator"
|
||||
observes="reportPhishingErrorBroadcaster"
|
||||
oncommand="openUILinkIn(gSafeBrowsing.getReportURL('Error'), 'tab');"
|
||||
oncommand="openUILinkIn(gSafeBrowsing.getReportURL('PhishMistake'), 'tab');"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
</menupopup>
|
||||
</overlay>
|
||||
|
@ -58,15 +58,23 @@ function testBenignPage(gTestBrowser)
|
||||
is(notification, null, "Tracking Content Doorhanger did NOT appear when protection was ON and tracking was NOT present");
|
||||
}
|
||||
|
||||
function testTrackingPage(gTestBrowser)
|
||||
function* testTrackingPage(gTestBrowser)
|
||||
{
|
||||
// Make sure the doorhanger appears
|
||||
var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
|
||||
isnot(notification, null, "Tracking Content Doorhanger did appear when protection was ON and tracking was present");
|
||||
notification.reshow();
|
||||
|
||||
// Wait for the method to be attached after showing the popup
|
||||
yield promiseWaitForCondition(() => {
|
||||
return PopupNotifications.panel.firstChild.disableTrackingContentProtection;
|
||||
});
|
||||
|
||||
|
||||
// Make sure the state of the doorhanger includes blocking tracking elements
|
||||
isnot(PopupNotifications.panel.firstChild.isTrackingContentBlocked, 0,
|
||||
"Tracking Content is being blocked");
|
||||
is(PopupNotifications.panel.firstChild.isTrackingContentBlocked,
|
||||
Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT,
|
||||
"Tracking Content is being blocked");
|
||||
|
||||
// Make sure the notification has no trackingblockdisabled attribute
|
||||
ok(!PopupNotifications.panel.firstChild.hasAttribute("trackingblockdisabled"),
|
||||
@ -127,7 +135,7 @@ add_task(function* () {
|
||||
|
||||
// Point tab to a test page containing tracking elements
|
||||
yield promiseTabLoadEvent(tab, "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html");
|
||||
testTrackingPage(gBrowser.getBrowserForTab(tab));
|
||||
yield testTrackingPage(gBrowser.getBrowserForTab(tab));
|
||||
|
||||
// Wait for tab to reload following tracking-protection page white-listing
|
||||
yield promiseTabLoadEvent(tab);
|
||||
|
@ -247,40 +247,6 @@ body {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.new-room-view > .context > .context-content {
|
||||
border: 1px solid #0096dd;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
padding: .8em;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
line-height: 1.1em;
|
||||
}
|
||||
|
||||
.new-room-view > .context > .context-content > .context-preview {
|
||||
float: left;
|
||||
width: 16px;
|
||||
max-height: 16px;
|
||||
-moz-margin-end: .8em;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .new-room-view > .context > .context-content > .context-preview {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.new-room-view > .context > .context-content > .context-description {
|
||||
flex: 0 1 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.new-room-view > .context > .context-content > .context-description > .context-url {
|
||||
display: block;
|
||||
color: #59A1D7;
|
||||
font-weight: 700;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.new-room-view > .btn {
|
||||
display: block;
|
||||
font-size: 1rem;
|
||||
|
@ -765,20 +765,19 @@ loop.panel = (function(_, mozL10n) {
|
||||
hide: !hostname ||
|
||||
!this.props.mozLoop.getLoopPref("contextInConversations.enabled")
|
||||
});
|
||||
var thumbnail = this.state.previewImage || "loop/shared/img/icons-16x16.svg#globe";
|
||||
|
||||
return (
|
||||
React.createElement("div", {className: "new-room-view"},
|
||||
React.createElement("div", {className: contextClasses},
|
||||
React.createElement(Checkbox, {label: mozL10n.get("context_inroom_label"),
|
||||
onChange: this.onCheckboxChange}),
|
||||
React.createElement("div", {className: "context-content"},
|
||||
React.createElement("img", {className: "context-preview", src: thumbnail}),
|
||||
React.createElement("span", {className: "context-description"},
|
||||
this.state.description,
|
||||
React.createElement("span", {className: "context-url"}, hostname)
|
||||
)
|
||||
)
|
||||
React.createElement(sharedViews.ContextUrlView, {
|
||||
allowClick: false,
|
||||
description: this.state.description,
|
||||
showContextTitle: false,
|
||||
thumbnail: this.state.previewImage,
|
||||
url: this.state.url,
|
||||
useDesktopPaths: true})
|
||||
),
|
||||
React.createElement("button", {className: "btn btn-info new-room-button",
|
||||
onClick: this.handleCreateButtonClick,
|
||||
|
@ -765,20 +765,19 @@ loop.panel = (function(_, mozL10n) {
|
||||
hide: !hostname ||
|
||||
!this.props.mozLoop.getLoopPref("contextInConversations.enabled")
|
||||
});
|
||||
var thumbnail = this.state.previewImage || "loop/shared/img/icons-16x16.svg#globe";
|
||||
|
||||
return (
|
||||
<div className="new-room-view">
|
||||
<div className={contextClasses}>
|
||||
<Checkbox label={mozL10n.get("context_inroom_label")}
|
||||
onChange={this.onCheckboxChange} />
|
||||
<div className="context-content">
|
||||
<img className="context-preview" src={thumbnail} />
|
||||
<span className="context-description">
|
||||
{this.state.description}
|
||||
<span className="context-url">{hostname}</span>
|
||||
</span>
|
||||
</div>
|
||||
<sharedViews.ContextUrlView
|
||||
allowClick={false}
|
||||
description={this.state.description}
|
||||
showContextTitle={false}
|
||||
thumbnail={this.state.previewImage}
|
||||
url={this.state.url}
|
||||
useDesktopPaths={true} />
|
||||
</div>
|
||||
<button className="btn btn-info new-room-button"
|
||||
onClick={this.handleCreateButtonClick}
|
||||
|
@ -761,7 +761,10 @@ loop.roomViews = (function(mozL10n) {
|
||||
mozLoop: this.props.mozLoop,
|
||||
roomData: roomData,
|
||||
show: !shouldRenderInvitationOverlay && shouldRenderContextView}),
|
||||
React.createElement(sharedViews.TextChatView, {dispatcher: this.props.dispatcher})
|
||||
React.createElement(sharedViews.TextChatView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
showAlways: false,
|
||||
showRoomName: false})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -761,7 +761,10 @@ loop.roomViews = (function(mozL10n) {
|
||||
mozLoop={this.props.mozLoop}
|
||||
roomData={roomData}
|
||||
show={!shouldRenderInvitationOverlay && shouldRenderContextView} />
|
||||
<sharedViews.TextChatView dispatcher={this.props.dispatcher} />
|
||||
<sharedViews.TextChatView
|
||||
dispatcher={this.props.dispatcher}
|
||||
showAlways={false}
|
||||
showRoomName={false} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -501,3 +501,59 @@ html[dir="rtl"] .checkbox {
|
||||
.checkbox.checked.disabled {
|
||||
background-image: url("../img/check.svg#check-disabled");
|
||||
}
|
||||
|
||||
/* ContextUrlView classes */
|
||||
|
||||
.context-content {
|
||||
color: black;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .context-content {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.context-content > p {
|
||||
font-weight: bold;
|
||||
margin-bottom: .8em;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.context-wrapper {
|
||||
border: 1px solid #0096dd;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
padding: .8em;
|
||||
/* Use the flex row mode to position the elements next to each other. */
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
line-height: 1.1em;
|
||||
}
|
||||
|
||||
.context-wrapper > .context-preview {
|
||||
float: left;
|
||||
/* 16px is standard height/width for a favicon */
|
||||
width: 16px;
|
||||
max-height: 16px;
|
||||
margin-right: .8em;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .context-wrapper > .context-preview {
|
||||
float: left;
|
||||
margin-left: .8em;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.context-wrapper > .context-description {
|
||||
flex: 0 1 auto;
|
||||
display: block;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.context-wrapper > .context-description > .context-url {
|
||||
display: block;
|
||||
color: #59A1D7;
|
||||
font-weight: 700;
|
||||
clear: both;
|
||||
}
|
||||
|
@ -266,11 +266,6 @@
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.standalone .room-conversation .local-stream,
|
||||
.standalone .room-conversation .remote-inset-stream {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Side by side video elements */
|
||||
|
||||
.conversation .media.side-by-side .focus-stream {
|
||||
@ -715,7 +710,6 @@ html, .fx-embedded, #main,
|
||||
min-width: 120px;
|
||||
min-height: 150px;
|
||||
width: 100%;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Nested video elements */
|
||||
@ -887,6 +881,13 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
|
||||
background-image: url("../img/icons-16x16.svg#add-active");
|
||||
}
|
||||
|
||||
.context-url-view-wrapper {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
padding-bottom: 0.5em;
|
||||
background-color: #E8F6FE;
|
||||
}
|
||||
|
||||
.room-context {
|
||||
background: rgba(0,0,0,.6);
|
||||
border-top: 2px solid #444;
|
||||
@ -1258,22 +1259,39 @@ html[dir="rtl"] .room-context-btn-edit {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.text-chat-entry > span {
|
||||
.text-chat-entry > p {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #0095dd;
|
||||
border-radius: 10000px;
|
||||
padding: .5em 1em;
|
||||
/* Drop the default margins from the 'p' element. */
|
||||
margin: 0;
|
||||
/* inline-block stops the elements taking 100% of the text-chat-view width */
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.text-chat-entry.received {
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.text-chat-entry.received > span {
|
||||
.text-chat-entry.received > p {
|
||||
border-color: #d8d8d8;
|
||||
}
|
||||
|
||||
.text-chat-entry.special > p {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.text-chat-entry.special.room-name {
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
text-align: start;
|
||||
background-color: #E8F6FE;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.text-chat-box {
|
||||
margin: auto;
|
||||
}
|
||||
|
@ -5,18 +5,21 @@
|
||||
var loop = loop || {};
|
||||
loop.store = loop.store || {};
|
||||
|
||||
loop.store.TextChatStore = (function() {
|
||||
loop.store.TextChatStore = (function(mozL10n) {
|
||||
"use strict";
|
||||
|
||||
var sharedActions = loop.shared.actions;
|
||||
|
||||
var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES = {
|
||||
RECEIVED: "recv",
|
||||
SENT: "sent"
|
||||
SENT: "sent",
|
||||
SPECIAL: "special"
|
||||
};
|
||||
|
||||
var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES = {
|
||||
TEXT: "chat-text"
|
||||
CONTEXT: "chat-context",
|
||||
TEXT: "chat-text",
|
||||
ROOM_NAME: "room-name"
|
||||
};
|
||||
|
||||
/**
|
||||
@ -27,7 +30,8 @@ loop.store.TextChatStore = (function() {
|
||||
actions: [
|
||||
"dataChannelsAvailable",
|
||||
"receivedTextChatMessage",
|
||||
"sendTextChatMessage"
|
||||
"sendTextChatMessage",
|
||||
"updateRoomInfo"
|
||||
],
|
||||
|
||||
/**
|
||||
@ -74,21 +78,29 @@ loop.store.TextChatStore = (function() {
|
||||
/**
|
||||
* Appends a message to the store, which may be of type 'sent' or 'received'.
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {sharedActions.ReceivedTextChatMessage|sharedActions.SendTextChatMessage} actionData
|
||||
* @param {CHAT_MESSAGE_TYPES} type
|
||||
* @param {Object} messageData Data for this message. Options are:
|
||||
* - {CHAT_CONTENT_TYPES} contentType
|
||||
* - {String} message The message detail.
|
||||
* - {Object} extraData Extra data associated with the message.
|
||||
*/
|
||||
_appendTextChatMessage: function(type, actionData) {
|
||||
_appendTextChatMessage: function(type, messageData) {
|
||||
// We create a new list to avoid updating the store's state directly,
|
||||
// which confuses the views.
|
||||
var message = {
|
||||
type: type,
|
||||
contentType: actionData.contentType,
|
||||
message: actionData.message
|
||||
contentType: messageData.contentType,
|
||||
message: messageData.message,
|
||||
extraData: messageData.extraData
|
||||
};
|
||||
var newList = this._storeState.messageList.concat(message);
|
||||
this.setStoreState({ messageList: newList });
|
||||
|
||||
window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
|
||||
// Notify MozLoopService if appropriate that a message has been appended
|
||||
// and it should therefore check if we need a different sized window or not.
|
||||
if (type != CHAT_MESSAGE_TYPES.SPECIAL) {
|
||||
window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -114,8 +126,38 @@ loop.store.TextChatStore = (function() {
|
||||
sendTextChatMessage: function(actionData) {
|
||||
this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SENT, actionData);
|
||||
this._sdkDriver.sendTextChatMessage(actionData);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles receiving information about the room - specifically the room name
|
||||
* so it can be added to the list.
|
||||
*
|
||||
* @param {sharedActions.UpdateRoomInfo} actionData
|
||||
*/
|
||||
updateRoomInfo: function(actionData) {
|
||||
// XXX When we add special messages to desktop, we'll need to not post
|
||||
// multiple changes of room name, only the first. Bug 1171940 should fix this.
|
||||
this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SPECIAL, {
|
||||
contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
|
||||
message: mozL10n.get("rooms_welcome_title", {conversationName: actionData.roomName})
|
||||
});
|
||||
|
||||
// Append the context if we have any.
|
||||
if ("urls" in actionData && actionData.urls.length) {
|
||||
// We only support the first url at the moment.
|
||||
var urlData = actionData.urls[0];
|
||||
|
||||
this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SPECIAL, {
|
||||
contentType: CHAT_CONTENT_TYPES.CONTEXT,
|
||||
message: urlData.description,
|
||||
extraData: {
|
||||
location: urlData.location,
|
||||
thumbnail: urlData.thumbnail
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return TextChatStore;
|
||||
})();
|
||||
})(navigator.mozL10n || window.mozL10n);
|
||||
|
@ -5,8 +5,9 @@
|
||||
var loop = loop || {};
|
||||
loop.shared = loop.shared || {};
|
||||
loop.shared.views = loop.shared.views || {};
|
||||
loop.shared.views.TextChatView = (function(mozl10n) {
|
||||
loop.shared.views.TextChatView = (function(mozL10n) {
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedViews = loop.shared.views;
|
||||
var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
|
||||
var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
|
||||
|
||||
@ -17,6 +18,7 @@ loop.shared.views.TextChatView = (function(mozl10n) {
|
||||
mixins: [React.addons.PureRenderMixin],
|
||||
|
||||
propTypes: {
|
||||
contentType: React.PropTypes.string.isRequired,
|
||||
message: React.PropTypes.string.isRequired,
|
||||
type: React.PropTypes.string.isRequired
|
||||
},
|
||||
@ -24,12 +26,14 @@ loop.shared.views.TextChatView = (function(mozl10n) {
|
||||
render: function() {
|
||||
var classes = React.addons.classSet({
|
||||
"text-chat-entry": true,
|
||||
"received": this.props.type === CHAT_MESSAGE_TYPES.RECEIVED
|
||||
"received": this.props.type === CHAT_MESSAGE_TYPES.RECEIVED,
|
||||
"special": this.props.type === CHAT_MESSAGE_TYPES.SPECIAL,
|
||||
"room-name": this.props.contentType === CHAT_CONTENT_TYPES.ROOM_NAME
|
||||
});
|
||||
|
||||
return (
|
||||
React.createElement("div", {className: classes},
|
||||
React.createElement("span", null, this.props.message)
|
||||
React.createElement("p", null, this.props.message)
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -44,6 +48,7 @@ loop.shared.views.TextChatView = (function(mozl10n) {
|
||||
mixins: [React.addons.PureRenderMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
messageList: React.PropTypes.array.isRequired
|
||||
},
|
||||
|
||||
@ -76,8 +81,26 @@ loop.shared.views.TextChatView = (function(mozl10n) {
|
||||
React.createElement("div", {className: "text-chat-scroller"},
|
||||
|
||||
this.props.messageList.map(function(entry, i) {
|
||||
if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL &&
|
||||
entry.contentType === CHAT_CONTENT_TYPES.CONTEXT) {
|
||||
return (
|
||||
React.createElement("div", {className: "context-url-view-wrapper"},
|
||||
React.createElement(sharedViews.ContextUrlView, {
|
||||
allowClick: true,
|
||||
description: entry.message,
|
||||
dispatcher: this.props.dispatcher,
|
||||
key: i,
|
||||
showContextTitle: true,
|
||||
thumbnail: entry.extraData.thumbnail,
|
||||
url: entry.extraData.location,
|
||||
useDesktopPaths: false})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
React.createElement(TextChatEntry, {key: i,
|
||||
contentType: entry.contentType,
|
||||
message: entry.message,
|
||||
type: entry.type})
|
||||
);
|
||||
@ -90,23 +113,29 @@ loop.shared.views.TextChatView = (function(mozl10n) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Displays the text chat view. This includes the text chat messages as well
|
||||
* as a field for entering new messages.
|
||||
* Displays a text chat entry input box for sending messages.
|
||||
*
|
||||
* @property {loop.Dispatcher} dispatcher
|
||||
* @property {Boolean} showPlaceholder Set to true to show the placeholder message.
|
||||
* @property {Boolean} textChatEnabled Set to true to enable the box. If false, the
|
||||
* text chat box won't be displayed.
|
||||
*/
|
||||
var TextChatView = React.createClass({displayName: "TextChatView",
|
||||
var TextChatInputView = React.createClass({displayName: "TextChatInputView",
|
||||
mixins: [
|
||||
React.addons.LinkedStateMixin,
|
||||
loop.store.StoreMixin("textChatStore")
|
||||
React.addons.PureRenderMixin
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
showPlaceholder: React.PropTypes.bool.isRequired,
|
||||
textChatEnabled: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return _.extend({
|
||||
return {
|
||||
messageDetail: ""
|
||||
}, this.getStoreState());
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
@ -139,23 +168,80 @@ loop.shared.views.TextChatView = (function(mozl10n) {
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (!this.state.textChatEnabled) {
|
||||
if (!this.props.textChatEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var messageList = this.state.messageList;
|
||||
return (
|
||||
React.createElement("div", {className: "text-chat-box"},
|
||||
React.createElement("form", {onSubmit: this.handleFormSubmit},
|
||||
React.createElement("input", {type: "text",
|
||||
placeholder: this.props.showPlaceholder ? mozL10n.get("chat_textbox_placeholder") : "",
|
||||
onKeyDown: this.handleKeyDown,
|
||||
valueLink: this.linkState("messageDetail")})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Displays the text chat view. This includes the text chat messages as well
|
||||
* as a field for entering new messages.
|
||||
*
|
||||
* @property {loop.Dispatcher} dispatcher
|
||||
* @property {Boolean} showAlways If false, the view will not be rendered
|
||||
* if text chat is not enabled and the
|
||||
* message list is empty.
|
||||
* @property {Boolean} showRoomName Set to true to show the room name special
|
||||
* list item.
|
||||
*/
|
||||
var TextChatView = React.createClass({displayName: "TextChatView",
|
||||
mixins: [
|
||||
React.addons.LinkedStateMixin,
|
||||
loop.store.StoreMixin("textChatStore")
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
showAlways: React.PropTypes.bool.isRequired,
|
||||
showRoomName: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return this.getStoreState();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var messageList;
|
||||
var hasNonSpecialMessages;
|
||||
|
||||
if (this.props.showRoomName) {
|
||||
messageList = this.state.messageList;
|
||||
hasNonSpecialMessages = messageList.some(function(item) {
|
||||
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
|
||||
});
|
||||
} else {
|
||||
// XXX Desktop should be showing the initial context here (bug 1171940).
|
||||
messageList = this.state.messageList.filter(function(item) {
|
||||
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
|
||||
});
|
||||
hasNonSpecialMessages = !!messageList.length;
|
||||
}
|
||||
|
||||
if (!this.props.showAlways && !this.state.textChatEnabled && !messageList.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
React.createElement("div", {className: "text-chat-view"},
|
||||
React.createElement(TextChatEntriesView, {messageList: messageList}),
|
||||
React.createElement("div", {className: "text-chat-box"},
|
||||
React.createElement("form", {onSubmit: this.handleFormSubmit},
|
||||
React.createElement("input", {type: "text",
|
||||
placeholder: messageList.length ? "" : mozl10n.get("chat_textbox_placeholder"),
|
||||
onKeyDown: this.handleKeyDown,
|
||||
valueLink: this.linkState("messageDetail")})
|
||||
)
|
||||
)
|
||||
React.createElement(TextChatEntriesView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
messageList: messageList}),
|
||||
React.createElement(TextChatInputView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
showPlaceholder: !hasNonSpecialMessages,
|
||||
textChatEnabled: this.state.textChatEnabled})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -5,8 +5,9 @@
|
||||
var loop = loop || {};
|
||||
loop.shared = loop.shared || {};
|
||||
loop.shared.views = loop.shared.views || {};
|
||||
loop.shared.views.TextChatView = (function(mozl10n) {
|
||||
loop.shared.views.TextChatView = (function(mozL10n) {
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedViews = loop.shared.views;
|
||||
var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
|
||||
var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
|
||||
|
||||
@ -17,6 +18,7 @@ loop.shared.views.TextChatView = (function(mozl10n) {
|
||||
mixins: [React.addons.PureRenderMixin],
|
||||
|
||||
propTypes: {
|
||||
contentType: React.PropTypes.string.isRequired,
|
||||
message: React.PropTypes.string.isRequired,
|
||||
type: React.PropTypes.string.isRequired
|
||||
},
|
||||
@ -24,12 +26,14 @@ loop.shared.views.TextChatView = (function(mozl10n) {
|
||||
render: function() {
|
||||
var classes = React.addons.classSet({
|
||||
"text-chat-entry": true,
|
||||
"received": this.props.type === CHAT_MESSAGE_TYPES.RECEIVED
|
||||
"received": this.props.type === CHAT_MESSAGE_TYPES.RECEIVED,
|
||||
"special": this.props.type === CHAT_MESSAGE_TYPES.SPECIAL,
|
||||
"room-name": this.props.contentType === CHAT_CONTENT_TYPES.ROOM_NAME
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<span>{this.props.message}</span>
|
||||
<p>{this.props.message}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -44,6 +48,7 @@ loop.shared.views.TextChatView = (function(mozl10n) {
|
||||
mixins: [React.addons.PureRenderMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
messageList: React.PropTypes.array.isRequired
|
||||
},
|
||||
|
||||
@ -76,8 +81,26 @@ loop.shared.views.TextChatView = (function(mozl10n) {
|
||||
<div className="text-chat-scroller">
|
||||
{
|
||||
this.props.messageList.map(function(entry, i) {
|
||||
if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL &&
|
||||
entry.contentType === CHAT_CONTENT_TYPES.CONTEXT) {
|
||||
return (
|
||||
<div className="context-url-view-wrapper">
|
||||
<sharedViews.ContextUrlView
|
||||
allowClick={true}
|
||||
description={entry.message}
|
||||
dispatcher={this.props.dispatcher}
|
||||
key={i}
|
||||
showContextTitle={true}
|
||||
thumbnail={entry.extraData.thumbnail}
|
||||
url={entry.extraData.location}
|
||||
useDesktopPaths={false} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TextChatEntry key={i}
|
||||
contentType={entry.contentType}
|
||||
message={entry.message}
|
||||
type={entry.type} />
|
||||
);
|
||||
@ -90,23 +113,29 @@ loop.shared.views.TextChatView = (function(mozl10n) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Displays the text chat view. This includes the text chat messages as well
|
||||
* as a field for entering new messages.
|
||||
* Displays a text chat entry input box for sending messages.
|
||||
*
|
||||
* @property {loop.Dispatcher} dispatcher
|
||||
* @property {Boolean} showPlaceholder Set to true to show the placeholder message.
|
||||
* @property {Boolean} textChatEnabled Set to true to enable the box. If false, the
|
||||
* text chat box won't be displayed.
|
||||
*/
|
||||
var TextChatView = React.createClass({
|
||||
var TextChatInputView = React.createClass({
|
||||
mixins: [
|
||||
React.addons.LinkedStateMixin,
|
||||
loop.store.StoreMixin("textChatStore")
|
||||
React.addons.PureRenderMixin
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
showPlaceholder: React.PropTypes.bool.isRequired,
|
||||
textChatEnabled: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return _.extend({
|
||||
return {
|
||||
messageDetail: ""
|
||||
}, this.getStoreState());
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
@ -139,23 +168,80 @@ loop.shared.views.TextChatView = (function(mozl10n) {
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (!this.state.textChatEnabled) {
|
||||
if (!this.props.textChatEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var messageList = this.state.messageList;
|
||||
return (
|
||||
<div className="text-chat-box">
|
||||
<form onSubmit={this.handleFormSubmit}>
|
||||
<input type="text"
|
||||
placeholder={this.props.showPlaceholder ? mozL10n.get("chat_textbox_placeholder") : ""}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
valueLink={this.linkState("messageDetail")} />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Displays the text chat view. This includes the text chat messages as well
|
||||
* as a field for entering new messages.
|
||||
*
|
||||
* @property {loop.Dispatcher} dispatcher
|
||||
* @property {Boolean} showAlways If false, the view will not be rendered
|
||||
* if text chat is not enabled and the
|
||||
* message list is empty.
|
||||
* @property {Boolean} showRoomName Set to true to show the room name special
|
||||
* list item.
|
||||
*/
|
||||
var TextChatView = React.createClass({
|
||||
mixins: [
|
||||
React.addons.LinkedStateMixin,
|
||||
loop.store.StoreMixin("textChatStore")
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
showAlways: React.PropTypes.bool.isRequired,
|
||||
showRoomName: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return this.getStoreState();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var messageList;
|
||||
var hasNonSpecialMessages;
|
||||
|
||||
if (this.props.showRoomName) {
|
||||
messageList = this.state.messageList;
|
||||
hasNonSpecialMessages = messageList.some(function(item) {
|
||||
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
|
||||
});
|
||||
} else {
|
||||
// XXX Desktop should be showing the initial context here (bug 1171940).
|
||||
messageList = this.state.messageList.filter(function(item) {
|
||||
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL;
|
||||
});
|
||||
hasNonSpecialMessages = !!messageList.length;
|
||||
}
|
||||
|
||||
if (!this.props.showAlways && !this.state.textChatEnabled && !messageList.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-chat-view">
|
||||
<TextChatEntriesView messageList={messageList} />
|
||||
<div className="text-chat-box">
|
||||
<form onSubmit={this.handleFormSubmit}>
|
||||
<input type="text"
|
||||
placeholder={messageList.length ? "" : mozl10n.get("chat_textbox_placeholder")}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
valueLink={this.linkState("messageDetail")} />
|
||||
</form>
|
||||
</div>
|
||||
<TextChatEntriesView
|
||||
dispatcher={this.props.dispatcher}
|
||||
messageList={messageList} />
|
||||
<TextChatInputView
|
||||
dispatcher={this.props.dispatcher}
|
||||
showPlaceholder={!hasNonSpecialMessages}
|
||||
textChatEnabled={this.state.textChatEnabled} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -689,6 +689,94 @@ loop.shared.views = (function(_, l10n) {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders a url that's part of context on the display.
|
||||
*
|
||||
* @property {Boolean} allowClick Set to true to allow the url to be clicked. If this
|
||||
* is specified, then 'dispatcher' is also required.
|
||||
* @property {String} description The description for the context url.
|
||||
* @property {loop.Dispatcher} dispatcher
|
||||
* @property {Boolean} showContextTitle Whether or not to show the "Let's talk about" title.
|
||||
* @property {String} thumbnail The thumbnail url (expected to be a data url) to
|
||||
* display. If not specified, a fallback url will be
|
||||
* shown.
|
||||
* @property {String} url The url to be displayed. If not present or invalid,
|
||||
* then this view won't be displayed.
|
||||
* @property {Boolean} useDesktopPaths Whether or not to use the desktop paths for for the
|
||||
* fallback url.
|
||||
*/
|
||||
var ContextUrlView = React.createClass({displayName: "ContextUrlView",
|
||||
mixins: [React.addons.PureRenderMixin],
|
||||
|
||||
PropTypes: {
|
||||
allowClick: React.PropTypes.bool.isRequired,
|
||||
description: React.PropTypes.string.isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
|
||||
showContextTitle: React.PropTypes.bool.isRequired,
|
||||
thumbnail: React.PropTypes.string,
|
||||
url: React.PropTypes.string,
|
||||
useDesktopPaths: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches an action to record when the link is clicked.
|
||||
*/
|
||||
handleLinkClick: function() {
|
||||
if (!this.props.allowClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
|
||||
linkInfo: "Shared URL"
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the context title ("Let's talk about") if necessary.
|
||||
*/
|
||||
renderContextTitle: function() {
|
||||
if (!this.props.showContextTitle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return React.createElement("p", null, l10n.get("context_inroom_label"));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var hostname;
|
||||
|
||||
try {
|
||||
hostname = new URL(this.props.url).hostname;
|
||||
} catch (ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var thumbnail = this.props.thumbnail;
|
||||
|
||||
if (!thumbnail) {
|
||||
thumbnail = this.props.useDesktopPaths ?
|
||||
"loop/shared/img/icons-16x16.svg#globe" :
|
||||
"shared/img/icons-16x16.svg#globe";
|
||||
}
|
||||
|
||||
return (
|
||||
React.createElement("div", {className: "context-content"},
|
||||
this.renderContextTitle(),
|
||||
React.createElement("div", {className: "context-wrapper"},
|
||||
React.createElement("img", {className: "context-preview", src: thumbnail}),
|
||||
React.createElement("span", {className: "context-description"},
|
||||
this.props.description,
|
||||
React.createElement("a", {className: "context-url",
|
||||
onClick: this.handleLinkClick,
|
||||
href: this.props.allowClick ? this.props.url : null,
|
||||
target: "_blank"}, hostname)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders a media element for display. This also handles displaying an avatar
|
||||
* instead of the video, and attaching a video stream to the video element.
|
||||
@ -800,6 +888,7 @@ loop.shared.views = (function(_, l10n) {
|
||||
Button: Button,
|
||||
ButtonGroup: ButtonGroup,
|
||||
Checkbox: Checkbox,
|
||||
ContextUrlView: ContextUrlView,
|
||||
ConversationView: ConversationView,
|
||||
ConversationToolbar: ConversationToolbar,
|
||||
MediaControlButton: MediaControlButton,
|
||||
|
@ -689,6 +689,94 @@ loop.shared.views = (function(_, l10n) {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders a url that's part of context on the display.
|
||||
*
|
||||
* @property {Boolean} allowClick Set to true to allow the url to be clicked. If this
|
||||
* is specified, then 'dispatcher' is also required.
|
||||
* @property {String} description The description for the context url.
|
||||
* @property {loop.Dispatcher} dispatcher
|
||||
* @property {Boolean} showContextTitle Whether or not to show the "Let's talk about" title.
|
||||
* @property {String} thumbnail The thumbnail url (expected to be a data url) to
|
||||
* display. If not specified, a fallback url will be
|
||||
* shown.
|
||||
* @property {String} url The url to be displayed. If not present or invalid,
|
||||
* then this view won't be displayed.
|
||||
* @property {Boolean} useDesktopPaths Whether or not to use the desktop paths for for the
|
||||
* fallback url.
|
||||
*/
|
||||
var ContextUrlView = React.createClass({
|
||||
mixins: [React.addons.PureRenderMixin],
|
||||
|
||||
PropTypes: {
|
||||
allowClick: React.PropTypes.bool.isRequired,
|
||||
description: React.PropTypes.string.isRequired,
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
|
||||
showContextTitle: React.PropTypes.bool.isRequired,
|
||||
thumbnail: React.PropTypes.string,
|
||||
url: React.PropTypes.string,
|
||||
useDesktopPaths: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches an action to record when the link is clicked.
|
||||
*/
|
||||
handleLinkClick: function() {
|
||||
if (!this.props.allowClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
|
||||
linkInfo: "Shared URL"
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the context title ("Let's talk about") if necessary.
|
||||
*/
|
||||
renderContextTitle: function() {
|
||||
if (!this.props.showContextTitle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <p>{l10n.get("context_inroom_label")}</p>;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var hostname;
|
||||
|
||||
try {
|
||||
hostname = new URL(this.props.url).hostname;
|
||||
} catch (ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var thumbnail = this.props.thumbnail;
|
||||
|
||||
if (!thumbnail) {
|
||||
thumbnail = this.props.useDesktopPaths ?
|
||||
"loop/shared/img/icons-16x16.svg#globe" :
|
||||
"shared/img/icons-16x16.svg#globe";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="context-content">
|
||||
{this.renderContextTitle()}
|
||||
<div className="context-wrapper">
|
||||
<img className="context-preview" src={thumbnail} />
|
||||
<span className="context-description">
|
||||
{this.props.description}
|
||||
<a className="context-url"
|
||||
onClick={this.handleLinkClick}
|
||||
href={this.props.allowClick ? this.props.url : null}
|
||||
target="_blank">{hostname}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders a media element for display. This also handles displaying an avatar
|
||||
* instead of the video, and attaching a video stream to the video element.
|
||||
@ -800,6 +888,7 @@ loop.shared.views = (function(_, l10n) {
|
||||
Button: Button,
|
||||
ButtonGroup: ButtonGroup,
|
||||
Checkbox: Checkbox,
|
||||
ContextUrlView: ContextUrlView,
|
||||
ConversationView: ConversationView,
|
||||
ConversationToolbar: ConversationToolbar,
|
||||
MediaControlButton: MediaControlButton,
|
||||
|
@ -365,13 +365,12 @@ p.standalone-btn-label {
|
||||
*/
|
||||
.text-chat-view {
|
||||
height: 60px;
|
||||
color: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.text-chat-entries {
|
||||
/* XXX Should use flex, this is just for the initial implementation. */
|
||||
height: calc(100% - 2em);
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.text-chat-box {
|
||||
|
@ -666,10 +666,15 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
hide: !this.state.receivingScreenShare
|
||||
});
|
||||
|
||||
// XXX Temporarily showAlways = showRoomName = false for TextChatView
|
||||
// until bug 1168829 is completed.
|
||||
return (
|
||||
React.createElement("div", {className: "room-conversation-wrapper"},
|
||||
React.createElement("div", {className: "beta-logo"}),
|
||||
React.createElement(sharedViews.TextChatView, {dispatcher: this.props.dispatcher}),
|
||||
React.createElement(sharedViews.TextChatView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
showAlways: false,
|
||||
showRoomName: false}),
|
||||
React.createElement(StandaloneRoomHeader, {dispatcher: this.props.dispatcher}),
|
||||
React.createElement(StandaloneRoomInfoArea, {roomState: this.state.roomState,
|
||||
failureReason: this.state.failureReason,
|
||||
|
@ -666,10 +666,15 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
||||
hide: !this.state.receivingScreenShare
|
||||
});
|
||||
|
||||
// XXX Temporarily showAlways = showRoomName = false for TextChatView
|
||||
// until bug 1168829 is completed.
|
||||
return (
|
||||
<div className="room-conversation-wrapper">
|
||||
<div className="beta-logo" />
|
||||
<sharedViews.TextChatView dispatcher={this.props.dispatcher} />
|
||||
<sharedViews.TextChatView
|
||||
dispatcher={this.props.dispatcher}
|
||||
showAlways={false}
|
||||
showRoomName={false} />
|
||||
<StandaloneRoomHeader dispatcher={this.props.dispatcher} />
|
||||
<StandaloneRoomInfoArea roomState={this.state.roomState}
|
||||
failureReason={this.state.failureReason}
|
||||
|
@ -111,6 +111,9 @@ help_label=Help
|
||||
tour_label=Tour
|
||||
|
||||
rooms_default_room_name_template=Conversation {{conversationLabel}}
|
||||
## LOCALIZATION_NOTE(rooms_welcome_title): {{conversationName}} will be replaced
|
||||
## by the user specified conversation name.
|
||||
rooms_welcome_title=Welcome to {{conversationName}}
|
||||
rooms_leave_button_label=Leave
|
||||
rooms_list_copy_url_tooltip=Copy Link
|
||||
rooms_list_delete_tooltip=Delete conversation
|
||||
@ -141,3 +144,9 @@ support_link=Get Help
|
||||
# Text chat strings
|
||||
|
||||
chat_textbox_placeholder=Type here…
|
||||
# LOCALIZATION NOTE (context_inroom_label): this string is followed by the
|
||||
# title/URL of the website you are having a conversation about, displayed on a
|
||||
# separate line. If this structure doesn't work for your locale, you might want
|
||||
# to consider this as a stand-alone title. See example screenshot:
|
||||
# https://bug1084991.bugzilla.mozilla.org/attachment.cgi?id=8614721
|
||||
context_inroom_label=Let's talk about:
|
||||
|
@ -64,7 +64,8 @@ describe("loop.store.TextChatStore", function () {
|
||||
expect(store.getStoreState("messageList")).eql([{
|
||||
type: CHAT_MESSAGE_TYPES.RECEIVED,
|
||||
contentType: CHAT_CONTENT_TYPES.TEXT,
|
||||
message: message
|
||||
message: message,
|
||||
extraData: undefined
|
||||
}]);
|
||||
});
|
||||
|
||||
@ -113,7 +114,8 @@ describe("loop.store.TextChatStore", function () {
|
||||
expect(store.getStoreState("messageList")).eql([{
|
||||
type: CHAT_MESSAGE_TYPES.SENT,
|
||||
contentType: messageData.contentType,
|
||||
message: messageData.message
|
||||
message: messageData.message,
|
||||
extraData: undefined
|
||||
}]);
|
||||
});
|
||||
|
||||
@ -128,4 +130,67 @@ describe("loop.store.TextChatStore", function () {
|
||||
new CustomEvent("LoopChatMessageAppended"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("#updateRoomInfo", function() {
|
||||
it("should add the room name to the list", function() {
|
||||
sandbox.stub(navigator.mozL10n, "get").returns("Let's really share!");
|
||||
|
||||
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
|
||||
roomName: "Let's share!",
|
||||
roomOwner: "Mark",
|
||||
roomUrl: "fake"
|
||||
}));
|
||||
|
||||
expect(store.getStoreState("messageList")).eql([{
|
||||
type: CHAT_MESSAGE_TYPES.SPECIAL,
|
||||
contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
|
||||
message: "Let's really share!",
|
||||
extraData: undefined
|
||||
}]);
|
||||
});
|
||||
|
||||
it("should add the context to the list", function() {
|
||||
sandbox.stub(navigator.mozL10n, "get").returns("Let's really share!");
|
||||
|
||||
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
|
||||
roomName: "Let's share!",
|
||||
roomOwner: "Mark",
|
||||
roomUrl: "fake",
|
||||
urls: [{
|
||||
description: "A wonderful event",
|
||||
location: "http://wonderful.invalid",
|
||||
thumbnail: "fake"
|
||||
}]
|
||||
}));
|
||||
|
||||
expect(store.getStoreState("messageList")).eql([
|
||||
{
|
||||
type: CHAT_MESSAGE_TYPES.SPECIAL,
|
||||
contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
|
||||
message: "Let's really share!",
|
||||
extraData: undefined
|
||||
}, {
|
||||
type: CHAT_MESSAGE_TYPES.SPECIAL,
|
||||
contentType: CHAT_CONTENT_TYPES.CONTEXT,
|
||||
message: "A wonderful event",
|
||||
extraData: {
|
||||
location: "http://wonderful.invalid",
|
||||
thumbnail: "fake"
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it("should not dispatch a LoopChatMessageAppended event", function() {
|
||||
sandbox.stub(navigator.mozL10n, "get").returns("Let's really share!");
|
||||
|
||||
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
|
||||
roomName: "Let's share!",
|
||||
roomOwner: "Mark",
|
||||
roomUrl: "fake"
|
||||
}));
|
||||
|
||||
sinon.assert.notCalled(window.dispatchEvent);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -6,6 +6,7 @@ describe("loop.shared.views.TextChatView", function () {
|
||||
|
||||
var expect = chai.expect;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedViews = loop.shared.views;
|
||||
var TestUtils = React.addons.TestUtils;
|
||||
var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
|
||||
var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
|
||||
@ -39,25 +40,118 @@ describe("loop.shared.views.TextChatView", function () {
|
||||
describe("TextChatView", function() {
|
||||
var view;
|
||||
|
||||
function mountTestComponent() {
|
||||
function mountTestComponent(extraProps) {
|
||||
var props = _.extend({
|
||||
dispatcher: dispatcher
|
||||
}, extraProps);
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(loop.shared.views.TextChatView, {
|
||||
dispatcher: dispatcher
|
||||
}));
|
||||
React.createElement(loop.shared.views.TextChatView, props));
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
store.setStoreState({ textChatEnabled: true });
|
||||
});
|
||||
|
||||
it("should not display anything if text chat is disabled", function() {
|
||||
it("should not display anything if no messages and text chat not enabled and showAlways is false", function() {
|
||||
store.setStoreState({ textChatEnabled: false });
|
||||
|
||||
view = mountTestComponent();
|
||||
view = mountTestComponent({
|
||||
showAlways: false
|
||||
});
|
||||
|
||||
expect(view.getDOMNode()).eql(null);
|
||||
});
|
||||
|
||||
it("should display the view if no messages and text chat not enabled and showAlways is true", function() {
|
||||
store.setStoreState({ textChatEnabled: false });
|
||||
|
||||
view = mountTestComponent({
|
||||
showAlways: true
|
||||
});
|
||||
|
||||
expect(view.getDOMNode()).not.eql(null);
|
||||
});
|
||||
|
||||
it("should display the view if text chat is enabled", function() {
|
||||
view = mountTestComponent({
|
||||
showAlways: true
|
||||
});
|
||||
|
||||
expect(view.getDOMNode()).not.eql(null);
|
||||
});
|
||||
|
||||
it("should display only the text chat box if entry is enabled but there are no messages", function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
var node = view.getDOMNode();
|
||||
|
||||
expect(node.querySelector(".text-chat-box")).not.eql(null);
|
||||
expect(node.querySelector(".text-chat-entries")).eql(null);
|
||||
});
|
||||
|
||||
it("should render message entries when message were sent/ received", function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
store.receivedTextChatMessage({
|
||||
contentType: CHAT_CONTENT_TYPES.TEXT,
|
||||
message: "Hello!"
|
||||
});
|
||||
store.sendTextChatMessage({
|
||||
contentType: CHAT_CONTENT_TYPES.TEXT,
|
||||
message: "Is it me you're looking for?"
|
||||
});
|
||||
|
||||
var node = view.getDOMNode();
|
||||
expect(node.querySelector(".text-chat-entries")).to.not.eql(null);
|
||||
|
||||
var entries = node.querySelectorAll(".text-chat-entry");
|
||||
expect(entries.length).to.eql(2);
|
||||
expect(entries[0].classList.contains("received")).to.eql(true);
|
||||
expect(entries[1].classList.contains("received")).to.not.eql(true);
|
||||
});
|
||||
|
||||
it("should render a room name special entry", function() {
|
||||
view = mountTestComponent({
|
||||
showRoomName: true
|
||||
});
|
||||
|
||||
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
|
||||
roomName: "A wonderful surprise!",
|
||||
roomOwner: "Chris",
|
||||
roomUrl: "Fake"
|
||||
}));
|
||||
|
||||
var node = view.getDOMNode();
|
||||
expect(node.querySelector(".text-chat-entries")).to.not.eql(null);
|
||||
|
||||
var entries = node.querySelectorAll(".text-chat-entry");
|
||||
expect(entries.length).eql(1);
|
||||
expect(entries[0].classList.contains("special")).eql(true);
|
||||
expect(entries[0].classList.contains("room-name")).eql(true);
|
||||
});
|
||||
|
||||
it("should render a special entry for the context url", function() {
|
||||
view = mountTestComponent({
|
||||
showRoomName: true
|
||||
});
|
||||
|
||||
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
|
||||
roomName: "A Very Long Conversation Name",
|
||||
roomOwner: "fake",
|
||||
roomUrl: "http://showcase",
|
||||
urls: [{
|
||||
description: "A wonderful page!",
|
||||
location: "http://wonderful.invalid"
|
||||
// use the fallback thumbnail
|
||||
}]
|
||||
}));
|
||||
|
||||
var node = view.getDOMNode();
|
||||
expect(node.querySelector(".text-chat-entries")).to.not.eql(null);
|
||||
|
||||
expect(node.querySelector(".context-url-view-wrapper")).to.not.eql(null);
|
||||
});
|
||||
|
||||
it("should dispatch SendTextChatMessage action when enter is pressed", function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
@ -80,32 +174,5 @@ describe("loop.shared.views.TextChatView", function () {
|
||||
message: "Hello!"
|
||||
}));
|
||||
});
|
||||
|
||||
it("should not render message entries when none are sent/ received yet", function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
expect(view.getDOMNode().querySelector(".text-chat-entries")).to.eql(null);
|
||||
});
|
||||
|
||||
it("should render message entries when message were sent/ received", function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
store.receivedTextChatMessage({
|
||||
contentType: CHAT_CONTENT_TYPES.TEXT,
|
||||
message: "Hello!"
|
||||
});
|
||||
store.sendTextChatMessage({
|
||||
contentType: CHAT_CONTENT_TYPES.TEXT,
|
||||
message: "Is it me you're looking for?"
|
||||
});
|
||||
|
||||
var node = view.getDOMNode();
|
||||
expect(node.querySelector(".text-chat-entries")).to.not.eql(null);
|
||||
|
||||
var entries = node.querySelectorAll(".text-chat-entry");
|
||||
expect(entries.length).to.eql(2);
|
||||
expect(entries[0].classList.contains("received")).to.eql(true);
|
||||
expect(entries[1].classList.contains("received")).to.not.eql(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -815,6 +815,89 @@ describe("loop.shared.views", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("ContextUrlView", function() {
|
||||
var view;
|
||||
|
||||
function mountTestComponent(extraProps) {
|
||||
var props = _.extend({
|
||||
dispatcher: dispatcher
|
||||
}, extraProps);
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(sharedViews.ContextUrlView, props));
|
||||
}
|
||||
|
||||
it("should display nothing if the url is invalid", function() {
|
||||
view = mountTestComponent({
|
||||
url: "fjrTykyw"
|
||||
});
|
||||
|
||||
expect(view.getDOMNode()).eql(null);
|
||||
});
|
||||
|
||||
it("should use a default thumbnail if one is not supplied", function() {
|
||||
view = mountTestComponent({
|
||||
url: "http://wonderful.invalid"
|
||||
});
|
||||
|
||||
expect(view.getDOMNode().querySelector(".context-preview").getAttribute("src"))
|
||||
.eql("shared/img/icons-16x16.svg#globe");
|
||||
});
|
||||
|
||||
it("should use a default thumbnail for desktop if one is not supplied", function() {
|
||||
view = mountTestComponent({
|
||||
useDesktopPaths: true,
|
||||
url: "http://wonderful.invalid"
|
||||
});
|
||||
|
||||
expect(view.getDOMNode().querySelector(".context-preview").getAttribute("src"))
|
||||
.eql("loop/shared/img/icons-16x16.svg#globe");
|
||||
});
|
||||
|
||||
it("should not display a title if by default", function() {
|
||||
view = mountTestComponent({
|
||||
url: "http://wonderful.invalid"
|
||||
});
|
||||
|
||||
expect(view.getDOMNode().querySelector(".context-content > p")).eql(null);
|
||||
});
|
||||
|
||||
it("should display a title if required", function() {
|
||||
view = mountTestComponent({
|
||||
showContextTitle: true,
|
||||
url: "http://wonderful.invalid"
|
||||
});
|
||||
|
||||
expect(view.getDOMNode().querySelector(".context-content > p")).not.eql(null);
|
||||
});
|
||||
|
||||
it("should set the href on the link if clicks are allowed", function() {
|
||||
view = mountTestComponent({
|
||||
allowClick: true,
|
||||
url: "http://wonderful.invalid"
|
||||
});
|
||||
|
||||
expect(view.getDOMNode().querySelector(".context-url").href)
|
||||
.eql("http://wonderful.invalid/");
|
||||
});
|
||||
|
||||
it("should dispatch an action to record link clicks", function() {
|
||||
view = mountTestComponent({
|
||||
allowClick: true,
|
||||
url: "http://wonderful.invalid"
|
||||
});
|
||||
|
||||
var linkNode = view.getDOMNode().querySelector(".context-url");
|
||||
|
||||
TestUtils.Simulate.click(linkNode);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWith(dispatcher.dispatch,
|
||||
new sharedActions.RecordClick({
|
||||
linkInfo: "Shared URL"
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("MediaView", function() {
|
||||
var view;
|
||||
|
||||
|
@ -151,3 +151,8 @@ body {
|
||||
margin-left: .5rem;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Temporary until bug 1168829 is completed */
|
||||
.standalone.text-chat-example .text-chat-view {
|
||||
height: 400px;
|
||||
}
|
||||
|
@ -11,6 +11,8 @@
|
||||
document.removeEventListener("DOMContentLoaded", loop.panel.init);
|
||||
document.removeEventListener("DOMContentLoaded", loop.conversation.init);
|
||||
|
||||
var sharedActions = loop.shared.actions;
|
||||
|
||||
// 1. Desktop components
|
||||
// 1.1 Panel
|
||||
var PanelView = loop.panel.PanelView;
|
||||
@ -32,6 +34,7 @@
|
||||
var ConversationToolbar = loop.shared.views.ConversationToolbar;
|
||||
var FeedbackView = loop.shared.views.FeedbackView;
|
||||
var Checkbox = loop.shared.views.Checkbox;
|
||||
var TextChatView = loop.shared.views.TextChatView;
|
||||
|
||||
// Store constants
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
@ -254,6 +257,18 @@
|
||||
textChatEnabled: false
|
||||
});
|
||||
|
||||
// Update the text chat store with the room info.
|
||||
textChatStore.updateRoomInfo(new sharedActions.UpdateRoomInfo({
|
||||
roomName: "A Very Long Conversation Name",
|
||||
roomOwner: "fake",
|
||||
roomUrl: "http://showcase",
|
||||
urls: [{
|
||||
description: "A wonderful page!",
|
||||
location: "http://wonderful.invalid"
|
||||
// use the fallback thumbnail
|
||||
}]
|
||||
}));
|
||||
|
||||
loop.store.StoreMixin.register({
|
||||
conversationStore: conversationStore,
|
||||
feedbackStore: feedbackStore,
|
||||
@ -937,6 +952,18 @@
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement(Section, {name: "TextChatView (standalone)"},
|
||||
React.createElement(FramedExample, {width: 200, height: 400,
|
||||
summary: "Standalone Text Chat conversation (200 x 400)"},
|
||||
React.createElement("div", {className: "standalone text-chat-example"},
|
||||
React.createElement(TextChatView, {
|
||||
dispatcher: dispatcher,
|
||||
showAlways: true,
|
||||
showRoomName: true})
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement(Section, {name: "SVG icons preview", className: "svg-icons"},
|
||||
React.createElement(Example, {summary: "10x10"},
|
||||
React.createElement(SVGIcons, {size: "10x10"})
|
||||
|
@ -11,6 +11,8 @@
|
||||
document.removeEventListener("DOMContentLoaded", loop.panel.init);
|
||||
document.removeEventListener("DOMContentLoaded", loop.conversation.init);
|
||||
|
||||
var sharedActions = loop.shared.actions;
|
||||
|
||||
// 1. Desktop components
|
||||
// 1.1 Panel
|
||||
var PanelView = loop.panel.PanelView;
|
||||
@ -32,6 +34,7 @@
|
||||
var ConversationToolbar = loop.shared.views.ConversationToolbar;
|
||||
var FeedbackView = loop.shared.views.FeedbackView;
|
||||
var Checkbox = loop.shared.views.Checkbox;
|
||||
var TextChatView = loop.shared.views.TextChatView;
|
||||
|
||||
// Store constants
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
@ -254,6 +257,18 @@
|
||||
textChatEnabled: false
|
||||
});
|
||||
|
||||
// Update the text chat store with the room info.
|
||||
textChatStore.updateRoomInfo(new sharedActions.UpdateRoomInfo({
|
||||
roomName: "A Very Long Conversation Name",
|
||||
roomOwner: "fake",
|
||||
roomUrl: "http://showcase",
|
||||
urls: [{
|
||||
description: "A wonderful page!",
|
||||
location: "http://wonderful.invalid"
|
||||
// use the fallback thumbnail
|
||||
}]
|
||||
}));
|
||||
|
||||
loop.store.StoreMixin.register({
|
||||
conversationStore: conversationStore,
|
||||
feedbackStore: feedbackStore,
|
||||
@ -937,6 +952,18 @@
|
||||
</FramedExample>
|
||||
</Section>
|
||||
|
||||
<Section name="TextChatView (standalone)">
|
||||
<FramedExample width={200} height={400}
|
||||
summary="Standalone Text Chat conversation (200 x 400)">
|
||||
<div className="standalone text-chat-example">
|
||||
<TextChatView
|
||||
dispatcher={dispatcher}
|
||||
showAlways={true}
|
||||
showRoomName={true} />
|
||||
</div>
|
||||
</FramedExample>
|
||||
</Section>
|
||||
|
||||
<Section name="SVG icons preview" className="svg-icons">
|
||||
<Example summary="10x10">
|
||||
<SVGIcons size="10x10"/>
|
||||
|
@ -61,7 +61,9 @@ let TabAttributesInternal = {
|
||||
|
||||
// Set attributes.
|
||||
for (let name in data) {
|
||||
tab.setAttribute(name, data[name]);
|
||||
if (!this._skipAttrs.has(name)) {
|
||||
tab.setAttribute(name, data[name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -233,7 +233,7 @@ let TabStateInternal = {
|
||||
if (value.hasOwnProperty("index")) {
|
||||
tabData.index = value.index;
|
||||
}
|
||||
} else if (value) {
|
||||
} else {
|
||||
tabData[key] = value;
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,8 @@ add_task(function* test() {
|
||||
// Make sure we're backwards compatible and restore old 'image' attributes.
|
||||
let state = {
|
||||
entries: [{url: "about:mozilla"}],
|
||||
attributes: {custom: "foobaz", image: gBrowser.getIcon(tab)}
|
||||
attributes: {custom: "foobaz"},
|
||||
image: gBrowser.getIcon(tab)
|
||||
};
|
||||
|
||||
// Prepare a pending tab waiting to be restored.
|
||||
@ -45,7 +46,8 @@ add_task(function* test() {
|
||||
yield promise;
|
||||
|
||||
ok(tab.hasAttribute("pending"), "tab is pending");
|
||||
is(gBrowser.getIcon(tab), state.attributes.image, "tab has correct icon");
|
||||
is(gBrowser.getIcon(tab), state.image, "tab has correct icon");
|
||||
ok(!state.attributes.image, "'image' attribute not saved");
|
||||
|
||||
// Let the pending tab load.
|
||||
gBrowser.selectedTab = tab;
|
||||
|
@ -27,6 +27,9 @@ add_task(function* () {
|
||||
let tabState = TabState.collect(tab);
|
||||
is(tabState.index, TAB_STATE.index, "correct shistory index");
|
||||
|
||||
// Check we don't collect userTypedValue when we shouldn't.
|
||||
ok(!tabState.userTypedValue, "tab didn't have a userTypedValue");
|
||||
|
||||
// Cleanup.
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
@ -45,6 +45,7 @@ support-files =
|
||||
code_ugly-8^headers^
|
||||
code_WorkerActor.attach-worker1.js
|
||||
code_WorkerActor.attach-worker2.js
|
||||
code_WorkerActor.attachThread-worker.js
|
||||
doc_auto-pretty-print-01.html
|
||||
doc_auto-pretty-print-02.html
|
||||
doc_binary_search.html
|
||||
@ -107,6 +108,7 @@ support-files =
|
||||
doc_with-frame.html
|
||||
doc_WorkerActor.attach-tab1.html
|
||||
doc_WorkerActor.attach-tab2.html
|
||||
doc_WorkerActor.attachThread-tab.html
|
||||
head.js
|
||||
sjs_random-javascript.sjs
|
||||
testactors.js
|
||||
@ -566,3 +568,5 @@ skip-if = e10s && debug
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_WorkerActor.attach.js]
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_WorkerActor.attachThread.js]
|
||||
skip-if = e10s && debug
|
||||
|
@ -27,7 +27,6 @@ function test() {
|
||||
// registered. Instead, we have to wait for the promise returned by
|
||||
// createWorker in the tab to be resolved.
|
||||
yield createWorkerInTab(tab, WORKER1_URL);
|
||||
|
||||
let { workers } = yield listWorkers(tabClient);
|
||||
let [, workerClient1] = yield attachWorker(tabClient,
|
||||
findWorker(workers, WORKER1_URL));
|
||||
|
@ -0,0 +1,89 @@
|
||||
let TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html";
|
||||
let WORKER_URL = "code_WorkerActor.attachThread-worker.js";
|
||||
|
||||
function test() {
|
||||
Task.spawn(function* () {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
|
||||
let client1 = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
yield connect(client1);
|
||||
let client2 = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
yield connect(client2);
|
||||
|
||||
let tab = yield addTab(TAB_URL);
|
||||
let { tabs: tabs1 } = yield listTabs(client1);
|
||||
let [, tabClient1] = yield attachTab(client1, findTab(tabs1, TAB_URL));
|
||||
let { tabs: tabs2 } = yield listTabs(client2);
|
||||
let [, tabClient2] = yield attachTab(client2, findTab(tabs2, TAB_URL));
|
||||
|
||||
yield listWorkers(tabClient1);
|
||||
yield listWorkers(tabClient2);
|
||||
yield createWorkerInTab(tab, WORKER_URL);
|
||||
let { workers: workers1 } = yield listWorkers(tabClient1);
|
||||
let [, workerClient1] = yield attachWorker(tabClient1,
|
||||
findWorker(workers1, WORKER_URL));
|
||||
let { workers: workers2 } = yield listWorkers(tabClient2);
|
||||
let [, workerClient2] = yield attachWorker(tabClient2,
|
||||
findWorker(workers2, WORKER_URL));
|
||||
|
||||
let location = { line: 5 };
|
||||
|
||||
let [, threadClient1] = yield attachThread(workerClient1);
|
||||
let sources1 = yield getSources(threadClient1);
|
||||
let sourceClient1 = threadClient1.source(findSource(sources1,
|
||||
EXAMPLE_URL + WORKER_URL));
|
||||
let [, breakpointClient1] = yield setBreakpoint(sourceClient1, location);
|
||||
yield resume(threadClient1);
|
||||
|
||||
let [, threadClient2] = yield attachThread(workerClient2);
|
||||
let sources2 = yield getSources(threadClient2);
|
||||
let sourceClient2 = threadClient2.source(findSource(sources2,
|
||||
EXAMPLE_URL + WORKER_URL));
|
||||
let [, breakpointClient2] = yield setBreakpoint(sourceClient2, location);
|
||||
yield resume(threadClient2);
|
||||
|
||||
postMessageToWorkerInTab(tab, WORKER_URL, "ping");
|
||||
yield Promise.all([
|
||||
waitForPause(threadClient1).then((packet) => {
|
||||
is(packet.type, "paused");
|
||||
let why = packet.why;
|
||||
is(why.type, "breakpoint");
|
||||
is(why.actors.length, 1);
|
||||
is(why.actors[0], breakpointClient1.actor);
|
||||
let frame = packet.frame;
|
||||
let where = frame.where;
|
||||
is(where.source.actor, sourceClient1.actor);
|
||||
is(where.line, location.line);
|
||||
let variables = frame.environment.bindings.variables;
|
||||
is(variables.a.value, 1);
|
||||
is(variables.b.value.type, "undefined");
|
||||
is(variables.c.value.type, "undefined");
|
||||
return resume(threadClient1);
|
||||
}),
|
||||
waitForPause(threadClient2).then((packet) => {
|
||||
is(packet.type, "paused");
|
||||
let why = packet.why;
|
||||
is(why.type, "breakpoint");
|
||||
is(why.actors.length, 1);
|
||||
is(why.actors[0], breakpointClient2.actor);
|
||||
let frame = packet.frame;
|
||||
let where = frame.where;
|
||||
is(where.source.actor, sourceClient2.actor);
|
||||
is(where.line, location.line);
|
||||
let variables = frame.environment.bindings.variables;
|
||||
is(variables.a.value, 1);
|
||||
is(variables.b.value.type, "undefined");
|
||||
is(variables.c.value.type, "undefined");
|
||||
return resume(threadClient2);
|
||||
}),
|
||||
]);
|
||||
|
||||
terminateWorkerInTab(tab, WORKER_URL);
|
||||
yield waitForWorkerClose(workerClient1);
|
||||
yield waitForWorkerClose(workerClient2);
|
||||
yield close(client1);
|
||||
yield close(client2);
|
||||
finish();
|
||||
});
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
|
||||
function f() {
|
||||
var a = 1;
|
||||
var b = 2;
|
||||
var c = 3;
|
||||
}
|
||||
|
||||
self.onmessage = function (event) {
|
||||
if (event.data == "ping") {
|
||||
f()
|
||||
postMessage("pong");
|
||||
}
|
||||
};
|
||||
|
||||
postMessage("load");
|
@ -83,3 +83,15 @@ addMessageListener("jsonrpc", function ({ data: { method, params, id } }) {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
addMessageListener("test:postMessageToWorker", function (message) {
|
||||
dump("Posting message '" + message.data.message + "' to worker with url '" +
|
||||
message.data.url + "'.\n");
|
||||
|
||||
let worker = workers[message.data.url];
|
||||
worker.postMessage(message.data.message);
|
||||
worker.addEventListener("message", function listener() {
|
||||
worker.removeEventListener("message", listener);
|
||||
sendAsyncMessage("test:postMessageToWorker");
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -512,9 +512,13 @@ function getTab(aTarget, aWindow) {
|
||||
}
|
||||
|
||||
function getSources(aClient) {
|
||||
info("Getting sources.");
|
||||
|
||||
let deferred = promise.defer();
|
||||
|
||||
aClient.getSources(({sources}) => deferred.resolve(sources));
|
||||
aClient.getSources((packet) => {
|
||||
deferred.resolve(packet.sources);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
@ -1129,6 +1133,15 @@ function waitForWorkerListChanged(tabClient) {
|
||||
});
|
||||
}
|
||||
|
||||
function attachThread(workerClient, options) {
|
||||
info("Attaching to thread.");
|
||||
return new Promise(function(resolve, reject) {
|
||||
workerClient.attachThread(options, function (response, threadClient) {
|
||||
resolve([response, threadClient]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function waitForWorkerClose(workerClient) {
|
||||
info("Waiting for worker to close.");
|
||||
return new Promise(function (resolve) {
|
||||
@ -1156,3 +1169,52 @@ function waitForWorkerThaw(workerClient) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function resume(threadClient) {
|
||||
info("Resuming thread.");
|
||||
return rdpInvoke(threadClient, threadClient.resume);
|
||||
}
|
||||
|
||||
function findSource(sources, url) {
|
||||
info("Finding source with url '" + url + "'.\n");
|
||||
for (let source of sources) {
|
||||
if (source.url === url) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function setBreakpoint(sourceClient, location) {
|
||||
info("Setting breakpoint.\n");
|
||||
return new Promise(function (resolve) {
|
||||
sourceClient.setBreakpoint(location, function (response, breakpointClient) {
|
||||
resolve([response, breakpointClient]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function waitForEvent(client, type, predicate) {
|
||||
return new Promise(function (resolve) {
|
||||
function listener(type, packet) {
|
||||
if (!predicate(packet)) {
|
||||
return;
|
||||
}
|
||||
client.removeListener(listener);
|
||||
resolve(packet);
|
||||
}
|
||||
|
||||
if (predicate) {
|
||||
client.addListener(type, listener);
|
||||
} else {
|
||||
client.addOneTimeListener(type, function (type, packet) {
|
||||
resolve(packet);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function waitForPause(threadClient) {
|
||||
info("Waiting for pause.\n");
|
||||
return waitForEvent(threadClient, "paused");
|
||||
}
|
||||
|
@ -31,7 +31,6 @@ browser.jar:
|
||||
content/browser/devtools/ruleview.css (styleinspector/ruleview.css)
|
||||
content/browser/devtools/layoutview/view.js (layoutview/view.js)
|
||||
content/browser/devtools/layoutview/view.xhtml (layoutview/view.xhtml)
|
||||
content/browser/devtools/layoutview/view.css (layoutview/view.css)
|
||||
content/browser/devtools/fontinspector/font-inspector.js (fontinspector/font-inspector.js)
|
||||
content/browser/devtools/fontinspector/font-inspector.xhtml (fontinspector/font-inspector.xhtml)
|
||||
content/browser/devtools/fontinspector/font-inspector.css (fontinspector/font-inspector.css)
|
||||
|
@ -1,266 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
body {
|
||||
max-width: 320px;
|
||||
position: relative;
|
||||
margin: 0px auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#header {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 4px 13px;
|
||||
display: -moz-box;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#header:-moz-dir(rtl) {
|
||||
-moz-box-direction: reverse;
|
||||
}
|
||||
|
||||
#header > span {
|
||||
display: -moz-box;
|
||||
}
|
||||
|
||||
#element-size {
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
|
||||
#element-size:-moz-dir(rtl) {
|
||||
-moz-box-pack: end;
|
||||
}
|
||||
|
||||
#main {
|
||||
margin: 0 14px 10px 14px;
|
||||
box-sizing: border-box;
|
||||
width: calc(100% - 2 * 14px);
|
||||
position: absolute;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
#content,
|
||||
#borders {
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
#content {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
#margins,
|
||||
#padding {
|
||||
border-style: solid;
|
||||
border-width: 25px;
|
||||
}
|
||||
|
||||
#borders {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.legend {
|
||||
position: absolute;
|
||||
margin: 5px 6px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.legend[data-box="margin"] {
|
||||
color: var(--theme-highlight-blue);
|
||||
}
|
||||
|
||||
#main > p {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#main > p {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#main > p > span {
|
||||
vertical-align: middle;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.size > span {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.editable {
|
||||
-moz-user-select: text;
|
||||
}
|
||||
|
||||
.top,
|
||||
.bottom {
|
||||
width: calc(100% - 2px);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.padding.top {
|
||||
top: 55px;
|
||||
}
|
||||
|
||||
.padding.bottom {
|
||||
bottom: 57px;
|
||||
}
|
||||
|
||||
.border.top {
|
||||
top: 30px;
|
||||
}
|
||||
|
||||
.border.bottom {
|
||||
bottom: 31px;
|
||||
}
|
||||
|
||||
.margin.top {
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.margin.bottom {
|
||||
bottom: 6px;
|
||||
}
|
||||
|
||||
.size,
|
||||
.margin.left,
|
||||
.margin.right,
|
||||
.border.left,
|
||||
.border.right,
|
||||
.padding.left,
|
||||
.padding.right {
|
||||
top: 22px;
|
||||
line-height: 132px;
|
||||
}
|
||||
|
||||
.size {
|
||||
width: calc(100% - 2px);
|
||||
}
|
||||
|
||||
.margin.right,
|
||||
.margin.left,
|
||||
.border.left,
|
||||
.border.right,
|
||||
.padding.right,
|
||||
.padding.left {
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.padding.left {
|
||||
left: 52px;
|
||||
}
|
||||
|
||||
.padding.right {
|
||||
right: 51px;
|
||||
}
|
||||
|
||||
.border.left {
|
||||
left: 26px;
|
||||
}
|
||||
|
||||
.border.right {
|
||||
right: 26px;
|
||||
}
|
||||
|
||||
.margin.right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.margin.left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.rotate.left:not(.editing) {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.rotate.right:not(.editing) {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
|
||||
body.dim > #header > #element-position,
|
||||
body.dim > #main > p {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@media (max-height: 228px) {
|
||||
#header {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#margins,
|
||||
#padding {
|
||||
border-width: 21px;
|
||||
}
|
||||
#borders {
|
||||
padding: 21px;
|
||||
}
|
||||
|
||||
#content {
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
.padding.top {
|
||||
top: 46px;
|
||||
}
|
||||
|
||||
.padding.bottom {
|
||||
bottom: 46px;
|
||||
}
|
||||
|
||||
.border.top {
|
||||
top: 25px;
|
||||
}
|
||||
|
||||
.border.bottom {
|
||||
bottom: 25px;
|
||||
}
|
||||
|
||||
.margin.top {
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.margin.bottom {
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
.size,
|
||||
.margin.left,
|
||||
.margin.right,
|
||||
.border.left,
|
||||
.border.right,
|
||||
.padding.left,
|
||||
.padding.right {
|
||||
line-height: 106px;
|
||||
}
|
||||
|
||||
.margin.right,
|
||||
.margin.left,
|
||||
.border.left,
|
||||
.border.right,
|
||||
.padding.right,
|
||||
.padding.left {
|
||||
width: 21px;
|
||||
}
|
||||
|
||||
.padding.left {
|
||||
left: 43px;
|
||||
}
|
||||
|
||||
.padding.right {
|
||||
right: 43px;
|
||||
}
|
||||
|
||||
.border.left {
|
||||
left: 22px;
|
||||
}
|
||||
|
||||
.border.right {
|
||||
right: 22px;
|
||||
}
|
||||
}
|
@ -3,25 +3,23 @@
|
||||
/* 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/. */
|
||||
/* globals ViewHelpers, window, document */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
const {utils: Cu, interfaces: Ci, classes: Cc} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
const {InplaceEditor, editableItem} = devtools.require("devtools/shared/inplace-editor");
|
||||
const {parseDeclarations} = devtools.require("devtools/styleinspector/css-parsing-utils");
|
||||
const {ReflowFront} = devtools.require("devtools/server/actors/layout");
|
||||
const {require} = devtools;
|
||||
const {InplaceEditor, editableItem} = require("devtools/shared/inplace-editor");
|
||||
const {ReflowFront} = require("devtools/server/actors/layout");
|
||||
|
||||
const SHARED_L10N = new ViewHelpers.L10N("chrome://browser/locale/devtools/shared.properties");
|
||||
const STRINGS_URI = "chrome://browser/locale/devtools/shared.properties";
|
||||
const SHARED_L10N = new ViewHelpers.L10N(STRINGS_URI);
|
||||
const NUMERIC = /^-?[\d\.]+$/;
|
||||
const LONG_TEXT_ROTATE_LIMIT = 3;
|
||||
|
||||
@ -205,13 +203,14 @@ LayoutView.prototype = {
|
||||
value: undefined},
|
||||
borderRight: {selector: ".border.right > span",
|
||||
property: "border-right-width",
|
||||
value: undefined},
|
||||
value: undefined}
|
||||
};
|
||||
|
||||
// Make each element the dimensions editable
|
||||
for (let i in this.map) {
|
||||
if (i == "position")
|
||||
if (i == "position") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dimension = this.map[i];
|
||||
editableItem({
|
||||
@ -231,7 +230,8 @@ LayoutView.prototype = {
|
||||
if (!this.reflowFront) {
|
||||
let toolbox = this.inspector.toolbox;
|
||||
if (toolbox.target.form.reflowActor) {
|
||||
this.reflowFront = ReflowFront(toolbox.target.client, toolbox.target.form);
|
||||
this.reflowFront = ReflowFront(toolbox.target.client,
|
||||
toolbox.target.form);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@ -265,11 +265,11 @@ LayoutView.prototype = {
|
||||
element: element,
|
||||
initial: initialValue,
|
||||
|
||||
start: (editor) => {
|
||||
start: editor => {
|
||||
editor.elt.parentNode.classList.add("editing");
|
||||
},
|
||||
|
||||
change: (value) => {
|
||||
change: value => {
|
||||
if (NUMERIC.test(value)) {
|
||||
value += "px";
|
||||
}
|
||||
@ -300,13 +300,25 @@ LayoutView.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the layoutview visible in the sidebar?
|
||||
* Is the layoutview visible in the sidebar.
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isActive: function() {
|
||||
isViewVisible: function() {
|
||||
return this.inspector &&
|
||||
this.inspector.sidebar.getCurrentTabID() == "layoutview";
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the layoutview visible in the sidebar and is the current node valid to
|
||||
* be displayed in the view.
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isViewVisibleAndNodeValid: function() {
|
||||
return this.isViewVisible() &&
|
||||
this.inspector.selection.isConnected() &&
|
||||
this.inspector.selection.isElementNode();
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the nodes. Remove listeners.
|
||||
*/
|
||||
@ -328,9 +340,7 @@ LayoutView.prototype = {
|
||||
},
|
||||
|
||||
onSidebarSelect: function(e, sidebar) {
|
||||
if (sidebar !== "layoutview") {
|
||||
this.dim();
|
||||
}
|
||||
this.setActive(sidebar === "layoutview");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -338,42 +348,37 @@ LayoutView.prototype = {
|
||||
*/
|
||||
onNewSelection: function() {
|
||||
let done = this.inspector.updating("layoutview");
|
||||
this.onNewNode().then(done, (err) => { console.error(err); done() });
|
||||
this.onNewNode().then(done, err => {
|
||||
console.error(err);
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @return a promise that resolves when the view has been updated
|
||||
*/
|
||||
onNewNode: function() {
|
||||
if (this.isActive() &&
|
||||
this.inspector.selection.isConnected() &&
|
||||
this.inspector.selection.isElementNode()) {
|
||||
this.undim();
|
||||
} else {
|
||||
this.dim();
|
||||
}
|
||||
|
||||
this.setActive(this.isViewVisibleAndNodeValid());
|
||||
return this.update();
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the layout boxes and stop refreshing on reflows. No node is selected
|
||||
* or the layout-view sidebar is inactive.
|
||||
* Stop tracking reflows and hide all values when no node is selected or the
|
||||
* layout-view is hidden, otherwise track reflows and show values.
|
||||
* @param {Boolean} isActive
|
||||
*/
|
||||
dim: function() {
|
||||
this.untrackReflows();
|
||||
this.doc.body.classList.add("dim");
|
||||
this.dimmed = true;
|
||||
},
|
||||
setActive: function(isActive) {
|
||||
if (isActive === this.isActive) {
|
||||
return;
|
||||
}
|
||||
this.isActive = isActive;
|
||||
|
||||
/**
|
||||
* Show the layout boxes and start refreshing on reflows. A node is selected
|
||||
* and the layout-view side is active.
|
||||
*/
|
||||
undim: function() {
|
||||
this.trackReflows();
|
||||
this.doc.body.classList.remove("dim");
|
||||
this.dimmed = false;
|
||||
this.doc.body.classList.toggle("inactive", !isActive);
|
||||
if (isActive) {
|
||||
this.trackReflows();
|
||||
} else {
|
||||
this.untrackReflows();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -383,15 +388,13 @@ LayoutView.prototype = {
|
||||
*/
|
||||
update: function() {
|
||||
let lastRequest = Task.spawn((function*() {
|
||||
if (!this.isActive() ||
|
||||
!this.inspector.selection.isConnected() ||
|
||||
!this.inspector.selection.isElementNode()) {
|
||||
if (!this.isViewVisibleAndNodeValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let node = this.inspector.selection.nodeFront;
|
||||
let layout = yield this.inspector.pageStyle.getLayout(node, {
|
||||
autoMargins: !this.dimmed
|
||||
autoMargins: this.isActive
|
||||
});
|
||||
let styleEntries = yield this.inspector.pageStyle.getApplied(node, {});
|
||||
|
||||
@ -409,8 +412,8 @@ LayoutView.prototype = {
|
||||
this.sizeHeadingLabel.textContent = newLabel;
|
||||
}
|
||||
|
||||
// If the view is dimmed, no need to do anything more.
|
||||
if (this.dimmed) {
|
||||
// If the view isn't active, no need to do anything more.
|
||||
if (!this.isActive) {
|
||||
this.inspector.emit("layoutview-updated");
|
||||
return null;
|
||||
}
|
||||
@ -433,10 +436,18 @@ LayoutView.prototype = {
|
||||
}
|
||||
|
||||
let margins = layout.autoMargins;
|
||||
if ("top" in margins) this.map.marginTop.value = "auto";
|
||||
if ("right" in margins) this.map.marginRight.value = "auto";
|
||||
if ("bottom" in margins) this.map.marginBottom.value = "auto";
|
||||
if ("left" in margins) this.map.marginLeft.value = "auto";
|
||||
if ("top" in margins) {
|
||||
this.map.marginTop.value = "auto";
|
||||
}
|
||||
if ("right" in margins) {
|
||||
this.map.marginRight.value = "auto";
|
||||
}
|
||||
if ("bottom" in margins) {
|
||||
this.map.marginBottom.value = "auto";
|
||||
}
|
||||
if ("left" in margins) {
|
||||
this.map.marginLeft.value = "auto";
|
||||
}
|
||||
|
||||
for (let i in this.map) {
|
||||
let selector = this.map[i].selector;
|
||||
@ -538,14 +549,17 @@ let elts;
|
||||
|
||||
let onmouseover = function(e) {
|
||||
let region = e.target.getAttribute("data-box");
|
||||
if (!region) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.layoutview.showBoxModel({region});
|
||||
|
||||
return false;
|
||||
}.bind(window);
|
||||
|
||||
let onmouseout = function(e) {
|
||||
let onmouseout = function() {
|
||||
this.layoutview.hideBoxModel();
|
||||
|
||||
return false;
|
||||
}.bind(window);
|
||||
|
||||
@ -561,8 +575,8 @@ window.setPanel = function(panel) {
|
||||
}
|
||||
|
||||
// Mark document as RTL or LTR:
|
||||
let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
|
||||
getService(Ci.nsIXULChromeRegistry);
|
||||
let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]
|
||||
.getService(Ci.nsIXULChromeRegistry);
|
||||
let dir = chromeReg.isLocaleRTL("global");
|
||||
document.body.setAttribute("dir", dir ? "rtl" : "ltr");
|
||||
|
||||
|
@ -19,7 +19,6 @@
|
||||
|
||||
<link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/devtools/layoutview.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="view.css" type="text/css"/>
|
||||
|
||||
</head>
|
||||
<body class="theme-sidebar devtools-monospace">
|
||||
|
@ -210,6 +210,19 @@ const JITOptimizations = function (rawSites, stringTable) {
|
||||
this.optimizationSites = sites.sort((a, b) => b.samples - a.samples);;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make JITOptimizations iterable.
|
||||
*/
|
||||
JITOptimizations.prototype = {
|
||||
[Symbol.iterator]: function *() {
|
||||
yield* this.optimizationSites;
|
||||
},
|
||||
|
||||
get length() {
|
||||
return this.optimizationSites.length;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes an "outcome" string from an OptimizationAttempt and returns
|
||||
* a boolean indicating whether or not its a successful outcome.
|
||||
|
@ -235,9 +235,9 @@ ThreadNode.prototype = {
|
||||
leafTable);
|
||||
if (isLeaf) {
|
||||
frameNode.youngestFrameSamples++;
|
||||
frameNode._addOptimizations(inflatedFrame.optimizations, stringTable);
|
||||
}
|
||||
frameNode.samples++;
|
||||
frameNode._addOptimizations(inflatedFrame.optimizations, stringTable);
|
||||
|
||||
prevFrameKey = frameKey;
|
||||
prevCalls = frameNode.calls;
|
||||
|
@ -31,12 +31,14 @@ function* spawnTest() {
|
||||
|
||||
yield injectAndRenderProfilerData();
|
||||
|
||||
// gRawSite1 and gRawSite2 are both optimizations on A, so they'll have
|
||||
// indices in descending order of # of samples.
|
||||
yield checkFrame(1, [{ i: 0, opt: gRawSite1 }, { i: 1, opt: gRawSite2 }]);
|
||||
// A is never a leaf, so it's optimizations should not be shown.
|
||||
yield checkFrame(1);
|
||||
|
||||
// gRawSite3 is the only optimization on B, so it'll have index 0.
|
||||
yield checkFrame(2, [{ i: 0, opt: gRawSite3 }]);
|
||||
// gRawSite2 and gRawSite3 are both optimizations on B, so they'll have
|
||||
// indices in descending order of # of samples.
|
||||
yield checkFrame(2, [{ i: 0, opt: gRawSite2 }, { i: 1, opt: gRawSite3 }]);
|
||||
|
||||
// Leaf node (C) with no optimizations should not display any opts.
|
||||
yield checkFrame(3);
|
||||
|
||||
let select = once(PerformanceController, EVENTS.RECORDING_SELECTED);
|
||||
@ -114,7 +116,7 @@ function* spawnTest() {
|
||||
|
||||
// The second and third optimization should display optimization failures.
|
||||
let warningIcon = $(`.tree-widget-container li[data-id='["${i}"]'] .opt-icon[severity=warning]`);
|
||||
if (opt === gRawSite2 || opt === gRawSite3) {
|
||||
if (opt === gRawSite3 || opt === gRawSite1) {
|
||||
ok(warningIcon, "did find a warning icon for all strategies failing.");
|
||||
} else {
|
||||
ok(!warningIcon, "did not find a warning icon for no successful strategies");
|
||||
@ -130,7 +132,7 @@ function uniqStr(s) {
|
||||
}
|
||||
|
||||
// Since deflateThread doesn't handle deflating optimization info, use
|
||||
// placeholder names A_O1, B_O3, and A_O2, which will be used to manually
|
||||
// placeholder names A_O1, B_O2, and B_O3, which will be used to manually
|
||||
// splice deduped opts into the profile.
|
||||
let gThread = RecordingUtils.deflateThread({
|
||||
samples: [{
|
||||
@ -143,7 +145,7 @@ let gThread = RecordingUtils.deflateThread({
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A_O1" },
|
||||
{ location: "B_O3" },
|
||||
{ location: "B_O2" },
|
||||
{ location: "C (http://foo/bar/baz:56)" }
|
||||
]
|
||||
}, {
|
||||
@ -151,14 +153,14 @@ let gThread = RecordingUtils.deflateThread({
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A (http://foo/bar/baz:12)" },
|
||||
{ location: "B (http://foo/bar/boo:34)" },
|
||||
{ location: "B_O2" },
|
||||
]
|
||||
}, {
|
||||
time: 5 + 1 + 2,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A_O2" },
|
||||
{ location: "B (http://foo/bar/boo:34)" },
|
||||
{ location: "A_O1" },
|
||||
{ location: "B_O3" },
|
||||
]
|
||||
}, {
|
||||
time: 5 + 1 + 2 + 7,
|
||||
@ -197,14 +199,14 @@ let gRawSite1 = {
|
||||
data: [
|
||||
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
|
||||
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
|
||||
[uniqStr("Inlined"), uniqStr("SomeGetter3")]
|
||||
[uniqStr("Failure3"), uniqStr("SomeGetter3")]
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
let gRawSite2 = {
|
||||
_testFrameInfo: { name: "A", line: "12", file: "@baz" },
|
||||
line: 12,
|
||||
_testFrameInfo: { name: "B", line: "10", file: "@boo" },
|
||||
line: 40,
|
||||
types: [{
|
||||
mirType: uniqStr("Int32"),
|
||||
site: uniqStr("Receiver")
|
||||
@ -217,13 +219,13 @@ let gRawSite2 = {
|
||||
data: [
|
||||
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
|
||||
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
|
||||
[uniqStr("Failure3"), uniqStr("SomeGetter3")]
|
||||
[uniqStr("Inlined"), uniqStr("SomeGetter3")]
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
let gRawSite3 = {
|
||||
_testFrameInfo: { name: "B", line: "34", file: "@boo" },
|
||||
_testFrameInfo: { name: "B", line: "10", file: "@boo" },
|
||||
line: 34,
|
||||
types: [{
|
||||
mirType: uniqStr("Int32"),
|
||||
@ -252,12 +254,12 @@ gThread.frameTable.data.forEach((frame) => {
|
||||
frame[LOCATION_SLOT] = uniqStr("A (http://foo/bar/baz:12)");
|
||||
frame[OPTIMIZATIONS_SLOT] = gRawSite1;
|
||||
break;
|
||||
case "A_O2":
|
||||
frame[LOCATION_SLOT] = uniqStr("A (http://foo/bar/baz:12)");
|
||||
case "B_O2":
|
||||
frame[LOCATION_SLOT] = uniqStr("B (http://foo/bar/boo:10)");
|
||||
frame[OPTIMIZATIONS_SLOT] = gRawSite2;
|
||||
break;
|
||||
case "B_O3":
|
||||
frame[LOCATION_SLOT] = uniqStr("B (http://foo/bar/boo:34)");
|
||||
frame[LOCATION_SLOT] = uniqStr("B (http://foo/bar/boo:10)");
|
||||
frame[OPTIMIZATIONS_SLOT] = gRawSite3;
|
||||
break;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
|
||||
let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||
let { console } = devtools.require("resource://gre/modules/devtools/Console.jsm");
|
||||
let { merge } = devtools.require("sdk/util/object");
|
||||
let { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
let { getPerformanceFront, PerformanceFront } = devtools.require("devtools/performance/front");
|
||||
|
@ -6,6 +6,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
let { console } = devtools.require("resource://gre/modules/devtools/Console.jsm");
|
||||
const RecordingUtils = devtools.require("devtools/performance/recording-utils");
|
||||
|
||||
const PLATFORM_DATA_PREF = "devtools.performance.ui.show-platform-data";
|
||||
|
@ -3,17 +3,57 @@
|
||||
|
||||
/**
|
||||
* Tests that when constructing FrameNodes, if optimization data is available,
|
||||
* the FrameNodes have the correct optimization data after iterating over samples.
|
||||
* the FrameNodes have the correct optimization data after iterating over samples,
|
||||
* and only youngest frames capture optimization data.
|
||||
*/
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function test() {
|
||||
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
|
||||
let root = getFrameNodePath(new ThreadNode(gThread, { startTime: 0, endTime: 30 }), "(root)");
|
||||
|
||||
let A = getFrameNodePath(root, "A");
|
||||
let B = getFrameNodePath(A, "B");
|
||||
let C = getFrameNodePath(B, "C");
|
||||
let Aopts = A.getOptimizations();
|
||||
let Bopts = B.getOptimizations();
|
||||
let Copts = C.getOptimizations();
|
||||
|
||||
ok(!Aopts, "A() was never youngest frame, so should not have optimization data");
|
||||
|
||||
equal(Bopts.length, 2, "B() only has optimization data when it was a youngest frame");
|
||||
|
||||
// Check a few properties on the OptimizationSites.
|
||||
let optSitesObserved = new Set();
|
||||
for (let opt of Bopts) {
|
||||
if (opt.data.line === 12) {
|
||||
equal(opt.samples, 2, "Correct amount of samples for B()'s first opt site");
|
||||
equal(opt.data.attempts.length, 3, "First opt site has 3 attempts");
|
||||
equal(opt.data.attempts[0].strategy, "SomeGetter1", "inflated strategy name");
|
||||
equal(opt.data.attempts[0].outcome, "Failure1", "inflated outcome name");
|
||||
equal(opt.data.types[0].typeset[0].keyedBy, "constructor", "inflates type info");
|
||||
optSitesObserved.add("first");
|
||||
} else {
|
||||
equal(opt.samples, 1, "Correct amount of samples for B()'s second opt site");
|
||||
optSitesObserved.add("second");
|
||||
}
|
||||
}
|
||||
|
||||
ok(optSitesObserved.has("first"), "first opt site for B() was checked");
|
||||
ok(optSitesObserved.has("second"), "second opt site for B() was checked");
|
||||
|
||||
equal(Copts.length, 1, "C() always youngest frame, so has optimization data");
|
||||
});
|
||||
|
||||
let gUniqueStacks = new RecordingUtils.UniqueStacks();
|
||||
|
||||
function uniqStr(s) {
|
||||
return gUniqueStacks.getOrAddStringIndex(s);
|
||||
}
|
||||
|
||||
let time = 1;
|
||||
|
||||
let gThread = RecordingUtils.deflateThread({
|
||||
samples: [{
|
||||
time: 0,
|
||||
@ -21,52 +61,48 @@ let gThread = RecordingUtils.deflateThread({
|
||||
{ location: "(root)" }
|
||||
]
|
||||
}, {
|
||||
time: time++,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A_O1" },
|
||||
{ location: "B" },
|
||||
{ location: "C" }
|
||||
]
|
||||
}, {
|
||||
time: time++,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A_O1" },
|
||||
{ location: "D" },
|
||||
{ location: "C" }
|
||||
]
|
||||
}, {
|
||||
time: time++,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A_O2" },
|
||||
{ location: "E_O3" },
|
||||
{ location: "C" }
|
||||
],
|
||||
}, {
|
||||
time: time++,
|
||||
time: 10,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A" },
|
||||
{ location: "B" },
|
||||
{ location: "F" }
|
||||
{ location: "B_LEAF_1" }
|
||||
]
|
||||
}, {
|
||||
time: 15,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A" },
|
||||
{ location: "B_NOTLEAF" },
|
||||
{ location: "C" },
|
||||
]
|
||||
}, {
|
||||
time: 20,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A" },
|
||||
{ location: "B_LEAF_2" }
|
||||
]
|
||||
}, {
|
||||
time: 25,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A" },
|
||||
{ location: "B_LEAF_2" }
|
||||
]
|
||||
}],
|
||||
markers: []
|
||||
}, gUniqueStacks);
|
||||
|
||||
// 3 OptimizationSites
|
||||
let gRawSite1 = {
|
||||
line: 12,
|
||||
column: 2,
|
||||
types: [{
|
||||
mirType: uniqStr("Object"),
|
||||
site: uniqStr("A (http://foo/bar/bar:12)"),
|
||||
site: uniqStr("B (http://foo/bar:10)"),
|
||||
typeset: [{
|
||||
keyedBy: uniqStr("constructor"),
|
||||
name: uniqStr("Foo"),
|
||||
location: uniqStr("A (http://foo/bar/baz:12)")
|
||||
location: uniqStr("B (http://foo/bar:10)")
|
||||
}, {
|
||||
keyedBy: uniqStr("primitive"),
|
||||
location: uniqStr("self-hosted")
|
||||
@ -86,7 +122,7 @@ let gRawSite1 = {
|
||||
};
|
||||
|
||||
let gRawSite2 = {
|
||||
line: 34,
|
||||
line: 22,
|
||||
types: [{
|
||||
mirType: uniqStr("Int32"),
|
||||
site: uniqStr("Receiver")
|
||||
@ -104,32 +140,9 @@ let gRawSite2 = {
|
||||
}
|
||||
};
|
||||
|
||||
let gRawSite3 = {
|
||||
line: 78,
|
||||
types: [{
|
||||
mirType: uniqStr("Object"),
|
||||
site: uniqStr("A (http://foo/bar/bar:12)"),
|
||||
typeset: [{
|
||||
keyedBy: uniqStr("constructor"),
|
||||
name: uniqStr("Foo"),
|
||||
location: uniqStr("A (http://foo/bar/baz:12)")
|
||||
}, {
|
||||
keyedBy: uniqStr("primitive"),
|
||||
location: uniqStr("self-hosted")
|
||||
}]
|
||||
}],
|
||||
attempts: {
|
||||
schema: {
|
||||
outcome: 0,
|
||||
strategy: 1
|
||||
},
|
||||
data: [
|
||||
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
|
||||
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
|
||||
[uniqStr("GenericSuccess"), uniqStr("SomeGetter3")]
|
||||
]
|
||||
}
|
||||
};
|
||||
function serialize (x) {
|
||||
return JSON.parse(JSON.stringify(x));
|
||||
}
|
||||
|
||||
gThread.frameTable.data.forEach((frame) => {
|
||||
const LOCATION_SLOT = gThread.frameTable.schema.location;
|
||||
@ -137,45 +150,25 @@ gThread.frameTable.data.forEach((frame) => {
|
||||
|
||||
let l = gThread.stringTable[frame[LOCATION_SLOT]];
|
||||
switch (l) {
|
||||
case "A_O1":
|
||||
frame[LOCATION_SLOT] = uniqStr("A");
|
||||
frame[OPTIMIZATIONS_SLOT] = gRawSite1;
|
||||
case "A":
|
||||
frame[OPTIMIZATIONS_SLOT] = serialize(gRawSite1);
|
||||
break;
|
||||
case "A_O2":
|
||||
frame[LOCATION_SLOT] = uniqStr("A");
|
||||
frame[OPTIMIZATIONS_SLOT] = gRawSite2;
|
||||
// Rename some of the location sites so we can register different
|
||||
// frames with different opt sites
|
||||
case "B_LEAF_1":
|
||||
frame[OPTIMIZATIONS_SLOT] = serialize(gRawSite2);
|
||||
frame[LOCATION_SLOT] = uniqStr("B");
|
||||
break;
|
||||
case "E_O3":
|
||||
frame[LOCATION_SLOT] = uniqStr("E");
|
||||
frame[OPTIMIZATIONS_SLOT] = gRawSite3;
|
||||
case "B_LEAF_2":
|
||||
frame[OPTIMIZATIONS_SLOT] = serialize(gRawSite1);
|
||||
frame[LOCATION_SLOT] = uniqStr("B");
|
||||
break;
|
||||
case "B_NOTLEAF":
|
||||
frame[OPTIMIZATIONS_SLOT] = serialize(gRawSite1);
|
||||
frame[LOCATION_SLOT] = uniqStr("B");
|
||||
break;
|
||||
case "C":
|
||||
frame[OPTIMIZATIONS_SLOT] = serialize(gRawSite1);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function test() {
|
||||
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
|
||||
|
||||
let root = new ThreadNode(gThread, { startTime: 0, endTime: 4 });
|
||||
|
||||
let A = getFrameNodePath(root, "(root) > A");
|
||||
|
||||
let opts = A.getOptimizations();
|
||||
let sites = opts.optimizationSites;
|
||||
equal(sites.length, 2, "Frame A has two optimization sites.");
|
||||
equal(sites[0].samples, 2, "first opt site has 2 samples.");
|
||||
equal(sites[1].samples, 1, "second opt site has 1 sample.");
|
||||
|
||||
let E = getFrameNodePath(A, "E");
|
||||
opts = E.getOptimizations();
|
||||
sites = opts.optimizationSites;
|
||||
equal(sites.length, 1, "Frame E has one optimization site.");
|
||||
equal(sites[0].samples, 1, "first opt site has 1 samples.");
|
||||
|
||||
let D = getFrameNodePath(A, "D");
|
||||
ok(!D.getOptimizations(),
|
||||
"frames that do not have any opts data do not have JITOptimizations instances.");
|
||||
});
|
||||
|
@ -111,15 +111,17 @@ diff --git a/browser/devtools/sourceeditor/codemirror/search/search.js b/browser
|
||||
+ if (!queryDialog) {
|
||||
+ let doc = cm.getWrapperElement().ownerDocument;
|
||||
+ let inp = doc.createElement("input");
|
||||
+ let txt = doc.createTextNode(cm.l10n("findCmd.promptMessage"));
|
||||
+
|
||||
+ inp.type = "text";
|
||||
+ inp.style.width = "10em";
|
||||
+ inp.type = "search";
|
||||
+ inp.placeholder = cm.l10n("findCmd.promptMessage");
|
||||
+ inp.style.MozMarginStart = "1em";
|
||||
+ inp.style.MozMarginEnd = "1em";
|
||||
+ inp.style.flexGrow = "1";
|
||||
+ inp.addEventListener("focus", () => inp.select());
|
||||
+
|
||||
+ queryDialog = doc.createElement("div");
|
||||
+ queryDialog.appendChild(txt);
|
||||
+ queryDialog.appendChild(inp);
|
||||
+ queryDialog.style.display = "flex";
|
||||
+ }
|
||||
var state = getSearchState(cm);
|
||||
if (state.query) return findNext(cm, rev);
|
||||
|
@ -75,15 +75,17 @@
|
||||
if (!queryDialog) {
|
||||
let doc = cm.getWrapperElement().ownerDocument;
|
||||
let inp = doc.createElement("input");
|
||||
let txt = doc.createTextNode(cm.l10n("findCmd.promptMessage"));
|
||||
|
||||
inp.type = "text";
|
||||
inp.style.width = "10em";
|
||||
inp.type = "search";
|
||||
inp.placeholder = cm.l10n("findCmd.promptMessage");
|
||||
inp.style.MozMarginStart = "1em";
|
||||
inp.style.MozMarginEnd = "1em";
|
||||
inp.style.flexGrow = "1";
|
||||
inp.addEventListener("focus", () => inp.select());
|
||||
|
||||
queryDialog = doc.createElement("div");
|
||||
queryDialog.appendChild(txt);
|
||||
queryDialog.appendChild(inp);
|
||||
queryDialog.style.display = "flex";
|
||||
}
|
||||
var state = getSearchState(cm);
|
||||
if (state.query) return findNext(cm, rev);
|
||||
|
@ -1,16 +1,67 @@
|
||||
/* 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/. */
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/ */
|
||||
|
||||
.theme-sidebar {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
/* The view will grow bigger as the window gets resized, until 400px */
|
||||
max-width: 400px;
|
||||
margin: 0px auto;
|
||||
padding: 0;
|
||||
/* "Contain" the absolutely positioned #main element */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Header: contains the position and size of the element */
|
||||
|
||||
#header {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 4px 14px;
|
||||
display: -moz-box;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#header:-moz-dir(rtl) {
|
||||
-moz-box-direction: reverse;
|
||||
}
|
||||
|
||||
#header > span {
|
||||
display: -moz-box;
|
||||
}
|
||||
|
||||
#element-size {
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
|
||||
#element-size:-moz-dir(rtl) {
|
||||
-moz-box-pack: end;
|
||||
}
|
||||
|
||||
@media (max-height: 228px) {
|
||||
#header {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Main: contains the box-model regions */
|
||||
|
||||
#main {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
/* The regions are semi-transparent, so the white background is partly
|
||||
visible */
|
||||
background-color: white;
|
||||
border-color: hsla(210,100%,85%,0.7);
|
||||
border-style: dotted;
|
||||
color: var(--theme-selection-color);
|
||||
/* Make sure there is some space between the window's edges and the regions */
|
||||
margin: 0 14px 10px 14px;
|
||||
width: calc(100% - 2 * 14px);
|
||||
}
|
||||
|
||||
.margin,
|
||||
@ -18,43 +69,271 @@
|
||||
color: var(--theme-highlight-blue);
|
||||
}
|
||||
|
||||
/* Regions are 3 nested elements with wide borders and outlines */
|
||||
|
||||
#content {
|
||||
background-color: #87ceeb;
|
||||
border-color: hsl(210,100%,85%);
|
||||
border-style: dotted;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
#padding,
|
||||
#margins {
|
||||
#margins,
|
||||
#borders,
|
||||
#padding {
|
||||
border-color: hsla(210,100%,85%,0.2);
|
||||
border-width: 25px;
|
||||
border-style: solid;
|
||||
outline: dotted 1px hsl(210,100%,85%);
|
||||
}
|
||||
|
||||
#padding {
|
||||
background-color: #6a5acd;
|
||||
}
|
||||
|
||||
#borders {
|
||||
background-color: #444444;
|
||||
border-style: dotted;
|
||||
border-color: hsl(210,100%,85%);
|
||||
}
|
||||
|
||||
#margins {
|
||||
background-color: #edff64;
|
||||
/* This opacity applies to all of the regions, since they are nested. */
|
||||
/* This opacity applies to all of the regions, since they are nested */
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
/* Respond to window size change by changing the size of the regions */
|
||||
|
||||
@media (max-height: 228px) {
|
||||
#content {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
#margins,
|
||||
#borders,
|
||||
#padding {
|
||||
border-width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Regions colors */
|
||||
|
||||
#margins {
|
||||
border-color: #edff64;
|
||||
}
|
||||
|
||||
#borders {
|
||||
border-color: #444444;
|
||||
}
|
||||
|
||||
#padding {
|
||||
border-color: #6a5acd;
|
||||
}
|
||||
|
||||
#content {
|
||||
background-color: #87ceeb;
|
||||
}
|
||||
|
||||
/* Editable region sizes are contained in absolutely positioned <p> */
|
||||
|
||||
#main > p {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#main > p {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#main > p > span {
|
||||
vertical-align: middle;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Coordinates for the region sizes */
|
||||
|
||||
.top,
|
||||
.bottom {
|
||||
width: calc(100% - 2px);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.padding.top {
|
||||
top: 55px;
|
||||
}
|
||||
|
||||
.padding.bottom {
|
||||
bottom: 57px;
|
||||
}
|
||||
|
||||
.border.top {
|
||||
top: 30px;
|
||||
}
|
||||
|
||||
.border.bottom {
|
||||
bottom: 31px;
|
||||
}
|
||||
|
||||
.margin.top {
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.margin.bottom {
|
||||
bottom: 6px;
|
||||
}
|
||||
|
||||
.size,
|
||||
.margin.left,
|
||||
.margin.right,
|
||||
.border.left,
|
||||
.border.right,
|
||||
.padding.left,
|
||||
.padding.right {
|
||||
top: 22px;
|
||||
line-height: 132px;
|
||||
}
|
||||
|
||||
.size {
|
||||
width: calc(100% - 2px);
|
||||
}
|
||||
|
||||
.margin.right,
|
||||
.margin.left,
|
||||
.border.left,
|
||||
.border.right,
|
||||
.padding.right,
|
||||
.padding.left {
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.padding.left {
|
||||
left: 52px;
|
||||
}
|
||||
|
||||
.padding.right {
|
||||
right: 51px;
|
||||
}
|
||||
|
||||
.border.left {
|
||||
left: 26px;
|
||||
}
|
||||
|
||||
.border.right {
|
||||
right: 26px;
|
||||
}
|
||||
|
||||
.margin.right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.margin.left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.rotate.left:not(.editing) {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.rotate.right:not(.editing) {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* Coordinates should be different when the window is small, because we make
|
||||
the regions smaller then */
|
||||
|
||||
@media (max-height: 228px) {
|
||||
.padding.top {
|
||||
top: 37px;
|
||||
}
|
||||
|
||||
.padding.bottom {
|
||||
bottom: 38px;
|
||||
}
|
||||
|
||||
.border.top {
|
||||
top: 19px;
|
||||
}
|
||||
|
||||
.border.bottom {
|
||||
bottom: 20px;
|
||||
}
|
||||
|
||||
.margin.top {
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.margin.bottom {
|
||||
bottom: 2px;
|
||||
}
|
||||
|
||||
.size,
|
||||
.margin.left,
|
||||
.margin.right,
|
||||
.border.left,
|
||||
.border.right,
|
||||
.padding.left,
|
||||
.padding.right {
|
||||
line-height: 80px;
|
||||
}
|
||||
|
||||
.margin.right,
|
||||
.margin.left,
|
||||
.border.left,
|
||||
.border.right,
|
||||
.padding.right,
|
||||
.padding.left {
|
||||
width: 21px;
|
||||
}
|
||||
|
||||
.padding.left {
|
||||
left: 35px;
|
||||
}
|
||||
|
||||
.padding.right {
|
||||
right: 35px;
|
||||
}
|
||||
|
||||
.border.left {
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
.border.right {
|
||||
right: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Legend, displayed inside regions */
|
||||
|
||||
.legend {
|
||||
position: absolute;
|
||||
margin: 5px 6px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.legend[data-box="margin"] {
|
||||
color: var(--theme-highlight-blue);
|
||||
}
|
||||
|
||||
@media (max-height: 228px) {
|
||||
.legend {
|
||||
margin: 2px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Editable fields */
|
||||
|
||||
.editable {
|
||||
border: 1px dashed transparent;
|
||||
-moz-user-select: text;
|
||||
}
|
||||
|
||||
.editable:hover {
|
||||
border-bottom-color: hsl(0,0%,50%);
|
||||
border-bottom-color: hsl(0, 0%, 50%);
|
||||
}
|
||||
|
||||
.styleinspector-propertyeditor {
|
||||
border: 1px solid #CCC;
|
||||
border: 1px solid #ccc;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Make sure the content size doesn't appear as editable like the other sizes */
|
||||
|
||||
.size > span {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Hide all values when the view is inactive */
|
||||
|
||||
body.inactive > #header > #element-position,
|
||||
body.inactive > #header > #element-size,
|
||||
body.inactive > #main > p {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
@ -322,6 +322,7 @@ description > html|a {
|
||||
|
||||
#dialogTitle {
|
||||
text-align: center;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
|
@ -1329,17 +1329,12 @@ void
|
||||
_memfree(void* aPtr)
|
||||
{
|
||||
PLUGIN_LOG_DEBUG_FUNCTION;
|
||||
// Only assert plugin thread here for consistency with in-process plugins.
|
||||
AssertPluginThread();
|
||||
free(aPtr);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
_memflush(uint32_t aSize)
|
||||
{
|
||||
PLUGIN_LOG_DEBUG_FUNCTION;
|
||||
// Only assert plugin thread here for consistency with in-process plugins.
|
||||
AssertPluginThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1398,8 +1393,6 @@ void*
|
||||
_memalloc(uint32_t aSize)
|
||||
{
|
||||
PLUGIN_LOG_DEBUG_FUNCTION;
|
||||
// Only assert plugin thread here for consistency with in-process plugins.
|
||||
AssertPluginThread();
|
||||
return moz_xmalloc(aSize);
|
||||
}
|
||||
|
||||
|
@ -599,13 +599,9 @@ pref("browser.safebrowsing.debug", false);
|
||||
|
||||
pref("browser.safebrowsing.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
|
||||
pref("browser.safebrowsing.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
|
||||
pref("browser.safebrowsing.reportURL", "https://safebrowsing.google.com/safebrowsing/report?");
|
||||
pref("browser.safebrowsing.reportGenericURL", "http://%LOCALE%.phish-generic.mozilla.com/?hl=%LOCALE%");
|
||||
pref("browser.safebrowsing.reportErrorURL", "http://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%");
|
||||
pref("browser.safebrowsing.reportPhishURL", "http://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%");
|
||||
pref("browser.safebrowsing.reportMalwareURL", "http://%LOCALE%.malware-report.mozilla.com/?hl=%LOCALE%");
|
||||
pref("browser.safebrowsing.reportMalwareErrorURL", "http://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%");
|
||||
|
||||
pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.malware.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
|
||||
|
||||
pref("browser.safebrowsing.id", @MOZ_APP_UA_NAME@);
|
||||
|
@ -6,7 +6,6 @@
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
@ -17,6 +16,7 @@ import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.util.SparseArray;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
@ -129,8 +129,8 @@ public class AndroidGamepadManager {
|
||||
}
|
||||
|
||||
private static boolean sStarted;
|
||||
private static HashMap<Integer, Gamepad> sGamepads;
|
||||
private static HashMap<Integer, List<KeyEvent>> sPendingGamepads;
|
||||
private static final SparseArray<Gamepad> sGamepads = new SparseArray<>();
|
||||
private static final SparseArray<List<KeyEvent>> sPendingGamepads = new SparseArray<>();
|
||||
private static InputManager.InputDeviceListener sListener;
|
||||
private static Timer sPollTimer;
|
||||
|
||||
@ -140,8 +140,6 @@ public class AndroidGamepadManager {
|
||||
public static void startup() {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
if (!sStarted) {
|
||||
sGamepads = new HashMap<Integer, Gamepad>();
|
||||
sPendingGamepads = new HashMap<Integer, List<KeyEvent>>();
|
||||
scanForGamepads();
|
||||
addDeviceListener();
|
||||
sStarted = true;
|
||||
@ -152,8 +150,8 @@ public class AndroidGamepadManager {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
if (sStarted) {
|
||||
removeDeviceListener();
|
||||
sPendingGamepads = null;
|
||||
sGamepads = null;
|
||||
sPendingGamepads.clear();
|
||||
sGamepads.clear();
|
||||
sStarted = false;
|
||||
}
|
||||
}
|
||||
@ -163,12 +161,13 @@ public class AndroidGamepadManager {
|
||||
if (!sStarted) {
|
||||
return;
|
||||
}
|
||||
if (!sPendingGamepads.containsKey(deviceId)) {
|
||||
|
||||
final List<KeyEvent> pending = sPendingGamepads.get(deviceId);
|
||||
if (pending == null) {
|
||||
removeGamepad(deviceId);
|
||||
return;
|
||||
}
|
||||
|
||||
List<KeyEvent> pending = sPendingGamepads.get(deviceId);
|
||||
sPendingGamepads.remove(deviceId);
|
||||
sGamepads.put(deviceId, new Gamepad(serviceId, deviceId));
|
||||
// Handle queued KeyEvents
|
||||
@ -200,12 +199,12 @@ public class AndroidGamepadManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sGamepads.containsKey(ev.getDeviceId())) {
|
||||
final Gamepad gamepad = sGamepads.get(ev.getDeviceId());
|
||||
if (gamepad == null) {
|
||||
// Not a device we care about.
|
||||
return false;
|
||||
}
|
||||
|
||||
Gamepad gamepad = sGamepads.get(ev.getDeviceId());
|
||||
// First check the analog stick axes
|
||||
boolean[] valid = new boolean[Axis.values().length];
|
||||
float[] axes = new float[Axis.values().length];
|
||||
@ -254,13 +253,14 @@ public class AndroidGamepadManager {
|
||||
}
|
||||
|
||||
int deviceId = ev.getDeviceId();
|
||||
if (sPendingGamepads.containsKey(deviceId)) {
|
||||
final List<KeyEvent> pendingGamepad = sPendingGamepads.get(deviceId);
|
||||
if (pendingGamepad != null) {
|
||||
// Queue up key events for pending devices.
|
||||
sPendingGamepads.get(deviceId).add(ev);
|
||||
pendingGamepad.add(ev);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!sGamepads.containsKey(deviceId)) {
|
||||
if (sGamepads.get(deviceId) == null) {
|
||||
InputDevice device = ev.getDevice();
|
||||
if (device != null &&
|
||||
(device.getSources() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
|
||||
@ -336,7 +336,8 @@ public class AndroidGamepadManager {
|
||||
sPollTimer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Integer deviceId : sGamepads.keySet()) {
|
||||
for (int i = 0; i < sGamepads.size(); ++i) {
|
||||
final int deviceId = sGamepads.keyAt(i);
|
||||
if (InputDevice.getDevice(deviceId) == null) {
|
||||
removeGamepad(deviceId);
|
||||
}
|
||||
@ -359,13 +360,13 @@ public class AndroidGamepadManager {
|
||||
|
||||
@Override
|
||||
public void onInputDeviceRemoved(int deviceId) {
|
||||
if (sPendingGamepads.containsKey(deviceId)) {
|
||||
if (sPendingGamepads.get(deviceId) != null) {
|
||||
// Got removed before Gecko's ack reached us.
|
||||
// gamepadAdded will deal with it.
|
||||
sPendingGamepads.remove(deviceId);
|
||||
return;
|
||||
}
|
||||
if (sGamepads.containsKey(deviceId)) {
|
||||
if (sGamepads.get(deviceId) != null) {
|
||||
removeGamepad(deviceId);
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,12 @@ import org.json.JSONObject;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@ -27,8 +31,14 @@ public final class IntentHelper implements GeckoEventListener {
|
||||
"Intent:GetHandlers",
|
||||
"Intent:Open",
|
||||
"Intent:OpenForResult",
|
||||
"Intent:OpenNoHandler",
|
||||
"WebActivity:Open"
|
||||
};
|
||||
|
||||
// via http://developer.android.com/distribute/tools/promote/linking.html
|
||||
private static String MARKET_INTENT_URI_PACKAGE_PREFIX = "market://details?id=";
|
||||
private static String EXTRA_BROWSER_FALLBACK_URL = "browser_fallback_url";
|
||||
|
||||
private static IntentHelper instance;
|
||||
|
||||
private final Activity activity;
|
||||
@ -64,6 +74,8 @@ public final class IntentHelper implements GeckoEventListener {
|
||||
open(message);
|
||||
} else if (event.equals("Intent:OpenForResult")) {
|
||||
openForResult(message);
|
||||
} else if (event.equals("Intent:OpenNoHandler")) {
|
||||
openNoHandler(message);
|
||||
} else if (event.equals("WebActivity:Open")) {
|
||||
openWebActivity(message);
|
||||
}
|
||||
@ -111,6 +123,66 @@ public final class IntentHelper implements GeckoEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a URI without any valid handlers on device. In the best case, a package is specified
|
||||
* and we can bring the user directly to the application page in an app market. If a package is
|
||||
* not specified and there is a fallback url in the intent extras, we open that url. If neither
|
||||
* is present, we alert the user that we were unable to open the link.
|
||||
*/
|
||||
private void openNoHandler(final JSONObject msg) {
|
||||
final String uri = msg.optString("uri");
|
||||
|
||||
if (TextUtils.isEmpty(uri)) {
|
||||
displayToastCannotOpenLink();
|
||||
Log.w(LOGTAG, "Received empty URL. Ignoring...");
|
||||
return;
|
||||
}
|
||||
|
||||
final Intent intent;
|
||||
try {
|
||||
// TODO (bug 1173626): This will not handle android-app uris on non 5.1 devices.
|
||||
intent = Intent.parseUri(uri, 0);
|
||||
} catch (final URISyntaxException e) {
|
||||
displayToastCannotOpenLink();
|
||||
// Don't log the exception to prevent leaking URIs.
|
||||
Log.w(LOGTAG, "Unable to parse Intent URI");
|
||||
return;
|
||||
}
|
||||
|
||||
// For this flow, we follow Chrome's lead:
|
||||
// https://developer.chrome.com/multidevice/android/intents
|
||||
//
|
||||
// Note on alternative flows: we could get the intent package from a component, however, for
|
||||
// security reasons, components are ignored when opening URIs (bug 1168998) so we should
|
||||
// ignore it here too.
|
||||
//
|
||||
// Our old flow used to prompt the user to search for their app in the market by scheme and
|
||||
// while this could help the user find a new app, there is not always a correlation in
|
||||
// scheme to application name and we could end up steering the user wrong (potentially to
|
||||
// malicious software). Better to leave that one alone.
|
||||
if (intent.getPackage() != null) {
|
||||
final String marketUri = MARKET_INTENT_URI_PACKAGE_PREFIX + intent.getPackage();
|
||||
final Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(marketUri));
|
||||
marketIntent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
marketIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
activity.startActivity(marketIntent);
|
||||
|
||||
} else if (intent.hasExtra(EXTRA_BROWSER_FALLBACK_URL)) {
|
||||
final String fallbackUrl = intent.getStringExtra(EXTRA_BROWSER_FALLBACK_URL);
|
||||
Tabs.getInstance().loadUrl(fallbackUrl);
|
||||
|
||||
} else {
|
||||
displayToastCannotOpenLink();
|
||||
// Don't log the URI to prevent leaking it.
|
||||
Log.w(LOGTAG, "Unable to handle URI");
|
||||
}
|
||||
}
|
||||
|
||||
private void displayToastCannotOpenLink() {
|
||||
final String errText = activity.getResources().getString(R.string.intent_uri_cannot_open);
|
||||
Toast.makeText(activity, errText, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void openWebActivity(JSONObject message) throws JSONException {
|
||||
final Intent intent = WebActivityMapper.getIntentForWebActivity(message.getJSONObject("activity"));
|
||||
ActivityHandlerHelper.startIntentForActivity(activity, intent, new ResultHandler(message));
|
||||
|
@ -177,7 +177,6 @@ public class SiteIdentity {
|
||||
mVerifier = identityData.getString("verifier");
|
||||
mEncrypted = identityData.optBoolean("encrypted", false);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Error fetching Site identity host info", e);
|
||||
resetIdentity();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -659,3 +659,5 @@ just addresses the organization to follow, e.g. "This site is run by " -->
|
||||
<!-- LOCALIZATION NOTE (find_matchcase): This is meant to appear as an icon that changes color
|
||||
if match-case is activated. i.e. No more than two letters, one uppercase, one lowercase. -->
|
||||
<!ENTITY find_matchcase "Aa">
|
||||
|
||||
<!ENTITY intent_uri_cannot_open "Cannot open link">
|
||||
|
@ -538,4 +538,6 @@
|
||||
<string name="percent">&percent;</string>
|
||||
|
||||
<string name="remote_tabs_last_synced">&remote_tabs_last_synced;</string>
|
||||
|
||||
<string name="intent_uri_cannot_open">&intent_uri_cannot_open;</string>
|
||||
</resources>
|
||||
|
@ -49,26 +49,20 @@ ContentDispatchChooser.prototype =
|
||||
if (aHandler.possibleApplicationHandlers.length > 1) {
|
||||
aHandler.launchWithURI(aURI, aWindowContext);
|
||||
} else {
|
||||
// xpcshell tests do not have an Android Bridge but we require Android
|
||||
// Bridge when using Messaging so we guard against this case. xpcshell
|
||||
// tests also do not have a window, so we use this state to guard.
|
||||
let win = this._getChromeWin();
|
||||
if (win && win.NativeWindow) {
|
||||
let bundle = Services.strings.createBundle("chrome://browser/locale/handling.properties");
|
||||
let failedText = bundle.GetStringFromName("protocol.failed");
|
||||
let searchText = bundle.GetStringFromName("protocol.toast.search");
|
||||
|
||||
win.NativeWindow.toast.show(failedText, "long", {
|
||||
button: {
|
||||
label: searchText,
|
||||
callback: function() {
|
||||
let message = {
|
||||
type: "Intent:Open",
|
||||
url: "market://search?q=" + aURI.scheme,
|
||||
};
|
||||
|
||||
Messaging.sendRequest(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
|
||||
let msg = {
|
||||
type: "Intent:OpenNoHandler",
|
||||
uri: aURI.spec,
|
||||
};
|
||||
|
||||
Messaging.sendRequest(msg);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -3,6 +3,3 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
download.blocked=Unable to download file
|
||||
protocol.failed=Couldn't find an app to open this link
|
||||
# A very short string shown in the button toast when no application can open the url
|
||||
protocol.toast.search=Search
|
||||
|
@ -863,8 +863,19 @@ LoginManagerPrompter.prototype = {
|
||||
updateButtonLabel();
|
||||
};
|
||||
|
||||
let onPasswordFocus = () => {
|
||||
chromeDoc.getElementById("password-notification-password").type = "";
|
||||
let onPasswordFocus = (focusEvent) => {
|
||||
let passwordField = chromeDoc.getElementById("password-notification-password");
|
||||
// Gets the caret position before changing the type of the textbox
|
||||
let selectionStart = passwordField.selectionStart;
|
||||
let selectionEnd = passwordField.selectionEnd;
|
||||
if (focusEvent.rangeParent != null) {
|
||||
// Check for a click over the SHOW placeholder
|
||||
selectionStart = passwordField.value.length;
|
||||
selectionEnd = passwordField.value.length;
|
||||
}
|
||||
passwordField.type = "";
|
||||
passwordField.selectionStart = selectionStart;
|
||||
passwordField.selectionEnd = selectionEnd;
|
||||
};
|
||||
|
||||
let onPasswordBlur = () => {
|
||||
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
|
||||
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
|
||||
<ShortName>engine-suggestions.xml</ShortName>
|
||||
<Url type="application/x-suggestions+json"
|
||||
method="GET"
|
||||
template="http://localhost:9000/suggest?{searchTerms}"/>
|
||||
<Url type="text/html"
|
||||
method="GET"
|
||||
template="http://localhost:9000/search"
|
||||
rel="searchform"/>
|
||||
</SearchPlugin>
|
@ -8,6 +8,7 @@ const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
|
||||
// Import common head.
|
||||
{
|
||||
@ -29,7 +30,14 @@ function* cleanup() {
|
||||
Services.prefs.clearUserPref("browser.urlbar.autoFill");
|
||||
Services.prefs.clearUserPref("browser.urlbar.autoFill.typed");
|
||||
Services.prefs.clearUserPref("browser.urlbar.autoFill.searchEngines");
|
||||
for (let type of ["history", "bookmark", "history.onlyTyped", "openpage"]) {
|
||||
let suggestPrefs = [
|
||||
"history",
|
||||
"bookmark",
|
||||
"history.onlyTyped",
|
||||
"openpage",
|
||||
"searches",
|
||||
];
|
||||
for (let type of suggestPrefs) {
|
||||
Services.prefs.clearUserPref("browser.urlbar.suggest." + type);
|
||||
}
|
||||
yield PlacesUtils.bookmarks.eraseEverything();
|
||||
@ -222,7 +230,7 @@ function* check_autocomplete(test) {
|
||||
|
||||
// We didn't hit the break, so we must have not found it
|
||||
if (j == matches.length)
|
||||
do_throw(`Didn't find the current result ("${result.value}", "${result.comment}") in matches`);
|
||||
do_throw(`Didn't find the current result ("${result.value}", "${result.comment}") in matches`); //' (Emacs syntax highlighting fix)
|
||||
}
|
||||
|
||||
Assert.equal(controller.matchCount, matches.length,
|
||||
@ -393,12 +401,56 @@ function setFaviconForHref(href, iconHref) {
|
||||
});
|
||||
}
|
||||
|
||||
function makeTestServer(port=-1) {
|
||||
let httpServer = new HttpServer();
|
||||
httpServer.start(port);
|
||||
do_register_cleanup(() => httpServer.stop(() => {}));
|
||||
return httpServer;
|
||||
}
|
||||
|
||||
function* addTestEngine(basename, httpServer=undefined) {
|
||||
httpServer = httpServer || makeTestServer();
|
||||
httpServer.registerDirectory("/", do_get_cwd());
|
||||
let dataUrl =
|
||||
"http://localhost:" + httpServer.identity.primaryPort + "/data/";
|
||||
|
||||
do_print("Adding engine: " + basename);
|
||||
return yield new Promise(resolve => {
|
||||
Services.obs.addObserver(function obs(subject, topic, data) {
|
||||
let engine = subject.QueryInterface(Ci.nsISearchEngine);
|
||||
do_print("Observed " + data + " for " + engine.name);
|
||||
if (data != "engine-added" || engine.name != basename) {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.obs.removeObserver(obs, "browser-search-engine-modified");
|
||||
do_register_cleanup(() => Services.search.removeEngine(engine));
|
||||
resolve(engine);
|
||||
}, "browser-search-engine-modified", false);
|
||||
|
||||
do_print("Adding engine from URL: " + dataUrl + basename);
|
||||
Services.search.addEngine(dataUrl + basename,
|
||||
Ci.nsISearchEngine.DATA_XML, null, false);
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure we have a default search engine and the keyword.enabled preference
|
||||
// set.
|
||||
add_task(function ensure_search_engine() {
|
||||
// keyword.enabled is necessary for the tests to see keyword searches.
|
||||
Services.prefs.setBoolPref("keyword.enabled", true);
|
||||
|
||||
// Initialize the search service, but first set this geo IP pref to a dummy
|
||||
// string. When the search service is initialized, it contacts the URI named
|
||||
// in this pref, which breaks the test since outside connections aren't
|
||||
// allowed.
|
||||
let geoPref = "browser.search.geoip.url";
|
||||
Services.prefs.setCharPref(geoPref, "");
|
||||
do_register_cleanup(() => Services.prefs.clearUserPref(geoPref));
|
||||
yield new Promise(resolve => {
|
||||
Services.search.init(resolve);
|
||||
});
|
||||
|
||||
// Remove any existing engines before adding ours.
|
||||
for (let engine of Services.search.getEngines()) {
|
||||
Services.search.removeEngine(engine);
|
||||
|
@ -1,42 +1,6 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
|
||||
function* addTestEngines(items) {
|
||||
let httpServer = new HttpServer();
|
||||
httpServer.start(-1);
|
||||
httpServer.registerDirectory("/", do_get_cwd());
|
||||
let gDataUrl = "http://localhost:" + httpServer.identity.primaryPort + "/data/";
|
||||
do_register_cleanup(() => httpServer.stop(() => {}));
|
||||
|
||||
let engines = [];
|
||||
|
||||
for (let item of items) {
|
||||
do_print("Adding engine: " + item);
|
||||
yield new Promise(resolve => {
|
||||
Services.obs.addObserver(function obs(subject, topic, data) {
|
||||
let engine = subject.QueryInterface(Ci.nsISearchEngine);
|
||||
do_print("Observed " + data + " for " + engine.name);
|
||||
if (data != "engine-added" || engine.name != item) {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.obs.removeObserver(obs, "browser-search-engine-modified");
|
||||
engines.push(engine);
|
||||
resolve();
|
||||
}, "browser-search-engine-modified", false);
|
||||
|
||||
do_print("`Adding engine from URL: " + gDataUrl + item);
|
||||
Services.search.addEngine(gDataUrl + item,
|
||||
Ci.nsISearchEngine.DATA_XML, null, false);
|
||||
});
|
||||
}
|
||||
|
||||
return engines;
|
||||
}
|
||||
|
||||
|
||||
add_task(function* test_searchEngine_autoFill() {
|
||||
Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", true);
|
||||
Services.search.addEngineWithDetails("MySearchEngine", "", "", "",
|
||||
@ -67,8 +31,7 @@ add_task(function* test_searchEngine_autoFill() {
|
||||
|
||||
add_task(function* test_searchEngine_noautoFill() {
|
||||
let engineName = "engine-rel-searchform.xml";
|
||||
let [engine] = yield addTestEngines([engineName]);
|
||||
do_register_cleanup(() => Services.search.removeEngine(engine));
|
||||
let engine = yield addTestEngine(engineName);
|
||||
equal(engine.searchForm, "http://example.com/?search");
|
||||
|
||||
Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false);
|
||||
|
@ -0,0 +1,147 @@
|
||||
Cu.import("resource://gre/modules/FormHistory.jsm");
|
||||
|
||||
const ENGINE_NAME = "engine-suggestions.xml";
|
||||
const SERVER_PORT = 9000;
|
||||
const SUGGEST_PREF = "browser.urlbar.suggest.searches";
|
||||
|
||||
// Set this to some other function to change how the server converts search
|
||||
// strings into suggestions.
|
||||
let suggestionsFromSearchString = searchStr => {
|
||||
let suffixes = ["foo", "bar"];
|
||||
return suffixes.map(s => searchStr + " " + s);
|
||||
};
|
||||
|
||||
add_task(function* setUp() {
|
||||
// Set up a server that provides some suggestions by appending strings onto
|
||||
// the search query.
|
||||
let server = makeTestServer(SERVER_PORT);
|
||||
server.registerPathHandler("/suggest", (req, resp) => {
|
||||
// URL query params are x-www-form-urlencoded, which converts spaces into
|
||||
// plus signs, so un-convert any plus signs back to spaces.
|
||||
let searchStr = decodeURIComponent(req.queryString.replace(/\+/g, " "));
|
||||
let suggestions = suggestionsFromSearchString(searchStr);
|
||||
let data = [searchStr, suggestions];
|
||||
resp.setHeader("Content-Type", "application/json", false);
|
||||
resp.write(JSON.stringify(data));
|
||||
});
|
||||
|
||||
// Install the test engine.
|
||||
let oldCurrentEngine = Services.search.currentEngine;
|
||||
do_register_cleanup(() => Services.search.currentEngine = oldCurrentEngine);
|
||||
let engine = yield addTestEngine(ENGINE_NAME, server);
|
||||
Services.search.currentEngine = engine;
|
||||
|
||||
yield cleanup();
|
||||
});
|
||||
|
||||
add_task(function* disabled() {
|
||||
Services.prefs.setBoolPref(SUGGEST_PREF, false);
|
||||
yield check_autocomplete({
|
||||
search: "hello",
|
||||
matches: [],
|
||||
});
|
||||
yield cleanup();
|
||||
});
|
||||
|
||||
add_task(function* singleWordQuery() {
|
||||
Services.prefs.setBoolPref(SUGGEST_PREF, true);
|
||||
|
||||
let searchStr = "hello";
|
||||
yield check_autocomplete({
|
||||
search: searchStr,
|
||||
matches: [{
|
||||
uri: makeActionURI(("searchengine"), {
|
||||
engineName: ENGINE_NAME,
|
||||
input: searchStr,
|
||||
searchQuery: searchStr,
|
||||
searchSuggestion: "hello foo",
|
||||
}),
|
||||
title: ENGINE_NAME,
|
||||
style: ["action", "searchengine"],
|
||||
icon: "",
|
||||
}, {
|
||||
uri: makeActionURI(("searchengine"), {
|
||||
engineName: ENGINE_NAME,
|
||||
input: searchStr,
|
||||
searchQuery: searchStr,
|
||||
searchSuggestion: "hello bar",
|
||||
}),
|
||||
title: ENGINE_NAME,
|
||||
style: ["action", "searchengine"],
|
||||
icon: "",
|
||||
}],
|
||||
});
|
||||
|
||||
yield cleanup();
|
||||
});
|
||||
|
||||
add_task(function* multiWordQuery() {
|
||||
Services.prefs.setBoolPref(SUGGEST_PREF, true);
|
||||
|
||||
let searchStr = "hello world";
|
||||
yield check_autocomplete({
|
||||
search: searchStr,
|
||||
matches: [{
|
||||
uri: makeActionURI(("searchengine"), {
|
||||
engineName: ENGINE_NAME,
|
||||
input: searchStr,
|
||||
searchQuery: searchStr,
|
||||
searchSuggestion: "hello world foo",
|
||||
}),
|
||||
title: ENGINE_NAME,
|
||||
style: ["action", "searchengine"],
|
||||
icon: "",
|
||||
}, {
|
||||
uri: makeActionURI(("searchengine"), {
|
||||
engineName: ENGINE_NAME,
|
||||
input: searchStr,
|
||||
searchQuery: searchStr,
|
||||
searchSuggestion: "hello world bar",
|
||||
}),
|
||||
title: ENGINE_NAME,
|
||||
style: ["action", "searchengine"],
|
||||
icon: "",
|
||||
}],
|
||||
});
|
||||
|
||||
yield cleanup();
|
||||
});
|
||||
|
||||
add_task(function* suffixMatch() {
|
||||
Services.prefs.setBoolPref(SUGGEST_PREF, true);
|
||||
|
||||
let oldFn = suggestionsFromSearchString;
|
||||
suggestionsFromSearchString = searchStr => {
|
||||
let prefixes = ["baz", "quux"];
|
||||
return prefixes.map(p => p + " " + searchStr);
|
||||
};
|
||||
|
||||
let searchStr = "hello";
|
||||
yield check_autocomplete({
|
||||
search: searchStr,
|
||||
matches: [{
|
||||
uri: makeActionURI(("searchengine"), {
|
||||
engineName: ENGINE_NAME,
|
||||
input: searchStr,
|
||||
searchQuery: searchStr,
|
||||
searchSuggestion: "baz hello",
|
||||
}),
|
||||
title: ENGINE_NAME,
|
||||
style: ["action", "searchengine"],
|
||||
icon: "",
|
||||
}, {
|
||||
uri: makeActionURI(("searchengine"), {
|
||||
engineName: ENGINE_NAME,
|
||||
input: searchStr,
|
||||
searchQuery: searchStr,
|
||||
searchSuggestion: "quux hello",
|
||||
}),
|
||||
title: ENGINE_NAME,
|
||||
style: ["action", "searchengine"],
|
||||
icon: "",
|
||||
}],
|
||||
});
|
||||
|
||||
suggestionsFromSearchString = oldFn;
|
||||
yield cleanup();
|
||||
});
|
@ -4,7 +4,7 @@ tail =
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
support-files =
|
||||
data/engine-rel-searchform.xml
|
||||
|
||||
data/engine-suggestions.xml
|
||||
|
||||
[test_416211.js]
|
||||
[test_416214.js]
|
||||
@ -34,6 +34,7 @@ support-files =
|
||||
[test_searchEngine_current.js]
|
||||
[test_searchEngine_host.js]
|
||||
[test_searchEngine_restyle.js]
|
||||
[test_searchSuggestions.js]
|
||||
[test_special_search.js]
|
||||
[test_swap_protocol.js]
|
||||
[test_tabmatches.js]
|
||||
|
@ -90,15 +90,36 @@ this.SafeBrowsing = {
|
||||
gethashURL: null,
|
||||
|
||||
reportURL: null,
|
||||
reportGenericURL: null,
|
||||
reportErrorURL: null,
|
||||
reportPhishURL: null,
|
||||
reportMalwareURL: null,
|
||||
reportMalwareErrorURL: null,
|
||||
|
||||
getReportURL: function(kind, URI) {
|
||||
let pref;
|
||||
switch (kind) {
|
||||
case "Phish":
|
||||
pref = "browser.safebrowsing.reportPhishURL";
|
||||
break;
|
||||
case "PhishMistake":
|
||||
pref = "browser.safebrowsing.reportPhishMistakeURL";
|
||||
break;
|
||||
case "MalwareMistake":
|
||||
pref = "browser.safebrowsing.reportMalwareMistakeURL";
|
||||
break;
|
||||
|
||||
getReportURL: function(kind) {
|
||||
return this["report" + kind + "URL"];
|
||||
default:
|
||||
let err = "SafeBrowsing getReportURL() called with unknown kind: " + kind;
|
||||
Components.utils.reportError(err);
|
||||
throw err;
|
||||
}
|
||||
let reportUrl = Services.urlFormatter.formatURLPref(pref);
|
||||
|
||||
let pageUri = URI.clone();
|
||||
|
||||
// Remove the query to avoid including potentially sensitive data
|
||||
if (pageUri instanceof Ci.nsIURL)
|
||||
pageUri.query = '';
|
||||
|
||||
reportUrl += encodeURIComponent(pageUri.asciiSpec);
|
||||
|
||||
return reportUrl;
|
||||
},
|
||||
|
||||
|
||||
@ -128,19 +149,10 @@ this.SafeBrowsing = {
|
||||
}
|
||||
|
||||
log("initializing safe browsing URLs, client id ", clientID);
|
||||
let basePref = "browser.safebrowsing.";
|
||||
|
||||
// Urls to HTML report pages
|
||||
this.reportURL = Services.urlFormatter.formatURLPref(basePref + "reportURL");
|
||||
this.reportGenericURL = Services.urlFormatter.formatURLPref(basePref + "reportGenericURL");
|
||||
this.reportErrorURL = Services.urlFormatter.formatURLPref(basePref + "reportErrorURL");
|
||||
this.reportPhishURL = Services.urlFormatter.formatURLPref(basePref + "reportPhishURL");
|
||||
this.reportMalwareURL = Services.urlFormatter.formatURLPref(basePref + "reportMalwareURL");
|
||||
this.reportMalwareErrorURL = Services.urlFormatter.formatURLPref(basePref + "reportMalwareErrorURL");
|
||||
|
||||
// Urls used to update DB
|
||||
this.updateURL = Services.urlFormatter.formatURLPref(basePref + "updateURL");
|
||||
this.gethashURL = Services.urlFormatter.formatURLPref(basePref + "gethashURL");
|
||||
this.updateURL = Services.urlFormatter.formatURLPref("browser.safebrowsing.updateURL");
|
||||
this.gethashURL = Services.urlFormatter.formatURLPref("browser.safebrowsing.gethashURL");
|
||||
|
||||
this.updateURL = this.updateURL.replace("SAFEBROWSING_ID", clientID);
|
||||
this.gethashURL = this.gethashURL.replace("SAFEBROWSING_ID", clientID);
|
||||
|
@ -1360,7 +1360,7 @@ TabClient.prototype = {
|
||||
eventSource(TabClient.prototype);
|
||||
|
||||
function WorkerClient(aClient, aForm) {
|
||||
this._client = aClient;
|
||||
this.client = aClient;
|
||||
this._actor = aForm.from;
|
||||
this._isClosed = false;
|
||||
this._isFrozen = aForm.isFrozen;
|
||||
@ -1376,11 +1376,11 @@ function WorkerClient(aClient, aForm) {
|
||||
|
||||
WorkerClient.prototype = {
|
||||
get _transport() {
|
||||
return this._client._transport;
|
||||
return this.client._transport;
|
||||
},
|
||||
|
||||
get request() {
|
||||
return this._client.request;
|
||||
return this.client.request;
|
||||
},
|
||||
|
||||
get actor() {
|
||||
@ -1397,19 +1397,41 @@ WorkerClient.prototype = {
|
||||
|
||||
detach: DebuggerClient.requester({ type: "detach" }, {
|
||||
after: function (aResponse) {
|
||||
this._client.unregisterClient(this);
|
||||
this.client.unregisterClient(this);
|
||||
return aResponse;
|
||||
},
|
||||
|
||||
telemetry: "WORKERDETACH"
|
||||
}),
|
||||
|
||||
attachThread: function(aOptions = {}, aOnResponse = noop) {
|
||||
if (this.thread) {
|
||||
DevToolsUtils.executeSoon(() => aOnResponse({
|
||||
type: "connected",
|
||||
threadActor: this.thread._actor,
|
||||
}, this.thread));
|
||||
return;
|
||||
}
|
||||
|
||||
this.request({
|
||||
to: this._actor,
|
||||
type: "connect",
|
||||
options: aOptions,
|
||||
}, (aResponse) => {
|
||||
if (!aResponse.error) {
|
||||
this.thread = new ThreadClient(this, aResponse.threadActor);
|
||||
this.client.registerClient(this.thread);
|
||||
}
|
||||
aOnResponse(aResponse, this.thread);
|
||||
});
|
||||
},
|
||||
|
||||
_onClose: function () {
|
||||
this.removeListener("close", this._onClose);
|
||||
this.removeListener("freeze", this._onFreeze);
|
||||
this.removeListener("thaw", this._onThaw);
|
||||
|
||||
this._client.unregisterClient(this);
|
||||
this.client.unregisterClient(this);
|
||||
this._closed = true;
|
||||
},
|
||||
|
||||
|
@ -15,6 +15,7 @@ const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
const { dbg_assert, dumpn, update, fetch } = DevToolsUtils;
|
||||
const { dirname, joinURI } = require("devtools/toolkit/path");
|
||||
const promise = require("promise");
|
||||
const PromiseDebugging = require("PromiseDebugging");
|
||||
const xpcInspector = require("xpcInspector");
|
||||
const ScriptStore = require("./utils/ScriptStore");
|
||||
const {DevToolsWorker} = require("devtools/toolkit/shared/worker.js");
|
||||
@ -1494,7 +1495,7 @@ ThreadActor.prototype = {
|
||||
// Clear DOM event breakpoints.
|
||||
// XPCShell tests don't use actual DOM windows for globals and cause
|
||||
// removeListenerForAllEvents to throw.
|
||||
if (this.global && !this.global.toString().includes("Sandbox")) {
|
||||
if (!isWorker && this.global && !this.global.toString().includes("Sandbox")) {
|
||||
let els = Cc["@mozilla.org/eventlistenerservice;1"]
|
||||
.getService(Ci.nsIEventListenerService);
|
||||
els.removeListenerForAllEvents(this.global, this._allEventsListener, true);
|
||||
@ -1933,7 +1934,7 @@ ThreadActor.prototype = {
|
||||
}
|
||||
|
||||
if (promises.length > 0) {
|
||||
this.synchronize(Promise.all(promises));
|
||||
this.synchronize(promise.all(promises));
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -2870,10 +2871,10 @@ SourceActor.prototype = {
|
||||
actor,
|
||||
GeneratedLocation.fromOriginalLocation(originalLocation)
|
||||
)) {
|
||||
return Promise.resolve(null);
|
||||
return promise.resolve(null);
|
||||
}
|
||||
|
||||
return Promise.resolve(originalLocation);
|
||||
return promise.resolve(originalLocation);
|
||||
} else {
|
||||
return this.sources.getAllGeneratedLocations(originalLocation)
|
||||
.then((generatedLocations) => {
|
||||
|
@ -10,7 +10,7 @@ const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
const { dbg_assert, fetch } = DevToolsUtils;
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
const { OriginalLocation, GeneratedLocation, getOffsetColumn } = require("devtools/server/actors/common");
|
||||
const { resolve } = Promise;
|
||||
const { resolve } = require("promise");
|
||||
|
||||
loader.lazyRequireGetter(this, "SourceActor", "devtools/server/actors/script", true);
|
||||
loader.lazyRequireGetter(this, "isEvalSource", "devtools/server/actors/script", true);
|
||||
|
@ -1,6 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
let { Ci, Cu } = require("chrome");
|
||||
let { DebuggerServer } = require("devtools/server/main");
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
@ -28,6 +29,8 @@ function matchWorkerDebugger(dbg, options) {
|
||||
function WorkerActor(dbg) {
|
||||
this._dbg = dbg;
|
||||
this._isAttached = false;
|
||||
this._threadActor = null;
|
||||
this._transport = null;
|
||||
}
|
||||
|
||||
WorkerActor.prototype = {
|
||||
@ -66,6 +69,33 @@ WorkerActor.prototype = {
|
||||
return { type: "detached" };
|
||||
},
|
||||
|
||||
onConnect: function (request) {
|
||||
if (!this._isAttached) {
|
||||
return { error: "wrongState" };
|
||||
}
|
||||
|
||||
if (this._threadActor !== null) {
|
||||
return {
|
||||
type: "connected",
|
||||
threadActor: this._threadActor
|
||||
};
|
||||
}
|
||||
|
||||
return DebuggerServer.connectToWorker(
|
||||
this.conn, this._dbg, this.actorID, request.options
|
||||
).then(({ threadActor, transport }) => {
|
||||
this._threadActor = threadActor;
|
||||
this._transport = transport;
|
||||
|
||||
return {
|
||||
type: "connected",
|
||||
threadActor: this._threadActor
|
||||
};
|
||||
}, (error) => {
|
||||
return { error: error.toString() };
|
||||
});
|
||||
},
|
||||
|
||||
onClose: function () {
|
||||
if (this._isAttached) {
|
||||
this._detach();
|
||||
@ -74,6 +104,10 @@ WorkerActor.prototype = {
|
||||
this.conn.sendActorEvent(this.actorID, "close");
|
||||
},
|
||||
|
||||
onError: function (filename, lineno, message) {
|
||||
reportError("ERROR:" + filename + ":" + lineno + ":" + message + "\n");
|
||||
},
|
||||
|
||||
onFreeze: function () {
|
||||
this.conn.sendActorEvent(this.actorID, "freeze");
|
||||
},
|
||||
@ -83,6 +117,12 @@ WorkerActor.prototype = {
|
||||
},
|
||||
|
||||
_detach: function () {
|
||||
if (this._threadActor !== null) {
|
||||
this._transport.close();
|
||||
this._transport = null;
|
||||
this._threadActor = null;
|
||||
}
|
||||
|
||||
this._dbg.removeListener(this);
|
||||
this._isAttached = false;
|
||||
}
|
||||
@ -90,7 +130,8 @@ WorkerActor.prototype = {
|
||||
|
||||
WorkerActor.prototype.requestTypes = {
|
||||
"attach": WorkerActor.prototype.onAttach,
|
||||
"detach": WorkerActor.prototype.onDetach
|
||||
"detach": WorkerActor.prototype.onDetach,
|
||||
"connect": WorkerActor.prototype.onConnect
|
||||
};
|
||||
|
||||
exports.WorkerActor = WorkerActor;
|
||||
|
@ -14,7 +14,7 @@ let { Ci, Cc, CC, Cu, Cr } = require("chrome");
|
||||
let Services = require("Services");
|
||||
let { ActorPool, OriginalLocation, RegisteredActorFactory,
|
||||
ObservedActorFactory } = require("devtools/server/actors/common");
|
||||
let { LocalDebuggerTransport, ChildDebuggerTransport } =
|
||||
let { LocalDebuggerTransport, ChildDebuggerTransport, WorkerDebuggerTransport } =
|
||||
require("devtools/toolkit/transport/transport");
|
||||
let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
let { dumpn, dumpv, dbg_assert } = DevToolsUtils;
|
||||
@ -685,10 +685,13 @@ var DebuggerServer = {
|
||||
* "debug:<prefix>:packet", and all its actors will have names
|
||||
* beginning with "<prefix>/".
|
||||
*/
|
||||
connectToParent: function(aPrefix, aMessageManager) {
|
||||
connectToParent: function(aPrefix, aScopeOrManager) {
|
||||
this._checkInit();
|
||||
|
||||
let transport = new ChildDebuggerTransport(aMessageManager, aPrefix);
|
||||
let transport = isWorker ?
|
||||
new WorkerDebuggerTransport(aScopeOrManager, aPrefix) :
|
||||
new ChildDebuggerTransport(aScopeOrManager, aPrefix);
|
||||
|
||||
return this._onConnection(transport, aPrefix, true);
|
||||
},
|
||||
|
||||
@ -755,6 +758,83 @@ var DebuggerServer = {
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
connectToWorker: function (aConnection, aDbg, aId, aOptions) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Step 1: Initialize the worker debugger.
|
||||
aDbg.initialize("resource://gre/modules/devtools/server/worker.js");
|
||||
|
||||
// Step 2: Send a connect request to the worker debugger.
|
||||
aDbg.postMessage(JSON.stringify({
|
||||
type: "connect",
|
||||
id: aId,
|
||||
options: aOptions
|
||||
}));
|
||||
|
||||
// Steps 3-5 are performed on the worker thread (see worker.js).
|
||||
|
||||
// Step 6: Wait for a response from the worker debugger.
|
||||
let listener = {
|
||||
onClose: () => {
|
||||
aDbg.removeListener(listener);
|
||||
|
||||
reject("closed");
|
||||
},
|
||||
|
||||
onMessage: (message) => {
|
||||
let packet = JSON.parse(message);
|
||||
if (packet.type !== "message" || packet.id !== aId) {
|
||||
return;
|
||||
}
|
||||
|
||||
message = packet.message;
|
||||
if (message.error) {
|
||||
reject(error);
|
||||
}
|
||||
|
||||
if (message.type !== "paused") {
|
||||
return;
|
||||
}
|
||||
|
||||
aDbg.removeListener(listener);
|
||||
|
||||
// Step 7: Create a transport for the connection to the worker.
|
||||
let transport = new WorkerDebuggerTransport(aDbg, aId);
|
||||
transport.ready();
|
||||
transport.hooks = {
|
||||
onClosed: () => {
|
||||
if (!aDbg.isClosed) {
|
||||
aDbg.postMessage(JSON.stringify({
|
||||
type: "disconnect",
|
||||
id: aId
|
||||
}));
|
||||
}
|
||||
|
||||
aConnection.cancelForwarding(aId);
|
||||
},
|
||||
|
||||
onPacket: (packet) => {
|
||||
// Ensure that any packets received from the server on the worker
|
||||
// thread are forwarded to the client on the main thread, as if
|
||||
// they had been sent by the server on the main thread.
|
||||
aConnection.send(packet);
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure that any packets received from the client on the main thread
|
||||
// to actors on the worker thread are forwarded to the server on the
|
||||
// worker thread.
|
||||
aConnection.setForwarding(aId, transport);
|
||||
|
||||
resolve({
|
||||
threadActor: message.from,
|
||||
transport: transport
|
||||
});
|
||||
}
|
||||
};
|
||||
aDbg.addListener(listener);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the caller is running in a content child process.
|
||||
*
|
||||
@ -1442,13 +1522,16 @@ DebuggerServerConnection.prototype = {
|
||||
// forwarding is needed: in DebuggerServerConnection instances in child
|
||||
// processes, every actor has a prefixed name.
|
||||
if (this._forwardingPrefixes.size > 0) {
|
||||
let separator = aPacket.to.indexOf('/');
|
||||
if (separator >= 0) {
|
||||
let to = aPacket.to;
|
||||
let separator = to.lastIndexOf('/');
|
||||
while (separator >= 0) {
|
||||
to = to.substring(0, separator);
|
||||
let forwardTo = this._forwardingPrefixes.get(aPacket.to.substring(0, separator));
|
||||
if (forwardTo) {
|
||||
forwardTo.send(aPacket);
|
||||
return;
|
||||
}
|
||||
separator = to.lastIndexOf('/');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,7 @@ EXTRA_JS_MODULES.devtools.server += [
|
||||
'content-globals.js',
|
||||
'main.js',
|
||||
'protocol.js',
|
||||
'worker.js'
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.devtools.server.actors += [
|
||||
|
66
toolkit/devtools/server/worker.js
Normal file
66
toolkit/devtools/server/worker.js
Normal file
@ -0,0 +1,66 @@
|
||||
"use strict"
|
||||
|
||||
loadSubScript("resource://gre/modules/devtools/worker-loader.js");
|
||||
|
||||
let { ActorPool } = worker.require("devtools/server/actors/common");
|
||||
let { ThreadActor } = worker.require("devtools/server/actors/script");
|
||||
let { TabSources } = worker.require("devtools/server/actors/utils/TabSources");
|
||||
let makeDebugger = worker.require("devtools/server/actors/utils/make-debugger");
|
||||
let { DebuggerServer } = worker.require("devtools/server/main");
|
||||
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.createRootActor = function () {
|
||||
throw new Error("Should never get here!");
|
||||
};
|
||||
|
||||
let connections = Object.create(null);
|
||||
|
||||
this.addEventListener("message", function (event) {
|
||||
let packet = JSON.parse(event.data);
|
||||
switch (packet.type) {
|
||||
case "connect":
|
||||
// Step 3: Create a connection to the parent.
|
||||
let connection = DebuggerServer.connectToParent(packet.id, this);
|
||||
connections[packet.id] = connection;
|
||||
|
||||
// Step 4: Create a thread actor for the connection to the parent.
|
||||
let pool = new ActorPool(connection);
|
||||
connection.addActorPool(pool);
|
||||
|
||||
let sources = null;
|
||||
|
||||
let actor = new ThreadActor({
|
||||
makeDebugger: makeDebugger.bind(null, {
|
||||
findDebuggees: () => {
|
||||
return [this.global];
|
||||
},
|
||||
|
||||
shouldAddNewGlobalAsDebuggee: () => {
|
||||
return true;
|
||||
},
|
||||
}),
|
||||
|
||||
get sources() {
|
||||
if (sources === null) {
|
||||
sources = new TabSources(actor);
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
}, global);
|
||||
|
||||
pool.addActor(actor);
|
||||
|
||||
// Step 5: Attach to the thread actor.
|
||||
//
|
||||
// This will cause a packet to be sent over the connection to the parent.
|
||||
// Because this connection uses WorkerDebuggerTransport internally, this
|
||||
// packet will be sent using WorkerDebuggerGlobalScope.postMessage, causing
|
||||
// an onMessage event to be fired on the WorkerDebugger in the main thread.
|
||||
actor.onAttach({});
|
||||
break;
|
||||
|
||||
case "disconnect":
|
||||
connections[packet.id].close();
|
||||
break;
|
||||
};
|
||||
});
|
@ -435,6 +435,8 @@ let {
|
||||
} else { // Worker thread
|
||||
let requestors = [];
|
||||
|
||||
let scope = this;
|
||||
|
||||
let xpcInspector = {
|
||||
get lastNestRequestor() {
|
||||
return requestors.length === 0 ? null : requestors[0];
|
||||
@ -442,13 +444,13 @@ let {
|
||||
|
||||
enterNestedEventLoop: function (requestor) {
|
||||
requestors.push(requestor);
|
||||
this.enterEventLoop();
|
||||
scope.enterEventLoop();
|
||||
return requestors.length;
|
||||
},
|
||||
|
||||
exitNestedEventLoop: function () {
|
||||
requestors.pop();
|
||||
this.leaveEventLoop();
|
||||
scope.leaveEventLoop();
|
||||
return requestors.length;
|
||||
}
|
||||
};
|
||||
|
@ -243,7 +243,6 @@ richlistitem:not([selected]) * {
|
||||
.view-pane[type="experiment"] .addon:not([pending="uninstall"]) .pending,
|
||||
.view-pane[type="experiment"] .disabled-postfix,
|
||||
.view-pane[type="experiment"] .update-postfix,
|
||||
.view-pane[type="experiment"] .version,
|
||||
#detail-view[type="experiment"] .alert-container,
|
||||
#detail-view[type="experiment"] #detail-version,
|
||||
#detail-view[type="experiment"] #detail-creator {
|
||||
|
@ -460,6 +460,31 @@ var gEventManager = {
|
||||
menuSep.hidden = (countMenuItemsBeforeSep == 0);
|
||||
|
||||
}, false);
|
||||
|
||||
let addonTooltip = document.getElementById("addonitem-tooltip");
|
||||
addonTooltip.addEventListener("popupshowing", function() {
|
||||
let addonItem = document.tooltipNode;
|
||||
// The way the test triggers the tooltip the richlistitem is the
|
||||
// tooltipNode but in normal use it is the anonymous node. This allows
|
||||
// any case
|
||||
if (addonItem.localName != "richlistitem")
|
||||
addonItem = document.getBindingParent(addonItem);
|
||||
|
||||
let tiptext = addonItem.getAttribute("name");
|
||||
|
||||
if (addonItem.mAddon) {
|
||||
if (shouldShowVersionNumber(addonItem.mAddon)) {
|
||||
tiptext += " " + (addonItem.hasAttribute("upgrade") ? addonItem.mManualUpdate.version
|
||||
: addonItem.mAddon.version);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (shouldShowVersionNumber(addonItem.mInstall))
|
||||
tiptext += " " + addonItem.mInstall.version;
|
||||
}
|
||||
|
||||
addonTooltip.label = tiptext;
|
||||
}, false);
|
||||
},
|
||||
|
||||
shutdown: function gEM_shutdown() {
|
||||
@ -1448,6 +1473,10 @@ function shouldShowVersionNumber(aAddon) {
|
||||
if (!aAddon.version)
|
||||
return false;
|
||||
|
||||
// The version number is hidden for experiments.
|
||||
if (aAddon.type == "experiment")
|
||||
return false;
|
||||
|
||||
// The version number is hidden for lightweight themes.
|
||||
if (aAddon.type == "theme")
|
||||
return !/@personas\.mozilla\.org$/.test(aAddon.id);
|
||||
|
@ -795,8 +795,7 @@
|
||||
<xul:hbox class="basicinfo-container">
|
||||
<xul:hbox class="name-container">
|
||||
<xul:label anonid="name" class="name" crop="end" flex="1"
|
||||
xbl:inherits="value=name,tooltiptext=name"/>
|
||||
<xul:label anonid="version" class="version"/>
|
||||
tooltip="addonitem-tooltip" xbl:inherits="value=name"/>
|
||||
<xul:label class="disabled-postfix" value="&addon.disabled.postfix;"/>
|
||||
<xul:label class="update-postfix" value="&addon.update.postfix;"/>
|
||||
<xul:spacer flex="5000"/> <!-- Necessary to make the name crop -->
|
||||
@ -979,9 +978,6 @@
|
||||
document.getAnonymousElementByAttribute(this, "anonid",
|
||||
"info");
|
||||
</field>
|
||||
<field name="_version">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "version");
|
||||
</field>
|
||||
<field name="_experimentState">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "experiment-state");
|
||||
</field>
|
||||
@ -1118,11 +1114,6 @@
|
||||
else
|
||||
this._icon.src = "";
|
||||
|
||||
if (shouldShowVersionNumber(this.mAddon))
|
||||
this._version.value = this.mAddon.version;
|
||||
else
|
||||
this._version.hidden = true;
|
||||
|
||||
if (this.mAddon.description)
|
||||
this._description.value = this.mAddon.description;
|
||||
else
|
||||
@ -1414,14 +1405,6 @@
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_updateUpgradeInfo">
|
||||
<body><![CDATA[
|
||||
// Only update the version string if we're displaying the upgrade info
|
||||
if (this.hasAttribute("upgrade") && shouldShowVersionNumber(this.mAddon))
|
||||
this._version.value = this.mManualUpdate.version;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_fetchReleaseNotes">
|
||||
<parameter name="aURI"/>
|
||||
<body><![CDATA[
|
||||
@ -1711,7 +1694,6 @@
|
||||
|
||||
this.mManualUpdate = aInstall;
|
||||
this._showStatus("update-available");
|
||||
this._updateUpgradeInfo();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
@ -1913,8 +1895,7 @@
|
||||
</xul:vbox>
|
||||
<xul:vbox class="fade name-outer-container" flex="1">
|
||||
<xul:hbox class="name-container">
|
||||
<xul:label anonid="name" class="name" crop="end"/>
|
||||
<xul:label anonid="version" class="version" hidden="true"/>
|
||||
<xul:label anonid="name" class="name" crop="end" tooltip="addonitem-tooltip"/>
|
||||
</xul:hbox>
|
||||
</xul:vbox>
|
||||
<xul:vbox class="install-status-container">
|
||||
@ -1936,9 +1917,6 @@
|
||||
<field name="_name">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "name");
|
||||
</field>
|
||||
<field name="_version">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "version");
|
||||
</field>
|
||||
<field name="_warning">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "warning");
|
||||
</field>
|
||||
@ -1966,14 +1944,6 @@
|
||||
this._icon.src = this.mAddon.iconURL ||
|
||||
(this.mInstall ? this.mInstall.iconURL : "");
|
||||
this._name.value = this.mAddon.name;
|
||||
|
||||
if (this.mAddon.version) {
|
||||
this._version.value = this.mAddon.version;
|
||||
this._version.hidden = false;
|
||||
} else {
|
||||
this._version.hidden = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
this._icon.src = this.mInstall.iconURL;
|
||||
// AddonInstall.name isn't always available - fallback to filename
|
||||
@ -1987,13 +1957,6 @@
|
||||
url.QueryInterface(Components.interfaces.nsIURL);
|
||||
this._name.value = url.fileName;
|
||||
}
|
||||
|
||||
if (this.mInstall.version) {
|
||||
this._version.value = this.mInstall.version;
|
||||
this._version.hidden = false;
|
||||
} else {
|
||||
this._version.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.mInstall.state == AddonManager.STATE_DOWNLOAD_FAILED) {
|
||||
|
@ -71,6 +71,8 @@
|
||||
label="&cmd.about.label;"
|
||||
accesskey="&cmd.about.accesskey;"/>
|
||||
</menupopup>
|
||||
|
||||
<tooltip id="addonitem-tooltip"/>
|
||||
</popupset>
|
||||
|
||||
<!-- global commands - these act on all addons, or affect the addons manager
|
||||
|
@ -9,9 +9,7 @@ var gManagerWindow;
|
||||
var gCategoryUtilities;
|
||||
var gProvider;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
add_task(function test() {
|
||||
gProvider = new MockProvider();
|
||||
|
||||
gProvider.createAddons([{
|
||||
@ -31,16 +29,9 @@ function test() {
|
||||
version: "789"
|
||||
}]);
|
||||
|
||||
open_manager(null, function(aWindow) {
|
||||
gManagerWindow = aWindow;
|
||||
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
|
||||
run_next_test();
|
||||
});
|
||||
}
|
||||
|
||||
function end_test() {
|
||||
close_manager(gManagerWindow, finish);
|
||||
}
|
||||
gManagerWindow = yield open_manager();
|
||||
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
|
||||
});
|
||||
|
||||
function get(aId) {
|
||||
return gManagerWindow.document.getElementById(aId);
|
||||
@ -54,58 +45,54 @@ function open_details(aList, aItem, aCallback) {
|
||||
aList.ensureElementIsVisible(aItem);
|
||||
EventUtils.synthesizeMouseAtCenter(aItem, { clickCount: 1 }, gManagerWindow);
|
||||
EventUtils.synthesizeMouseAtCenter(aItem, { clickCount: 2 }, gManagerWindow);
|
||||
wait_for_view_load(gManagerWindow, aCallback);
|
||||
return new Promise(resolve => wait_for_view_load(gManagerWindow, resolve));
|
||||
}
|
||||
|
||||
function check_addon_has_version(aList, aName, aVersion) {
|
||||
let check_addon_has_version = Task.async(function*(aList, aName, aVersion) {
|
||||
for (let i = 0; i < aList.itemCount; i++) {
|
||||
let item = aList.getItemAtIndex(i);
|
||||
if (get_node(item, "name").value === aName) {
|
||||
ok(true, "Item with correct name found");
|
||||
is(get_node(item, "version").value, aVersion, "Item has correct version");
|
||||
let { version } = yield get_tooltip_info(item);
|
||||
is(version, aVersion, "Item has correct version");
|
||||
return item;
|
||||
}
|
||||
}
|
||||
ok(false, "Item with correct name was not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
add_test(function() {
|
||||
gCategoryUtilities.openType("extension", function() {
|
||||
info("Extension");
|
||||
let list = gManagerWindow.document.getElementById("addon-list");
|
||||
let item = check_addon_has_version(list, "Extension 1", "123");
|
||||
open_details(list, item, function() {
|
||||
is_element_visible(get("detail-version"), "Details view has version visible");
|
||||
is(get("detail-version").value, "123", "Details view has correct version");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
gCategoryUtilities.openType("theme", function() {
|
||||
info("Normal theme");
|
||||
let list = gManagerWindow.document.getElementById("addon-list");
|
||||
let item = check_addon_has_version(list, "Theme 2", "456");
|
||||
open_details(list, item, function() {
|
||||
is_element_visible(get("detail-version"), "Details view has version visible");
|
||||
is(get("detail-version").value, "456", "Details view has correct version");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
add_task(function*() {
|
||||
yield gCategoryUtilities.openType("extension");
|
||||
info("Extension");
|
||||
let list = gManagerWindow.document.getElementById("addon-list");
|
||||
let item = yield check_addon_has_version(list, "Extension 1", "123");
|
||||
yield open_details(list, item);
|
||||
is_element_visible(get("detail-version"), "Details view has version visible");
|
||||
is(get("detail-version").value, "123", "Details view has correct version");
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
gCategoryUtilities.openType("theme", function() {
|
||||
info("Lightweight theme");
|
||||
let list = gManagerWindow.document.getElementById("addon-list");
|
||||
// See that the version isn't displayed
|
||||
let item = check_addon_has_version(list, "Persona 3", "");
|
||||
open_details(list, item, function() {
|
||||
is_element_hidden(get("detail-version"), "Details view has version hidden");
|
||||
// If the version element is hidden then we don't care about its value
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
add_task(function*() {
|
||||
yield gCategoryUtilities.openType("theme");
|
||||
info("Normal theme");
|
||||
let list = gManagerWindow.document.getElementById("addon-list");
|
||||
let item = yield check_addon_has_version(list, "Theme 2", "456");
|
||||
yield open_details(list, item);
|
||||
is_element_visible(get("detail-version"), "Details view has version visible");
|
||||
is(get("detail-version").value, "456", "Details view has correct version");
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
yield gCategoryUtilities.openType("theme");
|
||||
info("Lightweight theme");
|
||||
let list = gManagerWindow.document.getElementById("addon-list");
|
||||
// See that the version isn't displayed
|
||||
let item = yield check_addon_has_version(list, "Persona 3", undefined);
|
||||
yield open_details(list, item);
|
||||
is_element_hidden(get("detail-version"), "Details view has version hidden");
|
||||
// If the version element is hidden then we don't care about its value
|
||||
});
|
||||
|
||||
add_task(function end_test() {
|
||||
close_manager(gManagerWindow, finish);
|
||||
});
|
||||
|
@ -8,19 +8,12 @@
|
||||
var gManagerWindow;
|
||||
var gCategoryUtilities;
|
||||
|
||||
function test() {
|
||||
add_task(function* test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
open_manager("addons://list/extension", function(aWindow) {
|
||||
gManagerWindow = aWindow;
|
||||
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
|
||||
run_next_test();
|
||||
});
|
||||
}
|
||||
|
||||
function end_test() {
|
||||
close_manager(gManagerWindow, finish);
|
||||
}
|
||||
gManagerWindow = yield open_manager("addons://list/extension");
|
||||
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
|
||||
});
|
||||
|
||||
function get_list_item_count() {
|
||||
return get_test_items_in_list(gManagerWindow).length;
|
||||
@ -34,21 +27,23 @@ function get_class_node(parent, cls) {
|
||||
return parent.ownerDocument.getAnonymousElementByAttribute(parent, "class", cls);
|
||||
}
|
||||
|
||||
function install_addon(aXpi, aCallback) {
|
||||
AddonManager.getInstallForURL(TESTROOT + "addons/" + aXpi + ".xpi",
|
||||
function(aInstall) {
|
||||
aInstall.addListener({
|
||||
onInstallEnded: function(aInstall) {
|
||||
executeSoon(aCallback);
|
||||
}
|
||||
});
|
||||
aInstall.install();
|
||||
}, "application/x-xpinstall");
|
||||
function install_addon(aXpi) {
|
||||
return new Promise(resolve => {
|
||||
AddonManager.getInstallForURL(TESTROOT + "addons/" + aXpi + ".xpi",
|
||||
function(aInstall) {
|
||||
aInstall.addListener({
|
||||
onInstallEnded: function(aInstall) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
aInstall.install();
|
||||
}, "application/x-xpinstall");
|
||||
});
|
||||
}
|
||||
|
||||
function check_addon(aAddon, version) {
|
||||
let check_addon = Task.async(function*(aAddon, aVersion) {
|
||||
is(get_list_item_count(), 1, "Should be one item in the list");
|
||||
is(aAddon.version, version, "Add-on should have the right version");
|
||||
is(aAddon.version, aVersion, "Add-on should have the right version");
|
||||
|
||||
let item = get_addon_element(gManagerWindow, "addon1@tests.mozilla.org");
|
||||
ok(!!item, "Should see the add-on in the list");
|
||||
@ -56,127 +51,108 @@ function check_addon(aAddon, version) {
|
||||
// Force XBL to apply
|
||||
item.clientTop;
|
||||
|
||||
is(get_node(item, "version").value, version, "Version should be correct");
|
||||
let { version } = yield get_tooltip_info(item);
|
||||
is(version, aVersion, "Version should be correct");
|
||||
|
||||
if (aAddon.userDisabled)
|
||||
is_element_visible(get_class_node(item, "disabled-postfix"), "Disabled postfix should be hidden");
|
||||
else
|
||||
is_element_hidden(get_class_node(item, "disabled-postfix"), "Disabled postfix should be hidden");
|
||||
}
|
||||
});
|
||||
|
||||
// Install version 1 then upgrade to version 2 with the manager open
|
||||
add_test(function() {
|
||||
install_addon("browser_bug596336_1", function() {
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(aAddon) {
|
||||
check_addon(aAddon, "1.0");
|
||||
ok(!aAddon.userDisabled, "Add-on should not be disabled");
|
||||
add_task(function() {
|
||||
yield install_addon("browser_bug596336_1");
|
||||
let [aAddon] = yield promiseAddonsByIDs(["addon1@tests.mozilla.org"]);
|
||||
yield check_addon(aAddon, "1.0");
|
||||
ok(!aAddon.userDisabled, "Add-on should not be disabled");
|
||||
|
||||
install_addon("browser_bug596336_2", function() {
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(aAddon) {
|
||||
check_addon(aAddon, "2.0");
|
||||
ok(!aAddon.userDisabled, "Add-on should not be disabled");
|
||||
yield install_addon("browser_bug596336_2");
|
||||
[aAddon] = yield promiseAddonsByIDs(["addon1@tests.mozilla.org"]);
|
||||
yield check_addon(aAddon, "2.0");
|
||||
ok(!aAddon.userDisabled, "Add-on should not be disabled");
|
||||
|
||||
aAddon.uninstall();
|
||||
aAddon.uninstall();
|
||||
|
||||
is(get_list_item_count(), 0, "Should be no items in the list");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
is(get_list_item_count(), 0, "Should be no items in the list");
|
||||
});
|
||||
|
||||
// Install version 1 mark it as disabled then upgrade to version 2 with the
|
||||
// manager open
|
||||
add_test(function() {
|
||||
install_addon("browser_bug596336_1", function() {
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(aAddon) {
|
||||
aAddon.userDisabled = true;
|
||||
check_addon(aAddon, "1.0");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
add_task(function() {
|
||||
yield install_addon("browser_bug596336_1");
|
||||
let [aAddon] = yield promiseAddonsByIDs(["addon1@tests.mozilla.org"]);
|
||||
aAddon.userDisabled = true;
|
||||
yield check_addon(aAddon, "1.0");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
|
||||
install_addon("browser_bug596336_2", function() {
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(aAddon) {
|
||||
check_addon(aAddon, "2.0");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
yield install_addon("browser_bug596336_2");
|
||||
[aAddon] = yield promiseAddonsByIDs(["addon1@tests.mozilla.org"]);
|
||||
yield check_addon(aAddon, "2.0");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
|
||||
aAddon.uninstall();
|
||||
aAddon.uninstall();
|
||||
|
||||
is(get_list_item_count(), 0, "Should be no items in the list");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
is(get_list_item_count(), 0, "Should be no items in the list");
|
||||
});
|
||||
|
||||
// Install version 1 click the remove button and then upgrade to version 2 with
|
||||
// the manager open
|
||||
add_test(function() {
|
||||
install_addon("browser_bug596336_1", function() {
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(aAddon) {
|
||||
check_addon(aAddon, "1.0");
|
||||
ok(!aAddon.userDisabled, "Add-on should not be disabled");
|
||||
add_task(function() {
|
||||
yield install_addon("browser_bug596336_1");
|
||||
let [aAddon] = yield promiseAddonsByIDs(["addon1@tests.mozilla.org"]);
|
||||
yield check_addon(aAddon, "1.0");
|
||||
ok(!aAddon.userDisabled, "Add-on should not be disabled");
|
||||
|
||||
let item = get_addon_element(gManagerWindow, "addon1@tests.mozilla.org");
|
||||
EventUtils.synthesizeMouseAtCenter(get_node(item, "remove-btn"), { }, gManagerWindow);
|
||||
let item = get_addon_element(gManagerWindow, "addon1@tests.mozilla.org");
|
||||
EventUtils.synthesizeMouseAtCenter(get_node(item, "remove-btn"), { }, gManagerWindow);
|
||||
|
||||
// Force XBL to apply
|
||||
item.clientTop;
|
||||
// Force XBL to apply
|
||||
item.clientTop;
|
||||
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
ok(!aAddon.pendingUninstall, "Add-on should not be pending uninstall");
|
||||
is_element_visible(get_class_node(item, "pending"), "Pending message should be visible");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
ok(!aAddon.pendingUninstall, "Add-on should not be pending uninstall");
|
||||
is_element_visible(get_class_node(item, "pending"), "Pending message should be visible");
|
||||
|
||||
install_addon("browser_bug596336_2", function() {
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(aAddon) {
|
||||
check_addon(aAddon, "2.0");
|
||||
ok(!aAddon.userDisabled, "Add-on should not be disabled");
|
||||
yield install_addon("browser_bug596336_2");
|
||||
[aAddon] = yield promiseAddonsByIDs(["addon1@tests.mozilla.org"]);
|
||||
yield check_addon(aAddon, "2.0");
|
||||
ok(!aAddon.userDisabled, "Add-on should not be disabled");
|
||||
|
||||
aAddon.uninstall();
|
||||
aAddon.uninstall();
|
||||
|
||||
is(get_list_item_count(), 0, "Should be no items in the list");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
is(get_list_item_count(), 0, "Should be no items in the list");
|
||||
});
|
||||
|
||||
// Install version 1, disable it, click the remove button and then upgrade to
|
||||
// version 2 with the manager open
|
||||
add_test(function() {
|
||||
install_addon("browser_bug596336_1", function() {
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(aAddon) {
|
||||
aAddon.userDisabled = true;
|
||||
check_addon(aAddon, "1.0");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
add_task(function() {
|
||||
yield install_addon("browser_bug596336_1");
|
||||
let [aAddon] = yield promiseAddonsByIDs(["addon1@tests.mozilla.org"]);
|
||||
aAddon.userDisabled = true;
|
||||
yield check_addon(aAddon, "1.0");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
|
||||
let item = get_addon_element(gManagerWindow, "addon1@tests.mozilla.org");
|
||||
EventUtils.synthesizeMouseAtCenter(get_node(item, "remove-btn"), { }, gManagerWindow);
|
||||
let item = get_addon_element(gManagerWindow, "addon1@tests.mozilla.org");
|
||||
EventUtils.synthesizeMouseAtCenter(get_node(item, "remove-btn"), { }, gManagerWindow);
|
||||
|
||||
// Force XBL to apply
|
||||
item.clientTop;
|
||||
// Force XBL to apply
|
||||
item.clientTop;
|
||||
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
ok(!aAddon.pendingUninstall, "Add-on should not be pending uninstall");
|
||||
is_element_visible(get_class_node(item, "pending"), "Pending message should be visible");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
ok(!aAddon.pendingUninstall, "Add-on should not be pending uninstall");
|
||||
is_element_visible(get_class_node(item, "pending"), "Pending message should be visible");
|
||||
|
||||
install_addon("browser_bug596336_2", function() {
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(aAddon) {
|
||||
check_addon(aAddon, "2.0");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
yield install_addon("browser_bug596336_2");
|
||||
[aAddon] = yield promiseAddonsByIDs(["addon1@tests.mozilla.org"]);
|
||||
yield check_addon(aAddon, "2.0");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
|
||||
aAddon.uninstall();
|
||||
aAddon.uninstall();
|
||||
|
||||
is(get_list_item_count(), 0, "Should be no items in the list");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
is(get_list_item_count(), 0, "Should be no items in the list");
|
||||
});
|
||||
|
||||
add_task(function end_test() {
|
||||
close_manager(gManagerWindow, finish);
|
||||
});
|
||||
|
@ -426,8 +426,8 @@ add_task(function testActivateRealExperiments() {
|
||||
is_element_hidden(el, "warning-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container");
|
||||
is_element_hidden(el, "pending-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "version");
|
||||
is_element_hidden(el, "version should be hidden.");
|
||||
let { version } = yield get_tooltip_info(item);
|
||||
Assert.equal(version, undefined, "version should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
|
||||
is_element_hidden(el, "disabled-postfix should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix");
|
||||
@ -459,8 +459,8 @@ add_task(function testActivateRealExperiments() {
|
||||
is_element_hidden(el, "warning-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container");
|
||||
is_element_hidden(el, "pending-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "version");
|
||||
is_element_hidden(el, "version should be hidden.");
|
||||
({ version }) = yield get_tooltip_info(item);
|
||||
Assert.equal(version, undefined, "version should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
|
||||
is_element_hidden(el, "disabled-postfix should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix");
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -55,11 +55,17 @@ add_test(function() {
|
||||
|
||||
|
||||
add_test(function() {
|
||||
let finished = 0;
|
||||
function maybeRunNext() {
|
||||
if (++finished == 2)
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
gAvailableCategory.addEventListener("CategoryBadgeUpdated", function() {
|
||||
gAvailableCategory.removeEventListener("CategoryBadgeUpdated", arguments.callee, false);
|
||||
is(gCategoryUtilities.isVisible(gAvailableCategory), true, "Available Updates category should now be visible");
|
||||
is(gAvailableCategory.badgeCount, 1, "Badge for Available Updates should now be 1");
|
||||
run_next_test();
|
||||
maybeRunNext();
|
||||
}, false);
|
||||
|
||||
gCategoryUtilities.openType("extension", function() {
|
||||
@ -71,7 +77,10 @@ add_test(function() {
|
||||
}]);
|
||||
|
||||
var item = get_addon_element(gManagerWindow, "addon2@tests.mozilla.org");
|
||||
is(item._version.value, "1.0", "Should still show the old version in the normal list");
|
||||
get_tooltip_info(item).then(({ version }) => {
|
||||
is(version, "1.0", "Should still show the old version in the tooltip");
|
||||
maybeRunNext();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -92,66 +101,61 @@ add_test(function() {
|
||||
var item = list.firstChild;
|
||||
is(item.mAddon.id, "addon2@tests.mozilla.org", "Update item should be for the manually updating addon");
|
||||
|
||||
// for manual update items, update-related properties are updated asynchronously,
|
||||
// so we poll for one of the expected changes to know when its done
|
||||
function waitForAsyncInit() {
|
||||
if (item._version.value == "1.1") {
|
||||
run_next_test();
|
||||
return;
|
||||
}
|
||||
info("Update item not initialized yet, checking again in 100ms");
|
||||
setTimeout(waitForAsyncInit, 100);
|
||||
}
|
||||
waitForAsyncInit();
|
||||
// The item in the list will be checking for update information asynchronously
|
||||
// so we have to wait for it to complete. Doing the same async request should
|
||||
// make our callback be called later.
|
||||
AddonManager.getAllInstalls(run_next_test);
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
var list = gManagerWindow.document.getElementById("updates-list");
|
||||
var item = list.firstChild;
|
||||
is(item._version.value, "1.1", "Update item should have version number of the update");
|
||||
var postfix = gManagerWindow.document.getAnonymousElementByAttribute(item, "class", "update-postfix");
|
||||
is_element_visible(postfix, "'Update' postfix should be visible");
|
||||
is_element_visible(item._updateAvailable, "");
|
||||
is_element_visible(item._relNotesToggle, "Release notes toggle should be visible");
|
||||
is_element_hidden(item._warning, "Incompatible warning should be hidden");
|
||||
is_element_hidden(item._error, "Blocklist error should be hidden");
|
||||
get_tooltip_info(item).then(({ version }) => {
|
||||
is(version, "1.1", "Update item should have version number of the update");
|
||||
var postfix = gManagerWindow.document.getAnonymousElementByAttribute(item, "class", "update-postfix");
|
||||
is_element_visible(postfix, "'Update' postfix should be visible");
|
||||
is_element_visible(item._updateAvailable, "");
|
||||
is_element_visible(item._relNotesToggle, "Release notes toggle should be visible");
|
||||
is_element_hidden(item._warning, "Incompatible warning should be hidden");
|
||||
is_element_hidden(item._error, "Blocklist error should be hidden");
|
||||
|
||||
info("Opening release notes");
|
||||
item.addEventListener("RelNotesToggle", function() {
|
||||
item.removeEventListener("RelNotesToggle", arguments.callee, false);
|
||||
info("Release notes now open");
|
||||
|
||||
is_element_hidden(item._relNotesLoading, "Release notes loading message should be hidden");
|
||||
is_element_visible(item._relNotesError, "Release notes error message should be visible");
|
||||
is(item._relNotes.childElementCount, 0, "Release notes should be empty");
|
||||
|
||||
info("Closing release notes");
|
||||
info("Opening release notes");
|
||||
item.addEventListener("RelNotesToggle", function() {
|
||||
item.removeEventListener("RelNotesToggle", arguments.callee, false);
|
||||
info("Release notes now closed");
|
||||
info("Setting Release notes URI to something that should load");
|
||||
gProvider.installs[0].releaseNotesURI = Services.io.newURI(TESTROOT + "releaseNotes.xhtml", null, null)
|
||||
info("Release notes now open");
|
||||
|
||||
info("Re-opening release notes");
|
||||
is_element_hidden(item._relNotesLoading, "Release notes loading message should be hidden");
|
||||
is_element_visible(item._relNotesError, "Release notes error message should be visible");
|
||||
is(item._relNotes.childElementCount, 0, "Release notes should be empty");
|
||||
|
||||
info("Closing release notes");
|
||||
item.addEventListener("RelNotesToggle", function() {
|
||||
item.removeEventListener("RelNotesToggle", arguments.callee, false);
|
||||
info("Release notes now open");
|
||||
info("Release notes now closed");
|
||||
info("Setting Release notes URI to something that should load");
|
||||
gProvider.installs[0].releaseNotesURI = Services.io.newURI(TESTROOT + "releaseNotes.xhtml", null, null)
|
||||
|
||||
is_element_hidden(item._relNotesLoading, "Release notes loading message should be hidden");
|
||||
is_element_hidden(item._relNotesError, "Release notes error message should be hidden");
|
||||
isnot(item._relNotes.childElementCount, 0, "Release notes should have been inserted into container");
|
||||
run_next_test();
|
||||
info("Re-opening release notes");
|
||||
item.addEventListener("RelNotesToggle", function() {
|
||||
item.removeEventListener("RelNotesToggle", arguments.callee, false);
|
||||
info("Release notes now open");
|
||||
|
||||
is_element_hidden(item._relNotesLoading, "Release notes loading message should be hidden");
|
||||
is_element_hidden(item._relNotesError, "Release notes error message should be hidden");
|
||||
isnot(item._relNotes.childElementCount, 0, "Release notes should have been inserted into container");
|
||||
run_next_test();
|
||||
|
||||
}, false);
|
||||
EventUtils.synthesizeMouseAtCenter(item._relNotesToggle, { }, gManagerWindow);
|
||||
is_element_visible(item._relNotesLoading, "Release notes loading message should be visible");
|
||||
|
||||
}, false);
|
||||
EventUtils.synthesizeMouseAtCenter(item._relNotesToggle, { }, gManagerWindow);
|
||||
is_element_visible(item._relNotesLoading, "Release notes loading message should be visible");
|
||||
|
||||
}, false);
|
||||
EventUtils.synthesizeMouseAtCenter(item._relNotesToggle, { }, gManagerWindow);
|
||||
|
||||
}, false);
|
||||
EventUtils.synthesizeMouseAtCenter(item._relNotesToggle, { }, gManagerWindow);
|
||||
is_element_visible(item._relNotesLoading, "Release notes loading message should be visible");
|
||||
is_element_visible(item._relNotesLoading, "Release notes loading message should be visible");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
@ -49,16 +49,20 @@ add_test(function() {
|
||||
gProvider.installs[0]._addonToInstall = newAddon;
|
||||
|
||||
var item = get_addon_element(gManagerWindow, "addon1@tests.mozilla.org");
|
||||
is(item._version.value, "1.0", "Should still show the old version in the normal list");
|
||||
var name = gManagerWindow.document.getAnonymousElementByAttribute(item, "anonid", "name");
|
||||
is(name.value, "manually updating addon", "Should show the old name in the list");
|
||||
var update = gManagerWindow.document.getAnonymousElementByAttribute(item, "anonid", "update-btn");
|
||||
is_element_visible(update, "Update button should be visible");
|
||||
get_tooltip_info(item).then(({ name, version }) => {
|
||||
is(name, "manually updating addon", "Should show the old name in the tooltip");
|
||||
is(version, "1.0", "Should still show the old version in the tooltip");
|
||||
|
||||
item = get_addon_element(gManagerWindow, "addon2@tests.mozilla.org");
|
||||
is(item, null, "Should not show the new version in the list");
|
||||
var update = gManagerWindow.document.getAnonymousElementByAttribute(item, "anonid", "update-btn");
|
||||
is_element_visible(update, "Update button should be visible");
|
||||
|
||||
run_next_test();
|
||||
item = get_addon_element(gManagerWindow, "addon2@tests.mozilla.org");
|
||||
is(item, null, "Should not show the new version in the list");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -227,6 +227,45 @@ function run_next_test() {
|
||||
executeSoon(() => log_exceptions(test));
|
||||
}
|
||||
|
||||
let get_tooltip_info = Task.async(function*(addon) {
|
||||
let managerWindow = addon.ownerDocument.defaultView;
|
||||
|
||||
// The popup code uses a triggering event's target to set the
|
||||
// document.tooltipNode property.
|
||||
let nameNode = addon.ownerDocument.getAnonymousElementByAttribute(addon, "anonid", "name");
|
||||
let event = new managerWindow.CustomEvent("TriggerEvent");
|
||||
nameNode.dispatchEvent(event);
|
||||
|
||||
let tooltip = managerWindow.document.getElementById("addonitem-tooltip");
|
||||
|
||||
let promise = BrowserTestUtils.waitForEvent(tooltip, "popupshown");
|
||||
tooltip.openPopup(nameNode, "after_start", 0, 0, false, false, event);
|
||||
yield promise;
|
||||
|
||||
let tiptext = tooltip.label;
|
||||
|
||||
promise = BrowserTestUtils.waitForEvent(tooltip, "popuphidden");
|
||||
tooltip.hidePopup();
|
||||
yield promise;
|
||||
|
||||
let expectedName = addon.getAttribute("name");
|
||||
ok(tiptext.substring(0, expectedName.length), expectedName,
|
||||
"Tooltip should always start with the expected name");
|
||||
|
||||
if (expectedName.length == tiptext.length) {
|
||||
return {
|
||||
name: tiptext,
|
||||
version: undefined
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
name: tiptext.substring(0, expectedName.length),
|
||||
version: tiptext.substring(expectedName.length + 1)
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function get_addon_file_url(aFilename) {
|
||||
try {
|
||||
var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].
|
||||
@ -489,6 +528,11 @@ function is_element_hidden(aElement, aMsg) {
|
||||
ok(is_hidden(aElement), aMsg || (aElement + " should be hidden"));
|
||||
}
|
||||
|
||||
function promiseAddonsByIDs(aIDs) {
|
||||
return new Promise(resolve => {
|
||||
AddonManager.getAddonsByIDs(aIDs, resolve);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Install an add-on and call a callback when complete.
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user