mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-12 18:50:08 +00:00
Merge fx-team to m-c a=merge
This commit is contained in:
commit
103ab1ed0e
@ -326,7 +326,9 @@
|
||||
@RESPATH@/components/toolkit_finalizationwitness.xpt
|
||||
@RESPATH@/components/toolkit_formautofill.xpt
|
||||
@RESPATH@/components/toolkit_osfile.xpt
|
||||
#ifdef NIGHTLY_BUILD
|
||||
@RESPATH@/components/toolkit_perfmonitoring.xpt
|
||||
#endif
|
||||
@RESPATH@/components/toolkit_xulstore.xpt
|
||||
@RESPATH@/components/toolkitprofile.xpt
|
||||
#ifdef MOZ_ENABLE_XREMOTE
|
||||
|
@ -1180,8 +1180,8 @@ nsContextMenu.prototype = {
|
||||
|
||||
// Helper function to wait for appropriate MIME-type headers and
|
||||
// then prompt the user with a file picker
|
||||
saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc,
|
||||
linkDownload) {
|
||||
saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc, docURI,
|
||||
windowID, linkDownload) {
|
||||
// canonical def in nsURILoader.h
|
||||
const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
|
||||
|
||||
@ -1216,7 +1216,10 @@ nsContextMenu.prototype = {
|
||||
|
||||
const promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
|
||||
getService(Ci.nsIPromptService);
|
||||
promptSvc.alert(doc.defaultView, title, msg);
|
||||
const wm = Cc["@mozilla.org/appshell/window-mediator;1"].
|
||||
getService(Ci.nsIWindowMediator);
|
||||
let window = wm.getOuterWindowWithId(windowID);
|
||||
promptSvc.alert(window, title, msg);
|
||||
} catch (ex) {}
|
||||
return;
|
||||
}
|
||||
@ -1236,8 +1239,8 @@ nsContextMenu.prototype = {
|
||||
if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) {
|
||||
// do it the old fashioned way, which will pick the best filename
|
||||
// it can without waiting.
|
||||
saveURL(linkURL, linkText, dialogTitle, bypassCache, false,
|
||||
BrowserUtils.makeURIFromCPOW(doc.documentURIObject), doc);
|
||||
saveURL(linkURL, linkText, dialogTitle, bypassCache, false, docURI,
|
||||
doc);
|
||||
}
|
||||
if (this.extListener)
|
||||
this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
|
||||
@ -1306,7 +1309,7 @@ nsContextMenu.prototype = {
|
||||
channel.loadFlags |= flags;
|
||||
|
||||
if (channel instanceof Ci.nsIHttpChannel) {
|
||||
channel.referrer = BrowserUtils.makeURIFromCPOW(doc.documentURIObject);
|
||||
channel.referrer = docURI;
|
||||
if (channel instanceof Ci.nsIHttpChannelInternal)
|
||||
channel.forceAllowThirdPartyCookie = true;
|
||||
}
|
||||
@ -1326,6 +1329,8 @@ nsContextMenu.prototype = {
|
||||
saveLink: function() {
|
||||
urlSecurityCheck(this.linkURL, this.principal);
|
||||
this.saveHelper(this.linkURL, this.linkText, null, true, this.ownerDoc,
|
||||
gContextMenuContentData.documentURIObject,
|
||||
gContextMenuContentData.frameOuterWindowID,
|
||||
this.linkDownload);
|
||||
},
|
||||
|
||||
@ -1338,23 +1343,23 @@ nsContextMenu.prototype = {
|
||||
// Save URL of the clicked upon image, video, or audio.
|
||||
saveMedia: function() {
|
||||
var doc = this.target.ownerDocument;
|
||||
let referrerURI = gContextMenuContentData.documentURIObject;
|
||||
if (this.onCanvas) {
|
||||
// Bypass cache, since it's a data: URL.
|
||||
saveImageURL(this.target.toDataURL(), "canvas.png", "SaveImageTitle",
|
||||
true, false, gContextMenuContentData.documentURIObject,
|
||||
doc);
|
||||
true, false, referrerURI, doc);
|
||||
}
|
||||
else if (this.onImage) {
|
||||
urlSecurityCheck(this.mediaURL, this.principal);
|
||||
let uri = gContextMenuContentData.documentURIObject;
|
||||
saveImageURL(this.mediaURL, null, "SaveImageTitle", false,
|
||||
false, uri, doc, gContextMenuContentData.contentType,
|
||||
false, referrerURI, doc, gContextMenuContentData.contentType,
|
||||
gContextMenuContentData.contentDisposition);
|
||||
}
|
||||
else if (this.onVideo || this.onAudio) {
|
||||
urlSecurityCheck(this.mediaURL, this.principal);
|
||||
var dialogTitle = this.onVideo ? "SaveVideoTitle" : "SaveAudioTitle";
|
||||
this.saveHelper(this.mediaURL, null, dialogTitle, false, doc, "");
|
||||
this.saveHelper(this.mediaURL, null, dialogTitle, false, doc, referrerURI,
|
||||
gContextMenuContentData.frameOuterWindowID, "");
|
||||
}
|
||||
},
|
||||
|
||||
@ -1662,7 +1667,7 @@ nsContextMenu.prototype = {
|
||||
},
|
||||
|
||||
printFrame: function CM_printFrame() {
|
||||
PrintUtils.print(this.target.ownerDocument.defaultView);
|
||||
PrintUtils.print(this.target.ownerDocument.defaultView, this.browser);
|
||||
},
|
||||
|
||||
switchPageDirection: function CM_switchPageDirection() {
|
||||
|
@ -1,8 +1,5 @@
|
||||
|
||||
|
||||
let AddonManager = Cu.import("resource://gre/modules/AddonManager.jsm", {}).AddonManager;
|
||||
let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
|
||||
let AddonWatcher = Cu.import("resource://gre/modules/AddonWatcher.jsm", {}).AddonWatcher;
|
||||
|
||||
const ADDON_TYPE_SERVICE = "service";
|
||||
const ID_SUFFIX = "@services.mozilla.org";
|
||||
|
@ -49,14 +49,13 @@ loop.conversation = (function(mozL10n) {
|
||||
case "outgoing": {
|
||||
return (React.createElement(CallControllerView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
mozLoop: this.props.mozLoop}
|
||||
));
|
||||
mozLoop: this.props.mozLoop}));
|
||||
}
|
||||
case "room": {
|
||||
return (React.createElement(DesktopRoomConversationView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
roomStore: this.props.roomStore}
|
||||
));
|
||||
mozLoop: this.props.mozLoop,
|
||||
roomStore: this.props.roomStore}));
|
||||
}
|
||||
case "failed": {
|
||||
return React.createElement(GenericFailureView, {cancelCall: this.closeWindow});
|
||||
|
@ -49,14 +49,13 @@ loop.conversation = (function(mozL10n) {
|
||||
case "outgoing": {
|
||||
return (<CallControllerView
|
||||
dispatcher={this.props.dispatcher}
|
||||
mozLoop={this.props.mozLoop}
|
||||
/>);
|
||||
mozLoop={this.props.mozLoop} />);
|
||||
}
|
||||
case "room": {
|
||||
return (<DesktopRoomConversationView
|
||||
dispatcher={this.props.dispatcher}
|
||||
roomStore={this.props.roomStore}
|
||||
/>);
|
||||
mozLoop={this.props.mozLoop}
|
||||
roomStore={this.props.roomStore} />);
|
||||
}
|
||||
case "failed": {
|
||||
return <GenericFailureView cancelCall={this.closeWindow} />;
|
||||
|
@ -228,7 +228,6 @@ loop.conversationViews = (function(mozL10n) {
|
||||
},
|
||||
|
||||
render: function() {
|
||||
/* jshint ignore:start */
|
||||
var dropdownMenuClassesDecline = React.addons.classSet({
|
||||
"native-dropdown-menu": true,
|
||||
"conversation-window-dropdown": true,
|
||||
@ -276,7 +275,6 @@ loop.conversationViews = (function(mozL10n) {
|
||||
)
|
||||
)
|
||||
);
|
||||
/* jshint ignore:end */
|
||||
}
|
||||
});
|
||||
|
||||
@ -293,7 +291,6 @@ loop.conversationViews = (function(mozL10n) {
|
||||
render: function() {
|
||||
var mode = this.props.mode;
|
||||
return (
|
||||
/* jshint ignore:start */
|
||||
React.createElement("div", {className: "btn-chevron-menu-group"},
|
||||
React.createElement("div", {className: "btn-group"},
|
||||
React.createElement("button", {className: "btn btn-accept",
|
||||
@ -310,7 +307,6 @@ loop.conversationViews = (function(mozL10n) {
|
||||
)
|
||||
)
|
||||
)
|
||||
/* jshint ignore:end */
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -228,7 +228,6 @@ loop.conversationViews = (function(mozL10n) {
|
||||
},
|
||||
|
||||
render: function() {
|
||||
/* jshint ignore:start */
|
||||
var dropdownMenuClassesDecline = React.addons.classSet({
|
||||
"native-dropdown-menu": true,
|
||||
"conversation-window-dropdown": true,
|
||||
@ -276,7 +275,6 @@ loop.conversationViews = (function(mozL10n) {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
/* jshint ignore:end */
|
||||
}
|
||||
});
|
||||
|
||||
@ -293,7 +291,6 @@ loop.conversationViews = (function(mozL10n) {
|
||||
render: function() {
|
||||
var mode = this.props.mode;
|
||||
return (
|
||||
/* jshint ignore:start */
|
||||
<div className="btn-chevron-menu-group">
|
||||
<div className="btn-group">
|
||||
<button className="btn btn-accept"
|
||||
@ -310,7 +307,6 @@ loop.conversationViews = (function(mozL10n) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
/* jshint ignore:end */
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -31,6 +31,8 @@ loop.roomViews = (function(mozL10n) {
|
||||
componentWillMount: function() {
|
||||
this.listenTo(this.props.roomStore, "change:activeRoom",
|
||||
this._onActiveRoomStateChanged);
|
||||
this.listenTo(this.props.roomStore, "change:error",
|
||||
this._onRoomError);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
@ -46,6 +48,15 @@ loop.roomViews = (function(mozL10n) {
|
||||
}
|
||||
},
|
||||
|
||||
_onRoomError: function() {
|
||||
// Only update the state if we're mounted, to avoid the problem where
|
||||
// stopListening doesn't nuke the active listeners during a event
|
||||
// processing.
|
||||
if (this.isMounted()) {
|
||||
this.setState({error: this.props.roomStore.getStoreState("error")});
|
||||
}
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
var storeState = this.props.roomStore.getStoreState("activeRoom");
|
||||
return _.extend({
|
||||
@ -56,11 +67,12 @@ loop.roomViews = (function(mozL10n) {
|
||||
};
|
||||
|
||||
var SocialShareDropdown = React.createClass({displayName: "SocialShareDropdown",
|
||||
mixins: [ActiveRoomStoreMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
show: React.PropTypes.bool.isRequired
|
||||
roomUrl: React.PropTypes.string,
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
socialShareButtonAvailable: React.PropTypes.bool,
|
||||
socialShareProviders: React.PropTypes.array
|
||||
},
|
||||
|
||||
handleToolbarAddButtonClick: function(event) {
|
||||
@ -79,20 +91,20 @@ loop.roomViews = (function(mozL10n) {
|
||||
event.preventDefault();
|
||||
|
||||
var origin = event.currentTarget.dataset.provider;
|
||||
var provider = this.state.socialShareProviders.filter(function(provider) {
|
||||
var provider = this.props.socialShareProviders.filter(function(provider) {
|
||||
return provider.origin == origin;
|
||||
})[0];
|
||||
|
||||
this.props.dispatcher.dispatch(new sharedActions.ShareRoomUrl({
|
||||
provider: provider,
|
||||
roomUrl: this.state.roomUrl,
|
||||
roomUrl: this.props.roomUrl,
|
||||
previews: []
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// Don't render a thing when no data has been fetched yet.
|
||||
if (!this.state.socialShareProviders) {
|
||||
if (!this.props.socialShareProviders) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -100,13 +112,13 @@ loop.roomViews = (function(mozL10n) {
|
||||
var shareDropdown = cx({
|
||||
"share-service-dropdown": true,
|
||||
"dropdown-menu": true,
|
||||
"share-button-unavailable": !this.state.socialShareButtonAvailable,
|
||||
"share-button-unavailable": !this.props.socialShareButtonAvailable,
|
||||
"hide": !this.props.show
|
||||
});
|
||||
|
||||
// When the button is not yet available, we offer to put it in the navbar
|
||||
// for the user.
|
||||
if (!this.state.socialShareButtonAvailable) {
|
||||
if (!this.props.socialShareButtonAvailable) {
|
||||
return (
|
||||
React.createElement("div", {className: shareDropdown},
|
||||
React.createElement("div", {className: "share-panel-header"},
|
||||
@ -134,9 +146,9 @@ loop.roomViews = (function(mozL10n) {
|
||||
React.createElement("i", {className: "icon icon-add-share-service"}),
|
||||
React.createElement("span", null, mozL10n.get("share_add_service_button"))
|
||||
),
|
||||
this.state.socialShareProviders.length ? React.createElement("li", {className: "dropdown-menu-separator"}) : null,
|
||||
this.props.socialShareProviders.length ? React.createElement("li", {className: "dropdown-menu-separator"}) : null,
|
||||
|
||||
this.state.socialShareProviders.map(function(provider, idx) {
|
||||
this.props.socialShareProviders.map(function(provider, idx) {
|
||||
return (
|
||||
React.createElement("li", {className: "dropdown-menu-item",
|
||||
key: "provider-" + idx,
|
||||
@ -157,30 +169,24 @@ loop.roomViews = (function(mozL10n) {
|
||||
* Desktop room invitation view (overlay).
|
||||
*/
|
||||
var DesktopRoomInvitationView = React.createClass({displayName: "DesktopRoomInvitationView",
|
||||
mixins: [ActiveRoomStoreMixin, React.addons.LinkedStateMixin,
|
||||
sharedMixins.DropdownMenuMixin],
|
||||
mixins: [React.addons.LinkedStateMixin, sharedMixins.DropdownMenuMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
error: React.PropTypes.object,
|
||||
// This data is supplied by the activeRoomStore.
|
||||
roomData: React.PropTypes.object.isRequired,
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
showContext: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
copiedUrl: false,
|
||||
newRoomName: "",
|
||||
error: null,
|
||||
newRoomName: ""
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.listenTo(this.props.roomStore, "change:error",
|
||||
this.onRoomError);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this.stopListening(this.props.roomStore);
|
||||
},
|
||||
|
||||
handleTextareaKeyDown: function(event) {
|
||||
// Submit the form as soon as the user press Enter in that field
|
||||
// Note: We're using a textarea instead of a simple text input to display
|
||||
@ -195,7 +201,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
event.preventDefault();
|
||||
|
||||
this.props.dispatcher.dispatch(new sharedActions.RenameRoom({
|
||||
roomToken: this.state.roomToken,
|
||||
roomToken: this.props.roomData.roomToken,
|
||||
newRoomName: this.state.newRoomName
|
||||
}));
|
||||
},
|
||||
@ -204,14 +210,14 @@ loop.roomViews = (function(mozL10n) {
|
||||
event.preventDefault();
|
||||
|
||||
this.props.dispatcher.dispatch(
|
||||
new sharedActions.EmailRoomUrl({roomUrl: this.state.roomUrl}));
|
||||
new sharedActions.EmailRoomUrl({roomUrl: this.props.roomData.roomUrl}));
|
||||
},
|
||||
|
||||
handleCopyButtonClick: function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.props.dispatcher.dispatch(
|
||||
new sharedActions.CopyRoomUrl({roomUrl: this.state.roomUrl}));
|
||||
new sharedActions.CopyRoomUrl({roomUrl: this.props.roomData.roomUrl}));
|
||||
|
||||
this.setState({copiedUrl: true});
|
||||
},
|
||||
@ -222,51 +228,100 @@ loop.roomViews = (function(mozL10n) {
|
||||
this.toggleDropdownMenu();
|
||||
},
|
||||
|
||||
onRoomError: function() {
|
||||
// Only update the state if we're mounted, to avoid the problem where
|
||||
// stopListening doesn't nuke the active listeners during a event
|
||||
// processing.
|
||||
if (this.isMounted()) {
|
||||
this.setState({error: this.props.roomStore.getStoreState("error")});
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (!this.props.show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var cx = React.addons.classSet;
|
||||
return (
|
||||
React.createElement("div", {className: "room-invitation-overlay"},
|
||||
React.createElement("p", {className: cx({"error": !!this.state.error,
|
||||
"error-display-area": true})},
|
||||
mozL10n.get("rooms_name_change_failed_label")
|
||||
),
|
||||
React.createElement("form", {onSubmit: this.handleFormSubmit},
|
||||
React.createElement("textarea", {rows: "2", type: "text", className: "input-room-name",
|
||||
valueLink: this.linkState("newRoomName"),
|
||||
onBlur: this.handleFormSubmit,
|
||||
onKeyDown: this.handleTextareaKeyDown,
|
||||
placeholder: mozL10n.get("rooms_name_this_room_label")})
|
||||
),
|
||||
React.createElement("p", null, mozL10n.get("invite_header_text")),
|
||||
React.createElement("div", {className: "btn-group call-action-group"},
|
||||
React.createElement("button", {className: "btn btn-info btn-email",
|
||||
onClick: this.handleEmailButtonClick},
|
||||
mozL10n.get("email_link_button")
|
||||
React.createElement("div", {className: "room-invitation-content"},
|
||||
React.createElement("p", {className: cx({"error": !!this.props.error,
|
||||
"error-display-area": true})},
|
||||
mozL10n.get("rooms_name_change_failed_label")
|
||||
),
|
||||
React.createElement("button", {className: "btn btn-info btn-copy",
|
||||
onClick: this.handleCopyButtonClick},
|
||||
this.state.copiedUrl ? mozL10n.get("copied_url_button") :
|
||||
mozL10n.get("copy_url_button2")
|
||||
React.createElement("form", {onSubmit: this.handleFormSubmit},
|
||||
React.createElement("textarea", {rows: "2", type: "text", className: "input-room-name",
|
||||
valueLink: this.linkState("newRoomName"),
|
||||
onBlur: this.handleFormSubmit,
|
||||
onKeyDown: this.handleTextareaKeyDown,
|
||||
placeholder: mozL10n.get("rooms_name_this_room_label")})
|
||||
),
|
||||
React.createElement("button", {className: "btn btn-info btn-share",
|
||||
ref: "anchor",
|
||||
onClick: this.handleShareButtonClick},
|
||||
mozL10n.get("share_button3")
|
||||
)
|
||||
React.createElement("p", null, mozL10n.get("invite_header_text")),
|
||||
React.createElement("div", {className: "btn-group call-action-group"},
|
||||
React.createElement("button", {className: "btn btn-info btn-email",
|
||||
onClick: this.handleEmailButtonClick},
|
||||
mozL10n.get("email_link_button")
|
||||
),
|
||||
React.createElement("button", {className: "btn btn-info btn-copy",
|
||||
onClick: this.handleCopyButtonClick},
|
||||
this.state.copiedUrl ? mozL10n.get("copied_url_button") :
|
||||
mozL10n.get("copy_url_button2")
|
||||
),
|
||||
React.createElement("button", {className: "btn btn-info btn-share",
|
||||
ref: "anchor",
|
||||
onClick: this.handleShareButtonClick},
|
||||
mozL10n.get("share_button3")
|
||||
)
|
||||
),
|
||||
React.createElement(SocialShareDropdown, {dispatcher: this.props.dispatcher,
|
||||
roomUrl: this.props.roomData.roomUrl,
|
||||
show: this.state.showMenu,
|
||||
ref: "menu"})
|
||||
),
|
||||
React.createElement(SocialShareDropdown, {dispatcher: this.props.dispatcher,
|
||||
roomStore: this.props.roomStore,
|
||||
show: this.state.showMenu,
|
||||
ref: "menu"})
|
||||
React.createElement(DesktopRoomContextView, {
|
||||
roomData: this.props.roomData,
|
||||
show: this.props.showContext})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var DesktopRoomContextView = React.createClass({displayName: "DesktopRoomContextView",
|
||||
propTypes: {
|
||||
// This data is supplied by the activeRoomStore.
|
||||
roomData: React.PropTypes.object.isRequired,
|
||||
show: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
// When the 'show' prop is changed from outside this component, we do need
|
||||
// to update the state.
|
||||
if (("show" in nextProps) && nextProps.show !== this.props.show) {
|
||||
this.setState({ show: nextProps.show });
|
||||
}
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return { show: this.props.show };
|
||||
},
|
||||
|
||||
handleCloseClick: function() {
|
||||
this.setState({ show: false });
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (!this.state.show)
|
||||
return null;
|
||||
|
||||
var URL = this.props.roomData.roomContextUrls && this.props.roomData.roomContextUrls[0];
|
||||
var thumbnail = URL && URL.thumbnail || "";
|
||||
var URLDescription = URL && URL.description || "";
|
||||
var location = URL && URL.location || "";
|
||||
return (
|
||||
React.createElement("div", {className: "room-context"},
|
||||
React.createElement("img", {className: "room-context-thumbnail", src: thumbnail}),
|
||||
React.createElement("div", {className: "room-context-content"},
|
||||
React.createElement("div", {className: "room-context-label"}, mozL10n.get("context_inroom_label")),
|
||||
React.createElement("div", {className: "room-context-description"}, URLDescription),
|
||||
React.createElement("a", {className: "room-context-url", href: location, target: "_blank"}, location),
|
||||
this.props.roomData.roomDescription ?
|
||||
React.createElement("div", {className: "room-context-comment"}, this.props.roomData.roomDescription) :
|
||||
null,
|
||||
React.createElement("button", {className: "room-context-btn-close",
|
||||
onClick: this.handleCloseClick})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -285,18 +340,8 @@ loop.roomViews = (function(mozL10n) {
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
_renderInvitationOverlay: function() {
|
||||
if (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS) {
|
||||
return (
|
||||
React.createElement(DesktopRoomInvitationView, {
|
||||
roomStore: this.props.roomStore,
|
||||
dispatcher: this.props.dispatcher})
|
||||
);
|
||||
}
|
||||
return null;
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
mozLoop: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
componentWillUpdate: function(nextProps, nextState) {
|
||||
@ -341,6 +386,17 @@ loop.roomViews = (function(mozL10n) {
|
||||
}));
|
||||
},
|
||||
|
||||
_shouldRenderInvitationOverlay: function() {
|
||||
return (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS);
|
||||
},
|
||||
|
||||
_shouldRenderContextView: function() {
|
||||
return !!(
|
||||
this.props.mozLoop.getLoopPref("contextInConverations.enabled") &&
|
||||
(this.state.roomContextUrls || this.state.roomDescription)
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.roomName) {
|
||||
this.setTitle(this.state.roomName);
|
||||
@ -358,6 +414,10 @@ loop.roomViews = (function(mozL10n) {
|
||||
visible: true
|
||||
};
|
||||
|
||||
var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
|
||||
var shouldRenderContextView = this._shouldRenderContextView();
|
||||
var roomData = this.props.roomStore.getStoreState("activeRoom");
|
||||
|
||||
switch(this.state.roomState) {
|
||||
case ROOM_STATES.FAILED:
|
||||
case ROOM_STATES.FULL: {
|
||||
@ -378,7 +438,12 @@ loop.roomViews = (function(mozL10n) {
|
||||
default: {
|
||||
return (
|
||||
React.createElement("div", {className: "room-conversation-wrapper"},
|
||||
this._renderInvitationOverlay(),
|
||||
React.createElement(DesktopRoomInvitationView, {
|
||||
dispatcher: this.props.dispatcher,
|
||||
error: this.state.error,
|
||||
roomData: roomData,
|
||||
show: shouldRenderInvitationOverlay,
|
||||
showContext: shouldRenderContextView}),
|
||||
React.createElement("div", {className: "video-layout-wrapper"},
|
||||
React.createElement("div", {className: "conversation room-conversation"},
|
||||
React.createElement("div", {className: "media nested"},
|
||||
@ -396,7 +461,10 @@ loop.roomViews = (function(mozL10n) {
|
||||
hangup: this.leaveRoom,
|
||||
screenShare: screenShareData})
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement(DesktopRoomContextView, {
|
||||
roomData: roomData,
|
||||
show: !shouldRenderInvitationOverlay && shouldRenderContextView})
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -407,6 +475,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
return {
|
||||
ActiveRoomStoreMixin: ActiveRoomStoreMixin,
|
||||
SocialShareDropdown: SocialShareDropdown,
|
||||
DesktopRoomContextView: DesktopRoomContextView,
|
||||
DesktopRoomConversationView: DesktopRoomConversationView,
|
||||
DesktopRoomInvitationView: DesktopRoomInvitationView
|
||||
};
|
||||
|
@ -31,6 +31,8 @@ loop.roomViews = (function(mozL10n) {
|
||||
componentWillMount: function() {
|
||||
this.listenTo(this.props.roomStore, "change:activeRoom",
|
||||
this._onActiveRoomStateChanged);
|
||||
this.listenTo(this.props.roomStore, "change:error",
|
||||
this._onRoomError);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
@ -46,6 +48,15 @@ loop.roomViews = (function(mozL10n) {
|
||||
}
|
||||
},
|
||||
|
||||
_onRoomError: function() {
|
||||
// Only update the state if we're mounted, to avoid the problem where
|
||||
// stopListening doesn't nuke the active listeners during a event
|
||||
// processing.
|
||||
if (this.isMounted()) {
|
||||
this.setState({error: this.props.roomStore.getStoreState("error")});
|
||||
}
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
var storeState = this.props.roomStore.getStoreState("activeRoom");
|
||||
return _.extend({
|
||||
@ -56,11 +67,12 @@ loop.roomViews = (function(mozL10n) {
|
||||
};
|
||||
|
||||
var SocialShareDropdown = React.createClass({
|
||||
mixins: [ActiveRoomStoreMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
show: React.PropTypes.bool.isRequired
|
||||
roomUrl: React.PropTypes.string,
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
socialShareButtonAvailable: React.PropTypes.bool,
|
||||
socialShareProviders: React.PropTypes.array
|
||||
},
|
||||
|
||||
handleToolbarAddButtonClick: function(event) {
|
||||
@ -79,20 +91,20 @@ loop.roomViews = (function(mozL10n) {
|
||||
event.preventDefault();
|
||||
|
||||
var origin = event.currentTarget.dataset.provider;
|
||||
var provider = this.state.socialShareProviders.filter(function(provider) {
|
||||
var provider = this.props.socialShareProviders.filter(function(provider) {
|
||||
return provider.origin == origin;
|
||||
})[0];
|
||||
|
||||
this.props.dispatcher.dispatch(new sharedActions.ShareRoomUrl({
|
||||
provider: provider,
|
||||
roomUrl: this.state.roomUrl,
|
||||
roomUrl: this.props.roomUrl,
|
||||
previews: []
|
||||
}));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// Don't render a thing when no data has been fetched yet.
|
||||
if (!this.state.socialShareProviders) {
|
||||
if (!this.props.socialShareProviders) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -100,13 +112,13 @@ loop.roomViews = (function(mozL10n) {
|
||||
var shareDropdown = cx({
|
||||
"share-service-dropdown": true,
|
||||
"dropdown-menu": true,
|
||||
"share-button-unavailable": !this.state.socialShareButtonAvailable,
|
||||
"share-button-unavailable": !this.props.socialShareButtonAvailable,
|
||||
"hide": !this.props.show
|
||||
});
|
||||
|
||||
// When the button is not yet available, we offer to put it in the navbar
|
||||
// for the user.
|
||||
if (!this.state.socialShareButtonAvailable) {
|
||||
if (!this.props.socialShareButtonAvailable) {
|
||||
return (
|
||||
<div className={shareDropdown}>
|
||||
<div className="share-panel-header">
|
||||
@ -134,9 +146,9 @@ loop.roomViews = (function(mozL10n) {
|
||||
<i className="icon icon-add-share-service"></i>
|
||||
<span>{mozL10n.get("share_add_service_button")}</span>
|
||||
</li>
|
||||
{this.state.socialShareProviders.length ? <li className="dropdown-menu-separator"/> : null}
|
||||
{this.props.socialShareProviders.length ? <li className="dropdown-menu-separator"/> : null}
|
||||
{
|
||||
this.state.socialShareProviders.map(function(provider, idx) {
|
||||
this.props.socialShareProviders.map(function(provider, idx) {
|
||||
return (
|
||||
<li className="dropdown-menu-item"
|
||||
key={"provider-" + idx}
|
||||
@ -157,30 +169,24 @@ loop.roomViews = (function(mozL10n) {
|
||||
* Desktop room invitation view (overlay).
|
||||
*/
|
||||
var DesktopRoomInvitationView = React.createClass({
|
||||
mixins: [ActiveRoomStoreMixin, React.addons.LinkedStateMixin,
|
||||
sharedMixins.DropdownMenuMixin],
|
||||
mixins: [React.addons.LinkedStateMixin, sharedMixins.DropdownMenuMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
error: React.PropTypes.object,
|
||||
// This data is supplied by the activeRoomStore.
|
||||
roomData: React.PropTypes.object.isRequired,
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
showContext: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
copiedUrl: false,
|
||||
newRoomName: "",
|
||||
error: null,
|
||||
newRoomName: ""
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.listenTo(this.props.roomStore, "change:error",
|
||||
this.onRoomError);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this.stopListening(this.props.roomStore);
|
||||
},
|
||||
|
||||
handleTextareaKeyDown: function(event) {
|
||||
// Submit the form as soon as the user press Enter in that field
|
||||
// Note: We're using a textarea instead of a simple text input to display
|
||||
@ -195,7 +201,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
event.preventDefault();
|
||||
|
||||
this.props.dispatcher.dispatch(new sharedActions.RenameRoom({
|
||||
roomToken: this.state.roomToken,
|
||||
roomToken: this.props.roomData.roomToken,
|
||||
newRoomName: this.state.newRoomName
|
||||
}));
|
||||
},
|
||||
@ -204,14 +210,14 @@ loop.roomViews = (function(mozL10n) {
|
||||
event.preventDefault();
|
||||
|
||||
this.props.dispatcher.dispatch(
|
||||
new sharedActions.EmailRoomUrl({roomUrl: this.state.roomUrl}));
|
||||
new sharedActions.EmailRoomUrl({roomUrl: this.props.roomData.roomUrl}));
|
||||
},
|
||||
|
||||
handleCopyButtonClick: function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.props.dispatcher.dispatch(
|
||||
new sharedActions.CopyRoomUrl({roomUrl: this.state.roomUrl}));
|
||||
new sharedActions.CopyRoomUrl({roomUrl: this.props.roomData.roomUrl}));
|
||||
|
||||
this.setState({copiedUrl: true});
|
||||
},
|
||||
@ -222,51 +228,100 @@ loop.roomViews = (function(mozL10n) {
|
||||
this.toggleDropdownMenu();
|
||||
},
|
||||
|
||||
onRoomError: function() {
|
||||
// Only update the state if we're mounted, to avoid the problem where
|
||||
// stopListening doesn't nuke the active listeners during a event
|
||||
// processing.
|
||||
if (this.isMounted()) {
|
||||
this.setState({error: this.props.roomStore.getStoreState("error")});
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (!this.props.show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var cx = React.addons.classSet;
|
||||
return (
|
||||
<div className="room-invitation-overlay">
|
||||
<p className={cx({"error": !!this.state.error,
|
||||
"error-display-area": true})}>
|
||||
{mozL10n.get("rooms_name_change_failed_label")}
|
||||
</p>
|
||||
<form onSubmit={this.handleFormSubmit}>
|
||||
<textarea rows="2" type="text" className="input-room-name"
|
||||
valueLink={this.linkState("newRoomName")}
|
||||
onBlur={this.handleFormSubmit}
|
||||
onKeyDown={this.handleTextareaKeyDown}
|
||||
placeholder={mozL10n.get("rooms_name_this_room_label")} />
|
||||
</form>
|
||||
<p>{mozL10n.get("invite_header_text")}</p>
|
||||
<div className="btn-group call-action-group">
|
||||
<button className="btn btn-info btn-email"
|
||||
onClick={this.handleEmailButtonClick}>
|
||||
{mozL10n.get("email_link_button")}
|
||||
</button>
|
||||
<button className="btn btn-info btn-copy"
|
||||
onClick={this.handleCopyButtonClick}>
|
||||
{this.state.copiedUrl ? mozL10n.get("copied_url_button") :
|
||||
mozL10n.get("copy_url_button2")}
|
||||
</button>
|
||||
<button className="btn btn-info btn-share"
|
||||
ref="anchor"
|
||||
onClick={this.handleShareButtonClick}>
|
||||
{mozL10n.get("share_button3")}
|
||||
</button>
|
||||
<div className="room-invitation-content">
|
||||
<p className={cx({"error": !!this.props.error,
|
||||
"error-display-area": true})}>
|
||||
{mozL10n.get("rooms_name_change_failed_label")}
|
||||
</p>
|
||||
<form onSubmit={this.handleFormSubmit}>
|
||||
<textarea rows="2" type="text" className="input-room-name"
|
||||
valueLink={this.linkState("newRoomName")}
|
||||
onBlur={this.handleFormSubmit}
|
||||
onKeyDown={this.handleTextareaKeyDown}
|
||||
placeholder={mozL10n.get("rooms_name_this_room_label")} />
|
||||
</form>
|
||||
<p>{mozL10n.get("invite_header_text")}</p>
|
||||
<div className="btn-group call-action-group">
|
||||
<button className="btn btn-info btn-email"
|
||||
onClick={this.handleEmailButtonClick}>
|
||||
{mozL10n.get("email_link_button")}
|
||||
</button>
|
||||
<button className="btn btn-info btn-copy"
|
||||
onClick={this.handleCopyButtonClick}>
|
||||
{this.state.copiedUrl ? mozL10n.get("copied_url_button") :
|
||||
mozL10n.get("copy_url_button2")}
|
||||
</button>
|
||||
<button className="btn btn-info btn-share"
|
||||
ref="anchor"
|
||||
onClick={this.handleShareButtonClick}>
|
||||
{mozL10n.get("share_button3")}
|
||||
</button>
|
||||
</div>
|
||||
<SocialShareDropdown dispatcher={this.props.dispatcher}
|
||||
roomUrl={this.props.roomData.roomUrl}
|
||||
show={this.state.showMenu}
|
||||
ref="menu"/>
|
||||
</div>
|
||||
<DesktopRoomContextView
|
||||
roomData={this.props.roomData}
|
||||
show={this.props.showContext} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var DesktopRoomContextView = React.createClass({
|
||||
propTypes: {
|
||||
// This data is supplied by the activeRoomStore.
|
||||
roomData: React.PropTypes.object.isRequired,
|
||||
show: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
// When the 'show' prop is changed from outside this component, we do need
|
||||
// to update the state.
|
||||
if (("show" in nextProps) && nextProps.show !== this.props.show) {
|
||||
this.setState({ show: nextProps.show });
|
||||
}
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return { show: this.props.show };
|
||||
},
|
||||
|
||||
handleCloseClick: function() {
|
||||
this.setState({ show: false });
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (!this.state.show)
|
||||
return null;
|
||||
|
||||
var URL = this.props.roomData.roomContextUrls && this.props.roomData.roomContextUrls[0];
|
||||
var thumbnail = URL && URL.thumbnail || "";
|
||||
var URLDescription = URL && URL.description || "";
|
||||
var location = URL && URL.location || "";
|
||||
return (
|
||||
<div className="room-context">
|
||||
<img className="room-context-thumbnail" src={thumbnail}/>
|
||||
<div className="room-context-content">
|
||||
<div className="room-context-label">{mozL10n.get("context_inroom_label")}</div>
|
||||
<div className="room-context-description">{URLDescription}</div>
|
||||
<a className="room-context-url" href={location} target="_blank">{location}</a>
|
||||
{this.props.roomData.roomDescription ?
|
||||
<div className="room-context-comment">{this.props.roomData.roomDescription}</div> :
|
||||
null}
|
||||
<button className="room-context-btn-close"
|
||||
onClick={this.handleCloseClick}/>
|
||||
</div>
|
||||
<SocialShareDropdown dispatcher={this.props.dispatcher}
|
||||
roomStore={this.props.roomStore}
|
||||
show={this.state.showMenu}
|
||||
ref="menu"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -285,18 +340,8 @@ loop.roomViews = (function(mozL10n) {
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
},
|
||||
|
||||
_renderInvitationOverlay: function() {
|
||||
if (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS) {
|
||||
return (
|
||||
<DesktopRoomInvitationView
|
||||
roomStore={this.props.roomStore}
|
||||
dispatcher={this.props.dispatcher} />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
mozLoop: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
componentWillUpdate: function(nextProps, nextState) {
|
||||
@ -341,6 +386,17 @@ loop.roomViews = (function(mozL10n) {
|
||||
}));
|
||||
},
|
||||
|
||||
_shouldRenderInvitationOverlay: function() {
|
||||
return (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS);
|
||||
},
|
||||
|
||||
_shouldRenderContextView: function() {
|
||||
return !!(
|
||||
this.props.mozLoop.getLoopPref("contextInConverations.enabled") &&
|
||||
(this.state.roomContextUrls || this.state.roomDescription)
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.roomName) {
|
||||
this.setTitle(this.state.roomName);
|
||||
@ -358,6 +414,10 @@ loop.roomViews = (function(mozL10n) {
|
||||
visible: true
|
||||
};
|
||||
|
||||
var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
|
||||
var shouldRenderContextView = this._shouldRenderContextView();
|
||||
var roomData = this.props.roomStore.getStoreState("activeRoom");
|
||||
|
||||
switch(this.state.roomState) {
|
||||
case ROOM_STATES.FAILED:
|
||||
case ROOM_STATES.FULL: {
|
||||
@ -378,7 +438,12 @@ loop.roomViews = (function(mozL10n) {
|
||||
default: {
|
||||
return (
|
||||
<div className="room-conversation-wrapper">
|
||||
{this._renderInvitationOverlay()}
|
||||
<DesktopRoomInvitationView
|
||||
dispatcher={this.props.dispatcher}
|
||||
error={this.state.error}
|
||||
roomData={roomData}
|
||||
show={shouldRenderInvitationOverlay}
|
||||
showContext={shouldRenderContextView} />
|
||||
<div className="video-layout-wrapper">
|
||||
<div className="conversation room-conversation">
|
||||
<div className="media nested">
|
||||
@ -397,6 +462,9 @@ loop.roomViews = (function(mozL10n) {
|
||||
screenShare={screenShareData} />
|
||||
</div>
|
||||
</div>
|
||||
<DesktopRoomContextView
|
||||
roomData={roomData}
|
||||
show={!shouldRenderInvitationOverlay && shouldRenderContextView} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -407,6 +475,7 @@ loop.roomViews = (function(mozL10n) {
|
||||
return {
|
||||
ActiveRoomStoreMixin: ActiveRoomStoreMixin,
|
||||
SocialShareDropdown: SocialShareDropdown,
|
||||
DesktopRoomContextView: DesktopRoomContextView,
|
||||
DesktopRoomConversationView: DesktopRoomConversationView,
|
||||
DesktopRoomInvitationView: DesktopRoomInvitationView
|
||||
};
|
||||
|
@ -853,12 +853,25 @@ html, .fx-embedded, #main,
|
||||
background: rgba(0,0,0,.6);
|
||||
/* This matches .fx-embedded .conversation toolbar height */
|
||||
top: 26px;
|
||||
height: calc(100% - 26px);
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
z-index: 1010;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.room-invitation-content {
|
||||
order: 1;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.room-invitation-overlay .error-display-area.error,
|
||||
@ -883,8 +896,8 @@ html, .fx-embedded, #main,
|
||||
color: #d74345;
|
||||
}
|
||||
|
||||
.room-invitation-overlay form {
|
||||
padding: 6em 0 2em 0;
|
||||
.room-invitation-overlay .btn-group {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.room-invitation-overlay textarea {
|
||||
@ -900,11 +913,6 @@ html, .fx-embedded, #main,
|
||||
border-radius: .5em;
|
||||
}
|
||||
|
||||
.room-invitation-overlay .btn-group {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.share-service-dropdown {
|
||||
color: #000;
|
||||
text-align: start;
|
||||
@ -962,6 +970,112 @@ body[dir=rtl] .share-service-dropdown .share-panel-header {
|
||||
background-image: url("../img/icons-16x16.svg#add-active");
|
||||
}
|
||||
|
||||
.room-context {
|
||||
background: rgba(0,0,0,.6);
|
||||
border-top: 2px solid #444;
|
||||
border-bottom: 2px solid #444;
|
||||
padding: .5rem;
|
||||
max-height: 120px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
font-size: .9em;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.room-invitation-overlay .room-context {
|
||||
position: relative;
|
||||
left: auto;
|
||||
bottom: auto;
|
||||
order: 2;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.room-context-thumbnail {
|
||||
width: 100px;
|
||||
max-height: 200px;
|
||||
-moz-margin-end: 1ch;
|
||||
margin-bottom: 1em;
|
||||
order: 1;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
body[dir=rtl] .room-context-thumbnail {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.room-context-thumbnail[src=""] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.room-context-content {
|
||||
order: 2;
|
||||
flex: 1 1 auto;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
body[dir=rtl] .room-context-content {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.room-context-label {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.room-context-label,
|
||||
.room-context-description {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.room-context-comment {
|
||||
color: #707070;
|
||||
}
|
||||
|
||||
.room-context-description,
|
||||
.room-context-comment {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.room-context-url {
|
||||
color: #59A1D7;
|
||||
font-style: italic;
|
||||
text-decoration: none;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.room-context-url:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.room-context-btn-close {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: transparent;
|
||||
background-image: url("../img/icons-10x10.svg#close");
|
||||
background-size: 8px 8px;
|
||||
background-repeat: no-repeat;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.room-context-btn-close:hover,
|
||||
.room-context-btn-close:hover:active {
|
||||
background-image: url("../img/icons-10x10.svg#close-active");
|
||||
}
|
||||
|
||||
body[dir=rtl] .room-context-btn-close {
|
||||
right: auto;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
/* Standalone rooms */
|
||||
|
||||
.standalone .room-conversation-wrapper {
|
||||
|
@ -401,6 +401,8 @@ loop.shared.actions = (function() {
|
||||
* @see https://wiki.mozilla.org/Loop/Architecture/Rooms#GET_.2Frooms.2F.7Btoken.7D
|
||||
*/
|
||||
SetupRoomInfo: Action.define("setupRoomInfo", {
|
||||
// roomContextUrls: Array - Optional.
|
||||
// roomDescription: String - Optional.
|
||||
// roomName: String - Optional.
|
||||
roomOwner: String,
|
||||
roomToken: String,
|
||||
@ -416,7 +418,7 @@ loop.shared.actions = (function() {
|
||||
* @see https://wiki.mozilla.org/Loop/Architecture/Rooms#GET_.2Frooms.2F.7Btoken.7D
|
||||
*/
|
||||
UpdateRoomInfo: Action.define("updateRoomInfo", {
|
||||
// context: Object - Optional.
|
||||
// description: String - Optional.
|
||||
// roomName: String - Optional.
|
||||
roomOwner: String,
|
||||
roomUrl: String
|
||||
|
@ -23,6 +23,13 @@ loop.store.ActiveRoomStore = (function() {
|
||||
|
||||
var ROOM_INFO_FAILURES = loop.shared.utils.ROOM_INFO_FAILURES;
|
||||
|
||||
var OPTIONAL_ROOMINFO_FIELDS = {
|
||||
urls: "roomContextUrls",
|
||||
description: "roomDescription",
|
||||
roomInfoFailure: "roomInfoFailure",
|
||||
roomName: "roomName"
|
||||
};
|
||||
|
||||
/**
|
||||
* Active room store.
|
||||
*
|
||||
@ -84,6 +91,8 @@ loop.store.ActiveRoomStore = (function() {
|
||||
roomContextUrls: null,
|
||||
// The roomCryptoKey to decode the context data if necessary.
|
||||
roomCryptoKey: null,
|
||||
// The description for a room as stored in the context data.
|
||||
roomDescription: null,
|
||||
// Room information failed to be obtained for a reason. See ROOM_INFO_FAILURES.
|
||||
roomInfoFailure: null,
|
||||
// The name of the room.
|
||||
@ -185,6 +194,8 @@ loop.store.ActiveRoomStore = (function() {
|
||||
|
||||
this.dispatchAction(new sharedActions.SetupRoomInfo({
|
||||
roomToken: actionData.roomToken,
|
||||
roomContextUrls: roomData.decryptedContext.urls,
|
||||
roomDescription: roomData.decryptedContext.description,
|
||||
roomName: roomData.decryptedContext.roomName,
|
||||
roomOwner: roomData.roomOwner,
|
||||
roomUrl: roomData.roomUrl,
|
||||
@ -275,6 +286,7 @@ loop.store.ActiveRoomStore = (function() {
|
||||
.then(function(decryptedResult) {
|
||||
var realResult = JSON.parse(decryptedResult);
|
||||
|
||||
roomInfoData.description = realResult.description;
|
||||
roomInfoData.urls = realResult.urls;
|
||||
roomInfoData.roomName = realResult.roomName;
|
||||
|
||||
@ -299,6 +311,8 @@ loop.store.ActiveRoomStore = (function() {
|
||||
}
|
||||
|
||||
this.setStoreState({
|
||||
roomContextUrls: actionData.roomContextUrls,
|
||||
roomDescription: actionData.roomDescription,
|
||||
roomName: actionData.roomName,
|
||||
roomOwner: actionData.roomOwner,
|
||||
roomState: ROOM_STATES.READY,
|
||||
@ -324,13 +338,18 @@ loop.store.ActiveRoomStore = (function() {
|
||||
* @param {sharedActions.UpdateRoomInfo} actionData
|
||||
*/
|
||||
updateRoomInfo: function(actionData) {
|
||||
this.setStoreState({
|
||||
roomContextUrls: actionData.urls,
|
||||
roomInfoFailure: actionData.roomInfoFailure,
|
||||
roomName: actionData.roomName,
|
||||
var newState = {
|
||||
roomOwner: actionData.roomOwner,
|
||||
roomUrl: actionData.roomUrl
|
||||
};
|
||||
// Iterate over the optional fields that _may_ be present on the actionData
|
||||
// object.
|
||||
Object.keys(OPTIONAL_ROOMINFO_FIELDS).forEach(function(field) {
|
||||
if (actionData[field]) {
|
||||
newState[OPTIONAL_ROOMINFO_FIELDS[field]] = actionData[field];
|
||||
}
|
||||
});
|
||||
this.setStoreState(newState);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -66,11 +66,9 @@ loop.shared.views = (function(_, l10n) {
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
/* jshint ignore:start */
|
||||
React.createElement("button", {className: this._getClasses(),
|
||||
title: this._getTitle(),
|
||||
onClick: this.handleClick})
|
||||
/* jshint ignore:end */
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -451,7 +449,6 @@ loop.shared.views = (function(_, l10n) {
|
||||
"local-stream": true,
|
||||
"local-stream-audio": !this.state.video.enabled
|
||||
});
|
||||
/* jshint ignore:start */
|
||||
return (
|
||||
React.createElement("div", {className: "video-layout-wrapper"},
|
||||
React.createElement("div", {className: "conversation in-call"},
|
||||
@ -468,7 +465,6 @@ loop.shared.views = (function(_, l10n) {
|
||||
)
|
||||
)
|
||||
);
|
||||
/* jshint ignore:end */
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -66,11 +66,9 @@ loop.shared.views = (function(_, l10n) {
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
/* jshint ignore:start */
|
||||
<button className={this._getClasses()}
|
||||
title={this._getTitle()}
|
||||
onClick={this.handleClick}></button>
|
||||
/* jshint ignore:end */
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -451,7 +449,6 @@ loop.shared.views = (function(_, l10n) {
|
||||
"local-stream": true,
|
||||
"local-stream-audio": !this.state.video.enabled
|
||||
});
|
||||
/* jshint ignore:start */
|
||||
return (
|
||||
<div className="video-layout-wrapper">
|
||||
<div className="conversation in-call">
|
||||
@ -468,7 +465,6 @@ loop.shared.views = (function(_, l10n) {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
/* jshint ignore:end */
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -137,7 +137,8 @@ describe("loop.conversation", function() {
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(loop.conversation.AppControllerView, {
|
||||
roomStore: roomStore,
|
||||
dispatcher: dispatcher
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: navigator.mozLoop
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,8 @@ describe("loop.roomViews", function () {
|
||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||
var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
|
||||
|
||||
var sandbox, dispatcher, roomStore, activeRoomStore, fakeWindow;
|
||||
var fakeMozLoop;
|
||||
var sandbox, dispatcher, roomStore, activeRoomStore, fakeWindow,
|
||||
fakeMozLoop, fakeContextURL;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@ -48,6 +48,12 @@ describe("loop.roomViews", function () {
|
||||
mozLoop: {},
|
||||
activeRoomStore: activeRoomStore
|
||||
});
|
||||
|
||||
fakeContextURL = {
|
||||
description: "An invalid page",
|
||||
location: "http://invalid.com",
|
||||
thumbnail: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@ -103,22 +109,24 @@ describe("loop.roomViews", function () {
|
||||
view = null;
|
||||
});
|
||||
|
||||
function mountTestComponent() {
|
||||
function mountTestComponent(props) {
|
||||
props = _.extend({
|
||||
dispatcher: dispatcher,
|
||||
roomData: {},
|
||||
show: true,
|
||||
showContext: false
|
||||
}, props);
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(
|
||||
loop.roomViews.DesktopRoomInvitationView, {
|
||||
dispatcher: dispatcher,
|
||||
roomStore: roomStore
|
||||
}));
|
||||
React.createElement(loop.roomViews.DesktopRoomInvitationView, props));
|
||||
}
|
||||
|
||||
it("should dispatch an EmailRoomUrl action when the email button is " +
|
||||
"pressed", function() {
|
||||
view = mountTestComponent();
|
||||
it("should dispatch an EmailRoomUrl action when the email button is pressed",
|
||||
function() {
|
||||
view = mountTestComponent({
|
||||
roomData: { roomUrl: "http://invalid" }
|
||||
});
|
||||
|
||||
view.setState({roomUrl: "http://invalid"});
|
||||
|
||||
var emailBtn = view.getDOMNode().querySelector('.btn-email');
|
||||
var emailBtn = view.getDOMNode().querySelector(".btn-email");
|
||||
|
||||
React.addons.TestUtils.Simulate.click(emailBtn);
|
||||
|
||||
@ -131,13 +139,14 @@ describe("loop.roomViews", function () {
|
||||
var roomNameBox;
|
||||
|
||||
beforeEach(function() {
|
||||
view = mountTestComponent();
|
||||
view.setState({
|
||||
roomToken: "fakeToken",
|
||||
roomName: "fakeName"
|
||||
view = mountTestComponent({
|
||||
roomData: {
|
||||
roomToken: "fakeToken",
|
||||
roomName: "fakeName"
|
||||
}
|
||||
});
|
||||
|
||||
roomNameBox = view.getDOMNode().querySelector('.input-room-name');
|
||||
roomNameBox = view.getDOMNode().querySelector(".input-room-name");
|
||||
});
|
||||
|
||||
it("should dispatch a RenameRoom action when the focus is lost",
|
||||
@ -175,14 +184,14 @@ describe("loop.roomViews", function () {
|
||||
|
||||
describe("Copy Button", function() {
|
||||
beforeEach(function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
view.setState({roomUrl: "http://invalid"});
|
||||
view = mountTestComponent({
|
||||
roomData: { roomUrl: "http://invalid" }
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch a CopyRoomUrl action when the copy button is " +
|
||||
"pressed", function() {
|
||||
var copyBtn = view.getDOMNode().querySelector('.btn-copy');
|
||||
var copyBtn = view.getDOMNode().querySelector(".btn-copy");
|
||||
|
||||
React.addons.TestUtils.Simulate.click(copyBtn);
|
||||
|
||||
@ -192,7 +201,7 @@ describe("loop.roomViews", function () {
|
||||
});
|
||||
|
||||
it("should change the text when the url has been copied", function() {
|
||||
var copyBtn = view.getDOMNode().querySelector('.btn-copy');
|
||||
var copyBtn = view.getDOMNode().querySelector(".btn-copy");
|
||||
|
||||
React.addons.TestUtils.Simulate.click(copyBtn);
|
||||
|
||||
@ -215,6 +224,25 @@ describe("loop.roomViews", function () {
|
||||
expect(view.refs.menu.props.show).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Context", function() {
|
||||
it("should not render the context data when told not to", function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
expect(view.getDOMNode().querySelector(".room-context")).to.eql(null);
|
||||
});
|
||||
|
||||
it("should render context when data is available", function() {
|
||||
view = mountTestComponent({
|
||||
showContext: true,
|
||||
roomData: {
|
||||
roomContextUrls: [fakeContextURL]
|
||||
}
|
||||
});
|
||||
|
||||
expect(view.getDOMNode().querySelector(".room-context")).to.not.eql(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("DesktopRoomConversationView", function() {
|
||||
@ -468,14 +496,13 @@ describe("loop.roomViews", function () {
|
||||
view = fakeProvider = null;
|
||||
});
|
||||
|
||||
function mountTestComponent() {
|
||||
function mountTestComponent(props) {
|
||||
props = _.extend({
|
||||
dispatcher: dispatcher,
|
||||
show: true
|
||||
}, props);
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(loop.roomViews.SocialShareDropdown, {
|
||||
dispatcher: dispatcher,
|
||||
roomStore: roomStore,
|
||||
show: true
|
||||
})
|
||||
);
|
||||
React.createElement(loop.roomViews.SocialShareDropdown, props));
|
||||
}
|
||||
|
||||
describe("#render", function() {
|
||||
@ -486,36 +513,31 @@ describe("loop.roomViews", function () {
|
||||
});
|
||||
|
||||
it("should show different contents when the Share XUL button is not available", function() {
|
||||
activeRoomStore.setStoreState({
|
||||
view = mountTestComponent({
|
||||
socialShareProviders: []
|
||||
});
|
||||
view = mountTestComponent();
|
||||
|
||||
var node = view.getDOMNode();
|
||||
expect(node.querySelector(".share-panel-header")).to.not.eql(null);
|
||||
});
|
||||
|
||||
it("should show an empty list when no Social Providers are available", function() {
|
||||
activeRoomStore.setStoreState({
|
||||
view = mountTestComponent({
|
||||
socialShareButtonAvailable: true,
|
||||
socialShareProviders: []
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
var node = view.getDOMNode();
|
||||
expect(node.querySelector(".icon-add-share-service")).to.not.eql(null);
|
||||
expect(node.querySelectorAll(".dropdown-menu-item").length).to.eql(1);
|
||||
});
|
||||
|
||||
it("should show a list of available Social Providers", function() {
|
||||
activeRoomStore.setStoreState({
|
||||
view = mountTestComponent({
|
||||
socialShareButtonAvailable: true,
|
||||
socialShareProviders: [fakeProvider]
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
var node = view.getDOMNode();
|
||||
expect(node.querySelector(".icon-add-share-service")).to.not.eql(null);
|
||||
expect(node.querySelector(".dropdown-menu-separator")).to.not.eql(null);
|
||||
@ -530,12 +552,10 @@ describe("loop.roomViews", function () {
|
||||
|
||||
describe("#handleToolbarAddButtonClick", function() {
|
||||
it("should dispatch an action when the 'add to toolbar' button is clicked", function() {
|
||||
activeRoomStore.setStoreState({
|
||||
view = mountTestComponent({
|
||||
socialShareProviders: []
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
var addButton = view.getDOMNode().querySelector(".btn-toolbar-add");
|
||||
React.addons.TestUtils.Simulate.click(addButton);
|
||||
|
||||
@ -547,13 +567,11 @@ describe("loop.roomViews", function () {
|
||||
|
||||
describe("#handleAddServiceClick", function() {
|
||||
it("should dispatch an action when the 'add provider' item is clicked", function() {
|
||||
activeRoomStore.setStoreState({
|
||||
view = mountTestComponent({
|
||||
socialShareProviders: [],
|
||||
socialShareButtonAvailable: true
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
var addItem = view.getDOMNode().querySelector(".dropdown-menu-item:first-child");
|
||||
React.addons.TestUtils.Simulate.click(addItem);
|
||||
|
||||
@ -565,14 +583,12 @@ describe("loop.roomViews", function () {
|
||||
|
||||
describe("#handleProviderClick", function() {
|
||||
it("should dispatch an action when a provider item is clicked", function() {
|
||||
activeRoomStore.setStoreState({
|
||||
view = mountTestComponent({
|
||||
roomUrl: "http://example.com",
|
||||
socialShareButtonAvailable: true,
|
||||
socialShareProviders: [fakeProvider]
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
var providerItem = view.getDOMNode().querySelector(".dropdown-menu-item:last-child");
|
||||
React.addons.TestUtils.Simulate.click(providerItem);
|
||||
|
||||
@ -580,10 +596,73 @@ describe("loop.roomViews", function () {
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.ShareRoomUrl({
|
||||
provider: fakeProvider,
|
||||
roomUrl: activeRoomStore.getStoreState().roomUrl,
|
||||
roomUrl: "http://example.com",
|
||||
previews: []
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("DesktopRoomContextView", function() {
|
||||
var view;
|
||||
|
||||
afterEach(function() {
|
||||
view = null;
|
||||
});
|
||||
|
||||
function mountTestComponent(props) {
|
||||
props = _.extend({
|
||||
show: true
|
||||
}, props);
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(loop.roomViews.DesktopRoomContextView, props));
|
||||
}
|
||||
|
||||
describe("#render", function() {
|
||||
it("should show the context information properly when available", function() {
|
||||
view = mountTestComponent({
|
||||
roomData: {
|
||||
roomDescription: "Hello, is it me you're looking for?",
|
||||
roomContextUrls: [fakeContextURL]
|
||||
}
|
||||
});
|
||||
|
||||
var node = view.getDOMNode();
|
||||
expect(node).to.not.eql(null);
|
||||
expect(node.querySelector(".room-context-thumbnail").src).to.
|
||||
eql(fakeContextURL.thumbnail);
|
||||
expect(node.querySelector(".room-context-description").textContent).to.
|
||||
eql(fakeContextURL.description);
|
||||
expect(node.querySelector(".room-context-comment").textContent).to.
|
||||
eql(view.props.roomData.roomDescription);
|
||||
});
|
||||
|
||||
it("should not render optional data", function() {
|
||||
view = mountTestComponent({
|
||||
roomData: { roomContextUrls: [fakeContextURL] }
|
||||
});
|
||||
|
||||
expect(view.getDOMNode().querySelector(".room-context-comment")).to.
|
||||
eql(null);
|
||||
});
|
||||
|
||||
it("should not render the component when 'show' is false", function() {
|
||||
view = mountTestComponent({
|
||||
show: false
|
||||
});
|
||||
|
||||
expect(view.getDOMNode()).to.eql(null);
|
||||
});
|
||||
|
||||
it("should close the view when the close button is clicked", function() {
|
||||
view = mountTestComponent({
|
||||
roomData: { roomContextUrls: [fakeContextURL] }
|
||||
});
|
||||
|
||||
var closeBtn = view.getDOMNode().querySelector(".room-context-btn-close");
|
||||
React.addons.TestUtils.Simulate.click(closeBtn);
|
||||
expect(view.getDOMNode()).to.eql(null);
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
@ -282,6 +282,8 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
sinon.assert.calledTwice(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.SetupRoomInfo({
|
||||
roomContextUrls: undefined,
|
||||
roomDescription: undefined,
|
||||
roomToken: fakeToken,
|
||||
roomName: fakeRoomData.decryptedContext.roomName,
|
||||
roomOwner: fakeRoomData.roomOwner,
|
||||
@ -461,6 +463,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
fetchServerAction.cryptoKey = "fakeKey";
|
||||
|
||||
var roomContext = {
|
||||
description: "Never gonna let you down. Never gonna give you up...",
|
||||
roomName: "The wonderful Loopy room",
|
||||
urls: [{
|
||||
description: "An invalid page",
|
||||
|
@ -747,7 +747,9 @@ BrowserGlue.prototype = {
|
||||
|
||||
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
AddonWatcher.init(this._notifySlowAddon);
|
||||
#endif
|
||||
},
|
||||
|
||||
_checkForOldBuildUpdates: function () {
|
||||
@ -1006,7 +1008,9 @@ BrowserGlue.prototype = {
|
||||
#endif
|
||||
webrtcUI.uninit();
|
||||
FormValidationHandler.uninit();
|
||||
#ifdef NIGHTLY_BUILD
|
||||
AddonWatcher.uninit();
|
||||
#endif
|
||||
},
|
||||
|
||||
_initServiceDiscovery: function () {
|
||||
|
@ -22,6 +22,7 @@ support-files =
|
||||
doc_inspector_menu.html
|
||||
doc_inspector_remove-iframe-during-load.html
|
||||
doc_inspector_search.html
|
||||
doc_inspector_search-reserved.html
|
||||
doc_inspector_search-suggestions.html
|
||||
doc_inspector_select-last-selected-01.html
|
||||
doc_inspector_select-last-selected-02.html
|
||||
@ -95,6 +96,7 @@ skip-if = e10s # Test synthesize scrolling events in content. Also, see bug 1035
|
||||
[browser_inspector_search-03.js]
|
||||
[browser_inspector_search-04.js]
|
||||
[browser_inspector_search-05.js]
|
||||
[browser_inspector_search-reserved.js]
|
||||
[browser_inspector_select-docshell.js]
|
||||
[browser_inspector_select-last-selected.js]
|
||||
[browser_inspector_search-navigation.js]
|
||||
|
@ -0,0 +1,134 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Testing searching for ids and classes that contain reserved characters.
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_inspector_search-reserved.html";
|
||||
|
||||
// An array of (key, suggestions) pairs where key is a key to press and
|
||||
// suggestions is an array of suggestions that should be shown in the popup.
|
||||
// Suggestion is an object with label of the entry and optional count
|
||||
// (defaults to 1)
|
||||
const TEST_DATA = [
|
||||
{
|
||||
key: "#",
|
||||
suggestions: [{label: "#d1\\.d2", count: 1}]
|
||||
},
|
||||
{
|
||||
key: "d",
|
||||
suggestions: [{label: "#d1\\.d2", count: 1}]
|
||||
},
|
||||
{
|
||||
key: "VK_BACK_SPACE",
|
||||
suggestions: [{label: "#d1\\.d2", count: 1}]
|
||||
},
|
||||
{
|
||||
key: "VK_BACK_SPACE",
|
||||
suggestions: []
|
||||
},
|
||||
{
|
||||
key: ".",
|
||||
suggestions: [{label: ".c1\\.c2", count: 1}]
|
||||
},
|
||||
{
|
||||
key: "c",
|
||||
suggestions: [{label: ".c1\\.c2", count: 1}]
|
||||
},
|
||||
{
|
||||
key: "VK_BACK_SPACE",
|
||||
suggestions: [{label: ".c1\\.c2", count: 1}]
|
||||
},
|
||||
{
|
||||
key: "VK_BACK_SPACE",
|
||||
suggestions: []
|
||||
},
|
||||
{
|
||||
key: "d",
|
||||
suggestions: [{label: "div", count: 2},
|
||||
{label: "#d1\\.d2", count: 1}]
|
||||
},
|
||||
{
|
||||
key: "VK_BACK_SPACE",
|
||||
suggestions: []
|
||||
},
|
||||
{
|
||||
key:"c",
|
||||
suggestions: [{label: ".c1\\.c2", count: 1}]
|
||||
},
|
||||
{
|
||||
key: "VK_BACK_SPACE",
|
||||
suggestions: []
|
||||
},
|
||||
{
|
||||
key: "b",
|
||||
suggestions: [{label: "body", count: 1}]
|
||||
},
|
||||
{
|
||||
key: "o",
|
||||
suggestions: [{label: "body", count: 1}]
|
||||
},
|
||||
{
|
||||
key: "d",
|
||||
suggestions: [{label: "body", count: 1}]
|
||||
},
|
||||
{
|
||||
key: "y",
|
||||
suggestions: []
|
||||
},
|
||||
{
|
||||
key: " ",
|
||||
suggestions: [{label: "body div", count: 2}]
|
||||
},
|
||||
{
|
||||
key: ".",
|
||||
suggestions: [{label: "body .c1\\.c2", count: 1}]
|
||||
},
|
||||
{
|
||||
key: "VK_BACK_SPACE",
|
||||
suggestions: [{label: "body div", count: 2}]
|
||||
},
|
||||
{
|
||||
key: "#",
|
||||
suggestions: [{label: "body #", count: 1},
|
||||
{label: "body #d1\\.d2", count: 1}]
|
||||
}
|
||||
];
|
||||
|
||||
add_task(function* () {
|
||||
let { inspector } = yield openInspectorForURL(TEST_URL);
|
||||
let searchBox = inspector.searchBox;
|
||||
let popup = inspector.searchSuggestions.searchPopup;
|
||||
|
||||
yield focusSearchBoxUsingShortcut(inspector.panelWin);
|
||||
|
||||
for (let { key, suggestions } of TEST_DATA) {
|
||||
info("Pressing " + key + " to get " + formatSuggestions(suggestions));
|
||||
|
||||
let command = once(searchBox, "command");
|
||||
EventUtils.synthesizeKey(key, {}, inspector.panelWin);
|
||||
yield command;
|
||||
|
||||
info("Waiting for search query to complete");
|
||||
yield inspector.searchSuggestions._lastQuery;
|
||||
|
||||
info("Query completed. Performing checks for input '" + searchBox.value + "'");
|
||||
let actualSuggestions = popup.getItems().reverse();
|
||||
|
||||
is(popup.isOpen ? actualSuggestions.length: 0, suggestions.length,
|
||||
"There are expected number of suggestions.");
|
||||
|
||||
for (let i = 0; i < suggestions.length; i++) {
|
||||
is(suggestions[i].label, actualSuggestions[i].label,
|
||||
"The suggestion at " + i + "th index is correct.");
|
||||
is(suggestions[i].count || 1, actualSuggestions[i].count,
|
||||
"The count for suggestion at " + i + "th index is correct.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function formatSuggestions(suggestions) {
|
||||
return "[" + suggestions
|
||||
.map(s => "'" + s.label + "' (" + s.count || 1 + ")")
|
||||
.join(", ") + "]";
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Inspector Search Box Reserved Character Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="d1.d2">Hi, I'm an id that contains a CSS reserved character</div>
|
||||
<div class="c1.c2">Hi, a class that contains a CSS reserved character</div>
|
||||
</body>
|
||||
</html>
|
@ -1,7 +1,6 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const COLOR_UNIT_PREF = "devtools.defaultColorUnit";
|
||||
const TEST_URI = "data:text/html;charset=utf-8,browser_css_color.js";
|
||||
let {colorUtils} = devtools.require("devtools/css-color");
|
||||
let origColorUnit;
|
||||
@ -9,7 +8,6 @@ let origColorUnit;
|
||||
add_task(function*() {
|
||||
yield promiseTab("about:blank");
|
||||
let [host, win, doc] = yield createHost("bottom", TEST_URI);
|
||||
origColorUnit = Services.prefs.getCharPref(COLOR_UNIT_PREF);
|
||||
|
||||
info("Creating a test canvas element to test colors");
|
||||
let canvas = createTestCanvas(doc);
|
||||
@ -49,23 +47,19 @@ function testColorUtils(canvas) {
|
||||
}
|
||||
|
||||
function testToString(color, name, hex, hsl, rgb) {
|
||||
switchColorUnit(colorUtils.CssColor.COLORUNIT.name);
|
||||
color.colorUnit = colorUtils.CssColor.COLORUNIT.name;
|
||||
is(color.toString(), name, "toString() with authored type");
|
||||
|
||||
switchColorUnit(colorUtils.CssColor.COLORUNIT.hex);
|
||||
color.colorUnit = colorUtils.CssColor.COLORUNIT.hex;
|
||||
is(color.toString(), hex, "toString() with hex type");
|
||||
|
||||
switchColorUnit(colorUtils.CssColor.COLORUNIT.hsl);
|
||||
color.colorUnit = colorUtils.CssColor.COLORUNIT.hsl;
|
||||
is(color.toString(), hsl, "toString() with hsl type");
|
||||
|
||||
switchColorUnit(colorUtils.CssColor.COLORUNIT.rgb);
|
||||
color.colorUnit = colorUtils.CssColor.COLORUNIT.rgb;
|
||||
is(color.toString(), rgb, "toString() with rgb type");
|
||||
}
|
||||
|
||||
function switchColorUnit(unit) {
|
||||
Services.prefs.setCharPref(COLOR_UNIT_PREF, unit);
|
||||
}
|
||||
|
||||
function testColorMatch(name, hex, hsl, rgb, rgba, canvas) {
|
||||
let target;
|
||||
let ctx = canvas.getContext("2d");
|
||||
@ -110,7 +104,6 @@ function testColorMatch(name, hex, hsl, rgb, rgba, canvas) {
|
||||
test(hex, "hex");
|
||||
test(hsl, "hsl");
|
||||
test(rgb, "rgb");
|
||||
switchColorUnit(origColorUnit);
|
||||
}
|
||||
|
||||
function testProcessCSSString() {
|
||||
|
@ -28,7 +28,7 @@ function test() {
|
||||
expectUncaughtException();
|
||||
|
||||
if (!DeveloperToolbar.visible) {
|
||||
DeveloperToolbar.show(true, onOpenToolbar);
|
||||
DeveloperToolbar.show(true).then(onOpenToolbar);
|
||||
}
|
||||
else {
|
||||
onOpenToolbar();
|
||||
|
@ -44,6 +44,9 @@ function promiseTab(aURL) {
|
||||
}
|
||||
|
||||
registerCleanupFunction(function tearDown() {
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
yield gDevTools.closeToolbox(target);
|
||||
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
gBrowser.removeCurrentTab();
|
||||
}
|
||||
|
@ -1020,6 +1020,11 @@ SwatchBasedEditorTooltip.prototype = {
|
||||
|
||||
_onSwatchClick: function(event) {
|
||||
let swatch = this.swatches.get(event.target);
|
||||
|
||||
if (event.shiftKey) {
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
if (swatch) {
|
||||
this.activeSwatch = event.target;
|
||||
this.show();
|
||||
|
@ -376,7 +376,7 @@ StyleSheetEditor.prototype = {
|
||||
lineNumbers: true,
|
||||
mode: Editor.modes.css,
|
||||
readOnly: false,
|
||||
autoCloseBrackets: "{}()[]",
|
||||
autoCloseBrackets: "{}()",
|
||||
extraKeys: this._getKeyBindings(),
|
||||
contextMenu: "sourceEditorContextMenu",
|
||||
autocomplete: Services.prefs.getBoolPref(AUTOCOMPLETION_PREF),
|
||||
|
@ -48,6 +48,7 @@ support-files =
|
||||
doc_xulpage.xul
|
||||
|
||||
[browser_styleeditor_autocomplete.js]
|
||||
[browser_styleeditor_autocomplete-disabled.js]
|
||||
[browser_styleeditor_bug_740541_iframes.js]
|
||||
skip-if = os == "linux" || "mac" # bug 949355
|
||||
[browser_styleeditor_bug_851132_middle_click.js]
|
||||
|
@ -0,0 +1,26 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that autocomplete can be disabled.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "autocomplete.html";
|
||||
|
||||
// Pref which decides if CSS autocompletion is enabled in Style Editor or not.
|
||||
const AUTOCOMPLETION_PREF = "devtools.styleeditor.autocompletion-enabled";
|
||||
|
||||
add_task(function* () {
|
||||
Services.prefs.setBoolPref(AUTOCOMPLETION_PREF, false);
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
let editor = yield ui.editors[0].getSourceEditor();
|
||||
|
||||
is(editor.sourceEditor.getOption("autocomplete"), false,
|
||||
"Autocompletion option does not exist");
|
||||
ok(!editor.sourceEditor.getAutocompletionPopup(),
|
||||
"Autocompletion popup does not exist");
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(AUTOCOMPLETION_PREF);
|
||||
});
|
@ -1,20 +1,13 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
// Test that autocompletion works as expected.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "autocomplete.html";
|
||||
const MAX_SUGGESTIONS = 15;
|
||||
|
||||
// Pref which decides if CSS autocompletion is enabled in Style Editor or not.
|
||||
const AUTOCOMPLETION_PREF = "devtools.styleeditor.autocompletion-enabled";
|
||||
|
||||
const {CSSProperties, CSSValues} = getCSSKeywords();
|
||||
|
||||
// Test cases to test that autocompletion works correctly when enabled.
|
||||
@ -87,35 +80,21 @@ let TEST_CASES = [
|
||||
['Ctrl+Space', {total: 1, current: 0}],
|
||||
];
|
||||
|
||||
let gEditor;
|
||||
let gPopup;
|
||||
let index = 0;
|
||||
add_task(function* () {
|
||||
let { panel, ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
let editor = yield ui.editors[0].getSourceEditor();
|
||||
let sourceEditor = editor.sourceEditor;
|
||||
let popup = sourceEditor.getAutocompletionPopup();
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
yield SimpleTest.promiseFocus(panel.panelWindow);
|
||||
|
||||
addTabAndOpenStyleEditors(1, testEditorAdded);
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
|
||||
function testEditorAdded(panel) {
|
||||
info("Editor added, getting the source editor and starting tests");
|
||||
panel.UI.editors[0].getSourceEditor().then(editor => {
|
||||
info("source editor found, starting tests.");
|
||||
gEditor = editor.sourceEditor;
|
||||
gPopup = gEditor.getAutocompletionPopup();
|
||||
waitForFocus(testState, gPanelWindow);
|
||||
});
|
||||
}
|
||||
|
||||
function testState() {
|
||||
if (index == TEST_CASES.length) {
|
||||
testAutocompletionDisabled();
|
||||
return;
|
||||
for (let index in TEST_CASES) {
|
||||
yield testState(index, sourceEditor, popup, panel.panelWindow);
|
||||
yield checkState(index, sourceEditor, popup);
|
||||
}
|
||||
});
|
||||
|
||||
function testState(index, sourceEditor, popup, panelWindow) {
|
||||
let [key, details] = TEST_CASES[index];
|
||||
let entered;
|
||||
if (details) {
|
||||
@ -136,80 +115,54 @@ function testState() {
|
||||
evt = "popup-hidden";
|
||||
}
|
||||
else if (/(left|right|return|home|end)/ig.test(key) ||
|
||||
(key == "VK_DOWN" && !gPopup.isOpen)) {
|
||||
(key == "VK_DOWN" && !popup.isOpen)) {
|
||||
evt = "cursorActivity";
|
||||
}
|
||||
else if (key == "VK_TAB" || key == "VK_UP" || key == "VK_DOWN") {
|
||||
evt = "suggestion-entered";
|
||||
}
|
||||
|
||||
gEditor.once(evt, checkState);
|
||||
EventUtils.synthesizeKey(key, mods, gPanelWindow);
|
||||
let ready = sourceEditor.once(evt);
|
||||
EventUtils.synthesizeKey(key, mods, panelWindow);
|
||||
|
||||
return ready;
|
||||
}
|
||||
|
||||
function checkState() {
|
||||
function checkState(index, sourceEditor, popup) {
|
||||
let deferred = promise.defer();
|
||||
executeSoon(() => {
|
||||
let [key, details] = TEST_CASES[index];
|
||||
details = details || {};
|
||||
let {total, current, inserted} = details;
|
||||
|
||||
if (total != undefined) {
|
||||
ok(gPopup.isOpen, "Popup is open for index " + index);
|
||||
is(total, gPopup.itemCount,
|
||||
ok(popup.isOpen, "Popup is open for index " + index);
|
||||
is(total, popup.itemCount,
|
||||
"Correct total suggestions for index " + index);
|
||||
is(current, gPopup.selectedIndex,
|
||||
is(current, popup.selectedIndex,
|
||||
"Correct index is selected for index " + index);
|
||||
if (inserted) {
|
||||
let { preLabel, label, text } = gPopup.getItemAtIndex(current);
|
||||
let { line, ch } = gEditor.getCursor();
|
||||
let lineText = gEditor.getText(line);
|
||||
let { preLabel, label, text } = popup.getItemAtIndex(current);
|
||||
let { line, ch } = sourceEditor.getCursor();
|
||||
let lineText = sourceEditor.getText(line);
|
||||
is(lineText.substring(ch - text.length, ch), text,
|
||||
"Current suggestion from the popup is inserted into the editor.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
ok(!gPopup.isOpen, "Popup is closed for index " + index);
|
||||
ok(!popup.isOpen, "Popup is closed for index " + index);
|
||||
if (inserted) {
|
||||
let { preLabel, label, text } = gPopup.getItemAtIndex(current);
|
||||
let { line, ch } = gEditor.getCursor();
|
||||
let lineText = gEditor.getText(line);
|
||||
let { preLabel, label, text } = popup.getItemAtIndex(current);
|
||||
let { line, ch } = sourceEditor.getCursor();
|
||||
let lineText = sourceEditor.getText(line);
|
||||
is(lineText.substring(ch - text.length, ch), text,
|
||||
"Current suggestion from the popup is inserted into the editor.");
|
||||
}
|
||||
}
|
||||
index++;
|
||||
testState();
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function testAutocompletionDisabled() {
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
index = 0;
|
||||
info("Starting test to check if autocompletion is disabled correctly.")
|
||||
Services.prefs.setBoolPref(AUTOCOMPLETION_PREF, false);
|
||||
|
||||
addTabAndOpenStyleEditors(1, testEditorAddedDisabled);
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
|
||||
function testEditorAddedDisabled(panel) {
|
||||
info("Editor added, getting the source editor and starting tests");
|
||||
panel.UI.editors[0].getSourceEditor().then(editor => {
|
||||
is(editor.sourceEditor.getOption("autocomplete"), false,
|
||||
"Autocompletion option does not exist");
|
||||
ok(!editor.sourceEditor.getAutocompletionPopup(),
|
||||
"Autocompletion popup does not exist");
|
||||
cleanup();
|
||||
});
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
Services.prefs.clearUserPref(AUTOCOMPLETION_PREF);
|
||||
gEditor = null;
|
||||
gPopup = null;
|
||||
finish();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,10 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
function test()
|
||||
// Test that sheets inside iframes are shown in the editor.
|
||||
|
||||
add_task(function* ()
|
||||
{
|
||||
|
||||
function makeStylesheet(selector) {
|
||||
@ -69,10 +72,8 @@ function test()
|
||||
|
||||
const EXPECTED_STYLE_SHEET_COUNT = 12;
|
||||
|
||||
waitForExplicitFinish();
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
|
||||
// Wait for events until the right number of editors has been opened.
|
||||
addTabAndOpenStyleEditors(EXPECTED_STYLE_SHEET_COUNT, () => finish());
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
is(ui.editors.length, EXPECTED_STYLE_SHEET_COUNT,
|
||||
"Got the expected number of style sheets.");
|
||||
});
|
||||
|
@ -1,75 +1,56 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
// Test that middle click on style sheet doesn't open styleeditor.xul in a new
|
||||
// tab (bug 851132).
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "four.html";
|
||||
|
||||
let gUI;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
addTabAndOpenStyleEditors(4, runTests);
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
|
||||
let timeoutID;
|
||||
|
||||
function runTests(panel) {
|
||||
gUI = panel.UI;
|
||||
|
||||
add_task(function* () {
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
gBrowser.tabContainer.addEventListener("TabOpen", onTabAdded, false);
|
||||
gUI.editors[0].getSourceEditor().then(onEditor0Attach);
|
||||
gUI.editors[1].getSourceEditor().then(onEditor1Attach);
|
||||
}
|
||||
|
||||
function getStylesheetNameLinkFor(aEditor) {
|
||||
return aEditor.summary.querySelector(".stylesheet-name");
|
||||
}
|
||||
|
||||
function onEditor0Attach(aEditor) {
|
||||
yield ui.editors[0].getSourceEditor();
|
||||
info("first editor selected");
|
||||
|
||||
waitForFocus(function () {
|
||||
// left mouse click should focus editor 1
|
||||
EventUtils.synthesizeMouseAtCenter(
|
||||
getStylesheetNameLinkFor(gUI.editors[1]),
|
||||
{button: 0},
|
||||
gPanelWindow);
|
||||
}, gPanelWindow);
|
||||
}
|
||||
info("Left-clicking on the second editor link.");
|
||||
yield clickOnStyleSheetLink(ui.editors[1], 0);
|
||||
|
||||
function onEditor1Attach(aEditor) {
|
||||
info("second editor selected");
|
||||
info("Waiting for the second editor to be selected.");
|
||||
let editor = yield ui.once("editor-selected");
|
||||
|
||||
// Wait for the focus to be set.
|
||||
executeSoon(function () {
|
||||
ok(aEditor.sourceEditor.hasFocus(),
|
||||
"left mouse click has given editor 1 focus");
|
||||
ok(editor.sourceEditor.hasFocus(),
|
||||
"Left mouse click gave second editor focus.");
|
||||
|
||||
// right mouse click should not open a new tab
|
||||
EventUtils.synthesizeMouseAtCenter(
|
||||
getStylesheetNameLinkFor(gUI.editors[2]),
|
||||
{button: 1},
|
||||
gPanelWindow);
|
||||
// middle mouse click should not open a new tab
|
||||
info("Middle clicking on the third editor link.");
|
||||
yield clickOnStyleSheetLink(ui.editors[2], 1);
|
||||
});
|
||||
|
||||
setTimeout(finish, 0);
|
||||
});
|
||||
/**
|
||||
* A helper that clicks on style sheet link in the sidebar.
|
||||
*
|
||||
* @param {StyleSheetEditor} editor
|
||||
* The editor of which link should be clicked.
|
||||
* @param {MouseEvent.button} button
|
||||
* The button to click the link with.
|
||||
*/
|
||||
function* clickOnStyleSheetLink(editor, button) {
|
||||
let window = editor._window;
|
||||
let link = editor.summary.querySelector(".stylesheet-name");
|
||||
|
||||
info("Waiting for focus.");
|
||||
yield SimpleTest.promiseFocus(window);
|
||||
|
||||
info("Pressing button " + button + " on style sheet name link.");
|
||||
EventUtils.synthesizeMouseAtCenter(link, { button }, window);
|
||||
}
|
||||
|
||||
function onTabAdded() {
|
||||
ok(false, "middle mouse click has opened a new tab");
|
||||
finish();
|
||||
}
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
gBrowser.tabContainer.removeEventListener("TabOpen", onTabAdded, false);
|
||||
gUI = null;
|
||||
});
|
||||
|
@ -1,32 +1,17 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that style sheets can be disabled and enabled.
|
||||
|
||||
// https rather than chrome to improve coverage
|
||||
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
add_task(function* () {
|
||||
let { panel, ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
let editor = yield ui.editors[0].getSourceEditor();
|
||||
|
||||
let count = 0;
|
||||
addTabAndOpenStyleEditors(2, function(panel) {
|
||||
// we test against first stylesheet after all are ready
|
||||
let UI = panel.UI;
|
||||
let editor = UI.editors[0];
|
||||
editor.getSourceEditor().then(runTests.bind(this, UI, editor));
|
||||
});
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
|
||||
function runTests(UI, editor)
|
||||
{
|
||||
testEnabledToggle(UI, editor);
|
||||
}
|
||||
|
||||
function testEnabledToggle(UI, editor)
|
||||
{
|
||||
let summary = editor.summary;
|
||||
let enabledToggle = summary.querySelector(".stylesheet-enabled");
|
||||
ok(enabledToggle, "enabled toggle button exists");
|
||||
@ -37,34 +22,34 @@ function testEnabledToggle(UI, editor)
|
||||
is(summary.classList.contains("disabled"), false,
|
||||
"first stylesheet is initially enabled, UI does not have DISABLED class");
|
||||
|
||||
let disabledToggleCount = 0;
|
||||
editor.on("property-change", function(event, property) {
|
||||
if (property != "disabled") {
|
||||
return;
|
||||
}
|
||||
disabledToggleCount++;
|
||||
info("Disabling the first stylesheet.");
|
||||
yield toggleEnabled(editor, enabledToggle, panel.panelWindow);
|
||||
|
||||
if (disabledToggleCount == 1) {
|
||||
is(editor.styleSheet.disabled, true, "first stylesheet is now disabled");
|
||||
is(summary.classList.contains("disabled"), true,
|
||||
"first stylesheet is now disabled, UI has DISABLED class");
|
||||
is(editor.styleSheet.disabled, true, "first stylesheet is now disabled");
|
||||
is(summary.classList.contains("disabled"), true,
|
||||
"first stylesheet is now disabled, UI has DISABLED class");
|
||||
|
||||
// now toggle it back to enabled
|
||||
waitForFocus(function () {
|
||||
EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gPanelWindow);
|
||||
}, gPanelWindow);
|
||||
return;
|
||||
}
|
||||
info("Enabling the first stylesheet again.");
|
||||
yield toggleEnabled(editor, enabledToggle, panel.panelWindow);
|
||||
|
||||
// disabledToggleCount == 2
|
||||
is(editor.styleSheet.disabled, false, "first stylesheet is now enabled again");
|
||||
is(summary.classList.contains("disabled"), false,
|
||||
"first stylesheet is now enabled again, UI does not have DISABLED class");
|
||||
is(editor.styleSheet.disabled, false, "first stylesheet is now enabled again");
|
||||
is(summary.classList.contains("disabled"), false,
|
||||
"first stylesheet is now enabled again, UI does not have DISABLED class");
|
||||
});
|
||||
|
||||
finish();
|
||||
});
|
||||
function* toggleEnabled(editor, enabledToggle, panelWindow) {
|
||||
let changed = editor.once("property-change");
|
||||
|
||||
waitForFocus(function () {
|
||||
EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gPanelWindow);
|
||||
}, gPanelWindow);
|
||||
info("Waiting for focus.");
|
||||
yield SimpleTest.promiseFocus(panelWindow);
|
||||
|
||||
info("Clicking on the toggle.");
|
||||
EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, panelWindow);
|
||||
|
||||
info("Waiting for stylesheet to be disabled.");
|
||||
let property = yield changed;
|
||||
while (property !== "disabled") {
|
||||
info("Ignoring property-change for '" + property + "'.");
|
||||
property = yield editor.once("property-change");
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that 'Save' function works.
|
||||
|
||||
const TESTCASE_URI_HTML = TEST_BASE_HTTP + "simple.html";
|
||||
const TESTCASE_URI_CSS = TEST_BASE_HTTP + "simple.css";
|
||||
@ -14,50 +17,49 @@ Components.utils.import("resource://gre/modules/NetUtil.jsm", tempScope);
|
||||
let FileUtils = tempScope.FileUtils;
|
||||
let NetUtil = tempScope.NetUtil;
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
add_task(function* () {
|
||||
let htmlFile = yield copy(TESTCASE_URI_HTML, "simple.html");
|
||||
let cssFile = yield copy(TESTCASE_URI_CSS, "simple.css");
|
||||
let uri = Services.io.newFileURI(htmlFile);
|
||||
let filePath = uri.resolve("");
|
||||
|
||||
copy(TESTCASE_URI_HTML, "simple.html", function(htmlFile) {
|
||||
copy(TESTCASE_URI_CSS, "simple.css", function(cssFile) {
|
||||
addTabAndOpenStyleEditors(1, function(panel) {
|
||||
let UI = panel.UI;
|
||||
let editor = UI.editors[0];
|
||||
editor.getSourceEditor().then(runTests.bind(this, editor));
|
||||
});
|
||||
let { ui } = yield openStyleEditorForURL(filePath);
|
||||
|
||||
let uri = Services.io.newFileURI(htmlFile);
|
||||
let filePath = uri.resolve("");
|
||||
content.location = filePath;
|
||||
});
|
||||
});
|
||||
}
|
||||
let editor = ui.editors[0];
|
||||
yield editor.getSourceEditor();
|
||||
|
||||
function runTests(editor)
|
||||
{
|
||||
editor.sourceEditor.once("dirty-change", () => {
|
||||
is(editor.sourceEditor.isClean(), false, "Editor is dirty.");
|
||||
ok(editor.summary.classList.contains("unsaved"),
|
||||
"Star icon is present in the corresponding summary.");
|
||||
});
|
||||
info("Editing the style sheet.");
|
||||
let dirty = editor.sourceEditor.once("dirty-change");
|
||||
let beginCursor = {line: 0, ch: 0};
|
||||
editor.sourceEditor.replaceText("DIRTY TEXT", beginCursor, beginCursor);
|
||||
|
||||
editor.sourceEditor.once("dirty-change", () => {
|
||||
is(editor.sourceEditor.isClean(), true, "Editor is clean.");
|
||||
ok(!editor.summary.classList.contains("unsaved"),
|
||||
"Star icon is not present in the corresponding summary.");
|
||||
finish();
|
||||
});
|
||||
yield dirty;
|
||||
|
||||
is(editor.sourceEditor.isClean(), false, "Editor is dirty.");
|
||||
ok(editor.summary.classList.contains("unsaved"),
|
||||
"Star icon is present in the corresponding summary.");
|
||||
|
||||
info("Saving the changes.");
|
||||
dirty = editor.sourceEditor.once("dirty-change");
|
||||
|
||||
editor.saveToFile(null, function (file) {
|
||||
ok(file, "file should get saved directly when using a file:// URI");
|
||||
});
|
||||
}
|
||||
|
||||
function copy(aSrcChromeURL, aDestFileName, aCallback)
|
||||
yield dirty;
|
||||
|
||||
is(editor.sourceEditor.isClean(), true, "Editor is clean.");
|
||||
ok(!editor.summary.classList.contains("unsaved"),
|
||||
"Star icon is not present in the corresponding summary.");
|
||||
});
|
||||
|
||||
function copy(aSrcChromeURL, aDestFileName)
|
||||
{
|
||||
let deferred = promise.defer();
|
||||
let destFile = FileUtils.getFile("ProfD", [aDestFileName]);
|
||||
write(read(aSrcChromeURL), destFile, aCallback);
|
||||
write(read(aSrcChromeURL), destFile, deferred.resolve);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function read(aSrcChromeURL)
|
||||
|
@ -1,6 +1,9 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that the import button in the UI works.
|
||||
|
||||
// http rather than chrome to improve coverage
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "simple.html";
|
||||
@ -12,19 +15,22 @@ let FileUtils = tempScope.FileUtils;
|
||||
const FILENAME = "styleeditor-import-test.css";
|
||||
const SOURCE = "body{background:red;}";
|
||||
|
||||
add_task(function* () {
|
||||
let { panel, ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
|
||||
let gUI;
|
||||
let added = ui.once("editor-added");
|
||||
importSheet(ui, panel.panelWindow);
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
info("Waiting for editor to be added for the imported sheet.");
|
||||
let editor = yield added;
|
||||
|
||||
addTabAndCheckOnStyleEditorAdded(panel => gUI = panel.UI, testEditorAdded);
|
||||
is(editor.savedFile.leafName, FILENAME,
|
||||
"imported stylesheet will be saved directly into the same file");
|
||||
is(editor.friendlyName, FILENAME,
|
||||
"imported stylesheet has the same name as the filename");
|
||||
});
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
|
||||
function testImport()
|
||||
function importSheet(ui, panelWindow)
|
||||
{
|
||||
// create file to import first
|
||||
let file = FileUtils.getFile("ProfD", [FILENAME]);
|
||||
@ -37,37 +43,14 @@ function testImport()
|
||||
FileUtils.closeSafeFileOutputStream(ostream);
|
||||
|
||||
// click the import button now that the file to import is ready
|
||||
gUI._mockImportFile = file;
|
||||
ui._mockImportFile = file;
|
||||
|
||||
waitForFocus(function () {
|
||||
let document = gPanelWindow.document
|
||||
let document = panelWindow.document
|
||||
let importButton = document.querySelector(".style-editor-importButton");
|
||||
ok(importButton, "import button exists");
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(importButton, {}, gPanelWindow);
|
||||
}, gPanelWindow);
|
||||
EventUtils.synthesizeMouseAtCenter(importButton, {}, panelWindow);
|
||||
}, panelWindow);
|
||||
});
|
||||
}
|
||||
|
||||
let gAddedCount = 0;
|
||||
function testEditorAdded(aEditor)
|
||||
{
|
||||
if (++gAddedCount == 2) {
|
||||
// test import after the 2 initial stylesheets have been loaded
|
||||
gUI.editors[0].getSourceEditor().then(function() {
|
||||
testImport();
|
||||
});
|
||||
}
|
||||
|
||||
if (!aEditor.savedFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
is(aEditor.savedFile.leafName, FILENAME,
|
||||
"imported stylesheet will be saved directly into the same file");
|
||||
is(aEditor.friendlyName, FILENAME,
|
||||
"imported stylesheet has the same name as the filename");
|
||||
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
|
@ -1,37 +1,25 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that style editor shows sheets loaded with @import rules.
|
||||
|
||||
// http rather than chrome to improve coverage
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "import.html";
|
||||
|
||||
let gUI;
|
||||
add_task(function* () {
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
addTabAndOpenStyleEditors(3, onEditorAdded);
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
|
||||
function onEditorAdded(panel)
|
||||
{
|
||||
gUI = panel.UI;
|
||||
|
||||
is(gUI.editors.length, 3,
|
||||
is(ui.editors.length, 3,
|
||||
"there are 3 stylesheets after loading @imports");
|
||||
|
||||
is(gUI.editors[0].styleSheet.href, TEST_BASE_HTTP + "simple.css",
|
||||
is(ui.editors[0].styleSheet.href, TEST_BASE_HTTP + "simple.css",
|
||||
"stylesheet 1 is simple.css");
|
||||
|
||||
is(gUI.editors[1].styleSheet.href, TEST_BASE_HTTP + "import.css",
|
||||
is(ui.editors[1].styleSheet.href, TEST_BASE_HTTP + "import.css",
|
||||
"stylesheet 2 is import.css");
|
||||
|
||||
is(gUI.editors[2].styleSheet.href, TEST_BASE_HTTP + "import2.css",
|
||||
is(ui.editors[2].styleSheet.href, TEST_BASE_HTTP + "import2.css",
|
||||
"stylesheet 3 is import2.css");
|
||||
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
@ -1,66 +1,52 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
// Test that inline style sheets get correct names if they are saved to disk and
|
||||
// that those names survice a reload but not navigation to another page.
|
||||
|
||||
let gUI;
|
||||
|
||||
const FIRST_TEST_PAGE = TEST_BASE_HTTP + "inline-1.html"
|
||||
const SECOND_TEST_PAGE = TEST_BASE_HTTP + "inline-2.html"
|
||||
const FIRST_TEST_PAGE = TEST_BASE_HTTP + "inline-1.html";
|
||||
const SECOND_TEST_PAGE = TEST_BASE_HTTP + "inline-2.html";
|
||||
const SAVE_PATH = "test.css";
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
add_task(function* () {
|
||||
let { ui } = yield openStyleEditorForURL(FIRST_TEST_PAGE);
|
||||
|
||||
addTabAndOpenStyleEditors(2, function(panel) {
|
||||
gUI = panel.UI;
|
||||
loadCommonFrameScript();
|
||||
testIndentifierGeneration(ui);
|
||||
|
||||
// First test that identifiers are correcly generated. If not other tests
|
||||
// are likely to fail.
|
||||
testIndentifierGeneration();
|
||||
yield saveFirstInlineStyleSheet(ui);
|
||||
yield testFriendlyNamesAfterSave(ui);
|
||||
yield reloadPage(ui);
|
||||
yield testFriendlyNamesAfterSave(ui);
|
||||
yield navigateToAnotherPage(ui);
|
||||
yield testFriendlyNamesAfterNavigation(ui);
|
||||
});
|
||||
|
||||
saveFirstInlineStyleSheet()
|
||||
.then(testFriendlyNamesAfterSave)
|
||||
.then(reloadPage)
|
||||
.then(testFriendlyNamesAfterSave)
|
||||
.then(navigateToAnotherPage)
|
||||
.then(testFriendlyNamesAfterNavigation)
|
||||
.then(finishTests);
|
||||
});
|
||||
|
||||
content.location = FIRST_TEST_PAGE;
|
||||
}
|
||||
|
||||
function testIndentifierGeneration() {
|
||||
function testIndentifierGeneration(ui) {
|
||||
let fakeStyleSheetFile = {
|
||||
"href": "http://example.com/test.css",
|
||||
"nodeHref": "http://example.com/",
|
||||
"styleSheetIndex": 1
|
||||
}
|
||||
};
|
||||
|
||||
let fakeInlineStyleSheet = {
|
||||
"href": null,
|
||||
"nodeHref": "http://example.com/",
|
||||
"styleSheetIndex": 2
|
||||
}
|
||||
};
|
||||
|
||||
is(gUI.getStyleSheetIdentifier(fakeStyleSheetFile), "http://example.com/test.css",
|
||||
is(ui.getStyleSheetIdentifier(fakeStyleSheetFile), "http://example.com/test.css",
|
||||
"URI is the identifier of style sheet file.");
|
||||
|
||||
is(gUI.getStyleSheetIdentifier(fakeInlineStyleSheet), "inline-2-at-http://example.com/",
|
||||
is(ui.getStyleSheetIdentifier(fakeInlineStyleSheet), "inline-2-at-http://example.com/",
|
||||
"Inline style sheets are identified by their page and position at that page.");
|
||||
}
|
||||
|
||||
function saveFirstInlineStyleSheet() {
|
||||
function saveFirstInlineStyleSheet(ui) {
|
||||
let deferred = promise.defer();
|
||||
let editor = gUI.editors[0];
|
||||
let editor = ui.editors[0];
|
||||
|
||||
let destFile = FileUtils.getFile("ProfD", [SAVE_PATH]);
|
||||
|
||||
@ -72,9 +58,9 @@ function saveFirstInlineStyleSheet() {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testFriendlyNamesAfterSave() {
|
||||
let firstEditor = gUI.editors[0];
|
||||
let secondEditor = gUI.editors[1];
|
||||
function testFriendlyNamesAfterSave(ui) {
|
||||
let firstEditor = ui.editors[0];
|
||||
let secondEditor = ui.editors[1];
|
||||
|
||||
// The friendly name of first sheet should've been remembered, the second should
|
||||
// not be the same (bug 969900).
|
||||
@ -86,31 +72,21 @@ function testFriendlyNamesAfterSave() {
|
||||
return promise.resolve(null);
|
||||
}
|
||||
|
||||
function reloadPage() {
|
||||
function reloadPage(ui) {
|
||||
info("Reloading page.");
|
||||
content.location.reload();
|
||||
return waitForEditors(2);
|
||||
executeInContent("devtools:test:reload", {}, {}, false /* no response */);
|
||||
return ui.once("stylesheets-reset");
|
||||
}
|
||||
|
||||
function navigateToAnotherPage() {
|
||||
function navigateToAnotherPage(ui) {
|
||||
info("Navigating to another page.");
|
||||
let deferred = promise.defer();
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
gUI = null;
|
||||
|
||||
addTabAndOpenStyleEditors(2, function(panel) {
|
||||
gUI = panel.UI;
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
content.location = SECOND_TEST_PAGE;
|
||||
return deferred.promise;
|
||||
executeInContent("devtools:test:navigate", { location: SECOND_TEST_PAGE }, {}, false);
|
||||
return ui.once("stylesheets-reset");
|
||||
}
|
||||
|
||||
function testFriendlyNamesAfterNavigation() {
|
||||
let firstEditor = gUI.editors[0];
|
||||
let secondEditor = gUI.editors[1];
|
||||
function testFriendlyNamesAfterNavigation(ui) {
|
||||
let firstEditor = ui.editors[0];
|
||||
let secondEditor = ui.editors[1];
|
||||
|
||||
// Inline style sheets shouldn't have the name of previously saved file as the
|
||||
// page is different.
|
||||
@ -121,35 +97,3 @@ function testFriendlyNamesAfterNavigation() {
|
||||
|
||||
return promise.resolve(null);
|
||||
}
|
||||
|
||||
function finishTests() {
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for all editors to be added.
|
||||
*
|
||||
* @param {int} aNumberOfEditors
|
||||
* The number of editors to wait until proceeding.
|
||||
*
|
||||
* Returns a promise that's resolved once all editors are added.
|
||||
*/
|
||||
function waitForEditors(aNumberOfEditors) {
|
||||
let deferred = promise.defer();
|
||||
let count = 0;
|
||||
|
||||
info("Waiting for " + aNumberOfEditors + " editors to be added");
|
||||
gUI.on("editor-added", function editorAdded(event, editor) {
|
||||
if (++count == aNumberOfEditors) {
|
||||
info("All editors added. Resolving promise.");
|
||||
gUI.off("editor-added", editorAdded);
|
||||
gUI.editors[0].getSourceEditor().then(deferred.resolve);
|
||||
}
|
||||
else {
|
||||
info ("Editor " + count + " of " + aNumberOfEditors + " added.");
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
@ -2,54 +2,43 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test that resizing the source editor container doesn't move the caret.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "simple.html";
|
||||
|
||||
let gOriginalWidth; // these are set by runTests()
|
||||
let gOriginalHeight;
|
||||
add_task(function* () {
|
||||
let { toolbox, ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
is(ui.editors.length, 2, "There are 2 style sheets initially");
|
||||
|
||||
addTabAndOpenStyleEditors(2, panel => runTests(panel.UI));
|
||||
info("Changing toolbox host to a window.");
|
||||
yield toolbox.switchHost(devtools.Toolbox.HostType.WINDOW);
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
let editor = yield ui.editors[0].getSourceEditor();
|
||||
let originalSourceEditor = editor.sourceEditor;
|
||||
|
||||
function runTests(aUI)
|
||||
{
|
||||
is(aUI.editors.length, 2,
|
||||
"there is 2 stylesheets initially");
|
||||
let hostWindow = toolbox._host._window;
|
||||
let originalWidth = hostWindow.outerWidth;
|
||||
let originalHeight = hostWindow.outerHeight;
|
||||
|
||||
aUI.editors[0].getSourceEditor().then(aEditor => {
|
||||
executeSoon(function () {
|
||||
waitForFocus(function () {
|
||||
// queue a resize to inverse aspect ratio
|
||||
// this will trigger a detach and reattach (to workaround bug 254144)
|
||||
let originalSourceEditor = aEditor.sourceEditor;
|
||||
let editor = aEditor.sourceEditor;
|
||||
editor.setCursor(editor.getPosition(4)); // to check the caret is preserved
|
||||
// to check the caret is preserved
|
||||
originalSourceEditor.setCursor(originalSourceEditor.getPosition(4));
|
||||
|
||||
gOriginalWidth = gPanelWindow.outerWidth;
|
||||
gOriginalHeight = gPanelWindow.outerHeight;
|
||||
gPanelWindow.resizeTo(120, 480);
|
||||
info("Resizing window.");
|
||||
hostWindow.resizeTo(120, 480);
|
||||
|
||||
executeSoon(function () {
|
||||
is(aEditor.sourceEditor, originalSourceEditor,
|
||||
"the editor still references the same Editor instance");
|
||||
let editor = aEditor.sourceEditor;
|
||||
is(editor.getOffset(editor.getCursor()), 4,
|
||||
"the caret position has been preserved");
|
||||
let sourceEditor = ui.editors[0].sourceEditor;
|
||||
is(sourceEditor, originalSourceEditor,
|
||||
"the editor still references the same Editor instance");
|
||||
|
||||
// queue a resize to original aspect ratio
|
||||
waitForFocus(function () {
|
||||
gPanelWindow.resizeTo(gOriginalWidth, gOriginalHeight);
|
||||
executeSoon(function () {
|
||||
finish();
|
||||
});
|
||||
}, gPanelWindow);
|
||||
});
|
||||
}, gPanelWindow);
|
||||
});
|
||||
});
|
||||
}
|
||||
is(sourceEditor.getOffset(sourceEditor.getCursor()), 4,
|
||||
"the caret position has been preserved");
|
||||
|
||||
info("Restoring window to original size.");
|
||||
hostWindow.resizeTo(originalWidth, originalHeight);
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
// Restore the host type for other tests.
|
||||
Services.prefs.clearUserPref("devtools.toolbox.host");
|
||||
});
|
||||
|
@ -980,7 +980,6 @@ function PropertyView(aTree, aName)
|
||||
|
||||
this.link = "https://developer.mozilla.org/CSS/" + aName;
|
||||
|
||||
this.templateMatchedSelectors = aTree.styleDocument.getElementById("templateMatchedSelectors");
|
||||
this._propertyInfo = new PropertyInfo(aTree, aName);
|
||||
}
|
||||
|
||||
@ -1192,6 +1191,7 @@ PropertyView.prototype = {
|
||||
this.propertyInfo.value,
|
||||
{
|
||||
colorSwatchClass: "computedview-colorswatch",
|
||||
colorClass: "computedview-color",
|
||||
urlClass: "theme-link"
|
||||
// No need to use baseURI here as computed URIs are never relative.
|
||||
});
|
||||
@ -1222,9 +1222,10 @@ PropertyView.prototype = {
|
||||
}
|
||||
|
||||
this._matchedSelectorResponse = matched;
|
||||
CssHtmlTree.processTemplate(this.templateMatchedSelectors,
|
||||
this.matchedSelectorsContainer, this);
|
||||
|
||||
this._buildMatchedSelectors();
|
||||
this.matchedExpander.setAttribute("open", "");
|
||||
|
||||
this.tree.inspector.emit("computed-view-property-expanded");
|
||||
}).then(null, console.error);
|
||||
} else {
|
||||
@ -1240,6 +1241,40 @@ PropertyView.prototype = {
|
||||
return this._matchedSelectorResponse;
|
||||
},
|
||||
|
||||
_buildMatchedSelectors: function() {
|
||||
let frag = this.element.ownerDocument.createDocumentFragment();
|
||||
|
||||
for (let selector of this.matchedSelectorViews) {
|
||||
let p = createChild(frag, "p");
|
||||
let span = createChild(p, "span", {
|
||||
class: "rule-link"
|
||||
});
|
||||
let link = createChild(span, "a", {
|
||||
target: "_blank",
|
||||
class: "link theme-link",
|
||||
title: selector.href,
|
||||
sourcelocation: selector.source,
|
||||
tabindex: "0",
|
||||
textContent: selector.source
|
||||
});
|
||||
link.addEventListener("click", selector.openStyleEditor, false);
|
||||
link.addEventListener("keydown", selector.maybeOpenStyleEditor, false);
|
||||
|
||||
let status = createChild(p, "span", {
|
||||
dir: "ltr",
|
||||
class: "rule-text theme-fg-color3 " + selector.statusClass,
|
||||
title: selector.statusText,
|
||||
textContent: selector.sourceText
|
||||
});
|
||||
let valueSpan = createChild(status, "span", {
|
||||
class: "other-property-value theme-fg-color1"
|
||||
});
|
||||
valueSpan.appendChild(selector.outputFragment);
|
||||
}
|
||||
|
||||
this.matchedSelectorsContainer.appendChild(frag);
|
||||
},
|
||||
|
||||
/**
|
||||
* Provide access to the matched SelectorViews that we are currently
|
||||
* displaying.
|
||||
@ -1279,6 +1314,9 @@ PropertyView.prototype = {
|
||||
*/
|
||||
onMatchedToggle: function PropertyView_onMatchedToggle(aEvent)
|
||||
{
|
||||
if (aEvent.shiftKey) {
|
||||
return;
|
||||
}
|
||||
this.matchedExpanded = !this.matchedExpanded;
|
||||
this.refreshMatchedSelectors();
|
||||
aEvent.preventDefault();
|
||||
@ -1328,6 +1366,9 @@ function SelectorView(aTree, aSelectorInfo)
|
||||
this.selectorInfo = aSelectorInfo;
|
||||
this._cacheStatusNames();
|
||||
|
||||
this.openStyleEditor = this.openStyleEditor.bind(this);
|
||||
this.maybeOpenStyleEditor = this.maybeOpenStyleEditor.bind(this);
|
||||
|
||||
this.updateSourceLink();
|
||||
}
|
||||
|
||||
@ -1418,6 +1459,7 @@ SelectorView.prototype = {
|
||||
this.selectorInfo.name,
|
||||
this.selectorInfo.value, {
|
||||
colorSwatchClass: "computedview-colorswatch",
|
||||
colorClass: "computedview-color",
|
||||
urlClass: "theme-link",
|
||||
baseURI: this.selectorInfo.rule.href
|
||||
});
|
||||
@ -1540,5 +1582,32 @@ SelectorView.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a child element with a set of attributes.
|
||||
*
|
||||
* @param {Element} aParent
|
||||
* The parent node.
|
||||
* @param {string} aTag
|
||||
* The tag name.
|
||||
* @param {object} aAttributes
|
||||
* A set of attributes to set on the node.
|
||||
*/
|
||||
function createChild(aParent, aTag, aAttributes={}) {
|
||||
let elt = aParent.ownerDocument.createElementNS(HTML_NS, aTag);
|
||||
for (let attr in aAttributes) {
|
||||
if (aAttributes.hasOwnProperty(attr)) {
|
||||
if (attr === "textContent") {
|
||||
elt.textContent = aAttributes[attr];
|
||||
} else if(attr === "child") {
|
||||
elt.appendChild(aAttributes[attr]);
|
||||
} else {
|
||||
elt.setAttribute(attr, aAttributes[attr]);
|
||||
}
|
||||
}
|
||||
}
|
||||
aParent.appendChild(elt);
|
||||
return elt;
|
||||
}
|
||||
|
||||
exports.CssHtmlTree = CssHtmlTree;
|
||||
exports.PropertyView = PropertyView;
|
||||
|
@ -72,38 +72,5 @@
|
||||
&noPropertiesFound;
|
||||
</div>
|
||||
|
||||
<!--
|
||||
To visually debug the templates without running firefox, alter the display:none
|
||||
-->
|
||||
<div style="display:none;">
|
||||
<!--
|
||||
A templateMatchedSelectors sits inside each templateProperties showing the
|
||||
list of selectors that affect that property. Each needs data like this:
|
||||
{
|
||||
matchedSelectorViews: ..., // from cssHtmlTree.propertyViews[name].matchedSelectorViews
|
||||
}
|
||||
This is a template so the parent does not need to be a table, except that
|
||||
using a div as the parent causes the DOM to muck with the tr elements
|
||||
-->
|
||||
<div id="templateMatchedSelectors">
|
||||
<loop foreach="selector in ${matchedSelectorViews}">
|
||||
<p>
|
||||
<span class="rule-link">
|
||||
<a target="_blank" class="link theme-link"
|
||||
onclick="${selector.openStyleEditor}"
|
||||
onkeydown="${selector.maybeOpenStyleEditor}"
|
||||
title="${selector.href}"
|
||||
sourcelocation="${selector.source}"
|
||||
tabindex="0">${selector.source}</a>
|
||||
</span>
|
||||
<span dir="ltr" class="rule-text ${selector.statusClass} theme-fg-color3" title="${selector.statusText}">
|
||||
${selector.sourceText}
|
||||
<span class="other-property-value theme-fg-color1">${selector.outputFragment}</span>
|
||||
</span>
|
||||
</p>
|
||||
</loop>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -27,6 +27,7 @@ support-files =
|
||||
head.js
|
||||
|
||||
[browser_computedview_browser-styles.js]
|
||||
[browser_computedview_cycle_color.js]
|
||||
[browser_computedview_getNodeInfo.js]
|
||||
[browser_computedview_keybindings_01.js]
|
||||
[browser_computedview_keybindings_02.js]
|
||||
@ -99,6 +100,7 @@ skip-if = (os == "win" && debug) || e10s # bug 963492: win. bug 1040653: e10s.
|
||||
[browser_ruleview_multiple_properties_01.js]
|
||||
[browser_ruleview_multiple_properties_02.js]
|
||||
[browser_ruleview_original-source-link.js]
|
||||
[browser_ruleview_cycle-color.js]
|
||||
[browser_ruleview_override.js]
|
||||
[browser_ruleview_pseudo-element_01.js]
|
||||
[browser_ruleview_pseudo-element_02.js]
|
||||
|
@ -0,0 +1,68 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Computed view color cycling test.
|
||||
|
||||
const PAGE_CONTENT = [
|
||||
"<style type=\"text/css\">",
|
||||
".matches {color: #F00;}</style>",
|
||||
"<span id=\"matches\" class=\"matches\">Some styled text</span>",
|
||||
"</div>"
|
||||
].join("\n");
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," +
|
||||
"Computed view color cycling test.");
|
||||
content.document.body.innerHTML = PAGE_CONTENT;
|
||||
|
||||
info("Opening the computed view");
|
||||
let {toolbox, inspector, view} = yield openComputedView();
|
||||
|
||||
info("Selecting the test node");
|
||||
yield selectNode("#matches", inspector);
|
||||
|
||||
info("Checking the property itself");
|
||||
let container = getComputedViewPropertyView(view, "color").valueNode;
|
||||
checkColorCycling(container, inspector);
|
||||
|
||||
info("Checking matched selectors");
|
||||
container = yield getComputedViewMatchedRules(view, "color");
|
||||
checkColorCycling(container, inspector);
|
||||
});
|
||||
|
||||
function checkColorCycling(container, inspector) {
|
||||
let swatch = container.querySelector(".computedview-colorswatch");
|
||||
let valueNode = container.querySelector(".computedview-color");
|
||||
let win = inspector.sidebar.getWindowForTab("computedview");
|
||||
|
||||
// Hex (default)
|
||||
is(valueNode.textContent, "#F00", "Color displayed as a hex value.");
|
||||
|
||||
// HSL
|
||||
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "hsl(0, 100%, 50%)",
|
||||
"Color displayed as an HSL value.");
|
||||
|
||||
// RGB
|
||||
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "rgb(255, 0, 0)",
|
||||
"Color displayed as an RGB value.");
|
||||
|
||||
// Color name
|
||||
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "red",
|
||||
"Color displayed as a color name.");
|
||||
|
||||
// "Authored" (currently the computed value)
|
||||
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "rgb(255, 0, 0)",
|
||||
"Color displayed as an RGB value.");
|
||||
|
||||
// Back to hex
|
||||
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "#F00",
|
||||
"Color displayed as hex again.");
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test cycling color types in the rule view.
|
||||
|
||||
const PAGE_CONTENT = [
|
||||
"<style type=\"text/css\">",
|
||||
" body {",
|
||||
" color: #F00;",
|
||||
" }",
|
||||
"</style>",
|
||||
"Test cycling color types in the rule view!"
|
||||
].join("\n");
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8,Test cycling color types in the " +
|
||||
"rule view.");
|
||||
content.document.body.innerHTML = PAGE_CONTENT;
|
||||
let {toolbox, inspector, view} = yield openRuleView();
|
||||
|
||||
let container = getRuleViewProperty(view, "body", "color").valueSpan;
|
||||
checkColorCycling(container, inspector);
|
||||
});
|
||||
|
||||
function checkColorCycling(container, inspector) {
|
||||
let swatch = container.querySelector(".ruleview-colorswatch");
|
||||
let valueNode = container.querySelector(".ruleview-color");
|
||||
let win = inspector.sidebar.getWindowForTab("ruleview");
|
||||
|
||||
// Hex (default)
|
||||
is(valueNode.textContent, "#F00", "Color displayed as a hex value.");
|
||||
|
||||
// HSL
|
||||
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "hsl(0, 100%, 50%)",
|
||||
"Color displayed as an HSL value.");
|
||||
|
||||
// RGB
|
||||
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "rgb(255, 0, 0)",
|
||||
"Color displayed as an RGB value.");
|
||||
|
||||
// Color name
|
||||
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "red",
|
||||
"Color displayed as a color name.");
|
||||
|
||||
// "Authored" (currently the computed value)
|
||||
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "rgb(255, 0, 0)",
|
||||
"Color displayed as an RGB value.");
|
||||
|
||||
// Back to hex
|
||||
EventUtils.synthesizeMouseAtCenter(swatch, {type: "mousedown", shiftKey: true}, win);
|
||||
is(valueNode.textContent, "#F00",
|
||||
"Color displayed as hex again.");
|
||||
}
|
@ -327,7 +327,9 @@
|
||||
@RESPATH@/components/toolkit_finalizationwitness.xpt
|
||||
@RESPATH@/components/toolkit_formautofill.xpt
|
||||
@RESPATH@/components/toolkit_osfile.xpt
|
||||
#ifdef NIGHTLY_BUILD
|
||||
@RESPATH@/components/toolkit_perfmonitoring.xpt
|
||||
#endif
|
||||
@RESPATH@/components/toolkit_xulstore.xpt
|
||||
@RESPATH@/components/toolkitprofile.xpt
|
||||
#ifdef MOZ_ENABLE_XREMOTE
|
||||
|
@ -484,8 +484,9 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
|
||||
|
||||
NS_WARN_IF_FALSE(!aTargetFrame ||
|
||||
!aTargetFrame->GetContent() ||
|
||||
aTargetFrame->GetContent() == aTargetContent,
|
||||
"aTargetContent should be related with aTargetFrame");
|
||||
aTargetFrame->GetContent() == aTargetContent ||
|
||||
aTargetFrame->GetContent()->GetParent() == aTargetContent,
|
||||
"aTargetFrame should be related with aTargetContent");
|
||||
|
||||
mCurrentTarget = aTargetFrame;
|
||||
mCurrentTargetContent = nullptr;
|
||||
|
@ -7377,9 +7377,10 @@ PresShell::HandleEvent(nsIFrame* aFrame,
|
||||
}
|
||||
}
|
||||
|
||||
NS_WARN_IF_FALSE(frame, "Nothing to handle this event!");
|
||||
if (!frame)
|
||||
if (!frame) {
|
||||
NS_WARNING("Nothing to handle this event!");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsPresContext* framePresContext = frame->PresContext();
|
||||
nsPresContext* rootPresContext = framePresContext->GetRootPresContext();
|
||||
@ -7580,11 +7581,18 @@ PresShell::HandleEvent(nsIFrame* aFrame,
|
||||
|
||||
if (aEvent->mClass == ePointerEventClass) {
|
||||
if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) {
|
||||
// Try to keep frame for following check, because
|
||||
// frame can be damaged during CheckPointerCaptureState.
|
||||
nsWeakFrame frameKeeper(frame);
|
||||
// Before any pointer events, we should check state of pointer capture,
|
||||
// Thus got/lostpointercapture events emulate asynchronous behavior.
|
||||
// Handlers of got/lostpointercapture events can change capturing state,
|
||||
// That's why we should re-check pointer capture state until stable state.
|
||||
while(CheckPointerCaptureState(pointerEvent->pointerId));
|
||||
// Prevent application crashes, in case damaged frame.
|
||||
if (!frameKeeper.IsAlive()) {
|
||||
frame = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7626,7 +7634,11 @@ PresShell::HandleEvent(nsIFrame* aFrame,
|
||||
delete event;
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!frame) {
|
||||
NS_WARNING("Nothing to handle this event!");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
72
layout/base/tests/bug1153130_inner.html
Normal file
72
layout/base/tests/bug1153130_inner.html
Normal file
@ -0,0 +1,72 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1153130
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 1153130</title>
|
||||
<meta name="author" content="Maksim Lebedev" />
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<style>
|
||||
#target { background: yellow; padding: 10px; }
|
||||
</style>
|
||||
<script type="application/javascript">
|
||||
var target = undefined;
|
||||
var test_down = false;
|
||||
var test_capture = false;
|
||||
var test_move = false;
|
||||
var test_success = false;
|
||||
|
||||
function TargetHandler(event) {
|
||||
logger("Target receive event: " + event.type);
|
||||
if(event.type == "pointerdown") {
|
||||
test_down = true;
|
||||
target.setPointerCapture(event.pointerId);
|
||||
} else if(event.type == "gotpointercapture") {
|
||||
test_capture = true;
|
||||
} else if(event.type == "pointermove" && test_capture) {
|
||||
test_move = true;
|
||||
}
|
||||
}
|
||||
function logger(message) {
|
||||
console.log(message);
|
||||
var log = document.getElementById('target');
|
||||
log.innerHTML = message + "<br>" + log.innerHTML;
|
||||
}
|
||||
function prepareTest() {
|
||||
parent.turnOnPointerEvents(executeTest);
|
||||
}
|
||||
function executeTest() {
|
||||
logger("executeTest");
|
||||
target = document.getElementById("target");
|
||||
target.addEventListener("pointerdown", TargetHandler, false);
|
||||
target.addEventListener("gotpointercapture", TargetHandler, false);
|
||||
target.addEventListener("pointermove", TargetHandler, false);
|
||||
var rect = target.getBoundingClientRect();
|
||||
synthesizePointer(target, rect.width/5, rect.height/5, {type: "pointermove"});
|
||||
synthesizePointer(target, rect.width/5, rect.height/5, {type: "pointerdown"});
|
||||
synthesizePointer(target, rect.width/4, rect.height/4, {type: "pointermove"});
|
||||
synthesizePointer(target, rect.width/3, rect.height/3, {type: "pointermove"});
|
||||
synthesizePointer(target, rect.width/3, rect.height/3, {type: "pointerup"});
|
||||
synthesizePointer(target, rect.width/2, rect.height/2, {type: "pointermove"});
|
||||
test_success = true;
|
||||
finishTest();
|
||||
}
|
||||
function finishTest() {
|
||||
parent.is(test_down, true, "pointerdown event should be received by target");
|
||||
parent.is(test_capture, true, "gotpointercapture event should be received by target");
|
||||
parent.is(test_move, true, "pointermove event should be received by target while pointer capture is active");
|
||||
parent.is(test_success, true, "Firefox should be live!");
|
||||
logger("finishTest");
|
||||
parent.finishTest();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="prepareTest()">
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1153130">Mozilla Bug 1153130</a>
|
||||
<div id="target">div id=target</div>
|
||||
</body>
|
||||
</html>
|
@ -261,3 +261,5 @@ support-files = bug1080361_inner.html
|
||||
support-files = bug1093686_inner.html
|
||||
[test_bug1120705.html]
|
||||
skip-if = buildapp == 'android' || buildapp == 'b2g' || buildapp == 'b2g-debug' || os == 'mac' # android and b2g do not have clickable scrollbars, mac does not have scrollbar down and up buttons
|
||||
[test_bug1153130.html]
|
||||
support-files = bug1153130_inner.html
|
||||
|
37
layout/base/tests/test_bug1153130.html
Normal file
37
layout/base/tests/test_bug1153130.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1153130
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 1153130</title>
|
||||
<meta name="author" content="Maksim Lebedev" />
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="text/javascript">
|
||||
var iframe = undefined;
|
||||
function prepareTest() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
iframe = document.getElementById("testFrame");
|
||||
turnOnPointerEvents(startTest);
|
||||
}
|
||||
function turnOnPointerEvents(callback) {
|
||||
SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
["dom.w3c_pointer_events.enabled", true]
|
||||
]
|
||||
}, callback);
|
||||
}
|
||||
function startTest() {
|
||||
iframe.src = "bug1153130_inner.html";
|
||||
}
|
||||
function finishTest() {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="prepareTest()">
|
||||
<iframe id="testFrame" height="700" width="700"></iframe>
|
||||
</body>
|
||||
</html>
|
@ -114,7 +114,6 @@
|
||||
to. -->
|
||||
<!ENTITY overlay_share_send_tab_btn_label "Send to another device">
|
||||
<!ENTITY overlay_share_no_url "No link found in this share">
|
||||
<!ENTITY overlay_share_retry "Try again">
|
||||
<!ENTITY overlay_share_select_device "Select device">
|
||||
<!-- Localization note (overlay_no_synced_devices) : Used when the menu option
|
||||
to send a tab to a synced device is pressed and no other synced devices
|
||||
|
@ -117,7 +117,6 @@
|
||||
<string name="overlay_share_reading_list_btn_label_already">&overlay_share_reading_list_btn_label_already;</string>
|
||||
<string name="overlay_share_send_tab_btn_label">&overlay_share_send_tab_btn_label;</string>
|
||||
<string name="overlay_share_no_url">&overlay_share_no_url;</string>
|
||||
<string name="overlay_share_retry">&overlay_share_retry;</string>
|
||||
<string name="overlay_share_select_device">&overlay_share_select_device;</string>
|
||||
<string name="overlay_no_synced_devices">&overlay_no_synced_devices;</string>
|
||||
<string name="overlay_share_tab_not_sent">&overlay_share_tab_not_sent;</string>
|
||||
|
@ -253,7 +253,9 @@
|
||||
@BINPATH@/components/toolkit_finalizationwitness.xpt
|
||||
@BINPATH@/components/toolkit_formautofill.xpt
|
||||
@BINPATH@/components/toolkit_osfile.xpt
|
||||
#ifdef NIGHTLY_BUILD
|
||||
@BINPATH@/components/toolkit_perfmonitoring.xpt
|
||||
#endif
|
||||
@BINPATH@/components/toolkit_xulstore.xpt
|
||||
@BINPATH@/components/toolkitprofile.xpt
|
||||
#ifdef MOZ_ENABLE_XREMOTE
|
||||
|
@ -912,9 +912,9 @@ function testScope(aTester, aTest, expected) {
|
||||
|
||||
var self = this;
|
||||
this.ok = function test_ok(condition, name, diag, stack) {
|
||||
if (this.__expected == 'fail') {
|
||||
if (self.__expected == 'fail') {
|
||||
if (!condition) {
|
||||
this.__num_failed++;
|
||||
self.__num_failed++;
|
||||
condition = true;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
|
||||
const { AddonWatcher } = Cu.import("resource://gre/modules/AddonWatcher.jsm", {});
|
||||
const { PerformanceStats } = Cu.import("resource://gre/modules/PerformanceStats.jsm", {});
|
||||
|
||||
/**
|
||||
@ -84,8 +85,93 @@ let State = {
|
||||
|
||||
|
||||
function update() {
|
||||
updateLiveData();
|
||||
updateSlowAddons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the list of slow addons
|
||||
*/
|
||||
function updateSlowAddons() {
|
||||
try {
|
||||
let dataElt = document.getElementById("data");
|
||||
let data = AddonWatcher.alerts;
|
||||
if (data.size == 0) {
|
||||
// Nothing to display.
|
||||
return;
|
||||
}
|
||||
let alerts = 0;
|
||||
for (let [addonId, details] of data) {
|
||||
for (let k of Object.keys(details.alerts)) {
|
||||
alerts += details.alerts[k];
|
||||
}
|
||||
}
|
||||
|
||||
if (!alerts) {
|
||||
// Still nothing to display.
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let elData = document.getElementById("slowAddonsList");
|
||||
elData.innerHTML = "";
|
||||
let elTable = document.createElement("table");
|
||||
elData.appendChild(elTable);
|
||||
|
||||
// Generate header
|
||||
let elHeader = document.createElement("tr");
|
||||
elTable.appendChild(elHeader);
|
||||
for (let name of [
|
||||
"Alerts",
|
||||
"Jank level alerts",
|
||||
"(highest jank)",
|
||||
"Cross-Process alerts",
|
||||
"(highest CPOW)"
|
||||
]) {
|
||||
let elName = document.createElement("td");
|
||||
elName.textContent = name;
|
||||
elHeader.appendChild(elName);
|
||||
elName.classList.add("header");
|
||||
}
|
||||
for (let [addonId, details] of data) {
|
||||
let elAddon = document.createElement("tr");
|
||||
|
||||
// Display the number of occurrences of each alerts
|
||||
let elTotal = document.createElement("td");
|
||||
let total = 0;
|
||||
for (let k of Object.keys(details.alerts)) {
|
||||
total += details.alerts[k];
|
||||
}
|
||||
elTotal.textContent = total;
|
||||
elAddon.appendChild(elTotal);
|
||||
|
||||
for (let filter of ["longestDuration", "totalCPOWTime"]) {
|
||||
for (let stat of ["alerts", "peaks"]) {
|
||||
let el = document.createElement("td");
|
||||
el.textContent = details[stat][filter] || 0;
|
||||
elAddon.appendChild(el);
|
||||
}
|
||||
}
|
||||
|
||||
// Display the name of the add-on
|
||||
let elName = document.createElement("td");
|
||||
elAddon.appendChild(elName);
|
||||
AddonManager.getAddonByID(addonId, a => {
|
||||
elName.textContent = a ? a.name : addonId
|
||||
});
|
||||
|
||||
elTable.appendChild(elAddon);
|
||||
}
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the table of live data.
|
||||
*/
|
||||
function updateLiveData() {
|
||||
try {
|
||||
let dataElt = document.getElementById("liveData");
|
||||
dataElt.innerHTML = "";
|
||||
|
||||
// Generate table headers
|
||||
@ -137,7 +223,7 @@ function update() {
|
||||
let _el = el;
|
||||
let _item = item;
|
||||
AddonManager.getAddonByID(item.addonId, a => {
|
||||
_el.textContent = a?a.name:_item.name
|
||||
_el.textContent = a ? a.name : _item.name
|
||||
});
|
||||
} else {
|
||||
el.textContent = item.name;
|
||||
|
@ -85,7 +85,15 @@
|
||||
</style>
|
||||
</head>
|
||||
<body onload="go()">
|
||||
<table id="data">
|
||||
|
||||
<h1>Performance monitor</h1>
|
||||
<table id="liveData">
|
||||
</table>
|
||||
|
||||
<h1>Slow add-ons alerts</h1>
|
||||
<div id="slowAddonsList">
|
||||
(none)
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -16,7 +16,7 @@ function frameScript() {
|
||||
let hasURL = false;
|
||||
|
||||
try {
|
||||
let eltData = content.document.getElementById("data");
|
||||
let eltData = content.document.getElementById("liveData");
|
||||
if (!eltData) {
|
||||
return;
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ LOCAL_INCLUDES += [
|
||||
'../feeds',
|
||||
'../find',
|
||||
'../jsdownloads/src',
|
||||
'../perfmonitoring',
|
||||
'../protobuf',
|
||||
'../startup',
|
||||
'../statusfilter',
|
||||
@ -34,4 +33,9 @@ if not CONFIG['MOZ_DISABLE_PARENTAL_CONTROLS']:
|
||||
'../parentalcontrols',
|
||||
]
|
||||
|
||||
if CONFIG['NIGHTLY_BUILD']:
|
||||
LOCAL_INCLUDES += [
|
||||
'../perfmonitoring',
|
||||
]
|
||||
|
||||
FAIL_ON_WARNINGS = True
|
||||
|
@ -51,7 +51,13 @@
|
||||
#include "nsTerminator.h"
|
||||
#endif
|
||||
|
||||
#if defined(NIGHTLY_BUILD)
|
||||
#define MOZ_HAS_PERFSTATS
|
||||
#endif // defined(NIGHTLY_BUILD)
|
||||
|
||||
#if defined(MOZ_HAS_PERFSTATS)
|
||||
#include "nsPerformanceStats.h"
|
||||
#endif // defined (MOZ_HAS_PERFSTATS)
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
@ -59,7 +65,9 @@ using namespace mozilla;
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAppStartup, Init)
|
||||
|
||||
#if defined(MOZ_HAS_PERFSTATS)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsPerformanceStatsService)
|
||||
#endif // defined (MOZ_HAS_PERFSTATS)
|
||||
|
||||
#if defined(MOZ_HAS_TERMINATOR)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsTerminator)
|
||||
@ -119,7 +127,10 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NativeFileWatcherService, Init)
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AddonPathService, AddonPathService::GetInstance)
|
||||
|
||||
NS_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID);
|
||||
#if defined(MOZ_HAS_PERFSTATS)
|
||||
NS_DEFINE_NAMED_CID(NS_TOOLKIT_PERFORMANCESTATSSERVICE_CID);
|
||||
#endif // defined (MOZ_HAS_PERFSTATS)
|
||||
|
||||
#if defined(MOZ_HAS_TERMINATOR)
|
||||
NS_DEFINE_NAMED_CID(NS_TOOLKIT_TERMINATOR_CID);
|
||||
#endif
|
||||
@ -154,7 +165,9 @@ static const Module::CIDEntry kToolkitCIDs[] = {
|
||||
#if defined(MOZ_HAS_TERMINATOR)
|
||||
{ &kNS_TOOLKIT_TERMINATOR_CID, false, nullptr, nsTerminatorConstructor },
|
||||
#endif
|
||||
#if defined(MOZ_HAS_PERFSTATS)
|
||||
{ &kNS_TOOLKIT_PERFORMANCESTATSSERVICE_CID, false, nullptr, nsPerformanceStatsServiceConstructor },
|
||||
#endif // defined (MOZ_HAS_PERFSTATS)
|
||||
{ &kNS_USERINFO_CID, false, nullptr, nsUserInfoConstructor },
|
||||
{ &kNS_ALERTSSERVICE_CID, false, nullptr, nsAlertsServiceConstructor },
|
||||
#if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
|
||||
@ -188,7 +201,9 @@ static const Module::ContractIDEntry kToolkitContracts[] = {
|
||||
#if defined(MOZ_HAS_TERMINATOR)
|
||||
{ NS_TOOLKIT_TERMINATOR_CONTRACTID, &kNS_TOOLKIT_TERMINATOR_CID },
|
||||
#endif
|
||||
#if defined(MOZ_HAS_PERFSTATS)
|
||||
{ NS_TOOLKIT_PERFORMANCESTATSSERVICE_CONTRACTID, &kNS_TOOLKIT_PERFORMANCESTATSSERVICE_CID },
|
||||
#endif // defined (MOZ_HAS_PERFSTATS)
|
||||
{ NS_USERINFO_CONTRACTID, &kNS_USERINFO_CID },
|
||||
{ NS_ALERTSERVICE_CONTRACTID, &kNS_ALERTSSERVICE_CID },
|
||||
#if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
|
||||
|
@ -11,7 +11,6 @@ if CONFIG['MOZ_ENABLE_XREMOTE']:
|
||||
DIRS += [
|
||||
'aboutcache',
|
||||
'aboutmemory',
|
||||
'aboutperformance',
|
||||
'addoncompat',
|
||||
'alerts',
|
||||
'apppicker',
|
||||
@ -36,7 +35,6 @@ DIRS += [
|
||||
'parentalcontrols',
|
||||
'passwordmgr',
|
||||
'perf',
|
||||
'perfmonitoring',
|
||||
'places',
|
||||
'processsingleton',
|
||||
'promiseworker',
|
||||
@ -92,6 +90,12 @@ if CONFIG['MOZ_CAPTIVEDETECT']:
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk" and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
|
||||
DIRS += ['terminator']
|
||||
|
||||
if CONFIG['NIGHTLY_BUILD']: # Bug 1136927 - Performance Monitoring is not ready for prime-time yet
|
||||
DIRS += [
|
||||
'aboutperformance',
|
||||
'perfmonitoring',
|
||||
]
|
||||
|
||||
DIRS += ['build']
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
|
@ -23,8 +23,16 @@ XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
||||
const FILTERS = ["longestDuration", "totalCPOWTime"];
|
||||
|
||||
let AddonWatcher = {
|
||||
_previousPerformanceIndicators: {},
|
||||
|
||||
/**
|
||||
* Stats, designed to be consumed by clients of AddonWatcher.
|
||||
*
|
||||
*/
|
||||
_stats: new Map(),
|
||||
_timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
|
||||
_callback: null,
|
||||
/**
|
||||
@ -161,24 +169,36 @@ let AddonWatcher = {
|
||||
add(addonId, diff.totalCPOWTime / 1000);
|
||||
}
|
||||
|
||||
// Report mibehaviors to the user.
|
||||
let reason = null;
|
||||
// Store misbehaviors for about:performance and other clients
|
||||
|
||||
for (let k of ["longestDuration", "totalCPOWTime"]) {
|
||||
if (limits[k] > 0 && diff[k] > limits[k]) {
|
||||
reason = k;
|
||||
let stats = this._stats.get(addonId);
|
||||
if (!stats) {
|
||||
stats = {
|
||||
peaks: {},
|
||||
alerts: {},
|
||||
};
|
||||
this._stats.set(addonId, stats);
|
||||
}
|
||||
|
||||
// Report misbehaviors to the user.
|
||||
|
||||
for (let filter of FILTERS) {
|
||||
dump(`Checking addon ${addonId} with filter ${filter}\n`);
|
||||
let peak = stats.peaks[filter] || 0;
|
||||
stats.peaks[filter] = Math.max(diff[filter], peak);
|
||||
|
||||
if (limits[filter] <= 0 || diff[filter] <= limits[filter]) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!reason) {
|
||||
continue;
|
||||
}
|
||||
stats.alerts[filter] = (stats.alerts[filter] || 0) + 1;
|
||||
|
||||
try {
|
||||
this._callback(addonId, reason);
|
||||
} catch (ex) {
|
||||
Cu.reportError("Error in AddonWatcher._checkAddons callback " + ex);
|
||||
Cu.reportError(ex.stack);
|
||||
try {
|
||||
this._callback(addonId, filter);
|
||||
} catch (ex) {
|
||||
Cu.reportError("Error in AddonWatcher._checkAddons callback " + ex);
|
||||
Cu.reportError(ex.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
@ -200,5 +220,24 @@ let AddonWatcher = {
|
||||
} catch (ex) {
|
||||
Preferences.set("browser.addon-watch.ignore", JSON.stringify([addonid]));
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* The list of alerts for this session.
|
||||
*
|
||||
* @type {Map<String, Object>} A map associating addonId to
|
||||
* objects with fields
|
||||
* - {Object} peaks The highest values encountered for each filter.
|
||||
* - {number} longestDuration
|
||||
* - {number} totalCPOWTime
|
||||
* - {Object} alerts The number of alerts for each filter.
|
||||
* - {number} longestDuration
|
||||
* - {number} totalCPOWTime
|
||||
*/
|
||||
get alerts() {
|
||||
let result = new Map();
|
||||
for (let [k, v] of this._stats) {
|
||||
result.set(k, Cu.cloneInto(v, this));
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
@ -58,7 +58,9 @@ let burn_rubber = Task.async(function*({histogramName, topic, expectedReason, pr
|
||||
info("Preparing add-on watcher");
|
||||
let wait = new Promise(resolve => AddonWatcher.init((id, reason) => {
|
||||
Assert.equal(id, ADDON_ID, "The add-on watcher has detected the misbehaving addon");
|
||||
resolve(reason);
|
||||
if (reason == expectedReason) {
|
||||
resolve(reason);
|
||||
}
|
||||
}));
|
||||
let done = false;
|
||||
wait = wait.then(result => {
|
||||
|
@ -32,9 +32,32 @@ function frameScript() {
|
||||
});
|
||||
}
|
||||
|
||||
function Assert_leq(a, b, msg) {
|
||||
Assert.ok(a <= b, `${msg}: ${a} <= ${b}`);
|
||||
}
|
||||
// A variant of `Assert` that doesn't spam the logs
|
||||
// in case of success.
|
||||
let SilentAssert = {
|
||||
equal: function(a, b, msg) {
|
||||
if (a == b) {
|
||||
return;
|
||||
}
|
||||
Assert.equal(a, b, msg);
|
||||
},
|
||||
notEqual: function(a, b, msg) {
|
||||
if (a != b) {
|
||||
return;
|
||||
}
|
||||
Assert.notEqual(a, b, msg);
|
||||
},
|
||||
ok: function(a, msg) {
|
||||
if (a) {
|
||||
return;
|
||||
}
|
||||
Assert.ok(a, msg);
|
||||
},
|
||||
leq: function(a, b, msg) {
|
||||
this.ok(a <= b, `${msg}: ${a} <= ${b}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function monotinicity_tester(source, testName) {
|
||||
// In the background, check invariants:
|
||||
@ -54,22 +77,22 @@ function monotinicity_tester(source, testName) {
|
||||
return;
|
||||
}
|
||||
for (let k of ["name", "addonId", "isSystem"]) {
|
||||
Assert.equal(prev[k], next[k], `Sanity check (${testName}): ${k} hasn't changed.`);
|
||||
SilentAssert.equal(prev[k], next[k], `Sanity check (${testName}): ${k} hasn't changed.`);
|
||||
}
|
||||
for (let k of ["totalUserTime", "totalSystemTime", "totalCPOWTime", "ticks"]) {
|
||||
Assert.equal(typeof next[k], "number", `Sanity check (${testName}): ${k} is a number.`);
|
||||
Assert_leq(prev[k], next[k], `Sanity check (${testName}): ${k} is monotonic.`);
|
||||
Assert_leq(0, next[k], `Sanity check (${testName}): ${k} is >= 0.`)
|
||||
SilentAssert.equal(typeof next[k], "number", `Sanity check (${testName}): ${k} is a number.`);
|
||||
SilentAssert.leq(prev[k], next[k], `Sanity check (${testName}): ${k} is monotonic.`);
|
||||
SilentAssert.leq(0, next[k], `Sanity check (${testName}): ${k} is >= 0.`)
|
||||
}
|
||||
Assert.equal(prev.durations.length, next.durations.length);
|
||||
SilentAssert.equal(prev.durations.length, next.durations.length);
|
||||
for (let i = 0; i < next.durations.length; ++i) {
|
||||
Assert.ok(typeof next.durations[i] == "number" && next.durations[i] >= 0,
|
||||
SilentAssert.ok(typeof next.durations[i] == "number" && next.durations[i] >= 0,
|
||||
`Sanity check (${testName}): durations[${i}] is a non-negative number.`);
|
||||
Assert_leq(prev.durations[i], next.durations[i],
|
||||
SilentAssert.leq(prev.durations[i], next.durations[i],
|
||||
`Sanity check (${testName}): durations[${i}] is monotonic.`)
|
||||
}
|
||||
for (let i = 0; i < next.durations.length - 1; ++i) {
|
||||
Assert_leq(next.durations[i + 1], next.durations[i],
|
||||
SilentAssert.leq(next.durations[i + 1], next.durations[i],
|
||||
`Sanity check (${testName}): durations[${i}] >= durations[${i + 1}].`)
|
||||
}
|
||||
};
|
||||
@ -86,9 +109,9 @@ function monotinicity_tester(source, testName) {
|
||||
|
||||
// Sanity check on the process data.
|
||||
sanityCheck(previous.processData, snapshot.processData);
|
||||
Assert.equal(snapshot.processData.isSystem, true);
|
||||
Assert.equal(snapshot.processData.name, "<process>");
|
||||
Assert.equal(snapshot.processData.addonId, "");
|
||||
SilentAssert.equal(snapshot.processData.isSystem, true);
|
||||
SilentAssert.equal(snapshot.processData.name, "<process>");
|
||||
SilentAssert.equal(snapshot.processData.addonId, "");
|
||||
previous.procesData = snapshot.processData;
|
||||
|
||||
// Sanity check on components data.
|
||||
@ -102,17 +125,13 @@ function monotinicity_tester(source, testName) {
|
||||
previous.componentsMap.set(key, item);
|
||||
|
||||
for (let k of ["totalUserTime", "totalSystemTime", "totalCPOWTime"]) {
|
||||
Assert_leq(item[k], snapshot.processData[k],
|
||||
SilentAssert.leq(item[k], snapshot.processData[k],
|
||||
`Sanity check (${testName}): component has a lower ${k} than process`);
|
||||
}
|
||||
}
|
||||
// Check that we do not have duplicate components.
|
||||
info(`Before deduplication, we had the following components: ${keys.sort().join(", ")}`);
|
||||
info(`After deduplication, we have the following components: ${[...set.keys()].sort().join(", ")}`);
|
||||
|
||||
info(`Deactivating deduplication check (Bug 1150045)`);
|
||||
if (false) {
|
||||
Assert.equal(set.size, snapshot.componentsData.length);
|
||||
SilentAssert.equal(set.size, snapshot.componentsData.length);
|
||||
}
|
||||
});
|
||||
let interval = window.setInterval(frameCheck, 300);
|
||||
|
@ -461,8 +461,7 @@ BookmarkImporter.prototype = {
|
||||
this._importPromises.push(kwPromise);
|
||||
}
|
||||
if (aData.tags) {
|
||||
// TODO (bug 967196) the tagging service should trim by itself.
|
||||
let tags = aData.tags.split(",").map(tag => tag.trim());
|
||||
let tags = aData.tags.split(",");
|
||||
if (tags.length)
|
||||
PlacesUtils.tagging.tagURI(NetUtil.newURI(aData.uri), tags);
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ const Cr = Components.results;
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
|
||||
"resource://gre/modules/Deprecated.jsm");
|
||||
|
||||
const TOPIC_SHUTDOWN = "places-shutdown";
|
||||
|
||||
@ -98,12 +100,14 @@ TaggingService.prototype = {
|
||||
*
|
||||
* @param aTags
|
||||
* Array of tags. Entries can be tag names or concrete item id.
|
||||
* @param trim [optional]
|
||||
* Whether to trim passed-in named tags. Defaults to false.
|
||||
* @return Array of tag objects like { id: number, name: string }.
|
||||
*
|
||||
* @throws Cr.NS_ERROR_INVALID_ARG if any element of the input array is not
|
||||
* a valid tag.
|
||||
*/
|
||||
_convertInputMixedTagsArray: function TS__convertInputMixedTagsArray(aTags)
|
||||
_convertInputMixedTagsArray: function TS__convertInputMixedTagsArray(aTags, trim=false)
|
||||
{
|
||||
return aTags.map(function (val)
|
||||
{
|
||||
@ -117,7 +121,7 @@ TaggingService.prototype = {
|
||||
}
|
||||
else if (typeof(val) == "string" && val.length > 0 && val.length <= Ci.nsITaggingService.MAX_TAG_LENGTH) {
|
||||
// This is a tag name.
|
||||
tag.name = val;
|
||||
tag.name = trim ? val.trim() : val;
|
||||
// We can't know the id at this point, since a previous tag could
|
||||
// have created it.
|
||||
tag.__defineGetter__("id", function () this._self._getItemIdForTag(this.name));
|
||||
@ -137,7 +141,7 @@ TaggingService.prototype = {
|
||||
}
|
||||
|
||||
// This also does some input validation.
|
||||
let tags = this._convertInputMixedTagsArray(aTags);
|
||||
let tags = this._convertInputMixedTagsArray(aTags, true);
|
||||
|
||||
let taggingService = this;
|
||||
PlacesUtils.bookmarks.runInBatchMode({
|
||||
@ -215,6 +219,12 @@ TaggingService.prototype = {
|
||||
// This also does some input validation.
|
||||
let tags = this._convertInputMixedTagsArray(aTags);
|
||||
|
||||
let isAnyTagNotTrimmed = tags.some(tag => /^\s|\s$/.test(tag.name));
|
||||
if (isAnyTagNotTrimmed) {
|
||||
Deprecated.warning("At least one tag passed to untagURI was not trimmed",
|
||||
"https://bugzilla.mozilla.org/show_bug.cgi?id=967196");
|
||||
}
|
||||
|
||||
let taggingService = this;
|
||||
PlacesUtils.bookmarks.runInBatchMode({
|
||||
runBatched: function (aUserData)
|
||||
@ -239,6 +249,11 @@ TaggingService.prototype = {
|
||||
if (!aTagName || aTagName.length == 0)
|
||||
throw Cr.NS_ERROR_INVALID_ARG;
|
||||
|
||||
if (/^\s|\s$/.test(aTagName)) {
|
||||
Deprecated.warning("Tag passed to getURIsForTag was not trimmed",
|
||||
"https://bugzilla.mozilla.org/show_bug.cgi?id=967196");
|
||||
}
|
||||
|
||||
let uris = [];
|
||||
let tagId = this._getItemIdForTag(aTagName);
|
||||
if (tagId == -1)
|
||||
|
@ -175,4 +175,16 @@ function run_test() {
|
||||
|
||||
// cleanup
|
||||
tagRoot.containerOpen = false;
|
||||
|
||||
// Tagging service should trim tags (Bug967196)
|
||||
let exampleURI = uri("http://www.example.com/");
|
||||
PlacesUtils.tagging.tagURI(exampleURI, [ " test " ]);
|
||||
|
||||
let exampleTags = PlacesUtils.tagging.getTagsForURI(exampleURI);
|
||||
do_check_eq(exampleTags.length, 1);
|
||||
do_check_eq(exampleTags[0], "test");
|
||||
|
||||
PlacesUtils.tagging.untagURI(exampleURI, [ "test" ]);
|
||||
exampleTags = PlacesUtils.tagging.getTagsForURI(exampleURI);
|
||||
do_check_eq(exampleTags.length, 0);
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ const MAX_PING_FILE_AGE = 14 * 24 * 60 * 60 * 1000; // 2 weeks
|
||||
const OVERDUE_PING_FILE_AGE = 7 * 24 * 60 * 60 * 1000; // 1 week
|
||||
|
||||
// Maximum number of pings to save.
|
||||
const MAX_LRU_PINGS = 17;
|
||||
const MAX_LRU_PINGS = 50;
|
||||
|
||||
// The number of outstanding saved pings that we have issued loading
|
||||
// requests for.
|
||||
|
@ -1,166 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||
Cu.import("resource://gre/modules/ThirdPartyCookieProbe.jsm", this);
|
||||
Cu.import("resource://gre/modules/Promise.jsm", this);
|
||||
Cu.import("resource://gre/modules/TelemetryPing.jsm", this);
|
||||
|
||||
let TOPIC_ACCEPTED = "third-party-cookie-accepted";
|
||||
let TOPIC_REJECTED = "third-party-cookie-rejected";
|
||||
|
||||
let FLUSH_MILLISECONDS = 1000 * 60 * 60 * 24 / 2; /*Half a day, for testing purposes*/
|
||||
|
||||
const NUMBER_OF_REJECTS = 30;
|
||||
const NUMBER_OF_ACCEPTS = 17;
|
||||
const NUMBER_OF_REPEATS = 5;
|
||||
|
||||
let gCookieService;
|
||||
let gThirdPartyCookieProbe;
|
||||
|
||||
let gHistograms = {
|
||||
clear: function() {
|
||||
this.sitesAccepted.clear();
|
||||
this.requestsAccepted.clear();
|
||||
this.sitesRejected.clear();
|
||||
this.requestsRejected.clear();
|
||||
}
|
||||
};
|
||||
|
||||
function run_test() {
|
||||
do_print("Initializing environment");
|
||||
do_get_profile();
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
|
||||
gCookieService = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
|
||||
|
||||
do_print("Initializing ThirdPartyCookieProbe.jsm");
|
||||
gThirdPartyCookieProbe = new ThirdPartyCookieProbe();
|
||||
gThirdPartyCookieProbe.init();
|
||||
|
||||
do_print("Acquiring histograms");
|
||||
gHistograms.sitesAccepted =
|
||||
Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_SITES_ACCEPTED");
|
||||
gHistograms.sitesRejected =
|
||||
Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_SITES_BLOCKED"),
|
||||
gHistograms.requestsAccepted =
|
||||
Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_ATTEMPTS_ACCEPTED");
|
||||
gHistograms.requestsRejected =
|
||||
Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_ATTEMPTS_BLOCKED"),
|
||||
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function: try to set a cookie with the given document uri and referrer uri.
|
||||
*
|
||||
* @param obj An object with the following fields
|
||||
* - {string} request The uri of the request setting the cookie.
|
||||
* - {string} referrer The uri of the referrer for this request.
|
||||
*/
|
||||
function tryToSetCookie(obj) {
|
||||
let requestURI = Services.io.newURI(obj.request, null, null);
|
||||
let referrerURI = Services.io.newURI(obj.referrer, null, null);
|
||||
let requestChannel = Services.io.newChannelFromURI2(requestURI,
|
||||
null, // aLoadingNode
|
||||
Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
null, // aTriggeringPrincipal
|
||||
Ci.nsILoadInfo.SEC_NORMAL,
|
||||
Ci.nsIContentPolicy.TYPE_OTHER);
|
||||
gCookieService.setCookieString(referrerURI, null, "Is there a cookie in my jar?", requestChannel);
|
||||
}
|
||||
|
||||
function wait(ms) {
|
||||
let deferred = Promise.defer();
|
||||
do_timeout(ms, () => deferred.resolve());
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function oneTest(tld, flushUptime, check) {
|
||||
gHistograms.clear();
|
||||
do_print("Testing with tld " + tld);
|
||||
|
||||
do_print("Adding rejected entries");
|
||||
Services.prefs.setIntPref("network.cookie.cookieBehavior",
|
||||
1 /*reject third-party cookies*/);
|
||||
|
||||
for (let i = 0; i < NUMBER_OF_REJECTS; ++i) {
|
||||
for (let j = 0; j < NUMBER_OF_REPEATS; ++j) {
|
||||
for (let prefix of ["http://", "https://"]) {
|
||||
// Histogram sitesRejected should only count
|
||||
// NUMBER_OF_REJECTS entries.
|
||||
// Histogram requestsRejected should count
|
||||
// NUMBER_OF_REJECTS * NUMBER_OF_REPEATS * 2
|
||||
tryToSetCookie({
|
||||
request: prefix + "echelon" + tld,
|
||||
referrer: prefix + "domain" + i + tld
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do_print("Adding accepted entries");
|
||||
Services.prefs.setIntPref("network.cookie.cookieBehavior",
|
||||
0 /*accept third-party cookies*/);
|
||||
|
||||
for (let i = 0; i < NUMBER_OF_ACCEPTS; ++i) {
|
||||
for (let j = 0; j < NUMBER_OF_REPEATS; ++j) {
|
||||
for (let prefix of ["http://", "https://"]) {
|
||||
// Histogram sitesAccepted should only count
|
||||
// NUMBER_OF_ACCEPTS entries.
|
||||
// Histogram requestsAccepted should count
|
||||
// NUMBER_OF_ACCEPTS * NUMBER_OF_REPEATS * 2
|
||||
tryToSetCookie({
|
||||
request: prefix + "prism" + tld,
|
||||
referrer: prefix + "domain" + i + tld
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do_print("Checking that the histograms have not changed before ping()");
|
||||
do_check_eq(gHistograms.sitesAccepted.snapshot().sum, 0);
|
||||
do_check_eq(gHistograms.sitesRejected.snapshot().sum, 0);
|
||||
do_check_eq(gHistograms.requestsAccepted.snapshot().sum, 0);
|
||||
do_check_eq(gHistograms.requestsRejected.snapshot().sum, 0);
|
||||
|
||||
do_print("Checking that the resulting histograms are correct");
|
||||
if (flushUptime != null) {
|
||||
let now = Date.now();
|
||||
let before = now - flushUptime;
|
||||
gThirdPartyCookieProbe._latestFlush = before;
|
||||
gThirdPartyCookieProbe.flush(now);
|
||||
} else {
|
||||
gThirdPartyCookieProbe.flush();
|
||||
}
|
||||
check();
|
||||
}
|
||||
|
||||
add_task(function() {
|
||||
// To ensure that we work correctly with eTLD, test with several suffixes
|
||||
for (let tld of [".com", ".com.ar", ".co.uk", ".gouv.fr"]) {
|
||||
oneTest(tld, FLUSH_MILLISECONDS, function() {
|
||||
do_check_eq(gHistograms.sitesAccepted.snapshot().sum, NUMBER_OF_ACCEPTS * 2);
|
||||
do_check_eq(gHistograms.sitesRejected.snapshot().sum, NUMBER_OF_REJECTS * 2);
|
||||
do_check_eq(gHistograms.requestsAccepted.snapshot().sum, NUMBER_OF_ACCEPTS * NUMBER_OF_REPEATS * 2 * 2);
|
||||
do_check_eq(gHistograms.requestsRejected.snapshot().sum, NUMBER_OF_REJECTS * NUMBER_OF_REPEATS * 2 * 2);
|
||||
});
|
||||
}
|
||||
|
||||
// Check that things still work with default uptime management
|
||||
for (let tld of [".com", ".com.ar", ".co.uk", ".gouv.fr"]) {
|
||||
yield wait(1000); // Ensure that uptime is at least one second
|
||||
oneTest(tld, null, function() {
|
||||
do_check_true(gHistograms.sitesAccepted.snapshot().sum > 0);
|
||||
do_check_true(gHistograms.sitesRejected.snapshot().sum > 0);
|
||||
do_check_true(gHistograms.requestsAccepted.snapshot().sum > 0);
|
||||
do_check_true(gHistograms.requestsRejected.snapshot().sum > 0);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function() {
|
||||
gThirdPartyCookieProbe.dispose();
|
||||
});
|
||||
|
@ -38,13 +38,10 @@ skip-if = android_version == "18"
|
||||
[test_TelemetryPingBuildID.js]
|
||||
# Bug 1144395: crash on Android 4.3
|
||||
skip-if = android_version == "18"
|
||||
[test_ThirdPartyCookieProbe.js]
|
||||
skip-if = true # Bug 1149284
|
||||
[test_TelemetrySendOldPings.js]
|
||||
skip-if = debug == true || os == "android" # Disabled due to intermittent orange on Android
|
||||
[test_TelemetrySession.js]
|
||||
# Bug 1144395: crash on Android 4.3
|
||||
#skip-if = android_version == "18"
|
||||
skip-if = true # Bug 1149284
|
||||
skip-if = android_version == "18"
|
||||
[test_ThreadHangStats.js]
|
||||
run-sequentially = Bug 1046307, test can fail intermittently when CPU load is high
|
||||
|
@ -95,8 +95,22 @@ CssColor.COLORUNIT = {
|
||||
};
|
||||
|
||||
CssColor.prototype = {
|
||||
_colorUnit: null,
|
||||
|
||||
authored: null,
|
||||
|
||||
get colorUnit() {
|
||||
if (this._colorUnit === null) {
|
||||
let defaultUnit = Services.prefs.getCharPref(COLOR_UNIT_PREF);
|
||||
this._colorUnit = CssColor.COLORUNIT[defaultUnit];
|
||||
}
|
||||
return this._colorUnit;
|
||||
},
|
||||
|
||||
set colorUnit(unit) {
|
||||
this._colorUnit = unit;
|
||||
},
|
||||
|
||||
get hasAlpha() {
|
||||
if (!this.valid) {
|
||||
return false;
|
||||
@ -269,15 +283,31 @@ CssColor.prototype = {
|
||||
return this;
|
||||
},
|
||||
|
||||
nextColorUnit: function() {
|
||||
// Reorder the formats array to have the current format at the
|
||||
// front so we can cycle through.
|
||||
let formats = ["authored", "hex", "hsl", "rgb", "name"];
|
||||
let putOnEnd = formats.splice(0, formats.indexOf(this.colorUnit));
|
||||
formats = formats.concat(putOnEnd);
|
||||
let currentDisplayedColor = this[formats[0]];
|
||||
|
||||
for (let format of formats) {
|
||||
if (this[format].toLowerCase() !== currentDisplayedColor.toLowerCase()) {
|
||||
this.colorUnit = CssColor.COLORUNIT[format];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this.toString();
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a string representing a color of type defined in COLOR_UNIT_PREF.
|
||||
*/
|
||||
toString: function() {
|
||||
let color;
|
||||
let defaultUnit = Services.prefs.getCharPref(COLOR_UNIT_PREF);
|
||||
let unit = CssColor.COLORUNIT[defaultUnit];
|
||||
|
||||
switch(unit) {
|
||||
switch(this.colorUnit) {
|
||||
case CssColor.COLORUNIT.authored:
|
||||
color = this.authored;
|
||||
break;
|
||||
|
@ -77,6 +77,8 @@ loader.lazyGetter(this, "REGEX_ALL_CSS_PROPERTIES", function () {
|
||||
*/
|
||||
function OutputParser() {
|
||||
this.parsed = [];
|
||||
this.colorSwatches = new WeakMap();
|
||||
this._onSwatchMouseDown = this._onSwatchMouseDown.bind(this);
|
||||
}
|
||||
|
||||
exports.OutputParser = OutputParser;
|
||||
@ -396,12 +398,14 @@ OutputParser.prototype = {
|
||||
class: options.colorSwatchClass,
|
||||
style: "background-color:" + color
|
||||
});
|
||||
this.colorSwatches.set(swatch, colorObj);
|
||||
swatch.addEventListener("mousedown", this._onSwatchMouseDown, false);
|
||||
container.appendChild(swatch);
|
||||
}
|
||||
|
||||
if (options.defaultColorType) {
|
||||
color = colorObj.toString();
|
||||
container.dataset["color"] = color;
|
||||
container.dataset.color = color;
|
||||
}
|
||||
|
||||
let value = this._createNode("span", {
|
||||
@ -435,6 +439,21 @@ OutputParser.prototype = {
|
||||
this.parsed.push(container);
|
||||
},
|
||||
|
||||
_onSwatchMouseDown: function(event) {
|
||||
// Prevent text selection in the case of shift-click or double-click.
|
||||
event.preventDefault();
|
||||
|
||||
if (!event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
let swatch = event.target;
|
||||
let color = this.colorSwatches.get(swatch);
|
||||
let val = color.nextColorUnit();
|
||||
|
||||
swatch.nextElementSibling.textContent = val;
|
||||
},
|
||||
|
||||
/**
|
||||
* Append a URL to the output.
|
||||
*
|
||||
|
@ -21,10 +21,14 @@ let { TabActor } = require("devtools/server/actors/webbrowser");
|
||||
* The conection to the client.
|
||||
* @param chromeGlobal
|
||||
* The content script global holding |content| and |docShell| properties for a tab.
|
||||
* @param prefix
|
||||
* the prefix used in protocol to create IDs for each actor.
|
||||
* Used as ID identifying this particular TabActor from the parent process.
|
||||
*/
|
||||
function ContentActor(connection, chromeGlobal)
|
||||
function ContentActor(connection, chromeGlobal, prefix)
|
||||
{
|
||||
this._chromeGlobal = chromeGlobal;
|
||||
this._prefix = prefix;
|
||||
TabActor.call(this, connection, chromeGlobal);
|
||||
this.traits.reconfigure = false;
|
||||
this._sendForm = this._sendForm.bind(this);
|
||||
@ -49,32 +53,11 @@ Object.defineProperty(ContentActor.prototype, "title", {
|
||||
});
|
||||
|
||||
ContentActor.prototype.exit = function() {
|
||||
this._chromeGlobal.removeMessageListener("debug:form", this._sendForm);
|
||||
this._sendForm = null;
|
||||
TabActor.prototype.exit.call(this);
|
||||
};
|
||||
|
||||
// Override form just to rename this._tabActorPool to this._tabActorPool2
|
||||
// in order to prevent it to be cleaned on detach.
|
||||
// We have to keep tab actors alive as we keep the ContentActor
|
||||
// alive after detach and reuse it for multiple debug sessions.
|
||||
ContentActor.prototype.form = function () {
|
||||
let response = {
|
||||
"actor": this.actorID,
|
||||
"title": this.title,
|
||||
"url": this.url
|
||||
};
|
||||
|
||||
// Walk over tab actors added by extensions and add them to a new ActorPool.
|
||||
let actorPool = new ActorPool(this.conn);
|
||||
this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
|
||||
if (!actorPool.isEmpty()) {
|
||||
this._tabActorPool2 = actorPool;
|
||||
this.conn.addActorPool(this._tabActorPool2);
|
||||
if (this._sendForm) {
|
||||
this._chromeGlobal.removeMessageListener("debug:form", this._sendForm);
|
||||
this._sendForm = null;
|
||||
}
|
||||
|
||||
this._appendExtraActors(response);
|
||||
return response;
|
||||
return TabActor.prototype.exit.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -111,7 +111,7 @@ const DirectorRegistry = exports.DirectorRegistry = {
|
||||
|
||||
let gTrackedMessageManager = new Set();
|
||||
|
||||
exports.setupParentProcess = function setupParentProcess({mm, childID}) {
|
||||
exports.setupParentProcess = function setupParentProcess({mm, prefix}) {
|
||||
// prevents multiple subscriptions on the same messagemanager
|
||||
if (gTrackedMessageManager.has(mm)) {
|
||||
return;
|
||||
@ -121,7 +121,7 @@ exports.setupParentProcess = function setupParentProcess({mm, childID}) {
|
||||
// listen for director-script requests from the child process
|
||||
mm.addMessageListener("debug:director-registry-request", handleChildRequest);
|
||||
|
||||
DebuggerServer.once("disconnected-from-child:" + childID, handleMessageManagerDisconnected);
|
||||
DebuggerServer.once("disconnected-from-child:" + prefix, handleMessageManagerDisconnected);
|
||||
|
||||
/* parent process helpers */
|
||||
|
||||
|
@ -1917,7 +1917,7 @@ var WalkerActor = protocol.ActorClass({
|
||||
sugs.classes.delete(HIDDEN_CLASS);
|
||||
for (let [className, count] of sugs.classes) {
|
||||
if (className.startsWith(completing)) {
|
||||
result.push(["." + className, count, selectorState]);
|
||||
result.push(["." + CSS.escape(className), count, selectorState]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -1934,7 +1934,7 @@ var WalkerActor = protocol.ActorClass({
|
||||
}
|
||||
for (let [id, count] of sugs.ids) {
|
||||
if (id.startsWith(completing)) {
|
||||
result.push(["#" + id, count, selectorState]);
|
||||
result.push(["#" + CSS.escape(id), count, selectorState]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -196,6 +196,11 @@ RootActor.prototype = {
|
||||
this._parameters.onShutdown();
|
||||
}
|
||||
this._extraActors = null;
|
||||
this.conn = null;
|
||||
this._tabActorPool = null;
|
||||
this._globalActorPool = null;
|
||||
this._parameters = null;
|
||||
this._chromeActor = null;
|
||||
},
|
||||
|
||||
/* The 'listTabs' request and the 'tabListChanged' notification. */
|
||||
|
@ -213,9 +213,9 @@ function WebappsActor(aConnection) {
|
||||
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
// Keep reference of already created app actors.
|
||||
// key: app frame message manager, value: ContentActor's grip() value
|
||||
this._appActorsMap = new Map();
|
||||
// Keep reference of already connected app processes.
|
||||
// values: app frame message manager
|
||||
this._connectedApps = new Set();
|
||||
|
||||
this.conn = aConnection;
|
||||
this._uploads = [];
|
||||
@ -960,24 +960,33 @@ WebappsActor.prototype = {
|
||||
|
||||
// Only create a new actor, if we haven't already
|
||||
// instanciated one for this connection.
|
||||
let map = this._appActorsMap;
|
||||
let set = this._connectedApps;
|
||||
let mm = appFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
|
||||
.frameLoader
|
||||
.messageManager;
|
||||
let actor = map.get(mm);
|
||||
if (!actor) {
|
||||
if (!set.has(mm)) {
|
||||
let onConnect = actor => {
|
||||
map.set(mm, actor);
|
||||
set.add(mm);
|
||||
return { actor: actor };
|
||||
};
|
||||
let onDisconnect = mm => {
|
||||
map.delete(mm);
|
||||
set.delete(mm);
|
||||
};
|
||||
return DebuggerServer.connectToChild(this.conn, appFrame, onDisconnect)
|
||||
.then(onConnect);
|
||||
}
|
||||
|
||||
return { actor: actor };
|
||||
// We have to update the form as it may have changed
|
||||
// if we detached the TabActor
|
||||
let deferred = promise.defer();
|
||||
let onFormUpdate = msg => {
|
||||
mm.removeMessageListener("debug:form", onFormUpdate);
|
||||
deferred.resolve({ actor: msg.json });
|
||||
};
|
||||
mm.addMessageListener("debug:form", onFormUpdate);
|
||||
mm.sendAsyncMessage("debug:form");
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
watchApps: function () {
|
||||
|
@ -876,10 +876,7 @@ TabActor.prototype = {
|
||||
* Called when the actor is removed from the connection.
|
||||
*/
|
||||
disconnect: function BTA_disconnect() {
|
||||
this._detach();
|
||||
this._extraActors = null;
|
||||
this._styleSheetActors.clear();
|
||||
this._exited = true;
|
||||
this.exit();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -901,6 +898,14 @@ TabActor.prototype = {
|
||||
type: "tabDetached" });
|
||||
}
|
||||
|
||||
Object.defineProperty(this, "docShell", {
|
||||
value: null,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
this._extraActors = null;
|
||||
this._styleSheetActors.clear();
|
||||
|
||||
this._exited = true;
|
||||
},
|
||||
|
||||
@ -1221,11 +1226,6 @@ TabActor.prototype = {
|
||||
this._tabActorPool = null;
|
||||
}
|
||||
|
||||
Object.defineProperty(this, "docShell", {
|
||||
value: null,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
this._attached = false;
|
||||
return true;
|
||||
},
|
||||
@ -1822,7 +1822,10 @@ function RemoteBrowserTabActor(aConnection, aBrowser)
|
||||
|
||||
RemoteBrowserTabActor.prototype = {
|
||||
connect: function() {
|
||||
let connect = DebuggerServer.connectToChild(this._conn, this._browser);
|
||||
let onDestroy = () => {
|
||||
this._form = null;
|
||||
};
|
||||
let connect = DebuggerServer.connectToChild(this._conn, this._browser, onDestroy);
|
||||
return connect.then(form => {
|
||||
this._form = form;
|
||||
return this;
|
||||
@ -1835,15 +1838,21 @@ RemoteBrowserTabActor.prototype = {
|
||||
},
|
||||
|
||||
update: function() {
|
||||
let deferred = promise.defer();
|
||||
let onFormUpdate = msg => {
|
||||
this._mm.removeMessageListener("debug:form", onFormUpdate);
|
||||
this._form = msg.json;
|
||||
deferred.resolve(this);
|
||||
};
|
||||
this._mm.addMessageListener("debug:form", onFormUpdate);
|
||||
this._mm.sendAsyncMessage("debug:form");
|
||||
return deferred.promise;
|
||||
// If the child happens to be crashed/close/detach, it won't have _form set,
|
||||
// so only request form update if some code is still listening on the other side.
|
||||
if (this._form) {
|
||||
let deferred = promise.defer();
|
||||
let onFormUpdate = msg => {
|
||||
this._mm.removeMessageListener("debug:form", onFormUpdate);
|
||||
this._form = msg.json;
|
||||
deferred.resolve(this);
|
||||
};
|
||||
this._mm.addMessageListener("debug:form", onFormUpdate);
|
||||
this._mm.sendAsyncMessage("debug:form");
|
||||
return deferred.promise;
|
||||
} else {
|
||||
return this.connect();
|
||||
}
|
||||
},
|
||||
|
||||
form: function() {
|
||||
|
@ -17,10 +17,6 @@ let chromeGlobal = this;
|
||||
const { dumpn } = DevToolsUtils;
|
||||
const { DebuggerServer, ActorPool } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||
|
||||
if (!DebuggerServer.childID) {
|
||||
DebuggerServer.childID = 1;
|
||||
}
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
|
||||
@ -44,17 +40,16 @@ let chromeGlobal = this;
|
||||
|
||||
let mm = msg.target;
|
||||
let prefix = msg.data.prefix;
|
||||
let id = DebuggerServer.childID++;
|
||||
|
||||
let conn = DebuggerServer.connectToParent(prefix, mm);
|
||||
connections.set(id, conn);
|
||||
connections.set(prefix, conn);
|
||||
|
||||
let actor = new DebuggerServer.ContentActor(conn, chromeGlobal);
|
||||
let actor = new DebuggerServer.ContentActor(conn, chromeGlobal, prefix);
|
||||
let actorPool = new ActorPool(conn);
|
||||
actorPool.addActor(actor);
|
||||
conn.addActorPool(actorPool);
|
||||
|
||||
sendAsyncMessage("debug:actor", {actor: actor.form(), childID: id});
|
||||
sendAsyncMessage("debug:actor", {actor: actor.form(), prefix: prefix});
|
||||
});
|
||||
|
||||
addMessageListener("debug:connect", onConnect);
|
||||
@ -95,11 +90,11 @@ let chromeGlobal = this;
|
||||
// Call DebuggerServerConnection.close to destroy all child actors
|
||||
// (It should end up calling DebuggerServerConnection.onClosed
|
||||
// that would actually cleanup all actor pools)
|
||||
let childID = msg.data.childID;
|
||||
let conn = connections.get(childID);
|
||||
let prefix = msg.data.prefix;
|
||||
let conn = connections.get(prefix);
|
||||
if (conn) {
|
||||
conn.close();
|
||||
connections.delete(childID);
|
||||
connections.delete(prefix);
|
||||
}
|
||||
});
|
||||
addMessageListener("debug:disconnect", onDisconnect);
|
||||
|
@ -76,7 +76,7 @@ connected to the child process as parameter, e.g. in the **director-registry**:
|
||||
|
||||
let gTrackedMessageManager = new Set();
|
||||
|
||||
exports.setupParentProcess = function setupParentProcess({ mm, childID }) {
|
||||
exports.setupParentProcess = function setupParentProcess({ mm, prefix }) {
|
||||
if (gTrackedMessageManager.has(mm)) { return; }
|
||||
gTrackedMessageManager.add(mm);
|
||||
|
||||
@ -84,7 +84,7 @@ exports.setupParentProcess = function setupParentProcess({ mm, childID }) {
|
||||
mm.addMessageListener("debug:director-registry-request", handleChildRequest);
|
||||
|
||||
// time to unsubscribe from the disconnected message manager
|
||||
DebuggerServer.once("disconnected-from-child:" + childID, handleMessageManagerDisconnected);
|
||||
DebuggerServer.once("disconnected-from-child:" + prefix, handleMessageManagerDisconnected);
|
||||
|
||||
function handleMessageManagerDisconnected(evt, { mm: disconnected_mm }) {
|
||||
...
|
||||
@ -109,8 +109,8 @@ In the child process:
|
||||
In the parent process:
|
||||
- The DebuggerServer receives the DebuggerServer.setupInParent request
|
||||
- it tries to load the required module
|
||||
- it tries to call the **mod[setupParent]** method with the frame message manager and the childID
|
||||
in the json parameter **{ mm, childID }**
|
||||
- it tries to call the **mod[setupParent]** method with the frame message manager and the prefix
|
||||
in the json parameter **{ mm, prefix }**
|
||||
- the module setupParent helper use the mm to subscribe the messagemanager events
|
||||
- the module setupParent helper use the DebuggerServer object to subscribe *once* the
|
||||
**"disconnected-from-child:CHILDID"** event (needed to unsubscribe the messagemanager events)
|
||||
**"disconnected-from-child:PREFIX"** event (needed to unsubscribe the messagemanager events)
|
||||
|
@ -809,13 +809,15 @@ var DebuggerServer = {
|
||||
* The debugger server connection to use.
|
||||
* @param nsIDOMElement aFrame
|
||||
* The browser element that holds the child process.
|
||||
* @param function [aOnDisconnect]
|
||||
* Optional function to invoke when the child is disconnected.
|
||||
* @param function [aOnDestroy]
|
||||
* Optional function to invoke when the child process closes
|
||||
* or the connection shuts down. (Need to forget about the
|
||||
* related TabActor)
|
||||
* @return object
|
||||
* A promise object that is resolved once the connection is
|
||||
* established.
|
||||
*/
|
||||
connectToChild: function(aConnection, aFrame, aOnDisconnect) {
|
||||
connectToChild: function(aConnection, aFrame, aOnDestroy) {
|
||||
let deferred = defer();
|
||||
|
||||
let mm = aFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader
|
||||
@ -824,7 +826,6 @@ var DebuggerServer = {
|
||||
|
||||
let actor, childTransport;
|
||||
let prefix = aConnection.allocID("child");
|
||||
let childID = null;
|
||||
let netMonitor = null;
|
||||
|
||||
// provides hook to actor modules that need to exchange messages
|
||||
@ -841,7 +842,7 @@ var DebuggerServer = {
|
||||
return false;
|
||||
}
|
||||
|
||||
m[setupParent]({ mm: mm, childID: childID });
|
||||
m[setupParent]({ mm: mm, prefix: prefix });
|
||||
|
||||
return true;
|
||||
} catch(e) {
|
||||
@ -855,10 +856,11 @@ var DebuggerServer = {
|
||||
mm.addMessageListener("debug:setup-in-parent", onSetupInParent);
|
||||
|
||||
let onActorCreated = DevToolsUtils.makeInfallible(function (msg) {
|
||||
if (msg.json.prefix != prefix) {
|
||||
return;
|
||||
}
|
||||
mm.removeMessageListener("debug:actor", onActorCreated);
|
||||
|
||||
childID = msg.json.childID;
|
||||
|
||||
// Pipe Debugger message from/to parent/child via the message manager
|
||||
childTransport = new ChildDebuggerTransport(mm, prefix);
|
||||
childTransport.hooks = {
|
||||
@ -882,70 +884,65 @@ var DebuggerServer = {
|
||||
}).bind(this);
|
||||
mm.addMessageListener("debug:actor", onActorCreated);
|
||||
|
||||
let onMessageManagerClose = DevToolsUtils.makeInfallible(function (subject, topic, data) {
|
||||
if (subject == mm) {
|
||||
Services.obs.removeObserver(onMessageManagerClose, topic);
|
||||
let destroy = DevToolsUtils.makeInfallible(function () {
|
||||
// provides hook to actor modules that need to exchange messages
|
||||
// between e10s parent and child processes
|
||||
DebuggerServer.emit("disconnected-from-child:" + prefix, { mm: mm, prefix: prefix });
|
||||
|
||||
// provides hook to actor modules that need to exchange messages
|
||||
// between e10s parent and child processes
|
||||
this.emit("disconnected-from-child:" + childID, { mm: mm, childID: childID });
|
||||
|
||||
mm.removeMessageListener("debug:setup-in-parent", onSetupInParent);
|
||||
|
||||
if (childTransport) {
|
||||
// If we have a child transport, the actor has already
|
||||
// been created. We need to stop using this message manager.
|
||||
childTransport.close();
|
||||
childTransport = null;
|
||||
aConnection.cancelForwarding(prefix);
|
||||
|
||||
// ... and notify the child process to clean the tab actors.
|
||||
mm.sendAsyncMessage("debug:disconnect", { childID: childID });
|
||||
} else {
|
||||
// Otherwise, the app has been closed before the actor
|
||||
// had a chance to be created, so we are not able to create
|
||||
// the actor.
|
||||
deferred.resolve(null);
|
||||
}
|
||||
if (actor) {
|
||||
// The ContentActor within the child process doesn't necessary
|
||||
// have to time to uninitialize itself when the app is closed/killed.
|
||||
// So ensure telling the client that the related actor is detached.
|
||||
aConnection.send({ from: actor.actor, type: "tabDetached" });
|
||||
actor = null;
|
||||
}
|
||||
|
||||
if (netMonitor) {
|
||||
netMonitor.destroy();
|
||||
netMonitor = null;
|
||||
}
|
||||
|
||||
if (aOnDisconnect) {
|
||||
aOnDisconnect(mm);
|
||||
}
|
||||
}
|
||||
}).bind(this);
|
||||
Services.obs.addObserver(onMessageManagerClose,
|
||||
"message-manager-close", false);
|
||||
|
||||
events.once(aConnection, "closed", () => {
|
||||
if (childTransport) {
|
||||
// When the client disconnects, we have to unplug the dedicated
|
||||
// ChildDebuggerTransport...
|
||||
// If we have a child transport, the actor has already
|
||||
// been created. We need to stop using this message manager.
|
||||
childTransport.close();
|
||||
childTransport = null;
|
||||
aConnection.cancelForwarding(prefix);
|
||||
|
||||
// ... and notify the child process to clean the tab actors.
|
||||
mm.sendAsyncMessage("debug:disconnect", { childID: childID });
|
||||
|
||||
if (netMonitor) {
|
||||
netMonitor.destroy();
|
||||
netMonitor = null;
|
||||
}
|
||||
mm.sendAsyncMessage("debug:disconnect", { prefix: prefix });
|
||||
} else {
|
||||
// Otherwise, the app has been closed before the actor
|
||||
// had a chance to be created, so we are not able to create
|
||||
// the actor.
|
||||
deferred.resolve(null);
|
||||
}
|
||||
if (actor) {
|
||||
// The ContentActor within the child process doesn't necessary
|
||||
// have time to uninitialize itself when the app is closed/killed.
|
||||
// So ensure telling the client that the related actor is detached.
|
||||
aConnection.send({ from: actor.actor, type: "tabDetached" });
|
||||
actor = null;
|
||||
}
|
||||
|
||||
if (netMonitor) {
|
||||
netMonitor.destroy();
|
||||
netMonitor = null;
|
||||
}
|
||||
|
||||
if (aOnDestroy) {
|
||||
aOnDestroy(mm);
|
||||
}
|
||||
|
||||
// Cleanup all listeners
|
||||
Services.obs.removeObserver(onMessageManagerClose, "message-manager-close");
|
||||
mm.removeMessageListener("debug:setup-in-parent", onSetupInParent);
|
||||
if (!actor) {
|
||||
mm.removeMessageListener("debug:actor", onActorCreated);
|
||||
}
|
||||
events.off(aConnection, "closed", destroy);
|
||||
});
|
||||
|
||||
// Listen for app process exit
|
||||
let onMessageManagerClose = function (subject, topic, data) {
|
||||
if (subject == mm) {
|
||||
destroy();
|
||||
}
|
||||
};
|
||||
Services.obs.addObserver(onMessageManagerClose,
|
||||
"message-manager-close", false);
|
||||
|
||||
// Listen for connection close to cleanup things
|
||||
// when user unplug the device or we lose the connection somehow.
|
||||
events.on(aConnection, "closed", destroy);
|
||||
|
||||
mm.sendAsyncMessage("debug:connect", { prefix: prefix });
|
||||
|
||||
return deferred.promise;
|
||||
@ -1558,6 +1555,8 @@ DebuggerServerConnection.prototype = {
|
||||
this._extraPools.map(function(p) { p.cleanup(); });
|
||||
this._extraPools = null;
|
||||
|
||||
this.rootActor = null;
|
||||
this._transport = null;
|
||||
DebuggerServer._connectionClosed(this);
|
||||
},
|
||||
|
||||
|
@ -90,7 +90,7 @@ function objEquiv(a, b) {
|
||||
return false;
|
||||
}
|
||||
// An identical 'prototype' property.
|
||||
if (a.prototype !== b.prototype) {
|
||||
if ((a.prototype || undefined) != (b.prototype || undefined)) {
|
||||
return false;
|
||||
}
|
||||
// Object.keys may be broken through screwy arguments passing. Converting to
|
||||
|
@ -623,6 +623,7 @@ function logTestInfo(aText, aCaller) {
|
||||
*/
|
||||
function debugDump(aText, aCaller) {
|
||||
if (DEBUG_AUS_TEST) {
|
||||
logTestInfo(aText, aCaller);
|
||||
let caller = aCaller ? aCaller : Components.stack.caller;
|
||||
logTestInfo(aText, caller);
|
||||
}
|
||||
}
|
||||
|
@ -1000,7 +1000,14 @@ function doTestFinish() {
|
||||
if (gPassed === undefined) {
|
||||
gPassed = true;
|
||||
}
|
||||
do_test_finished();
|
||||
if (DEBUG_AUS_TEST) {
|
||||
// This prevents do_print errors from being printed by the xpcshell test
|
||||
// harness due to nsUpdateService.js logging to the console when the
|
||||
// app.update.log preference is true.
|
||||
Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, false);
|
||||
gAUS.observe(null, "nsPref:changed", PREF_APP_UPDATE_LOG);
|
||||
}
|
||||
do_execute_soon(do_test_finished);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1601,8 +1608,9 @@ function runUpdate(aExpectedExitValue, aExpectedStatus, aCallback) {
|
||||
let updateLog = getUpdatesPatchDir();
|
||||
updateLog.append(FILE_UPDATE_LOG);
|
||||
// xpcshell tests won't display the entire contents so log each line.
|
||||
let contents = readFileBytes(updateLog).replace(/\r\n/g, "\n");
|
||||
let aryLogContents = contents.split("\n");
|
||||
let updateLogContents = readFileBytes(updateLog).replace(/\r\n/g, "\n");
|
||||
updateLogContents = replaceLogPaths(updateLogContents);
|
||||
let aryLogContents = updateLogContents.split("\n");
|
||||
logTestInfo("contents of " + updateLog.path + ":");
|
||||
aryLogContents.forEach(function RU_LC_FE(aLine) {
|
||||
logTestInfo(aLine);
|
||||
@ -2181,8 +2189,9 @@ function runUpdateUsingService(aInitialStatus, aExpectedStatus, aCheckSvcLog) {
|
||||
let updateLog = getUpdatesPatchDir();
|
||||
updateLog.append(FILE_UPDATE_LOG);
|
||||
// xpcshell tests won't display the entire contents so log each line.
|
||||
let contents = readFileBytes(updateLog).replace(/\r\n/g, "\n");
|
||||
let aryLogContents = contents.split("\n");
|
||||
let updateLogContents = readFileBytes(updateLog).replace(/\r\n/g, "\n");
|
||||
updateLogContents = replaceLogPaths(updateLogContents);
|
||||
let aryLogContents = updateLogContents.split("\n");
|
||||
logTestInfo("contents of " + updateLog.path + ":");
|
||||
aryLogContents.forEach(function RUUS_TC_LC_FE(aLine) {
|
||||
logTestInfo(aLine);
|
||||
@ -2449,6 +2458,41 @@ function createUpdaterINI(aIsExeAsync) {
|
||||
writeFile(updaterIni, updaterIniContents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that replaces the common part of paths in the update log's
|
||||
* contents with <test_dir_path> for paths to the the test directory and
|
||||
* <update_dir_path> for paths to the update directory. This is needed since
|
||||
* Assert.equal will truncate what it prints to the xpcshell log file.
|
||||
*
|
||||
* @param aLogContents
|
||||
* The update log file's contents.
|
||||
* @return the log contents with the paths replaced.
|
||||
*/
|
||||
function replaceLogPaths(aLogContents) {
|
||||
let logContents = aLogContents;
|
||||
// Remove the majority of the path up to the test directory. This is needed
|
||||
// since Assert.equal won't print long strings to the test logs.
|
||||
let testDirPath = do_get_file(gTestID, false).path;
|
||||
if (IS_WIN) {
|
||||
// Replace \\ with \\\\ so the regexp works.
|
||||
testDirPath = testDirPath.replace(/\\/g, "\\\\");
|
||||
}
|
||||
logContents = logContents.replace(new RegExp(testDirPath, "g"),
|
||||
"<test_dir_path>/" + gTestID);
|
||||
let updatesDirPath = getMockUpdRootD().path;
|
||||
if (IS_WIN) {
|
||||
// Replace \\ with \\\\ so the regexp works.
|
||||
updatesDirPath = updatesDirPath.replace(/\\/g, "\\\\");
|
||||
}
|
||||
logContents = logContents.replace(new RegExp(updatesDirPath, "g"),
|
||||
"<update_dir_path>/" + gTestID);
|
||||
if (IS_WIN) {
|
||||
// Replace \ with /
|
||||
logContents = logContents.replace(/\\/g, "/");
|
||||
}
|
||||
return logContents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for updater binary tests for verifying the contents of the
|
||||
* update log after a successful update.
|
||||
@ -2464,12 +2508,13 @@ function checkUpdateLogContents(aCompareLogFile, aExcludeDistributionDir) {
|
||||
// Sorting on Linux is different so skip checking the logs for now.
|
||||
return;
|
||||
}
|
||||
|
||||
let updateLog = getUpdatesPatchDir();
|
||||
updateLog.append(FILE_UPDATE_LOG);
|
||||
let updateLogContents = readFileBytes(updateLog);
|
||||
|
||||
// The channel-prefs.js is defined in gTestFilesCommon which will always be
|
||||
// located to the end of gTestFiles.
|
||||
// located to the end of gTestFiles when it is present.
|
||||
if (gTestFiles.length > 1 &&
|
||||
gTestFiles[gTestFiles.length - 1].fileName == "channel-prefs.js" &&
|
||||
!gTestFiles[gTestFiles.length - 1].originalContents) {
|
||||
@ -2515,9 +2560,7 @@ function checkUpdateLogContents(aCompareLogFile, aExcludeDistributionDir) {
|
||||
updateLogContents = updateLogContents.replace(/\n+/g, "\n");
|
||||
// Remove leading and trailing newlines
|
||||
updateLogContents = updateLogContents.replace(/^\n|\n$/g, "");
|
||||
// The update log when running the service tests sometimes starts with data
|
||||
// from the previous launch of the updater.
|
||||
updateLogContents = updateLogContents.replace(/^calling QuitProgressUI\n[^\n]*\nUPDATE TYPE/g, "UPDATE TYPE");
|
||||
updateLogContents = replaceLogPaths(updateLogContents);
|
||||
|
||||
let compareLogContents = "";
|
||||
if (aCompareLogFile) {
|
||||
@ -2583,8 +2626,9 @@ function checkUpdateLogContains(aCheckString) {
|
||||
let updateLog = getUpdatesPatchDir();
|
||||
updateLog.append(FILE_UPDATE_LOG);
|
||||
let updateLogContents = readFileBytes(updateLog);
|
||||
updateLogContents = replaceLogPaths(updateLogContents);
|
||||
Assert.notEqual(updateLogContents.indexOf(aCheckString), -1,
|
||||
"the update log contents" + MSG_SHOULD_EQUAL + ", value: " +
|
||||
"the update log contents should contain value: " +
|
||||
aCheckString);
|
||||
}
|
||||
|
||||
@ -2848,6 +2892,8 @@ function checkCallbackAppLog() {
|
||||
gTimeoutRuns++;
|
||||
if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
|
||||
logTestInfo("callback log contents are not correct");
|
||||
// This file doesn't contain full paths so there is no need to call
|
||||
// replaceLogPaths.
|
||||
let aryLog = logContents.split("\n");
|
||||
let aryCompare = expectedLogContents.split("\n");
|
||||
// Pushing an empty string to both arrays makes it so either array's length
|
||||
|
@ -224,7 +224,6 @@ function finishCheckUpdateFinished() {
|
||||
checkAppBundleModTime();
|
||||
checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
|
||||
checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
|
||||
checkCallbackAppLog();
|
||||
|
||||
standardInit();
|
||||
|
||||
|
@ -202,7 +202,6 @@ function finishCheckUpdateApplied() {
|
||||
gSwitchApp = true;
|
||||
checkUpdateLogContents();
|
||||
gSwitchApp = false;
|
||||
checkCallbackAppLog();
|
||||
|
||||
standardInit();
|
||||
|
||||
|
@ -106,7 +106,6 @@ function finishCheckUpdateFinished() {
|
||||
checkAppBundleModTime();
|
||||
checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
|
||||
checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
|
||||
checkCallbackAppLog();
|
||||
|
||||
standardInit();
|
||||
|
||||
|
@ -44,6 +44,7 @@ skip-if = os != 'win'
|
||||
skip-if = os != 'win'
|
||||
[marFileLockedStageFailureComplete_win.js]
|
||||
skip-if = os != 'win'
|
||||
run-sequentially = Bug 1156446
|
||||
[marFileLockedStageFailurePartial_win.js]
|
||||
skip-if = os != 'win'
|
||||
[marFileLockedFallbackStageFailureComplete_win.js]
|
||||
|
@ -228,7 +228,6 @@ function finishCheckUpdateFinished() {
|
||||
checkAppBundleModTime();
|
||||
checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
|
||||
checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
|
||||
checkCallbackAppLog();
|
||||
|
||||
standardInit();
|
||||
|
||||
|
@ -206,7 +206,6 @@ function finishCheckUpdateApplied() {
|
||||
gSwitchApp = true;
|
||||
checkUpdateLogContents();
|
||||
gSwitchApp = false;
|
||||
checkCallbackAppLog();
|
||||
|
||||
standardInit();
|
||||
|
||||
|
@ -110,7 +110,6 @@ function finishCheckUpdateFinished() {
|
||||
checkAppBundleModTime();
|
||||
checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
|
||||
checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
|
||||
checkCallbackAppLog();
|
||||
|
||||
standardInit();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user