MozReview-Commit-ID: EGaA6e4loBM
This commit is contained in:
Kartikaya Gupta 2017-04-13 11:08:02 -04:00
Родитель 79b1971bae 9b1d010f9b
Коммит b4341d2689
775 изменённых файлов: 38850 добавлений и 25427 удалений

Просмотреть файл

@ -25,6 +25,7 @@
^gfx/ots/.*
^gfx/qcms/.*
^gfx/skia/.*
^gfx/vr/openvr/.*
^gfx/webrender.*
^gfx/webrender_traits.*
^gfx/ycbcr/.*

Просмотреть файл

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 1351074 - required because bug 1352982 means removing a .jsm requires a clobber
Bug 1356151 - Clobber needed after bug 1353295 was backed out

Просмотреть файл

@ -11,9 +11,9 @@
"unpack": true,
"algorithm": "sha512",
"filename": "sixgill.tar.xz",
"hg_id": "8cb9c3fb039a+ tip",
"digest": "36dc644e24c0aa824975ad8f5c15714445d5cb064d823000c3cb637e885199414d7df551e6b99233f0656dcf5760918192ef04113c486af37f3c489bb93ad029",
"size": 2631908
"hg_id": "221d0d2eead9",
"digest": "2e56a3cf84764b8e63720e5f961cff7ba8ba5cf2f353dac55c69486489bcd89f53a757e09469a07700b80cd09f09666c2db4ce375b67060ac3be967714597231",
"size": 2629600
},
{
"algorithm": "sha512",

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -685,7 +685,11 @@ pref("browser.preferences.search", false);
// (The Storage Management-related prefs are browser.storageManager.* )
// The Offline(Appcache) Group section in about:preferences will be hidden.
// And the task to clear appcache will be done by Storage Management.
#if defined(NIGHTLY_BUILD)
pref("browser.preferences.offlineGroup.enabled", false);
#else
pref("browser.preferences.offlineGroup.enabled", true);
#endif
pref("browser.download.show_plugins_in_list", true);
pref("browser.download.hide_plugins_without_extensions", true);

Просмотреть файл

@ -71,7 +71,7 @@ var gDataNotificationInfoBar = {
if (Preferences.get("browser.preferences.useOldOrganization", false)) {
window.openAdvancedPreferences("dataChoicesTab");
} else {
window.openPreferences("paneAdvanced");
window.openPreferences("advanced-reports");
}
},
}];

Просмотреть файл

