Merge fx-team to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-08-07 17:24:52 -04:00
commit d4a25b4a60
81 changed files with 2170 additions and 1201 deletions

View File

@ -144,6 +144,7 @@
@BINPATH@/components/content_html.xpt
@BINPATH@/components/content_xslt.xpt
@BINPATH@/components/cookie.xpt
@BINPATH@/components/devtools_security.xpt
@BINPATH@/components/directory.xpt
@BINPATH@/components/diskspacewatcher.xpt
@BINPATH@/components/docshell.xpt

View File

@ -60,14 +60,6 @@ ul {
}
}
/* common.css overrides */
button {
font-size: 1em;
min-width: 150px;
}
/* Pressing the retry button will cause the cursor to flicker from a pointer to
* not-allowed. Override the disabled cursor behaviour since we will never show
* the button disabled as the initial state. */

View File

@ -3,16 +3,30 @@
"use strict";
let notificationObserver;
registerCleanupFunction(function() {
Services.prefs.clearUserPref("browser.fixup.domainwhitelist.localhost");
if (notificationObserver) {
notificationObserver.disconnect();
}
});
function promiseNotificationForTab(value, expected, tab=gBrowser.selectedTab) {
let deferred = Promise.defer();
let notificationBox = gBrowser.getNotificationBox(tab.linkedBrowser);
if (expected) {
waitForCondition(() => notificationBox.getNotificationWithValue(value) !== null,
deferred.resolve, "Were expecting to get a notification");
let checkForNotification = function() {
if (notificationBox.getNotificationWithValue(value)) {
notificationObserver.disconnect();
notificationObserver = null;
deferred.resolve();
}
}
if (notificationObserver) {
notificationObserver.disconnect();
}
notificationObserver = new MutationObserver(checkForNotification);
notificationObserver.observe(notificationBox, {childList: true});
} else {
setTimeout(() => {
is(notificationBox.getNotificationWithValue(value), null, "We are expecting to not get a notification");

View File

@ -1819,11 +1819,11 @@
this._setupLink(linkLabel, action.detailsLink);
this._primaryButton.label = gNavigatorBundle.getString(button1.label);
this._primaryButton.accesskey = gNavigatorBundle.getString(button1.accesskey);
this._primaryButton.accessKey = gNavigatorBundle.getString(button1.accesskey);
this._primaryButton.setAttribute("action", button1.action);
this._secondaryButton.label = gNavigatorBundle.getString(button2.label);
this._secondaryButton.accesskey = gNavigatorBundle.getString(button2.accesskey);
this._secondaryButton.accessKey = gNavigatorBundle.getString(button2.accesskey);
this._secondaryButton.setAttribute("action", button2.action);
if (button1.default) {
this._primaryButton.setAttribute("default", "true");

View File

@ -11,8 +11,10 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/loop/MozLoopService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "hookWindowCloseForPanelClose",
"resource://gre/modules/MozSocialAPI.jsm");
"resource://gre/modules/MozSocialAPI.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper");
this.EXPORTED_SYMBOLS = ["injectLoopAPI"];
/**
@ -225,6 +227,19 @@ function injectLoopAPI(targetWindow) {
});
}
},
/**
* Copies passed string onto the system clipboard.
*
* @param {String} str The string to copy
*/
copyString: {
enumerable: true,
writable: true,
value: function(str) {
clipboardHelper.copyString(str);
}
}
};
let contentObj = Cu.createObjectIn(targetWindow);

View File

@ -152,11 +152,17 @@ loop.panel = (function(_, mozL10n) {
});
var CallUrlResult = React.createClass({displayName: 'CallUrlResult',
propTypes: {
callUrl: React.PropTypes.string,
notifier: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {
pending: false,
callUrl: ''
copied: false,
callUrl: this.props.callUrl || ""
};
},
@ -184,7 +190,7 @@ loop.panel = (function(_, mozL10n) {
if (err) {
this.props.notifier.errorL10n("unable_retrieve_url");
this.setState({pending: false});
this.setState(this.getInitialState());
} else {
try {
var callUrl = new window.URL(callUrlData.callUrl);
@ -194,43 +200,57 @@ loop.panel = (function(_, mozL10n) {
callUrl.pathname.split('/').pop();
navigator.mozLoop.setLoopCharPref('loopToken', token);
this.setState({pending: false, callUrl: callUrl.href});
this.setState({pending: false, copied: false, callUrl: callUrl.href});
} catch(e) {
console.log(e);
this.props.notifier.errorL10n("unable_retrieve_url");
this.setState({pending: false});
this.setState(this.getInitialState());
}
}
},
_generateMailto: function() {
_generateMailTo: function() {
return encodeURI([
"mailto:?subject=" + __("share_email_subject") + "&",
"body=" + __("share_email_body", {callUrl: this.state.callUrl})
].join(""));
},
handleEmailButtonClick: function(event) {
// Note: side effect
document.location = event.target.dataset.mailto;
},
handleCopyButtonClick: function(event) {
// XXX the mozLoop object should be passed as a prop, to ease testing and
// using a fake implementation in UI components showcase.
navigator.mozLoop.copyString(this.state.callUrl);
this.setState({copied: true});
},
render: function() {
// XXX setting elem value from a state (in the callUrl input)
// makes it immutable ie read only but that is fine in our case.
// readOnly attr will suppress a warning regarding this issue
// from the react lib.
var cx = React.addons.classSet;
var inputCSSClass = {
"pending": this.state.pending,
"callUrl": !this.state.pending
};
return (
PanelLayout({summary: __("share_link_header_text")},
React.DOM.div({className: "invite"},
React.DOM.input({type: "url", value: this.state.callUrl, readOnly: "true",
className: cx(inputCSSClass)}),
React.DOM.a({className: cx({btn: true, hide: !this.state.callUrl}),
href: this._generateMailto()},
React.DOM.span(null,
__("share_button")
)
)
className: cx({pending: this.state.pending})}),
React.DOM.p({className: "button-group url-actions"},
React.DOM.button({className: "btn btn-email", disabled: !this.state.callUrl,
onClick: this.handleEmailButtonClick,
'data-mailto': this._generateMailTo()},
__("share_button")
),
React.DOM.button({className: "btn btn-copy", disabled: !this.state.callUrl,
onClick: this.handleCopyButtonClick},
this.state.copied ? __("copied_url_button") :
__("copy_url_button")
)
)
)
)
);
@ -243,14 +263,17 @@ loop.panel = (function(_, mozL10n) {
var PanelView = React.createClass({displayName: 'PanelView',
propTypes: {
notifier: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
client: React.PropTypes.object.isRequired,
// Mostly used for UI components showcase and unit tests
callUrl: React.PropTypes.string
},
render: function() {
return (
React.DOM.div(null,
CallUrlResult({client: this.props.client,
notifier: this.props.notifier}),
notifier: this.props.notifier,
callUrl: this.props.callUrl}),
ToSView(null),
AvailabilityDropdown(null)
)

View File

@ -152,11 +152,17 @@ loop.panel = (function(_, mozL10n) {
});
var CallUrlResult = React.createClass({
propTypes: {
callUrl: React.PropTypes.string,
notifier: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {
pending: false,
callUrl: ''
copied: false,
callUrl: this.props.callUrl || ""
};
},
@ -184,7 +190,7 @@ loop.panel = (function(_, mozL10n) {
if (err) {
this.props.notifier.errorL10n("unable_retrieve_url");
this.setState({pending: false});
this.setState(this.getInitialState());
} else {
try {
var callUrl = new window.URL(callUrlData.callUrl);
@ -194,43 +200,57 @@ loop.panel = (function(_, mozL10n) {
callUrl.pathname.split('/').pop();
navigator.mozLoop.setLoopCharPref('loopToken', token);
this.setState({pending: false, callUrl: callUrl.href});
this.setState({pending: false, copied: false, callUrl: callUrl.href});
} catch(e) {
console.log(e);
this.props.notifier.errorL10n("unable_retrieve_url");
this.setState({pending: false});
this.setState(this.getInitialState());
}
}
},
_generateMailto: function() {
_generateMailTo: function() {
return encodeURI([
"mailto:?subject=" + __("share_email_subject") + "&",
"body=" + __("share_email_body", {callUrl: this.state.callUrl})
].join(""));
},
handleEmailButtonClick: function(event) {
// Note: side effect
document.location = event.target.dataset.mailto;
},
handleCopyButtonClick: function(event) {
// XXX the mozLoop object should be passed as a prop, to ease testing and
// using a fake implementation in UI components showcase.
navigator.mozLoop.copyString(this.state.callUrl);
this.setState({copied: true});
},
render: function() {
// XXX setting elem value from a state (in the callUrl input)
// makes it immutable ie read only but that is fine in our case.
// readOnly attr will suppress a warning regarding this issue
// from the react lib.
var cx = React.addons.classSet;
var inputCSSClass = {
"pending": this.state.pending,
"callUrl": !this.state.pending
};
return (
<PanelLayout summary={__("share_link_header_text")}>
<div className="invite">
<input type="url" value={this.state.callUrl} readOnly="true"
className={cx(inputCSSClass)} />
<a className={cx({btn: true, hide: !this.state.callUrl})}
href={this._generateMailto()}>
<span>
{__("share_button")}
</span>
</a>
className={cx({pending: this.state.pending})} />
<p className="button-group url-actions">
<button className="btn btn-email" disabled={!this.state.callUrl}
onClick={this.handleEmailButtonClick}
data-mailto={this._generateMailTo()}>
{__("share_button")}
</button>
<button className="btn btn-copy" disabled={!this.state.callUrl}
onClick={this.handleCopyButtonClick}>
{this.state.copied ? __("copied_url_button") :
__("copy_url_button")}
</button>
</p>
</div>
</PanelLayout>
);
@ -243,14 +263,17 @@ loop.panel = (function(_, mozL10n) {
var PanelView = React.createClass({
propTypes: {
notifier: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
client: React.PropTypes.object.isRequired,
// Mostly used for UI components showcase and unit tests
callUrl: React.PropTypes.string
},
render: function() {
return (
<div>
<CallUrlResult client={this.props.client}
notifier={this.props.notifier} />
notifier={this.props.notifier}
callUrl={this.props.callUrl} />
<ToSView />
<AvailabilityDropdown />
</div>

View File

@ -320,6 +320,7 @@
.feedback label {
display: block;
line-height: 1.5em;
}
.feedback form input[type="radio"] {

View File

@ -86,15 +86,17 @@
margin-top: 10px;
}
.share .action .btn:hover {
.share > .action .btn:hover {
background-color: #008ACB;
border: 1px solid #008ACB;
}
.share .action .btn span {
margin-top: 2px;
font-size: 12px;
display: inline-block;
.share > .action > .invite > .url-actions {
margin: 0 0 5px;
}
.share > .action > .invite > .url-actions > .btn:first-child {
-moz-margin-end: 1em;
}
/* Specific cases */

View File

@ -12,7 +12,6 @@ loop.shared.views = (function(_, OT, l10n) {
"use strict";
var sharedModels = loop.shared.models;
var __ = l10n.get;
var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
/**
@ -134,7 +133,7 @@ loop.shared.views = (function(_, OT, l10n) {
var prefix = this.props.enabled ? "mute" : "unmute";
var suffix = "button_title";
var msgId = [prefix, this.props.scope, this.props.type, suffix].join("_");
return __(msgId);
return l10n.get(msgId);
},
render: function() {
@ -184,7 +183,7 @@ loop.shared.views = (function(_, OT, l10n) {
React.DOM.ul({className: "conversation-toolbar"},
React.DOM.li(null, React.DOM.button({className: "btn btn-hangup",
onClick: this.handleClickHangup,
title: __("hangup_button_title")})),
title: l10n.get("hangup_button_title")})),
React.DOM.li(null, MediaControlButton({action: this.handleToggleVideo,
enabled: this.props.video.enabled,
scope: "local", type: "video"})),
@ -371,7 +370,7 @@ loop.shared.views = (function(_, OT, l10n) {
if (this.props.reset) {
backButton = (
React.DOM.button({className: "back", type: "button", onClick: this.props.reset},
"« ", __("feedback_back_button")
"« ", l10n.get("feedback_back_button")
)
);
}
@ -405,11 +404,11 @@ loop.shared.views = (function(_, OT, l10n) {
_getCategories: function() {
return {
audio_quality: __("feedback_category_audio_quality"),
video_quality: __("feedback_category_video_quality"),
disconnected : __("feedback_category_was_disconnected"),
confusing: __("feedback_category_confusing"),
other: __("feedback_category_other")
audio_quality: l10n.get("feedback_category_audio_quality"),
video_quality: l10n.get("feedback_category_video_quality"),
disconnected : l10n.get("feedback_category_was_disconnected"),
confusing: l10n.get("feedback_category_confusing"),
other: l10n.get("feedback_category_other")
};
},
@ -420,7 +419,8 @@ loop.shared.views = (function(_, OT, l10n) {
React.DOM.label({key: key},
React.DOM.input({type: "radio", ref: "category", name: "category",
value: category,
onChange: this.handleCategoryChange}),
onChange: this.handleCategoryChange,
checked: this.state.category === category}),
categories[category]
)
);
@ -429,28 +429,43 @@ loop.shared.views = (function(_, OT, l10n) {
/**
* Checks if the form is ready for submission:
* - a category (reason) must be chosen
* - no feedback submission should be pending
*
* - no feedback submission should be pending.
* - a category (reason) must be chosen;
* - if the "other" category is chosen, a custom description must have been
* entered by the end user;
*
* @return {Boolean}
*/
_isFormReady: function() {
return this.state.category !== "" && !this.props.pending;
if (this.props.pending || !this.state.category) {
return false;
}
if (this.state.category === "other" && !this.state.description) {
return false;
}
return true;
},
handleCategoryChange: function(event) {
var category = event.target.value;
if (category !== "other") {
// resets description text field
this.setState({description: ""});
this.setState({
category: category,
description: category == "other" ? "" : this._getCategories()[category]
});
if (category == "other") {
this.refs.description.getDOMNode().focus();
}
this.setState({category: category});
},
handleCustomTextChange: function(event) {
handleDescriptionFieldChange: function(event) {
this.setState({description: event.target.value});
},
handleDescriptionFieldFocus: function(event) {
this.setState({category: "other", description: ""});
},
handleFormSubmit: function(event) {
event.preventDefault();
this.props.sendFeedback({
@ -461,18 +476,24 @@ loop.shared.views = (function(_, OT, l10n) {
},
render: function() {
var descriptionDisplayValue = this.state.category === "other" ?
this.state.description : "";
return (
FeedbackLayout({title: __("feedback_what_makes_you_sad"),
FeedbackLayout({title: l10n.get("feedback_what_makes_you_sad"),
reset: this.props.reset},
React.DOM.form({onSubmit: this.handleFormSubmit},
this._getCategoryFields(),
React.DOM.p(null, React.DOM.input({type: "text", ref: "description", name: "description",
disabled: this.state.category !== "other",
onChange: this.handleCustomTextChange,
value: this.state.description})),
React.DOM.p(null,
React.DOM.input({type: "text", ref: "description", name: "description",
onChange: this.handleDescriptionFieldChange,
onFocus: this.handleDescriptionFieldFocus,
value: descriptionDisplayValue,
placeholder:
l10n.get("feedback_custom_category_text_placeholder")})
),
React.DOM.button({type: "submit", className: "btn btn-success",
disabled: !this._isFormReady()},
__("feedback_submit_button")
l10n.get("feedback_submit_button")
)
)
)
@ -506,10 +527,11 @@ loop.shared.views = (function(_, OT, l10n) {
window.close();
}
return (
FeedbackLayout({title: __("feedback_thank_you_heading")},
React.DOM.p({className: "info thank-you"}, __("feedback_window_will_close_in", {
countdown: this.state.countdown
}))
FeedbackLayout({title: l10n.get("feedback_thank_you_heading")},
React.DOM.p({className: "info thank-you"},
l10n.get("feedback_window_will_close_in", {
countdown: this.state.countdown
}))
)
);
}
@ -573,7 +595,8 @@ loop.shared.views = (function(_, OT, l10n) {
pending: this.state.pending});
default:
return (
FeedbackLayout({title: __("feedback_call_experience_heading")},
FeedbackLayout({title:
l10n.get("feedback_call_experience_heading")},
React.DOM.div({className: "faces"},
React.DOM.button({className: "face face-happy",
onClick: this.handleHappyClick}),

View File

@ -12,7 +12,6 @@ loop.shared.views = (function(_, OT, l10n) {
"use strict";
var sharedModels = loop.shared.models;
var __ = l10n.get;
var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
/**
@ -134,7 +133,7 @@ loop.shared.views = (function(_, OT, l10n) {
var prefix = this.props.enabled ? "mute" : "unmute";
var suffix = "button_title";
var msgId = [prefix, this.props.scope, this.props.type, suffix].join("_");
return __(msgId);
return l10n.get(msgId);
},
render: function() {
@ -184,7 +183,7 @@ loop.shared.views = (function(_, OT, l10n) {
<ul className="conversation-toolbar">
<li><button className="btn btn-hangup"
onClick={this.handleClickHangup}
title={__("hangup_button_title")}></button></li>
title={l10n.get("hangup_button_title")}></button></li>
<li><MediaControlButton action={this.handleToggleVideo}
enabled={this.props.video.enabled}
scope="local" type="video" /></li>
@ -371,7 +370,7 @@ loop.shared.views = (function(_, OT, l10n) {
if (this.props.reset) {
backButton = (
<button className="back" type="button" onClick={this.props.reset}>
&laquo;&nbsp;{__("feedback_back_button")}
&laquo;&nbsp;{l10n.get("feedback_back_button")}
</button>
);
}
@ -405,11 +404,11 @@ loop.shared.views = (function(_, OT, l10n) {
_getCategories: function() {
return {
audio_quality: __("feedback_category_audio_quality"),
video_quality: __("feedback_category_video_quality"),
disconnected : __("feedback_category_was_disconnected"),
confusing: __("feedback_category_confusing"),
other: __("feedback_category_other")
audio_quality: l10n.get("feedback_category_audio_quality"),
video_quality: l10n.get("feedback_category_video_quality"),
disconnected : l10n.get("feedback_category_was_disconnected"),
confusing: l10n.get("feedback_category_confusing"),
other: l10n.get("feedback_category_other")
};
},
@ -420,7 +419,8 @@ loop.shared.views = (function(_, OT, l10n) {
<label key={key}>
<input type="radio" ref="category" name="category"
value={category}
onChange={this.handleCategoryChange} />
onChange={this.handleCategoryChange}
checked={this.state.category === category} />
{categories[category]}
</label>
);
@ -429,28 +429,43 @@ loop.shared.views = (function(_, OT, l10n) {
/**
* Checks if the form is ready for submission:
* - a category (reason) must be chosen
* - no feedback submission should be pending
*
* - no feedback submission should be pending.
* - a category (reason) must be chosen;
* - if the "other" category is chosen, a custom description must have been
* entered by the end user;
*
* @return {Boolean}
*/
_isFormReady: function() {
return this.state.category !== "" && !this.props.pending;
if (this.props.pending || !this.state.category) {
return false;
}
if (this.state.category === "other" && !this.state.description) {
return false;
}
return true;
},
handleCategoryChange: function(event) {
var category = event.target.value;
if (category !== "other") {
// resets description text field
this.setState({description: ""});
this.setState({
category: category,
description: category == "other" ? "" : this._getCategories()[category]
});
if (category == "other") {
this.refs.description.getDOMNode().focus();
}
this.setState({category: category});
},
handleCustomTextChange: function(event) {
handleDescriptionFieldChange: function(event) {
this.setState({description: event.target.value});
},
handleDescriptionFieldFocus: function(event) {
this.setState({category: "other", description: ""});
},
handleFormSubmit: function(event) {
event.preventDefault();
this.props.sendFeedback({
@ -461,18 +476,24 @@ loop.shared.views = (function(_, OT, l10n) {
},
render: function() {
var descriptionDisplayValue = this.state.category === "other" ?
this.state.description : "";
return (
<FeedbackLayout title={__("feedback_what_makes_you_sad")}
<FeedbackLayout title={l10n.get("feedback_what_makes_you_sad")}
reset={this.props.reset}>
<form onSubmit={this.handleFormSubmit}>
{this._getCategoryFields()}
<p><input type="text" ref="description" name="description"
disabled={this.state.category !== "other"}
onChange={this.handleCustomTextChange}
value={this.state.description} /></p>
<p>
<input type="text" ref="description" name="description"
onChange={this.handleDescriptionFieldChange}
onFocus={this.handleDescriptionFieldFocus}
value={descriptionDisplayValue}
placeholder={
l10n.get("feedback_custom_category_text_placeholder")} />
</p>
<button type="submit" className="btn btn-success"
disabled={!this._isFormReady()}>
{__("feedback_submit_button")}
{l10n.get("feedback_submit_button")}
</button>
</form>
</FeedbackLayout>
@ -506,10 +527,11 @@ loop.shared.views = (function(_, OT, l10n) {
window.close();
}
return (
<FeedbackLayout title={__("feedback_thank_you_heading")}>
<p className="info thank-you">{__("feedback_window_will_close_in", {
countdown: this.state.countdown
})}</p>
<FeedbackLayout title={l10n.get("feedback_thank_you_heading")}>
<p className="info thank-you">{
l10n.get("feedback_window_will_close_in", {
countdown: this.state.countdown
})}</p>
</FeedbackLayout>
);
}
@ -573,7 +595,8 @@ loop.shared.views = (function(_, OT, l10n) {
pending={this.state.pending} />;
default:
return (
<FeedbackLayout title={__("feedback_call_experience_heading")}>
<FeedbackLayout title={
l10n.get("feedback_call_experience_heading")}>
<div className="faces">
<button className="face face-happy"
onClick={this.handleHappyClick}></button>

View File

@ -48,7 +48,8 @@ describe("loop.panel", function() {
return "en-US";
},
setLoopCharPref: sandbox.stub(),
getLoopCharPref: sandbox.stub().returns("unseen")
getLoopCharPref: sandbox.stub().returns("unseen"),
copyString: sandbox.stub()
};
document.mozL10n.initialize(navigator.mozLoop);
@ -314,9 +315,28 @@ describe("loop.panel", function() {
}));
view.setState({pending: false, callUrl: "http://example.com"});
TestUtils.findRenderedDOMComponentWithTag(view, "a");
var shareButton = view.getDOMNode().querySelector("a.btn");
expect(shareButton.href).to.equal(encodeURI(mailto));
TestUtils.findRenderedDOMComponentWithClass(view, "btn-email");
expect(view.getDOMNode().querySelector(".btn-email").dataset.mailto)
.to.equal(encodeURI(mailto));
});
it("should feature a copy button capable of copying the call url when clicked", function() {
fakeClient.requestCallUrl = sandbox.stub();
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
notifier: notifier,
client: fakeClient
}));
view.setState({
pending: false,
copied: false,
callUrl: "http://example.com"
});
TestUtils.Simulate.click(view.getDOMNode().querySelector(".btn-copy"));
sinon.assert.calledOnce(navigator.mozLoop.copyString);
sinon.assert.calledWithExactly(navigator.mozLoop.copyString,
view.state.callUrl);
});
it("should notify the user when the operation failed", function() {

View File

@ -405,6 +405,9 @@ describe("loop.shared.views", function() {
var comp, fakeFeedbackApiClient;
beforeEach(function() {
sandbox.stub(l10n, "get", function(x) {
return x;
});
fakeFeedbackApiClient = {send: sandbox.stub()};
comp = TestUtils.renderIntoDocument(sharedViews.FeedbackView({
feedbackApiClient: fakeFeedbackApiClient
@ -476,7 +479,39 @@ describe("loop.shared.views", function() {
.querySelector("form button").disabled).eql(true);
});
it("should enable the form submit button once a choice is made",
it("should disable the form submit button when the 'other' category is " +
"chosen but no description has been entered yet",
function() {
clickSadFace(comp);
fillSadFeedbackForm(comp, "other");
expect(comp.getDOMNode()
.querySelector("form button").disabled).eql(true);
});
it("should enable the form submit button when the 'other' category is " +
"chosen and a description is entered",
function() {
clickSadFace(comp);
fillSadFeedbackForm(comp, "other", "fake");
expect(comp.getDOMNode()
.querySelector("form button").disabled).eql(false);
});
it("should empty the description field when a predefined category is " +
"chosen",
function() {
clickSadFace(comp);
fillSadFeedbackForm(comp, "confusing");
expect(comp.getDOMNode()
.querySelector("form input[type='text']").value).eql("");
});
it("should enable the form submit button once a predefined category is " +
"chosen",
function() {
clickSadFace(comp);

View File

@ -4,11 +4,12 @@
/**
* /!\ FIXME: THIS IS A HORRID HACK which fakes both the mozL10n and webL10n
* objects and makes them returning "fake string" for any requested string id.
* objects and makes them returning the string id and serialized vars if any,
* for any requested string id.
* @type {Object}
*/
document.webL10n = document.mozL10n = {
get: function() {
return "fake text";
get: function(sringId, vars) {
return "" + sringId + (vars ? ";" + JSON.stringify(vars) : "");
}
};

View File

@ -93,8 +93,14 @@
return (
ShowCase(null,
Section({name: "PanelView"},
Example({summary: "332px wide", dashed: "true", style: {width: "332px"}},
React.DOM.p({className: "note"},
React.DOM.strong(null, "Note:"), " 332px wide."
),
Example({summary: "Pending call url retrieval", dashed: "true", style: {width: "332px"}},
PanelView(null)
),
Example({summary: "Call URL retrieved", dashed: "true", style: {width: "332px"}},
PanelView({callUrl: "http://invalid.example.url/"})
)
),

View File

@ -93,9 +93,15 @@
return (
<ShowCase>
<Section name="PanelView">
<Example summary="332px wide" dashed="true" style={{width: "332px"}}>
<p className="note">
<strong>Note:</strong> 332px wide.
</p>
<Example summary="Pending call url retrieval" dashed="true" style={{width: "332px"}}>
<PanelView />
</Example>
<Example summary="Call URL retrieved" dashed="true" style={{width: "332px"}}>
<PanelView callUrl="http://invalid.example.url/" />
</Example>
</Section>
<Section name="IncomingCallView">

View File

@ -8,30 +8,31 @@
<?xml-stylesheet href="chrome://mozapps/content/preferences/preferences.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
<?xml-stylesheet
<?xml-stylesheet href="chrome://browser/skin/in-content/common.css"?>
<?xml-stylesheet
href="chrome://browser/skin/preferences/in-content/preferences.css"?>
<?xml-stylesheet
<?xml-stylesheet
href="chrome://browser/content/preferences/handlers.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/applications.css"?>
<!DOCTYPE page [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
<!ENTITY % globalPreferencesDTD SYSTEM "chrome://global/locale/preferences.dtd">
<!ENTITY % preferencesDTD SYSTEM
<!ENTITY % preferencesDTD SYSTEM
"chrome://browser/locale/preferences/preferences.dtd">
<!ENTITY % privacyDTD SYSTEM "chrome://browser/locale/preferences/privacy.dtd">
<!ENTITY % tabsDTD SYSTEM "chrome://browser/locale/preferences/tabs.dtd">
<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
<!ENTITY % syncDTD SYSTEM "chrome://browser/locale/preferences/sync.dtd">
<!ENTITY % securityDTD SYSTEM
<!ENTITY % securityDTD SYSTEM
"chrome://browser/locale/preferences/security.dtd">
<!ENTITY % sanitizeDTD SYSTEM "chrome://browser/locale/sanitize.dtd">
<!ENTITY % mainDTD SYSTEM "chrome://browser/locale/preferences/main.dtd">
<!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
<!ENTITY % contentDTD SYSTEM "chrome://browser/locale/preferences/content.dtd">
<!ENTITY % applicationsDTD SYSTEM
<!ENTITY % applicationsDTD SYSTEM
"chrome://browser/locale/preferences/applications.dtd">
<!ENTITY % advancedDTD SYSTEM
<!ENTITY % advancedDTD SYSTEM
"chrome://browser/locale/preferences/advanced.dtd">
%brandDTD;
%globalPreferencesDTD;
@ -70,7 +71,7 @@
src="chrome://browser/content/preferences/in-content/preferences.js"/>
<script src="chrome://browser/content/preferences/in-content/subdialogs.js"/>
<stringbundle id="bundleBrand"
<stringbundle id="bundleBrand"
src="chrome://branding/locale/brand.properties"/>
<stringbundle id="bundlePreferences"
src="chrome://browser/locale/preferences/preferences.properties"/>

View File

@ -11,6 +11,7 @@ let gSubDialog = {
_box: null,
_injectedStyleSheets: ["chrome://mozapps/content/preferences/preferences.css",
"chrome://browser/skin/preferences/preferences.css",
"chrome://browser/skin/in-content/common.css",
"chrome://browser/skin/preferences/in-content/preferences.css"],
init: function() {

View File

@ -9,7 +9,6 @@ support-files =
[browser_devtools_api.js]
[browser_dynamic_tool_enabling.js]
[browser_keybindings.js]
skip-if = e10s # Bug 1030318
[browser_new_activation_workflow.js]
[browser_target_events.js]
[browser_target_remote.js]
@ -19,6 +18,7 @@ skip-if = e10s # Bug 1030318
[browser_toolbox_options.js]
[browser_toolbox_options_disable_buttons.js]
[browser_toolbox_options_disable_cache.js]
skip-if = e10s # Bug 1030318
[browser_toolbox_options_disable_js.js]
# [browser_toolbox_raise.js] # Bug 962258
# skip-if = os == "win"

View File

@ -18,7 +18,7 @@ function test()
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
doc = content.document;
node = doc.querySelector("h1");
waitForFocus(setupKeyBindingsTest, content);
waitForFocus(setupKeyBindingsTest);
}, true);
content.location = "data:text/html,<html><head><title>Test for the " +

View File

@ -32,7 +32,8 @@ function onHidden() {
function onVisible() {
ok(true, "Visible event received");
target.once("will-navigate", onWillNavigate);
gBrowser.contentWindow.location = "data:text/html,test navigation";
let mm = getFrameScript();
mm.sendAsyncMessage("devtools:test:navigate", { location: "data:text/html,<meta charset='utf8'/>test navigation" });
}
function onWillNavigate(event, request) {

View File

@ -131,7 +131,8 @@ function reloadTab(tabX) {
}, true);
info("Reloading tab " + tabX.title);
content.document.location.reload(false);
let mm = getFrameScript();
mm.sendAsyncMessage("devtools:test:reload");
return def.promise;
}
@ -139,15 +140,17 @@ function reloadTab(tabX) {
function* destroyTab(tabX) {
let toolbox = gDevTools.getToolbox(tabX.target);
let onceDestroyed = promise.resolve();
if (toolbox) {
onceDestroyed = gDevTools.once("toolbox-destroyed");
}
info("Removing tab " + tabX.title);
gBrowser.removeTab(tabX.tab);
info("Removed tab " + tabX.title);
if (toolbox) {
info("Waiting for toolbox-destroyed");
yield gDevTools.once("toolbox-destroyed");
info("toolbox-destroyed event received for " + tabX.title);
}
info("Waiting for toolbox-destroyed");
yield onceDestroyed;
}
function* finishUp() {

View File

@ -32,13 +32,11 @@ function testJSEnabled(event, tool, secondPass) {
ok(true, "Toolbox selected via selectTool method");
info("Testing that JS is enabled");
let logJSEnabled = doc.getElementById("logJSEnabled");
let output = doc.getElementById("output");
// We use executeSoon here because switching docSehll.allowJavascript to true
// takes a while to become live.
executeSoon(function() {
EventUtils.synthesizeMouseAtCenter(logJSEnabled, {}, doc.defaultView);
let output = doc.getElementById("output");
doc.querySelector("#logJSEnabled").click();
is(output.textContent, "JavaScript Enabled", 'Output is "JavaScript Enabled"');
testJSEnabledIframe(secondPass);
});
@ -49,12 +47,10 @@ function testJSEnabledIframe(secondPass) {
let iframe = doc.querySelector("iframe");
let iframeDoc = iframe.contentDocument;
let logJSEnabled = iframeDoc.getElementById("logJSEnabled");
let output = iframeDoc.getElementById("output");
EventUtils.synthesizeMouseAtCenter(logJSEnabled, {}, iframe.contentWindow);
iframeDoc.querySelector("#logJSEnabled").click();
is(output.textContent, "JavaScript Enabled",
'Output is "JavaScript Enabled" in iframe');
'Output is "JavaScript Enabled" in iframe');
if (secondPass) {
finishUp();
} else {
@ -75,18 +71,13 @@ function toggleJS() {
info("Checking checkbox to disable JS");
}
// After uising scrollIntoView() we need to use executeSoon() to wait for the
// browser to scroll.
executeSoon(function() {
gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
doc = content.document;
gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
doc = content.document;
deferred.resolve();
}, true);
deferred.resolve();
}, true);
EventUtils.synthesizeMouseAtCenter(cbx, {}, panel.panelWin);
});
cbx.click();
return deferred.promise;
}
@ -94,13 +85,11 @@ function toggleJS() {
function testJSDisabled() {
info("Testing that JS is disabled");
let logJSDisabled = doc.getElementById("logJSDisabled");
let output = doc.getElementById("output");
doc.querySelector("#logJSDisabled").click();
EventUtils.synthesizeMouseAtCenter(logJSDisabled, {}, doc.defaultView);
ok(output.textContent !== "JavaScript Disabled",
'output is not "JavaScript Disabled"');
testJSDisabledIframe();
}
@ -109,10 +98,8 @@ function testJSDisabledIframe() {
let iframe = doc.querySelector("iframe");
let iframeDoc = iframe.contentDocument;
let logJSDisabled = iframeDoc.getElementById("logJSDisabled");
let output = iframeDoc.getElementById("output");
EventUtils.synthesizeMouseAtCenter(logJSDisabled, {}, iframe.contentWindow);
iframeDoc.querySelector("#logJSDisabled").click();
ok(output.textContent !== "JavaScript Disabled",
'output is not "JavaScript Disabled" in iframe');
toggleJS().then(function() {

View File

@ -16,6 +16,16 @@ waitForExplicitFinish();
// Uncomment this pref to dump all devtools emitted events to the console.
// Services.prefs.setBoolPref("devtools.dump.emit", true);
function getFrameScript() {
let mm = gBrowser.selectedBrowser.messageManager;
let frameURL = "chrome://browser/content/devtools/frame-script-utils.js";
mm.loadFrameScript(frameURL, false);
SimpleTest.registerCleanupFunction(() => {
mm = null;
});
return mm;
}
gDevTools.testing = true;
SimpleTest.registerCleanupFunction(() => {
gDevTools.testing = false;

View File

@ -1356,16 +1356,12 @@ Toolbox.prototype = {
}
// Destroying the walker and inspector fronts
outstanding.push(this.destroyInspector());
// Removing buttons
outstanding.push(() => {
outstanding.push(this.destroyInspector().then(() => {
// Removing buttons
this._pickerButton.removeEventListener("command", this._togglePicker, false);
this._pickerButton = null;
let container = this.doc.getElementById("toolbox-buttons");
while (container.firstChild) {
container.removeChild(container.firstChild);
}
});
}));
// Remove the host UI
outstanding.push(this.destroyHost());

View File

@ -7,3 +7,12 @@
addMessageListener("devtools:test:history", function ({ data }) {
content.history[data.direction]();
});
addMessageListener("devtools:test:navigate", function ({ data }) {
content.location = data.location;
});
addMessageListener("devtools:test:reload", function ({ data }) {
data = data || {};
content.location.reload(data.forceget);
});

View File

@ -1483,6 +1483,10 @@ CssRuleView.prototype = {
// Remove context menu
if (this._contextmenu) {
// Destroy the Add Rule menuitem.
this.menuitemAddRule.removeEventListener("command", this._onAddRule);
this.menuitemAddRule = null;
// Destroy the Select All menuitem.
this.menuitemSelectAll.removeEventListener("command", this._onSelectAll);
this.menuitemSelectAll = null;

View File

@ -6,6 +6,7 @@ const Cu = Components.utils;
Cu.import('resource:///modules/devtools/gDevTools.jsm');
const {require} = Cu.import('resource://gre/modules/devtools/Loader.jsm', {}).devtools;
const {Services} = Cu.import('resource://gre/modules/Services.jsm');
const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
const {AppManager} = require('devtools/webide/app-manager');
const {AppActorFront} = require('devtools/app-actor-front');
const {Connection} = require('devtools/client/connection-manager');
@ -45,7 +46,8 @@ let Monitor = {
front: null,
socket: null,
wstimeout: null,
loaded: false,
b2ginfo: false,
b2gtimeout: null,
/**
* Add new data to the graphs, create a new graph if necessary.
@ -56,6 +58,12 @@ let Monitor = {
return;
}
if (Monitor.b2ginfo && data.graph === 'USS') {
// If we're polling b2g-info, ignore USS updates from the device's
// USSAgents (see Monitor.pollB2GInfo()).
return;
}
if (fallback) {
for (let key in fallback) {
if (!data[key]) {
@ -84,7 +92,6 @@ let Monitor = {
AppManager.on('app-manager-update', Monitor.onAppManagerUpdate);
Monitor.connectToRuntime();
Monitor.connectToWebSocket();
Monitor.loaded = true;
},
/**
@ -125,6 +132,7 @@ let Monitor = {
* Use an AppActorFront on a runtime to watch track its apps.
*/
connectToRuntime: function() {
Monitor.pollB2GInfo();
let client = AppManager.connection && AppManager.connection.client;
let resp = AppManager._listTabsResponse;
if (client && resp && !Monitor.front) {
@ -137,6 +145,7 @@ let Monitor = {
* Destroy our AppActorFront.
*/
disconnectFromRuntime: function() {
Monitor.unpollB2GInfo();
if (Monitor.front) {
Monitor.front.unwatchApps(Monitor.onRuntimeAppEvent);
Monitor.front = null;
@ -206,6 +215,66 @@ let Monitor = {
fallback.curve = app.manifest.name
}
Monitor.update(packet.data, fallback);
},
/**
* Bug 1047355: If possible, parsing the output of `b2g-info` has several
* benefits over bug 1037465's multi-process USSAgent approach, notably:
* - Works for older Firefox OS devices (pre-2.1),
* - Doesn't need certified-apps debugging,
* - Polling time is synchronized for all processes.
* TODO: After bug 1043324 lands, consider removing this hack.
*/
pollB2GInfo: function() {
if (AppManager.selectedRuntime) {
let id = AppManager.selectedRuntime.id;
let device = Devices.getByName(id);
if (device && device.shell) {
device.shell('b2g-info').then(s => {
let lines = s.split('\n');
let line = '';
// Find the header row to locate NAME and USS, looks like:
// ' NAME PID NICE USS PSS RSS VSIZE OOM_ADJ USER '.
while (line.indexOf('NAME') < 0) {
if (lines.length < 1) {
// Something is wrong with this output, don't trust b2g-info.
Monitor.unpollB2GInfo();
return;
}
line = lines.shift();
}
let namelength = line.indexOf('NAME') + 'NAME'.length;
let ussindex = line.slice(namelength).split(/\s+/).indexOf('USS');
// Get the NAME and USS in each following line, looks like:
// 'Homescreen 375 18 12.6 16.3 27.1 67.8 4 app_375'.
while (lines.length > 0 && lines[0].length > namelength) {
line = lines.shift();
let name = line.slice(0, namelength);
let uss = line.slice(namelength).split(/\s+/)[ussindex];
Monitor.update({
curve: name.trim(),
value: 1024 * 1024 * parseFloat(uss) // Convert MB to bytes.
}, {
// Note: We use the fallback object to set the graph name to 'USS'
// so that Monitor.update() can ignore USSAgent updates.
graph: 'USS'
});
}
});
}
}
Monitor.b2ginfo = true;
Monitor.b2gtimeout = setTimeout(Monitor.pollB2GInfo, 350);
},
/**
* Polling b2g-info doesn't work or is no longer needed.
*/
unpollB2GInfo: function() {
clearTimeout(Monitor.b2gtimeout);
Monitor.b2ginfo = false;
}
};

View File

@ -208,10 +208,8 @@ let UI = {
setupBusyTimeout: function() {
this.cancelBusyTimeout();
this._busyTimeout = setTimeout(() => {
let busyPromise = this._busyPromise;
this.unbusy();
UI.reportError("error_operationTimeout", this._busyOperationDescription);
busyPromise.reject("promise timeout: " + this._busyOperationDescription);
}, 30000);
},

View File

@ -173,6 +173,7 @@
#endif
@BINPATH@/components/content_xslt.xpt
@BINPATH@/components/cookie.xpt
@BINPATH@/components/devtools_security.xpt
@BINPATH@/components/directory.xpt
@BINPATH@/components/docshell.xpt
@BINPATH@/components/dom.xpt

View File

@ -50,6 +50,7 @@ feedback_category_video_quality=Video quality
feedback_category_was_disconnected=Was disconnected
feedback_category_confusing=Confusing
feedback_category_other=Other:
feedback_custom_category_text_placeholder=What went wrong?
feedback_submit_button=Submit
feedback_back_button=Back
## LOCALIZATION NOTE (feedback_window_will_close_in): In this item, don't
@ -61,4 +62,5 @@ feedback_window_will_close_in=This window will close in {{countdown}} seconds
share_email_subject=Loop invitation to chat
share_email_body=Please click that link to call me back:\r\n\r\n{{callUrl}}
share_button=Email
copy_url_button=Copy
copied_url_button=Copied!

View File

@ -0,0 +1,62 @@
/* - This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
- You can obtain one at http://mozilla.org/MPL/2.0/. */
%include ../../shared/in-content/common.inc.css
xul|tab[selected] {
/* Override styles for tab[selected] from
toolkit/themes/linux/global/tabbox.css */
margin-bottom: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
xul|button,
xul|colorpicker[type="button"],
xul|menulist {
margin: 2px 4px;
}
xul|button > xul|*.button-box,
xul|menulist > xul|*.menulist-label-box {
-moz-appearance: none;
}
xul|button[type="menu"] > xul|*.button-box > xul|*.button-menu-dropmarker {
-moz-appearance: none !important;
}
xul|*.help-button > xul|*.button-box > xul|*.button-icon {
-moz-margin-end: 0;
}
xul|menulist {
font-size: inherit;
}
xul|menulist:not([editable="true"]) > xul|*.menulist-dropmarker {
display: -moz-box;
margin-top: 6px;
margin-bottom: 6px;
}
xul|checkbox {
-moz-binding: url("chrome://global/content/bindings/checkbox.xml#checkbox");
-moz-box-align: center;
}
xul|radio {
-moz-binding: url("chrome://global/content/bindings/radio.xml#radio");
-moz-box-align: center;
-moz-margin-start: 0;
}
xul|*.radio-label-box {
-moz-appearance: none;
}
xul|*.numberbox-input-box {
-moz-appearance: none;
border-width: 0;
}

View File

@ -35,7 +35,17 @@ browser.jar:
skin/classic/browser/identity-icons-https-mixed-active.png
skin/classic/browser/identity-icons-https-mixed-display.png
skin/classic/browser/Info.png
skin/classic/browser/in-content/common.css (../shared/in-content/common.css)
* skin/classic/browser/in-content/common.css (in-content/common.css)
skin/classic/browser/in-content/check.png (../shared/in-content/check.png)
skin/classic/browser/in-content/check@2x.png (../shared/in-content/check@2x.png)
skin/classic/browser/in-content/dropdown.png (../shared/in-content/dropdown.png)
skin/classic/browser/in-content/dropdown@2x.png (../shared/in-content/dropdown@2x.png)
skin/classic/browser/in-content/dropdown-disabled.png (../shared/in-content/dropdown-disabled.png)
skin/classic/browser/in-content/dropdown-disabled@2x.png (../shared/in-content/dropdown-disabled@2x.png)
skin/classic/browser/in-content/help-glyph.png (../shared/in-content/help-glyph.png)
skin/classic/browser/in-content/help-glyph@2x.png (../shared/in-content/help-glyph@2x.png)
skin/classic/browser/in-content/sorter.png (../shared/in-content/sorter.png)
skin/classic/browser/in-content/sorter@2x.png (../shared/in-content/sorter@2x.png)
skin/classic/browser/menuPanel.png
skin/classic/browser/menuPanel-customize.png
skin/classic/browser/menuPanel-exit.png
@ -150,18 +160,8 @@ browser.jar:
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico)
skin/classic/browser/preferences/in-content/check.png (../shared/incontentprefs/check.png)
skin/classic/browser/preferences/in-content/check@2x.png (../shared/incontentprefs/check@2x.png)
skin/classic/browser/preferences/in-content/icons.png (../shared/incontentprefs/icons.png)
skin/classic/browser/preferences/in-content/icons@2x.png (../shared/incontentprefs/icons@2x.png)
skin/classic/browser/preferences/in-content/help-glyph.png (../shared/incontentprefs/help-glyph.png)
skin/classic/browser/preferences/in-content/help-glyph@2x.png (../shared/incontentprefs/help-glyph@2x.png)
skin/classic/browser/preferences/in-content/dropdown.png (../shared/incontentprefs/dropdown.png)
skin/classic/browser/preferences/in-content/dropdown@2x.png (../shared/incontentprefs/dropdown@2x.png)
skin/classic/browser/preferences/in-content/sorter.png (../shared/incontentprefs/sorter.png)
skin/classic/browser/preferences/in-content/sorter@2x.png (../shared/incontentprefs/sorter@2x.png)
skin/classic/browser/preferences/in-content/dropdown-disabled.png (../shared/incontentprefs/dropdown-disabled.png)
skin/classic/browser/preferences/in-content/dropdown-disabled@2x.png (../shared/incontentprefs/dropdown-disabled@2x.png)
skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png)

View File

@ -4,63 +4,6 @@
%include ../../../shared/incontentprefs/preferences.css
tab[selected] {
/* Override styles for tab[selected] from
toolkit/themes/linux/global/tabbox.css */
margin-bottom: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
button,
colorpicker[type="button"],
menulist {
margin: 2px 4px;
}
button > .button-box,
menulist > .menulist-label-box {
-moz-appearance: none;
}
button[type="menu"] > .button-box > .button-menu-dropmarker {
-moz-appearance: none !important;
}
.help-button > .button-box > .button-icon {
-moz-margin-end: 0;
}
menulist {
font-size: inherit;
}
menulist:not([editable="true"]) > .menulist-dropmarker {
display: -moz-box;
margin-top: 6px;
margin-bottom: 6px;
}
checkbox {
-moz-binding: url("chrome://global/content/bindings/checkbox.xml#checkbox");
-moz-box-align: center;
}
radio {
-moz-binding: url("chrome://global/content/bindings/radio.xml#radio");
-moz-box-align: center;
-moz-margin-start: 0;
}
.radio-label-box {
-moz-appearance: none;
}
.numberbox-input-box {
-moz-appearance: none;
border-width: 0;
}
spinbuttons {
-moz-appearance: none;
}

View File

@ -0,0 +1,60 @@
/* - This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
- You can obtain one at http://mozilla.org/MPL/2.0/. */
%include ../../shared/in-content/common.inc.css
xul|tabs {
padding-right: 0;
padding-left: 0;
}
xul|tab[selected] {
text-shadow: none;
}
xul|button,
xul|colorpicker[type="button"],
xul|menulist {
margin-top: 3px;
}
xul|menulist:not([editable="true"]) > xul|menupopup > xul|menuitem[checked="true"]::before,
xul|menulist:not([editable="true"]) > xul|menupopup > xul|menuitem[selected="true"]::before {
display: none;
}
xul|menulist:not([editable="true"]) > xul|*.menulist-dropmarker {
display: -moz-box;
margin-top: 1px;
margin-bottom: 1px;
}
xul|menulist > xul|menupopup xul|menu,
xul|menulist > xul|menupopup xul|menuitem,
xul|button[type="menu"] > xul|menupopup xul|menu,
xul|button[type="menu"] > xul|menupopup xul|menuitem {
-moz-padding-end: 34px;
}
xul|*.help-button > xul|*.button-box > xul|*.button-icon {
-moz-margin-start: 0;
}
xul|*.checkbox-icon {
margin-right: 0;
}
xul|*.radio-icon {
-moz-margin-end: 0;
}
xul|*.numberbox-input-box {
-moz-appearance: none;
border-width: 0;
}
xul|description {
font-size: 1.25rem;
line-height: 22px;
}

View File

@ -43,7 +43,17 @@ browser.jar:
skin/classic/browser/identity-icons-https-mixed-display.png
skin/classic/browser/identity-icons-https-mixed-display@2x.png
skin/classic/browser/Info.png
skin/classic/browser/in-content/common.css (../shared/in-content/common.css)
* skin/classic/browser/in-content/common.css (in-content/common.css)
skin/classic/browser/in-content/check.png (../shared/in-content/check.png)
skin/classic/browser/in-content/check@2x.png (../shared/in-content/check@2x.png)
skin/classic/browser/in-content/dropdown.png (../shared/in-content/dropdown.png)
skin/classic/browser/in-content/dropdown@2x.png (../shared/in-content/dropdown@2x.png)
skin/classic/browser/in-content/dropdown-disabled.png (../shared/in-content/dropdown-disabled.png)
skin/classic/browser/in-content/dropdown-disabled@2x.png (../shared/in-content/dropdown-disabled@2x.png)
skin/classic/browser/in-content/help-glyph.png (../shared/in-content/help-glyph.png)
skin/classic/browser/in-content/help-glyph@2x.png (../shared/in-content/help-glyph@2x.png)
skin/classic/browser/in-content/sorter.png (../shared/in-content/sorter.png)
skin/classic/browser/in-content/sorter@2x.png (../shared/in-content/sorter@2x.png)
skin/classic/browser/keyhole-circle.png
skin/classic/browser/keyhole-circle@2x.png
skin/classic/browser/KUI-background.png
@ -248,18 +258,8 @@ browser.jar:
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico)
skin/classic/browser/preferences/in-content/check.png (../shared/incontentprefs/check.png)
skin/classic/browser/preferences/in-content/check@2x.png (../shared/incontentprefs/check@2x.png)
skin/classic/browser/preferences/in-content/icons.png (../shared/incontentprefs/icons.png)
skin/classic/browser/preferences/in-content/icons@2x.png (../shared/incontentprefs/icons@2x.png)
skin/classic/browser/preferences/in-content/help-glyph.png (../shared/incontentprefs/help-glyph.png)
skin/classic/browser/preferences/in-content/help-glyph@2x.png (../shared/incontentprefs/help-glyph@2x.png)
skin/classic/browser/preferences/in-content/sorter.png (../shared/incontentprefs/sorter.png)
skin/classic/browser/preferences/in-content/sorter@2x.png (../shared/incontentprefs/sorter@2x.png)
skin/classic/browser/preferences/in-content/dropdown.png (../shared/incontentprefs/dropdown.png)
skin/classic/browser/preferences/in-content/dropdown@2x.png (../shared/incontentprefs/dropdown@2x.png)
skin/classic/browser/preferences/in-content/dropdown-disabled.png (../shared/incontentprefs/dropdown-disabled.png)
skin/classic/browser/preferences/in-content/dropdown-disabled@2x.png (../shared/incontentprefs/dropdown-disabled@2x.png)
skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png)

View File

@ -9,56 +9,6 @@ prefpane .groupbox-title {
margin-bottom: 0;
}
tabs {
padding-right: 0;
padding-left: 0;
}
tab[selected] {
text-shadow: none;
}
button,
colorpicker[type="button"],
menulist {
margin-top: 3px;
}
menulist:not([editable="true"]) > menupopup > menuitem[checked="true"]::before,
menulist:not([editable="true"]) > menupopup > menuitem[selected="true"]::before {
display: none;
}
menulist:not([editable="true"]) > .menulist-dropmarker {
display: -moz-box;
margin-top: 1px;
margin-bottom: 1px;
}
menulist > menupopup menu,
menulist > menupopup menuitem,
button[type="menu"] > menupopup menu,
button[type="menu"] > menupopup menuitem {
-moz-padding-end: 34px;
}
.help-button > .button-box > .button-icon {
-moz-margin-start: 0;
}
.checkbox-icon {
margin-right: 0;
}
.radio-icon {
-moz-margin-end: 0;
}
.numberbox-input-box {
-moz-appearance: none;
border-width: 0;
}
spinbuttons {
-moz-appearance: none;
}
@ -88,11 +38,6 @@ spinbuttons {
-moz-margin-end: 8px !important;
}
description {
font-size: 1.25rem;
line-height: 22px;
}
#downloadFolder > .fileFieldContentBox {
-moz-padding-start: 3px;
}

View File

Before

Width:  |  Height:  |  Size: 288 B

After

Width:  |  Height:  |  Size: 288 B

View File

Before

Width:  |  Height:  |  Size: 471 B

After

Width:  |  Height:  |  Size: 471 B

View File

@ -1,55 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
body {
font: message-box;
font-size: 15px;
font-weight: normal;
margin: 0;
color: #424e5a;
background-color: #f1f1f1;
}
h1 {
font-size: 2.5em;
font-weight: lighter;
line-height: 1.2;
margin: 0;
margin-bottom: .5em;
}
button {
line-height: 20px;
height: 30px;
max-height: 30px;
padding: 3px;
color: #333333;
border: 1px solid rgba(23,50,77,.4);
border-radius: 5px;
background-color: #f1f1f1;
background-image: linear-gradient(#fff, rgba(255,255,255,.1));
box-shadow: 0 1px 1px 0 #fff, inset 0 2px 2px 0 #fff;
text-shadow: 0 1px 1px #fefffe;
-moz-appearance: none;
-moz-border-top-colors: none !important;
-moz-border-right-colors: none !important;
-moz-border-bottom-colors: none !important;
-moz-border-left-colors: none !important;
}
button:enabled:hover {
background-image: linear-gradient(#fff, rgba(255,255,255,.6));
}
button:enabled:hover:active {
background-image: linear-gradient(rgba(255,255,255,.1), rgba(255,255,255,.6));
}
button:disabled {
cursor: not-allowed;
color: rgba(115,121,128,.5);
border-color: rgba(23,50,77,.25);
background-image: linear-gradient(rgba(255,255,255,.5), rgba(255,255,255,.1));
text-shadow: 0 1px 1px #fff;
}

View File

@ -0,0 +1,593 @@
%if 0
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
%endif
@namespace html "http://www.w3.org/1999/xhtml";
@namespace xul "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
html|body,
xul|page {
font: message-box;
-moz-appearance: none;
background-color: #f1f1f1;
color: #424e5a;
}
*|* {
-moz-user-select: text;
}
xul|treecol {
/* override the * rule to let the treecol be sortable */
-moz-user-select: none;
}
html|body {
font-size: 15px;
font-weight: normal;
margin: 0;
}
html|h1 {
font-size: 2.5em;
font-weight: lighter;
line-height: 1.2;
margin: 0;
margin-bottom: .5em;
}
xul|caption {
-moz-appearance: none;
margin: 0;
}
xul|caption > xul|label {
font-size: 1.3rem;
font-weight: bold;
line-height: 22px;
margin: 0 !important;
}
*|*.main-content {
padding: 40px 48px 48px;
overflow: auto;
}
xul|prefpane > xul|*.content-box {
overflow: visible;
}
/* groupboxes */
xul|groupbox {
-moz-appearance: none;
border: none;
margin-top: 15px;
margin-bottom: 15px;
-moz-margin-end: 0;
-moz-padding-start: 0;
-moz-padding-end: 0;
font-size: 1.25rem;
}
xul|groupbox xul|label {
/* !important needed to override toolkit !important rule */
-moz-margin-start: 0 !important;
-moz-margin-end: 0 !important;
}
/* tabpanels and tabs */
xul|tabpanels {
-moz-appearance: none;
font-size: 1.25rem;
line-height: 22px;
border: none;
padding: 0;
background-color: transparent;
}
xul|tabs {
margin-bottom: 15px;
border-top: 1px solid #c1c1c1;
border-bottom: 1px solid #c1c1c1;
background-color: #fbfbfb;
}
xul|*.tabs-left,
xul|*.tabs-right {
border-bottom: none;
}
xul|tab {
-moz-appearance: none;
margin-top: 0;
padding: 0 20px;
min-height: 44px;
color: #424f5a;
background-color: #fbfbfb;
border-width: 0;
transition: background-color 50ms ease 0s;
}
xul|tab:hover {
background-color: #ebebeb;
}
xul|tab[selected] {
background-color: #ebebeb;
box-shadow: inset 0 -4px 0 0 #ff9500;
}
xul|*.tab-text {
font-size: 1.3rem;
line-height: 22px;
}
/* html buttons */
html|button {
padding: 3px;
/* override forms.css */
font: inherit;
}
/* xul buttons and menulists */
*|button,
xul|colorpicker[type="button"],
xul|menulist {
-moz-appearance: none;
height: 30px;
color: #333;
line-height: 20px;
text-shadow: 0 1px 1px #fefffe;
border: 1px solid #c1c1c1;
-moz-border-top-colors: none !important;
-moz-border-right-colors: none !important;
-moz-border-bottom-colors: none !important;
-moz-border-left-colors: none !important;
border-radius: 2px;
background-color: #fbfbfb;
}
html|button:enabled:hover,
xul|button:not([disabled="true"]):hover,
xul|colorpicker[type="button"]:not([disabled="true"]):hover,
xul|menulist:not([disabled="true"]):hover {
background-color: #ebebeb;
}
html|button:enabled:hover:active,
xul|button:not([disabled="true"]):hover:active,
xul|colorpicker[type="button"]:not([disabled="true"]):hover:active,
xul|menulist[open="true"]:not([disabled="true"]) {
background-color: #dadada;
}
html|button:disabled,
xul|button[disabled="true"],
xul|colorpicker[type="button"][disabled="true"],
xul|menulist[disabled="true"] {
cursor: not-allowed;
opacity: 0.5;
}
xul|colorpicker[type="button"] {
padding: 6px;
width: 50px;
}
xul|button > xul|*.button-box,
xul|menulist > xul|*.menulist-label-box {
padding-right: 10px !important;
padding-left: 10px !important;
}
xul|button[type="menu"] > xul|*.button-box > xul|*.button-menu-dropmarker {
-moz-appearance: none;
margin: 1px 0;
-moz-margin-start: 10px;
padding: 0;
width: 10px;
height: 16px;
border: none;
background-color: transparent;
list-style-image: url("chrome://browser/skin/in-content/dropdown.png");
}
xul|*.help-button {
min-width: 30px;
border-radius: 2px;
border: 1px solid #c1c1c1;
background-color: #ffcb00;
background-image: none;
box-shadow: none;
}
xul|*.help-button:not([disabled="true"]):hover {
background-color: #f4c200;
background-image: none;
}
xul|*.help-button:not([disabled="true"]):hover:active {
background-color: #eaba00;
background-image: none;
}
xul|*.close-icon > xul|*.button-box,
xul|*.help-button > xul|*.button-box {
padding-top: 0;
padding-bottom: 0;
padding-right: 0 !important;
padding-left: 0 !important;
}
xul|*.help-button > xul|*.button-box > xul|*.button-icon {
width: 26px;
height: 26px;
background-image: url("chrome://browser/skin/in-content/help-glyph.png");
background-position: center;
}
@media (min-resolution: 2dppx) {
xul|*.help-button > xul|*.button-box > xul|*.button-icon {
background-size: 26px 26px;
background-image: url("chrome://browser/skin/in-content/help-glyph@2x.png");
}
}
xul|*.help-button > xul|*.button-box > xul|*.button-text {
display: none;
}
xul|*.spinbuttons-button {
-moz-margin-start: 10px !important;
-moz-margin-end: 2px !important;
}
xul|*.spinbuttons-up {
margin-top: 2px !important;
border-radius: 1px 1px 0 0;
}
xul|*.spinbuttons-down {
margin-bottom: 2px !important;
border-radius: 0 0 1px 1px;
}
xul|*.spinbuttons-button > xul|*.button-box {
padding: 1px 5px 2px !important;
}
xul|*.spinbuttons-up > xul|*.button-box > xul|*.button-icon {
list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
}
xul|*.spinbuttons-up[disabled="true"] > xul|*.button-box > xul|*.button-icon {
list-style-image: url("chrome://global/skin/arrow/arrow-up-dis.gif");
}
xul|*.spinbuttons-down > xul|*.button-box > xul|*.button-icon {
list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
}
xul|*.spinbuttons-down[disabled="true"] > xul|*.button-box > xul|*.button-icon {
list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.gif");
}
xul|menulist:not([editable="true"]) > xul|*.menulist-dropmarker {
-moz-appearance: none;
-moz-margin-end: 10px;
padding: 0;
border: none;
background-color: transparent;
list-style-image: url("chrome://browser/skin/in-content/dropdown.png");
}
xul|menulist[disabled="true"]:not([editable="true"]) > xul|*.menulist-dropmarker {
list-style-image: url("chrome://browser/skin/in-content/dropdown-disabled.png")
}
@media (min-resolution: 2dppx) {
xul|menulist:not([editable="true"]) > xul|*.menulist-dropmarker,
xul|button[type="menu"] > xul|*.button-box > xul|*.button-menu-dropmarker {
list-style-image: url("chrome://browser/skin/in-content/dropdown@2x.png");
}
xul|menulist[disabled="true"]:not([editable="true"]) > xul|*.menulist-dropmarker {
list-style-image: url("chrome://browser/skin/in-content/dropdown-disabled@2x.png")
}
xul|menulist:not([editable="true"]) > xul|*.menulist-dropmarker > xul|*.dropmarker-icon,
xul|button[type="menu"] > xul|*.button-box > xul|*.button-menu-dropmarker > xul|*.dropmarker-icon {
width: 10px;
height: 16px;
}
}
xul|menulist > xul|menupopup,
xul|button[type="menu"] > xul|menupopup {
-moz-appearance: none;
border: 1px solid rgba(23,50,77,0.4);
border-radius: 2px;
background-color: #fff;
}
xul|menulist > xul|menupopup xul|menu,
xul|menulist > xul|menupopup xul|menuitem,
xul|button[type="menu"] > xul|menupopup xul|menu,
xul|button[type="menu"] > xul|menupopup xul|menuitem {
-moz-appearance: none;
font-size: 1.25rem;
line-height: 22px;
height: 40px;
color: #333;
-moz-padding-start: 10px;
-moz-padding-end: 30px;
}
xul|menulist > xul|menupopup > xul|menu[_moz-menuactive="true"],
xul|menulist > xul|menupopup > xul|menuitem[_moz-menuactive="true"],
xul|button[type="menu"] > xul|menupopup > xul|menu[_moz-menuactive="true"],
xul|button[type="menu"] > xul|menupopup > xul|menuitem[_moz-menuactive="true"] {
color: #333;
background-color: transparent;
background-image: linear-gradient(rgba(76,177,255,0.25), rgba(23,146,229,0.25));
}
xul|menulist > xul|menupopup > xul|menu[selected="true"],
xul|menulist > xul|menupopup > xul|menuitem[selected="true"],
xul|button[type="menu"] > xul|menupopup > xul|menu[selected="true"],
xul|button[type="menu"] > xul|menupopup > xul|menuitem[selected="true"] {
color: #fff;
background-image: linear-gradient(#4cb1ff, #1792e5);
}
xul|menulist > xul|menupopup xul|menuseparator,
xul|button[type="menu"] > xul|menupopup xul|menuseparator {
-moz-appearance: none;
margin-top: 2px;
margin-bottom: 2px;
padding: 0;
border-top: 1px solid rgba(23,50,77,0.4);
border-bottom: none;
}
/* textboxes */
*|textbox {
-moz-appearance: none;
height: 30px;
color: #333;
line-height: 20px;
text-shadow: 0 1px 1px #fefffe;
padding-right: 10px;
padding-left: 10px;
border: 1px solid #c1c1c1;
-moz-border-top-colors: none !important;
-moz-border-right-colors: none !important;
-moz-border-bottom-colors: none !important;
-moz-border-left-colors: none !important;
border-radius: 2px;
background-color: #fff;
}
html|textbox:focus,
xul|textbox[focused] {
border-color: #0095dd;
}
html|textbox:disabled,
xul|textbox[disabled="true"] {
opacity: 0.5;
}
/* Links */
xul|*.text-link,
xul|*.inline-link,
html|a.inline-link {
font-size: 1.25rem;
line-height: 22px;
color: #0095dd;
}
xul|*.text-link:hover,
xul|*.inline-link:hover {
color: #4cb1ff;
text-decoration: none;
}
xul|*.text-link:hover:active,
xul|*.inline-link:hover:active {
color: #ff9500;
text-decoration: none;
}
/* Checkboxes and radio buttons */
xul|checkbox {
-moz-margin-start: 0;
}
xul|*.checkbox-check {
-moz-appearance: none;
width: 23px;
height: 23px;
border-radius: 2px;
border: 1px solid #c1c1c1;
-moz-margin-end: 10px;
background-color: #f1f1f1;
background-image: linear-gradient(#fff, rgba(255,255,255,0.8));
background-position: center center;
background-repeat: no-repeat;
box-shadow: 0 1px 1px 0 #fff, inset 0 2px 0 0 rgba(0,0,0,0.03);
}
xul|checkbox:not([disabled="true"]):hover > xul|*.checkbox-check {
border-color: #0095dd;
}
xul|*.checkbox-check[checked] {
background-image: url("chrome://browser/skin/in-content/check.png"),
/* !important needed to override toolkit !important rule */
linear-gradient(#fff, rgba(255,255,255,0.8)) !important;
}
xul|checkbox[disabled="true"] > xul|*.checkbox-check {
opacity: 0.5;
}
xul|*.checkbox-label-box {
-moz-margin-start: -1px; /* negative margin for the transparent border */
-moz-padding-start: 0;
}
@media (min-resolution: 2dppx) {
xul|*.checkbox-check[checked] {
background-size: 12px 12px, auto;
background-image: url("chrome://browser/skin/in-content/check@2x.png"),
linear-gradient(#fff, rgba(255,255,255,0.8)) !important;
}
}
xul|*.radio-check {
-moz-appearance: none;
width: 23px;
height: 23px;
border: 1px solid #c1c1c1;
border-radius: 50%;
-moz-margin-end: 10px;
background-color: #f1f1f1;
background-image: linear-gradient(#fff, rgba(255,255,255,0.80));
box-shadow: 0 1px 1px 0 #fff, inset 0 2px 0 0 rgba(0,0,0,0.03);
}
xul|radio:not([disabled="true"]):hover > xul|*.radio-check {
border-color: #0095dd;
}
xul|*.radio-check[selected] {
background-image: radial-gradient(circle, rgb(23,146,229),
rgb(76,177,255) 5.5px, rgba(76,177,255,0.2) 6px,
transparent 6px),
linear-gradient(rgb(255,255,255), rgba(255,255,255,0.8));
}
xul|radio[disabled="true"] > xul|*.radio-check {
opacity: 0.5;
}
xul|*.radio-label-box {
-moz-margin-start: -1px; /* negative margin for the transparent border */
-moz-margin-end: 10px;
-moz-padding-start: 0;
}
/* Category List */
xul|*#categories {
-moz-appearance: none;
background-color: #424f5a;
padding-top: 39px;
margin: 0;
}
xul|*.category {
-moz-appearance: none;
color: #c1c1c1;
-moz-border-end-width: 0;
-moz-padding-start: 15px;
-moz-padding-end: 21px;
min-height: 40px;
transition: background-color 150ms;
}
xul|*.category:hover {
background-color: #5e6972;
}
xul|*.category[selected] {
background-color: #343f48;
color: #f2f2f2;
box-shadow: inset 4px 0 0 0 #ff9500;
}
xul|*#categories[keyboard-navigation="true"]:-moz-focusring > xul|*.category[current] {
border-top: 1px #ffffff dotted;
border-bottom: 1px #ffffff dotted;
}
*|*.category-name {
line-height: 22px;
font-size: 1.25rem;
padding-bottom: 2px;
-moz-padding-start: 9px;
margin: 0;
}
*|*.category-icon {
width: 24px;
height: 24px;
}
/* header */
*|*.header {
border-bottom: 1px solid #c8c8c8;
margin-bottom: 15px;
padding-bottom: 15px;
}
*|*.header-name {
font-size: 2.5rem;
font-weight: normal;
line-height: 40px;
margin: 0;
}
/* File fields */
xul|filefield {
-moz-appearance: none;
background-color: transparent;
border: none;
padding: 0;
}
xul|*.fileFieldContentBox {
background-color: transparent;
}
xul|*.fileFieldIcon {
-moz-margin-start: 10px;
-moz-margin-end: 0;
}
xul|*.fileFieldLabel {
-moz-margin-start: -26px;
-moz-padding-start: 36px;
}
xul|textbox:-moz-locale-dir(rtl),
xul|*.fileFieldLabel:-moz-locale-dir(rtl),
xul|textbox + xul|button:-moz-locale-dir(ltr),
xul|filefield + xul|button:-moz-locale-dir(ltr) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
xul|textbox:-moz-locale-dir(ltr),
xul|*.fileFieldLabel:-moz-locale-dir(ltr),
xul|textbox + xul|button:-moz-locale-dir(rtl),
xul|filefield + xul|button:-moz-locale-dir(rtl) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
xul|textbox + xul|button,
xul|filefield + xul|button {
-moz-border-start: none;
}

View File

Before

Width:  |  Height:  |  Size: 412 B

After

Width:  |  Height:  |  Size: 412 B

View File

Before

Width:  |  Height:  |  Size: 865 B

After

Width:  |  Height:  |  Size: 865 B

View File

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 421 B

View File

Before

Width:  |  Height:  |  Size: 885 B

After

Width:  |  Height:  |  Size: 885 B

View File

Before

Width:  |  Height:  |  Size: 280 B

After

Width:  |  Height:  |  Size: 280 B

View File

Before

Width:  |  Height:  |  Size: 500 B

After

Width:  |  Height:  |  Size: 500 B

View File

Before

Width:  |  Height:  |  Size: 264 B

After

Width:  |  Height:  |  Size: 264 B

View File

Before

Width:  |  Height:  |  Size: 523 B

After

Width:  |  Height:  |  Size: 523 B

View File

@ -9,39 +9,12 @@
dialog,
window,
prefwindow,
.windowDialog,
page {
.windowDialog {
-moz-appearance: none;
background-color: #f1f1f1;
color: #424E5A;
}
* {
-moz-user-select: text;
}
treecol {
/* override the * rule to let the treecol be sortable */
-moz-user-select: none;
}
caption {
-moz-appearance: none;
margin: 0;
}
caption > label {
font-size: 1.3rem;
font-weight: bold;
line-height: 22px;
margin: 0 !important;
}
.main-content {
padding: 40px 48px 48px;
overflow: auto;
}
prefpane {
max-width: 800px;
padding: 0;
@ -50,469 +23,9 @@ prefpane {
line-height: 22px;
}
prefpane > .content-box {
overflow: visible;
}
/* groupboxes */
groupbox {
-moz-appearance: none;
border: none;
margin-top: 15px;
margin-bottom: 15px;
-moz-margin-end: 0;
-moz-padding-start: 0;
-moz-padding-end: 0;
font-size: 1.25rem;
}
groupbox label {
/* !important needed to override toolkit !important rule */
-moz-margin-start: 0 !important;
-moz-margin-end: 0 !important;
}
/* tabpanels and tabs */
tabpanels {
-moz-appearance: none;
font-size: 1.25rem;
line-height: 22px;
border: none;
padding: 0;
background-color: transparent;
}
tabs {
margin-bottom: 15px;
border-top: 1px solid #c1c1c1;
border-bottom: 1px solid #c1c1c1;
background-color: #fbfbfb;
}
.tabs-left,
.tabs-right {
border-bottom: none;
}
tab {
-moz-appearance: none;
margin-top: 0;
padding: 0 20px;
min-height: 44px;
color: #424f5a;
background-color: #fbfbfb;
border-width: 0;
transition: background-color 50ms ease 0s;
}
tab:hover {
background-color: #ebebeb;
}
tab[selected] {
background-color: #ebebeb;
box-shadow: inset 0 -4px 0 0 #ff9500;
}
.tab-text {
font-size: 1.3rem;
line-height: 22px;
}
/* buttons and menulists */
button,
colorpicker[type="button"],
menulist {
-moz-appearance: none;
height: 30px;
color: #333333;
line-height: 20px;
text-shadow: 0 1px 1px #FEFFFE;
border: 1px solid #C1C1C1;
-moz-border-top-colors: none !important;
-moz-border-right-colors: none !important;
-moz-border-bottom-colors: none !important;
-moz-border-left-colors: none !important;
border-radius: 2px;
background-color: #FBFBFB;
}
button:not([disabled="true"]):hover,
colorpicker[type="button"]:not([disabled="true"]):hover,
menulist:not([disabled="true"]):hover {
background-color: #EBEBEB;
}
button:not([disabled="true"]):hover:active,
colorpicker[type="button"]:not([disabled="true"]):hover:active,
menulist[open="true"]:not([disabled="true"]) {
background-color: #DADADA;
}
button[disabled="true"],
colorpicker[type="button"][disabled="true"],
menulist[disabled="true"] {
opacity: 0.5;
}
colorpicker[type="button"] {
padding: 6px;
width: 50px;
}
button > .button-box,
menulist > .menulist-label-box {
padding-right: 10px !important;
padding-left: 10px !important;
}
button[type="menu"] > .button-box > .button-menu-dropmarker {
-moz-appearance: none;
margin: 1px 0;
-moz-margin-start: 10px;
padding: 0;
width: 10px;
height: 16px;
border: none;
background-color: transparent;
list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown.png");
}
.help-button {
min-width: 30px;
border-radius: 2px;
border: 1px solid #C1C1C1;
background-color: #FFCB00;
background-image: none;
box-shadow: none;
}
.help-button:not([disabled="true"]):hover {
background-color: #F4C200;
background-image: none;
}
.help-button:not([disabled="true"]):hover:active {
background-color: #EABA00;
background-image: none;
}
.close-icon > .button-box,
.help-button > .button-box {
padding-top: 0;
padding-bottom: 0;
padding-right: 0 !important;
padding-left: 0 !important;
}
.help-button > .button-box > .button-icon {
width: 26px;
height: 26px;
background-image: url("chrome://browser/skin/preferences/in-content/help-glyph.png");
background-position: center;
}
@media (min-resolution: 2dppx) {
.help-button > .button-box > .button-icon {
background-size: 26px 26px;
background-image: url("chrome://browser/skin/preferences/in-content/help-glyph@2x.png");
}
}
.help-button > .button-box > .button-text {
display: none;
}
.spinbuttons-button {
-moz-margin-start: 10px !important;
-moz-margin-end: 2px !important;
}
.spinbuttons-up {
margin-top: 2px !important;
border-radius: 1px 1px 0 0;
}
.spinbuttons-down {
margin-bottom: 2px !important;
border-radius: 0 0 1px 1px;
}
.spinbuttons-button > .button-box {
padding: 1px 5px 2px !important;
}
.spinbuttons-up > .button-box > .button-icon {
list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
}
.spinbuttons-up[disabled="true"] > .button-box > .button-icon {
list-style-image: url("chrome://global/skin/arrow/arrow-up-dis.gif");
}
.spinbuttons-down > .button-box > .button-icon {
list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
}
.spinbuttons-down[disabled="true"] > .button-box > .button-icon {
list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.gif");
}
menulist:not([editable="true"]) > .menulist-dropmarker {
-moz-appearance: none;
-moz-margin-end: 10px;
padding: 0;
border: none;
background-color: transparent;
list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown.png")
}
menulist[disabled="true"]:not([editable="true"]) > .menulist-dropmarker {
list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown-disabled.png")
}
@media (min-resolution: 2dppx) {
menulist:not([editable="true"]) > .menulist-dropmarker,
button[type="menu"] > .button-box > .button-menu-dropmarker {
list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown@2x.png");
}
menulist[disabled="true"]:not([editable="true"]) > .menulist-dropmarker {
list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown-disabled@2x.png")
}
menulist:not([editable="true"]) > .menulist-dropmarker > .dropmarker-icon,
button[type="menu"] > .button-box > .button-menu-dropmarker > .dropmarker-icon {
width: 10px;
height: 16px;
}
}
menulist > menupopup,
button[type="menu"] > menupopup {
-moz-appearance: none;
border: 1px solid rgba(23,50,77,0.4);
border-radius: 2px;
background-color: #FFFFFF;
}
menulist > menupopup menu,
menulist > menupopup menuitem,
button[type="menu"] > menupopup menu,
button[type="menu"] > menupopup menuitem {
-moz-appearance: none;
font-size: 1.25rem;
line-height: 22px;
height: 40px;
color: #333333;
-moz-padding-start: 10px;
-moz-padding-end: 30px;
}
menulist > menupopup > menu[_moz-menuactive="true"],
menulist > menupopup > menuitem[_moz-menuactive="true"],
button[type="menu"] > menupopup > menu[_moz-menuactive="true"],
button[type="menu"] > menupopup > menuitem[_moz-menuactive="true"] {
color: #333333;
background-color: transparent;
background-image: linear-gradient(rgba(76,177,255,0.25), rgba(23,146,229,0.25));
}
menulist > menupopup > menu[selected="true"],
menulist > menupopup > menuitem[selected="true"],
button[type="menu"] > menupopup > menu[selected="true"],
button[type="menu"] > menupopup > menuitem[selected="true"] {
color: #fff;
background-image: linear-gradient(#4CB1FF, #1792E5);
}
menulist > menupopup menuseparator,
button[type="menu"] > menupopup menuseparator {
-moz-appearance: none;
margin-top: 2px;
margin-bottom: 2px;
padding: 0;
border-top: 1px solid rgba(23,50,77,0.4);
border-bottom: none;
}
/* textboxes */
textbox {
-moz-appearance: none;
height: 30px;
color: #333333;
line-height: 20px;
text-shadow: 0 1px 1px #FEFFFE;
padding-right: 10px;
padding-left: 10px;
border: 1px solid #C1C1C1;
-moz-border-top-colors: none !important;
-moz-border-right-colors: none !important;
-moz-border-bottom-colors: none !important;
-moz-border-left-colors: none !important;
border-radius: 2px;
background-color: #FFF;
}
textbox[focused] {
border-color: #0095DD;
}
textbox[disabled="true"] {
opacity: 0.5;
}
/* Links */
.text-link,
.inline-link,
html|a.inline-link {
font-size: 1.25rem;
line-height: 22px;
color: #0095DD;
}
.text-link:hover,
.inline-link:hover {
color: #4CB1FF;
text-decoration: none;
}
.text-link:hover:active,
.inline-link:hover:active {
color: #FF9500;
text-decoration: none;
}
/* Checkboxes and radio buttons */
checkbox {
-moz-margin-start: 0;
}
.checkbox-check {
-moz-appearance: none;
width: 23px;
height: 23px;
border-radius: 2px;
border: 1px solid #C1C1C1;
-moz-margin-end: 10px;
background-color: #f1f1f1;
background-image: linear-gradient(#ffffff, rgba(255,255,255,0.8));
background-position: center center;
background-repeat: no-repeat;
box-shadow: 0 1px 1px 0 #ffffff, inset 0 2px 0 0 rgba(0,0,0,0.03);
}
checkbox:not([disabled="true"]):hover > .checkbox-check {
border-color: #0095DD;
}
.checkbox-check[checked] {
background-image: url("chrome://browser/skin/preferences/in-content/check.png"),
/* !important needed to override toolkit !important rule */
linear-gradient(#ffffff, rgba(255,255,255,0.8)) !important;
}
checkbox[disabled="true"] > .checkbox-check {
opacity: 0.5;
}
.checkbox-label-box {
-moz-margin-start: -1px; /* negative margin for the transparent border */
-moz-padding-start: 0;
}
@media (min-resolution: 2dppx) {
.checkbox-check[checked] {
background-size: 12px 12px, auto;
background-image: url("chrome://browser/skin/preferences/in-content/check@2x.png"),
linear-gradient(#ffffff, rgba(255,255,255,0.8)) !important;
}
}
.radio-check {
-moz-appearance: none;
width: 23px;
height: 23px;
border: 1px solid #C1C1C1;
border-radius: 50%;
-moz-margin-end: 10px;
background-color: #f1f1f1;
background-image: linear-gradient(#ffffff, rgba(255,255,255,0.80));
box-shadow: 0 1px 1px 0 #ffffff, inset 0 2px 0 0 rgba(0,0,0,0.03);
}
radio:not([disabled="true"]):hover > .radio-check {
border-color: #0095DD;
}
.radio-check[selected] {
background-image: radial-gradient(circle, rgb(23,146,229),
rgb(76,177,255) 5.5px, rgba(76,177,255,0.2) 6px,
transparent 6px),
linear-gradient(rgb(255,255,255), rgba(255,255,255,0.8));
}
radio[disabled="true"] > .radio-check {
opacity: 0.5;
}
.radio-label-box {
-moz-margin-start: -1px; /* negative margin for the transparent border */
-moz-margin-end: 10px;
-moz-padding-start: 0;
}
/* Category List */
#categories {
-moz-appearance: none;
background-color: #424f5a;
padding-top: 39px;
margin: 0;
}
.category {
-moz-appearance: none;
color: #c1c1c1;
-moz-border-end-width: 0;
-moz-padding-start: 15px;
-moz-padding-end: 21px;
min-height: 40px;
transition: background-color 150ms;
}
.category:hover {
background-color: #5e6972;
}
.category[selected] {
background-color: #343f48;
color: #f2f2f2;
box-shadow: inset 4px 0 0 0 #FF9500;
}
#categories[keyboard-navigation="true"]:-moz-focusring > .category[current] {
border-top: 1px #ffffff dotted;
border-bottom: 1px #ffffff dotted;
}
.category-name {
line-height: 22px;
font-size: 1.25rem;
padding-bottom: 2px;
-moz-padding-start: 9px;
margin: 0;
}
.category-icon {
width: 24px;
height: 24px;
list-style-image: url("chrome://browser/skin/preferences/in-content/icons.png");
}
@ -580,68 +93,13 @@ radio[disabled="true"] > .radio-check {
/* header */
.header {
border-bottom: 1px solid #c8c8c8;
margin-bottom: 15px;
padding-bottom: 15px;
}
#header-advanced {
border-bottom: none;
padding-bottom: 0;
}
.header-name {
font-size: 2.5rem;
font-weight: normal;
line-height: 40px;
margin: 0;
}
/* General Pane */
filefield {
-moz-appearance: none;
background-color: transparent;
border: none;
padding: 0;
}
.fileFieldContentBox {
background-color: transparent;
}
.fileFieldIcon {
-moz-margin-start: 10px;
-moz-margin-end: 0;
}
.fileFieldLabel {
-moz-margin-start: -26px;
-moz-padding-start: 36px;
}
textbox:-moz-locale-dir(rtl),
.fileFieldLabel:-moz-locale-dir(rtl),
textbox + button:-moz-locale-dir(ltr),
filefield + button:-moz-locale-dir(ltr) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
textbox:-moz-locale-dir(ltr),
.fileFieldLabel:-moz-locale-dir(ltr),
textbox + button:-moz-locale-dir(rtl),
filefield + button:-moz-locale-dir(rtl) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
textbox + button,
filefield + button {
-moz-border-start: none;
}
#downloadFolder {
-moz-margin-start: 0;
}

View File

@ -0,0 +1,35 @@
/* - This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
- You can obtain one at http://mozilla.org/MPL/2.0/. */
%include ../../shared/in-content/common.inc.css
xul|caption {
background-color: transparent;
}
xul|button,
xul|colorpicker[type="button"],
xul|menulist {
margin: 2px 4px;
}
xul|menulist:not([editable="true"]) > xul|*.menulist-dropmarker {
margin-top: 1px;
margin-bottom: 1px;
}
xul|checkbox {
-moz-padding-start: 0;
}
xul|radio {
-moz-binding: url("chrome://global/content/bindings/radio.xml#radio");
-moz-margin-start: 0;
-moz-padding-start: 0;
}
xul|*.radio-icon,
xul|*.checkbox-icon {
-moz-margin-end: 0;
}

View File

@ -37,7 +37,17 @@ browser.jar:
skin/classic/browser/identity-icons-https-ev.png
skin/classic/browser/identity-icons-https-mixed-active.png
skin/classic/browser/identity-icons-https-mixed-display.png
skin/classic/browser/in-content/common.css (../shared/in-content/common.css)
* skin/classic/browser/in-content/common.css (in-content/common.css)
skin/classic/browser/in-content/check.png (../shared/in-content/check.png)
skin/classic/browser/in-content/check@2x.png (../shared/in-content/check@2x.png)
skin/classic/browser/in-content/dropdown.png (../shared/in-content/dropdown.png)
skin/classic/browser/in-content/dropdown@2x.png (../shared/in-content/dropdown@2x.png)
skin/classic/browser/in-content/dropdown-disabled.png (../shared/in-content/dropdown-disabled.png)
skin/classic/browser/in-content/dropdown-disabled@2x.png (../shared/in-content/dropdown-disabled@2x.png)
skin/classic/browser/in-content/help-glyph.png (../shared/in-content/help-glyph.png)
skin/classic/browser/in-content/help-glyph@2x.png (../shared/in-content/help-glyph@2x.png)
skin/classic/browser/in-content/sorter.png (../shared/in-content/sorter.png)
skin/classic/browser/in-content/sorter@2x.png (../shared/in-content/sorter@2x.png)
skin/classic/browser/keyhole-forward-mask.svg
skin/classic/browser/KUI-background.png
skin/classic/browser/livemark-folder.png
@ -177,18 +187,8 @@ browser.jar:
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico)
skin/classic/browser/preferences/in-content/check.png (../shared/incontentprefs/check.png)
skin/classic/browser/preferences/in-content/check@2x.png (../shared/incontentprefs/check@2x.png)
skin/classic/browser/preferences/in-content/icons.png (../shared/incontentprefs/icons.png)
skin/classic/browser/preferences/in-content/icons@2x.png (../shared/incontentprefs/icons@2x.png)
skin/classic/browser/preferences/in-content/help-glyph.png (../shared/incontentprefs/help-glyph.png)
skin/classic/browser/preferences/in-content/help-glyph@2x.png (../shared/incontentprefs/help-glyph@2x.png)
skin/classic/browser/preferences/in-content/sorter.png (../shared/incontentprefs/sorter.png)
skin/classic/browser/preferences/in-content/sorter@2x.png (../shared/incontentprefs/sorter@2x.png)
skin/classic/browser/preferences/in-content/dropdown.png (../shared/incontentprefs/dropdown.png)
skin/classic/browser/preferences/in-content/dropdown@2x.png (../shared/incontentprefs/dropdown@2x.png)
skin/classic/browser/preferences/in-content/dropdown-disabled.png (../shared/incontentprefs/dropdown-disabled.png)
skin/classic/browser/preferences/in-content/dropdown-disabled@2x.png (../shared/incontentprefs/dropdown-disabled@2x.png)
skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png)
@ -456,7 +456,17 @@ browser.jar:
skin/classic/aero/browser/identity-icons-https-ev.png
skin/classic/aero/browser/identity-icons-https-mixed-active.png
skin/classic/aero/browser/identity-icons-https-mixed-display.png
skin/classic/aero/browser/in-content/common.css (../shared/in-content/common.css)
* skin/classic/aero/browser/in-content/common.css (in-content/common.css)
skin/classic/aero/browser/in-content/check.png (../shared/in-content/check.png)
skin/classic/aero/browser/in-content/check@2x.png (../shared/in-content/check@2x.png)
skin/classic/aero/browser/in-content/dropdown.png (../shared/in-content/dropdown.png)
skin/classic/aero/browser/in-content/dropdown@2x.png (../shared/in-content/dropdown@2x.png)
skin/classic/aero/browser/in-content/dropdown-disabled.png (../shared/in-content/dropdown-disabled.png)
skin/classic/aero/browser/in-content/dropdown-disabled@2x.png (../shared/in-content/dropdown-disabled@2x.png)
skin/classic/aero/browser/in-content/help-glyph.png (../shared/in-content/help-glyph.png)
skin/classic/aero/browser/in-content/help-glyph@2x.png (../shared/in-content/help-glyph@2x.png)
skin/classic/aero/browser/in-content/sorter.png (../shared/in-content/sorter.png)
skin/classic/aero/browser/in-content/sorter@2x.png (../shared/in-content/sorter@2x.png)
skin/classic/aero/browser/keyhole-forward-mask.svg
skin/classic/aero/browser/KUI-background.png
skin/classic/aero/browser/livemark-folder.png (livemark-folder-aero.png)
@ -602,18 +612,8 @@ browser.jar:
* skin/classic/aero/browser/preferences/preferences.css (preferences/preferences.css)
* skin/classic/aero/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/aero/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico)
skin/classic/aero/browser/preferences/in-content/check.png (../shared/incontentprefs/check.png)
skin/classic/aero/browser/preferences/in-content/check@2x.png (../shared/incontentprefs/check@2x.png)
skin/classic/aero/browser/preferences/in-content/icons.png (../shared/incontentprefs/icons.png)
skin/classic/aero/browser/preferences/in-content/icons@2x.png (../shared/incontentprefs/icons@2x.png)
skin/classic/aero/browser/preferences/in-content/help-glyph.png (../shared/incontentprefs/help-glyph.png)
skin/classic/aero/browser/preferences/in-content/help-glyph@2x.png (../shared/incontentprefs/help-glyph@2x.png)
skin/classic/aero/browser/preferences/in-content/sorter.png (../shared/incontentprefs/sorter.png)
skin/classic/aero/browser/preferences/in-content/sorter@2x.png (../shared/incontentprefs/sorter@2x.png)
skin/classic/aero/browser/preferences/in-content/dropdown.png (../shared/incontentprefs/dropdown.png)
skin/classic/aero/browser/preferences/in-content/dropdown@2x.png (../shared/incontentprefs/dropdown@2x.png)
skin/classic/aero/browser/preferences/in-content/dropdown-disabled.png (../shared/incontentprefs/dropdown-disabled.png)
skin/classic/aero/browser/preferences/in-content/dropdown-disabled@2x.png (../shared/incontentprefs/dropdown-disabled@2x.png)
skin/classic/aero/browser/preferences/applications.css (preferences/applications.css)
skin/classic/aero/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/aero/browser/social/services-16.png (social/services-16.png)

View File

@ -4,36 +4,6 @@
%include ../../../shared/incontentprefs/preferences.css
caption {
background-color: transparent;
}
button,
colorpicker[type="button"],
menulist {
margin: 2px 4px;
}
menulist:not([editable="true"]) > .menulist-dropmarker {
margin-top: 1px;
margin-bottom: 1px;
}
checkbox {
-moz-padding-start: 0;
}
radio {
-moz-binding: url("chrome://global/content/bindings/radio.xml#radio");
-moz-margin-start: 0;
-moz-padding-start: 0;
}
.radio-icon,
.checkbox-icon {
-moz-margin-end: 0;
}
.actionsMenu > .menulist-label-box > .menulist-icon {
-moz-margin-end: 9px;
}

View File

@ -26,8 +26,6 @@
<script>
<![CDATA[
SimpleTest.expectAssertions(1, 2);
SimpleTest.waitForExplicitFinish();
copyToProfile('animals.sqlite');

View File

@ -17,7 +17,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=495648
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
SimpleTest.expectAssertions(18, 24);
SimpleTest.expectAssertions(15, 24);
/** Test for Bug 495648 **/
var uri = window.location.href.replace(/test_bug495648.xul/, "bug495648.rdf");

View File

@ -24,9 +24,18 @@ import android.database.Cursor;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.Color;
/**
* A utility wrapper for accessing a static {@link LocalBrowserDB},
* manually initialized with a particular profile, and similarly
* managing a static instance of {@link SuggestedSites}.
*
* Be careful using this class: if you're not BrowserApp, you probably
* want to manually instantiate and use LocalBrowserDB itself.
*
* Also manages some mapping column names and flags.
* The column name mapping will be removed in Bug 1050034.
*/
public class BrowserDB {
private static boolean sAreContentProvidersEnabled = true;
public static interface URLColumns {
public static String URL = "url";
public static String TITLE = "title";
@ -41,121 +50,9 @@ public class BrowserDB {
EXCLUDE_PINNED_SITES
}
private static BrowserDBIface sDb;
private static SuggestedSites sSuggestedSites;
public interface BrowserDBIface {
public void invalidateCachedState();
@RobocopTarget
public Cursor filter(ContentResolver cr, CharSequence constraint, int limit,
EnumSet<FilterFlags> flags);
// This should only return frecent sites. BrowserDB.getTopSites will do the
// work to combine that list with the pinned sites list.
public Cursor getTopSites(ContentResolver cr, int limit);
public void updateVisitedHistory(ContentResolver cr, String uri);
public void updateHistoryTitle(ContentResolver cr, String uri, String title);
public void updateHistoryEntry(ContentResolver cr, String uri, String title,
long date, int visits);
@RobocopTarget
public Cursor getAllVisitedHistory(ContentResolver cr);
public Cursor getRecentHistory(ContentResolver cr, int limit);
public void expireHistory(ContentResolver cr, ExpirePriority priority);
public void removeHistoryEntry(ContentResolver cr, int id);
@RobocopTarget
public void removeHistoryEntry(ContentResolver cr, String url);
public void clearHistory(ContentResolver cr);
@RobocopTarget
public Cursor getBookmarksInFolder(ContentResolver cr, long folderId);
public Cursor getReadingList(ContentResolver cr);
public boolean isVisited(ContentResolver cr, String uri);
public int getReadingListCount(ContentResolver cr);
@RobocopTarget
public boolean isBookmark(ContentResolver cr, String uri);
public boolean isReadingListItem(ContentResolver cr, String uri);
/**
* Return a combination of fields about the provided URI
* in a single hit on the DB.
*/
public int getItemFlags(ContentResolver cr, String uri);
public String getUrlForKeyword(ContentResolver cr, String keyword);
@RobocopTarget
public void addBookmark(ContentResolver cr, String title, String uri);
public void removeBookmark(ContentResolver cr, int id);
@RobocopTarget
public void removeBookmarksWithURL(ContentResolver cr, String uri);
@RobocopTarget
public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword);
public void addReadingListItem(ContentResolver cr, ContentValues values);
public void removeReadingListItemWithURL(ContentResolver cr, String uri);
public void removeReadingListItem(ContentResolver cr, int id);
public LoadFaviconResult getFaviconForUrl(ContentResolver cr, String uri);
public String getFaviconUrlForHistoryUrl(ContentResolver cr, String url);
public void updateFaviconForUrl(ContentResolver cr, String pageUri, byte[] encodedFavicon, String faviconUri);
public void updateThumbnailForUrl(ContentResolver cr, String uri, BitmapDrawable thumbnail);
@RobocopTarget
public byte[] getThumbnailForUrl(ContentResolver cr, String uri);
public Cursor getThumbnailsForUrls(ContentResolver cr, List<String> urls);
@RobocopTarget
public void removeThumbnails(ContentResolver cr);
public void registerBookmarkObserver(ContentResolver cr, ContentObserver observer);
public void registerHistoryObserver(ContentResolver cr, ContentObserver observer);
public int getCount(ContentResolver cr, String database);
public void pinSite(ContentResolver cr, String url, String title, int position);
public void unpinSite(ContentResolver cr, int position);
public void unpinAllSites(ContentResolver cr);
public Cursor getPinnedSites(ContentResolver cr, int limit);
@RobocopTarget
public Cursor getBookmarkForUrl(ContentResolver cr, String url);
public int addDefaultBookmarks(Context context, ContentResolver cr, int offset);
public int addDistributionBookmarks(ContentResolver cr, Distribution distribution, int offset);
}
static {
// Forcing local DB no option to switch to Android DB for now
sDb = null;
}
private static volatile LocalBrowserDB sDb;
private static volatile SuggestedSites sSuggestedSites;
private static volatile boolean sAreContentProvidersEnabled = true;
public static void initialize(String profile) {
sDb = new LocalBrowserDB(profile);
@ -181,11 +78,6 @@ public class BrowserDB {
sDb.invalidateCachedState();
}
@RobocopTarget
public static Cursor filter(ContentResolver cr, CharSequence constraint, int limit) {
return filter(cr, constraint, limit, EnumSet.noneOf(FilterFlags.class));
}
@RobocopTarget
public static Cursor filter(ContentResolver cr, CharSequence constraint, int limit,
EnumSet<FilterFlags> flags) {
@ -243,13 +135,6 @@ public class BrowserDB {
}
}
public static void updateHistoryEntry(ContentResolver cr, String uri, String title,
long date, int visits) {
if (sAreContentProvidersEnabled) {
sDb.updateHistoryEntry(cr, uri, title, date, visits);
}
}
@RobocopTarget
public static Cursor getAllVisitedHistory(ContentResolver cr) {
return (sAreContentProvidersEnabled ? sDb.getAllVisitedHistory(cr) : null);
@ -260,16 +145,11 @@ public class BrowserDB {
}
public static void expireHistory(ContentResolver cr, ExpirePriority priority) {
if (sDb == null)
if (sDb == null) {
return;
}
if (priority == null)
priority = ExpirePriority.NORMAL;
sDb.expireHistory(cr, priority);
}
public static void removeHistoryEntry(ContentResolver cr, int id) {
sDb.removeHistoryEntry(cr, id);
sDb.expireHistory(cr, priority == null ? ExpirePriority.NORMAL : priority);
}
@RobocopTarget
@ -296,14 +176,6 @@ public class BrowserDB {
return sDb.getUrlForKeyword(cr, keyword);
}
public static boolean isVisited(ContentResolver cr, String uri) {
return sDb.isVisited(cr, uri);
}
public static int getReadingListCount(ContentResolver cr) {
return sDb.getReadingListCount(cr);
}
@RobocopTarget
public static boolean isBookmark(ContentResolver cr, String uri) {
return (sAreContentProvidersEnabled && sDb.isBookmark(cr, uri));
@ -324,10 +196,6 @@ public class BrowserDB {
sDb.addBookmark(cr, title, uri);
}
public static void removeBookmark(ContentResolver cr, int id) {
sDb.removeBookmark(cr, id);
}
@RobocopTarget
public static void removeBookmarksWithURL(ContentResolver cr, String uri) {
sDb.removeBookmarksWithURL(cr, uri);
@ -346,10 +214,6 @@ public class BrowserDB {
sDb.removeReadingListItemWithURL(cr, uri);
}
public static void removeReadingListItem(ContentResolver cr, int id) {
sDb.removeReadingListItem(cr, id);
}
public static LoadFaviconResult getFaviconForFaviconUrl(ContentResolver cr, String faviconURL) {
return sDb.getFaviconForUrl(cr, faviconURL);
}

View File

@ -38,6 +38,7 @@ import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
import org.mozilla.gecko.favicons.decoders.LoadFaviconResult;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.mozglue.RobocopTarget;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.util.GeckoJarReader;
@ -55,7 +56,7 @@ import android.provider.Browser;
import android.text.TextUtils;
import android.util.Log;
public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
public class LocalBrowserDB {
// Calculate these once, at initialization. isLoggable is too expensive to
// have in-line in each log call.
private static final String LOGTAG = "GeckoLocalBrowserDB";
@ -145,7 +146,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
* Add default bookmarks to the database.
* Takes an offset; returns a new offset.
*/
@Override
public int addDefaultBookmarks(Context context, ContentResolver cr, final int offset) {
long folderID = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
if (folderID == -1L) {
@ -238,7 +238,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
* Add bookmarks from the provided distribution.
* Takes an offset; returns a new offset.
*/
@Override
public int addDistributionBookmarks(ContentResolver cr, Distribution distribution, int offset) {
if (!distribution.exists()) {
Log.d(LOGTAG, "No distribution from which to add bookmarks.");
@ -448,7 +447,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
}
// Invalidate cached data
@Override
public void invalidateCachedState() {
mDesktopBookmarksExist = null;
}
@ -535,7 +533,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return new LocalDBCursor(c);
}
@Override
public int getCount(ContentResolver cr, String database) {
int count = 0;
String[] columns = null;
@ -572,7 +569,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return count;
}
@Override
@RobocopTarget
public Cursor filter(ContentResolver cr, CharSequence constraint, int limit,
EnumSet<FilterFlags> flags) {
String selection = "";
@ -598,7 +595,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
selection, selectionArgs);
}
@Override
public Cursor getTopSites(ContentResolver cr, int limit) {
// Filter out unvisited bookmarks and the ones that don't have real
// parents (e.g. pinned sites or reading list items).
@ -622,7 +618,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
selectionArgs);
}
@Override
public void updateVisitedHistory(ContentResolver cr, String uri) {
ContentValues values = new ContentValues();
@ -638,7 +633,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
new String[] { uri });
}
@Override
public void updateHistoryTitle(ContentResolver cr, String uri, String title) {
ContentValues values = new ContentValues();
values.put(History.TITLE, title);
@ -649,7 +643,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
new String[] { uri });
}
@Override
public void updateHistoryEntry(ContentResolver cr, String uri, String title,
long date, int visits) {
int oldVisits = 0;
@ -682,7 +675,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
new String[] { uri });
}
@Override
@RobocopTarget
public Cursor getAllVisitedHistory(ContentResolver cr) {
Cursor c = cr.query(mHistoryUriWithProfile,
new String[] { History.URL },
@ -693,7 +686,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return new LocalDBCursor(c);
}
@Override
public Cursor getRecentHistory(ContentResolver cr, int limit) {
Cursor c = cr.query(combinedUriWithLimit(limit),
new String[] { Combined._ID,
@ -710,33 +702,30 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return new LocalDBCursor(c);
}
@Override
public void expireHistory(ContentResolver cr, ExpirePriority priority) {
Uri url = mHistoryExpireUriWithProfile;
url = url.buildUpon().appendQueryParameter(BrowserContract.PARAM_EXPIRE_PRIORITY, priority.toString()).build();
cr.delete(url, null, null);
}
@Override
public void removeHistoryEntry(ContentResolver cr, int id) {
cr.delete(mHistoryUriWithProfile,
History._ID + " = ?",
new String[] { String.valueOf(id) });
}
@Override
@RobocopTarget
public void removeHistoryEntry(ContentResolver cr, String url) {
cr.delete(mHistoryUriWithProfile,
History.URL + " = ?",
new String[] { url });
}
@Override
public void clearHistory(ContentResolver cr) {
cr.delete(mHistoryUriWithProfile, null, null);
}
@Override
@RobocopTarget
public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
Cursor c = null;
boolean addDesktopFolder = false;
@ -745,7 +734,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
if (folderId == Bookmarks.FIXED_ROOT_ID) {
folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
// We'll add a fake "Desktop Bookmarks" folder to the root view if desktop
// We'll add a fake "Desktop Bookmarks" folder to the root view if desktop
// bookmarks exist, so that the user can still access non-mobile bookmarks.
addDesktopFolder = desktopBookmarksExist(cr);
}
@ -784,7 +773,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return new LocalDBCursor(c);
}
@Override
public Cursor getReadingList(ContentResolver cr) {
return cr.query(mReadingListUriWithProfile,
ReadingListItems.DEFAULT_PROJECTION,
@ -825,7 +813,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return mDesktopBookmarksExist;
}
@Override
public int getReadingListCount(ContentResolver cr) {
Cursor c = null;
try {
@ -842,7 +829,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
}
}
@Override
@RobocopTarget
public boolean isBookmark(ContentResolver cr, String uri) {
Cursor c = null;
try {
@ -864,7 +851,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return false;
}
@Override
public boolean isReadingListItem(ContentResolver cr, String uri) {
Cursor c = null;
try {
@ -892,7 +878,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
*
* This will expand as necessary to eliminate multiple consecutive queries.
*/
@Override
public int getItemFlags(ContentResolver cr, String uri) {
final Cursor c = cr.query(mFlagsUriWithProfile,
null,
@ -912,7 +897,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
}
}
@Override
public String getUrlForKeyword(ContentResolver cr, String keyword) {
Cursor c = null;
try {
@ -1021,13 +1005,12 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
debug("Updated " + updated + " rows to new modified time.");
}
@Override
@RobocopTarget
public void addBookmark(ContentResolver cr, String title, String uri) {
long folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
addBookmarkItem(cr, title, uri, folderId);
}
@Override
public void removeBookmark(ContentResolver cr, int id) {
Uri contentUri = mBookmarksUriWithProfile;
@ -1040,7 +1023,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
cr.delete(contentUri, idEquals, idArgs);
}
@Override
@RobocopTarget
public void removeBookmarksWithURL(ContentResolver cr, String uri) {
Uri contentUri = mBookmarksUriWithProfile;
@ -1053,7 +1036,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
cr.delete(contentUri, urlEquals, urlArgs);
}
@Override
public void addReadingListItem(ContentResolver cr, ContentValues values) {
// Check that required fields are present.
for (String field: ReadingListItems.REQUIRED_FIELDS) {
@ -1079,27 +1061,23 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
debug("Updated " + updated + " rows to new modified time.");
}
@Override
public void removeReadingListItemWithURL(ContentResolver cr, String uri) {
cr.delete(mReadingListUriWithProfile, ReadingListItems.URL + " = ? ", new String[] { uri });
}
@Override
public void removeReadingListItem(ContentResolver cr, int id) {
cr.delete(mReadingListUriWithProfile, ReadingListItems._ID + " = ? ", new String[] { String.valueOf(id) });
}
@Override
public void registerBookmarkObserver(ContentResolver cr, ContentObserver observer) {
cr.registerContentObserver(mBookmarksUriWithProfile, false, observer);
}
@Override
public void registerHistoryObserver(ContentResolver cr, ContentObserver observer) {
cr.registerContentObserver(mHistoryUriWithProfile, false, observer);
}
@Override
@RobocopTarget
public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword) {
ContentValues values = new ContentValues();
values.put(Browser.BookmarkColumns.TITLE, title);
@ -1120,7 +1098,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
* @param faviconURL The URL of the favicon to fetch from the database.
* @return The decoded Bitmap from the database, if any. null if none is stored.
*/
@Override
public LoadFaviconResult getFaviconForUrl(ContentResolver cr, String faviconURL) {
Cursor c = null;
byte[] b = null;
@ -1151,7 +1128,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return FaviconDecoder.decodeFavicon(b);
}
@Override
public String getFaviconUrlForHistoryUrl(ContentResolver cr, String uri) {
Cursor c = null;
@ -1172,7 +1148,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return null;
}
@Override
public void updateFaviconForUrl(ContentResolver cr, String pageUri,
byte[] encodedFavicon, String faviconUri) {
ContentValues values = new ContentValues();
@ -1190,7 +1165,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
new String[] { faviconUri });
}
@Override
public void updateThumbnailForUrl(ContentResolver cr, String uri,
BitmapDrawable thumbnail) {
@ -1222,7 +1196,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
new String[] { uri });
}
@Override
@RobocopTarget
public byte[] getThumbnailForUrl(ContentResolver cr, String uri) {
Cursor c = null;
byte[] b = null;
@ -1255,7 +1229,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
*
* Returns null if the provided list of URLs is empty or null.
*/
@Override
public Cursor getThumbnailsForUrls(ContentResolver cr, List<String> urls) {
if (urls == null) {
return null;
@ -1278,7 +1251,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
null);
}
@Override
@RobocopTarget
public void removeThumbnails(ContentResolver cr) {
cr.delete(mThumbnailsUriWithProfile, null, null);
}
@ -1461,12 +1434,10 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
}
}
@Override
public int getCount() {
return super.getCount() + mIndexOffset;
}
@Override
public boolean moveToPosition(int position) {
mAtDesktopBookmarksPosition = (mDesktopBookmarksIndex == position);
@ -1476,7 +1447,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return super.moveToPosition(position - mIndexOffset);
}
@Override
public long getLong(int columnIndex) {
if (!mAtDesktopBookmarksPosition)
return super.getLong(columnIndex);
@ -1488,7 +1458,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return -1;
}
@Override
public int getInt(int columnIndex) {
if (!mAtDesktopBookmarksPosition)
return super.getInt(columnIndex);
@ -1502,7 +1471,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return -1;
}
@Override
public String getString(int columnIndex) {
if (!mAtDesktopBookmarksPosition)
return super.getString(columnIndex);
@ -1535,19 +1503,16 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return columnName;
}
@Override
public int getColumnIndex(String columnName) {
return super.getColumnIndex(translateColumnName(columnName));
}
@Override
public int getColumnIndexOrThrow(String columnName) {
return super.getColumnIndexOrThrow(translateColumnName(columnName));
}
}
@Override
public void pinSite(ContentResolver cr, String url, String title, int position) {
ContentValues values = new ContentValues();
final long now = System.currentTimeMillis();
@ -1572,7 +1537,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) });
}
@Override
public Cursor getPinnedSites(ContentResolver cr, int limit) {
return cr.query(bookmarksUriWithLimit(limit),
new String[] { Bookmarks._ID,
@ -1584,7 +1548,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
Bookmarks.POSITION + " ASC");
}
@Override
public void unpinSite(ContentResolver cr, int position) {
cr.delete(mBookmarksUriWithProfile,
Bookmarks.PARENT + " == ? AND " + Bookmarks.POSITION + " = ?",
@ -1594,7 +1557,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
});
}
@Override
public void unpinAllSites(ContentResolver cr) {
cr.delete(mBookmarksUriWithProfile,
Bookmarks.PARENT + " == ?",
@ -1603,7 +1565,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
});
}
@Override
public boolean isVisited(ContentResolver cr, String uri) {
int count = 0;
Cursor c = null;
@ -1625,6 +1586,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return (count > 0);
}
@RobocopTarget
public Cursor getBookmarkForUrl(ContentResolver cr, String url) {
Cursor c = cr.query(bookmarksUriWithLimit(1),
new String[] { Bookmarks._ID,

View File

@ -17,10 +17,11 @@
android:layout_weight="1"/>
<ImageView android:id="@+id/home_empty_image"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:gravity="top|center"
android:scaleType="fitCenter"/>
android:scaleType="center"/>
<TextView android:id="@+id/home_empty_text"
android:layout_width="match_parent"

View File

@ -92,6 +92,9 @@ function Tester(aTests, aDumper, aCallback) {
this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/MemoryStats.js", simpleTestScope);
this._scriptLoader.loadSubScript("chrome://mochikit/content/chrome-harness.js", simpleTestScope);
this.SimpleTest = simpleTestScope.SimpleTest;
this.SimpleTest.harnessParameters = gConfig;
this.MemoryStats = simpleTestScope.MemoryStats;
this.Task = Task;
this.Task.Debugging.maintainStack = true;

View File

@ -337,9 +337,10 @@ class MochitestRunner(MozbuildObject):
manifest = TestManifest()
manifest.tests.extend(tests)
if (len(tests) == 1):
options.closeWhenDone = False
options.manifestFile = manifest
if len(test_paths) == 1 and len(tests) == 1:
options.testPath = test_paths[0]
if rerun_failures:
options.testManifest = failure_file_path

View File

@ -637,6 +637,9 @@ def main():
dm.killProcess(procName)
if options.robocopIni != "":
# turning buffering off as it's not used in robocop
message_logger.buffering = False
# sut may wait up to 300 s for a robocop am process before returning
dm.default_timeout = 320
mp = manifestparser.TestManifest(strict=False)
@ -647,10 +650,6 @@ def main():
my_tests = tests
for test in robocop_tests:
tests.append(test['name'])
# suite_start message when running robocop tests
log.suite_start(tests)
# turning buffering off as it's not used in robocop
message_logger.buffering = False
if options.totalChunks:
tests_per_chunk = math.ceil(len(tests) / (options.totalChunks * 1.0))
@ -676,6 +675,8 @@ def main():
dm._checkCmd(["install", "-r", options.robocopApk])
retVal = None
# Filtering tests
active_tests = []
for test in robocop_tests:
if options.testPath and options.testPath != test['name']:
continue
@ -687,6 +688,11 @@ def main():
log.info('TEST-INFO | skipping %s | %s' % (test['name'], test['disabled']))
continue
active_tests.append(test)
log.suite_start([t['name'] for t in active_tests])
for test in active_tests:
# When running in a loop, we need to create a fresh profile for each cycle
if mochitest.localProfile:
options.profilePath = mochitest.localProfile

View File

@ -673,7 +673,7 @@ function testListing(metadata, response)
),
DIV({class: "clear"}),
DIV({class: "frameholder"},
IFRAME({scrolling: "no", id: "testframe", width: "500", height: "300"})
IFRAME({scrolling: "no", id: "testframe"})
),
DIV({class: "clear"}),
DIV({class: "toggle"},

View File

@ -95,3 +95,33 @@ div#current-test {
margin: 0;
padding: .5em;
}
#testframe {
width: 500px;
height: 300px;
}
body[singletest=true] table,
body[singletest=true] h2,
body[singletest=true] p,
body[singletest=true] br,
body[singletest=true] .clear,
body[singletest=true] .toggle,
body[singletest=true] #current-test,
body[singletest=true] .status {
display: none;
}
body[singletest=true],
body[singletest=true] .container,
body[singletest=true] .frameholder,
body[singletest=true] #testframe {
display: flex;
flex: 1 1 auto;
height: 100%;
box-sizing: border-box;
margin: 0;
}

View File

@ -3,6 +3,7 @@ skip-if = buildapp == 'mulet' || buildapp == 'b2g'
[test_sanity.html]
[test_sanityException.html]
[test_sanityException2.html]
[test_sanityParams.html]
[test_sanityWindowSnapshot.html]
[test_SpecialPowersExtension.html]
[test_SpecialPowersExtension2.html]

View File

@ -0,0 +1,13 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for exposing test suite information</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script class="testbody" type="text/javascript">
ok(SimpleTest.harnessParameters, "Should have parameters.");
</script>
</body>
</html>

View File

@ -50,6 +50,10 @@ var isPrimaryTestWindow = !!parent.TestRunner || isSingleTestRun;
}
w = ancestor(w);
}
if (parentRunner) {
SimpleTest.harnessParameters = parentRunner.getParameterInfo();
}
})();
/* Helper functions pulled out of various MochiKit modules */
@ -841,7 +845,9 @@ SimpleTest.finish = function() {
if (parentRunner) {
/* We're running in an iframe, and the parent has a TestRunner */
parentRunner.testFinished(SimpleTest._tests);
} else {
}
if (!parentRunner || parentRunner.showTestReport) {
SpecialPowers.flushAllAppsLaunchable();
SpecialPowers.flushPermissions(function () {
SpecialPowers.flushPrefEnv(function() {

View File

@ -492,6 +492,14 @@ TestRunner.getLoadedTestURL = function () {
return prefix + $('testframe').contentWindow.location.pathname;
};
TestRunner.setParameterInfo = function (params) {
this._params = params;
};
TestRunner.getParameterInfo = function() {
return this._params;
};
/**
* TestRunner entry point.
*
@ -506,7 +514,14 @@ TestRunner.runTests = function (/*url...*/) {
TestRunner._urls = flattenArguments(arguments);
$('testframe').src="";
var singleTestRun = this._urls.length <= 1 && TestRunner.repeat <= 1;
TestRunner.showTestReport = singleTestRun;
var frame = $('testframe');
frame.src="";
if (singleTestRun) {
document.body.setAttribute("singletest", singleTestRun);
frame.removeAttribute("scrolling");
}
TestRunner._checkForHangs();
TestRunner.runNextTest();
};
@ -551,7 +566,10 @@ TestRunner.runNextTest = function() {
TestRunner._makeIframe(url, 0);
} else {
$("current-test").innerHTML = "<b>Finished</b>";
TestRunner._makeIframe("about:blank", 0);
// Only unload the last test to run if we're running more than one test.
if (TestRunner._urls.length > 1) {
TestRunner._makeIframe("about:blank", 0);
}
var passCount = parseInt($("pass-count").innerHTML, 10);
var failCount = parseInt($("fail-count").innerHTML, 10);
@ -694,6 +712,12 @@ TestRunner.testFinished = function(tests) {
TestRunner.updateUI(tests);
// Don't show the interstitial if we just run one test with no repeats:
if (TestRunner._urls.length == 1 && TestRunner.repeat <= 1) {
TestRunner.testUnloaded();
return;
}
var interstitialURL;
if ($('testframe').contentWindow.location.protocol == "chrome:") {
interstitialURL = "tests/SimpleTest/iframe-between-tests.html";

View File

@ -176,6 +176,7 @@ RunSet.runtests = function(e) {
my_tests[i] = tmp;
}
}
TestRunner.setParameterInfo(params);
TestRunner.runTests(my_tests);
}

View File

@ -6,6 +6,7 @@ support-files =
[browser_async.js]
[browser_head.js]
[browser_pass.js]
[browser_parameters.js]
[browser_popupNode.js]
[browser_popupNode_check.js]
[browser_privileges.js]

View File

@ -0,0 +1,4 @@
function test() {
ok(SimpleTest.harnessParameters, "Should have parameters");
}

View File

@ -22,3 +22,4 @@
[include:b2g/components/test/unit/xpcshell.ini]
[include:security/manager/ssl/tests/unit/xpcshell.ini]
[include:toolkit/devtools/discovery/tests/unit/xpcshell.ini]
[include:toolkit/devtools/security/tests/unit/xpcshell.ini]

View File

@ -544,10 +544,16 @@ Entry.prototype = {
},
_mediaToEnclosures: function Entry_mediaToEnclosures(mediaType, contentType) {
var content = this.fields.getPropertyAsInterface(mediaType, Ci.nsIArray);
var content;
if (contentType)
content = content.getPropertyAsInterface(contentType, Ci.nsIArray);
// If a contentType is specified, the mediaType is a simple propertybag,
// and the contentType is an array inside it.
if (contentType) {
var group = this.fields.getPropertyAsInterface(mediaType, Ci.nsIPropertyBag2);
content = group.getPropertyAsInterface(contentType, Ci.nsIArray);
} else {
content = this.fields.getPropertyAsInterface(mediaType, Ci.nsIArray);
}
for (var i = 0; i < content.length; ++i) {
var contentElement = content.queryElementAt(i, Ci.nsIWritablePropertyBag2);

View File

@ -5,20 +5,21 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += [
'server',
'acorn',
'apps',
'client',
'discovery',
'gcli',
'jsbeautify',
'sourcemap',
'webconsole',
'apps',
'styleinspector',
'acorn',
'pretty-fast',
'qrcode',
'transport',
'security',
'server',
'sourcemap',
'styleinspector',
'tern',
'discovery'
'transport',
'webconsole',
]
MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']

View File

@ -0,0 +1,498 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "LocalCertService.h"
#include "mozilla/ModuleUtils.h"
#include "mozilla/RefPtr.h"
#include "cert.h"
#include "CryptoTask.h"
#include "nsIPK11Token.h"
#include "nsIPK11TokenDB.h"
#include "nsIX509Cert.h"
#include "nsIX509CertDB.h"
#include "nsIX509CertValidity.h"
#include "nsLiteralString.h"
#include "nsProxyRelease.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "pk11pub.h"
#include "ScopedNSSTypes.h"
namespace mozilla {
class LocalCertTask : public CryptoTask
{
protected:
LocalCertTask(const nsACString& aNickname)
: mNickname(aNickname)
{
}
nsresult RemoveExisting()
{
// Search for any existing certs with this name and remove them
nsresult rv;
for (;;) {
ScopedCERTCertificate cert(
PK11_FindCertFromNickname(mNickname.get(), nullptr));
if (!cert) {
return NS_OK; // All done
}
// Found a cert, check if generated by this service
if (!cert->isRoot) {
return NS_ERROR_UNEXPECTED; // Should be self-signed
}
NS_NAMED_LITERAL_CSTRING(commonNamePrefix, "CN=");
nsAutoCString subjectNameFromNickname(commonNamePrefix + mNickname);
if (!subjectNameFromNickname.Equals(cert->subjectName)) {
return NS_ERROR_UNEXPECTED; // Subject should match nickname
}
if (!subjectNameFromNickname.Equals(cert->issuerName)) {
return NS_ERROR_UNEXPECTED; // Issuer should match nickname
}
rv = MapSECStatus(PK11_DeleteTokenCertAndKey(cert, nullptr));
if (NS_FAILED(rv)) {
return rv; // Some error, abort the loop
}
}
}
nsCString mNickname;
};
class LocalCertGetTask MOZ_FINAL : public LocalCertTask
{
public:
LocalCertGetTask(const nsACString& aNickname,
nsILocalCertGetCallback* aCallback)
: LocalCertTask(aNickname)
, mCallback(new nsMainThreadPtrHolder<nsILocalCertGetCallback>(aCallback))
, mCert(nullptr)
{
}
private:
virtual nsresult CalculateResult() MOZ_OVERRIDE
{
// Try to lookup an existing cert in the DB
nsresult rv = GetFromDB();
// Make a new one if getting fails
if (NS_FAILED(rv)) {
rv = Generate();
}
// If generation fails, we're out of luck
if (NS_FAILED(rv)) {
return rv;
}
// Validate cert, make a new one if it fails
rv = Validate();
if (NS_FAILED(rv)) {
rv = Generate();
}
// If generation fails, we're out of luck
if (NS_FAILED(rv)) {
return rv;
}
return NS_OK;
}
nsresult Generate()
{
nsresult rv;
// Get the key slot for generation later
ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
if (!slot) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Remove existing certs with this name (if any)
rv = RemoveExisting();
if (NS_FAILED(rv)) {
return rv;
}
// Generate a new cert
NS_NAMED_LITERAL_CSTRING(commonNamePrefix, "CN=");
nsAutoCString subjectNameStr(commonNamePrefix + mNickname);
ScopedCERTName subjectName(CERT_AsciiToName(subjectNameStr.get()));
if (!subjectName) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Use the well-known NIST P-256 curve
SECOidData* curveOidData = SECOID_FindOIDByTag(SEC_OID_SECG_EC_SECP256R1);
if (!curveOidData) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Get key params from the curve
ScopedAutoSECItem keyParams(2 + curveOidData->oid.len);
keyParams.data[0] = SEC_ASN1_OBJECT_ID;
keyParams.data[1] = curveOidData->oid.len;
memcpy(keyParams.data + 2, curveOidData->oid.data, curveOidData->oid.len);
// Generate cert key pair
ScopedSECKEYPrivateKey privateKey;
ScopedSECKEYPublicKey publicKey;
SECKEYPublicKey* tempPublicKey;
privateKey = PK11_GenerateKeyPair(slot, CKM_EC_KEY_PAIR_GEN, &keyParams,
&tempPublicKey, true /* token */,
true /* sensitive */, nullptr);
if (!privateKey) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
publicKey = tempPublicKey;
// Create subject public key info and cert request
ScopedCERTSubjectPublicKeyInfo spki(
SECKEY_CreateSubjectPublicKeyInfo(publicKey));
if (!spki) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
ScopedCERTCertificateRequest certRequest(
CERT_CreateCertificateRequest(subjectName, spki, nullptr));
if (!certRequest) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Valid from one day before to 1 year after
static const PRTime oneDay = PRTime(PR_USEC_PER_SEC)
* PRTime(60) // sec
* PRTime(60) // min
* PRTime(24); // hours
PRTime now = PR_Now();
PRTime notBefore = now - oneDay;
PRTime notAfter = now + (PRTime(365) * oneDay);
ScopedCERTValidity validity(CERT_CreateValidity(notBefore, notAfter));
if (!validity) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Generate random serial
unsigned long serial;
// This serial in principle could collide, but it's unlikely
rv = MapSECStatus(
PK11_GenerateRandomOnSlot(slot,
reinterpret_cast<unsigned char *>(&serial),
sizeof(serial)));
if (NS_FAILED(rv)) {
return rv;
}
// Create the cert from these pieces
ScopedCERTCertificate cert(
CERT_CreateCertificate(serial, subjectName, validity, certRequest));
if (!cert) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Update the cert version to X509v3
if (!cert->version.data) {
return NS_ERROR_INVALID_POINTER;
}
*(cert->version.data) = SEC_CERTIFICATE_VERSION_3;
cert->version.len = 1;
// Set cert signature algorithm
PLArenaPool* arena = cert->arena;
if (!arena) {
return NS_ERROR_INVALID_POINTER;
}
rv = MapSECStatus(
SECOID_SetAlgorithmID(arena, &cert->signature,
SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, 0));
if (NS_FAILED(rv)) {
return rv;
}
// Encode and self-sign the cert
ScopedSECItem certDER(
SEC_ASN1EncodeItem(nullptr, nullptr, cert,
SEC_ASN1_GET(CERT_CertificateTemplate)));
if (!certDER) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
rv = MapSECStatus(
SEC_DerSignData(arena, &cert->derCert, certDER->data, certDER->len,
privateKey,
SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE));
if (NS_FAILED(rv)) {
return rv;
}
// Create a CERTCertificate from the signed data
ScopedCERTCertificate certFromDER(
CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &cert->derCert, nullptr,
true /* perm */, true /* copyDER */));
if (!certFromDER) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// Save the cert in the DB
rv = MapSECStatus(PK11_ImportCert(slot, certFromDER, CK_INVALID_HANDLE,
mNickname.get(), false /* unused */));
if (NS_FAILED(rv)) {
return rv;
}
// We should now have cert in the DB, read it back in nsIX509Cert form
return GetFromDB();
}
nsresult GetFromDB()
{
nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID);
if (!certDB) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIX509Cert> certFromDB;
nsresult rv;
rv = certDB->FindCertByNickname(nullptr, NS_ConvertASCIItoUTF16(mNickname),
getter_AddRefs(certFromDB));
if (NS_FAILED(rv)) {
return rv;
}
mCert = certFromDB;
return NS_OK;
}
nsresult Validate()
{
// Verify cert is self-signed
bool selfSigned;
nsresult rv = mCert->GetIsSelfSigned(&selfSigned);
if (NS_FAILED(rv)) {
return rv;
}
if (!selfSigned) {
return NS_ERROR_FAILURE;
}
// Check that subject and issuer match nickname
nsXPIDLString subjectName;
nsXPIDLString issuerName;
mCert->GetSubjectName(subjectName);
mCert->GetIssuerName(issuerName);
if (!subjectName.Equals(issuerName)) {
return NS_ERROR_FAILURE;
}
NS_NAMED_LITERAL_STRING(commonNamePrefix, "CN=");
nsAutoString subjectNameFromNickname(
commonNamePrefix + NS_ConvertASCIItoUTF16(mNickname));
if (!subjectName.Equals(subjectNameFromNickname)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIX509CertValidity> validity;
mCert->GetValidity(getter_AddRefs(validity));
PRTime notBefore, notAfter;
validity->GetNotBefore(&notBefore);
validity->GetNotAfter(&notAfter);
// Ensure cert will last at least one more day
static const PRTime oneDay = PRTime(PR_USEC_PER_SEC)
* PRTime(60) // sec
* PRTime(60) // min
* PRTime(24); // hours
PRTime now = PR_Now();
if (notBefore > now ||
notAfter < (now - oneDay)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
virtual void ReleaseNSSResources() {}
virtual void CallCallback(nsresult rv)
{
(void) mCallback->HandleCert(mCert, rv);
}
nsMainThreadPtrHandle<nsILocalCertGetCallback> mCallback;
nsCOMPtr<nsIX509Cert> mCert; // out
};
class LocalCertRemoveTask MOZ_FINAL : public LocalCertTask
{
public:
LocalCertRemoveTask(const nsACString& aNickname,
nsILocalCertCallback* aCallback)
: LocalCertTask(aNickname)
, mCallback(new nsMainThreadPtrHolder<nsILocalCertCallback>(aCallback))
{
}
private:
virtual nsresult CalculateResult() MOZ_OVERRIDE
{
return RemoveExisting();
}
virtual void ReleaseNSSResources() {}
virtual void CallCallback(nsresult rv)
{
(void) mCallback->HandleResult(rv);
}
nsMainThreadPtrHandle<nsILocalCertCallback> mCallback;
};
NS_IMPL_ISUPPORTS(LocalCertService, nsILocalCertService)
LocalCertService::LocalCertService()
{
}
LocalCertService::~LocalCertService()
{
}
nsresult
LocalCertService::LoginToKeySlot()
{
nsresult rv;
// Get access to key slot
ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
if (!slot) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// If no user password yet, set it an empty one
if (PK11_NeedUserInit(slot)) {
rv = MapSECStatus(PK11_InitPin(slot, "", ""));
if (NS_FAILED(rv)) {
return rv;
}
}
// If user has a password set, prompt to login
if (PK11_NeedLogin(slot) && !PK11_IsLoggedIn(slot, nullptr)) {
// Switching to XPCOM to get the UI prompt that PSM owns
nsCOMPtr<nsIPK11TokenDB> tokenDB =
do_GetService(NS_PK11TOKENDB_CONTRACTID);
if (!tokenDB) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIPK11Token> keyToken;
tokenDB->GetInternalKeyToken(getter_AddRefs(keyToken));
if (!keyToken) {
return NS_ERROR_FAILURE;
}
// Prompt the user to login
return keyToken->Login(false /* force */);
}
return NS_OK;
}
NS_IMETHODIMP
LocalCertService::GetOrCreateCert(const nsACString& aNickname,
nsILocalCertGetCallback* aCallback)
{
if (NS_WARN_IF(aNickname.IsEmpty())) {
return NS_ERROR_INVALID_ARG;
}
if (NS_WARN_IF(!aCallback)) {
return NS_ERROR_INVALID_POINTER;
}
// Before sending off the task, login to key slot if needed
nsresult rv = LoginToKeySlot();
if (NS_FAILED(rv)) {
aCallback->HandleCert(nullptr, rv);
return NS_OK;
}
RefPtr<LocalCertGetTask> task(new LocalCertGetTask(aNickname, aCallback));
return task->Dispatch("LocalCertGet");
}
NS_IMETHODIMP
LocalCertService::RemoveCert(const nsACString& aNickname,
nsILocalCertCallback* aCallback)
{
if (NS_WARN_IF(aNickname.IsEmpty())) {
return NS_ERROR_INVALID_ARG;
}
if (NS_WARN_IF(!aCallback)) {
return NS_ERROR_INVALID_POINTER;
}
// Before sending off the task, login to key slot if needed
nsresult rv = LoginToKeySlot();
if (NS_FAILED(rv)) {
aCallback->HandleResult(rv);
return NS_OK;
}
RefPtr<LocalCertRemoveTask> task(
new LocalCertRemoveTask(aNickname, aCallback));
return task->Dispatch("LocalCertRm");
}
NS_IMETHODIMP
LocalCertService::GetLoginPromptRequired(bool* aRequired)
{
nsresult rv;
// Get access to key slot
ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
if (!slot) {
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
// If no user password yet, set it an empty one
if (PK11_NeedUserInit(slot)) {
rv = MapSECStatus(PK11_InitPin(slot, "", ""));
if (NS_FAILED(rv)) {
return rv;
}
}
*aRequired = PK11_NeedLogin(slot) && !PK11_IsLoggedIn(slot, nullptr);
return NS_OK;
}
#define LOCALCERTSERVICE_CID \
{ 0x47402be2, 0xe653, 0x45d0, \
{ 0x8d, 0xaa, 0x9f, 0x0d, 0xce, 0x0a, 0xc1, 0x48 } }
NS_GENERIC_FACTORY_CONSTRUCTOR(LocalCertService)
NS_DEFINE_NAMED_CID(LOCALCERTSERVICE_CID);
static const Module::CIDEntry kLocalCertServiceCIDs[] = {
{ &kLOCALCERTSERVICE_CID, false, nullptr, LocalCertServiceConstructor },
{ nullptr }
};
static const Module::ContractIDEntry kLocalCertServiceContracts[] = {
{ LOCALCERTSERVICE_CONTRACTID, &kLOCALCERTSERVICE_CID },
{ nullptr }
};
static const Module kLocalCertServiceModule = {
Module::kVersion,
kLocalCertServiceCIDs,
kLocalCertServiceContracts
};
NSMODULE_DEFN(LocalCertServiceModule) = &kLocalCertServiceModule;
} // namespace mozilla

View File

@ -0,0 +1,27 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef LocalCertService_h
#define LocalCertService_h
#include "nsILocalCertService.h"
namespace mozilla {
class LocalCertService MOZ_FINAL : public nsILocalCertService
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSILOCALCERTSERVICE
LocalCertService();
private:
nsresult LoginToKeySlot();
~LocalCertService();
};
} // namespace mozilla
#endif // LocalCertService_h

View File

@ -0,0 +1,21 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
XPIDL_SOURCES += [
'nsILocalCertService.idl',
]
XPIDL_MODULE = 'devtools_security'
UNIFIED_SOURCES += [
'LocalCertService.cpp',
]
FAIL_ON_WARNINGS = True
FINAL_LIBRARY = 'xul'

View File

@ -0,0 +1,69 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIX509Cert;
interface nsILocalCertGetCallback;
interface nsILocalCertCallback;
[scriptable, uuid(9702fdd4-4c2c-439c-ba2e-19cda018eb99)]
interface nsILocalCertService : nsISupports
{
/**
* Get or create a new self-signed X.509 cert to represent this device over a
* secure transport, like TLS.
*
* The cert is stored permanently in the profile's key store after first use,
* and is valid for 1 year. If an expired or otherwise invalid cert is found
* with the nickname supplied here, it is removed and a new one is made.
*
* @param nickname Nickname that identifies the cert
* @param cb Callback to be notified with the result
*/
void getOrCreateCert(in ACString nickname, in nsILocalCertGetCallback cb);
/**
* Remove a X.509 cert with the given nickname.
*
* @param nickname Nickname that identifies the cert
* @param cb Callback to be notified with the result
*/
void removeCert(in ACString nickname, in nsILocalCertCallback cb);
/**
* Whether calling |getOrCreateCert| or |removeCert| will trigger a login
* prompt to be displayed. Generally this happens if the user has set a
* master password, but has not yet logged in.
*/
readonly attribute boolean loginPromptRequired;
};
[scriptable, uuid(cc09633e-7c70-4093-a9cf-79ab676ca8a9)]
interface nsILocalCertGetCallback : nsISupports
{
/**
* Called with the result of the getOrCreateCert operation above.
*
* @param cert Requested cert, or null if some error
* @param result Result code from the get operation
*/
void handleCert(in nsIX509Cert cert, in nsresult result);
};
[scriptable, uuid(518124e9-55e6-4e23-97c0-4995b3a1bec6)]
interface nsILocalCertCallback : nsISupports
{
/**
* Called with the result of the removeCert operation above.
*
* @param result Result code from the operation
*/
void handleResult(in nsresult result);
};
%{ C++
#define LOCALCERTSERVICE_CONTRACTID \
"@mozilla.org/security/local-cert-service;1"
%}

View File

@ -0,0 +1,87 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
const { Promise: promise } =
Cu.import("resource://gre/modules/Promise.jsm", {});
const certService = Cc["@mozilla.org/security/local-cert-service;1"]
.getService(Ci.nsILocalCertService);
const gNickname = "devtools";
function run_test() {
// Need profile dir to store the key / cert
do_get_profile();
// Ensure PSM is initialized
Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
run_next_test();
}
function getOrCreateCert(nickname) {
let deferred = promise.defer();
certService.getOrCreateCert(nickname, {
handleCert: function(c, rv) {
if (rv) {
deferred.reject(rv);
return;
}
deferred.resolve(c);
}
});
return deferred.promise;
}
function removeCert(nickname) {
let deferred = promise.defer();
certService.removeCert(nickname, {
handleResult: function(rv) {
if (rv) {
deferred.reject(rv);
return;
}
deferred.resolve();
}
});
return deferred.promise;
}
add_task(function*() {
// No master password, so prompt required here
ok(!certService.loginPromptRequired);
let certA = yield getOrCreateCert(gNickname);
equal(certA.nickname, gNickname);
// Getting again should give the same cert
let certB = yield getOrCreateCert(gNickname);
equal(certB.nickname, gNickname);
// Should be matching instances
ok(certA.equals(certB));
// Check a few expected attributes
ok(certA.isSelfSigned);
equal(certA.certType, Ci.nsIX509Cert.USER_CERT);
// New nickname should give a different cert
let diffNameCert = yield getOrCreateCert("cool-stuff");
ok(!diffNameCert.equals(certA));
// Remove the cert, and get a new one again
yield removeCert(gNickname);
let newCert = yield getOrCreateCert(gNickname);
ok(!newCert.equals(certA));
// Drop all cert references and GC
let serial = newCert.serialNumber;
certA = certB = diffNameCert = newCert = null;
Cu.forceGC();
Cu.forceCC();
// Should still get the same cert back
let certAfterGC = yield getOrCreateCert(gNickname);
equal(certAfterGC.serialNumber, serial);
});

View File

@ -0,0 +1,5 @@
[DEFAULT]
head =
tail =
[test_cert.js]

View File

@ -16,6 +16,9 @@ SimpleTest.waitForExplicitFinish();
let gState;
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let {MAX_AUTOCOMPLETE_ATTEMPTS,MAX_AUTOCOMPLETIONS} = devtools.require("devtools/toolkit/webconsole/utils");
function startTest()
{
removeEventListener("load", startTest);
@ -35,10 +38,21 @@ function onAttach(aState, aResponse)
top.foobarObject.omgstr = "foobarz" +
(new Array(DebuggerServer.LONG_STRING_LENGTH * 2)).join("abb");
top.largeObject1 = Object.create(null);
for (let i = 0; i < MAX_AUTOCOMPLETE_ATTEMPTS + 1; i++) {
top.largeObject1['a' + i] = i;
}
top.largeObject2 = Object.create(null);
for (let i = 0; i < MAX_AUTOCOMPLETIONS * 2; i++) {
top.largeObject2['a' + i] = i;
}
gState = aState;
let tests = [doAutocomplete1, doAutocomplete2, doAutocomplete3,
doAutocomplete4, doSimpleEval, doWindowEval, doEvalWithException,
doAutocomplete4, doAutocompleteLarge1, doAutocompleteLarge2,
doSimpleEval, doWindowEval, doEvalWithException,
doEvalWithHelper, doEvalString, doEvalLongString];
runTests(tests, testEnd);
}
@ -112,6 +126,39 @@ function onAutocomplete4(aResponse)
nextTest();
}
function doAutocompleteLarge1()
{
// Check that completion requests with too large objects will
// have no suggestions.
info("test autocomplete for 'window.largeObject1.'");
gState.client.autocomplete("window.largeObject1.", 20, onAutocompleteLarge1);
}
function onAutocompleteLarge1(aResponse)
{
ok(!aResponse.matchProp, "matchProp");
info (aResponse.matches.join("|"));
is(aResponse.matches.length, 0, "Bailed out with too many properties");
nextTest();
}
function doAutocompleteLarge2()
{
// Check that completion requests with pretty large objects will
// have MAX_AUTOCOMPLETIONS suggestions
info("test autocomplete for 'window.largeObject2.'");
gState.client.autocomplete("window.largeObject2.", 20, onAutocompleteLarge2);
}
function onAutocompleteLarge2(aResponse)
{
ok(!aResponse.matchProp, "matchProp");
is(aResponse.matches.length, MAX_AUTOCOMPLETIONS, "matches.length is MAX_AUTOCOMPLETIONS");
nextTest();
}
function doSimpleEval()
{
info("test eval '2+2'");

View File

@ -33,6 +33,15 @@ const REGEX_MATCH_FUNCTION_ARGS = /^\(?function\s*[^\s(]*\s*\((.+?)\)/;
// Number of terminal entries for the self-xss prevention to go away
const CONSOLE_ENTRY_THRESHOLD = 5
// Provide an easy way to bail out of even attempting an autocompletion
// if an object has way too many properties. Protects against large objects
// with numeric values that wouldn't be tallied towards MAX_AUTOCOMPLETIONS.
const MAX_AUTOCOMPLETE_ATTEMPTS = exports.MAX_AUTOCOMPLETE_ATTEMPTS = 100000;
// Prevent iterating over too many properties during autocomplete suggestions.
const MAX_AUTOCOMPLETIONS = exports.MAX_AUTOCOMPLETIONS = 1500;
let WebConsoleUtils = {
/**
* Convenience function to unwrap a wrapped object.
@ -710,8 +719,6 @@ const OPEN_CLOSE_BODY = {
"(": ")",
};
const MAX_COMPLETIONS = 1500;
/**
* Analyses a given string to find the last statement that is interesting for
* later completion.
@ -1044,11 +1051,22 @@ function getMatchedProps(aObj, aMatch)
function getMatchedProps_impl(aObj, aMatch, {chainIterator, getProperties})
{
let matches = new Set();
let numProps = 0;
// We need to go up the prototype chain.
let iter = chainIterator(aObj);
for (let obj of iter) {
let props = getProperties(obj);
numProps += props.length;
// If there are too many properties to event attempt autocompletion,
// or if we have already added the max number, then stop looping
// and return the partial set that has already been discovered.
if (numProps >= MAX_AUTOCOMPLETE_ATTEMPTS ||
matches.size >= MAX_AUTOCOMPLETIONS) {
break;
}
for (let i = 0; i < props.length; i++) {
let prop = props[i];
if (prop.indexOf(aMatch) != 0) {
@ -1062,14 +1080,10 @@ function getMatchedProps_impl(aObj, aMatch, {chainIterator, getProperties})
matches.add(prop);
}
if (matches.size > MAX_COMPLETIONS) {
if (matches.size >= MAX_AUTOCOMPLETIONS) {
break;
}
}
if (matches.size > MAX_COMPLETIONS) {
break;
}
}
return {