mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge fx-team to m-c. a=merge
This commit is contained in:
commit
6a8f66fd96
@ -102,10 +102,10 @@ pref("app.update.log", false);
|
||||
pref("app.update.backgroundMaxErrors", 10);
|
||||
|
||||
// The aus update xml certificate checks for application update are disabled on
|
||||
// Windows since the mar signature check which is currently only implemented on
|
||||
// Windows is sufficient for preventing us from applying a mar that is not
|
||||
// Windows and Mac OS X since the mar signature check are implemented on these
|
||||
// platforms and is sufficient to prevent us from applying a mar that is not
|
||||
// valid.
|
||||
#ifdef XP_WIN
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX)
|
||||
pref("app.update.cert.requireBuiltIn", false);
|
||||
pref("app.update.cert.checkAttributes", false);
|
||||
#else
|
||||
|
@ -568,11 +568,16 @@ let AboutReaderListener = {
|
||||
}
|
||||
},
|
||||
updateReaderButton: function() {
|
||||
if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader) {
|
||||
if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader ||
|
||||
!(content.document instanceof content.HTMLDocument) ||
|
||||
content.document.mozSyntheticDocument) {
|
||||
return;
|
||||
}
|
||||
let isArticle = ReaderMode.isProbablyReaderable(content.document);
|
||||
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: isArticle });
|
||||
// Only send updates when there are articles; there's no point updating with
|
||||
// |false| all the time.
|
||||
if (ReaderMode.isProbablyReaderable(content.document)) {
|
||||
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true });
|
||||
}
|
||||
},
|
||||
};
|
||||
AboutReaderListener.init();
|
||||
|
@ -54,7 +54,7 @@ If you install eslint and the react plugin globally:
|
||||
|
||||
You can also run it by hand in the browser/components/loop directory:
|
||||
|
||||
eslint -ext .js -ext .jsx .
|
||||
eslint -ext .js -ext .jsx --ext .jsm .
|
||||
|
||||
Front-End Unit Tests
|
||||
====================
|
||||
|
@ -11,6 +11,7 @@ loop.conversationViews = (function(mozL10n) {
|
||||
|
||||
var CALL_STATES = loop.store.CALL_STATES;
|
||||
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
||||
var REST_ERRNOS = loop.shared.utils.REST_ERRNOS;
|
||||
var WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS;
|
||||
var sharedActions = loop.shared.actions;
|
||||
@ -322,7 +323,8 @@ loop.conversationViews = (function(mozL10n) {
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
cancelCall: React.PropTypes.func.isRequired
|
||||
cancelCall: React.PropTypes.func.isRequired,
|
||||
failureReason: React.PropTypes.string
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
@ -332,9 +334,18 @@ loop.conversationViews = (function(mozL10n) {
|
||||
render: function() {
|
||||
this.setTitle(mozL10n.get("generic_failure_title"));
|
||||
|
||||
var errorString;
|
||||
switch (this.props.failureReason) {
|
||||
case FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA:
|
||||
errorString = mozL10n.get("no_media_failure_message");
|
||||
break;
|
||||
default:
|
||||
errorString = mozL10n.get("generic_failure_title");
|
||||
}
|
||||
|
||||
return (
|
||||
React.createElement("div", {className: "call-window"},
|
||||
React.createElement("h2", null, mozL10n.get("generic_failure_title")),
|
||||
React.createElement("h2", null, errorString),
|
||||
|
||||
React.createElement("div", {className: "btn-group call-action-group"},
|
||||
React.createElement("button", {className: "btn btn-cancel",
|
||||
@ -467,21 +478,22 @@ loop.conversationViews = (function(mozL10n) {
|
||||
},
|
||||
|
||||
_getTitleMessage: function() {
|
||||
var callStateReason =
|
||||
this.getStoreState().callStateReason;
|
||||
switch (this.getStoreState().callStateReason) {
|
||||
case WEBSOCKET_REASONS.REJECT:
|
||||
case WEBSOCKET_REASONS.BUSY:
|
||||
case REST_ERRNOS.USER_UNAVAILABLE:
|
||||
var contactDisplayName = _getContactDisplayName(this.props.contact);
|
||||
if (contactDisplayName.length) {
|
||||
return mozL10n.get(
|
||||
"contact_unavailable_title",
|
||||
{"contactName": contactDisplayName});
|
||||
}
|
||||
|
||||
if (callStateReason === WEBSOCKET_REASONS.REJECT || callStateReason === WEBSOCKET_REASONS.BUSY ||
|
||||
callStateReason === REST_ERRNOS.USER_UNAVAILABLE) {
|
||||
var contactDisplayName = _getContactDisplayName(this.props.contact);
|
||||
if (contactDisplayName.length) {
|
||||
return mozL10n.get(
|
||||
"contact_unavailable_title",
|
||||
{"contactName": contactDisplayName});
|
||||
}
|
||||
|
||||
return mozL10n.get("generic_contact_unavailable_title");
|
||||
} else {
|
||||
return mozL10n.get("generic_failure_title");
|
||||
return mozL10n.get("generic_contact_unavailable_title");
|
||||
case FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA:
|
||||
return mozL10n.get("no_media_failure_message");
|
||||
default:
|
||||
return mozL10n.get("generic_failure_title");
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -11,6 +11,7 @@ loop.conversationViews = (function(mozL10n) {
|
||||
|
||||
var CALL_STATES = loop.store.CALL_STATES;
|
||||
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
||||
var REST_ERRNOS = loop.shared.utils.REST_ERRNOS;
|
||||
var WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS;
|
||||
var sharedActions = loop.shared.actions;
|
||||
@ -322,7 +323,8 @@ loop.conversationViews = (function(mozL10n) {
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
cancelCall: React.PropTypes.func.isRequired
|
||||
cancelCall: React.PropTypes.func.isRequired,
|
||||
failureReason: React.PropTypes.string
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
@ -332,9 +334,18 @@ loop.conversationViews = (function(mozL10n) {
|
||||
render: function() {
|
||||
this.setTitle(mozL10n.get("generic_failure_title"));
|
||||
|
||||
var errorString;
|
||||
switch (this.props.failureReason) {
|
||||
case FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA:
|
||||
errorString = mozL10n.get("no_media_failure_message");
|
||||
break;
|
||||
default:
|
||||
errorString = mozL10n.get("generic_failure_title");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="call-window">
|
||||
<h2>{mozL10n.get("generic_failure_title")}</h2>
|
||||
<h2>{errorString}</h2>
|
||||
|
||||
<div className="btn-group call-action-group">
|
||||
<button className="btn btn-cancel"
|
||||
@ -467,21 +478,22 @@ loop.conversationViews = (function(mozL10n) {
|
||||
},
|
||||
|
||||
_getTitleMessage: function() {
|
||||
var callStateReason =
|
||||
this.getStoreState().callStateReason;
|
||||
switch (this.getStoreState().callStateReason) {
|
||||
case WEBSOCKET_REASONS.REJECT:
|
||||
case WEBSOCKET_REASONS.BUSY:
|
||||
case REST_ERRNOS.USER_UNAVAILABLE:
|
||||
var contactDisplayName = _getContactDisplayName(this.props.contact);
|
||||
if (contactDisplayName.length) {
|
||||
return mozL10n.get(
|
||||
"contact_unavailable_title",
|
||||
{"contactName": contactDisplayName});
|
||||
}
|
||||
|
||||
if (callStateReason === WEBSOCKET_REASONS.REJECT || callStateReason === WEBSOCKET_REASONS.BUSY ||
|
||||
callStateReason === REST_ERRNOS.USER_UNAVAILABLE) {
|
||||
var contactDisplayName = _getContactDisplayName(this.props.contact);
|
||||
if (contactDisplayName.length) {
|
||||
return mozL10n.get(
|
||||
"contact_unavailable_title",
|
||||
{"contactName": contactDisplayName});
|
||||
}
|
||||
|
||||
return mozL10n.get("generic_contact_unavailable_title");
|
||||
} else {
|
||||
return mozL10n.get("generic_failure_title");
|
||||
return mozL10n.get("generic_contact_unavailable_title");
|
||||
case FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA:
|
||||
return mozL10n.get("no_media_failure_message");
|
||||
default:
|
||||
return mozL10n.get("generic_failure_title");
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -251,7 +251,8 @@ loop.roomViews = (function(mozL10n) {
|
||||
// FULL case should never happen on desktop.
|
||||
return (
|
||||
React.createElement(loop.conversationViews.GenericFailureView, {
|
||||
cancelCall: this.closeWindow})
|
||||
cancelCall: this.closeWindow,
|
||||
failureReason: this.state.failureReason})
|
||||
);
|
||||
}
|
||||
case ROOM_STATES.ENDED: {
|
||||
|
@ -251,7 +251,8 @@ loop.roomViews = (function(mozL10n) {
|
||||
// FULL case should never happen on desktop.
|
||||
return (
|
||||
<loop.conversationViews.GenericFailureView
|
||||
cancelCall={this.closeWindow} />
|
||||
cancelCall={this.closeWindow}
|
||||
failureReason={this.state.failureReason} />
|
||||
);
|
||||
}
|
||||
case ROOM_STATES.ENDED: {
|
||||
|
@ -13,6 +13,7 @@ describe("loop.conversationViews", function () {
|
||||
|
||||
var CALL_STATES = loop.store.CALL_STATES;
|
||||
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
||||
var REST_ERRNOS = loop.shared.utils.REST_ERRNOS;
|
||||
var WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS;
|
||||
|
||||
@ -450,6 +451,15 @@ describe("loop.conversationViews", function () {
|
||||
{contactName: loop.conversationViews._getContactDisplayName(contact)});
|
||||
});
|
||||
|
||||
it("should show 'no media' when the reason is FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA",
|
||||
function () {
|
||||
store.setStoreState({callStateReason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA});
|
||||
|
||||
view = mountTestComponent({contact: contact});
|
||||
|
||||
sinon.assert.calledWithExactly(document.mozL10n.get, "no_media_failure_message");
|
||||
});
|
||||
|
||||
it("should display a generic contact unavailable msg when the reason is" +
|
||||
" WEBSOCKET_REASONS.BUSY and no display name is available", function() {
|
||||
store.setStoreState({callStateReason: WEBSOCKET_REASONS.BUSY});
|
||||
@ -887,6 +897,11 @@ describe("loop.conversationViews", function () {
|
||||
describe("GenericFailureView", function() {
|
||||
var view, fakeAudio;
|
||||
|
||||
function mountTestComponent(props) {
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(loop.conversationViews.GenericFailureView, props));
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
fakeAudio = {
|
||||
play: sinon.spy(),
|
||||
@ -895,14 +910,11 @@ describe("loop.conversationViews", function () {
|
||||
};
|
||||
navigator.mozLoop.doNotDisturb = false;
|
||||
sandbox.stub(window, "Audio").returns(fakeAudio);
|
||||
|
||||
view = TestUtils.renderIntoDocument(
|
||||
React.createElement(loop.conversationViews.GenericFailureView, {
|
||||
cancelCall: function() {}
|
||||
}));
|
||||
});
|
||||
|
||||
it("should play a failure sound, once", function() {
|
||||
view = mountTestComponent({cancelCall: function() {}});
|
||||
|
||||
sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
|
||||
sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,
|
||||
"failure", sinon.match.func);
|
||||
@ -911,7 +923,24 @@ describe("loop.conversationViews", function () {
|
||||
});
|
||||
|
||||
it("should set the title to generic_failure_title", function() {
|
||||
view = mountTestComponent({cancelCall: function() {}});
|
||||
|
||||
expect(fakeWindow.document.title).eql("generic_failure_title");
|
||||
});
|
||||
|
||||
it("should show 'no media' for FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA reason", function() {
|
||||
view = mountTestComponent({
|
||||
cancelCall: function() {},
|
||||
failureReason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
|
||||
});
|
||||
|
||||
expect(view.getDOMNode().querySelector("h2").textContent).eql("no_media_failure_message");
|
||||
});
|
||||
|
||||
it("should show 'generic_failure_title' when no reason is specified", function() {
|
||||
view = mountTestComponent({cancelCall: function() {}});
|
||||
|
||||
expect(view.getDOMNode().querySelector("h2").textContent).eql("generic_failure_title");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -346,7 +346,7 @@ ReadingListImpl.prototype = {
|
||||
let item = this._itemFromRecord(record);
|
||||
this._callListeners("onItemAdded", item);
|
||||
let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
|
||||
mm.broadcastAsyncMessage("Reader:Added", item);
|
||||
mm.broadcastAsyncMessage("Reader:Added", item.toJSON());
|
||||
return item;
|
||||
}),
|
||||
|
||||
@ -427,7 +427,7 @@ ReadingListImpl.prototype = {
|
||||
}
|
||||
this._invalidateIterators();
|
||||
let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
|
||||
mm.broadcastAsyncMessage("Reader:Removed", item);
|
||||
mm.broadcastAsyncMessage("Reader:Removed", item.toJSON());
|
||||
this._callListeners("onItemDeleted", item);
|
||||
}),
|
||||
|
||||
|
@ -21,6 +21,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "ReadingList",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ServerClient",
|
||||
"resource:///modules/readinglist/ServerClient.jsm");
|
||||
|
||||
// The maximum number of sub-requests per POST /batch supported by the server.
|
||||
// See http://readinglist.readthedocs.org/en/latest/api/batch.html.
|
||||
const BATCH_REQUEST_LIMIT = 25;
|
||||
|
||||
// The Last-Modified header of server responses is stored here.
|
||||
const SERVER_LAST_MODIFIED_HEADER_PREF = "readinglist.sync.serverLastModified";
|
||||
|
||||
@ -180,8 +184,6 @@ SyncImpl.prototype = {
|
||||
|
||||
// Send the request.
|
||||
let request = {
|
||||
method: "POST",
|
||||
path: "/batch",
|
||||
body: {
|
||||
defaults: {
|
||||
method: "PATCH",
|
||||
@ -189,7 +191,7 @@ SyncImpl.prototype = {
|
||||
requests: requests,
|
||||
},
|
||||
};
|
||||
let batchResponse = yield this._sendRequest(request);
|
||||
let batchResponse = yield this._postBatch(request);
|
||||
if (batchResponse.status != 200) {
|
||||
this._handleUnexpectedResponse("uploading changes", batchResponse);
|
||||
return;
|
||||
@ -244,8 +246,6 @@ SyncImpl.prototype = {
|
||||
|
||||
// Send the request.
|
||||
let request = {
|
||||
method: "POST",
|
||||
path: "/batch",
|
||||
body: {
|
||||
defaults: {
|
||||
method: "POST",
|
||||
@ -254,7 +254,7 @@ SyncImpl.prototype = {
|
||||
requests: requests,
|
||||
},
|
||||
};
|
||||
let batchResponse = yield this._sendRequest(request);
|
||||
let batchResponse = yield this._postBatch(request);
|
||||
if (batchResponse.status != 200) {
|
||||
this._handleUnexpectedResponse("uploading new items", batchResponse);
|
||||
return;
|
||||
@ -308,8 +308,6 @@ SyncImpl.prototype = {
|
||||
|
||||
// Send the request.
|
||||
let request = {
|
||||
method: "POST",
|
||||
path: "/batch",
|
||||
body: {
|
||||
defaults: {
|
||||
method: "DELETE",
|
||||
@ -317,7 +315,7 @@ SyncImpl.prototype = {
|
||||
requests: requests,
|
||||
},
|
||||
};
|
||||
let batchResponse = yield this._sendRequest(request);
|
||||
let batchResponse = yield this._postBatch(request);
|
||||
if (batchResponse.status != 200) {
|
||||
this._handleUnexpectedResponse("uploading deleted items", batchResponse);
|
||||
return;
|
||||
@ -504,6 +502,50 @@ SyncImpl.prototype = {
|
||||
return response;
|
||||
}),
|
||||
|
||||
/**
|
||||
* The server limits the number of sub-requests in POST /batch'es to
|
||||
* BATCH_REQUEST_LIMIT. This method takes an arbitrarily big batch request
|
||||
* and breaks it apart into many individual batch requests in order to stay
|
||||
* within the limit.
|
||||
*
|
||||
* @param bigRequest The same type of request object that _sendRequest takes.
|
||||
* Since it's a POST /batch request, its `body` should have a
|
||||
* `requests` property whose value is an array of sub-requests.
|
||||
* `method` and `path` are automatically filled.
|
||||
* @return Promise<response> Resolved when all requests complete with 200s, or
|
||||
* when the first response that is not a 200 is received. In the
|
||||
* first case, the resolved response is a combination of all the
|
||||
* server responses, and response.body.responses contains the sub-
|
||||
* responses for all the sub-requests in bigRequest. In the second
|
||||
* case, the resolved response is the non-200 response straight from
|
||||
* the server.
|
||||
*/
|
||||
_postBatch: Task.async(function* (bigRequest) {
|
||||
log.debug("Sending batch requests");
|
||||
let allSubResponses = [];
|
||||
let remainingSubRequests = bigRequest.body.requests;
|
||||
while (remainingSubRequests.length) {
|
||||
let request = Object.assign({}, bigRequest);
|
||||
request.method = "POST";
|
||||
request.path = "/batch";
|
||||
request.body.requests =
|
||||
remainingSubRequests.splice(0, BATCH_REQUEST_LIMIT);
|
||||
let response = yield this._sendRequest(request);
|
||||
if (response.status != 200) {
|
||||
return response;
|
||||
}
|
||||
allSubResponses = allSubResponses.concat(response.body.responses);
|
||||
}
|
||||
let bigResponse = {
|
||||
status: 200,
|
||||
body: {
|
||||
responses: allSubResponses,
|
||||
},
|
||||
};
|
||||
log.debug("All batch requests successfully sent");
|
||||
return bigResponse;
|
||||
}),
|
||||
|
||||
_handleUnexpectedResponse(contextMsgFragment, response) {
|
||||
log.error(`Unexpected response ${contextMsgFragment}`, response);
|
||||
},
|
||||
|
@ -40,6 +40,8 @@ let AnimationsPanel = {
|
||||
|
||||
this.startListeners();
|
||||
|
||||
yield this.createPlayerWidgets();
|
||||
|
||||
this.initialized.resolve();
|
||||
|
||||
this.emit(this.PANEL_INITIALIZED);
|
||||
|
@ -2,6 +2,7 @@
|
||||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
doc_body_animation.html
|
||||
doc_frame_script.js
|
||||
doc_simple_animation.html
|
||||
head.js
|
||||
@ -12,6 +13,7 @@ support-files =
|
||||
[browser_animation_participate_in_inspector_update.js]
|
||||
[browser_animation_play_pause_button.js]
|
||||
[browser_animation_playerFronts_are_refreshed.js]
|
||||
[browser_animation_playerWidgets_appear_on_panel_init.js]
|
||||
[browser_animation_playerWidgets_destroy.js]
|
||||
[browser_animation_playerWidgets_disables_on_finished.js]
|
||||
[browser_animation_playerWidgets_dont_show_time_after_duration.js]
|
||||
|
@ -0,0 +1,15 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that player widgets are displayed right when the animation panel is
|
||||
// initialized, if the selected node (<body> by default) is animated.
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(TEST_URL_ROOT + "doc_body_animation.html");
|
||||
let {panel} = yield openAnimationInspector();
|
||||
|
||||
is(panel.playerWidgets.length, 1, "One animation player is displayed after init");
|
||||
});
|
@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
body {
|
||||
background-color: white;
|
||||
color: black;
|
||||
animation: change-background-color 3s infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes change-background-color {
|
||||
to {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Animated body element</h1>
|
||||
</body>
|
||||
</html>
|
@ -119,12 +119,12 @@ ToolSidebar.prototype = {
|
||||
|
||||
let tabs = this._tabbox.tabs;
|
||||
|
||||
// Create a toolbar and insert it first in the tabbox
|
||||
let allTabsToolbar = this._panelDoc.createElementNS(XULNS, "toolbar");
|
||||
this._tabbox.insertBefore(allTabsToolbar, tabs);
|
||||
// Create a container and insert it first in the tabbox
|
||||
let allTabsContainer = this._panelDoc.createElementNS(XULNS, "box");
|
||||
this._tabbox.insertBefore(allTabsContainer, tabs);
|
||||
|
||||
// Move the tabs inside and make them flex
|
||||
allTabsToolbar.appendChild(tabs);
|
||||
allTabsContainer.appendChild(tabs);
|
||||
tabs.setAttribute("flex", "1");
|
||||
|
||||
// Create the dropdown menu next to the tabs
|
||||
@ -134,7 +134,7 @@ ToolSidebar.prototype = {
|
||||
this._allTabsBtn.setAttribute("label", l10n("sidebar.showAllTabs.label"));
|
||||
this._allTabsBtn.setAttribute("tooltiptext", l10n("sidebar.showAllTabs.tooltip"));
|
||||
this._allTabsBtn.setAttribute("hidden", "true");
|
||||
allTabsToolbar.appendChild(this._allTabsBtn);
|
||||
allTabsContainer.appendChild(this._allTabsBtn);
|
||||
|
||||
let menuPopup = this._panelDoc.createElementNS(XULNS, "menupopup");
|
||||
this._allTabsBtn.appendChild(menuPopup);
|
||||
@ -162,7 +162,7 @@ ToolSidebar.prototype = {
|
||||
|
||||
// Moving back the tabs as a first child of the tabbox
|
||||
this._tabbox.insertBefore(tabs, this._tabbox.tabpanels);
|
||||
this._tabbox.querySelector("toolbar").remove();
|
||||
this._tabbox.querySelector("box").remove();
|
||||
|
||||
this._allTabsBtn = null;
|
||||
},
|
||||
|
@ -31,6 +31,7 @@ EXTRA_JS_MODULES.devtools += [
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.devtools.shared.profiler += [
|
||||
'profiler/frame-utils.js',
|
||||
'profiler/global.js',
|
||||
'profiler/jit.js',
|
||||
'profiler/tree-model.js',
|
||||
@ -46,7 +47,6 @@ EXTRA_JS_MODULES.devtools.shared.timeline += [
|
||||
|
||||
EXTRA_JS_MODULES.devtools.shared += [
|
||||
'autocomplete-popup.js',
|
||||
'd3.js',
|
||||
'devices.js',
|
||||
'doorhanger.js',
|
||||
'frame-script-utils.js',
|
||||
|
131
browser/devtools/shared/profiler/frame-utils.js
Normal file
131
browser/devtools/shared/profiler/frame-utils.js
Normal file
@ -0,0 +1,131 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
const { Ci } = require("chrome");
|
||||
const { extend } = require("sdk/util/object");
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
loader.lazyRequireGetter(this, "CATEGORY_OTHER",
|
||||
"devtools/shared/profiler/global", true);
|
||||
|
||||
// The cache used in the `nsIURL` function.
|
||||
const gNSURLStore = new Map();
|
||||
const CHROME_SCHEMES = ["chrome://", "resource://", "jar:file://"];
|
||||
const CONTENT_SCHEMES = ["http://", "https://", "file://", "app://"];
|
||||
|
||||
/**
|
||||
* Parses the raw location of this function call to retrieve the actual
|
||||
* function name, source url, host name, line and column.
|
||||
*/
|
||||
exports.parseLocation = function parseLocation (frame) {
|
||||
// Parse the `location` for the function name, source url, line, column etc.
|
||||
let lineAndColumn = frame.location.match(/((:\d+)*)\)?$/)[1];
|
||||
let [, line, column] = lineAndColumn.split(":");
|
||||
line = line || frame.line;
|
||||
column = column || frame.column;
|
||||
|
||||
let firstParenIndex = frame.location.indexOf("(");
|
||||
let lineAndColumnIndex = frame.location.indexOf(lineAndColumn);
|
||||
let resource = frame.location.substring(firstParenIndex + 1, lineAndColumnIndex);
|
||||
|
||||
let url = resource.split(" -> ").pop();
|
||||
let uri = nsIURL(url);
|
||||
let functionName, fileName, hostName;
|
||||
|
||||
// If the URI digged out from the `location` is valid, this is a JS frame.
|
||||
if (uri) {
|
||||
functionName = frame.location.substring(0, firstParenIndex - 1);
|
||||
fileName = (uri.fileName + (uri.ref ? "#" + uri.ref : "")) || "/";
|
||||
hostName = url.indexOf("jar:") == 0 ? "" : uri.host;
|
||||
} else {
|
||||
functionName = frame.location;
|
||||
url = null;
|
||||
}
|
||||
|
||||
return {
|
||||
functionName: functionName,
|
||||
fileName: fileName,
|
||||
hostName: hostName,
|
||||
url: url,
|
||||
line: line,
|
||||
column: column
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the specified function represents a chrome or content frame.
|
||||
*
|
||||
* @param object frame
|
||||
* The { category, location } properties of the frame.
|
||||
* @return boolean
|
||||
* True if a content frame, false if a chrome frame.
|
||||
*/
|
||||
exports.isContent = function isContent ({ category, location }) {
|
||||
// Only C++ stack frames have associated category information.
|
||||
return !!(!category &&
|
||||
!CHROME_SCHEMES.find(e => location.contains(e)) &&
|
||||
CONTENT_SCHEMES.find(e => location.contains(e)));
|
||||
}
|
||||
|
||||
/**
|
||||
* This filters out platform data frames in a sample. With latest performance
|
||||
* tool in Fx40, when displaying only content, we still filter out all platform data,
|
||||
* except we generalize platform data that are leaves. We do this because of two
|
||||
* observations:
|
||||
*
|
||||
* 1. The leaf is where time is _actually_ being spent, so we _need_ to show it
|
||||
* to developers in some way to give them accurate profiling data. We decide to
|
||||
* split the platform into various category buckets and just show time spent in
|
||||
* each bucket.
|
||||
*
|
||||
* 2. The calls leading to the leaf _aren't_ where we are spending time, but
|
||||
* _do_ give the developer context for how they got to the leaf where they _are_
|
||||
* spending time. For non-platform hackers, the non-leaf platform frames don't
|
||||
* give any meaningful context, and so we can safely filter them out.
|
||||
*
|
||||
* Example transformations:
|
||||
* Before: PlatformA -> PlatformB -> ContentA -> ContentB
|
||||
* After: ContentA -> ContentB
|
||||
*
|
||||
* Before: PlatformA -> ContentA -> PlatformB -> PlatformC
|
||||
* After: ContentA -> Category(PlatformC)
|
||||
*/
|
||||
exports.filterPlatformData = function filterPlatformData (frames) {
|
||||
let result = [];
|
||||
let last = frames.length - 1;
|
||||
let frame;
|
||||
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
frame = frames[i];
|
||||
if (exports.isContent(frame)) {
|
||||
result.push(frame);
|
||||
} else if (last === i) {
|
||||
// Extend here so we're not destructively editing
|
||||
// the original profiler data. Set isMetaCategory `true`,
|
||||
// and ensure we have a category set by default, because that's how
|
||||
// the generalized frame nodes are organized.
|
||||
result.push(extend({ isMetaCategory: true, category: CATEGORY_OTHER }, frame));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for getting an nsIURL instance out of a string.
|
||||
*/
|
||||
function nsIURL(url) {
|
||||
let cached = gNSURLStore.get(url);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
let uri = null;
|
||||
try {
|
||||
uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
|
||||
} catch(e) {
|
||||
// The passed url string is invalid.
|
||||
}
|
||||
gNSURLStore.set(url, uri);
|
||||
return uri;
|
||||
}
|
@ -4,9 +4,7 @@
|
||||
"use strict";
|
||||
|
||||
const {Cc, Ci, Cu, Cr} = require("chrome");
|
||||
const {extend} = require("sdk/util/object");
|
||||
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
loader.lazyRequireGetter(this, "L10N",
|
||||
"devtools/shared/profiler/global", true);
|
||||
loader.lazyRequireGetter(this, "CATEGORY_MAPPINGS",
|
||||
@ -17,15 +15,12 @@ loader.lazyRequireGetter(this, "CATEGORY_JIT",
|
||||
"devtools/shared/profiler/global", true);
|
||||
loader.lazyRequireGetter(this, "JITOptimizations",
|
||||
"devtools/shared/profiler/jit", true);
|
||||
loader.lazyRequireGetter(this, "CATEGORY_OTHER",
|
||||
"devtools/shared/profiler/global", true);
|
||||
|
||||
const CHROME_SCHEMES = ["chrome://", "resource://", "jar:file://"];
|
||||
const CONTENT_SCHEMES = ["http://", "https://", "file://", "app://"];
|
||||
loader.lazyRequireGetter(this, "FrameUtils",
|
||||
"devtools/shared/profiler/frame-utils");
|
||||
|
||||
exports.ThreadNode = ThreadNode;
|
||||
exports.FrameNode = FrameNode;
|
||||
exports.FrameNode.isContent = isContent;
|
||||
exports.FrameNode.isContent = FrameUtils.isContent;
|
||||
|
||||
/**
|
||||
* A call tree for a thread. This is essentially a linkage between all frames
|
||||
@ -102,7 +97,7 @@ ThreadNode.prototype = {
|
||||
// should be taken into consideration.
|
||||
if (options.contentOnly) {
|
||||
// The (root) node is not considered a content function, it'll be removed.
|
||||
sampleFrames = filterPlatformData(sampleFrames);
|
||||
sampleFrames = FrameUtils.filterPlatformData(sampleFrames);
|
||||
} else {
|
||||
// Remove the (root) node manually.
|
||||
sampleFrames = sampleFrames.slice(1);
|
||||
@ -253,42 +248,13 @@ FrameNode.prototype = {
|
||||
// default to an "unknown" category otherwise.
|
||||
let categoryData = CATEGORY_MAPPINGS[this.category] || {};
|
||||
|
||||
// Parse the `location` for the function name, source url, line, column etc.
|
||||
let lineAndColumn = this.location.match(/((:\d+)*)\)?$/)[1];
|
||||
let [, line, column] = lineAndColumn.split(":");
|
||||
line = line || this.line;
|
||||
column = column || this.column;
|
||||
let parsedData = FrameUtils.parseLocation(this);
|
||||
parsedData.nodeType = "Frame";
|
||||
parsedData.categoryData = categoryData;
|
||||
parsedData.isContent = FrameUtils.isContent(this);
|
||||
parsedData.isMetaCategory = this.isMetaCategory;
|
||||
|
||||
let firstParenIndex = this.location.indexOf("(");
|
||||
let lineAndColumnIndex = this.location.indexOf(lineAndColumn);
|
||||
let resource = this.location.substring(firstParenIndex + 1, lineAndColumnIndex);
|
||||
|
||||
let url = resource.split(" -> ").pop();
|
||||
let uri = nsIURL(url);
|
||||
let functionName, fileName, hostName;
|
||||
|
||||
// If the URI digged out from the `location` is valid, this is a JS frame.
|
||||
if (uri) {
|
||||
functionName = this.location.substring(0, firstParenIndex - 1);
|
||||
fileName = (uri.fileName + (uri.ref ? "#" + uri.ref : "")) || "/";
|
||||
hostName = url.indexOf("jar:") == 0 ? "" : uri.host;
|
||||
} else {
|
||||
functionName = this.location;
|
||||
url = null;
|
||||
}
|
||||
|
||||
return this._data = {
|
||||
nodeType: "Frame",
|
||||
functionName: functionName,
|
||||
fileName: fileName,
|
||||
hostName: hostName,
|
||||
url: url,
|
||||
line: line,
|
||||
column: column,
|
||||
categoryData: categoryData,
|
||||
isContent: !!isContent(this),
|
||||
isMetaCategory: this.isMetaCategory
|
||||
};
|
||||
return this._data = parsedData;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -310,83 +276,3 @@ FrameNode.prototype = {
|
||||
return this._optimizations;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the specified function represents a chrome or content frame.
|
||||
*
|
||||
* @param object frame
|
||||
* The { category, location } properties of the frame.
|
||||
* @return boolean
|
||||
* True if a content frame, false if a chrome frame.
|
||||
*/
|
||||
function isContent({ category, location }) {
|
||||
// Only C++ stack frames have associated category information.
|
||||
return !category &&
|
||||
!CHROME_SCHEMES.find(e => location.contains(e)) &&
|
||||
CONTENT_SCHEMES.find(e => location.contains(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for getting an nsIURL instance out of a string.
|
||||
*/
|
||||
function nsIURL(url) {
|
||||
let cached = gNSURLStore.get(url);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
let uri = null;
|
||||
try {
|
||||
uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
|
||||
} catch(e) {
|
||||
// The passed url string is invalid.
|
||||
}
|
||||
gNSURLStore.set(url, uri);
|
||||
return uri;
|
||||
}
|
||||
|
||||
// The cache used in the `nsIURL` function.
|
||||
let gNSURLStore = new Map();
|
||||
|
||||
/**
|
||||
* This filters out platform data frames in a sample. With latest performance
|
||||
* tool in Fx40, when displaying only content, we still filter out all platform data,
|
||||
* except we generalize platform data that are leaves. We do this because of two
|
||||
* observations:
|
||||
*
|
||||
* 1. The leaf is where time is _actually_ being spent, so we _need_ to show it
|
||||
* to developers in some way to give them accurate profiling data. We decide to
|
||||
* split the platform into various category buckets and just show time spent in
|
||||
* each bucket.
|
||||
*
|
||||
* 2. The calls leading to the leaf _aren't_ where we are spending time, but
|
||||
* _do_ give the developer context for how they got to the leaf where they _are_
|
||||
* spending time. For non-platform hackers, the non-leaf platform frames don't
|
||||
* give any meaningful context, and so we can safely filter them out.
|
||||
*
|
||||
* Example transformations:
|
||||
* Before: PlatformA -> PlatformB -> ContentA -> ContentB
|
||||
* After: ContentA -> ContentB
|
||||
*
|
||||
* Before: PlatformA -> ContentA -> PlatformB -> PlatformC
|
||||
* After: ContentA -> Category(PlatformC)
|
||||
*/
|
||||
function filterPlatformData (frames) {
|
||||
let result = [];
|
||||
let last = frames.length - 1;
|
||||
let frame;
|
||||
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
frame = frames[i];
|
||||
if (isContent(frame)) {
|
||||
result.push(frame);
|
||||
} else if (last === i) {
|
||||
// Extend here so we're not destructively editing
|
||||
// the original profiler data. Set isMetaCategory `true`,
|
||||
// and ensure we have a category set by default, because that's how
|
||||
// the generalized frame nodes are organized.
|
||||
result.push(extend({ isMetaCategory: true, category: CATEGORY_OTHER }, frame));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ support-files =
|
||||
[browser_flame-graph-utils-03.js]
|
||||
[browser_flame-graph-utils-04.js]
|
||||
[browser_flame-graph-utils-05.js]
|
||||
[browser_flame-graph-utils-06.js]
|
||||
[browser_flame-graph-utils-hash.js]
|
||||
[browser_graphs-01.js]
|
||||
[browser_graphs-02.js]
|
||||
|
86
browser/devtools/shared/test/browser_flame-graph-utils-06.js
Normal file
86
browser/devtools/shared/test/browser_flame-graph-utils-06.js
Normal file
@ -0,0 +1,86 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that the text displayed is the function name, file name and line number
|
||||
// if applicable.
|
||||
|
||||
let {FlameGraphUtils, FLAME_GRAPH_BLOCK_HEIGHT} = devtools.require("devtools/shared/widgets/FlameGraph");
|
||||
|
||||
add_task(function*() {
|
||||
yield promiseTab("about:blank");
|
||||
yield performTest();
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function* performTest() {
|
||||
let out = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, {
|
||||
flattenRecursion: true
|
||||
});
|
||||
|
||||
ok(out, "Some data was outputted properly");
|
||||
is(out.length, 10, "The outputted length is correct.");
|
||||
|
||||
info("Got flame graph data:\n" + out.toSource() + "\n");
|
||||
|
||||
for (let i = 0; i < out.length; i++) {
|
||||
let found = out[i];
|
||||
let expected = EXPECTED_OUTPUT[i];
|
||||
|
||||
is(found.blocks.length, expected.blocks.length,
|
||||
"The correct number of blocks were found in this bucket.");
|
||||
|
||||
for (let j = 0; j < found.blocks.length; j++) {
|
||||
is(found.blocks[j].x, expected.blocks[j].x,
|
||||
"The expected block X position is correct for this frame.");
|
||||
is(found.blocks[j].y, expected.blocks[j].y,
|
||||
"The expected block Y position is correct for this frame.");
|
||||
is(found.blocks[j].width, expected.blocks[j].width,
|
||||
"The expected block width is correct for this frame.");
|
||||
is(found.blocks[j].height, expected.blocks[j].height,
|
||||
"The expected block height is correct for this frame.");
|
||||
is(found.blocks[j].text, expected.blocks[j].text,
|
||||
"The expected block text is correct for this frame.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let TEST_DATA = [{
|
||||
frames: [{
|
||||
location: "A (http://path/to/file.js:10:5"
|
||||
}, {
|
||||
location: "B (http://path/to/file.js:100:5"
|
||||
}],
|
||||
time: 50,
|
||||
}];
|
||||
|
||||
let EXPECTED_OUTPUT = [{
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: [{
|
||||
srcData: {
|
||||
startTime: 0,
|
||||
rawLocation: "A (http://path/to/file.js:10:5)"
|
||||
},
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 50,
|
||||
height: FLAME_GRAPH_BLOCK_HEIGHT,
|
||||
text: "A (file.js:10)"
|
||||
}]
|
||||
}, {
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: []
|
||||
}];
|
@ -9,6 +9,7 @@ const { Promise } = require("resource://gre/modules/Promise.jsm");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const { getColor } = require("devtools/shared/theme");
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
const FrameUtils = require("devtools/shared/profiler/frame-utils");
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const GRAPH_SRC = "chrome://browser/content/devtools/graphs-frame.xhtml";
|
||||
@ -1021,10 +1022,11 @@ let FlameGraphUtils = {
|
||||
|
||||
// If no frames are available, add a pseudo "idle" block in between.
|
||||
if (options.showIdleBlocks && frames.length == 0) {
|
||||
frames = [{ location: options.showIdleBlocks || "" }];
|
||||
frames = [{ location: options.showIdleBlocks || "", idle: true }];
|
||||
}
|
||||
|
||||
for (let { location } of frames) {
|
||||
for (let frame of frames) {
|
||||
let { location } = frame;
|
||||
let prevFrame = prevFrames[frameIndex];
|
||||
|
||||
// Frames at the same location and the same depth will be reused.
|
||||
@ -1045,7 +1047,7 @@ let FlameGraphUtils = {
|
||||
y: frameIndex * FLAME_GRAPH_BLOCK_HEIGHT,
|
||||
width: time - prevTime,
|
||||
height: FLAME_GRAPH_BLOCK_HEIGHT,
|
||||
text: location
|
||||
text: this._formatLabel(frame)
|
||||
});
|
||||
}
|
||||
|
||||
@ -1115,6 +1117,30 @@ let FlameGraphUtils = {
|
||||
}
|
||||
|
||||
return hash;
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes a FrameNode and returns a string that should be displayed
|
||||
* in its flame block.
|
||||
*
|
||||
* @param FrameNode frame
|
||||
* @return string
|
||||
*/
|
||||
_formatLabel: function (frame) {
|
||||
// If an idle block, just return the location which will just be "(idle)" text
|
||||
// anyway.
|
||||
if (frame.idle) {
|
||||
return frame.location;
|
||||
}
|
||||
|
||||
let { functionName, fileName, line } = FrameUtils.parseLocation(frame);
|
||||
let label = functionName;
|
||||
|
||||
if (fileName) {
|
||||
label += ` (${fileName}${line != null ? (":" + line) : ""})`;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -65,6 +65,7 @@ skip-if = e10s # Bug 1055333 - style editor tests disabled with e10s
|
||||
[browser_styleeditor_loading.js]
|
||||
[browser_styleeditor_media_sidebar.js]
|
||||
[browser_styleeditor_media_sidebar_sourcemaps.js]
|
||||
[browser_styleeditor_navigate.js]
|
||||
[browser_styleeditor_new.js]
|
||||
[browser_styleeditor_nostyle.js]
|
||||
[browser_styleeditor_pretty.js]
|
||||
|
@ -6,11 +6,7 @@
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/styleeditor/" +
|
||||
"test/browser_styleeditor_cmd_edit.html";
|
||||
|
||||
function test() {
|
||||
return Task.spawn(spawnTest).then(finish, helpers.handleError);
|
||||
}
|
||||
|
||||
function spawnTest() {
|
||||
add_task(function* () {
|
||||
let options = yield helpers.openTab(TEST_URI);
|
||||
yield helpers.openToolbar(options);
|
||||
|
||||
@ -203,4 +199,4 @@ function spawnTest() {
|
||||
|
||||
yield helpers.closeToolbar(options);
|
||||
yield helpers.closeTab(options);
|
||||
}
|
||||
});
|
||||
|
@ -9,8 +9,6 @@
|
||||
const TEST_URL = TEST_BASE_HTTP + "doc_uncached.html";
|
||||
|
||||
add_task(function() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
info("Opening netmonitor");
|
||||
let tab = yield addTab("about:blank");
|
||||
let target = TargetFactory.forTab(tab);
|
||||
|
@ -2,24 +2,15 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
|
||||
// Test that hovering over a simple selector in the style-editor requests the
|
||||
// highlighting of the corresponding nodes
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf8," +
|
||||
"<style>div{color:red}</style><div>highlighter test</div>";
|
||||
|
||||
add_task(function*() {
|
||||
let {UI} = yield addTabAndOpenStyleEditors(1, null, TEST_URL);
|
||||
let editor = UI.editors[0];
|
||||
let { ui } = yield openStyleEditorForURL(TEST_URL);
|
||||
let editor = ui.editors[0];
|
||||
|
||||
// Mock the highlighter so we can locally assert that things happened
|
||||
// correctly instead of accessing the highlighter elements
|
||||
|
@ -1,37 +1,36 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that style editor loads correctly.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "longload.html";
|
||||
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
add_task(function* () {
|
||||
// launch Style Editor right when the tab is created (before load)
|
||||
// this checks that the Style Editor still launches correctly when it is opened
|
||||
// *while* the page is still loading. The Style Editor should not signal that
|
||||
// it is loaded until the accompanying content page is loaded.
|
||||
let tabAdded = addTab(TESTCASE_URI);
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let styleEditorLoaded = gDevTools.showToolbox(target, "styleeditor");
|
||||
|
||||
addTabAndCheckOnStyleEditorAdded(function(panel) {
|
||||
content.location = TESTCASE_URI;
|
||||
}, testEditorAdded);
|
||||
}
|
||||
yield Promise.all([tabAdded, styleEditorLoaded]);
|
||||
|
||||
function testEditorAdded(event, editor)
|
||||
{
|
||||
let root = gPanelWindow.document.querySelector(".splitview-root");
|
||||
let toolbox = gDevTools.getToolbox(target);
|
||||
let panel = toolbox.getPanel("styleeditor");
|
||||
let { panelWindow } = panel;
|
||||
|
||||
let root = panelWindow.document.querySelector(".splitview-root");
|
||||
ok(!root.classList.contains("loading"),
|
||||
"style editor root element does not have 'loading' class name anymore");
|
||||
|
||||
let button = gPanelWindow.document.querySelector(".style-editor-newButton");
|
||||
let button = panelWindow.document.querySelector(".style-editor-newButton");
|
||||
ok(!button.hasAttribute("disabled"),
|
||||
"new style sheet button is enabled");
|
||||
|
||||
button = gPanelWindow.document.querySelector(".style-editor-importButton");
|
||||
button = panelWindow.document.querySelector(".style-editor-importButton");
|
||||
ok(!button.hasAttribute("disabled"),
|
||||
"import button is enabled");
|
||||
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
@ -14,31 +14,31 @@ const NEW_RULE = "\n@media (max-width: 600px) { div { color: blue; } }";
|
||||
waitForExplicitFinish();
|
||||
|
||||
add_task(function*() {
|
||||
let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
|
||||
is(UI.editors.length, 2, "correct number of editors");
|
||||
is(ui.editors.length, 2, "correct number of editors");
|
||||
|
||||
// Test first plain css editor
|
||||
let plainEditor = UI.editors[0];
|
||||
let plainEditor = ui.editors[0];
|
||||
yield openEditor(plainEditor);
|
||||
testPlainEditor(plainEditor);
|
||||
|
||||
// Test editor with @media rules
|
||||
let mediaEditor = UI.editors[1];
|
||||
let mediaEditor = ui.editors[1];
|
||||
yield openEditor(mediaEditor);
|
||||
testMediaEditor(mediaEditor);
|
||||
|
||||
// Test that sidebar hides when flipping pref
|
||||
yield testShowHide(UI, mediaEditor);
|
||||
yield testShowHide(ui, mediaEditor);
|
||||
|
||||
// Test adding a rule updates the list
|
||||
yield testMediaRuleAdded(UI, mediaEditor);
|
||||
yield testMediaRuleAdded(ui, mediaEditor);
|
||||
|
||||
// Test resizing and seeing @media matching state change
|
||||
let originalWidth = window.outerWidth;
|
||||
let originalHeight = window.outerHeight;
|
||||
|
||||
let onMatchesChange = listenForMediaChange(UI);
|
||||
let onMatchesChange = listenForMediaChange(ui);
|
||||
window.resizeTo(RESIZE, RESIZE);
|
||||
yield onMatchesChange;
|
||||
|
||||
|
@ -0,0 +1,37 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that selected sheet and cursor position is reset during navigation.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
|
||||
const NEW_URI = TEST_BASE_HTTPS + "media.html";
|
||||
|
||||
const LINE_NO = 5;
|
||||
const COL_NO = 3;
|
||||
|
||||
add_task(function* () {
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
loadCommonFrameScript();
|
||||
|
||||
is(ui.editors.length, 2, "Two sheets present after load.");
|
||||
|
||||
info("Selecting the second editor");
|
||||
yield ui.selectStyleSheet(ui.editors[1].styleSheet, LINE_NO, COL_NO);
|
||||
|
||||
info("Navigating to another page.");
|
||||
executeInContent("devtools:test:navigate", { location: NEW_URI }, {}, false);
|
||||
|
||||
info("Waiting for sheets to be loaded after navigation.");
|
||||
yield ui.once("stylesheets-reset");
|
||||
|
||||
info("Waiting for source editor to be ready.");
|
||||
yield ui.editors[0].getSourceEditor();
|
||||
|
||||
is(ui.selectedEditor, ui.editors[0], "first editor is selected");
|
||||
|
||||
let {line, ch} = ui.selectedEditor.sourceEditor.getCursor();
|
||||
is(line, 0, "first line is selected");
|
||||
is(ch, 0, "first column is selected");
|
||||
});
|
@ -1,55 +1,44 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
// Test that new sheets can be added and edited.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "simple.html";
|
||||
|
||||
let TESTCASE_CSS_SOURCE = "body{background-color:red;";
|
||||
|
||||
let gOriginalHref;
|
||||
let gUI;
|
||||
|
||||
waitForExplicitFinish();
|
||||
const TESTCASE_CSS_SOURCE = "body{background-color:red;";
|
||||
|
||||
add_task(function*() {
|
||||
let panel = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
|
||||
gUI = panel.UI;
|
||||
let { panel, ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
|
||||
let editor = yield createNew();
|
||||
let editor = yield createNew(ui, panel.panelWindow);
|
||||
testInitialState(editor);
|
||||
|
||||
let originalHref = editor.styleSheet.href;
|
||||
let waitForPropertyChange = onPropertyChange(editor);
|
||||
|
||||
yield typeInEditor(editor);
|
||||
yield typeInEditor(editor, panel.panelWindow);
|
||||
|
||||
yield waitForPropertyChange;
|
||||
|
||||
testUpdated(editor);
|
||||
|
||||
gUI = null;
|
||||
testUpdated(editor, originalHref);
|
||||
});
|
||||
|
||||
function createNew() {
|
||||
function createNew(ui, panelWindow) {
|
||||
info("Creating a new stylesheet now");
|
||||
let deferred = promise.defer();
|
||||
|
||||
gUI.once("editor-added", (ev, editor) => {
|
||||
ui.once("editor-added", (ev, editor) => {
|
||||
editor.getSourceEditor().then(deferred.resolve);
|
||||
});
|
||||
|
||||
waitForFocus(function () {// create a new style sheet
|
||||
let newButton = gPanelWindow.document.querySelector(".style-editor-newButton");
|
||||
let newButton = panelWindow.document.querySelector(".style-editor-newButton");
|
||||
ok(newButton, "'new' button exists");
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(newButton, {}, gPanelWindow);
|
||||
}, gPanelWindow);
|
||||
EventUtils.synthesizeMouseAtCenter(newButton, {}, panelWindow);
|
||||
}, panelWindow);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
@ -71,7 +60,6 @@ function onPropertyChange(aEditor) {
|
||||
|
||||
function testInitialState(aEditor) {
|
||||
info("Testing the initial state of the new editor");
|
||||
gOriginalHref = aEditor.styleSheet.href;
|
||||
|
||||
let summary = aEditor.summary;
|
||||
|
||||
@ -90,22 +78,22 @@ function testInitialState(aEditor) {
|
||||
"content's background color is initially white");
|
||||
}
|
||||
|
||||
function typeInEditor(aEditor) {
|
||||
function typeInEditor(aEditor, panelWindow) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
waitForFocus(function () {
|
||||
for each (let c in TESTCASE_CSS_SOURCE) {
|
||||
EventUtils.synthesizeKey(c, {}, gPanelWindow);
|
||||
for (let c of TESTCASE_CSS_SOURCE) {
|
||||
EventUtils.synthesizeKey(c, {}, panelWindow);
|
||||
}
|
||||
ok(aEditor.unsaved, "new editor has unsaved flag");
|
||||
|
||||
deferred.resolve();
|
||||
}, gPanelWindow);
|
||||
}, panelWindow);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testUpdated(aEditor) {
|
||||
function testUpdated(aEditor, originalHref) {
|
||||
info("Testing the state of the new editor after editing it");
|
||||
|
||||
is(aEditor.sourceEditor.getText(), TESTCASE_CSS_SOURCE + "}",
|
||||
@ -115,6 +103,6 @@ function testUpdated(aEditor) {
|
||||
is(parseInt(ruleCount), 1,
|
||||
"new editor shows 1 rule after modification");
|
||||
|
||||
is(aEditor.styleSheet.href, gOriginalHref,
|
||||
is(aEditor.styleSheet.href, originalHref,
|
||||
"style sheet href did not change");
|
||||
}
|
||||
|
@ -1,41 +1,28 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that 'no styles' indicator is shown if a page doesn't contain any style
|
||||
// sheets.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "nostyle.html";
|
||||
|
||||
add_task(function* () {
|
||||
let { panel } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
let { panelWindow } = panel;
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
// launch Style Editor right when the tab is created (before load)
|
||||
// this checks that the Style Editor still launches correctly when it is opened
|
||||
// *while* the page is still loading. The Style Editor should not signal that
|
||||
// it is loaded until the accompanying content page is loaded.
|
||||
|
||||
addTabAndCheckOnStyleEditorAdded(function(panel) {
|
||||
panel.UI.once("stylesheets-reset", testDocumentLoad);
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}, () => {});
|
||||
}
|
||||
|
||||
function testDocumentLoad(event)
|
||||
{
|
||||
let root = gPanelWindow.document.querySelector(".splitview-root");
|
||||
let root = panelWindow.document.querySelector(".splitview-root");
|
||||
ok(!root.classList.contains("loading"),
|
||||
"style editor root element does not have 'loading' class name anymore");
|
||||
|
||||
ok(root.querySelector(".empty.placeholder"), "showing 'no style' indicator");
|
||||
|
||||
let button = gPanelWindow.document.querySelector(".style-editor-newButton");
|
||||
let button = panelWindow.document.querySelector(".style-editor-newButton");
|
||||
ok(!button.hasAttribute("disabled"),
|
||||
"new style sheet button is enabled");
|
||||
|
||||
button = gPanelWindow.document.querySelector(".style-editor-importButton");
|
||||
button = panelWindow.document.querySelector(".style-editor-importButton");
|
||||
ok(!button.hasAttribute("disabled"),
|
||||
"import button is enabled");
|
||||
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
@ -1,56 +1,51 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
// Test that minified sheets are automatically prettified but other are left
|
||||
// untouched.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "minified.html";
|
||||
|
||||
let gUI;
|
||||
const PRETTIFIED_SOURCE = "" +
|
||||
"body\{\r?\n" + // body{
|
||||
"\tbackground\:white;\r?\n" + // background:white;
|
||||
"\}\r?\n" + // }
|
||||
"\r?\n" + //
|
||||
"div\{\r?\n" + // div{
|
||||
"\tfont\-size\:4em;\r?\n" + // font-size:4em;
|
||||
"\tcolor\:red\r?\n" + // color:red
|
||||
"\}\r?\n" + // }
|
||||
"\r?\n" + //
|
||||
"span\{\r?\n" + // span{
|
||||
"\tcolor\:green;\r?\n" // color:green;
|
||||
"\}\r?\n"; // }
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
const ORIGINAL_SOURCE = "" +
|
||||
"body \{ background\: red; \}\r?\n" + // body { background: red; }
|
||||
"div \{\r?\n" + // div {
|
||||
"font\-size\: 5em;\r?\n" + // font-size: 5em;
|
||||
"color\: red\r?\n" + // color: red
|
||||
"\}"; // }
|
||||
|
||||
addTabAndCheckOnStyleEditorAdded(panel => gUI = panel.UI, editor => {
|
||||
editor.getSourceEditor().then(function() {
|
||||
testEditor(editor);
|
||||
});
|
||||
});
|
||||
add_task(function* () {
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
is(ui.editors.length, 2, "Two sheets present.");
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
info("Testing minified style sheet.");
|
||||
let editor = yield ui.editors[0].getSourceEditor();
|
||||
|
||||
let editorTestedCount = 0;
|
||||
function testEditor(aEditor)
|
||||
{
|
||||
if (aEditor.styleSheet.styleSheetIndex == 0) {
|
||||
let prettifiedSource = "body\{\r?\n\tbackground\:white;\r?\n\}\r?\n\r?\ndiv\{\r?\n\tfont\-size\:4em;\r?\n\tcolor\:red\r?\n\}\r?\n\r?\nspan\{\r?\n\tcolor\:green;\r?\n\}\r?\n";
|
||||
let prettifiedSourceRE = new RegExp(prettifiedSource);
|
||||
let prettifiedSourceRE = new RegExp(PRETTIFIED_SOURCE);
|
||||
ok(prettifiedSourceRE.test(editor.sourceEditor.getText()),
|
||||
"minified source has been prettified automatically");
|
||||
|
||||
ok(prettifiedSourceRE.test(aEditor.sourceEditor.getText()),
|
||||
"minified source has been prettified automatically");
|
||||
editorTestedCount++;
|
||||
let summary = gUI.editors[1].summary;
|
||||
EventUtils.synthesizeMouseAtCenter(summary, {}, gPanelWindow);
|
||||
}
|
||||
info("Selecting second, non-minified style sheet.");
|
||||
yield ui.selectStyleSheet(ui.editors[1].styleSheet);
|
||||
|
||||
if (aEditor.styleSheet.styleSheetIndex == 1) {
|
||||
let originalSource = "body \{ background\: red; \}\r?\ndiv \{\r?\nfont\-size\: 5em;\r?\ncolor\: red\r?\n\}";
|
||||
let originalSourceRE = new RegExp(originalSource);
|
||||
editor = ui.editors[1];
|
||||
|
||||
ok(originalSourceRE.test(aEditor.sourceEditor.getText()),
|
||||
"non-minified source has been left untouched");
|
||||
editorTestedCount++;
|
||||
}
|
||||
|
||||
if (editorTestedCount == 2) {
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
}
|
||||
let originalSourceRE = new RegExp(ORIGINAL_SOURCE);
|
||||
ok(originalSourceRE.test(editor.sourceEditor.getText()),
|
||||
"non-minified source has been left untouched");
|
||||
});
|
||||
|
@ -1,105 +1,38 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
// Test that selected sheet and cursor position persists during reload.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
|
||||
const NEW_URI = TEST_BASE_HTTPS + "media.html";
|
||||
|
||||
const LINE_NO = 5;
|
||||
const COL_NO = 3;
|
||||
|
||||
let gContentWin;
|
||||
let gUI;
|
||||
add_task(function* () {
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
loadCommonFrameScript();
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
is(ui.editors.length, 2, "Two sheets present after load.");
|
||||
|
||||
addTabAndOpenStyleEditors(2, function(panel) {
|
||||
gContentWin = gBrowser.selectedBrowser.contentWindow.wrappedJSObject;
|
||||
gUI = panel.UI;
|
||||
gUI.editors[0].getSourceEditor().then(runTests);
|
||||
});
|
||||
info("Selecting the second editor");
|
||||
yield ui.selectStyleSheet(ui.editors[1].styleSheet, LINE_NO, COL_NO);
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
info("Reloading page.");
|
||||
executeInContent("devtools:test:reload", {}, {}, false /* no response */);
|
||||
|
||||
function runTests()
|
||||
{
|
||||
let count = 0;
|
||||
gUI.on("editor-selected", function editorSelected(event, editor) {
|
||||
if (editor.styleSheet != gUI.editors[1].styleSheet) {
|
||||
return;
|
||||
}
|
||||
gUI.off("editor-selected", editorSelected);
|
||||
editor.getSourceEditor().then(() => {
|
||||
info("selected second editor, about to reload page");
|
||||
reloadPage();
|
||||
info("Waiting for sheets to be loaded after reload.");
|
||||
yield ui.once("stylesheets-reset");
|
||||
|
||||
gUI.on("editor-added", function editorAdded(event, editor) {
|
||||
if (++count == 2) {
|
||||
info("all editors added after reload");
|
||||
gUI.off("editor-added", editorAdded);
|
||||
gUI.editors[1].getSourceEditor().then(testRemembered);
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
gUI.selectStyleSheet(gUI.editors[1].styleSheet, LINE_NO, COL_NO);
|
||||
}
|
||||
is(ui.editors.length, 2, "Two sheets present after reload.");
|
||||
|
||||
function testRemembered()
|
||||
{
|
||||
is(gUI.selectedEditor, gUI.editors[1], "second editor is selected");
|
||||
info("Waiting for source editor to be ready.");
|
||||
yield ui.editors[1].getSourceEditor();
|
||||
|
||||
let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
|
||||
is(ui.selectedEditor, ui.editors[1], "second editor is selected after reload");
|
||||
|
||||
let {line, ch} = ui.selectedEditor.sourceEditor.getCursor();
|
||||
is(line, LINE_NO, "correct line selected");
|
||||
is(ch, COL_NO, "correct column selected");
|
||||
|
||||
testNewPage();
|
||||
}
|
||||
|
||||
function testNewPage()
|
||||
{
|
||||
let count = 0;
|
||||
gUI.on("editor-added", function editorAdded(event, editor) {
|
||||
info("editor added here")
|
||||
if (++count == 2) {
|
||||
info("all editors added after navigating page");
|
||||
gUI.off("editor-added", editorAdded);
|
||||
gUI.editors[0].getSourceEditor().then(testNotRemembered);
|
||||
}
|
||||
})
|
||||
|
||||
info("navigating to a different page");
|
||||
navigatePage();
|
||||
}
|
||||
|
||||
function testNotRemembered()
|
||||
{
|
||||
is(gUI.selectedEditor, gUI.editors[0], "first editor is selected");
|
||||
|
||||
let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
|
||||
is(line, 0, "first line is selected");
|
||||
is(ch, 0, "first column is selected");
|
||||
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
|
||||
function reloadPage()
|
||||
{
|
||||
gContentWin.location.reload();
|
||||
}
|
||||
|
||||
function navigatePage()
|
||||
{
|
||||
gContentWin.location = NEW_URI;
|
||||
}
|
||||
});
|
||||
|
@ -1,58 +1,26 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
// Test that StyleEditorUI.selectStyleSheet selects the correct sheet, line and
|
||||
// column.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
|
||||
const NEW_URI = TEST_BASE_HTTPS + "media.html";
|
||||
|
||||
const LINE_NO = 5;
|
||||
const COL_NO = 0;
|
||||
|
||||
let gContentWin;
|
||||
let gUI;
|
||||
add_task(function* () {
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
let editor = ui.editors[1];
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
info("Selecting style sheet #1.");
|
||||
yield ui.selectStyleSheet(editor.styleSheet.href, LINE_NO);
|
||||
|
||||
addTabAndOpenStyleEditors(2, function(panel) {
|
||||
gContentWin = gBrowser.selectedBrowser.contentWindow.wrappedJSObject;
|
||||
gUI = panel.UI;
|
||||
gUI.editors[0].getSourceEditor().then(runTests);
|
||||
});
|
||||
is(ui.selectedEditor, ui.editors[1], "Second editor is selected.");
|
||||
let {line, ch} = ui.selectedEditor.sourceEditor.getCursor();
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
|
||||
function runTests()
|
||||
{
|
||||
let count = 0;
|
||||
|
||||
// Make sure Editor doesn't go into an infinite loop when
|
||||
// column isn't passed. See bug 941018.
|
||||
gUI.on("editor-selected", function editorSelected(event, editor) {
|
||||
if (editor.styleSheet != gUI.editors[1].styleSheet) {
|
||||
return;
|
||||
}
|
||||
gUI.off("editor-selected", editorSelected);
|
||||
|
||||
editor.getSourceEditor().then(() => {
|
||||
is(gUI.selectedEditor, gUI.editors[1], "second editor is selected");
|
||||
let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
|
||||
|
||||
is(line, LINE_NO, "correct line selected");
|
||||
is(ch, COL_NO, "correct column selected");
|
||||
|
||||
gUI = null;
|
||||
finish();
|
||||
});
|
||||
});
|
||||
gUI.selectStyleSheet(gUI.editors[1].styleSheet.href, LINE_NO);
|
||||
}
|
||||
is(line, LINE_NO, "correct line selected");
|
||||
is(ch, COL_NO, "correct column selected");
|
||||
});
|
||||
|
@ -1,82 +1,67 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
// Test that the style sheet list can be navigated with keyboard.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "four.html";
|
||||
|
||||
let gUI;
|
||||
add_task(function* () {
|
||||
let { panel, ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
info("Waiting for source editor to load.");
|
||||
yield ui.editors[0].getSourceEditor();
|
||||
|
||||
addTabAndOpenStyleEditors(4, runTests);
|
||||
let selected = ui.once("editor-selected");
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
info("Testing keyboard navigation on the sheet list.");
|
||||
testKeyboardNavigation(ui.editors[0], panel);
|
||||
|
||||
function runTests(panel)
|
||||
{
|
||||
gUI = panel.UI;
|
||||
gUI.editors[0].getSourceEditor().then(onEditor0Attach);
|
||||
gUI.editors[2].getSourceEditor().then(onEditor2Attach);
|
||||
}
|
||||
info("Waiting for editor #2 to be selected due to keyboard navigation.");
|
||||
yield selected;
|
||||
|
||||
ok(ui.editors[2].sourceEditor.hasFocus(), "Editor #2 has focus.");
|
||||
});
|
||||
|
||||
function getStylesheetNameLinkFor(aEditor)
|
||||
{
|
||||
return aEditor.summary.querySelector(".stylesheet-name");
|
||||
}
|
||||
|
||||
function onEditor0Attach(aEditor)
|
||||
function testKeyboardNavigation(aEditor, panel)
|
||||
{
|
||||
let panelWindow = panel.panelWindow;
|
||||
let ui = panel.UI;
|
||||
waitForFocus(function () {
|
||||
let summary = aEditor.summary;
|
||||
EventUtils.synthesizeMouseAtCenter(summary, {}, gPanelWindow);
|
||||
EventUtils.synthesizeMouseAtCenter(summary, {}, panelWindow);
|
||||
|
||||
let item = getStylesheetNameLinkFor(gUI.editors[0]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
let item = getStylesheetNameLinkFor(ui.editors[0]);
|
||||
is(panelWindow.document.activeElement, item,
|
||||
"editor 0 item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_DOWN", {}, gPanelWindow);
|
||||
item = getStylesheetNameLinkFor(gUI.editors[1]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
EventUtils.synthesizeKey("VK_DOWN", {}, panelWindow);
|
||||
item = getStylesheetNameLinkFor(ui.editors[1]);
|
||||
is(panelWindow.document.activeElement, item,
|
||||
"editor 1 item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_HOME", {}, gPanelWindow);
|
||||
item = getStylesheetNameLinkFor(gUI.editors[0]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
EventUtils.synthesizeKey("VK_HOME", {}, panelWindow);
|
||||
item = getStylesheetNameLinkFor(ui.editors[0]);
|
||||
is(panelWindow.document.activeElement, item,
|
||||
"fist editor item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_END", {}, gPanelWindow);
|
||||
item = getStylesheetNameLinkFor(gUI.editors[3]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
EventUtils.synthesizeKey("VK_END", {}, panelWindow);
|
||||
item = getStylesheetNameLinkFor(ui.editors[3]);
|
||||
is(panelWindow.document.activeElement, item,
|
||||
"last editor item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_UP", {}, gPanelWindow);
|
||||
item = getStylesheetNameLinkFor(gUI.editors[2]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
EventUtils.synthesizeKey("VK_UP", {}, panelWindow);
|
||||
item = getStylesheetNameLinkFor(ui.editors[2]);
|
||||
is(panelWindow.document.activeElement, item,
|
||||
"editor 2 item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, gPanelWindow);
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, panelWindow);
|
||||
// this will attach and give focus editor 2
|
||||
}, gPanelWindow);
|
||||
}
|
||||
|
||||
function onEditor2Attach(aEditor)
|
||||
{
|
||||
// Wait for the focus to be set.
|
||||
executeSoon(function () {
|
||||
ok(aEditor.sourceEditor.hasFocus(),
|
||||
"editor 2 has focus");
|
||||
|
||||
gUI = null;
|
||||
finish();
|
||||
});
|
||||
}, panelWindow);
|
||||
}
|
||||
|
@ -4,16 +4,14 @@
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
const NEW_RULE = "body { background-color: purple; }";
|
||||
|
||||
add_task(function*() {
|
||||
let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
|
||||
is(UI.editors.length, 2, "correct number of editors");
|
||||
is(ui.editors.length, 2, "correct number of editors");
|
||||
|
||||
let editor = UI.editors[0];
|
||||
let editor = ui.editors[0];
|
||||
yield openEditor(editor);
|
||||
|
||||
// Set text twice in a row
|
||||
|
@ -179,13 +179,22 @@ let UI = {
|
||||
UI.updateCommands();
|
||||
UI.updateProjectButton();
|
||||
UI.openProject();
|
||||
UI.autoStartProject();
|
||||
yield UI.autoStartProject();
|
||||
UI.autoOpenToolbox();
|
||||
UI.saveLastSelectedProject();
|
||||
projectList.update();
|
||||
});
|
||||
return;
|
||||
case "project-stopped":
|
||||
case "project-started":
|
||||
this.updateCommands();
|
||||
projectList.update();
|
||||
UI.autoOpenToolbox();
|
||||
break;
|
||||
case "project-stopped":
|
||||
UI.destroyToolbox();
|
||||
this.updateCommands();
|
||||
projectList.update();
|
||||
break;
|
||||
case "runtime-global-actors":
|
||||
this.updateCommands();
|
||||
projectList.update();
|
||||
@ -657,7 +666,7 @@ let UI = {
|
||||
}, console.error);
|
||||
},
|
||||
|
||||
autoStartProject: function() {
|
||||
autoStartProject: Task.async(function*() {
|
||||
let project = AppManager.selectedProject;
|
||||
|
||||
if (!project) {
|
||||
@ -669,15 +678,27 @@ let UI = {
|
||||
return; // For something that is not an editable app, we're done.
|
||||
}
|
||||
|
||||
Task.spawn(function() {
|
||||
// Do not force opening apps that are already running, as they may have
|
||||
// some activity being opened and don't want to dismiss them.
|
||||
if (project.type == "runtimeApp" && !AppManager.isProjectRunning()) {
|
||||
yield UI.busyUntil(AppManager.launchRuntimeApp(), "running app");
|
||||
}
|
||||
yield UI.createToolbox();
|
||||
});
|
||||
},
|
||||
// Do not force opening apps that are already running, as they may have
|
||||
// some activity being opened and don't want to dismiss them.
|
||||
if (project.type == "runtimeApp" && !AppManager.isProjectRunning()) {
|
||||
yield UI.busyUntil(AppManager.launchRuntimeApp(), "running app");
|
||||
}
|
||||
}),
|
||||
|
||||
autoOpenToolbox: Task.async(function*() {
|
||||
let project = AppManager.selectedProject;
|
||||
|
||||
if (!project) {
|
||||
return;
|
||||
}
|
||||
if (!(project.type == "runtimeApp" ||
|
||||
project.type == "mainProcess" ||
|
||||
project.type == "tab")) {
|
||||
return; // For something that is not an editable app, we're done.
|
||||
}
|
||||
|
||||
yield UI.createToolbox();
|
||||
}),
|
||||
|
||||
importAndSelectApp: Task.async(function* (source) {
|
||||
let isPackaged = !!source.path;
|
||||
@ -1015,6 +1036,7 @@ let UI = {
|
||||
let panel = document.querySelector("#deck").selectedPanel;
|
||||
let nbox = document.querySelector("#notificationbox");
|
||||
if (panel && panel.id == "deck-panel-details" &&
|
||||
AppManager.selectedProject &&
|
||||
AppManager.selectedProject.type != "packaged" &&
|
||||
this.toolboxIframe) {
|
||||
nbox.setAttribute("toolboxfullscreen", "true");
|
||||
|
@ -249,6 +249,7 @@ cancel_button=Cancel
|
||||
cannot_start_call_session_not_ready=Can't start call, session is not ready.
|
||||
network_disconnected=The network connection terminated abruptly.
|
||||
connection_error_see_console_notification=Call failed; see console for details.
|
||||
no_media_failure_message=No camera or microphone found.
|
||||
|
||||
## LOCALIZATION NOTE (legal_text_and_links3): In this item, don't translate the
|
||||
## parts between {{..}} because these will be replaced with links with the labels
|
||||
|
@ -201,7 +201,7 @@ let AboutHome = {
|
||||
|
||||
// Trigger a search through nsISearchEngine.getSubmission()
|
||||
let submission = engine.getSubmission(data.searchTerms, null, "homepage");
|
||||
let where = data.useNewTab ? "tab" : "current";
|
||||
let where = data.useNewTab ? "tabshifted" : "current";
|
||||
window.openUILinkIn(submission.uri.spec, where, false,
|
||||
submission.postData);
|
||||
|
||||
|
@ -226,9 +226,6 @@ this.ContentSearch = {
|
||||
// method on it will throw.
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (data.useNewTab) {
|
||||
browser.getTabBrowser().selectedTab = newTab;
|
||||
}
|
||||
let win = browser.ownerDocument.defaultView;
|
||||
win.BrowserSearch.recordSearchInHealthReport(engine, data.whence,
|
||||
data.selection || null);
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.0 KiB |
@ -6,13 +6,10 @@
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.AppConstants.Versions;
|
||||
import org.mozilla.gecko.prompts.PromptInput;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.widget.AnchoredPopup;
|
||||
@ -21,7 +18,6 @@ import org.mozilla.gecko.widget.DoorHanger;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import org.mozilla.gecko.widget.DoorhangerConfig;
|
||||
|
||||
public class DoorHangerPopup extends AnchoredPopup
|
||||
@ -109,15 +105,16 @@ public class DoorHangerPopup extends AnchoredPopup
|
||||
private DoorhangerConfig makeConfigFromJSON(JSONObject json) throws JSONException {
|
||||
final int tabId = json.getInt("tabID");
|
||||
final String id = json.getString("value");
|
||||
final DoorhangerConfig config = new DoorhangerConfig(tabId, id);
|
||||
|
||||
final String typeString = json.optString("category");
|
||||
final boolean isLogin = DoorHanger.Type.LOGIN.toString().equals(typeString);
|
||||
final DoorHanger.Type doorhangerType = isLogin ? DoorHanger.Type.LOGIN : DoorHanger.Type.DEFAULT;
|
||||
|
||||
final DoorhangerConfig config = new DoorhangerConfig(tabId, id, doorhangerType, this);
|
||||
|
||||
config.setMessage(json.getString("message"));
|
||||
config.setButtons(json.getJSONArray("buttons"));
|
||||
config.appendButtonsFromJSON(json.getJSONArray("buttons"));
|
||||
config.setOptions(json.getJSONObject("options"));
|
||||
final String typeString = json.optString("category");
|
||||
if (DoorHanger.Type.LOGIN.toString().equals(typeString)) {
|
||||
config.setType(DoorHanger.Type.LOGIN);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
@ -181,18 +178,6 @@ public class DoorHangerPopup extends AnchoredPopup
|
||||
|
||||
final DoorHanger newDoorHanger = DoorHanger.Get(mContext, config);
|
||||
|
||||
final JSONArray buttons = config.getButtons();
|
||||
for (int i = 0; i < buttons.length(); i++) {
|
||||
try {
|
||||
JSONObject buttonObject = buttons.getJSONObject(i);
|
||||
String label = buttonObject.getString("label");
|
||||
String tag = String.valueOf(buttonObject.getInt("callback"));
|
||||
newDoorHanger.addButton(label, tag, this);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating doorhanger button", e);
|
||||
}
|
||||
}
|
||||
|
||||
mDoorHangers.add(newDoorHanger);
|
||||
mContent.addView(newDoorHanger);
|
||||
|
||||
@ -206,32 +191,10 @@ public class DoorHangerPopup extends AnchoredPopup
|
||||
* DoorHanger.OnButtonClickListener implementation
|
||||
*/
|
||||
@Override
|
||||
public void onButtonClick(DoorHanger dh, String tag) {
|
||||
JSONObject response = new JSONObject();
|
||||
try {
|
||||
response.put("callback", tag);
|
||||
|
||||
CheckBox checkBox = dh.getCheckBox();
|
||||
// If the checkbox is being used, pass its value
|
||||
if (checkBox != null) {
|
||||
response.put("checked", checkBox.isChecked());
|
||||
}
|
||||
|
||||
List<PromptInput> doorHangerInputs = dh.getInputs();
|
||||
if (doorHangerInputs != null) {
|
||||
JSONObject inputs = new JSONObject();
|
||||
for (PromptInput input : doorHangerInputs) {
|
||||
inputs.put(input.getId(), input.getValue());
|
||||
}
|
||||
response.put("inputs", inputs);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating onClick response", e);
|
||||
}
|
||||
|
||||
public void onButtonClick(JSONObject response, DoorHanger doorhanger) {
|
||||
GeckoEvent e = GeckoEvent.createBroadcastEvent("Doorhanger:Reply", response.toString());
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
removeDoorHanger(dh);
|
||||
removeDoorHanger(doorhanger);
|
||||
updatePopup();
|
||||
}
|
||||
|
||||
|
@ -381,6 +381,7 @@ size. -->
|
||||
<!ENTITY doorhanger_login_edit_username_hint "Username">
|
||||
<!ENTITY doorhanger_login_edit_password_hint "Password">
|
||||
<!ENTITY doorhanger_login_edit_toggle "Show password">
|
||||
<!ENTITY doorhanger_login_edit_toast_error "Failed to save login">
|
||||
|
||||
<!ENTITY pref_titlebar_mode "Title bar">
|
||||
<!ENTITY pref_titlebar_mode_title "Show page title">
|
||||
|
@ -39,6 +39,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
|
||||
android:textColor="@color/link_blue"
|
||||
android:paddingBottom="@dimen/doorhanger_section_padding_large"
|
||||
android:visibility="gone"/>
|
||||
|
||||
|
30
mobile/android/base/resources/layout/login_edit_dialog.xml
Normal file
30
mobile/android/base/resources/layout/login_edit_dialog.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/doorhanger_padding"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText android:id="@+id/username_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:hint="@string/doorhanger_login_edit_username_hint"/>
|
||||
|
||||
<EditText android:id="@+id/password_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:password="true"
|
||||
android:hint="@string/doorhanger_login_edit_password_hint"/>
|
||||
|
||||
<CheckBox android:id="@+id/checkbox_toggle_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/doorhanger_login_edit_toggle"
|
||||
android:paddingTop="@dimen/doorhanger_padding"/>
|
||||
|
||||
</LinearLayout>
|
@ -345,6 +345,7 @@
|
||||
<string name="doorhanger_login_edit_username_hint">&doorhanger_login_edit_username_hint;</string>
|
||||
<string name="doorhanger_login_edit_password_hint">&doorhanger_login_edit_password_hint;</string>
|
||||
<string name="doorhanger_login_edit_toggle">&doorhanger_login_edit_toggle;</string>
|
||||
<string name="doorhanger_login_edit_toast_error">&doorhanger_login_edit_toast_error;</string>
|
||||
|
||||
<string name="pref_titlebar_mode">&pref_titlebar_mode;</string>
|
||||
<string name="pref_titlebar_mode_title">&pref_titlebar_mode_title;</string>
|
||||
|
@ -17,7 +17,6 @@ import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.widget.AnchoredPopup;
|
||||
import org.mozilla.gecko.widget.DoorHanger;
|
||||
import org.mozilla.gecko.widget.DoorHanger.OnButtonClickListener;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.Context;
|
||||
@ -34,6 +33,8 @@ import org.mozilla.gecko.widget.DoorhangerConfig;
|
||||
* an arrow panel popup hanging from the lock icon in the browser toolbar.
|
||||
*/
|
||||
public class SiteIdentityPopup extends AnchoredPopup {
|
||||
public static enum ButtonType { DISABLE, ENABLE, KEEP_BLOCKING };
|
||||
|
||||
private static final String LOGTAG = "GeckoSiteIdentityPopup";
|
||||
|
||||
private static final String MIXED_CONTENT_SUPPORT_URL =
|
||||
@ -142,7 +143,7 @@ public class SiteIdentityPopup extends AnchoredPopup {
|
||||
// Remove any existing mixed content notification.
|
||||
removeMixedContentNotification();
|
||||
|
||||
final DoorhangerConfig config = new DoorhangerConfig();
|
||||
final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.MIXED_CONTENT, mButtonClickListener);
|
||||
int icon;
|
||||
if (blocked) {
|
||||
icon = R.drawable.shield_enabled_doorhanger;
|
||||
@ -154,11 +155,11 @@ public class SiteIdentityPopup extends AnchoredPopup {
|
||||
}
|
||||
|
||||
config.setLink(mContext.getString(R.string.learn_more), MIXED_CONTENT_SUPPORT_URL, "\n\n");
|
||||
config.setType(DoorHanger.Type.SITE);
|
||||
addNotificationButtons(config, blocked);
|
||||
|
||||
mMixedContentNotification = DoorHanger.Get(mContext, config);
|
||||
mMixedContentNotification.setIcon(icon);
|
||||
|
||||
addNotificationButtons(mMixedContentNotification, blocked);
|
||||
|
||||
mContent.addView(mMixedContentNotification);
|
||||
mDivider.setVisibility(View.VISIBLE);
|
||||
@ -175,7 +176,7 @@ public class SiteIdentityPopup extends AnchoredPopup {
|
||||
// Remove any existing tracking content notification.
|
||||
removeTrackingContentNotification();
|
||||
|
||||
final DoorhangerConfig config = new DoorhangerConfig();
|
||||
final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.TRACKING, mButtonClickListener);
|
||||
|
||||
int icon;
|
||||
if (blocked) {
|
||||
@ -189,12 +190,12 @@ public class SiteIdentityPopup extends AnchoredPopup {
|
||||
}
|
||||
|
||||
config.setLink(mContext.getString(R.string.learn_more), TRACKING_CONTENT_SUPPORT_URL, "\n\n");
|
||||
config.setType(DoorHanger.Type.SITE);
|
||||
addNotificationButtons(config, blocked);
|
||||
|
||||
mTrackingContentNotification = DoorHanger.Get(mContext, config);
|
||||
|
||||
mTrackingContentNotification.setIcon(icon);
|
||||
|
||||
addNotificationButtons(mTrackingContentNotification, blocked);
|
||||
|
||||
mContent.addView(mTrackingContentNotification);
|
||||
mDivider.setVisibility(View.VISIBLE);
|
||||
@ -207,13 +208,12 @@ public class SiteIdentityPopup extends AnchoredPopup {
|
||||
}
|
||||
}
|
||||
|
||||
private void addNotificationButtons(DoorHanger dh, boolean blocked) {
|
||||
// TODO: Add support for buttons in DoorHangerConfig.
|
||||
private void addNotificationButtons(DoorhangerConfig config, boolean blocked) {
|
||||
if (blocked) {
|
||||
dh.addButton(mContext.getString(R.string.disable_protection), "disable", mButtonClickListener);
|
||||
dh.addButton(mContext.getString(R.string.keep_blocking), "keepBlocking", mButtonClickListener);
|
||||
config.appendButton(mContext.getString(R.string.disable_protection), ButtonType.DISABLE.ordinal());
|
||||
config.appendButton(mContext.getString(R.string.keep_blocking), ButtonType.KEEP_BLOCKING.ordinal());
|
||||
} else {
|
||||
dh.addButton(mContext.getString(R.string.enable_protection), "enable", mButtonClickListener);
|
||||
config.appendButton(mContext.getString(R.string.enable_protection), ButtonType.ENABLE.ordinal());
|
||||
}
|
||||
}
|
||||
|
||||
@ -290,18 +290,9 @@ public class SiteIdentityPopup extends AnchoredPopup {
|
||||
|
||||
private class PopupButtonListener implements OnButtonClickListener {
|
||||
@Override
|
||||
public void onButtonClick(DoorHanger dh, String tag) {
|
||||
try {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("allowContent", tag.equals("disable"));
|
||||
data.put("contentType", (dh == mMixedContentNotification ? "mixed" : "tracking"));
|
||||
|
||||
GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", data.toString());
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Exception creating message to enable/disable content blocking", e);
|
||||
}
|
||||
|
||||
public void onButtonClick(JSONObject response, DoorHanger doorhanger) {
|
||||
GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", response.toString());
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,9 @@
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.Button;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.prompts.PromptInput;
|
||||
|
||||
@ -13,11 +16,11 @@ import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import org.mozilla.gecko.toolbar.SiteIdentityPopup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -25,23 +28,16 @@ import java.util.List;
|
||||
public class DefaultDoorHanger extends DoorHanger {
|
||||
private static final String LOGTAG = "GeckoDefaultDoorHanger";
|
||||
|
||||
private final Resources mResources;
|
||||
private static int sSpinnerTextColor = -1;
|
||||
|
||||
private List<PromptInput> mInputs;
|
||||
private CheckBox mCheckBox;
|
||||
|
||||
public DefaultDoorHanger(Context context, DoorhangerConfig config) {
|
||||
this(context, config, Type.DEFAULT);
|
||||
}
|
||||
|
||||
public DefaultDoorHanger(Context context, DoorhangerConfig config, Type type) {
|
||||
super(context, config, type);
|
||||
|
||||
mResources = getResources();
|
||||
|
||||
if (sSpinnerTextColor == -1) {
|
||||
sSpinnerTextColor = getResources().getColor(R.color.text_color_primary_disable_only);
|
||||
sSpinnerTextColor = mResources.getColor(R.color.text_color_primary_disable_only);
|
||||
}
|
||||
loadConfig(config);
|
||||
}
|
||||
@ -62,15 +58,15 @@ public class DefaultDoorHanger extends DoorHanger {
|
||||
if (link != null) {
|
||||
addLink(link.label, link.url, link.delimiter);
|
||||
}
|
||||
|
||||
setButtons(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PromptInput> getInputs() {
|
||||
private List<PromptInput> getInputs() {
|
||||
return mInputs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CheckBox getCheckBox() {
|
||||
private CheckBox getCheckBox() {
|
||||
return mCheckBox;
|
||||
}
|
||||
|
||||
@ -115,6 +111,54 @@ public class DefaultDoorHanger extends DoorHanger {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Button createButtonInstance(final String text, final int id) {
|
||||
final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
|
||||
button.setText(text);
|
||||
|
||||
button.setOnClickListener(new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final JSONObject response = new JSONObject();
|
||||
try {
|
||||
// TODO: Bug 1149359 - Split this into each Doorhanger Type class.
|
||||
switch (mType) {
|
||||
case MIXED_CONTENT:
|
||||
response.put("allowContent", (id == SiteIdentityPopup.ButtonType.DISABLE.ordinal()));
|
||||
response.put("contentType", ("mixed"));
|
||||
break;
|
||||
case TRACKING:
|
||||
response.put("allowContent", (id == SiteIdentityPopup.ButtonType.DISABLE.ordinal()));
|
||||
response.put("contentType", ("tracking"));
|
||||
break;
|
||||
default:
|
||||
response.put("callback", id);
|
||||
|
||||
CheckBox checkBox = getCheckBox();
|
||||
// If the checkbox is being used, pass its value
|
||||
if (checkBox != null) {
|
||||
response.put("checked", checkBox.isChecked());
|
||||
}
|
||||
|
||||
List<PromptInput> doorHangerInputs = getInputs();
|
||||
if (doorHangerInputs != null) {
|
||||
JSONObject inputs = new JSONObject();
|
||||
for (PromptInput input : doorHangerInputs) {
|
||||
inputs.put(input.getId(), input.getValue());
|
||||
}
|
||||
response.put("inputs", inputs);
|
||||
}
|
||||
}
|
||||
mOnButtonClickListener.onButtonClick(response, DefaultDoorHanger.this);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating onClick response", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private void styleInput(PromptInput input, View view) {
|
||||
if (input instanceof PromptInput.MenulistInput) {
|
||||
styleDropdownInputs(input, view);
|
||||
|
@ -6,49 +6,47 @@
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.URLSpan;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.prompts.PromptInput;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class DoorHanger extends LinearLayout {
|
||||
|
||||
public static DoorHanger Get(Context context, DoorhangerConfig config) {
|
||||
final Type type = config.getType();
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case LOGIN:
|
||||
return new LoginDoorHanger(context, config);
|
||||
case SITE:
|
||||
return new DefaultDoorHanger(context, config, type);
|
||||
}
|
||||
switch (type) {
|
||||
case LOGIN:
|
||||
return new LoginDoorHanger(context, config);
|
||||
case TRACKING:
|
||||
case MIXED_CONTENT:
|
||||
return new DefaultDoorHanger(context, config, type);
|
||||
}
|
||||
|
||||
return new DefaultDoorHanger(context, config);
|
||||
return new DefaultDoorHanger(context, config, type);
|
||||
}
|
||||
|
||||
public static enum Type { DEFAULT, LOGIN, SITE }
|
||||
public static enum Type { DEFAULT, LOGIN, TRACKING, MIXED_CONTENT}
|
||||
|
||||
public interface OnButtonClickListener {
|
||||
public void onButtonClick(DoorHanger dh, String tag);
|
||||
public void onButtonClick(JSONObject response, DoorHanger doorhanger);
|
||||
}
|
||||
|
||||
private static final LayoutParams sButtonParams;
|
||||
protected static final LayoutParams sButtonParams;
|
||||
static {
|
||||
sButtonParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1.0f);
|
||||
}
|
||||
@ -58,7 +56,8 @@ public abstract class DoorHanger extends LinearLayout {
|
||||
// Divider between doorhangers.
|
||||
private final View mDivider;
|
||||
|
||||
private final LinearLayout mButtonsContainer;
|
||||
protected final LinearLayout mButtonsContainer;
|
||||
protected final OnButtonClickListener mOnButtonClickListener;
|
||||
|
||||
// The tab this doorhanger is associated with.
|
||||
private final int mTabId;
|
||||
@ -66,10 +65,13 @@ public abstract class DoorHanger extends LinearLayout {
|
||||
// DoorHanger identifier.
|
||||
private final String mIdentifier;
|
||||
|
||||
protected final Type mType;
|
||||
|
||||
private final ImageView mIcon;
|
||||
private final TextView mMessage;
|
||||
|
||||
protected Context mContext;
|
||||
protected final Context mContext;
|
||||
protected final Resources mResources;
|
||||
|
||||
protected int mDividerColor;
|
||||
|
||||
@ -80,6 +82,7 @@ public abstract class DoorHanger extends LinearLayout {
|
||||
protected DoorHanger(Context context, DoorhangerConfig config, Type type) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mResources = context.getResources();
|
||||
mTabId = config.getTabId();
|
||||
mIdentifier = config.getId();
|
||||
|
||||
@ -96,16 +99,22 @@ public abstract class DoorHanger extends LinearLayout {
|
||||
mDivider = findViewById(R.id.divider_doorhanger);
|
||||
mIcon = (ImageView) findViewById(R.id.doorhanger_icon);
|
||||
mMessage = (TextView) findViewById(R.id.doorhanger_message);
|
||||
if (type == Type.SITE) {
|
||||
|
||||
// TODO: Bug 1149359 - split this into DoorHanger subclasses.
|
||||
if (type == Type.TRACKING || type == Type.MIXED_CONTENT) {
|
||||
mMessage.setTextAppearance(getContext(), R.style.TextAppearance_DoorHanger_Small);
|
||||
}
|
||||
mButtonsContainer = (LinearLayout) findViewById(R.id.doorhanger_buttons);
|
||||
|
||||
mDividerColor = getResources().getColor(R.color.divider_light);
|
||||
mType = type;
|
||||
|
||||
mButtonsContainer = (LinearLayout) findViewById(R.id.doorhanger_buttons);
|
||||
mOnButtonClickListener = config.getButtonClickListener();
|
||||
|
||||
mDividerColor = mResources.getColor(R.color.divider_light);
|
||||
setOrientation(VERTICAL);
|
||||
}
|
||||
|
||||
abstract protected void loadConfig(DoorhangerConfig config);
|
||||
protected abstract void loadConfig(DoorhangerConfig config);
|
||||
|
||||
protected void setOptions(final JSONObject options) {
|
||||
final int persistence = options.optInt("persistence");
|
||||
@ -119,6 +128,21 @@ public abstract class DoorHanger extends LinearLayout {
|
||||
if (timeout > 0) {
|
||||
mTimeout = timeout;
|
||||
}
|
||||
}
|
||||
|
||||
protected void setButtons(DoorhangerConfig config) {
|
||||
final JSONArray buttons = config.getButtons();
|
||||
final OnButtonClickListener listener = config.getButtonClickListener();
|
||||
for (int i = 0; i < buttons.length(); i++) {
|
||||
try {
|
||||
final JSONObject buttonObject = buttons.getJSONObject(i);
|
||||
final String label = buttonObject.getString("label");
|
||||
final int callbackId = buttonObject.getInt("callback");
|
||||
addButtonToLayout(label, callbackId);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating doorhanger button", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getTabId() {
|
||||
@ -166,18 +190,13 @@ public abstract class DoorHanger extends LinearLayout {
|
||||
mMessage.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
|
||||
public void addButton(final String text, final String tag, final OnButtonClickListener listener) {
|
||||
final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
|
||||
button.setText(text);
|
||||
button.setTag(tag);
|
||||
|
||||
button.setOnClickListener(new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onButtonClick(DoorHanger.this, tag);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates and adds a button into the DoorHanger.
|
||||
* @param text Button text
|
||||
* @param id Identifier associated with the button
|
||||
*/
|
||||
private void addButtonToLayout(String text, int id) {
|
||||
final Button button = createButtonInstance(text, id);
|
||||
if (mButtonsContainer.getChildCount() == 0) {
|
||||
// If this is the first button we're adding, make the choices layout visible.
|
||||
mButtonsContainer.setVisibility(View.VISIBLE);
|
||||
@ -195,6 +214,8 @@ public abstract class DoorHanger extends LinearLayout {
|
||||
mButtonsContainer.addView(button, sButtonParams);
|
||||
}
|
||||
|
||||
protected abstract Button createButtonInstance(String text, int id);
|
||||
|
||||
/*
|
||||
* Checks with persistence and timeout options to see if it's okay to remove a doorhanger.
|
||||
*
|
||||
@ -222,14 +243,4 @@ public abstract class DoorHanger extends LinearLayout {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: remove and expose through instance Button Handler.
|
||||
public List<PromptInput> getInputs() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public CheckBox getCheckBox() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,7 +5,9 @@
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import android.util.Log;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import org.mozilla.gecko.widget.DoorHanger.Type;
|
||||
@ -24,23 +26,29 @@ public class DoorhangerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
private static final String LOGTAG = "DoorhangerConfig";
|
||||
|
||||
private final int tabId;
|
||||
private final String id;
|
||||
private DoorHanger.Type type;
|
||||
private final DoorHanger.OnButtonClickListener buttonClickListener;
|
||||
private final DoorHanger.Type type;
|
||||
private String message;
|
||||
private JSONObject options;
|
||||
private Link link;
|
||||
private JSONArray buttons;
|
||||
private JSONArray buttons = new JSONArray();
|
||||
|
||||
public DoorhangerConfig() {
|
||||
public DoorhangerConfig(Type type, DoorHanger.OnButtonClickListener listener) {
|
||||
// XXX: This should only be used by SiteIdentityPopup doorhangers which
|
||||
// don't need tab or id references, until bug 1141904 unifies doorhangers.
|
||||
this(-1, null);
|
||||
|
||||
this(-1, null, type, listener);
|
||||
}
|
||||
|
||||
public DoorhangerConfig(int tabId, String id) {
|
||||
public DoorhangerConfig(int tabId, String id, DoorHanger.Type type, DoorHanger.OnButtonClickListener buttonClickListener) {
|
||||
this.tabId = tabId;
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.buttonClickListener = buttonClickListener;
|
||||
}
|
||||
|
||||
public int getTabId() {
|
||||
@ -51,10 +59,6 @@ public class DoorhangerConfig {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setType(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
@ -75,8 +79,33 @@ public class DoorhangerConfig {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setButtons(JSONArray buttons) {
|
||||
this.buttons = buttons;
|
||||
/**
|
||||
* Add buttons from JSON to the Config object.
|
||||
* @param buttons JSONArray of JSONObjects of the form { label: <label>, callback: <callback_id> }
|
||||
*/
|
||||
public void appendButtonsFromJSON(JSONArray buttons) {
|
||||
try {
|
||||
for (int i = 0; i < buttons.length(); i++) {
|
||||
this.buttons.put(buttons.get(i));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error parsing buttons from JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void appendButton(String label, int callbackId) {
|
||||
final JSONObject button = new JSONObject();
|
||||
try {
|
||||
button.put("label", label);
|
||||
button.put("callback", callbackId);
|
||||
this.buttons.put(button);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating button", e);
|
||||
}
|
||||
}
|
||||
|
||||
public DoorHanger.OnButtonClickListener getButtonClickListener() {
|
||||
return this.buttonClickListener;
|
||||
}
|
||||
|
||||
public JSONArray getButtons() {
|
||||
|
@ -5,12 +5,22 @@
|
||||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import ch.boye.httpclientandroidlib.util.TextUtils;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@ -20,9 +30,11 @@ import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
|
||||
|
||||
public class LoginDoorHanger extends DoorHanger {
|
||||
private static final String LOGTAG = "LoginDoorHanger";
|
||||
private enum ActionType { EDIT };
|
||||
|
||||
final TextView mTitle;
|
||||
final TextView mLogin;
|
||||
private final TextView mTitle;
|
||||
private final TextView mLogin;
|
||||
private int mCallbackID;
|
||||
|
||||
public LoginDoorHanger(Context context, DoorhangerConfig config) {
|
||||
super(context, config, Type.LOGIN);
|
||||
@ -37,7 +49,7 @@ public class LoginDoorHanger extends DoorHanger {
|
||||
protected void loadConfig(DoorhangerConfig config) {
|
||||
setOptions(config.getOptions());
|
||||
setMessage(config.getMessage());
|
||||
|
||||
setButtons(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -51,7 +63,7 @@ public class LoginDoorHanger extends DoorHanger {
|
||||
final String text = titleObj.getString("text");
|
||||
mTitle.setText(text);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error loading title from options JSON");
|
||||
Log.e(LOGTAG, "Error loading title from options JSON", e);
|
||||
}
|
||||
|
||||
final String resource = titleObj.optString("resource");
|
||||
@ -60,20 +72,132 @@ public class LoginDoorHanger extends DoorHanger {
|
||||
@Override
|
||||
public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) {
|
||||
if (favicon != null) {
|
||||
mTitle.setCompoundDrawablesWithIntrinsicBounds(new BitmapDrawable(mContext.getResources(), favicon), null, null, null);
|
||||
mTitle.setCompoundDrawablePadding((int) mContext.getResources().getDimension(R.dimen.doorhanger_drawable_padding));
|
||||
mTitle.setCompoundDrawablesWithIntrinsicBounds(new BitmapDrawable(mResources, favicon), null, null, null);
|
||||
mTitle.setCompoundDrawablePadding((int) mResources.getDimension(R.dimen.doorhanger_drawable_padding));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
final String subtext = options.optString("subtext");
|
||||
if (!TextUtils.isEmpty(subtext)) {
|
||||
mLogin.setText(subtext);
|
||||
mLogin.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
final JSONObject actionText = options.optJSONObject("actionText");
|
||||
addActionText(actionText);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Button createButtonInstance(final String text, final int id) {
|
||||
// HACK: Confirm button will the the rightmost/last button added. Bug 1147064 should add differentiation of the two.
|
||||
mCallbackID = id;
|
||||
|
||||
final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
|
||||
button.setText(text);
|
||||
|
||||
button.setOnClickListener(new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final JSONObject response = new JSONObject();
|
||||
try {
|
||||
response.put("callback", id);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error making doorhanger response message", e);
|
||||
}
|
||||
mOnButtonClickListener.onButtonClick(response, LoginDoorHanger.this);
|
||||
}
|
||||
});
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add sub-text to the doorhanger and add the click action.
|
||||
*
|
||||
* If the parsing the action from the JSON throws, the text is left visible, but there is no
|
||||
* click action.
|
||||
* @param actionTextObj JSONObject containing blob for making an action.
|
||||
*/
|
||||
private void addActionText(JSONObject actionTextObj) {
|
||||
if (actionTextObj == null) {
|
||||
mLogin.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasUsername = true;
|
||||
String text = actionTextObj.optString("text");
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
hasUsername = false;
|
||||
text = mResources.getString(R.string.doorhanger_login_no_username);
|
||||
}
|
||||
mLogin.setText(text);
|
||||
mLogin.setVisibility(View.VISIBLE);
|
||||
|
||||
// Make action.
|
||||
try {
|
||||
final JSONObject bundle = actionTextObj.getJSONObject("bundle");
|
||||
final ActionType type = ActionType.valueOf(actionTextObj.getString("type"));
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
|
||||
|
||||
switch (type) {
|
||||
case EDIT:
|
||||
builder.setTitle(mResources.getString(R.string.doorhanger_login_edit_title));
|
||||
|
||||
final View view = LayoutInflater.from(mContext).inflate(R.layout.login_edit_dialog, null);
|
||||
final EditText username = (EditText) view.findViewById(R.id.username_edit);
|
||||
username.setText(bundle.getString("username"));
|
||||
final EditText password = (EditText) view.findViewById(R.id.password_edit);
|
||||
password.setText(bundle.getString("password"));
|
||||
final CheckBox passwordCheckbox = (CheckBox) view.findViewById(R.id.checkbox_toggle_password);
|
||||
passwordCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
password.setTransformationMethod(null);
|
||||
} else {
|
||||
password.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.setView(view);
|
||||
|
||||
builder.setPositiveButton(R.string.button_remember, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
JSONObject response = new JSONObject();
|
||||
try {
|
||||
response.put("callback", mCallbackID);
|
||||
final JSONObject inputs = new JSONObject();
|
||||
inputs.put("username", username.getText());
|
||||
inputs.put("password", password.getText());
|
||||
response.put("inputs", inputs);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating doorhanger reply message");
|
||||
response = null;
|
||||
Toast.makeText(mContext, mResources.getString(R.string.doorhanger_login_edit_toast_error), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
mOnButtonClickListener.onButtonClick(response, LoginDoorHanger.this);
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Dialog dialog = builder.create();
|
||||
mLogin.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
|
||||
} catch (JSONException e) {
|
||||
// Log an error, but leave the text visible if there was a username.
|
||||
Log.e(LOGTAG, "Error fetching actionText from JSON", e);
|
||||
if (!hasUsername) {
|
||||
mLogin.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2232,7 +2232,11 @@ var NativeWindow = {
|
||||
* { text: <title>,
|
||||
* resource: <resource_url> }
|
||||
*
|
||||
* subtext: A string to appear below the doorhanger message.
|
||||
* actionText: An object that specifies a clickable string, a type of action,
|
||||
* and a bundle blob for the consumer to create a click action.
|
||||
* { text: <text>,
|
||||
* type: <type>,
|
||||
* bundle: <blob-object> }
|
||||
*
|
||||
* @param aCategory
|
||||
* Doorhanger type to display (e.g., LOGIN)
|
||||
|
@ -148,11 +148,11 @@ LoginManagerPrompter.prototype = {
|
||||
* String message to be displayed in the doorhanger
|
||||
* @param aButtons
|
||||
* Buttons to display with the doorhanger
|
||||
* @param aSubtext
|
||||
* String to be displayed below the aBody message
|
||||
* @param aActionText
|
||||
* Object with text to be displayed as clickable, along with a bundle to create an action
|
||||
*
|
||||
*/
|
||||
_showLoginNotification : function (aName, aTitle, aBody, aButtons, aSubtext) {
|
||||
_showLoginNotification : function (aName, aTitle, aBody, aButtons, aActionText) {
|
||||
this.log("Adding new " + aName + " notification bar");
|
||||
let notifyWin = this._window.top;
|
||||
let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
|
||||
@ -171,7 +171,7 @@ LoginManagerPrompter.prototype = {
|
||||
persistWhileVisible: true,
|
||||
timeout: Date.now() + 10000,
|
||||
title: aTitle,
|
||||
subtext: aSubtext
|
||||
actionText: aActionText
|
||||
}
|
||||
|
||||
var nativeWindow = this._getNativeWindow();
|
||||
@ -194,11 +194,16 @@ LoginManagerPrompter.prototype = {
|
||||
|
||||
let displayHost = this._getShortDisplayHost(aLogin.hostname);
|
||||
let title = { text: displayHost, resource: aLogin.hostname };
|
||||
let subtext = null;
|
||||
|
||||
if (aLogin.username) {
|
||||
subtext = this._sanitizeUsername(aLogin.username);
|
||||
}
|
||||
let username = aLogin.username ? this._sanitizeUsername(aLogin.username) : "";
|
||||
|
||||
let actionText = {
|
||||
text: username,
|
||||
type: "EDIT",
|
||||
bundle: { username: username,
|
||||
password: aLogin.password }
|
||||
};
|
||||
|
||||
// The callbacks in |buttons| have a closure to access the variables
|
||||
// in scope here; set one to |this._pwmgr| so we can get back to pwmgr
|
||||
// without a getService() call.
|
||||
@ -215,14 +220,19 @@ LoginManagerPrompter.prototype = {
|
||||
},
|
||||
{
|
||||
label: this._getLocalizedString("rememberButton"),
|
||||
callback: function() {
|
||||
callback: function(checked, response) {
|
||||
|
||||
if (response) {
|
||||
aLogin.username = response["username"] || aLogin.username;
|
||||
aLogin.password = response["password"] || aLogin.password;
|
||||
}
|
||||
pwmgr.addLogin(aLogin);
|
||||
promptHistogram.add(PROMPT_ADD);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
this._showLoginNotification("password-save", title, notificationText, buttons, subtext);
|
||||
this._showLoginNotification("password-save", title, notificationText, buttons, actionText);
|
||||
},
|
||||
|
||||
/*
|
||||
|
@ -74,7 +74,7 @@ body {
|
||||
display: none;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.header {
|
||||
@ -101,7 +101,7 @@ body {
|
||||
}
|
||||
|
||||
.header > h1 {
|
||||
font-size: 1.33rem;
|
||||
font-size: 1.33em;
|
||||
font-weight: 700;
|
||||
line-height: 1.1em;
|
||||
width: 100%;
|
||||
@ -146,16 +146,16 @@ body {
|
||||
/* This covers caption, domain, and credits
|
||||
texts in the reader UI */
|
||||
|
||||
.content .wp-caption-text,
|
||||
.content figcaption,
|
||||
#moz-reader-content .wp-caption-text,
|
||||
#moz-reader-content figcaption,
|
||||
.header > .domain,
|
||||
.header > .credits {
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.content {
|
||||
#moz-reader-content {
|
||||
display: none;
|
||||
font-size: 1rem;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.content a {
|
||||
@ -175,7 +175,7 @@ body {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.content p {
|
||||
#moz-reader-content p {
|
||||
line-height: 1.4em !important;
|
||||
margin: 0px !important;
|
||||
margin-bottom: 20px !important;
|
||||
@ -258,8 +258,8 @@ body {
|
||||
border-left-color: #777777 !important;
|
||||
}
|
||||
|
||||
.content ul,
|
||||
.content ol {
|
||||
#moz-reader-content ul,
|
||||
#moz-reader-content ol {
|
||||
margin: 0px !important;
|
||||
margin-bottom: 20px !important;
|
||||
padding: 0px !important;
|
||||
|
@ -48,7 +48,7 @@ let AboutReader = function(mm, win, articlePromise) {
|
||||
this._domainElementRef = Cu.getWeakReference(doc.getElementById("reader-domain"));
|
||||
this._titleElementRef = Cu.getWeakReference(doc.getElementById("reader-title"));
|
||||
this._creditsElementRef = Cu.getWeakReference(doc.getElementById("reader-credits"));
|
||||
this._contentElementRef = Cu.getWeakReference(doc.getElementById("reader-content"));
|
||||
this._contentElementRef = Cu.getWeakReference(doc.getElementById("moz-reader-content"));
|
||||
this._toolbarElementRef = Cu.getWeakReference(doc.getElementById("reader-toolbar"));
|
||||
this._messageElementRef = Cu.getWeakReference(doc.getElementById("reader-message"));
|
||||
|
||||
@ -354,13 +354,13 @@ AboutReader.prototype = {
|
||||
},
|
||||
|
||||
_setFontSize: function Reader_setFontSize(newFontSize) {
|
||||
let htmlClasses = this._doc.documentElement.classList;
|
||||
let containerClasses = this._doc.getElementById("container").classList;
|
||||
|
||||
if (this._fontSize > 0)
|
||||
htmlClasses.remove("font-size" + this._fontSize);
|
||||
containerClasses.remove("font-size" + this._fontSize);
|
||||
|
||||
this._fontSize = newFontSize;
|
||||
htmlClasses.add("font-size" + this._fontSize);
|
||||
containerClasses.add("font-size" + this._fontSize);
|
||||
|
||||
this._mm.sendAsyncMessage("Reader:SetIntPref", {
|
||||
name: "reader.font_size",
|
||||
|
@ -18,6 +18,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ReaderWorker", "resource://gre/modules/reader/ReaderWorker.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "Readability", function() {
|
||||
let scope = {};
|
||||
Services.scriptloader.loadSubScript("resource://gre/modules/reader/Readability.js", scope);
|
||||
return scope["Readability"];
|
||||
});
|
||||
|
||||
this.ReaderMode = {
|
||||
// Version of the cache schema.
|
||||
CACHE_VERSION: 1,
|
||||
@ -78,36 +84,7 @@ this.ReaderMode = {
|
||||
return false;
|
||||
}
|
||||
|
||||
let REGEXPS = {
|
||||
unlikelyCandidates: /combx|comment|community|disqus|extra|foot|header|menu|remark|rss|shoutbox|sidebar|sponsor|ad-break|agegate|pagination|pager|popup|tweet|twitter/i,
|
||||
okMaybeItsACandidate: /and|article|body|column|main|shadow/i,
|
||||
};
|
||||
|
||||
let nodes = doc.getElementsByTagName("p");
|
||||
if (nodes.length < 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let possibleParagraphs = 0;
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let node = nodes[i];
|
||||
let matchString = node.className + " " + node.id;
|
||||
|
||||
if (REGEXPS.unlikelyCandidates.test(matchString) &&
|
||||
!REGEXPS.okMaybeItsACandidate.test(matchString)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.textContent.trim().length < 100) {
|
||||
continue;
|
||||
}
|
||||
|
||||
possibleParagraphs++;
|
||||
if (possibleParagraphs >= 5) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return new Readability(uri, doc).isProbablyReaderable();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -19,7 +19,7 @@
|
||||
<div id="reader-credits" class="credits"></div>
|
||||
</div>
|
||||
|
||||
<div id="reader-content" class="content">
|
||||
<div id="moz-reader-content" class="content">
|
||||
</div>
|
||||
|
||||
<div id="reader-message" class="message">
|
||||
|
@ -77,6 +77,8 @@
|
||||
<li><a href="about:license#chromium">Chromium License</a></li>
|
||||
<li><a href="about:license#codemirror">CodeMirror License</a></li>
|
||||
<li><a href="about:license#cubic-bezier">cubic-bezier License</a></li>
|
||||
<li><a href="about:license#d3">D3 License</a></li>
|
||||
<li><a href="about:license#dagre-d3">Dagre-D3 License</a></li>
|
||||
<li><a href="about:license#dtoa">dtoa License</a></li>
|
||||
<li><a href="about:license#hunspell-nl">Dutch Spellchecking Dictionary License</a></li>
|
||||
<li><a href="about:license#edl">Eclipse Distribution License</a></li>
|
||||
@ -1161,13 +1163,13 @@ DAMAGES.
|
||||
<p>Some versions of this product contains code from the following LGPLed libraries:</p>
|
||||
|
||||
<ul>
|
||||
<li><a
|
||||
<li><a
|
||||
href="https://addons.mozilla.org/en-US/firefox/addon/görans-hemmasnickrade-ordli/">Swedish dictionary</a>
|
||||
</ul>
|
||||
|
||||
<pre>Copyright © 2007 Free Software Foundation, Inc.
|
||||
<<a href="http://fsf.org/">http://fsf.org/</a>>
|
||||
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.</pre>
|
||||
|
||||
@ -1368,7 +1370,7 @@ executables of your software products.
|
||||
|
||||
<h1><a id="adobecmap"></a>Adobe CMap License</h1>
|
||||
|
||||
<p>This license applies to files in the directory
|
||||
<p>This license applies to files in the directory
|
||||
<span class="path">browser/extensions/pdfjs/content/web/cmaps/</span>.</p>
|
||||
|
||||
<pre>
|
||||
@ -1386,12 +1388,12 @@ disclaimer.
|
||||
Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
provided with the distribution.
|
||||
|
||||
Neither the name of Adobe Systems Incorporated nor the names
|
||||
of its contributors may be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
@ -2056,6 +2058,73 @@ DEALINGS IN THE SOFTWARE.
|
||||
</pre>
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
<h1><a id="d3"></a>D3 License</h1>
|
||||
|
||||
<p>This license applies to the file
|
||||
<span class="path">browser/devtools/shared/d3.js</span>.
|
||||
</p>
|
||||
<pre>
|
||||
Copyright (c) 2014, Michael Bostock
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* The name Michael Bostock may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
</pre>
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
<h1><a id="dagre-d3"></a>Dagre-D3 License</h1>
|
||||
|
||||
<p>This license applies to the file
|
||||
<span class="path">browser/devtools/webaudioeditor/lib/dagre-d3.js</span>.
|
||||
</p>
|
||||
<pre>
|
||||
Copyright (c) 2013 Chris Pettitt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
</pre>
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
<h1><a id="dtoa"></a>dtoa License</h1>
|
||||
@ -4685,4 +4754,3 @@ following terms:
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
@ -2,6 +2,32 @@
|
||||
* 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/. */
|
||||
|
||||
/**
|
||||
* Test log warnings that happen before the test has started
|
||||
* "Couldn't get the user appdata directory. Crash events may not be produced."
|
||||
* in nsExceptionHandler.cpp (possibly bug 619104)
|
||||
*
|
||||
* Test log warnings that happen after the test has finished
|
||||
* "OOPDeinit() without successful OOPInit()" in nsExceptionHandler.cpp
|
||||
* (bug 619104)
|
||||
* "XPCOM objects created/destroyed from static ctor/dtor" in nsTraceRefcnt.cpp
|
||||
* (possibly bug 457479)
|
||||
*
|
||||
* Other warnings printed to the test logs
|
||||
* "site security information will not be persisted" in
|
||||
* nsSiteSecurityService.cpp and the error in nsSystemInfo.cpp preceding this
|
||||
* error are due to not having a profile when running some of the xpcshell
|
||||
* tests. Since most xpcshell tests also log these errors these tests don't
|
||||
* call do_get_profile unless necessary for the test.
|
||||
* The "This method is lossy. Use GetCanonicalPath !" warning on Windows in
|
||||
* nsLocalFileWin.cpp is from the call to GetNSSProfilePath in
|
||||
* nsNSSComponent.cpp due to it using GetNativeCanonicalPath.
|
||||
* "!mMainThread" in nsThreadManager.cpp are due to using timers and it might be
|
||||
* possible to fix some or all of these in the test itself.
|
||||
* "NS_FAILED(rv)" in nsThreadUtils.cpp are due to using timers and it might be
|
||||
* possible to fix some or all of these in the test itself.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { classes: Cc, interfaces: Ci, manager: Cm, results: Cr,
|
||||
@ -782,6 +808,17 @@ function setupTestCommon() {
|
||||
// it is defined as a function.
|
||||
adjustGeneralPaths();
|
||||
|
||||
// This prevents a warning about not being able to find the greprefs.js file
|
||||
// from being logged.
|
||||
let grePrefsFile = getGREDir();
|
||||
if (!grePrefsFile.exists()) {
|
||||
grePrefsFile.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
|
||||
}
|
||||
grePrefsFile.append("greprefs.js");
|
||||
if (!grePrefsFile.exists()) {
|
||||
grePrefsFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
|
||||
}
|
||||
|
||||
// Remove the updates directory on Windows and Mac OS X which is located
|
||||
// outside of the application directory after the call to adjustGeneralPaths
|
||||
// has set it up. Since the test hasn't ran yet and the directory shouldn't
|
||||
@ -1581,8 +1618,8 @@ function shouldRunServiceTest(aFirstTest) {
|
||||
// in which case we should fail the test if the updater binary is signed so
|
||||
// the build system can be fixed by adding the registry key.
|
||||
if (IS_AUTHENTICODE_CHECK_ENABLED) {
|
||||
Assert.ok(isBinSigned, "the updater.exe binary should not be signed " +
|
||||
"when the test registry key doesn't exist (if not, build " +
|
||||
Assert.ok(!isBinSigned, "the updater.exe binary should not be signed " +
|
||||
"when the test registry key doesn't exist (if it is, build " +
|
||||
"system configuration bug?)");
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ body.loaded {
|
||||
display: none;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
@ -104,8 +104,8 @@ body.loaded {
|
||||
}
|
||||
|
||||
.domain {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.33rem;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.48em;
|
||||
padding-bottom: 4px;
|
||||
font-family: "Fira Sans", Helvetica, Arial, sans-serif;
|
||||
text-decoration: none;
|
||||
@ -123,16 +123,16 @@ body.loaded {
|
||||
}
|
||||
|
||||
.header > h1 {
|
||||
font-size: 1.33rem;
|
||||
line-height: 1.66rem;
|
||||
font-size: 1.33em;
|
||||
line-height: 1.25em;
|
||||
width: 100%;
|
||||
margin: 30px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.header > .credits {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.33rem;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.48em;
|
||||
margin: 0 0 30px 0;
|
||||
padding: 0;
|
||||
font-style: italic;
|
||||
@ -140,10 +140,10 @@ body.loaded {
|
||||
|
||||
/* Content */
|
||||
|
||||
.content {
|
||||
#moz-reader-content {
|
||||
display: none;
|
||||
font-size: 1rem;
|
||||
line-height: 1.6rem;
|
||||
font-size: 1em;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
.content h1,
|
||||
@ -152,19 +152,19 @@ body.loaded {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.content h1 {
|
||||
font-size: 1.33rem;
|
||||
line-height: 1.66rem;
|
||||
#moz-reader-content h1 {
|
||||
font-size: 1.33em;
|
||||
line-height: 1.25em;
|
||||
}
|
||||
|
||||
.content h2 {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.66rem;
|
||||
#moz-reader-content h2 {
|
||||
font-size: 1.1em;
|
||||
line-height: 1.51em;
|
||||
}
|
||||
|
||||
.content h3 {
|
||||
font-size: 1rem;
|
||||
line-height: 1.66rem;
|
||||
#moz-reader-content h3 {
|
||||
font-size: 1em;
|
||||
line-height: 1.66em;
|
||||
}
|
||||
|
||||
.content a {
|
||||
@ -208,11 +208,11 @@ body.loaded {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.content .caption,
|
||||
.content .wp-caption-text,
|
||||
.content figcaption {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.33rem;
|
||||
#moz-reader-content .caption,
|
||||
#moz-reader-content .wp-caption-text,
|
||||
#moz-reader-content figcaption {
|
||||
font-size: 0.9em;
|
||||
line-height: 1.48em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user