@ -207,11 +207,18 @@ let gDecoderDoctorHandler = {
AppConstants.platform == "linux") {
return gNavigatorBundle.getString("decoder.unsupportedLibavcodec.message");
}
if (type == "decode-error") {
return gNavigatorBundle.getString("decoder.decodeError.message");
}
if (type == "decode-warning") {
return gNavigatorBundle.getString("decoder.decodeWarning.message");
}
return "";
},
getSumoForLearnHowButton(type) {
if (AppConstants.platform == "win") {
if (type == "platform-decoder-not-found" &&
AppConstants.platform == "win") {
return "fix-video-audio-problems-firefox-windows";
}
if (type == "cannot-initialize-pulseaudio") {
@ -220,6 +227,13 @@ let gDecoderDoctorHandler = {
return "";
},
getEndpointForReportIssueButton(type) {
if (type == "decode-error" || type == "decode-warning") {
return Services.prefs.getStringPref("media.decoder-doctor.new-issue-endpoint", "");
}
return "";
},
receiveMessage({target: browser, data: data}) {
let box = gBrowser.getNotificationBox(browser);
let notificationId = "decoder-doctor-notification";
@ -238,6 +252,8 @@ let gDecoderDoctorHandler = {
// contains analysis information from Decoder Doctor:
// - 'type' is the type of issue, it determines which text to show in the
// infobar.
// - 'isSolved' is true when the notification actually indicates the
// resolution of that issue, to be reported as telemetry.
// - 'decoderDoctorReportId' is the Decoder Doctor issue identifier, to be
// used here as key for the telemetry (counting infobar displays,
// "Learn how" buttons clicks, and resolutions) and for the prefs used
@ -245,9 +261,10 @@ let gDecoderDoctorHandler = {
// - 'formats' contains a comma-separated list of formats (or key systems)
// that suffer the issue. These are kept in a pref, which the backend
// uses to later find when an issue is resolved.
// - 'isSolved' is true when the notification actually indicates the
// resolution of that issue, to be reported as telemetry.
let {type, isSolved, decoderDoctorReportId, formats} = parsedData;
// - 'decodeIssue' is a description of the decode error/warning.
// - 'resourceURL' is the resource with the issue.
let {type, isSolved, decoderDoctorReportId,
formats, decodeIssue, docURL, resourceURL} = parsedData;
type = type.toLowerCase();
// Error out early on invalid ReportId
if (!(/^\w+$/mi).test(decoderDoctorReportId)) {
@ -260,34 +277,36 @@ let gDecoderDoctorHandler = {
// We keep the list of formats in prefs for the sake of the decoder itself,
// which reads it to determine when issues get solved for these formats.
// (Writing prefs from e10s content is now allowed.)
let formatsPref = "media.decoder-doctor." + decoderDoctorReportId + ".formats";
// (Writing prefs from e10s content is not allowed.)
let formatsPref = formats &&
"media.decoder-doctor." + decoderDoctorReportId + ".formats";
let buttonClickedPref = "media.decoder-doctor." + decoderDoctorReportId + ".button-clicked";
let histogram =
Services.telemetry.getKeyedHistogramById("DECODER_DOCTOR_INFOBAR_STATS");
let formatsInPref = Services.prefs.getPrefType(formatsPref) &&
Services.prefs.getCharPref(formatsPref);
let formatsInPref = formats &&
Services.prefs.getCharPref(formatsPref, "");
if (!isSolved) {
if (!formats) {
Cu.reportError("Malformed Decoder Doctor unsolved message with no formats");
return;
}
if (!formatsInPref) {
Services.prefs.setCharPref(formatsPref, formats);
histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_SHOWN_FIRST);
} else {
// Split existing formats into an array of strings.
let existing = formatsInPref.split(",").map(x => x.trim());
// Keep given formats that were not already recorded.
let newbies = formats.split(",").map(x => x.trim())
.filter(x => !existing.includes(x));
// And rewrite pref with the added new formats (if any).
if (newbies.length) {
Services.prefs.setCharPref(formatsPref,
existing.concat(newbies).join(", "));
if (formats) {
if (!formatsInPref) {
Services.prefs.setCharPref(formatsPref, formats);
histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_SHOWN_FIRST);
} else {
// Split existing formats into an array of strings.
let existing = formatsInPref.split(",").map(x => x.trim());
// Keep given formats that were not already recorded.
let newbies = formats.split(",").map(x => x.trim())
.filter(x => !existing.includes(x));
// And rewrite pref with the added new formats (if any).
if (newbies.length) {
Services.prefs.setCharPref(formatsPref,
existing.concat(newbies).join(", "));
}
}
} else if (!decodeIssue) {
Cu.reportError("Malformed Decoder Doctor unsolved message with no formats nor decode issue");
return;
}
histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_SHOWN);
@ -298,8 +317,8 @@ let gDecoderDoctorHandler = {
label: gNavigatorBundle.getString("decoder.noCodecs.button"),
accessKey: gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
callback() {
let clickedInPref = Services.prefs.getPrefType(buttonClickedPref) &&
Services.prefs.getBoolPref(buttonClickedPref);
let clickedInPref =
Services.prefs.getBoolPref(buttonClickedPref, false);
if (!clickedInPref) {
Services.prefs.setBoolPref(buttonClickedPref, true);
histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_CLICKED_FIRST);
@ -311,6 +330,31 @@ let gDecoderDoctorHandler = {
}
});
}
let endpoint = gDecoderDoctorHandler.getEndpointForReportIssueButton(type);
if (endpoint) {
buttons.push({
label: gNavigatorBundle.getString("decoder.decodeError.button"),
accessKey: gNavigatorBundle.getString("decoder.decodeError.accesskey"),
callback() {
let clickedInPref =
Services.prefs.getBoolPref(buttonClickedPref, false);
if (!clickedInPref) {
Services.prefs.setBoolPref(buttonClickedPref, true);
histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_CLICKED_FIRST);
}
histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_CLICKED);
let params = new URLSearchParams;
params.append("url", docURL);
params.append("problem_type", "video_bug");
params.append("src", "media-decode-error");
params.append("details",
"Technical Information:\n" + decodeIssue +
(resourceURL ? ("\nResource: " + resourceURL) : ""));
openUILinkIn(endpoint + "?" + params.toString(), "tab");
}
});
}
box.appendNotification(
title,

Просмотреть файл

@ -298,24 +298,23 @@ var StarUI = {
parent.setAttribute("open", "true");
}
}
let panel = this.panel;
let target = panel;
if (target.parentNode) {
// By targeting the panel's parent and using a capturing listener, we
// can have our listener called before others waiting for the panel to
// be shown (which probably expect the panel to be fully initialized)
target = target.parentNode;
}
target.addEventListener("popupshown", function shownListener(event) {
if (event.target == panel) {
target.removeEventListener("popupshown", shownListener, true);
gEditItemOverlay.initPanel({ node: aNode
, hiddenRows: ["description", "location",
"loadInSidebar", "keyword"]
, focusedElement: "preferred"});
let onPanelReady = fn => {
let target = this.panel;
if (target.parentNode) {
// By targeting the panel's parent and using a capturing listener, we
// can have our listener called before others waiting for the panel to
// be shown (which probably expect the panel to be fully initialized)
target = target.parentNode;
}
}, true);
target.addEventListener("popupshown", function(event) {
fn();
}, {"capture": true, "once": true});
};
gEditItemOverlay.initPanel({ node: aNode
, onPanelReady
, hiddenRows: ["description", "location",
"loadInSidebar", "keyword"]
, focusedElement: "preferred"});
this.panel.openPopup(aAnchorElement, aPosition);
}),

Просмотреть файл

@ -2545,8 +2545,18 @@
// we decide that we're animating, we cancel the non-animating case,
// and vice-versa. Then, in _endRemoveTab, we "finish" both
// stopwatches, which is a no-op for cancelled stopwatches.
TelemetryStopwatch.start("FX_TAB_CLOSE_TIME_ANIM_MS", aTab);
TelemetryStopwatch.start("FX_TAB_CLOSE_TIME_NO_ANIM_MS", aTab);
//
// Note that removeTab might be re-entrant, as we currently spin an
// event loop while waiting for remote browsers to tell us whether or
// not we're allowed to unload them via permitUnload. This means that
// subsequent calls to removeTab will also have these stopwatches
// on file. There's also the case that the tab was animating closed,
// and removeTab was called on it before it completed. In either case,
// we skip (re)starting the stopwatches.
if (!aTab._pendingPermitUnload && !aTab.closing) {
TelemetryStopwatch.start("FX_TAB_CLOSE_TIME_ANIM_MS", aTab);
TelemetryStopwatch.start("FX_TAB_CLOSE_TIME_NO_ANIM_MS", aTab);
}
if (aParams) {
var animate = aParams.animate;
@ -7104,7 +7114,8 @@
class="tab-content" align="center">
<xul:image xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected"
class="tab-throbber"
role="presentation"/>
role="presentation"
layer="true" />
<xul:image xbl:inherits="src=image,loadingprincipal=iconLoadingPrincipal,fadein,pinned,selected=visuallyselected,busy,crashed,sharing"
anonid="tab-icon-image"
class="tab-icon-image"

Просмотреть файл

@ -139,6 +139,27 @@ const SELECT_STYLE_OF_OPTION_CHANGES_AFTER_TRANSITIONEND =
' <option selected="true">{"end": "true"}</option>' +
"</select></body></html>";
const SELECT_TRANSPARENT_COLOR_WITH_TEXT_SHADOW =
"<html><head><style>" +
" select { color: transparent; text-shadow: 0 0 0 #303030; }" +
"</style></head><body><select id='one'>" +
' <option>{"color": "rgba(0, 0, 0, 0)", "backgroundColor": "rgba(0, 0, 0, 0)", "textShadow": "rgb(48, 48, 48) 0px 0px 0px"}</option>' +
' <option selected="true">{"end": "true"}</option>' +
"</select></body></html>";
let SELECT_LONG_WITH_TRANSITION =
"<html><head><style>" +
" select { transition: all .2s linear; }" +
" select:focus { color: purple; }" +
"</style></head><body><select id='one'>";
for (let i = 0; i < 75; i++) {
SELECT_LONG_WITH_TRANSITION +=
' <option>{"color": "rgb(128, 0, 128)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>';
}
SELECT_LONG_WITH_TRANSITION +=
' <option selected="true">{"end": "true"}</option>' +
"</select></body></html>";
function getSystemColor(color) {
// Need to convert system color to RGB color.
let textarea = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
@ -174,6 +195,10 @@ function testOptionColors(index, item, menulist) {
"Item " + (index) + " has correct foreground color");
is(getComputedStyle(item).backgroundColor, expected.backgroundColor,
"Item " + (index) + " has correct background color");
if (expected.textShadow) {
is(getComputedStyle(item).textShadow, expected.textShadow,
"Item " + (index) + " has correct text-shadow color");
}
}
}
@ -205,6 +230,11 @@ function* testSelectColors(select, itemCount, options) {
is(getComputedStyle(selectPopup).color, options.selectColor,
"popup has expected foreground color");
if (options.selectTextShadow) {
is(getComputedStyle(selectPopup).textShadow, options.selectTextShadow,
"popup has expected text-shadow color");
}
// Combine the select popup's backgroundColor and the
// backgroundImage color to get the color that is seen by
// the user.
@ -240,9 +270,10 @@ function* testSelectColors(select, itemCount, options) {
child = child.nextSibling;
}
yield hideSelectPopup(selectPopup, "escape");
yield BrowserTestUtils.removeTab(tab);
if (!options.leaveOpen) {
yield hideSelectPopup(selectPopup, "escape");
yield BrowserTestUtils.removeTab(tab);
}
}
add_task(function* setup() {
@ -388,3 +419,35 @@ add_task(function* test_style_of_options_is_dependent_on_transitionend() {
yield testSelectColors(SELECT_STYLE_OF_OPTION_CHANGES_AFTER_TRANSITIONEND, 2, options);
});
add_task(function* test_transparent_color_with_text_shadow() {
let options = {
selectColor: "rgba(0, 0, 0, 0)",
selectTextShadow: "rgb(48, 48, 48) 0px 0px 0px",
selectBgColor: "rgb(255, 255, 255)"
};
yield testSelectColors(SELECT_TRANSPARENT_COLOR_WITH_TEXT_SHADOW, 2, options);
});
add_task(function* test_select_with_transition_doesnt_lose_scroll_position() {
let options = {
selectColor: "rgb(128, 0, 128)",
selectBgColor: "rgb(255, 255, 255)",
waitForComputedStyle: {
property: "color",
value: "rgb(128, 0, 128)"
},
leaveOpen: true
};
yield testSelectColors(SELECT_LONG_WITH_TRANSITION, 76, options);
let menulist = document.getElementById("ContentSelectDropdown");
let selectPopup = menulist.menupopup;
let scrollBox = selectPopup.scrollBox;
is(scrollBox.scrollTop, scrollBox.scrollTopMax,
"The popup should be scrolled to the bottom of the list (where the selected item is)");
yield hideSelectPopup(selectPopup, "escape");
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

Просмотреть файл

@ -352,7 +352,6 @@ skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug
skip-if = !datareporting
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_decoderDoctor.js]
skip-if = os == "mac" # decoder doctor isn't implemented on osx
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_discovery.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.

Просмотреть файл

@ -1,19 +1,43 @@
"use strict";
function* test_decoder_doctor_notification(type, notificationMessage, options) {
// 'data' contains the notification data object:
// - data.type must be provided.
// - data.isSolved and data.decoderDoctorReportId will be added if not provided
// (false and "testReportId" resp.)
// - Other fields (e.g.: data.formats) may be provided as needed.
// 'notificationMessage': Expected message in the notification bar.
// Falsy if nothing is expected after the notification is sent, in which case
// we won't have further checks, so the following parameters are not needed.
// 'label': Expected button label. Falsy if no button is expected, in which case
// we won't have further checks, so the following parameters are not needed.
// 'accessKey': Expected access key for the button.
// 'tabChecker': function(openedTab) called with the opened tab that resulted
// from clicking the button.
function* test_decoder_doctor_notification(data, notificationMessage,
label, accessKey, tabChecker) {
if (typeof data.type === "undefined") {
ok(false, "Test implementation error: data.type must be provided");
return;
}
data.isSolved = data.isSolved || false;
if (typeof data.decoderDoctorReportId === "undefined") {
data.decoderDoctorReportId = "testReportId";
}
yield BrowserTestUtils.withNewTab({ gBrowser }, function*(browser) {
let awaitNotificationBar =
BrowserTestUtils.waitForNotificationBar(gBrowser, browser, "decoder-doctor-notification");
yield ContentTask.spawn(browser, type, function*(aType) {
yield ContentTask.spawn(browser, data, function*(aData) {
Services.obs.notifyObservers(content.window,
"decoder-doctor-notification",
JSON.stringify({type: aType,
isSolved: false,
decoderDoctorReportId: "test",
formats: "test"}));
JSON.stringify(aData));
});
if (!notificationMessage) {
ok(true, "Tested notifying observers with a nonsensical message, no effects expected");
return;
}
let notification;
try {
notification = yield awaitNotificationBar;
@ -24,62 +48,132 @@ function* test_decoder_doctor_notification(type, notificationMessage, options) {
ok(notification, "Got decoder-doctor-notification notification");
is(notification.getAttribute("label"), notificationMessage,
"notification message should match expectation");
"notification message should match expectation");
let button = notification.childNodes[0];
if (options && options.noLearnMoreButton) {
ok(!button, "There should not be a Learn More button");
if (!label) {
ok(!button, "There should not be button");
return;
}
is(button.getAttribute("label"), gNavigatorBundle.getString("decoder.noCodecs.button"),
"notification button should be 'Learn more'");
is(button.getAttribute("accesskey"), gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
"notification button should have accesskey");
is(button.getAttribute("label"),
label,
`notification button should be '${label}'`);
is(button.getAttribute("accesskey"),
accessKey,
"notification button should have accesskey");
let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
let url = baseURL + ((options && options.sumo) ||
"fix-video-audio-problems-firefox-windows");
let awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser, url);
if (!tabChecker) {
ok(false, "Test implementation error: Missing tabChecker");
return;
}
let awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser);
button.click();
let sumoTab = yield awaitNewTab;
yield BrowserTestUtils.removeTab(sumoTab);
let openedTab = yield awaitNewTab;
tabChecker(openedTab);
yield BrowserTestUtils.removeTab(openedTab);
});
}
function tab_checker_for_sumo(expectedPath) {
return function(openedTab) {
let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
let url = baseURL + expectedPath;
is(openedTab.linkedBrowser.currentURI.spec, url,
`Expected '${url}' in new tab`);
};
}
function tab_checker_for_webcompat(expectedParams) {
return function(openedTab) {
let urlString = openedTab.linkedBrowser.currentURI.spec;
let endpoint = Services.prefs.getStringPref("media.decoder-doctor.new-issue-endpoint", "");
ok(urlString.startsWith(endpoint),
`Expected URL starting with '${endpoint}', got '${urlString}'`)
let params = new URL(urlString).searchParams;
for (let k in expectedParams) {
if (!params.has(k)) {
ok(false, `Expected ${k} in webcompat URL`);
} else {
is(params.get(k), expectedParams[k],
`Expected ${k}='${expectedParams[k]}' in webcompat URL`);
}
}
};
}
add_task(function* test_platform_decoder_not_found() {
let message;
let message = "";
let isLinux = AppConstants.platform == "linux";
if (isLinux) {
message = gNavigatorBundle.getString("decoder.noCodecsLinux.message");
} else {
} else if (AppConstants.platform == "win") {
message = gNavigatorBundle.getString("decoder.noHWAcceleration.message");
}
yield test_decoder_doctor_notification("platform-decoder-not-found",
message,
{noLearnMoreButton: isLinux});
yield test_decoder_doctor_notification(
{type: "platform-decoder-not-found", formats: "testFormat"},
message,
isLinux ? "" : gNavigatorBundle.getString("decoder.noCodecs.button"),
isLinux ? "" : gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
tab_checker_for_sumo("fix-video-audio-problems-firefox-windows"));
});
add_task(function* test_cannot_initialize_pulseaudio() {
let message = "";
// This is only sent on Linux.
if (AppConstants.platform != "linux") {
return;
if (AppConstants.platform == "linux") {
message = gNavigatorBundle.getString("decoder.noPulseAudio.message");
}
let message = gNavigatorBundle.getString("decoder.noPulseAudio.message");
yield test_decoder_doctor_notification("cannot-initialize-pulseaudio",
message,
{sumo: "fix-common-audio-and-video-issues"});
yield test_decoder_doctor_notification(
{type: "cannot-initialize-pulseaudio", formats: "testFormat"},
message,
gNavigatorBundle.getString("decoder.noCodecs.button"),
gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
tab_checker_for_sumo("fix-common-audio-and-video-issues"));
});
add_task(function* test_unsupported_libavcodec() {
let message = "";
// This is only sent on Linux.
if (AppConstants.platform != "linux") {
return;
if (AppConstants.platform == "linux") {
message =
gNavigatorBundle.getString("decoder.unsupportedLibavcodec.message");
}
let message = gNavigatorBundle.getString("decoder.unsupportedLibavcodec.message");
yield test_decoder_doctor_notification("unsupported-libavcodec",
message,
{noLearnMoreButton: true});
yield test_decoder_doctor_notification(
{type: "unsupported-libavcodec", formats: "testFormat"}, message);
});
add_task(function* test_decode_error() {
yield SpecialPowers.pushPrefEnv(
{ set: [["media.decoder-doctor.new-issue-endpoint",
"http://127.0.0.1/webcompat"]] });
let message = gNavigatorBundle.getString("decoder.decodeError.message");
yield test_decoder_doctor_notification(
{type: "decode-error", decodeIssue: "DecodeIssue",
docURL: "DocURL", resourceURL: "ResURL"},
message,
gNavigatorBundle.getString("decoder.decodeError.button"),
gNavigatorBundle.getString("decoder.decodeError.accesskey"),
tab_checker_for_webcompat(
{url: "DocURL", problem_type: "video_bug",
details: "Technical Information:\nDecodeIssue\nResource: ResURL"}));
});
add_task(function* test_decode_warning() {
yield SpecialPowers.pushPrefEnv(
{ set: [["media.decoder-doctor.new-issue-endpoint",
"http://127.0.0.1/webcompat"]] });
let message = gNavigatorBundle.getString("decoder.decodeWarning.message");
yield test_decoder_doctor_notification(
{type: "decode-warning", decodeIssue: "DecodeIssue",
docURL: "DocURL", resourceURL: "ResURL"},
message,
gNavigatorBundle.getString("decoder.decodeError.button"),
gNavigatorBundle.getString("decoder.decodeError.accesskey"),
tab_checker_for_webcompat(
{url: "DocURL", problem_type: "video_bug",
details: "Technical Information:\nDecodeIssue\nResource: ResURL"}));
});

Просмотреть файл

@ -57,6 +57,6 @@ add_task(function* () {
prefBtn.doCommand();
yield aboutPrefPromise;
let prefDoc = gBrowser.selectedBrowser.contentDocument;
let offlineGroup = prefDoc.getElementById("offlineGroup");
is_element_visible(offlineGroup, "Should open the Network tab in about:preferences#privacy");
let siteDataGroup = prefDoc.getElementById("siteDataGroup");
is_element_visible(siteDataGroup, "Should open the Network tab in about:preferences#privacy");
});

Просмотреть файл

@ -53,6 +53,10 @@ let whitelist = [
intermittent: true,
errorMessage: /Property contained reference to invalid variable.*color/i,
isFromDevTools: true},
{sourceName: /webide\/skin\/logs\.css$/i,
intermittent: true,
errorMessage: /Property contained reference to invalid variable.*background/i,
isFromDevTools: true},
{sourceName: /devtools\/skin\/animationinspector\.css$/i,
intermittent: true,
errorMessage: /Property contained reference to invalid variable.*color/i,

Просмотреть файл

@ -69,6 +69,9 @@ skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliabil
[browser_urlbarHashChangeProxyState.js]
[browser_urlbarKeepStateAcrossTabSwitches.js]
[browser_urlbarOneOffs.js]
support-files =
searchSuggestionEngine.xml
searchSuggestionEngine.sjs
[browser_urlbarPrivateBrowsingWindowChange.js]
[browser_urlbarRaceWithTabs.js]
[browser_urlbarRevert.js]

Просмотреть файл

@ -73,6 +73,18 @@ add_task(function* history() {
assertState(-1, i, "");
}
// Key right through each one-off.
for (let i = 1; i < numButtons; i++) {
EventUtils.synthesizeKey("VK_RIGHT", {})
assertState(-1, i, "");
}
// Key left through each one-off.
for (let i = numButtons - 2; i >= 0; i--) {
EventUtils.synthesizeKey("VK_LEFT", {})
assertState(-1, i, "");
}
// Key up through each result.
for (let i = gMaxResults - 1; i >= 0; i--) {
EventUtils.synthesizeKey("VK_UP", {})

Просмотреть файл

@ -218,7 +218,6 @@ const CustomizableWidgets = [
while ((row = aResultSet.getNextRow())) {
let uri = row.getResultByIndex(1);
let title = row.getResultByIndex(2);
let icon = row.getResultByIndex(6);
let item = doc.createElementNS(kNSXUL, "toolbarbutton");
item.setAttribute("label", title || uri);
@ -226,10 +225,7 @@ const CustomizableWidgets = [
item.setAttribute("class", "subviewbutton");
item.addEventListener("command", onItemCommand);
item.addEventListener("click", onItemCommand);
if (icon) {
let iconURL = "moz-anno:favicon:" + icon;
item.setAttribute("image", iconURL);
}
item.setAttribute("image", "page-icon:" + uri);
fragment.appendChild(item);
}
items.appendChild(fragment);

Просмотреть файл

@ -128,6 +128,7 @@ FirefoxProfileMigrator.prototype._getResourcesInternal = function(sourceProfileD
let types = MigrationUtils.resourceTypes;
let places = getFileResource(types.HISTORY, ["places.sqlite"]);
let favicons = getFileResource(types.HISTORY, ["favicons.sqlite"]);
let cookies = getFileResource(types.COOKIES, ["cookies.sqlite"]);
let passwords = getFileResource(types.PASSWORDS,
["signons.sqlite", "logins.json", "key3.db",
@ -240,7 +241,7 @@ FirefoxProfileMigrator.prototype._getResourcesInternal = function(sourceProfileD
};
return [places, cookies, passwords, formData, dictionary, bookmarksBackups,
session, times, telemetry].filter(r => r);
session, times, telemetry, favicons].filter(r => r);
};
Object.defineProperty(FirefoxProfileMigrator.prototype, "startupOnlyMigrator", {

Просмотреть файл

@ -576,11 +576,9 @@ add_task(function* checkUndoVisitsState() {
// to accurately determine whether we're doing the right thing.
let frecencyUpdatesHandled = new Promise(resolve => {
PlacesUtils.history.addObserver({
onFrecencyChanged(aURI) {
if (aURI.spec == "http://www.unrelated.org/") {
PlacesUtils.history.removeObserver(this);
resolve();
}
onManyFrecenciesChanged() {
PlacesUtils.history.removeObserver(this);
resolve();
}
}, false);
});

Просмотреть файл

@ -164,7 +164,10 @@ add_task(function* test_Links_onLinkChanged() {
// add a visit
let testURI = NetUtil.newURI(url);
yield PlacesTestUtils.addVisits(testURI);
yield PlacesUtils.history.insert({
url: testURI,
visits: [{ transition: PlacesUtils.history.TRANSITIONS.LINK }]
});
yield linkChangedPromise;
yield PlacesTestUtils.clearHistory();

Просмотреть файл

@ -508,14 +508,10 @@ PlacesViewBase.prototype = {
return;
// Here we need the <menu>.
if (elt.localName == "menupopup")
if (elt.localName == "menupopup") {
elt = elt.parentNode;
let icon = aPlacesNode.icon;
if (!icon)
elt.removeAttribute("image");
else if (icon != elt.getAttribute("image"))
elt.setAttribute("image", icon);
}
elt.setAttribute("image", aPlacesNode.icon);
},
nodeAnnotationChanged:

Просмотреть файл

@ -57,12 +57,14 @@ var gEditItemOverlay = {
}
}
let focusedElement = aInitInfo.focusedElement;
let onPanelReady = aInitInfo.onPanelReady;
return this._paneInfo = { itemId, itemGuid, isItem,
isURI, uri, title,
isBookmark, isFolderShortcut, isParentReadOnly,
bulkTagging, uris,
visibleRows, postData, isTag, focusedElement };
visibleRows, postData, isTag, focusedElement,
onPanelReady };
},
get initialized() {
@ -207,7 +209,8 @@ var gEditItemOverlay = {
let { itemId, isItem, isURI,
isBookmark, bulkTagging, uris,
visibleRows, focusedElement } = this._setPaneInfo(aInfo);
visibleRows, focusedElement,
onPanelReady } = this._setPaneInfo(aInfo);
let showOrCollapse =
(rowId, isAppropriateForInput, nameInHiddenRows = null) => {
@ -279,26 +282,34 @@ var gEditItemOverlay = {
this._observersAdded = true;
}
// The focusedElement possible values are:
// * preferred: focus the field that the user touched first the last
// time the pane was shown (either namePicker or tagsField)
// * first: focus the first non collapsed textbox
// Note: since all controls are collapsed by default, we don't get the
// default XUL dialog behavior, that selects the first control, so we set
// the focus explicitly.
// Note: If focusedElement === "preferred", this file expects gPrefService
// to be defined in the global scope.
let elt;
if (focusedElement === "preferred") {
/* eslint-disable no-undef */
elt = this._element(gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField"));
/* eslint-enable no-undef */
} else if (focusedElement === "first") {
elt = document.querySelector("textbox:not([collapsed=true])");
}
if (elt) {
elt.focus();
elt.select();
let focusElement = () => {
// The focusedElement possible values are:
// * preferred: focus the field that the user touched first the last
// time the pane was shown (either namePicker or tagsField)
// * first: focus the first non collapsed textbox
// Note: since all controls are collapsed by default, we don't get the
// default XUL dialog behavior, that selects the first control, so we set
// the focus explicitly.
// Note: If focusedElement === "preferred", this file expects gPrefService
// to be defined in the global scope.
let elt;
if (focusedElement === "preferred") {
/* eslint-disable no-undef */
elt = this._element(gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField"));
/* eslint-enable no-undef */
} else if (focusedElement === "first") {
elt = document.querySelector("textbox:not([collapsed=true])");
}
if (elt) {
elt.focus();
elt.select();
}
};
if (onPanelReady) {
onPanelReady(focusElement);
} else {
focusElement();
}
},
@ -332,10 +343,23 @@ var gEditItemOverlay = {
if (aElement.value != aValue) {
aElement.value = aValue;
// Clear the undo stack
let editor = aElement.editor;
if (editor)
editor.transactionManager.clear();
// Clear the editor's undo stack
let transactionManager;
try {
transactionManager = aElement.editor.transactionManager;
} catch (e) {
// When retrieving the transaction manager, editor may be null resulting
// in a TypeError. Additionally, the transaction manager may not
// exist yet, which causes access to it to throw NS_ERROR_FAILURE.
// In either event, the transaction manager doesn't exist it, so we
// don't need to worry about clearing it.
if (!(e instanceof TypeError) && e.result != Cr.NS_ERROR_FAILURE) {
throw e;
}
}
if (transactionManager) {
transactionManager.clear();
}
}
},

Просмотреть файл

@ -288,24 +288,19 @@
<!-- Containers -->
<groupbox id="browserContainersGroup" data-category="panePrivacy" hidden="true">
<vbox id="browserContainersbox" hidden="true">
<caption><label>&browserContainersHeader.label;
<caption><label>&browserContainersHeader.label;</label></caption>
<hbox align="center">
<checkbox id="browserContainersCheckbox"
label="&browserContainersEnabled.label;"
accesskey="&browserContainersEnabled.accesskey;"
preference="privacy.userContext.enabled"
onsyncfrompreference="return gPrivacyPane.readBrowserContainersCheckbox();"/>
<label id="browserContainersLearnMore" class="learnMore text-link"
value="&browserContainersLearnMore.label;"/>
</label></caption>
<hbox align="start">
<vbox>
<checkbox id="browserContainersCheckbox"
label="&browserContainersEnabled.label;"
accesskey="&browserContainersEnabled.accesskey;"
preference="privacy.userContext.enabled"
onsyncfrompreference="return gPrivacyPane.readBrowserContainersCheckbox();"/>
</vbox>
<spacer flex="1"/>
<vbox>
<button id="browserContainersSettings"
label="&browserContainersSettings.label;"
accesskey="&browserContainersSettings.accesskey;"/>
</vbox>
<button id="browserContainersSettings"
label="&browserContainersSettings.label;"
accesskey="&browserContainersSettings.accesskey;"/>
</hbox>
</vbox>
</groupbox>

Просмотреть файл

@ -99,7 +99,7 @@
<!-- Data Choices -->
#ifdef MOZ_TELEMETRY_REPORTING
<groupbox id="historyGroup" data-category="paneAdvanced" hidden="true">
<groupbox id="historyGroup" data-category="paneAdvanced" data-subcategory="reports" hidden="true">
<caption><label>&reports.label;</label></caption>
<vbox>
<caption>
@ -131,7 +131,7 @@
#ifdef MOZ_DATA_REPORTING
#ifdef MOZ_CRASHREPORTER
<groupbox data-category="paneAdvanced" hidden="true">
<groupbox data-category="paneAdvanced" data-subcategory="reports" hidden="true">
<caption>
<checkbox id="automaticallySubmitCrashesBox"
preference="browser.crashReports.unsubmittedCheck.autoSubmit"

Просмотреть файл

@ -73,9 +73,7 @@
flex="1">
<!--Downloads-->
<groupbox id="downloadsGroup"
data-category="paneApplications"
hidden="false">
<groupbox id="downloadsGroup">
<caption><label>&downloads.label;</label></caption>
<radiogroup id="saveWhere"

Просмотреть файл

@ -302,7 +302,7 @@
</groupbox>
<!-- Default Search Engine -->
<groupbox id="defaultEngineGroup" align="start" data-category="paneGeneral">
<groupbox id="defaultEngineGroup" align="start" data-category="paneGeneral" data-subcategory="search">
<caption label="&defaultSearchEngine.label;"/>
<label>&chooseYourDefaultSearchEngine.label;</label>
<menulist id="defaultEngine">
@ -323,7 +323,7 @@
</vbox>
</groupbox>
<groupbox id="oneClickSearchProvidersGroup" data-category="paneGeneral">
<groupbox id="oneClickSearchProvidersGroup" data-category="paneGeneral" data-subcategory="search">
<caption label="&oneClickSearchEngines.label;"/>
<label>&chooseWhichOneToDisplay.label;</label>

Просмотреть файл

@ -142,12 +142,19 @@ function gotoPref(aCategory) {
let categories = document.getElementById("categories");
const kDefaultCategoryInternalName = "paneGeneral";
let hash = document.location.hash;
// Subcategories allow for selecting smaller sections of the preferences
// until proper search support is enabled (bug 1353954).
let breakIndex = hash.indexOf("-");
let subcategory = breakIndex != -1 && hash.substring(breakIndex + 1);
if (subcategory) {
hash = hash.substring(0, breakIndex);
}
let category = aCategory || hash.substr(1) || kDefaultCategoryInternalName;
category = friendlyPrefCategoryNameToInternalName(category);
// Updating the hash (below) or changing the selected category
// will re-enter gotoPref.
if (gLastHash == category)
if (gLastHash == category && !subcategory)
return;
let item = categories.querySelector(".category[value=" + category + "]");
if (!item) {
@ -163,7 +170,7 @@ function gotoPref(aCategory) {
}
let friendlyName = internalPrefCategoryNameToFriendlyName(category);
if (gLastHash || category != kDefaultCategoryInternalName) {
if (gLastHash || category != kDefaultCategoryInternalName || subcategory) {
document.location.hash = friendlyName;
}
// Need to set the gLastHash before setting categories.selectedItem since
@ -171,7 +178,8 @@ function gotoPref(aCategory) {
gLastHash = category;
categories.selectedItem = item;
window.history.replaceState(category, document.title);
search(category, "data-category");
search(category, "data-category", subcategory, "data-subcategory");
let mainContent = document.querySelector(".main-content");
mainContent.scrollTop = 0;
@ -180,7 +188,7 @@ function gotoPref(aCategory) {
.add(telemetryBucketForCategory(friendlyName));
}
function search(aQuery, aAttribute) {
function search(aQuery, aAttribute, aSubquery, aSubAttribute) {
let mainPrefPane = document.getElementById("mainPrefPane");
let elements = mainPrefPane.children;
for (let element of elements) {
@ -190,7 +198,17 @@ function search(aQuery, aAttribute) {
// development and should not be shown for any reason.
if (element.getAttribute("data-hidden-from-search") != "true") {
let attributeValue = element.getAttribute(aAttribute);
element.hidden = (attributeValue != aQuery);
if (attributeValue == aQuery) {
if (!element.classList.contains("header") &&
aSubquery && aSubAttribute) {
let subAttributeValue = element.getAttribute(aSubAttribute);
element.hidden = subAttributeValue != aSubquery;
} else {
element.hidden = false;
}
} else {
element.hidden = true;
}
}
}

Просмотреть файл

@ -335,7 +335,7 @@
<groupbox id="trackingGroup" data-category="panePrivacy" hidden="true">
<vbox id="trackingprotectionbox" hidden="true">
<hbox align="start">
<vbox hidden="false">
<vbox>
<caption><label>&trackingProtectionHeader2.label;
<label id="trackingProtectionLearnMore" class="learnMore text-link"
value="&trackingProtectionLearnMore.label;"/>
@ -353,7 +353,7 @@
</radiogroup>
</vbox>
<spacer flex="1" />
<vbox hidden="true">
<vbox>
<button id="trackingProtectionExceptions"
label="&trackingProtectionExceptions.label;"
accesskey="&trackingProtectionExceptions.accesskey;"
@ -381,7 +381,7 @@
preference="pref.privacy.disable_button.change_blocklist"/>
</hbox>
</vbox>
<vbox hidden="false">
<vbox>
<description>&doNotTrack.pre.label;<label
class="text-link" id="doNotTrackSettings"
>&doNotTrack.settings.label;</label>&doNotTrack.post.label;</description>
@ -565,24 +565,19 @@
<!-- Containers -->
<groupbox id="browserContainersGroup" data-category="panePrivacy" hidden="true">
<vbox id="browserContainersbox" hidden="true">
<caption><label>&browserContainersHeader.label;
<caption><label>&browserContainersHeader.label;</label></caption>
<hbox align="center">
<checkbox id="browserContainersCheckbox"
label="&browserContainersEnabled.label;"
accesskey="&browserContainersEnabled.accesskey;"
preference="privacy.userContext.enabled"
onsyncfrompreference="return gPrivacyPane.readBrowserContainersCheckbox();"/>
<label id="browserContainersLearnMore" class="learnMore text-link"
value="&browserContainersLearnMore.label;"/>
</label></caption>
<hbox align="start">
<vbox>
<checkbox id="browserContainersCheckbox"
label="&browserContainersEnabled.label;"
accesskey="&browserContainersEnabled.accesskey;"
preference="privacy.userContext.enabled"
onsyncfrompreference="return gPrivacyPane.readBrowserContainersCheckbox();"/>
</vbox>
<spacer flex="1"/>
<vbox>
<button id="browserContainersSettings"
label="&browserContainersSettings.label;"
accesskey="&browserContainersSettings.accesskey;"/>
</vbox>
<button id="browserContainersSettings"
label="&browserContainersSettings.label;"
accesskey="&browserContainersSettings.accesskey;"/>
</hbox>
</vbox>
</groupbox>

Просмотреть файл

@ -20,6 +20,20 @@ add_task(function*() {
is(prefs.selectedPane, "paneGeneral", "General pane is selected when hash is a nonexistant-category");
prefs = yield openPreferencesViaHash();
is(prefs.selectedPane, "paneGeneral", "General pane is selected by default");
prefs = yield openPreferencesViaOpenPreferencesAPI("advanced-reports", {leaveOpen: true});
is(prefs.selectedPane, "paneAdvanced", "Advanced pane is selected by default");
let doc = gBrowser.contentDocument;
is(doc.location.hash, "#advanced", "The subcategory should be removed from the URI");
ok(doc.querySelector("#updateOthers").hidden, "Search Updates should be hidden when only Reports are requested");
ok(!doc.querySelector("#header-advanced").hidden, "The header should be visible when a subcategory is requested");
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
prefs = yield openPreferencesViaOpenPreferencesAPI("general-search", {leaveOpen: true});
is(prefs.selectedPane, "paneGeneral", "General pane is selected by default");
doc = gBrowser.contentDocument;
is(doc.location.hash, "#general", "The subcategory should be removed from the URI");
ok(doc.querySelector("#startupGroup").hidden, "Startup should be hidden when only Search is requested");
ok(!doc.querySelector("#engineList").hidden, "The search engine list should be visible when Search is requested");
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
function openPreferencesViaHash(aPane) {

Просмотреть файл

@ -21,6 +21,16 @@ function checkElements(expectedPane) {
element.id === "drmGroup") {
continue;
}
// The siteDataGroup in the Storage Management project will replace the offlineGroup eventually,
// so during the transition period, we have to check the pref to see if the offlineGroup
// should be hidden always. See the bug 1354530 for the details.
if (element.id == "offlineGroup" &&
!SpecialPowers.getBoolPref("browser.preferences.offlineGroup.enabled")) {
is_element_hidden(element, "Disabled offlineGroup should be hidden");
continue;
}
let attributeValue = element.getAttribute("data-category");
let suffix = " (id=" + element.id + ")";
if (attributeValue == "pane" + expectedPane) {

Просмотреть файл

@ -14,6 +14,11 @@ let gSiteDataRemoveSelected = {
_tree: null,
init() {
let bundlePreferences = document.getElementById("bundlePreferences");
let acceptBtn = document.getElementById("SiteDataRemoveSelectedDialog")
.getButton("accept");
acceptBtn.label = bundlePreferences.getString("acceptRemove");
// Organize items for the tree from the argument
let hostsTable = window.arguments[0].hostsTable;
let visibleItems = [];

Просмотреть файл

@ -37,7 +37,7 @@
#endif
>&removingDialog.title;</label>
<separator class="thin"/>
<description id="removing-description">&removingDialog.description;</description>
<description id="removing-description">&removingSelected.description;</description>
</vbox>
</hbox>

Просмотреть файл

@ -36,7 +36,7 @@ let gSiteDataSettings = {
this._list = document.getElementById("sitesList");
this._searchBox = document.getElementById("searchBox");
this._prefStrBundle = document.getElementById("bundlePreferences")
this._prefStrBundle = document.getElementById("bundlePreferences");
SiteDataManager.getSites().then(sites => {
this._sites = sites;
let sortCol = document.getElementById("hostCol");
@ -45,6 +45,10 @@ let gSiteDataSettings = {
Services.obs.notifyObservers(null, "sitedata-settings-init", null);
});
let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
let settingsDescription = document.getElementById("settingsDescription");
settingsDescription.textContent = this._prefStrBundle.getFormattedString("siteDataSettings.description", [brandShortName]);
setEventListener("hostCol", "click", this.onClickTreeCol);
setEventListener("usageCol", "click", this.onClickTreeCol);
setEventListener("statusCol", "click", this.onClickTreeCol);
@ -139,13 +143,14 @@ let gSiteDataSettings = {
continue;
}
let statusStrId = data.status === Ci.nsIPermissionManager.ALLOW_ACTION ? "important" : "default";
let size = DownloadUtils.convertByteUnits(data.usage);
let item = document.createElement("richlistitem");
item.setAttribute("data-origin", data.uri.spec);
item.setAttribute("host", host);
item.setAttribute("status", this._prefStrBundle.getString(statusStrId));
item.setAttribute("usage", this._prefStrBundle.getFormattedString("siteUsage", size));
if (data.status === Ci.nsIPermissionManager.ALLOW_ACTION ) {
item.setAttribute("status", this._prefStrBundle.getString("persistent"));
}
this._list.appendChild(item);
}
this._updateButtonsState();

Просмотреть файл

@ -22,14 +22,15 @@
<stringbundle id="bundlePreferences"
src="chrome://browser/locale/preferences/preferences.properties"/>
<stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
<vbox flex="1">
<description>&settings.description;</description>
<description id="settingsDescription"></description>
<separator class="thin"/>
<hbox id="searchBoxContainer">
<textbox id="searchBox" type="search" flex="1"
placeholder="&searchPlaceHolder;" accesskey="&searchPlaceHolder.accesskey;"/>
placeholder="&searchTextboxPlaceHolder;" accesskey="&searchTextboxPlaceHolder.accesskey;"/>
</hbox>
<separator class="thin"/>

Просмотреть файл

@ -926,7 +926,7 @@
<xul:treecols anonid="treecols">
<xul:treecol id="treecolAutoCompleteValue" class="autocomplete-treecol" flex="1" overflow="true"/>
</xul:treecols>
<xul:treechildren class="autocomplete-treebody"/>
<xul:treechildren class="autocomplete-treebody searchbar-treebody"/>
</xul:tree>
<xul:vbox anonid="search-one-off-buttons" class="search-one-offs"/>
</content>
@ -1139,6 +1139,19 @@
</binding>
<!-- This is the same as the autocomplete-treebody binding except it does not
select rows on mousemove. -->
<binding id="searchbar-treebody"
extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-treebody">
<handlers>
<handler event="mousemove"><![CDATA[
// Cancel the event so that the base binding doesn't select the row.
event.preventDefault();
]]></handler>
</handlers>
</binding>
<!-- Used for additional open search providers in the search panel. -->
<binding id="addengine-icon" extends="xul:box">
<content>
@ -1307,7 +1320,27 @@
return this._selectedButton;
]]></getter>
<setter><![CDATA[
this._changeVisuallySelectedButton(val, true);
if (val && val.classList.contains("dummy")) {
// Never select dummy buttons.
val = null;
}
if (this._selectedButton) {
this._selectedButton.removeAttribute("selected");
}
if (val) {
val.setAttribute("selected", "true");
}
this._selectedButton = val;
this._updateStateForButton(null);
if (val && !val.engine) {
// If the button doesn't have an engine, then clear the popup's
// selection to indicate that pressing Return while the button is
// selected will do the button's command, not search.
this.popup.selectedIndex = -1;
}
let event = document.createEvent("Events");
event.initEvent("SelectedOneOffButtonChanged", true, false);
this.dispatchEvent(event);
return val;
]]></setter>
</property>
@ -1331,20 +1364,6 @@
]]></setter>
</property>
<!-- The visually selected one-off is the same as the selected one-off
unless a one-off is moused over. In that case, the visually selected
one-off is the moused-over one-off, which may be different from the
selected one-off. The visually selected one-off is always the one
that is visually highlighted. Includes the add-engine button and the
search-settings button. A xul:button. -->
<property name="visuallySelectedButton" readonly="true">
<getter><![CDATA[
return this.getSelectableButtons(true).find(button => {
return button.getAttribute("selected") == "true";
});
]]></getter>
</property>
<property name="compact" readonly="true">
<getter><![CDATA[
return this.getAttribute("compact") == "true";
@ -1420,7 +1439,7 @@
<method name="showSettings">
<body><![CDATA[
BrowserUITelemetry.countSearchSettingsEvent(this.telemetryOrigin);
openPreferences("paneGeneral");
openPreferences("general-search");
// If the preference tab was already selected, the panel doesn't
// close itself automatically.
this.popup.hidePopup();
@ -1738,52 +1757,50 @@
]]></body>
</method>
<method name="_changeVisuallySelectedButton">
<parameter name="val"/>
<parameter name="aUpdateLogicallySelectedButton"/>
<body><![CDATA[
let visuallySelectedButton = this.visuallySelectedButton;
if (visuallySelectedButton)
visuallySelectedButton.removeAttribute("selected");
<!--
Updates the popup and textbox for the currently selected or moused-over
button.
@param mousedOverButton
The currently moused-over button, or null if there isn't one.
-->
<method name="_updateStateForButton">
<parameter name="mousedOverButton"/>
<body><![CDATA[
let button = mousedOverButton;
let header =
document.getAnonymousElementByAttribute(this, "anonid",
"search-panel-one-offs-header");
// Avoid selecting dummy buttons.
if (val && !val.classList.contains("dummy")) {
val.setAttribute("selected", "true");
if (val.classList.contains("searchbar-engine-one-off-item") &&
val.engine) {
let headerEngineText =
document.getAnonymousElementByAttribute(this, "anonid",
"searchbar-oneoffheader-engine");
header.selectedIndex = 2;
headerEngineText.value = val.engine.name;
} else {
header.selectedIndex = this.query ? 1 : 0;
}
if (this.textbox) {
this.textbox.setAttribute("aria-activedescendant", val.id);
}
} else {
val = null;
// Ignore dummy buttons.
if (button && button.classList.contains("dummy")) {
button = null;
}
// If there's no moused-over button, then the one-offs should reflect
// the selected button, if any.
button = button || this.selectedButton;
if (!button) {
header.selectedIndex = this.query ? 1 : 0;
if (this.textbox) {
this.textbox.removeAttribute("aria-activedescendant");
}
return;
}
if (aUpdateLogicallySelectedButton) {
this._selectedButton = val;
if (val && !val.engine) {
// If the button doesn't have an engine, then clear the popup's
// selection to indicate that pressing Return while the button is
// selected will do the button's command, not search.
this.popup.selectedIndex = -1;
}
let event = document.createEvent("Events");
event.initEvent("SelectedOneOffButtonChanged", true, false);
this.dispatchEvent(event);
if (button.classList.contains("searchbar-engine-one-off-item") &&
button.engine) {
let headerEngineText =
document.getAnonymousElementByAttribute(this, "anonid",
"searchbar-oneoffheader-engine");
header.selectedIndex = 2;
headerEngineText.value = button.engine.name;
} else {
header.selectedIndex = this.query ? 1 : 0;
}
if (this.textbox) {
this.textbox.setAttribute("aria-activedescendant", button.id);
}
]]></body>
</method>
@ -1859,76 +1876,56 @@
@param aForward
If true, the index is incremented, and if false, the index is
decremented.
@param aIncludeNonEngineButtons
If true, non-dummy buttons that do not have engines are included.
These buttons include the OpenSearch and settings buttons. For
example, if the currently selected button is an engine button,
the next button is the settings button, and you pass true for
aForward, then passing true for this value would cause the
settings to be selected. Passing false for this value would
cause the selection to clear or wrap around, depending on what
value you passed for the aWrapAround parameter.
@param aWrapAround
This has a couple of effects, depending on whether there is
currently a selection.
(1) If true and the last one-off is currently selected,
incrementing the index will cause the selection to be cleared and
this method to return true. Calling advanceSelection again after
that (again with aForward=true) will select the first one-off.
Likewise if decrementing the index when the first one-off is
selected, except in the opposite direction of course.
(2) If true and there currently is no selection, decrementing the
index will cause the last one-off to become selected and this
method to return true. Only the aForward=false case is affected
because it is always the case that if aForward=true and there
currently is no selection, the first one-off becomes selected and
this method returns true.
@param aCycleEngines
If true, only engine buttons are included.
If true, the selection wraps around between the first and last
buttons.
@return True if the selection can continue to advance after this method
returns and false if not.
-->
<method name="advanceSelection">
<parameter name="aForward"/>
<parameter name="aIncludeNonEngineButtons"/>
<parameter name="aWrapAround"/>
<parameter name="aCycleEngines"/>
<body><![CDATA[
let selectedButton = this.selectedButton;
let buttons = this.getSelectableButtons(aCycleEngines);
if (selectedButton) {
// cycle through one-off buttons.
let index = buttons.indexOf(selectedButton);
if (aForward)
++index;
else
--index;
if (index >= 0 && index < buttons.length)
this.selectedButton = buttons[index];
else
this.selectedButton = null;
if (this.selectedButton || aWrapAround)
return true;
return false;
let buttons = this.getSelectableButtons(aIncludeNonEngineButtons);
let index;
if (this.selectedButton) {
let inc = aForward ? 1 : -1;
let oldIndex = buttons.indexOf(this.selectedButton);
index = ((oldIndex + inc) + buttons.length) % buttons.length;
if (!aWrapAround &&
((aForward && index <= oldIndex) ||
(!aForward && oldIndex <= index))) {
// The index has wrapped around, but wrapping around isn't
// allowed.
index = -1;
}
} else {
index = aForward ? 0 : buttons.length - 1;
}
// If no selection, select the first button or ...
if (aForward) {
this.selectedButton = buttons[0];
return true;
}
if (!aForward && aWrapAround) {
// the last button.
this.selectedButton = buttons[buttons.length - 1];
return true;
}
return false;
this.selectedButton = index < 0 ? null : buttons[index];
]]></body>
</method>
<!--
This handles key presses specific to the one-off buttons like Tab and
Alt-Up/Down, and Up/Down keys within the buttons. Since one-off buttons
Alt+Up/Down, and Up/Down keys within the buttons. Since one-off buttons
are always used in conjunction with a list of some sort (in this.popup),
it also handles Up/Down keys that cross the boundaries between list
items and the one-off buttons.
If this method handles the key press, then event.defaultPrevented will
be true when it returns.
@param event
The key event.
@param numListItems
@ -1946,10 +1943,6 @@
restored to the value that the user typed. Pass that value here.
However, if you pass true for allowEmptySelection, you don't need
to pass anything for this parameter. (Pass undefined or null.)
@return True if this method handled the keypress and false if not. If
false, then you should let the autocomplete controller handle
the keypress. The value of event.defaultPrevented will be the
same as this return value.
-->
<method name="handleKeyPress">
<parameter name="event"/>
@ -1958,112 +1951,188 @@
<parameter name="textboxUserValue"/>
<body><![CDATA[
if (!this.popup) {
return false;
return;
}
let handled = this._handleKeyPress(event, numListItems,
allowEmptySelection,
textboxUserValue);
if (handled) {
event.preventDefault();
event.stopPropagation();
}
]]></body>
</method>
let stopEvent = false;
// Tab cycles through the one-offs and moves the focus out at the end.
// But only if non-Shift modifiers aren't also pressed, to avoid
// clobbering other shortcuts.
if (event.keyCode == KeyEvent.DOM_VK_TAB &&
!event.altKey &&
!event.ctrlKey &&
!event.metaKey &&
this.getAttribute("disabletab") != "true") {
stopEvent = this.advanceSelection(!event.shiftKey, false, true);
} else if (event.altKey &&
(event.keyCode == KeyEvent.DOM_VK_DOWN ||
event.keyCode == KeyEvent.DOM_VK_UP)) {
// Alt + up/down is very similar to (shift +) tab but differs in that
// it loops through the list, whereas tab will move the focus out.
stopEvent =
this.advanceSelection(event.keyCode == KeyEvent.DOM_VK_DOWN,
true, false);
} else if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP) {
if (numListItems > 0) {
if (this.popup.selectedIndex > 0) {
// The autocomplete controller should handle this case.
} else if (this.popup.selectedIndex == 0) {
if (!allowEmptySelection) {
// Wrap around the selection to the last one-off.
this.selectedButton = null;
this.popup.selectedIndex = -1;
// Call advanceSelection after setting selectedIndex so that
// screen readers see the newly selected one-off. Both trigger
// accessibility events.
this.advanceSelection(false, true, true);
stopEvent = true;
}
} else {
let firstButtonSelected =
this.selectedButton &&
this.selectedButton == this.getSelectableButtons(true)[0];
if (firstButtonSelected) {
this.selectedButton = null;
} else {
stopEvent = this.advanceSelection(false, true, true);
}
}
} else {
stopEvent = this.advanceSelection(false, true, true);
}
} else if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN) {
if (numListItems > 0) {
if (this.popup.selectedIndex >= 0 &&
this.popup.selectedIndex < numListItems - 1) {
// The autocomplete controller should handle this case.
} else if (this.popup.selectedIndex == numListItems - 1) {
this.selectedButton = null;
if (!allowEmptySelection) {
this.popup.selectedIndex = -1;
stopEvent = true;
}
if (this.textbox && typeof(textboxUserValue) == "string") {
this.textbox.value = textboxUserValue;
}
// Call advanceSelection after setting selectedIndex so that
// screen readers see the newly selected one-off. Both trigger
// accessibility events.
this.advanceSelection(true, true, true);
} else {
let buttons = this.getSelectableButtons(true);
let lastButtonSelected =
this.selectedButton &&
this.selectedButton == buttons[buttons.length - 1];
if (lastButtonSelected) {
this.selectedButton = null;
stopEvent = allowEmptySelection;
} else if (this.selectedButton) {
stopEvent = this.advanceSelection(true, true, true);
} else {
// The autocomplete controller should handle this case.
}
}
} else {
stopEvent = this.advanceSelection(true, true, true);
}
} else if (this.selectedButton &&
this.selectedButton.getAttribute("anonid") ==
"addengine-menu-button" &&
event.keyCode == KeyEvent.DOM_VK_RIGHT) {
<method name="_handleKeyPress">
<parameter name="event"/>
<parameter name="numListItems"/>
<parameter name="allowEmptySelection"/>
<parameter name="textboxUserValue"/>
<body><![CDATA[
if (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
this.selectedButton &&
this.selectedButton.getAttribute("anonid") ==
"addengine-menu-button") {
// If the add-engine overflow menu item is selected and the user
// presses the right arrow key, open the submenu. Unfortunately
// handling the left arrow key -- to close the popup -- isn't
// straightforward. Once the popup is open, it consumes all key
// events. Setting ignorekeys=handled on it doesn't help, since the
// popup handles all arrow keys. Setting ignorekeys=true on it does
// mean that the popup no longer consumes the left arrow key, but then
// it no longer handles up/down keys to select items in the popup.
// mean that the popup no longer consumes the left arrow key, but
// then it no longer handles up/down keys to select items in the
// popup.
this.selectedButton.open = true;
stopEvent = true;
}
if (stopEvent) {
event.preventDefault();
event.stopPropagation();
return true;
}
// Handle the Tab key, but only if non-Shift modifiers aren't also
// pressed to avoid clobbering other shortcuts (like the Alt+Tab
// browser tab switcher). The reason this uses getModifierState() and
// checks for "AltGraph" is that when you press Shift-Alt-Tab,
// event.altKey is actually false for some reason, at least on macOS.
// getModifierState("Alt") is also false, but "AltGraph" is true.
if (event.keyCode == KeyEvent.DOM_VK_TAB &&
!event.getModifierState("Alt") &&
!event.getModifierState("AltGraph") &&
!event.getModifierState("Control") &&
!event.getModifierState("Meta")) {
if (this.getAttribute("disabletab") == "true" ||
(event.shiftKey &&
this.selectedButtonIndex <= 0) ||
(!event.shiftKey &&
this.selectedButtonIndex ==
this.getSelectableButtons(true).length - 1)) {
this.selectedButton = null;
return false;
}
this.popup.selectedIndex = -1;
this.advanceSelection(!event.shiftKey, true, false);
return !!this.selectedButton;
}
if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP) {
if (event.altKey) {
// Keep the currently selected result in the list (if any) as a
// secondary "alt" selection and move the selection up within the
// buttons.
this.advanceSelection(false, false, false);
return true;
}
if (numListItems == 0) {
this.advanceSelection(false, true, false);
return true;
}
if (this.popup.selectedIndex > 0) {
// Moving up within the list. The autocomplete controller should
// handle this case. A button may be selected, so null it.
this.selectedButton = null;
return false;
}
if (this.popup.selectedIndex == 0) {
// Moving up from the top of the list.
if (allowEmptySelection) {
// Let the autocomplete controller remove selection in the list
// and revert the typed text in the textbox.
return false;
}
// Wrap selection around to the last button.
if (this.textbox && typeof(textboxUserValue) == "string") {
this.textbox.value = textboxUserValue;
}
this.advanceSelection(false, true, true);
return true;
}
if (!this.selectedButton) {
// Moving up from no selection in the list or the buttons, back
// down to the last button.
this.advanceSelection(false, true, true);
return true;
}
if (this.selectedButtonIndex == 0) {
// Moving up from the buttons to the bottom of the list.
this.selectedButton = null;
return false;
}
// Moving up/left within the buttons.
this.advanceSelection(false, true, false);
return true;
}
if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN) {
if (event.altKey) {
// Keep the currently selected result in the list (if any) as a
// secondary "alt" selection and move the selection down within
// the buttons.
this.advanceSelection(true, false, false);
return true;
}
if (numListItems == 0) {
this.advanceSelection(true, true, false);
return true;
}
if (this.popup.selectedIndex >= 0 &&
this.popup.selectedIndex < numListItems - 1) {
// Moving down within the list. The autocomplete controller
// should handle this case. A button may be selected, so null it.
this.selectedButton = null;
return false;
}
if (this.popup.selectedIndex == numListItems - 1) {
// Moving down from the last item in the list to the buttons.
this.selectedButtonIndex = 0;
if (allowEmptySelection) {
// Let the autocomplete controller remove selection in the list
// and revert the typed text in the textbox.
return false;
}
if (this.textbox && typeof(textboxUserValue) == "string") {
this.textbox.value = textboxUserValue;
}
this.popup.selectedIndex = -1;
return true;
}
if (this.selectedButton) {
let buttons = this.getSelectableButtons(true);
if (this.selectedButtonIndex == buttons.length - 1) {
// Moving down from the buttons back up to the top of the list.
this.selectedButton = null;
if (allowEmptySelection) {
// Prevent the selection from wrapping around to the top of
// the list by returning true, since the list currently has no
// selection. Nothing should be selected after handling this
// Down key.
return true;
}
return false;
}
// Moving down/right within the buttons.
this.advanceSelection(true, true, false);
return true;
}
return false;
}
if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_LEFT) {
if (this.selectedButton &&
(this.compact || this.selectedButton.engine)) {
// Moving left within the buttons.
this.advanceSelection(false, this.compact, true);
return true;
}
return false;
}
if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RIGHT) {
if (this.selectedButton &&
(this.compact || this.selectedButton.engine)) {
// Moving right within the buttons.
this.advanceSelection(true, this.compact, true);
return true;
}
return false;
}
return false;
]]></body>
</method>
@ -2174,14 +2243,10 @@
if (target.getAttribute("anonid") == "addengine-menu-button" ||
(target.localName == "menuitem" &&
target.classList.contains("addengine-item"))) {
// Make the menu button visually selected. It's highlighted in the
// CSS when the popup is open, but the popup doesn't open until a
// short timeout has elapsed. Making the button visually selected now
// provides better feedback to the user.
let menuButton = document.getAnonymousElementByAttribute(
this, "anonid", "addengine-menu-button"
);
this._changeVisuallySelectedButton(menuButton);
this._updateStateForButton(menuButton);
this._addEngineMenuShouldBeOpen = true;
this._resetAddEngineMenuTimeout();
return;
@ -2200,7 +2265,7 @@
if (isOneOff ||
target.classList.contains("addengine-item") ||
target.classList.contains("search-setting-button")) {
this._changeVisuallySelectedButton(target);
this._updateStateForButton(target);
}
]]></handler>
@ -2212,9 +2277,7 @@
if (target.getAttribute("anonid") == "addengine-menu-button" ||
(target.localName == "menuitem" &&
target.classList.contains("addengine-item"))) {
// The menu button will appear selected since the mouse is either over
// it or over one of the menu items in the popup. Make it unselected.
this._changeVisuallySelectedButton(null);
this._updateStateForButton(null);
this._addEngineMenuShouldBeOpen = false;
this._resetAddEngineMenuTimeout();
return;
@ -2224,19 +2287,11 @@
return;
}
// Don't deselect the current button if the context menu is open.
// Don't update the mouseover state if the context menu is open.
if (this._ignoreMouseEvents)
return;
// Unfortunately this will fire before mouseover hits another item.
// If this button is selected, we replace that selection only if
// we're not moving to a different one-off item:
if (target.getAttribute("selected") == "true" &&
(!event.relatedTarget ||
!event.relatedTarget.classList.contains("searchbar-engine-one-off-item") ||
event.relatedTarget.classList.contains("dummy"))) {
this._changeVisuallySelectedButton(this.selectedButton);
}
this._updateStateForButton(null);
]]></handler>
<handler event="click"><![CDATA[

Просмотреть файл

@ -8,6 +8,10 @@
-moz-binding: url("chrome://browser/content/search/search.xml#searchbar-textbox");
}
.searchbar-treebody {
-moz-binding: url("chrome://browser/content/search/search.xml#searchbar-treebody");
}
.search-one-offs {
-moz-binding: url("chrome://browser/content/search/search.xml#search-one-offs");
}

Просмотреть файл

@ -328,42 +328,53 @@ add_task(function* test_tab_and_arrows() {
ok(!textbox.selectedButton, "no one-off button should be selected");
// After pressing tab, the first one-off should be selected,
// and the first suggestion still selected.
// and no suggestion should be selected.
let oneOffs = getOneOffs();
EventUtils.synthesizeKey("VK_TAB", {});
is(textbox.selectedButton, oneOffs[0],
"the first one-off button should be selected");
is(searchPopup.selectedIndex, 0, "first suggestion should still be selected");
// After pressing down, the second suggestion should be selected,
// and the first one-off still selected.
EventUtils.synthesizeKey("VK_DOWN", {});
is(textbox.selectedButton, oneOffs[0],
"the first one-off button should still be selected");
is(searchPopup.selectedIndex, 1, "second suggestion should be selected");
// After pressing up, the first suggestion should be selected again,
// and the first one-off still selected.
EventUtils.synthesizeKey("VK_UP", {});
is(textbox.selectedButton, oneOffs[0],
"the first one-off button should still be selected");
is(searchPopup.selectedIndex, 0, "second suggestion should be selected again");
// After pressing up again, we should have no suggestion selected anymore,
// the textfield value back to the user-typed value, and still the first one-off
// selected.
EventUtils.synthesizeKey("VK_UP", {});
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
is(textbox.value, kUserValue,
"the textfield value should be back to user typed value");
is(textbox.selectedButton, oneOffs[0],
"the first one-off button should still be selected");
// Now pressing down should select the second one-off.
// After pressing down, the second one-off should be selected.
EventUtils.synthesizeKey("VK_DOWN", {});
is(textbox.selectedButton, oneOffs[1],
"the second one-off button should be selected");
is(searchPopup.selectedIndex, -1, "there should still be no selected suggestion");
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
// After pressing right, the third one-off should be selected.
EventUtils.synthesizeKey("VK_RIGHT", {});
is(textbox.selectedButton, oneOffs[2],
"the third one-off button should be selected");
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
// After pressing left, the second one-off should be selected again.
EventUtils.synthesizeKey("VK_LEFT", {});
is(textbox.selectedButton, oneOffs[1],
"the second one-off button should be selected again");
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
// After pressing up, the first one-off should be selected again.
EventUtils.synthesizeKey("VK_UP", {});
is(textbox.selectedButton, oneOffs[0],
"the first one-off button should be selected again");
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
// After pressing up again, the last suggestion should be selected.
// the textfield value back to the user-typed value, and still the first one-off
// selected.
EventUtils.synthesizeKey("VK_UP", {});
is(searchPopup.selectedIndex, kValues.length - 1,
"last suggestion should be selected");
is(textbox.value, kValues[kValues.length - 1],
"the textfield value should match the suggestion");
is(textbox.selectedButton, null,
"no one-off button should be selected");
// Now pressing down should select the first one-off.
EventUtils.synthesizeKey("VK_DOWN", {});
is(textbox.selectedButton, oneOffs[0],
"the first one-off button should be selected");
is(searchPopup.selectedIndex, -1, "there should be no selected suggestion");
// Finally close the panel.
let promise = promiseEvent(searchPopup, "popuphidden");

Просмотреть файл

@ -29,9 +29,9 @@ this.SessionStorage = Object.freeze({
* That tab's docshell (containing the sessionStorage)
* @param frameTree
* The docShell's FrameTree instance.
* @return Returns a nested object that will have hosts as keys and per-host
* @return Returns a nested object that will have hosts as keys and per-origin
* session storage data as strings. For example:
* {"example.com": {"key": "value", "my_number": "123"}}
* {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
*/
collect(docShell, frameTree) {
return SessionStorageInternal.collect(docShell, frameTree);
@ -43,8 +43,8 @@ this.SessionStorage = Object.freeze({
* A tab's docshell (containing the sessionStorage)
* @param aStorageData
* A nested object with storage data to be restored that has hosts as
* keys and per-host session storage data as strings. For example:
* {"example.com": {"key": "value", "my_number": "123"}}
* keys and per-origin session storage data as strings. For example:
* {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
*/
restore(aDocShell, aStorageData) {
SessionStorageInternal.restore(aDocShell, aStorageData);
@ -58,9 +58,9 @@ var SessionStorageInternal = {
* A tab's docshell (containing the sessionStorage)
* @param frameTree
* The docShell's FrameTree instance.
* @return Returns a nested object that will have hosts as keys and per-host
* @return Returns a nested object that will have hosts as keys and per-origin
* session storage data as strings. For example:
* {"example.com": {"key": "value", "my_number": "123"}}
* {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
*/
collect(docShell, frameTree) {
let data = {};
@ -105,8 +105,8 @@ var SessionStorageInternal = {
* A tab's docshell (containing the sessionStorage)
* @param aStorageData
* A nested object with storage data to be restored that has hosts as
* keys and per-host session storage data as strings. For example:
* {"example.com": {"key": "value", "my_number": "123"}}
* keys and per-origin session storage data as strings. For example:
* {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
*/
restore(aDocShell, aStorageData) {
for (let origin of Object.keys(aStorageData)) {
@ -115,9 +115,24 @@ var SessionStorageInternal = {
let principal;
try {
// NOTE: In capture() we record the full origin for the URI which the
// sessionStorage is being captured for. As of bug 1235657 this code
// stopped parsing any origins which have originattributes correctly, as
// it decided to use the origin attributes from the docshell, and try to
// interpret the origin as a URI. Since bug 1353844 this code now correctly
// parses the full origin, and then discards the origin attributes, to
// make the behavior line up with the original intentions in bug 1235657
// while preserving the ability to read all session storage from
// previous versions. In the future, if this behavior is desired, we may
// want to use the spec instead of the origin as the key, and avoid
// transmitting origin attribute information which we then discard when
// restoring.
//
// If changing this logic, make sure to also change the principal
// computation logic in SessionStore::_sendRestoreHistory.
let attrs = aDocShell.getOriginAttributes();
let originURI = Services.io.newURI(origin);
principal = Services.scriptSecurityManager.createCodebasePrincipal(originURI, attrs);
let dataPrincipal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
principal = Services.scriptSecurityManager.createCodebasePrincipal(dataPrincipal.URI, attrs);
} catch (e) {
console.error(e);
continue;

Просмотреть файл

@ -3700,8 +3700,7 @@ var SessionStoreInternal = {
userTypedClear: tabData.userTypedClear || 0
});
browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory",
{tabData, epoch, loadArguments});
this._sendRestoreHistory(browser, {tabData, epoch, loadArguments});
// Update tab label and icon to show something
// while we wait for the messages to be processed.
@ -3784,7 +3783,7 @@ var SessionStoreInternal = {
// will be ignored and don't override any tab data set when restoring.
let epoch = this.startNextEpoch(browser);
browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory", {
this._sendRestoreHistory(browser, {
tabData,
epoch,
loadArguments: aLoadArguments,
@ -4720,7 +4719,40 @@ var SessionStoreInternal = {
// and not garbage-collected until then.
promise.then(() => timer.cancel(), () => timer.cancel());
return promise;
}
},
/**
* Send the "SessionStore:restoreHistory" message to content, triggering a
* content restore. This method is intended to be used internally by
* SessionStore, as it also ensures that permissions are avaliable in the
* content process before triggering the history restore in the content
* process.
*
* @param browser The browser to transmit the permissions for
* @param options The options data to send to content.
*/
_sendRestoreHistory(browser, options) {
// If the tabData which we're sending down has any sessionStorage associated
// with it, we need to send down permissions for the domains, as this
// information will be needed to correctly restore the session.
if (options.tabData.storage) {
for (let origin of Object.getOwnPropertyNames(options.tabData.storage)) {
try {
let {frameLoader} = browser.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
if (frameLoader.tabParent) {
let attrs = browser.contentPrincipal.originAttributes;
let dataPrincipal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
let principal = Services.scriptSecurityManager.createCodebasePrincipal(dataPrincipal.URI, attrs);
frameLoader.tabParent.transmitPermissionsForPrincipal(principal);
}
} catch (e) {
console.error(e);
}
}
}
browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory", options);
},
};
/**

Просмотреть файл

@ -155,6 +155,9 @@ add_task(function* test_about_page_navigate() {
is(entries.length, 1, "there is one shistory entry");
is(entries[0].url, "about:blank", "url is correct");
// Verify that the title is also recorded.
is(entries[0].title, "about:blank", "title is correct");
browser.loadURI("about:robots");
yield promiseBrowserLoaded(browser);

Просмотреть файл

@ -8,12 +8,12 @@
"unpack": true
},
{
"digest": "36dc644e24c0aa824975ad8f5c15714445d5cb064d823000c3cb637e885199414d7df551e6b99233f0656dcf5760918192ef04113c486af37f3c489bb93ad029",
"digest": "2e56a3cf84764b8e63720e5f961cff7ba8ba5cf2f353dac55c69486489bcd89f53a757e09469a07700b80cd09f09666c2db4ce375b67060ac3be967714597231",
"unpack": true,
"algorithm": "sha512",
"filename": "sixgill.tar.xz",
"size": 2631908,
"hg_id": "8cb9c3fb039a+ tip"
"size": 2629600,
"hg_id": "221d0d2eead9"
},
{
"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",

Просмотреть файл

@ -1,3 +1,3 @@
This is the pdf.js project output, https://github.com/mozilla/pdf.js
Current extension version is: 1.8.173
Current extension version is: 1.8.183

Просмотреть файл

@ -3377,8 +3377,8 @@ var _UnsupportedManager = function UnsupportedManagerClosure() {
}
};
}();
exports.version = '1.8.173';
exports.build = 'c5199d08';
exports.version = '1.8.183';
exports.build = '22744655';
exports.getDocument = getDocument;
exports.PDFDataRangeTransport = PDFDataRangeTransport;
exports.PDFWorker = PDFWorker;
@ -4345,8 +4345,8 @@ if (!globalScope.PDFJS) {
globalScope.PDFJS = {};
}
var PDFJS = globalScope.PDFJS;
PDFJS.version = '1.8.173';
PDFJS.build = 'c5199d08';
PDFJS.version = '1.8.183';
PDFJS.build = '22744655';
PDFJS.pdfBug = false;
if (PDFJS.verbosity !== undefined) {
sharedUtil.setVerbosityLevel(PDFJS.verbosity);
@ -5169,17 +5169,7 @@ var CanvasGraphics = function CanvasGraphicsClosure() {
this.ctx.globalAlpha = state[1];
break;
case 'BM':
if (value && value.name && value.name !== 'Normal') {
var mode = value.name.replace(/([A-Z])/g, function (c) {
return '-' + c.toLowerCase();
}).substring(1);
this.ctx.globalCompositeOperation = mode;
if (this.ctx.globalCompositeOperation !== mode) {
warn('globalCompositeOperation "' + mode + '" is not supported');
}
} else {
this.ctx.globalCompositeOperation = 'source-over';
}
this.ctx.globalCompositeOperation = value;
break;
case 'SMask':
if (this.current.activeSMask) {
@ -5214,7 +5204,7 @@ var CanvasGraphics = function CanvasGraphicsClosure() {
activeSMask.startTransformInverse = groupCtx.mozCurrentTransformInverse;
copyCtxState(currentCtx, groupCtx);
this.ctx = groupCtx;
this.setGState([['BM', 'Normal'], ['ca', 1], ['CA', 1]]);
this.setGState([['BM', 'source-over'], ['ca', 1], ['CA', 1]]);
this.groupStack.push(currentCtx);
this.groupLevel++;
},
@ -5883,7 +5873,7 @@ var CanvasGraphics = function CanvasGraphicsClosure() {
}
copyCtxState(currentCtx, groupCtx);
this.ctx = groupCtx;
this.setGState([['BM', 'Normal'], ['ca', 1], ['CA', 1]]);
this.setGState([['BM', 'source-over'], ['ca', 1], ['CA', 1]]);
this.groupStack.push(currentCtx);
this.groupLevel++;
this.current.activeSMask = null;
@ -6685,8 +6675,8 @@ exports.TilingPattern = TilingPattern;
"use strict";
var pdfjsVersion = '1.8.173';
var pdfjsBuild = 'c5199d08';
var pdfjsVersion = '1.8.183';
var pdfjsBuild = '22744655';
var pdfjsSharedUtil = __w_pdfjs_require__(0);
var pdfjsDisplayGlobal = __w_pdfjs_require__(8);
var pdfjsDisplayAPI = __w_pdfjs_require__(3);

Просмотреть файл

@ -14543,6 +14543,48 @@ var PartialEvaluator = function PartialEvaluatorClosure() {
this.checked = 0;
}
};
function normalizeBlendMode(value) {
if (!isName(value)) {
return 'source-over';
}
switch (value.name) {
case 'Normal':
case 'Compatible':
return 'source-over';
case 'Multiply':
return 'multiply';
case 'Screen':
return 'screen';
case 'Overlay':
return 'overlay';
case 'Darken':
return 'darken';
case 'Lighten':
return 'lighten';
case 'ColorDodge':
return 'color-dodge';
case 'ColorBurn':
return 'color-burn';
case 'HardLight':
return 'hard-light';
case 'SoftLight':
return 'soft-light';
case 'Difference':
return 'difference';
case 'Exclusion':
return 'exclusion';
case 'Hue':
return 'hue';
case 'Saturation':
return 'saturation';
case 'Color':
return 'color';
case 'Luminosity':
return 'luminosity';
}
warn('Unsupported blend mode: ' + value.name);
return 'source-over';
}
var deferred = Promise.resolve();
var TILING_PATTERN = 1,
SHADING_PATTERN = 2;
@ -14819,7 +14861,7 @@ var PartialEvaluator = function PartialEvaluatorClosure() {
});
break;
case 'BM':
gStateObj.push([key, value]);
gStateObj.push([key, normalizeBlendMode(value)]);
break;
case 'SMask':
if (isName(value, 'None')) {
@ -14989,7 +15031,7 @@ var PartialEvaluator = function PartialEvaluatorClosure() {
operatorList.addOp(fn, pattern.getIR());
return Promise.resolve();
}
return Promise.reject('Unknown PatternType: ' + typeNum);
return Promise.reject(new Error('Unknown PatternType: ' + typeNum));
}
operatorList.addOp(fn, args);
return Promise.resolve();
@ -36370,8 +36412,8 @@ exports.Type1Parser = Type1Parser;
"use strict";
var pdfjsVersion = '1.8.173';
var pdfjsBuild = 'c5199d08';
var pdfjsVersion = '1.8.183';
var pdfjsBuild = '22744655';
var pdfjsCoreWorker = __w_pdfjs_require__(17);
;
exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;

Просмотреть файл

@ -853,6 +853,11 @@ decoder.noHWAcceleration.message = To improve video quality, you may need to ins
decoder.noPulseAudio.message = To play audio, you may need to install the required PulseAudio software.
decoder.unsupportedLibavcodec.message = libavcodec may be vulnerable or is not supported, and should be updated to play video.
decoder.decodeError.message = An error occurred while decoding a media resource.
decoder.decodeError.button = Report Site Issue
decoder.decodeError.accesskey = R
decoder.decodeWarning.message = A recoverable error occurred while decoding a media resource.
# LOCALIZATION NOTE (captivePortal.infoMessage3):
# Shown in a notification bar when we detect a captive portal is blocking network access
# and requires the user to log in before browsing.

Просмотреть файл

@ -188,9 +188,11 @@ loadingSiteDataSize=Calculating site data size…
clearSiteDataPromptTitle=Clear all cookies and site data
clearSiteDataPromptText=Selecting Clear Now will clear all cookies and site data stored by Firefox. This may sign you out of websites and remove offline web content.
clearSiteDataNow=Clear Now
important=Important
default=Default
persistent=Persistent
siteUsage=%1$S %2$S
acceptRemove=Remove
# LOCALIZATION NOTE (siteDataSettings.description): %S = brandShortName
siteDataSettings.description=The following websites store site data on your computer. %S keeps data from sites with persistent storage until you delete it, and deletes data from sites with non-persistent storage as space is needed.
# LOCALIZATION NOTE (removeAllSiteData, removeAllSiteDataShown):
# removeAllSiteData and removeAllSiteDataShown are both used on the same one button,
# never displayed together and can share the same accesskey.

Просмотреть файл

@ -188,9 +188,11 @@ loadingSiteDataSize=Calculating site data size…
clearSiteDataPromptTitle=Clear all cookies and site data
clearSiteDataPromptText=Selecting Clear Now will clear all cookies and site data stored by Firefox. This may sign you out of websites and remove offline web content.
clearSiteDataNow=Clear Now
important=Important
default=Default
persistent=Persistent
siteUsage=%1$S %2$S
acceptRemove=Remove
# LOCALIZATION NOTE (siteDataSettings.description): %S = brandShortName
siteDataSettings.description=The following websites store site data on your computer. %S keeps data from sites with persistent storage until you delete it, and deletes data from sites with non-persistent storage as space is needed.
# LOCALIZATION NOTE (removeAllSiteData, removeAllSiteDataShown):
# removeAllSiteData and removeAllSiteDataShown are both used on the same one button,
# never displayed together and can share the same accesskey.

Просмотреть файл

@ -3,12 +3,11 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY window.title "Settings - Site Data">
<!ENTITY settings.description "The following websites asked to store site data in your disk. You can specify which websites are allowed to store site data. Default site data is temporary and could be deleted automatically.">
<!ENTITY hostCol.label "Site">
<!ENTITY statusCol.label "Status">
<!ENTITY usageCol.label "Storage">
<!ENTITY searchPlaceHolder "Search">
<!ENTITY searchPlaceHolder.accesskey "S">
<!ENTITY searchTextboxPlaceHolder "Search websites">
<!ENTITY searchTextboxPlaceHolder.accesskey "S">
<!ENTITY removeSelected.label "Remove Selected">
<!ENTITY removeSelected.accesskey "r">
<!ENTITY save.label "Save Changes">
@ -16,5 +15,5 @@
<!ENTITY cancel.label "Cancel">
<!ENTITY cancel.accesskey "C">
<!ENTITY removingDialog.title "Removing Site Data">
<!ENTITY removingDialog.description "Removing site data will also remove cookies. This may log you out of websites and remove offline web content. Are you sure you want to make the changes?">
<!ENTITY removingSelected.description "Removing site data will also remove related cookies and offline web content. This may log you out of websites. Are you sure you want to make the changes?">
<!ENTITY siteTree.label "The following website cookies will be removed:">

Просмотреть файл

@ -259,16 +259,15 @@ let urlbarListener = {
.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX")
.add(idx);
// Ideally this would be a keyed histogram and we'd just add(actionType),
// but keyed histograms aren't currently shown on the telemetry dashboard
// (bug 1151756).
//
// You can add values but don't change any of the existing values.
// Otherwise you'll break our data.
if (actionType in URLBAR_SELECTED_RESULT_TYPES) {
Services.telemetry
.getHistogramById("FX_URLBAR_SELECTED_RESULT_TYPE")
.add(URLBAR_SELECTED_RESULT_TYPES[actionType]);
Services.telemetry
.getKeyedHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE")
.add(actionType, idx);
} else {
Cu.reportError("Unknown FX_URLBAR_SELECTED_RESULT_TYPE type: " +
actionType);

Просмотреть файл

@ -537,13 +537,7 @@ PluginContent.prototype = {
break;
case "PluginInstantiated":
let key = this._getPluginInfo(plugin).pluginTag.niceName;
Services.telemetry.getKeyedHistogramById("PLUGIN_ACTIVATION_COUNT").add(key);
shouldShowNotification = true;
let pluginRect = plugin.getBoundingClientRect();
if (pluginRect.width <= 5 && pluginRect.height <= 5) {
Services.telemetry.getHistogramById("PLUGIN_TINY_CONTENT").add(1);
}
break;
}

Просмотреть файл

@ -102,6 +102,8 @@ add_task(function* test_simpleQuery() {
Services.telemetry.clearEvents();
let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
let resultTypeHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_TYPE");
let resultIndexByTypeHist = Services.telemetry.getKeyedHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
resultIndexByTypeHist.clear();
resultIndexHist.clear();
resultTypeHist.clear();
@ -138,6 +140,11 @@ add_task(function* test_simpleQuery() {
URLBAR_SELECTED_RESULT_TYPES.searchengine,
"FX_URLBAR_SELECTED_RESULT_TYPE");
let resultIndexByType = resultIndexByTypeHist.snapshot("searchengine");
checkHistogramResults(resultIndexByType,
0,
"FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
yield BrowserTestUtils.removeTab(tab);
});
@ -147,6 +154,8 @@ add_task(function* test_searchAlias() {
Services.telemetry.clearEvents();
let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
let resultTypeHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_TYPE");
let resultIndexByTypeHist = Services.telemetry.getKeyedHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
resultIndexByTypeHist.clear();
resultIndexHist.clear();
resultTypeHist.clear();
@ -183,6 +192,11 @@ add_task(function* test_searchAlias() {
URLBAR_SELECTED_RESULT_TYPES.searchengine,
"FX_URLBAR_SELECTED_RESULT_TYPE");
let resultIndexByType = resultIndexByTypeHist.snapshot("searchengine");
checkHistogramResults(resultIndexByType,
0,
"FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
yield BrowserTestUtils.removeTab(tab);
});
@ -192,6 +206,8 @@ add_task(function* test_oneOff() {
Services.telemetry.clearEvents();
let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
let resultTypeHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_TYPE");
let resultIndexByTypeHist = Services.telemetry.getKeyedHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
resultIndexByTypeHist.clear();
resultIndexHist.clear();
resultTypeHist.clear();
@ -231,6 +247,11 @@ add_task(function* test_oneOff() {
URLBAR_SELECTED_RESULT_TYPES.searchengine,
"FX_URLBAR_SELECTED_RESULT_TYPE");
let resultIndexByType = resultIndexByTypeHist.snapshot("searchengine");
checkHistogramResults(resultIndexByType,
0,
"FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
yield BrowserTestUtils.removeTab(tab);
});
@ -240,6 +261,8 @@ add_task(function* test_suggestion() {
Services.telemetry.clearEvents();
let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
let resultTypeHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_TYPE");
let resultIndexByTypeHist = Services.telemetry.getKeyedHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
resultIndexByTypeHist.clear();
resultIndexHist.clear();
resultTypeHist.clear();
@ -291,6 +314,11 @@ add_task(function* test_suggestion() {
URLBAR_SELECTED_RESULT_TYPES.searchsuggestion,
"FX_URLBAR_SELECTED_RESULT_TYPE");
let resultIndexByType = resultIndexByTypeHist.snapshot("searchsuggestion");
checkHistogramResults(resultIndexByType,
3,
"FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
Services.search.currentEngine = previousEngine;
Services.search.removeEngine(suggestionEngine);
yield BrowserTestUtils.removeTab(tab);

Просмотреть файл

@ -931,6 +931,17 @@ notification[value="translation"] menulist > .menulist-dropmarker {
border: 1px solid transparent;
}
.autocomplete-richlistitem:hover,
treechildren.searchbar-treebody::-moz-tree-row(hover) {
background-color: hsla(0, 0%, 0%, 0.06);
border-color: hsla(0, 0%, 0%, 0.1);
}
.autocomplete-richlistitem[selected],
treechildren.searchbar-treebody::-moz-tree-row(selected) {
background-color: Highlight;
}
.ac-title {
font-size: 1.05em;
}

Просмотреть файл

@ -221,6 +221,13 @@ menuitem[cmd="cmd_clearhistory"][disabled] {
background-image: none;
}
.searchbar-engine-one-off-item:not([selected]):not(.dummy):hover,
.search-setting-button:hover,
.addengine-item:hover {
background-color: hsla(0, 0%, 0%, 0.06);
color: inherit;
}
.searchbar-engine-one-off-item[selected] {
background-color: Highlight;
background-image: none;

Просмотреть файл

@ -1698,11 +1698,21 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button {
border: 1px solid transparent;
}
.autocomplete-richlistitem:hover,
treechildren.searchbar-treebody::-moz-tree-row(hover) {
background-color: hsla(0, 0%, 0%, 0.06);
border-color: hsla(0, 0%, 0%, 0.1);
}
.autocomplete-richlistitem[selected] {
background-color: hsl(210, 80%, 52%);
color: hsl(0, 0%, 100%);
}
treechildren.searchbar-treebody::-moz-tree-row(selected) {
background-color: Highlight;
}
.ac-title {
font-size: 14px;
}

Просмотреть файл

@ -210,6 +210,13 @@
background-image: none;
}
.searchbar-engine-one-off-item:not([selected]):not(.dummy):hover,
.search-setting-button:hover,
.addengine-item:hover {
background-color: hsla(0, 0%, 0%, 0.06);
color: inherit;
}
.searchbar-engine-one-off-item[selected] {
background-color: Highlight;
background-image: none;

Просмотреть файл

@ -302,7 +302,13 @@ window:not([chromehidden~="toolbar"]) #urlbar-wrapper > #urlbar:-moz-locale-dir(
}
.tab-throbber[selected][progress] {
fill: #fff;
list-style-image: url("chrome://browser/skin/compacttheme/loading-inverted.png");
}
@media (min-resolution: 1.1dppx) {
.tab-throbber[selected][progress] {
list-style-image: url("chrome://browser/skin/compacttheme/loading-inverted@2x.png");
}
}
.tab-icon-sound[soundplaying],

Двоичные данные
browser/themes/shared/compacttheme/loading-inverted.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.2 KiB

Двоичные данные
browser/themes/shared/compacttheme/loading-inverted@2x.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Просмотреть файл

@ -44,7 +44,7 @@ toolbar[brighttext] #downloads-indicator-progress-icon {
}
}
#downloads-button[notification="start"] > #downloads-indicator-anchor {
#downloads-button[notification="start"] > #downloads-indicator-anchor > #downloads-indicator-icon {
animation-name: downloadsIndicatorStartJump;
/* Upon changing the overall duration below, please keep the delay time of
setTimeout() identical in indicator.js for this animation. */

Просмотреть файл

@ -102,9 +102,9 @@
skin/classic/browser/search-indicator-magnifying-glass.svg (../shared/search/search-indicator-magnifying-glass.svg)
skin/classic/browser/search-arrow-go.svg (../shared/search/search-arrow-go.svg)
skin/classic/browser/gear.svg (../shared/search/gear.svg)
skin/classic/browser/tabbrowser/connecting.svg (../shared/tabbrowser/connecting.svg)
skin/classic/browser/tabbrowser/connecting.png (../shared/tabbrowser/connecting.png)
skin/classic/browser/tabbrowser/connecting@2x.png (../shared/tabbrowser/connecting@2x.png)
skin/classic/browser/tabbrowser/crashed.svg (../shared/tabbrowser/crashed.svg)
skin/classic/browser/tabbrowser/loading.svg (../shared/tabbrowser/loading.svg)
skin/classic/browser/tabbrowser/pendingpaint.png (../shared/tabbrowser/pendingpaint.png)
skin/classic/browser/tabbrowser/tab-audio.svg (../shared/tabbrowser/tab-audio.svg)
skin/classic/browser/tabbrowser/tab-audio-small.svg (../shared/tabbrowser/tab-audio-small.svg)
@ -140,6 +140,8 @@
skin/classic/browser/privatebrowsing/private-browsing.svg (../shared/privatebrowsing/private-browsing.svg)
skin/classic/browser/privatebrowsing/tracking-protection-off.svg (../shared/privatebrowsing/tracking-protection-off.svg)
skin/classic/browser/privatebrowsing/tracking-protection.svg (../shared/privatebrowsing/tracking-protection.svg)
skin/classic/browser/compacttheme/loading-inverted.png (../shared/compacttheme/loading-inverted.png)
skin/classic/browser/compacttheme/loading-inverted@2x.png (../shared/compacttheme/loading-inverted@2x.png)
skin/classic/browser/compacttheme/urlbar-history-dropmarker.svg (../shared/compacttheme/urlbar-history-dropmarker.svg)
skin/classic/browser/urlbar-star.svg (../shared/urlbar-star.svg)
skin/classic/browser/urlbar-tab.svg (../shared/urlbar-tab.svg)

Двоичные данные
browser/themes/shared/tabbrowser/connecting.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.3 KiB

Просмотреть файл

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path d="M8,0A8,8,0,0,0,0,8a8,8,0,0,0,8,8,8,8,0,0,0,8-8A8,8,0,0,0,8,0ZM8,13A5,5,0,0,1,3,8,5,5,0,0,1,8,3a5,5,0,0,1,5,5A5,5,0,0,1,8,13Z" fill="context-fill" fill-opacity=".2"/>
<circle cx="14.5" cy="8" r="1.5" fill="context-fill" fill-opacity=".8"/>
</svg>

До

Ширина:  |  Высота:  |  Размер: 396 B

Двоичные данные
browser/themes/shared/tabbrowser/connecting@2x.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 29 KiB

Просмотреть файл

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<defs>
<radialGradient id="radialGradient" cx=".85" cy=".65" r=".75">
<stop stop-color="#000" offset=".2"/>
<stop stop-color="#fff" offset="1"/>
</radialGradient>
<mask id="myMask">
<rect width="16" height="16" fill="#fff"/>
<path d="M11.8,9.3A4,4,0,1,1,5.2,5.2L1.6,1.6a9,9,0,1,0,14.9,9.2Z" fill="url(#radialGradient)"/>
</mask>
</defs>
<path fill="context-fill" fill-opacity=".15" d="M8,0A8,8,0,0,0,0,8a8,8,0,0,0,8,8,8,8,0,0,0,8-8A8,8,0,0,0,8,0ZM8,13A5,5,0,0,1,3,8,5,5,0,0,1,8,3a5,5,0,0,1,5,5A5,5,0,0,1,8,13Z"/>
<path mask="url(#myMask)" fill="context-fill" d="M8,3a5,5,0,0,1,5,5h0a1.5,1.5,0,0,0,3,0h0a8,8,0,1,0-.8,3.5l-2.7-1.3A5,5,0,1,1,8,3Z"/>
</svg>

До

Ширина:  |  Высота:  |  Размер: 843 B

Просмотреть файл

@ -178,35 +178,12 @@
list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white-blocked");
}
@keyframes tab-throbber-loading {
to {
transform: rotate(360deg);
}
}
@keyframes tab-throbber-connecting {
from {
transform: rotate(360deg);
}
}
.tab-throbber[busy] {
list-style-image: url("chrome://browser/skin/tabbrowser/connecting.svg");
animation-duration: 960ms;
animation-iteration-count: infinite;
animation-name: tab-throbber-connecting;
animation-timing-function: linear;
/* uncomment after bug 1350010:
context-properties: fill;
*/
fill: currentColor;
list-style-image: url("chrome://browser/skin/tabbrowser/connecting.png");
}
.tab-throbber[progress] {
list-style-image: url("chrome://browser/skin/tabbrowser/loading.svg");
animation-duration: 800ms;
animation-name: tab-throbber-loading;
fill: #0077ff;
list-style-image: url("chrome://global/skin/icons/loading.png");
}
.tab-label {
@ -585,9 +562,17 @@
var(--lwt-header-image);
}
.tab-throbber[busy] {
list-style-image: url("chrome://browser/skin/tabbrowser/connecting@2x.png");
}
.tab-icon-image {
list-style-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png");
}
.tab-throbber[progress] {
list-style-image: url("chrome://global/skin/icons/loading@2x.png");
}
}
/* All tabs menupopup */

Просмотреть файл

@ -1438,11 +1438,21 @@ html|span.ac-emphasize-text-url {
}
@media (-moz-windows-default-theme) {
.autocomplete-richlistitem[selected=true] {
.autocomplete-richlistitem:hover,
treechildren.searchbar-treebody::-moz-tree-row(hover) {
background-color: hsla(0, 0%, 0%, 0.06);
border-color: hsla(0, 0%, 0%, 0.1);
}
.autocomplete-richlistitem[selected] {
background-color: hsl(210, 80%, 52%);
color: hsl(0, 0%, 100%);
}
treechildren.searchbar-treebody::-moz-tree-row(selected) {
background-color: Highlight;
}
.ac-title:not([selected=true]) {
color: hsl(0, 0%, 0%);
}

Просмотреть файл

@ -222,6 +222,13 @@
background-image: none;
}
.searchbar-engine-one-off-item:not([selected]):not(.dummy):hover,
.search-setting-button:hover,
.addengine-item:hover {
background-color: hsla(0, 0%, 0%, 0.06);
color: inherit;
}
.searchbar-engine-one-off-item[selected] {
background-color: Highlight;
background-image: none;

Просмотреть файл

@ -368,6 +368,8 @@ def split_triplet(triplet):
canonical_os = canonical_kernel = 'NetBSD'
elif os.startswith('openbsd'):
canonical_os = canonical_kernel = 'OpenBSD'
elif os.startswith('solaris'):
canonical_os = canonical_kernel = 'SunOS'
else:
die('Unknown OS: %s' % os)
@ -626,6 +628,13 @@ def target_is_linux(target):
set_define('XP_LINUX', target_is_linux)
@depends(target)
def target_is_solaris(target):
if target.kernel == 'SunOS':
return True
set_define('XP_SOLARIS', target_is_solaris)
# The application/project to build
# ==============================================================
option('--enable-application', nargs=1, env='MOZ_BUILD_APP',

Просмотреть файл

@ -1187,8 +1187,7 @@ nsScriptSecurityManager::
{
NS_ENSURE_STATE(aLoadContext);
OriginAttributes docShellAttrs;
bool result = aLoadContext->GetOriginAttributes(docShellAttrs);
NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
aLoadContext->GetOriginAttributes(docShellAttrs);
nsCOMPtr<nsIPrincipal> prin =
BasePrincipal::CreateCodebasePrincipal(aURI, docShellAttrs);

1
devtools/bootstrap.js поставляемый
Просмотреть файл

@ -82,6 +82,7 @@ function processPrefFile(url) {
function setPrefs() {
processPrefFile(resourceURI.spec + "./client/preferences/devtools.js");
processPrefFile(resourceURI.spec + "./client/preferences/debugger.js");
processPrefFile(resourceURI.spec + "./client/webide/webide-prefs.js");
}
// Helper to listen to a key on all windows

Просмотреть файл

@ -10,7 +10,7 @@ const React = require("devtools/client/shared/vendor/react");
const { l10n } = require("../utils");
// Reps
const { createFactories } = require("devtools/client/shared/components/reps/reps");
const { createFactories } = require("devtools/client/shared/react-utils");
const { Toolbar, ToolbarButton } = createFactories(require("devtools/client/jsonview/components/reps/toolbar"));
// DOM Panel

Просмотреть файл

@ -29,6 +29,7 @@ module.exports = createClass({
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
onShowGridAreaHighlight: PropTypes.func.isRequired,
onShowGridCellHighlight: PropTypes.func.isRequired,
onShowGridLineNamesHighlight: PropTypes.func.isRequired,
onToggleGridHighlighter: PropTypes.func.isRequired,
onToggleShowGridLineNumbers: PropTypes.func.isRequired,
onToggleShowInfiniteLines: PropTypes.func.isRequired,
@ -51,6 +52,7 @@ module.exports = createClass({
onToggleShowInfiniteLines,
onShowGridAreaHighlight,
onShowGridCellHighlight,
onShowGridLineNamesHighlight,
} = this.props;
return grids.length ?
@ -63,6 +65,7 @@ module.exports = createClass({
grids,
onShowGridAreaHighlight,
onShowGridCellHighlight,
onShowGridLineNamesHighlight,
})
:
null,

Просмотреть файл

@ -10,6 +10,9 @@ const { throttle } = require("devtools/client/inspector/shared/utils");
const Types = require("../types");
const COLUMNS = "cols";
const ROWS = "rows";
// The delay prior to executing the grid cell highlighting.
const GRID_CELL_MOUSEOVER_TIMEOUT = 150;
@ -31,6 +34,7 @@ module.exports = createClass({
grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
onShowGridAreaHighlight: PropTypes.func.isRequired,
onShowGridCellHighlight: PropTypes.func.isRequired,
onShowGridLineNamesHighlight: PropTypes.func.isRequired,
},
mixins: [ addons.PureRenderMixin ],
@ -259,6 +263,103 @@ module.exports = createClass({
);
},
/**
* Renders the grid line of a grid fragment.
*
* @param {Number} id
* The grid id stored on the grid fragment
* @param {Number} gridFragmentIndex
* The index of the grid fragment rendered to the document.
* @param {String} color
* The color of the grid.
* @param {Number} x1
* The starting x-coordinate of the grid line.
* @param {Number} y1
* The starting y-coordinate of the grid line.
* @param {Number} x2
* The ending x-coordinate of the grid line.
* @param {Number} y2
* The ending y-coordinate of the grid line.
* @param {Number} gridLineNumber
* The grid line number of the line being rendered.
* @param {String} lineType
* The grid line name(s) of the line being rendered.
*/
renderGridLine(id, gridFragmentIndex, color, x1, y1, x2, y2,
gridLineNumber, lineType) {
return dom.line(
{
className: "grid-outline-line",
"data-grid-fragment-index": gridFragmentIndex,
"data-grid-id": id,
"data-grid-line-color": color,
"data-grid-line-number": gridLineNumber,
"data-grid-line-type": lineType,
x1,
y1,
x2,
y2,
onMouseOver: this.onMouseOverLine,
onMouseOut: this.onMouseLeaveLine,
stroke: "#000000",
}
);
},
renderGridLines(grids) {
return dom.g(
{
className: "grid-outline-lines",
},
grids.map(grid => this.renderLines(grid))
);
},
renderLines(grid) {
const { id, color, gridFragments } = grid;
const { width, height } = this.state;
let gridFragmentIndex = 0;
const { rows, cols } = gridFragments[gridFragmentIndex];
const numberOfColumns = cols.lines.length - 1;
const numberOfRows = rows.lines.length - 1;
const lines = [];
let x = 1;
let y = 1;
let rowBreadth = 0;
let colBreadth = 0;
if (width > 0 && height > 0) {
for (let row = 0; row <= numberOfRows; row++) {
if (row < numberOfRows) {
rowBreadth = GRID_CELL_SCALE_FACTOR * (rows.tracks[row].breadth / 100);
}
const { number } = rows.lines[row];
const rowLine = this.renderGridLine(id, gridFragmentIndex, color,
x, y, width - 20, y, number, ROWS);
lines.push(rowLine);
y += rowBreadth;
}
y = 1;
for (let col = 0; col <= numberOfColumns; col++) {
if (col < numberOfColumns) {
colBreadth = GRID_CELL_SCALE_FACTOR * (cols.tracks[col].breadth / 100);
}
const { number } = cols.lines[col];
const colLine = this.renderGridLine(id, gridFragmentIndex, color,
x, y, x, height - 20, number, COLUMNS);
lines.push(colLine);
x += colBreadth;
}
}
return lines;
},
onMouseLeaveCell({ target }) {
const {
grids,
@ -279,6 +380,27 @@ module.exports = createClass({
this.highlightCell(event);
},
onMouseLeaveLine({ target }) {
const { grids, onShowGridLineNamesHighlight } = this.props;
const fragmentIndex = target.getAttribute("data-grid-fragment-index");
const id = target.getAttribute("data-grid-id");
const color = target.getAttribute("data-grid-line-color");
onShowGridLineNamesHighlight(grids[id].nodeFront, fragmentIndex, color);
},
onMouseOverLine({ target }) {
const { grids, onShowGridLineNamesHighlight } = this.props;
const fragmentIndex = target.getAttribute("data-grid-fragment-index");
const id = target.getAttribute("data-grid-id");
const lineNumber = target.getAttribute("data-grid-line-number");
const type = target.getAttribute("data-grid-line-type");
const color = target.getAttribute("data-grid-line-color");
onShowGridLineNamesHighlight(grids[id].nodeFront, fragmentIndex, color,
lineNumber, type);
},
render() {
const { selectedGrids, height, width } = this.state;
@ -290,7 +412,8 @@ module.exports = createClass({
height: this.getHeight(),
viewBox: `${TRANSLATE_X} ${TRANSLATE_Y} ${width} ${height}`,
},
this.renderGridOutline(selectedGrids)
this.renderGridOutline(selectedGrids),
this.renderGridLines(selectedGrids)
)
:
null;

Просмотреть файл

@ -51,6 +51,7 @@ function GridInspector(inspector, window) {
this.onSetGridOverlayColor = this.onSetGridOverlayColor.bind(this);
this.onShowGridAreaHighlight = this.onShowGridAreaHighlight.bind(this);
this.onShowGridCellHighlight = this.onShowGridCellHighlight.bind(this);
this.onShowGridLineNamesHighlight = this.onShowGridLineNamesHighlight.bind(this);
this.onSidebarSelect = this.onSidebarSelect.bind(this);
this.onToggleGridHighlighter = this.onToggleGridHighlighter.bind(this);
this.onToggleShowGridLineNumbers = this.onToggleShowGridLineNumbers.bind(this);
@ -121,6 +122,7 @@ GridInspector.prototype = {
onSetGridOverlayColor: this.onSetGridOverlayColor,
onShowGridAreaHighlight: this.onShowGridAreaHighlight,
onShowGridCellHighlight: this.onShowGridCellHighlight,
onShowGridLineNamesHighlight: this.onShowGridLineNamesHighlight,
onToggleGridHighlighter: this.onToggleGridHighlighter,
onToggleShowGridLineNumbers: this.onToggleShowGridLineNumbers,
onToggleShowInfiniteLines: this.onToggleShowInfiniteLines,
@ -378,6 +380,38 @@ GridInspector.prototype = {
this.highlighters.showGridHighlighter(node, highlighterSettings);
},
/**
* Highlights the grid line in the CSS Grid Highlighter for the given grid.
*
* @param {NodeFront} node
* The NodeFront of the grid container element for which the grid
* highlighter is highlighted for.
* @param {Number|null} gridFragmentIndex
* The index of the grid fragment for which the grid highlighter
* is highlighted for.
* @param {String} color
* The color of the grid line for which the grid highlighter
* is highlighted for.
* @param {Number|null} lineNumber
* The line number of the grid for which the grid highlighter
* is highlighted for.
* @param {String|null} type
* The type of line for which the grid line is being highlighted for.
*/
onShowGridLineNamesHighlight(node, gridFragmentIndex, color, lineNumber, type) {
let { highlighterSettings } = this.store.getState();
highlighterSettings.showGridLineNames = {
gridFragmentIndex,
lineNumber,
type
};
highlighterSettings.color = color;
this.highlighters.showGridHighlighter(node, highlighterSettings);
},
/**
* Handler for the inspector sidebar select event. Starts listening for
* "grid-layout-changed" if the layout panel is visible. Otherwise, stop

Просмотреть файл

@ -49,6 +49,7 @@ LayoutView.prototype = {
onSetGridOverlayColor,
onShowGridAreaHighlight,
onShowGridCellHighlight,
onShowGridLineNamesHighlight,
onToggleGridHighlighter,
onToggleShowGridLineNumbers,
onToggleShowInfiniteLines,
@ -76,6 +77,7 @@ LayoutView.prototype = {
onShowBoxModelHighlighterForNode,
onShowGridAreaHighlight,
onShowGridCellHighlight,
onShowGridLineNamesHighlight,
onToggleGeometryEditor,
onToggleGridHighlighter,
onToggleShowGridLineNumbers,

Просмотреть файл

@ -9,7 +9,7 @@
define(function (require, exports, module) {
const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { createFactories } = require("devtools/client/shared/components/reps/reps");
const { createFactories } = require("devtools/client/shared/react-utils");
const { Headers } = createFactories(require("./headers"));
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));

Просмотреть файл

@ -10,7 +10,8 @@ define(function (require, exports, module) {
const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const TreeView = createFactory(require("devtools/client/shared/components/tree/tree-view"));
const { REPS, createFactories, MODE } = require("devtools/client/shared/components/reps/reps");
const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
const { createFactories } = require("devtools/client/shared/react-utils");
const Rep = createFactory(REPS.Rep);
const { SearchBox } = createFactories(require("./search-box"));

Просмотреть файл

@ -9,7 +9,7 @@
define(function (require, exports, module) {
const { createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { createFactories } = require("devtools/client/shared/components/reps/reps");
const { createFactories } = require("devtools/client/shared/react-utils");
const { JsonPanel } = createFactories(require("./json-panel"));
const { TextPanel } = createFactories(require("./text-panel"));
const { HeadersPanel } = createFactories(require("./headers-panel"));

Просмотреть файл

@ -9,8 +9,7 @@
define(function (require, exports, module) {
const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
// we'll need to make load-reps define friendly aka UMD
const { createFactories } = require("devtools/client/shared/components/reps/reps");
const { createFactories } = require("devtools/client/shared/react-utils");
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
const { div, pre } = dom;

Просмотреть файл

@ -8,7 +8,7 @@
define(function (require, exports, module) {
const { render } = require("devtools/client/shared/vendor/react-dom");
const { createFactories } = require("devtools/client/shared/components/reps/reps");
const { createFactories } = require("devtools/client/shared/react-utils");
const { MainTabbedArea } = createFactories(require("./components/main-tabbed-area"));
const json = document.getElementById("json");

Просмотреть файл

@ -32,3 +32,4 @@ skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32
[browser_jsonview_valid_json.js]
[browser_jsonview_save_json.js]
[browser_jsonview_manifest.js]
[browser_json_refresh.js]

Просмотреть файл

@ -0,0 +1,65 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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";
const TEST_JSON_FILE = "simple_json.json";
add_task(function* () {
info("Test JSON refresh started");
// generate file:// URI for JSON file and load in new tab
let dir = getChromeDir(getResolvedURI(gTestPath));
dir.append(TEST_JSON_FILE);
let uri = Services.io.newFileURI(dir);
let tab = yield addJsonViewTab(uri.spec);
// perform sanity checks for URI and pricnipals in loadInfo
yield ContentTask.spawn(tab.linkedBrowser, {TEST_JSON_FILE}, function*({TEST_JSON_FILE}) { // eslint-disable-line
let channel = content.document.docShell.currentDocumentChannel;
let channelURI = channel.URI.spec;
ok(channelURI.startsWith("file://") && channelURI.includes(TEST_JSON_FILE),
"sanity: correct channel uri");
let contentPolicyType = channel.loadInfo.externalContentPolicyType;
is(contentPolicyType, Ci.nsIContentPolicy.TYPE_DOCUMENT,
"sanity: correct contentPolicyType");
let loadingPrincipal = channel.loadInfo.loadingPrincipal;
is(loadingPrincipal, null, "sanity: correct loadingPrincipal");
let triggeringPrincipal = channel.loadInfo.triggeringPrincipal;
ok(Services.scriptSecurityManager.isSystemPrincipal(triggeringPrincipal),
"sanity: correct triggeringPrincipal");
let principalToInherit = channel.loadInfo.principalToInherit;
ok(principalToInherit.isNullPrincipal, "sanity: correct principalToInherit");
ok(content.document.nodePrincipal.isNullPrincipal,
"sanity: correct doc.nodePrincipal");
});
// reload the tab
let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
tab.linkedBrowser.reload();
yield loaded;
// check principals in loadInfo are still correct
yield ContentTask.spawn(tab.linkedBrowser, {TEST_JSON_FILE}, function*({TEST_JSON_FILE}) { // eslint-disable-line
let channel = content.document.docShell.currentDocumentChannel;
let channelURI = channel.URI.spec;
ok(channelURI.startsWith("file://") && channelURI.includes(TEST_JSON_FILE),
"reloaded: correct channel uri");
let contentPolicyType = channel.loadInfo.externalContentPolicyType;
is(contentPolicyType, Ci.nsIContentPolicy.TYPE_DOCUMENT,
"reloaded: correct contentPolicyType");
let loadingPrincipal = channel.loadInfo.loadingPrincipal;
is(loadingPrincipal, null, "reloaded: correct loadingPrincipal");
let triggeringPrincipal = channel.loadInfo.triggeringPrincipal;
ok(Services.scriptSecurityManager.isSystemPrincipal(triggeringPrincipal),
"reloaded: correct triggeringPrincipal");
let principalToInherit = channel.loadInfo.principalToInherit;
ok(principalToInherit.isNullPrincipal, "reloaded: correct principalToInherit");
ok(content.document.nodePrincipal.isNullPrincipal,
"reloaded: correct doc.nodePrincipal");
});
});

Просмотреть файл

@ -411,6 +411,10 @@ netmonitor.toolbar.file=File
# in the network table toolbar, above the "domain" column.
netmonitor.toolbar.domain=Domain
# LOCALIZATION NOTE (netmonitor.toolbar.remoteip): This is the label displayed
# in the network table toolbar, above the "remoteip" column.
netmonitor.toolbar.remoteip=Remote IP
# LOCALIZATION NOTE (netmonitor.toolbar.cause): This is the label displayed
# in the network table toolbar, above the "cause" column.
netmonitor.toolbar.cause=Cause

Просмотреть файл

@ -1,16 +1,27 @@
# netmonitor
# Network Monitor
The Network Monitor shows you all the network requests Firefox makes (for example, when it loads a page, or due to XMLHttpRequests), how long each request takes, and details of each request.
The Network Monitor (netmonitor) shows you all the network requests Firefox makes (for example, when a page is loaded or when an XMLHttpRequest is performed) , how long each request takes, and details of each request. You can edit the method, query, header and resend the request as well. Read [MDN](https://developer.mozilla.org/en-US/docs/Tools/Network_Monitor) to learn how to use the tool.
## Prerequisite
If you want to build the Network Monitor inside of the DevTools toolbox (Firefox Devtools Panels), follow the [simple Firefox build](https://developer.mozilla.org/docs/Mozilla/Developer_guide/Build_Instructions/Simple_Firefox_build) document in MDN. Start your compiled firefox and open the Firefox developer tool, you can see the Network Monitor inside.
If you would like to run the Network Monitor in the browser tab (experimental), you need following packages:
* [node](https://nodejs.org/) >= 6.9.x
* [npm](https://docs.npmjs.com/getting-started/installing-node) >= 3.x
* [yarn](https://yarnpkg.com/docs/install) >= 0.21.x
* [Firefox](https://www.mozilla.org/firefox/new/) either the released version or build from the source code.
Once `node` (`npm` included) is installed, use the following command to install `yarn`.
```
$ npm install -g yarn
```
## Quick Setup
Assume you are in netmonitor folder
Navigate to the `netmonitor` folder with your terminal, then run the following commands:
```bash
# Install packages
@ -23,4 +34,92 @@ yarn start
firefox http://localhost:8000 --start-debugger-server 6080
```
Then open localhost:8000 in any browser to see all tabs in Firefox.
Then open `localhost:8000` in any browser to see all tabs in Firefox.
### More detailed setup
If you have an opened Firefox browser, you can manually configure Firefox as well.
Type `about:config` in Firefox URL field, grant the warning to access preferences. We need to set two preferences:
* disable `devtools.debugger.prompt-connection` to remove the connection prompt.
* enable `devtools.debugger.remote-enabled` to allow remote debugging a browser tab via the Mozilla remote debugging protocol (RDP)
Go to the Web Developer menu in Firefox and select [Developer Toolbar](https://developer.mozilla.org/docs/Tools/GCLI). Run the command
`listen 6080 mozilla-rdp`
The command will make Firefox act as a remote debugging server.
Then run the command
`yarn start`
### How it works
The Network Monitor uses [webpack](https://webpack.js.org/) and several packages from [devtools-core](https://github.com/devtools-html/devtools-core) to run the Network Monitor as a normal web page. The Network Monitor uses [Mozilla remote debugging protocol](http://searchfox.org/mozilla-central/source/devtools/docs/backend/protocol.md) to fetch result and execute commands from Firefox browser.
Open `localhost:8000` in any browser to see the [launchpad](https://github.com/devtools-html/devtools-core/tree/master/packages/devtools-launchpad) interface. Devtools Launchpad will communicate with Firefox (the remote debugging server) and list all opened tabs from Firefox. Click one of the browser tab entry, now you can see the Network Monitor runs in a browser tab.
### Develop with related modules
When working on make the Network Monitor running in the browser tab, you may need to work on external modules. Besides the third party modules, here are modules required for the Network Monitor and is hosted under `devtools-html` (modules shared accross Devtools):
* [devtools-config](https://github.com/devtools-html/devtools-core/blob/master/packages/devtools-config/#readme) config used in dev server
* [devtools-launchpad](https://github.com/devtools-html/devtools-core/blob/master/packages/devtools-launchpad/#readme) provide the dev server, landing page and the bootstrap functions to run devtools in the browser tab.
* [devtools-modules](https://github.com/devtools-html/devtools-core/blob/master/packages/devtools-modules/#readme) Devtools shared and shim modules.
* [devtools-source-editor](https://github.com/devtools-html/devtools-core/blob/master/packages/devtools-source-editor/#readme) Source Editor component.
* [devtools-splitter](https://github.com/devtools-html/devtools-core/blob/master/packages/devtools-splitter/#readme) Splitter component.
* [devtools-reps](https://github.com/devtools-html/reps/#readme) remote object formatter for variables representation.
Do `yarn link` modules in related module directory, then do `yarn link [module-name]` after `yarn install` modules.
## Code Structure
Top level files are used to launch the Network Monitor inside of the DevTools toolbox or run in the browser tab (experimental). The Network Monitor source is mainly located in the `src/` folder, the same code base is used to run in both environments.
We prefer use web standard API instead of FIrefox specific API, to make the Network Monitor can be opened in any browser tab.
### Run inside of the DevTools toolbox
Files used to run the Network Monitor inside of the DevTools toolbox.
* `panel.js` called by devtools toolbox to launch the Network Monitor panel.
* `index.html` panel UI and launch scripts.
* `src/utils/firefox/` wrap function call for Firefox specific API. Files in this folder should provide alias in `webpack.config.js` to mock or polyfill those function call. Therefore run the Network Monitor in the browser tab would work.
### Run in the browser tab (experimental)
Files used to run the Network Monitor in the browser tab
* `bin/` files to launch test server.
* `configs/` dev configs.
* `index.js` the entry point, equivalent to `index.html`.
* `webpack.config.js` the webpack config file, including plenty of module alias map to shims and polyfills.
* `package.json` declare every required packages and available commands.
To run in the browser tab, the Network Monitor needs to get some dependencies from npm module. Check `package.json` to see all dependencies. Check `webpack.config.js` to find the module alias, and check [devtools-core](https://github.com/devtools-html/devtools-core) packages to dive into actual modules used by the Network Monitor and other Devtools.
### UI
The Network Monitor UI is built using [React](http://searchfox.org/mozilla-central/source/devtools/docs/frontend/react.md) components (in `src/components/`).
* **MonitorPanel** in `monitor-panel.js` is the root element.
* Three major container components are
- **Toolbar** Panel related functions.
- **RequestList** Show each request information.
- **NetworkDetailsPanel** Show detailed infomation per request.
* `src/assets` Styles that affect the Network Monitor panel.
We prefer stateless component (define by function) instead of stateful component (define by class) unless the component has to maintain its internal state.
### State
Besides the UI, the Network Monitor manages the app state via [Redux](http://searchfox.org/mozilla-central/source/devtools/docs/frontend/redux.md). The following locations define the app state:
* `src/constants.js` constants used across the tool including action and event names.
* `src/actions/` for all actions that change the state.
* `src/reducers/` for all reducers that change the state.
* `src/selectors/` functions that return a formatted version of parts of the app state.
We use [immutable.js](http://facebook.github.io/immutable-js/) and [reselect](https://github.com/reactjs/reselect) libraries to return new a state object efficiently.

Просмотреть файл

@ -328,7 +328,7 @@ body,
.requests-list-status {
max-width: 6em;
text-align: center;
width: 10vw;
width: 8vw;
}
.requests-list-method,
@ -357,6 +357,10 @@ body,
width: 14vw;
}
.requests-list-remoteip {
width: 8vw;
}
.requests-security-state-icon {
flex: none;
width: 16px;
@ -1303,7 +1307,6 @@ body,
.requests-list-status {
max-width: none;
width: 10vw;
}
.requests-list-status-code {
@ -1324,12 +1327,16 @@ body,
width: 16vw;
}
.requests-list-remoteip {
width: 8vw;
}
.requests-list-cause,
.requests-list-type,
.requests-list-transferred,
.requests-list-size {
max-width: none;
width: 10vw;
width: 8vw;
}
.requests-list-waterfall {

Просмотреть файл

@ -135,6 +135,7 @@ const RequestListItem = createClass({
columns.get("method") && MethodColumn({ item }),
columns.get("file") && FileColumn({ item }),
columns.get("domain") && DomainColumn({ item, onSecurityIconClick }),
columns.get("remoteip") && RemoteIPColumn({ item }),
columns.get("cause") && CauseColumn({ item, onCauseBadgeClick }),
columns.get("type") && TypeColumn({ item }),
columns.get("transferred") && TransferredSizeColumn({ item }),
@ -303,6 +304,29 @@ const DomainColumn = createFactory(createClass({
}
}));
const RemoteIPColumn = createFactory(createClass({
displayName: "RemoteIP",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.item.remoteAddress !== nextProps.item.remoteAddress;
},
render() {
const { remoteAddress, remotePort } = this.props.item;
let remoteSummary = remoteAddress ? `${remoteAddress}:${remotePort}` : "";
return (
div({ className: "requests-list-subitem requests-list-remoteip" },
span({ className: "subitem-label", title: remoteSummary }, remoteSummary),
)
);
}
}));
const CauseColumn = createFactory(createClass({
displayName: "CauseColumn",

Просмотреть файл

@ -131,6 +131,11 @@ const HEADERS = [
boxName: "security-and-domain",
canFilter: true,
},
{
name: "remoteip",
canFilter: true,
filterKey: "remote-ip",
},
{
name: "cause",
canFilter: true,

Просмотреть файл

@ -31,16 +31,13 @@ function prefsMiddleware(store) {
"devtools.netmonitor.filters", JSON.stringify(filters));
break;
case TOGGLE_COLUMN:
case RESET_COLUMNS:
let hiddenColumns = [...store.getState().ui.columns]
.filter(([column, shown]) => !shown)
.map(([column, shown]) => column);
Services.prefs.setCharPref(
"devtools.netmonitor.hiddenColumns", JSON.stringify(hiddenColumns));
break;
case RESET_COLUMNS:
Services.prefs.setCharPref(
"devtools.netmonitor.hiddenColumns", JSON.stringify([]));
break;
}
return res;
};

Просмотреть файл

@ -23,6 +23,7 @@ const Columns = I.Record({
method: true,
file: true,
domain: true,
remoteip: false,
cause: true,
type: true,
transferred: true,

Просмотреть файл

@ -23,11 +23,15 @@ function configureStore() {
activeFilters[filter] = true;
});
let hiddenColumns = Services.prefs.getCharPref("devtools.netmonitor.hiddenColumns");
let inactiveColumns = JSON.parse(hiddenColumns).reduce((acc, col) => {
acc[col] = false;
return acc;
}, {});
let columns = new Columns();
let hiddenColumns =
JSON.parse(Services.prefs.getCharPref("devtools.netmonitor.hiddenColumns"));
for (let [col] of columns) {
columns = columns.withMutations((state) => {
state.set(col, !hiddenColumns.includes(col));
});
}
const initialState = {
filters: new Filters({
@ -37,7 +41,7 @@ function configureStore() {
sort: new Sort(),
timingMarkers: new TimingMarkers(),
ui: new UI({
columns: new Columns(inactiveColumns)
columns,
}),
};

Просмотреть файл

@ -125,6 +125,9 @@ function isFlagFilterMatch(item, { type, value, negative }) {
case "domain":
match = item.urlDetails.host.toLowerCase().includes(value);
break;
case "remote-ip":
match = `${item.remoteAddress}:${item.remotePort}`.toLowerCase().includes(value);
break;
case "cause":
let causeType = item.cause.type;
match = typeof causeType === "string" ?

Просмотреть файл

@ -240,6 +240,40 @@ function parseFormData(sections) {
});
}
/**
* Reduces an IP address into a number for easier sorting
*
* @param {string} ip - IP address to reduce
* @return {number} the number representing the IP address
*/
function ipToLong(ip) {
if (!ip) {
// Invalid IP
return -1;
}
let base;
let octets = ip.split(".");
if (octets.length === 4) { // IPv4
base = 10;
} else if (ip.includes(":")) { // IPv6
let numberOfZeroSections = 8 - ip.replace(/^:+|:+$/g, "").split(/:+/g).length;
octets = ip
.replace("::", `:${"0:".repeat(numberOfZeroSections)}`)
.replace(/^:|:$/g, "")
.split(":");
base = 16;
} else { // Invalid IP
return -1;
}
return octets.map((val, ix, arr) => {
return parseInt(val, base) * Math.pow(256, (arr.length - 1) - ix);
}).reduce((sum, val) => {
return sum + val;
}, 0);
}
module.exports = {
getFormDataSections,
fetchHeaders,
@ -255,4 +289,5 @@ module.exports = {
getUrlDetails,
parseQueryString,
parseFormData,
ipToLong,
};

Просмотреть файл

@ -6,6 +6,7 @@
const {
getAbbreviatedMimeType,
ipToLong,
} = require("./request-utils");
/**
@ -57,6 +58,13 @@ function domain(first, second) {
return result || waterfall(first, second);
}
function remoteip(first, second) {
const firstIP = ipToLong(first.remoteAddress);
const secondIP = ipToLong(second.remoteAddress);
const result = compareValues(firstIP, secondIP);
return result || waterfall(first, second);
}
function cause(first, second) {
const firstCause = first.cause.type;
const secondCause = second.cause.type;
@ -86,6 +94,7 @@ exports.Sorters = {
method,
file,
domain,
remoteip,
cause,
type,
transferred,

Просмотреть файл

@ -1,263 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Task } = require("devtools/shared/task");
const EventEmitter = require("devtools/shared/event-emitter");
const { Poller } = require("devtools/client/shared/poller");
const CompatUtils = require("devtools/client/performance/legacy/compatibility");
const RecordingUtils = require("devtools/shared/performance/recording-utils");
const { TimelineFront } = require("devtools/shared/fronts/timeline");
const { ProfilerFront } = require("devtools/shared/fronts/profiler");
// How often do we check the status of the profiler's circular buffer in milliseconds.
const PROFILER_CHECK_TIMER = 5000;
const TIMELINE_ACTOR_METHODS = [
"start", "stop",
];
const PROFILER_ACTOR_METHODS = [
"startProfiler", "getStartOptions", "stopProfiler",
"registerEventNotifications", "unregisterEventNotifications"
];
/**
* Constructor for a facade around an underlying ProfilerFront.
*/
function LegacyProfilerFront(target) {
this._target = target;
this._onProfilerEvent = this._onProfilerEvent.bind(this);
this._checkProfilerStatus = this._checkProfilerStatus.bind(this);
this._PROFILER_CHECK_TIMER = this._target.TEST_MOCK_PROFILER_CHECK_TIMER ||
PROFILER_CHECK_TIMER;
EventEmitter.decorate(this);
}
LegacyProfilerFront.prototype = {
EVENTS: ["console-api-profiler", "profiler-stopped"],
// Connects to the targets underlying real ProfilerFront.
connect: Task.async(function* () {
let target = this._target;
this._front = new ProfilerFront(target.client, target.form);
// Fetch and store information about the Gecko Profiler and
// server profiler.
this.traits = {};
this.traits.filterable = target.getTrait("profilerDataFilterable");
// Directly register to event notifications when connected
// to hook into `console.profile|profileEnd` calls.
yield this.registerEventNotifications({ events: this.EVENTS });
target.client.addListener("eventNotification", this._onProfilerEvent);
}),
/**
* Unregisters events for the underlying profiler actor.
*/
destroy: Task.async(function* () {
if (this._poller) {
yield this._poller.destroy();
}
yield this.unregisterEventNotifications({ events: this.EVENTS });
this._target.client.removeListener("eventNotification", this._onProfilerEvent);
yield this._front.destroy();
}),
/**
* Starts the profiler actor, if necessary.
*
* @option {number?} bufferSize
* @option {number?} sampleFrequency
*/
start: Task.async(function* (options = {}) {
// Check for poller status even if the profiler is already active --
// profiler can be activated via `console.profile` or another source, like
// the Gecko Profiler.
if (!this._poller) {
this._poller = new Poller(this._checkProfilerStatus, this._PROFILER_CHECK_TIMER,
false);
}
if (!this._poller.isPolling()) {
this._poller.on();
}
// Start the profiler only if it wasn't already active. The built-in
// nsIPerformance module will be kept recording, because it's the same instance
// for all targets and interacts with the whole platform, so we don't want
// to affect other clients by stopping (or restarting) it.
let {
isActive,
currentTime,
position,
generation,
totalSize
} = yield this.getStatus();
if (isActive) {
return { startTime: currentTime, position, generation, totalSize };
}
// Translate options from the recording model into profiler-specific
// options for the nsIProfiler
let profilerOptions = {
entries: options.bufferSize,
interval: options.sampleFrequency
? (1000 / (options.sampleFrequency * 1000))
: void 0
};
let startInfo = yield this.startProfiler(profilerOptions);
let startTime = 0;
if ("currentTime" in startInfo) {
startTime = startInfo.currentTime;
}
return { startTime, position, generation, totalSize };
}),
/**
* Indicates the end of a recording -- does not actually stop the profiler
* (stopProfiler does that), but notes that we no longer need to poll
* for buffer status.
*/
stop: Task.async(function* () {
yield this._poller.off();
}),
/**
* Wrapper around `profiler.isActive()` to take profiler status data and emit.
*/
getStatus: Task.async(function* () {
let data = yield (CompatUtils.callFrontMethod("isActive").call(this));
// If no data, the last poll for `isActive()` was wrapping up, and the target.client
// is now null, so we no longer have data, so just abort here.
if (!data) {
return undefined;
}
// If TEST_PROFILER_FILTER_STATUS defined (via array of fields), filter
// out any field from isActive, used only in tests. Used to filter out
// buffer status fields to simulate older geckos.
if (this._target.TEST_PROFILER_FILTER_STATUS) {
data = Object.keys(data).reduce((acc, prop) => {
if (this._target.TEST_PROFILER_FILTER_STATUS.indexOf(prop) === -1) {
acc[prop] = data[prop];
}
return acc;
}, {});
}
this.emit("profiler-status", data);
return data;
}),
/**
* Returns profile data from now since `startTime`.
*/
getProfile: Task.async(function* (options) {
let profilerData = yield (CompatUtils.callFrontMethod("getProfile")
.call(this, options));
// If the backend is not deduped, dedupe it ourselves, as rest of the code
// expects a deduped profile.
if (profilerData.profile.meta.version === 2) {
RecordingUtils.deflateProfile(profilerData.profile);
}
// If the backend does not support filtering by start and endtime on
// platform (< Fx40), do it on the client (much slower).
if (!this.traits.filterable) {
RecordingUtils.filterSamples(profilerData.profile, options.startTime || 0);
}
return profilerData;
}),
/**
* Invoked whenever a registered event was emitted by the profiler actor.
*
* @param object response
* The data received from the backend.
*/
_onProfilerEvent: function (_, { topic, subject, details }) {
if (topic === "console-api-profiler") {
if (subject.action === "profile") {
this.emit("console-profile-start", details);
} else if (subject.action === "profileEnd") {
this.emit("console-profile-stop", details);
}
} else if (topic === "profiler-stopped") {
this.emit("profiler-stopped");
}
},
_checkProfilerStatus: Task.async(function* () {
// Calling `getStatus()` will emit the "profiler-status" on its own
yield this.getStatus();
}),
toString: () => "[object LegacyProfilerFront]"
};
/**
* Constructor for a facade around an underlying TimelineFront.
*/
function LegacyTimelineFront(target) {
this._target = target;
EventEmitter.decorate(this);
}
LegacyTimelineFront.prototype = {
EVENTS: ["markers", "frames", "ticks"],
connect: Task.async(function* () {
let supported = yield CompatUtils.timelineActorSupported(this._target);
this._front = supported ?
new TimelineFront(this._target.client, this._target.form) :
new CompatUtils.MockTimelineFront();
this.IS_MOCK = !supported;
// Binds underlying actor events and consolidates them to a `timeline-data`
// exposed event.
this.EVENTS.forEach(type => {
let handler = this[`_on${type}`] = this._onTimelineData.bind(this, type);
this._front.on(type, handler);
});
}),
/**
* Override actor's destroy, so we can unregister listeners before
* destroying the underlying actor.
*/
destroy: Task.async(function* () {
this.EVENTS.forEach(type => this._front.off(type, this[`_on${type}`]));
yield this._front.destroy();
}),
/**
* An aggregate of all events (markers, frames, ticks) and exposes
* to PerformanceActorsConnection as a single event.
*/
_onTimelineData: function (type, ...data) {
this.emit("timeline-data", type, ...data);
},
toString: () => "[object LegacyTimelineFront]"
};
// Bind all the methods that directly proxy to the actor
PROFILER_ACTOR_METHODS.forEach(m => {
LegacyProfilerFront.prototype[m] = CompatUtils.callFrontMethod(m);
});
TIMELINE_ACTOR_METHODS.forEach(m => {
LegacyTimelineFront.prototype[m] = CompatUtils.callFrontMethod(m);
});
exports.LegacyProfilerFront = LegacyProfilerFront;
exports.LegacyTimelineFront = LegacyTimelineFront;

Просмотреть файл

@ -1,66 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const EventEmitter = require("devtools/shared/event-emitter");
/**
* A dummy front decorated with the provided methods.
*
* @param array blueprint
* A list of [funcName, retVal] describing the class.
*/
function MockFront(blueprint) {
EventEmitter.decorate(this);
for (let [funcName, retVal] of blueprint) {
this[funcName] = (x => typeof x === "function" ? x() : x).bind(this, retVal);
}
}
function MockTimelineFront() {
MockFront.call(this, [
["destroy"],
["start", 0],
["stop", 0],
]);
}
/**
* Takes a TabTarget, and checks existence of a TimelineActor on
* the server, or if TEST_MOCK_TIMELINE_ACTOR is to be used.
*
* @param {TabTarget} target
* @return {Boolean}
*/
function timelineActorSupported(target) {
// This `target` property is used only in tests to test
// instances where the timeline actor is not available.
if (target.TEST_MOCK_TIMELINE_ACTOR) {
return false;
}
return target.hasActor("timeline");
}
/**
* Returns a function to be used as a method on an "Front" in ./actors.
* Calls the underlying actor's method.
*/
function callFrontMethod(method) {
return function () {
// If there's no target or client on this actor facade,
// abort silently -- this occurs in tests when polling occurs
// after the test ends, when tests do not wait for toolbox destruction
// (which will destroy the actor facade, turning off the polling).
if (!this._target || !this._target.client) {
return undefined;
}
return this._front[method].apply(this._front, arguments);
};
}
exports.MockTimelineFront = MockTimelineFront;
exports.timelineActorSupported = timelineActorSupported;
exports.callFrontMethod = callFrontMethod;

Просмотреть файл

@ -1,484 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Task } = require("devtools/shared/task");
const Services = require("Services");
const promise = require("promise");
const { extend } = require("sdk/util/object");
const Actors = require("devtools/client/performance/legacy/actors");
const { LegacyPerformanceRecording } = require("devtools/client/performance/legacy/recording");
const { importRecording } = require("devtools/client/performance/legacy/recording");
const { normalizePerformanceFeatures } = require("devtools/shared/performance/recording-utils");
const flags = require("devtools/shared/flags");
const { getDeviceFront } = require("devtools/shared/device/device");
const { getSystemInfo } = require("devtools/shared/system");
const events = require("sdk/event/core");
const { EventTarget } = require("sdk/event/target");
const { Class } = require("sdk/core/heritage");
/**
* A connection to underlying actors (profiler, framerate, etc.)
* shared by all tools in a target.
*/
const LegacyPerformanceFront = Class({
extends: EventTarget,
LEGACY_FRONT: true,
traits: {
features: {
withMarkers: true,
withTicks: true,
withMemory: false,
withFrames: false,
withGCEvents: false,
withDocLoadingEvents: false,
withAllocations: false,
},
},
initialize: function (target) {
let { form, client } = target;
this._target = target;
this._form = form;
this._client = client;
this._pendingConsoleRecordings = [];
this._sitesPullTimeout = 0;
this._recordings = [];
this._pipeToFront = this._pipeToFront.bind(this);
this._onTimelineData = this._onTimelineData.bind(this);
this._onConsoleProfileStart = this._onConsoleProfileStart.bind(this);
this._onConsoleProfileStop = this._onConsoleProfileStop.bind(this);
this._onProfilerStatus = this._onProfilerStatus.bind(this);
this._onProfilerUnexpectedlyStopped = this._onProfilerUnexpectedlyStopped.bind(this);
},
/**
* Initializes a connection to the profiler and other miscellaneous actors.
* If in the process of opening, or already open, nothing happens.
*
* @return object
* A promise that is resolved once the connection is established.
*/
connect: Task.async(function* () {
if (this._connecting) {
return this._connecting.promise;
}
// Create a promise that gets resolved upon connecting, so that
// other attempts to open the connection use the same resolution promise
this._connecting = promise.defer();
// Sets `this._profiler`, `this._timeline`.
// Only initialize the timeline fronts if the respective actors
// are available. Older Gecko versions don't have existing implementations,
// in which case all the methods we need can be easily mocked.
yield this._connectActors();
yield this._registerListeners();
this._connecting.resolve();
return this._connecting.promise;
}),
/**
* Destroys this connection.
*/
destroy: Task.async(function* () {
if (this._connecting) {
yield this._connecting.promise;
} else {
return;
}
yield this._unregisterListeners();
yield this._disconnectActors();
this._connecting = null;
this._profiler = null;
this._timeline = null;
this._client = null;
this._form = null;
this._target = this._target;
}),
/**
* Initializes fronts and connects to the underlying actors using the facades
* found in ./actors.js.
*/
_connectActors: Task.async(function* () {
this._profiler = new Actors.LegacyProfilerFront(this._target);
this._timeline = new Actors.LegacyTimelineFront(this._target);
yield promise.all([
this._profiler.connect(),
this._timeline.connect()
]);
// If mocked timeline, update the traits
this.traits.features.withMarkers = !this._timeline.IS_MOCK;
this.traits.features.withTicks = !this._timeline.IS_MOCK;
}),
/**
* Registers listeners on events from the underlying
* actors, so the connection can handle them.
*/
_registerListeners: function () {
this._timeline.on("timeline-data", this._onTimelineData);
this._profiler.on("console-profile-start", this._onConsoleProfileStart);
this._profiler.on("console-profile-stop", this._onConsoleProfileStop);
this._profiler.on("profiler-stopped", this._onProfilerUnexpectedlyStopped);
this._profiler.on("profiler-status", this._onProfilerStatus);
},
/**
* Unregisters listeners on events on the underlying actors.
*/
_unregisterListeners: function () {
this._timeline.off("timeline-data", this._onTimelineData);
this._profiler.off("console-profile-start", this._onConsoleProfileStart);
this._profiler.off("console-profile-stop", this._onConsoleProfileStop);
this._profiler.off("profiler-stopped", this._onProfilerUnexpectedlyStopped);
this._profiler.off("profiler-status", this._onProfilerStatus);
},
/**
* Closes the connections to non-profiler actors.
*/
_disconnectActors: Task.async(function* () {
yield promise.all([
this._profiler.destroy(),
this._timeline.destroy(),
]);
}),
/**
* Invoked whenever `console.profile` is called.
*
* @param string profileLabel
* The provided string argument if available; undefined otherwise.
* @param number currentTime
* The time (in milliseconds) when the call was made, relative to when
* the nsIProfiler module was started.
*/
_onConsoleProfileStart: Task.async(function* (_, { profileLabel,
currentTime: startTime }) {
let recordings = this._recordings;
// Abort if a profile with this label already exists.
if (recordings.find(e => e.getLabel() === profileLabel)) {
return;
}
events.emit(this, "console-profile-start");
yield this.startRecording(extend({}, getLegacyPerformanceRecordingPrefs(), {
console: true,
label: profileLabel
}));
}),
/**
* Invoked whenever `console.profileEnd` is called.
*
* @param string profileLabel
* The provided string argument if available; undefined otherwise.
* @param number currentTime
* The time (in milliseconds) when the call was made, relative to when
* the nsIProfiler module was started.
*/
_onConsoleProfileStop: Task.async(function* (_, data) {
// If no data, abort; can occur if profiler isn't running and we get a surprise
// call to console.profileEnd()
if (!data) {
return;
}
let { profileLabel } = data;
let pending = this._recordings.filter(r => r.isConsole() && r.isRecording());
if (pending.length === 0) {
return;
}
let model;
// Try to find the corresponding `console.profile` call if
// a label was used in profileEnd(). If no matches, abort.
if (profileLabel) {
model = pending.find(e => e.getLabel() === profileLabel);
} else {
// If no label supplied, pop off the most recent pending console recording
model = pending[pending.length - 1];
}
// If `profileEnd()` was called with a label, and there are no matching
// sessions, abort.
if (!model) {
console.error(
"console.profileEnd() called with label that does not match a recording.");
return;
}
yield this.stopRecording(model);
}),
/**
* TODO handle bug 1144438
*/
_onProfilerUnexpectedlyStopped: function () {
console.error("Profiler unexpectedly stopped.", arguments);
},
/**
* Called whenever there is timeline data of any of the following types:
* - markers
* - frames
* - ticks
*
* Populate our internal store of recordings for all currently recording sessions.
*/
_onTimelineData: function (_, ...data) {
this._recordings.forEach(e => e._addTimelineData.apply(e, data));
events.emit(this, "timeline-data", ...data);
},
/**
* Called whenever the underlying profiler polls its current status.
*/
_onProfilerStatus: function (_, data) {
// If no data emitted (whether from an older actor being destroyed
// from a previous test, or the server does not support it), just ignore.
if (!data || data.position === void 0) {
return;
}
this._currentBufferStatus = data;
events.emit(this, "profiler-status", data);
},
/**
* Begins a recording session
*
* @param object options
* An options object to pass to the actors. Supported properties are
* `withTicks`, `withMemory` and `withAllocations`, `probability`, and
* `maxLogLength`.
* @return object
* A promise that is resolved once recording has started.
*/
startRecording: Task.async(function* (options = {}) {
let model = new LegacyPerformanceRecording(
normalizePerformanceFeatures(options, this.traits.features));
// All actors are started asynchronously over the remote debugging protocol.
// Get the corresponding start times from each one of them.
// The timeline actors are target-dependent, so start those as well,
// even though these are mocked in older Geckos (FF < 35)
let profilerStart = this._profiler.start(options);
let timelineStart = this._timeline.start(options);
let { startTime, position, generation, totalSize } = yield profilerStart;
let timelineStartTime = yield timelineStart;
let data = {
profilerStartTime: startTime, timelineStartTime,
generation, position, totalSize
};
// Signify to the model that the recording has started,
// populate with data and store the recording model here.
model._populate(data);
this._recordings.push(model);
events.emit(this, "recording-started", model);
return model;
}),
/**
* Manually ends the recording session for the corresponding LegacyPerformanceRecording.
*
* @param LegacyPerformanceRecording model
* The corresponding LegacyPerformanceRecording that belongs to the recording
* session wished to stop.
* @return LegacyPerformanceRecording
* Returns the same model, populated with the profiling data.
*/
stopRecording: Task.async(function* (model) {
// If model isn't in the LegacyPerformanceFront internal store,
// then do nothing.
if (this._recordings.indexOf(model) === -1) {
return undefined;
}
// Flag the recording as no longer recording, so that `model.isRecording()`
// is false. Do this before we fetch all the data, and then subsequently
// the recording can be considered "completed".
let endTime = Date.now();
model._onStoppingRecording(endTime);
events.emit(this, "recording-stopping", model);
// Currently there are two ways profiles stop recording. Either manually in the
// performance tool, or via console.profileEnd. Once a recording is done,
// we want to deliver the model to the performance tool (either as a return
// from the LegacyPerformanceFront or via `console-profile-stop` event) and then
// remove it from the internal store.
//
// In the case where a console.profile is generated via the console (so the tools are
// open), we initialize the Performance tool so it can listen to those events.
this._recordings.splice(this._recordings.indexOf(model), 1);
let config = model.getConfiguration();
let startTime = model._getProfilerStartTime();
let profilerData = yield this._profiler.getProfile({ startTime });
let timelineEndTime = Date.now();
// Only if there are no more sessions recording do we stop
// the underlying timeline actors. If we're still recording,
// juse use Date.now() for the timeline end times, as those
// are only used in tests.
if (!this.isRecording()) {
// This doesn't stop the profiler, just turns off polling for
// events, and also turns off events on timeline actors.
yield this._profiler.stop();
timelineEndTime = yield this._timeline.stop(config);
}
let form = yield this._client.listTabs();
let systemHost = yield getDeviceFront(this._client, form).getDescription();
let systemClient = yield getSystemInfo();
// Set the results on the LegacyPerformanceRecording itself.
model._onStopRecording({
// Data available only at the end of a recording.
profile: profilerData.profile,
// End times for all the actors.
profilerEndTime: profilerData.currentTime,
timelineEndTime: timelineEndTime,
systemHost,
systemClient,
});
events.emit(this, "recording-stopped", model);
return model;
}),
/**
* Creates a recording object when given a nsILocalFile.
*
* @param {nsILocalFile} file
* The file to import the data from.
* @return {Promise<LegacyPerformanceRecording>}
*/
importRecording: function (file) {
return importRecording(file);
},
/**
* Checks all currently stored recording models and returns a boolean
* if there is a session currently being recorded.
*
* @return Boolean
*/
isRecording: function () {
return this._recordings.some(recording => recording.isRecording());
},
/**
* Pass in a PerformanceRecording and get a normalized value from 0 to 1 of how much
* of this recording's lifetime remains without being overwritten.
*
* @param {PerformanceRecording} recording
* @return {number?}
*/
getBufferUsageForRecording: function (recording) {
if (!recording.isRecording() || !this._currentBufferStatus) {
return null;
}
let {
position: currentPosition,
totalSize,
generation: currentGeneration
} = this._currentBufferStatus;
let {
position: origPosition,
generation: origGeneration
} = recording.getStartingBufferStatus();
let normalizedCurrent = (totalSize * (currentGeneration - origGeneration))
+ currentPosition;
let percent = (normalizedCurrent - origPosition) / totalSize;
return percent > 1 ? 1 : percent;
},
/**
* Returns the configurations set on underlying components, used in tests.
* Returns an object with `probability`, `maxLogLength` for allocations, and
* `entries` and `interval` for profiler.
*
* @return {object}
*/
getConfiguration: Task.async(function* () {
let profilerConfig = yield this._request("profiler", "getStartOptions");
return profilerConfig;
}),
/**
* An event from an underlying actor that we just want
* to pipe to the front itself.
*/
_pipeToFront: function (eventName, ...args) {
events.emit(this, eventName, ...args);
},
/**
* Helper method to interface with the underlying actors directly.
* Used only in tests.
*/
_request: function (actorName, method, ...args) {
if (!flags.testing) {
throw new Error("LegacyPerformanceFront._request may only be used in tests.");
}
let actor = this[`_${actorName}`];
return actor[method].apply(actor, args);
},
/**
* Sets how often the "profiler-status" event should be emitted.
* Used in tests.
*/
setProfilerStatusInterval: function (n) {
if (this._profiler._poller) {
this._profiler._poller._wait = n;
}
this._profiler._PROFILER_CHECK_TIMER = n;
},
toString: () => "[object LegacyPerformanceFront]"
});
/**
* Creates an object of configurations based off of preferences for a
* LegacyPerformanceRecording.
*/
function getLegacyPerformanceRecordingPrefs() {
return {
withMarkers: true,
withMemory: Services.prefs.getBoolPref(
"devtools.performance.ui.enable-memory"),
withTicks: Services.prefs.getBoolPref(
"devtools.performance.ui.enable-framerate"),
withAllocations: Services.prefs.getBoolPref(
"devtools.performance.ui.enable-allocations"),
allocationsSampleProbability: +Services.prefs.getCharPref(
"devtools.performance.memory.sample-probability"),
allocationsMaxLogLength: Services.prefs.getIntPref(
"devtools.performance.memory.max-log-length")
};
}
exports.LegacyPerformanceFront = LegacyPerformanceFront;

Просмотреть файл

@ -1,12 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'actors.js',
'compatibility.js',
'front.js',
'recording.js',
)

Просмотреть файл

@ -1,174 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Task } = require("devtools/shared/task");
const PerformanceIO = require("devtools/client/performance/modules/io");
const RecordingUtils = require("devtools/shared/performance/recording-utils");
const { PerformanceRecordingCommon } = require("devtools/shared/performance/recording-common");
const { merge } = require("sdk/util/object");
/**
* Model for a wholistic profile, containing the duration, profiling data,
* frames data, timeline (marker, tick, memory) data, and methods to mark
* a recording as 'in progress' or 'finished'.
*/
const LegacyPerformanceRecording = function (options = {}) {
this._label = options.label || "";
this._console = options.console || false;
this._configuration = {
withMarkers: options.withMarkers || false,
withTicks: options.withTicks || false,
withMemory: options.withMemory || false,
withAllocations: options.withAllocations || false,
allocationsSampleProbability: options.allocationsSampleProbability || 0,
allocationsMaxLogLength: options.allocationsMaxLogLength || 0,
bufferSize: options.bufferSize || 0,
sampleFrequency: options.sampleFrequency || 1
};
};
LegacyPerformanceRecording.prototype = merge({
_profilerStartTime: 0,
_timelineStartTime: 0,
_memoryStartTime: 0,
/**
* Saves the current recording to a file.
*
* @param nsILocalFile file
* The file to stream the data into.
*/
exportRecording: Task.async(function* (file) {
let recordingData = this.getAllData();
yield PerformanceIO.saveRecordingToFile(recordingData, file);
}),
/**
* Sets up the instance with data from the PerformanceFront when
* starting a recording. Should only be called by PerformanceFront.
*/
_populate: function (info) {
// Times must come from the actor in order to be self-consistent.
// However, we also want to update the view with the elapsed time
// even when the actor is not generating data. To do this we get
// the local time and use it to compute a reasonable elapsed time.
this._localStartTime = Date.now();
this._profilerStartTime = info.profilerStartTime;
this._timelineStartTime = info.timelineStartTime;
this._memoryStartTime = info.memoryStartTime;
this._startingBufferStatus = {
position: info.position,
totalSize: info.totalSize,
generation: info.generation
};
this._recording = true;
this._systemHost = {};
this._systemClient = {};
this._markers = [];
this._frames = [];
this._memory = [];
this._ticks = [];
this._allocations = { sites: [], timestamps: [], frames: [], sizes: [] };
},
/**
* Called when the signal was sent to the front to no longer record more
* data, and begin fetching the data. There's some delay during fetching,
* even though the recording is stopped, the model is not yet completed until
* all the data is fetched.
*/
_onStoppingRecording: function (endTime) {
this._duration = endTime - this._localStartTime;
this._recording = false;
},
/**
* Sets results available from stopping a recording from PerformanceFront.
* Should only be called by PerformanceFront.
*/
_onStopRecording: Task.async(function* ({ profilerEndTime, profile, systemClient,
systemHost }) {
// Update the duration with the accurate profilerEndTime, so we don't have
// samples outside of the approximate duration set in `_onStoppingRecording`.
this._duration = profilerEndTime - this._profilerStartTime;
this._profile = profile;
this._completed = true;
// We filter out all samples that fall out of current profile's range
// since the profiler is continuously running. Because of this, sample
// times are not guaranteed to have a zero epoch, so offset the
// timestamps.
RecordingUtils.offsetSampleTimes(this._profile, this._profilerStartTime);
// Markers need to be sorted ascending by time, to be properly displayed
// in a waterfall view.
this._markers = this._markers.sort((a, b) => (a.start > b.start));
this._systemHost = systemHost;
this._systemClient = systemClient;
}),
/**
* Gets the profile's start time.
* @return number
*/
_getProfilerStartTime: function () {
return this._profilerStartTime;
},
/**
* Fired whenever the PerformanceFront emits markers, memory or ticks.
*/
_addTimelineData: function (eventName, ...data) {
// If this model isn't currently recording,
// ignore the timeline data.
if (!this.isRecording()) {
return;
}
let config = this.getConfiguration();
switch (eventName) {
// Accumulate timeline markers into an array. Furthermore, the timestamps
// do not have a zero epoch, so offset all of them by the start time.
case "markers": {
if (!config.withMarkers) {
break;
}
let [markers] = data;
RecordingUtils.offsetMarkerTimes(markers, this._timelineStartTime);
RecordingUtils.pushAll(this._markers, markers);
break;
}
// Accumulate stack frames into an array.
case "frames": {
if (!config.withMarkers) {
break;
}
let [, frames] = data;
RecordingUtils.pushAll(this._frames, frames);
break;
}
// Save the accumulated refresh driver ticks.
case "ticks": {
if (!config.withTicks) {
break;
}
let [, timestamps] = data;
this._ticks = timestamps;
break;
}
}
},
toString: () => "[object LegacyPerformanceRecording]"
}, PerformanceRecordingCommon);
exports.LegacyPerformanceRecording = LegacyPerformanceRecording;

Просмотреть файл

@ -5,7 +5,6 @@
DIRS += [
'components',
'legacy',
'modules',
'test',
]
@ -18,5 +17,5 @@ DevToolsModules(
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Performance Tools (Profiler/Timeline)')
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Performance Tools (Profiler/Timeline)')

Просмотреть файл

@ -235,11 +235,6 @@ var PerformanceController = {
* @return Promise:boolean
*/
canCurrentlyRecord: Task.async(function* () {
// If we're testing the legacy front, the performance actor will exist,
// with `canCurrentlyRecord` method; this ensures we test the legacy path.
if (gFront.LEGACY_FRONT) {
return true;
}
let hasActor = yield gTarget.hasActor("performance");
if (!hasActor) {
return true;

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше