Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-09-08 12:42:45 +02:00
Родитель 56ceb6506c f4a16a723a
Коммит e3c2ddf887
114 изменённых файлов: 2486 добавлений и 1285 удалений

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

@ -3714,11 +3714,14 @@ const BrowserSearch = {
return document.getElementById("searchbar");
},
get searchEnginesURL() {
return formatURL("browser.search.searchEnginesURL", true);
},
loadAddEngines: function BrowserSearch_loadAddEngines() {
var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
var where = newWindowPref == 3 ? "tab" : "window";
var searchEnginesURL = formatURL("browser.search.searchEnginesURL", true);
openUILinkIn(searchEnginesURL, where);
openUILinkIn(this.searchEnginesURL, where);
},
get _isExtendedTelemetryEnabled() {

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

@ -603,8 +603,9 @@ nsContextMenu.prototype = {
}
const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
if (aNode.namespaceURI == xulNS ||
aNode.nodeType == Node.DOCUMENT_NODE) {
if (aNode.nodeType == Node.DOCUMENT_NODE ||
// Not display on XUL element but relax for <label class="text-link">
(aNode.namespaceURI == xulNS && !isXULTextLinkLabel(aNode))) {
this.shouldDisplay = false;
return;
}
@ -798,7 +799,8 @@ nsContextMenu.prototype = {
if (!this.onLink &&
// Be consistent with what hrefAndLinkNodeForClickEvent
// does in browser.js
((elem instanceof HTMLAnchorElement && elem.href) ||
(isXULTextLinkLabel(elem) ||
(elem instanceof HTMLAnchorElement && elem.href) ||
(elem instanceof HTMLAreaElement && elem.href) ||
elem instanceof HTMLLinkElement ||
elem.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")) {
@ -898,6 +900,13 @@ nsContextMenu.prototype = {
this.showItem("spell-separator", canSpell);
}
}
function isXULTextLinkLabel(node) {
return node.namespaceURI == xulNS &&
node.tagName == "label" &&
node.classList.contains('text-link') &&
node.href;
}
},
// Returns the computed style attribute for the given element.

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

@ -77,14 +77,22 @@ var gSyncUtils = {
this._openLink(Weave.Service.pwResetURL);
},
openToS: function () {
get tosURL() {
let root = this.fxAccountsEnabled ? "fxa." : "";
this._openLink(Weave.Svc.Prefs.get(root + "termsURL"));
return Weave.Svc.Prefs.get(root + "termsURL");
},
openToS: function () {
this._openLink(this.tosURL);
},
get privacyPolicyURL() {
let root = this.fxAccountsEnabled ? "fxa." : "";
return Weave.Svc.Prefs.get(root + "privacyURL");
},
openPrivacyPolicy: function () {
let root = this.fxAccountsEnabled ? "fxa." : "";
this._openLink(Weave.Svc.Prefs.get(root + "privacyURL"));
this._openLink(this.privacyPolicyURL);
},
/**

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

@ -3842,6 +3842,9 @@
spinnerDisplayed: function () {
this.assert(!this.spinnerTab);
TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
// We have a second, similar probe for capturing recordings of
// when the spinner is displayed for very long periods.
TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", window);
this.addMarker("AsyncTabSwitch:SpinnerShown");
},
@ -3850,6 +3853,7 @@
this.log("DEBUG: spinner time = " +
TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window));
TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", window);
this.addMarker("AsyncTabSwitch:SpinnerHidden");
// we do not get a onPaint after displaying the spinner
this.maybeFinishTabSwitch();

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

@ -87,6 +87,7 @@ support-files =
searchSuggestionEngine2.xml
subtst_contextmenu.html
subtst_contextmenu_input.html
subtst_contextmenu_xul.xul
test-mixedcontent-securityerrors.html
test_bug435035.html
test_bug462673.html

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

@ -10,18 +10,48 @@ let LOGIN_FILL_ITEMS = [
"fill-login-saved-passwords", true
], null,
];
let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
let hasContainers = Services.prefs.getBoolPref("privacy.userContext.enabled");
add_task(function* test_setup() {
const example_base = "http://example.com/browser/browser/base/content/test/general/";
const url = example_base + "subtst_contextmenu.html";
const example_base = "http://example.com/browser/browser/base/content/test/general/";
const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
Services.scriptloader.loadSubScript(chrome_base + "contextmenu_common.js", this);
// Below are test cases for XUL element
add_task(function* test_xul_text_link_label() {
let url = chrome_base + "subtst_contextmenu_xul.xul";
yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
const contextmenu_common = chrome_base + "contextmenu_common.js";
Services.scriptloader.loadSubScript(contextmenu_common, this);
yield test_contextmenu("#test-xul-text-link-label",
["context-openlinkintab", true,
...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
// We need a blank entry here because the containers submenu is
// dynamically generated with no ids.
...(hasContainers ? ["", null] : []),
"context-openlink", true,
"context-openlinkprivate", true,
"---", null,
"context-bookmarklink", true,
"context-savelink", true,
...(hasPocket ? ["context-savelinktopocket", true] : []),
"context-copylink", true,
"context-searchselect", true
]
);
// Clean up so won't affect HTML element test cases
lastElementSelector = null;
gBrowser.removeCurrentTab();
});
// Below are test cases for HTML element
add_task(function* test_setup_html() {
let url = example_base + "subtst_contextmenu.html";
yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
let doc = content.document;
@ -926,7 +956,7 @@ add_task(function* test_link_sendlinktodevice() {
restoreRemoteClients(oldGetter);
});
add_task(function* test_cleanup() {
add_task(function* test_cleanup_html() {
gBrowser.removeCurrentTab();
});

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

@ -184,14 +184,17 @@ add_task(function* testPermissionIcons() {
SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.BLOCK);
SitePermissions.set(gBrowser.currentURI, "microphone", SitePermissions.SESSION);
let geoIcon = gIdentityHandler._identityBox.querySelector("[data-permission-id='geo']");
let geoIcon = gIdentityHandler._identityBox
.querySelector(".blocked-permission-icon[data-permission-id='geo']");
ok(geoIcon.hasAttribute("showing"), "blocked permission icon is shown");
let cameraIcon = gIdentityHandler._identityBox.querySelector("[data-permission-id='camera']");
let cameraIcon = gIdentityHandler._identityBox
.querySelector(".blocked-permission-icon[data-permission-id='camera']");
ok(!cameraIcon.hasAttribute("showing"),
"allowed permission icon is not shown");
let microphoneIcon = gIdentityHandler._identityBox.querySelector("[data-permission-id='microphone']");
let microphoneIcon = gIdentityHandler._identityBox
.querySelector(".blocked-permission-icon[data-permission-id='microphone']");
ok(!microphoneIcon.hasAttribute("showing"),
"allowed permission icon is not shown");

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

@ -0,0 +1,9 @@
<?xml version="1.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/. -->
<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<label id="test-xul-text-link-label" class="text-link" value="XUL text-link label" href="https://www.mozilla.com"/>
</page>

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

@ -825,13 +825,16 @@ function openNewWindowWith(aURL, aDocument, aPostData, aAllowThirdPartyFixup,
});
}
// aCalledFromModal is optional
function openHelpLink(aHelpTopic, aCalledFromModal, aWhere) {
function getHelpLinkURL(aHelpTopic) {
var url = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
.getService(Components.interfaces.nsIURLFormatter)
.formatURLPref("app.support.baseURL");
url += aHelpTopic;
return url + aHelpTopic;
}
// aCalledFromModal is optional
function openHelpLink(aHelpTopic, aCalledFromModal, aWhere) {
var url = getHelpLinkURL(aHelpTopic);
var where = aWhere;
if (!aWhere)
where = aCalledFromModal ? "window" : "tab";

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

@ -120,8 +120,7 @@
hidden="true"
data-category="paneAdvanced">
<label class="header-name" flex="1">&paneAdvanced.title;</label>
<button class="help-button"
aria-label="&helpButton.label;"/>
<html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
</hbox>
<tabbox id="advancedPrefs"

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

@ -58,13 +58,12 @@
<key key="&focusSearch2.key;" modifiers="accel" id="focusSearch2" oncommand=";"/>
</keyset>
<hbox id="header-application"
<hbox id="header-applications"
class="header"
hidden="true"
data-category="paneApplications">
<label class="header-name" flex="1">&paneApplications.title;</label>
<button class="help-button"
aria-label="&helpButton.label;"/>
<html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
</hbox>
<vbox id="applicationsContent"

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

@ -37,8 +37,7 @@
hidden="true"
data-category="paneContent">
<label class="header-name" flex="1">&paneContent.title;</label>
<button class="help-button"
aria-label="&helpButton.label;"/>
<html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
</hbox>
<groupbox id="drmGroup" data-category="paneContent" hidden="true">

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

@ -115,8 +115,7 @@
hidden="true"
data-category="paneGeneral">
<label class="header-name" flex="1">&paneGeneral.title;</label>
<button class="help-button"
aria-label="&helpButton.label;"/>
<html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
</hbox>
<!-- Startup -->

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

@ -78,9 +78,13 @@ function init_all() {
});
document.dispatchEvent(initFinished);
let helpCmds = document.querySelectorAll(".help-button");
for (let helpCmd of helpCmds)
helpCmd.addEventListener("command", helpButtonCommand);
categories = categories.querySelectorAll("richlistitem.category");
for (let category of categories) {
let name = internalPrefCategoryNameToFriendlyName(category.value);
let helpSelector = `#header-${name} > .help-button`;
let helpButton = document.querySelector(helpSelector);
helpButton.setAttribute("href", getHelpLinkURL(category.getAttribute("helpTopic")));
}
// Wait until initialization of all preferences are complete before
// notifying observers that the UI is now ready.

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

@ -80,8 +80,7 @@
hidden="true"
data-category="panePrivacy">
<label class="header-name" flex="1">&panePrivacy.title;</label>
<button class="help-button"
aria-label="&helpButton.label;"/>
<html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
</hbox>
<!-- Tracking -->

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

@ -36,6 +36,11 @@ var gSearchPane = {
document.getElementById("engineList").view = gEngineView;
this.buildDefaultEngineDropDown();
let addEnginesLink = document.getElementById("addEngines");
let searchEnginesURL = Services.wm.getMostRecentWindow('navigator:browser')
.BrowserSearch.searchEnginesURL;
addEnginesLink.setAttribute("href", searchEnginesURL);
window.addEventListener("click", this, false);
window.addEventListener("command", this, false);
window.addEventListener("dragstart", this, false);
@ -123,10 +128,6 @@ var gSearchPane = {
engineList.blur();
}
}
if (aEvent.target.id == "addEngines" && aEvent.button == 0) {
Services.wm.getMostRecentWindow('navigator:browser')
.BrowserSearch.loadAddEngines();
}
break;
case "command":
switch (aEvent.target.id) {

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

@ -28,8 +28,7 @@
hidden="true"
data-category="paneSearch">
<label class="header-name" flex="1">&paneSearch.title;</label>
<button class="help-button"
aria-label="&helpButton.label;"/>
<html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
</hbox>
<!-- Default Search Engine -->

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

@ -53,8 +53,7 @@
hidden="true"
data-category="paneSecurity">
<label class="header-name" flex="1">&paneSecurity.title;</label>
<button class="help-button"
aria-label="&helpButton.label;"/>
<html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
</hbox>
<!-- addons, forgery (phishing) UI -->

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

@ -142,8 +142,19 @@ var gSyncPane = {
let url = Services.prefs.getCharPref("identity.mobilepromo.android") + "sync-preferences";
document.getElementById("fxaMobilePromo-android").setAttribute("href", url);
document.getElementById("fxaMobilePromo-android-hasFxaAccount").setAttribute("href", url);
url = Services.prefs.getCharPref("identity.mobilepromo.ios") + "sync-preferences";
document.getElementById("fxaMobilePromo-ios").setAttribute("href", url);
document.getElementById("fxaMobilePromo-ios-hasFxaAccount").setAttribute("href", url);
document.getElementById("tosPP-small-ToS").setAttribute("href", gSyncUtils.tosURL);
document.getElementById("tosPP-normal-ToS").setAttribute("href", gSyncUtils.tosURL);
document.getElementById("tosPP-small-PP").setAttribute("href", gSyncUtils.privacyPolicyURL);
document.getElementById("tosPP-normal-PP").setAttribute("href", gSyncUtils.privacyPolicyURL);
fxAccounts.promiseAccountsManageURI(this._getEntryPoint()).then(url => {
document.getElementById("verifiedManage").setAttribute("href", url);
});
this.updateWeavePrefs();
@ -240,8 +251,6 @@ var gSyncPane = {
gSyncPane.startOver(true);
return false;
});
setEventListener("tosPP-normal-ToS", "click", gSyncPane.openToS);
setEventListener("tosPP-normal-PP", "click", gSyncPane.openPrivacyPolicy);
setEventListener("loginErrorUpdatePass", "click", function () {
gSyncPane.updatePass();
return false;
@ -276,8 +285,6 @@ var gSyncPane = {
setEventListener("rejectUnlinkFxaAccount", "command", function () {
gSyncPane.unlinkFirefoxAccount(true);
});
setEventListener("tosPP-small-ToS", "click", gSyncPane.openToS);
setEventListener("tosPP-small-PP", "click", gSyncPane.openPrivacyPolicy);
setEventListener("fxaSyncComputerName", "keypress", function (e) {
if (e.keyCode == KeyEvent.DOM_VK_RETURN) {
document.getElementById("fxaSaveChangeDeviceName").click();
@ -523,16 +530,6 @@ var gSyncPane = {
browser.loadURI(url);
},
openPrivacyPolicy: function(aEvent) {
aEvent.stopPropagation();
gSyncUtils.openPrivacyPolicy();
},
openToS: function(aEvent) {
aEvent.stopPropagation();
gSyncUtils.openToS();
},
signUp: function() {
this._openAboutAccounts("signup");
},

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

@ -35,8 +35,7 @@
hidden="true"
data-category="paneSync">
<label class="header-name" flex="1">&paneSync.title;</label>
<button class="help-button"
aria-label="&helpButton.label;"/>
<html:a class="help-button text-link" target="_blank" aria-label="&helpButton.label;"></html:a>
</hbox>
<deck id="weavePrefsDeck" data-category="paneSync" hidden="true">
@ -234,7 +233,6 @@
<hbox class="fxaAccountBoxButtons" align="center">
<button id="fxaUnlinkButton" label="&disconnect.label;"/>
<label id="verifiedManage" class="text-link"
onclick="gSyncPane.openManageFirefoxAccount(event);"
onkeypress="gSyncPane.openManageFirefoxAccount(event);"><!--
-->&verifiedManage.label;</label>
</hbox>
@ -340,12 +338,10 @@
</groupbox>
<label class="fxaMobilePromo">
&mobilePromo3.start;<!-- We put these comments to avoid inserting white spaces
--><label class="androidLink text-link"
href="https://www.mozilla.org/firefox/android/?utm_source=firefox-browser&amp;utm_medium=firefox-browser&amp;utm_campaign=sync-preferences"><!--
--><label class="androidLink text-link" id="fxaMobilePromo-android-hasFxaAccount"><!--
-->&mobilePromo3.androidLink;</label><!--
-->&mobilePromo3.iOSBefore;<!--
--><label class="iOSLink text-link"
href="https://www.mozilla.org/firefox/ios/?utm_source=firefox-browser&amp;utm_medium=firefox-browser&amp;utm_campaign=sync-preferences"><!--
--><label class="iOSLink text-link" id="fxaMobilePromo-ios-hasFxaAccount"><!--
-->&mobilePromo3.iOSLink;</label><!--
-->&mobilePromo3.end;
</label>

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

До

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

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

@ -56,7 +56,6 @@ browser.jar:
skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png)
skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
skin/classic/browser/downloads/download-summary.png (downloads/download-summary.png)
* skin/classic/browser/downloads/downloads.css (downloads/downloads.css)
skin/classic/browser/feeds/feedIcon.png (feeds/feedIcon.png)
skin/classic/browser/feeds/feedIcon16.png (feeds/feedIcon16.png)

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

До

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

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

До

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

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

@ -80,8 +80,6 @@ browser.jar:
skin/classic/browser/downloads/download-notification-finish@2x.png (downloads/download-notification-finish@2x.png)
skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
skin/classic/browser/downloads/download-notification-start@2x.png (downloads/download-notification-start@2x.png)
skin/classic/browser/downloads/download-summary.png (downloads/download-summary.png)
skin/classic/browser/downloads/download-summary@2x.png (downloads/download-summary@2x.png)
* skin/classic/browser/downloads/downloads.css (downloads/downloads.css)
skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)

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

@ -0,0 +1,11 @@
<?xml version="1.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/. -->
<svg xmlns="http://www.w3.org/2000/svg"
width="32" height="32" viewBox="0 0 32 32">
<path fill="#fff" d="M26.2,31.4H6.8c-1.8,0-3.4-1.5-3.4-3.3V3.8c0-1.8,1.6-3.3,3.4-3.3H23l5.6,6.4V28C28.7,29.9,28,31.4,26.2,31.4z" />
<path fill="#939393" d="M26.2,31.9H6.8c-2,0-3.8-1.7-3.8-3.8V3.8C3,1.7,4.8,0,6.8,0h16.4L29,6.7v21.4C29,30.2,28.2,31.9,26.2,31.9z M6.8,1C5.2,1,4,2.3,4,3.8V28c0,1.6,1.4,3,2.9,3h19.2c1.6,0,2-1.5,2-3V7.1L22.7,1C22.7,1,6.8,1,6.8,1z" />
<path fill="#4c4c4c" d="M22.5,18.2L17,23.6c-0.3,0.3-0.6,0.4-1.1,0.4c-0.3,0-0.8-0.1-1.1-0.4l-5.5-5.4c-0.5-0.5-0.4-1.1,0.4-1.1H13 v-5.9c0-0.5,0.4-1,1-1h3.9c0.5,0,1,0.4,1,1v5.9h3.1C22.8,17.2,23,17.6,22.5,18.2z" />
<polygon fill="#939393" points="27,9 21,9 21,1.9 22,1.9 22,8 27,8" />
</svg>

После

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

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

@ -150,17 +150,9 @@ toolbarseparator.downloadsDropmarkerSplitter {
}
#downloadsSummary > .downloadTypeIcon {
list-style-image: url("chrome://browser/skin/downloads/download-summary.png");
list-style-image: url("chrome://browser/skin/downloads/download-summary.svg");
}
%ifdef XP_MACOSX
@media (min-resolution: 2dppx) {
#downloadsSummary > .downloadTypeIcon {
list-style-image: url("chrome://browser/skin/downloads/download-summary@2x.png");
}
}
%endif
#downloadsSummaryDescription {
color: -moz-nativehyperlinktext;
}

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

@ -102,6 +102,15 @@ treecol {
}
/* header */
.header {
display: flex;
align-items: center;
justify-content: space-between;
}
.header[hidden=true] {
display: none;
}
#header-advanced {
border-bottom: none;

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

@ -55,6 +55,7 @@
skin/classic/browser/downloads/contentAreaDownloadsView.css (../shared/downloads/contentAreaDownloadsView.css)
skin/classic/browser/downloads/download-blocked.svg (../shared/downloads/download-blocked.svg)
skin/classic/browser/downloads/menubutton-dropmarker.svg (../shared/downloads/menubutton-dropmarker.svg)
skin/classic/browser/downloads/download-summary.svg (../shared/downloads/download-summary.svg)
skin/classic/browser/drm-icon.svg (../shared/drm-icon.svg)
skin/classic/browser/filters.svg (../shared/filters.svg)
skin/classic/browser/fullscreen/insecure.svg (../shared/fullscreen/insecure.svg)

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

До

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

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

@ -92,7 +92,6 @@ browser.jar:
skin/classic/browser/downloads/download-glow-menuPanel-XPVista7.png (downloads/download-glow-menuPanel-XPVista7.png)
skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
skin/classic/browser/downloads/download-summary.png (downloads/download-summary.png)
* skin/classic/browser/downloads/downloads.css (downloads/downloads.css)
skin/classic/browser/feeds/feedIcon.png (feeds/feedIcon.png)
skin/classic/browser/feeds/feedIcon16.png (feeds/feedIcon16.png)

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

@ -9,7 +9,11 @@
<body>
<div id="mount"></div>
<script type="text/javascript">
var devtoolsRequire = Components.utils.import("resource://devtools/shared/Loader.jsm", {}).require;
const { BrowserLoader } = Components.utils.import("resource://devtools/client/shared/browser-loader.js", {});
const { require: devtoolsRequire } = BrowserLoader({
baseURI: "resource://devtools/client/debugger/new/",
window,
});
</script>
<script type="text/javascript" src="resource://devtools/client/debugger/new/bundle.js"></script>
</body>

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

@ -40,7 +40,7 @@
class="devtools-button" />
<html:div class="devtools-toolbar-spacer" />
<html:span id="inspector-searchlabel" />
<html:div id="inspector-search" class="devtools-searchbox">
<html:div id="inspector-search" class="devtools-searchbox has-clear-btn">
<html:input id="inspector-searchbox" class="devtools-searchinput"
type="search"
placeholder="&inspectorSearchHTML.label3;"/>
@ -73,7 +73,7 @@
<html:div id="sidebar-panel-ruleview" class="devtools-monospace theme-sidebar inspector-tabpanel">
<html:div id="ruleview-toolbar-container" class="devtools-toolbar">
<html:div id="ruleview-toolbar">
<html:div class="devtools-searchbox">
<html:div class="devtools-searchbox has-clear-btn">
<html:input id="ruleview-searchbox"
class="devtools-filterinput devtools-rule-searchbox"
type="search"
@ -100,7 +100,7 @@
<html:div id="sidebar-panel-computedview" class="devtools-monospace theme-sidebar inspector-tabpanel">
<html:div id="computedview-toolbar" class="devtools-toolbar">
<html:div class="devtools-searchbox">
<html:div class="devtools-searchbox has-clear-btn">
<html:input id="computedview-searchbox"
class="devtools-filterinput devtools-rule-searchbox"
type="search"

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

@ -19,12 +19,12 @@ const ELEMENTS = ["box-model-root",
"box-model-guide-right",
"box-model-guide-bottom",
"box-model-guide-left",
"box-model-nodeinfobar-container",
"box-model-nodeinfobar-tagname",
"box-model-nodeinfobar-id",
"box-model-nodeinfobar-classes",
"box-model-nodeinfobar-pseudo-classes",
"box-model-nodeinfobar-dimensions"];
"box-model-infobar-container",
"box-model-infobar-tagname",
"box-model-infobar-id",
"box-model-infobar-classes",
"box-model-infobar-pseudo-classes",
"box-model-infobar-dimensions"];
add_task(function* () {
let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URL);

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

@ -24,7 +24,7 @@ const TEST_DATA = [
options: {},
checkHighlighter: function* (testActor) {
let hidden = yield testActor.getHighlighterNodeAttribute(
"box-model-nodeinfobar-container", "hidden");
"box-model-infobar-container", "hidden");
ok(!hidden, "Node infobar is visible");
hidden = yield testActor.getHighlighterNodeAttribute(
@ -64,8 +64,8 @@ const TEST_DATA = [
options: {hideInfoBar: true},
checkHighlighter: function* (testActor) {
let hidden = yield testActor.getHighlighterNodeAttribute(
"box-model-nodeinfobar-container", "hidden");
is(hidden, "true", "nodeinfobar has been hidden");
"box-model-infobar-container", "hidden");
is(hidden, "true", "infobar has been hidden");
}
},
{

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

@ -64,26 +64,26 @@ function* testPosition(test, inspector, testActor) {
yield selectAndHighlightNode(test.selector, inspector);
let position = yield testActor.getHighlighterNodeAttribute(
"box-model-nodeinfobar-container", "position");
"box-model-infobar-container", "position");
is(position, test.position, "Node " + test.selector + ": position matches");
let tag = yield testActor.getHighlighterNodeTextContent(
"box-model-nodeinfobar-tagname");
"box-model-infobar-tagname");
is(tag, test.tag, "node " + test.selector + ": tagName matches.");
if (test.id) {
let id = yield testActor.getHighlighterNodeTextContent(
"box-model-nodeinfobar-id");
"box-model-infobar-id");
is(id, "#" + test.id, "node " + test.selector + ": id matches.");
}
let classes = yield testActor.getHighlighterNodeTextContent(
"box-model-nodeinfobar-classes");
"box-model-infobar-classes");
is(classes, test.classes, "node " + test.selector + ": classes match.");
if (test.dims) {
let dims = yield testActor.getHighlighterNodeTextContent(
"box-model-nodeinfobar-dimensions");
"box-model-infobar-dimensions");
is(dims, test.dims, "node " + test.selector + ": dims match.");
}
}

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

@ -45,6 +45,6 @@ function* testNode(test, inspector, testActor) {
yield selectAndHighlightNode(test.selector, inspector);
let tag = yield testActor.getHighlighterNodeTextContent(
"box-model-nodeinfobar-tagname");
"box-model-infobar-tagname");
is(tag, test.tag, "node " + test.selector + ": tagName matches.");
}

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

@ -26,7 +26,7 @@ function* testPositionAndStyle(test, inspector, testActor) {
yield selectAndHighlightNode(test.selector, inspector);
let style = yield testActor.getHighlighterNodeAttribute(
"box-model-nodeinfobar-container", "style");
"box-model-infobar-container", "style");
is(style.split(";")[0], test.style,
"Infobar shows on top of the page when page isn't scrolled");
@ -34,7 +34,7 @@ function* testPositionAndStyle(test, inspector, testActor) {
yield testActor.scrollWindow(0, 500);
style = yield testActor.getHighlighterNodeAttribute(
"box-model-nodeinfobar-container", "style");
"box-model-infobar-container", "style");
is(style.split(";")[0], test.style,
"Infobar shows on top of the page even if the page is scrolled");

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

@ -117,7 +117,7 @@ function* assertPseudoAddedToNode(inspector, testActor, ruleview) {
info("Check that the infobar selector contains the pseudo-class");
let value = yield testActor.getHighlighterNodeTextContent(
"box-model-nodeinfobar-pseudo-classes");
"box-model-infobar-pseudo-classes");
is(value, PSEUDO, "pseudo-class in infobar selector");
yield inspector.toolbox.highlighter.hideBoxModel();
}
@ -143,7 +143,7 @@ function* assertPseudoRemovedFromView(inspector, testActor, ruleview) {
yield showPickerOn("#div-1", inspector);
let value = yield testActor.getHighlighterNodeTextContent(
"box-model-nodeinfobar-pseudo-classes");
"box-model-infobar-pseudo-classes");
is(value, "", "pseudo-class removed from infobar selector");
yield inspector.toolbox.highlighter.hideBoxModel();
}

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

@ -527,6 +527,15 @@ checkbox:-moz-focusring {
font-size: inherit;
}
/*
* @TODO : has-clear-btn class was added for bug 1296187 and we should remove it
* once we have a standardized search and filter input across the toolboxes.
*/
.has-clear-btn > .devtools-searchinput,
.has-clear-btn > .devtools-filterinput {
padding-inline-end: 23px;
}
.devtools-searchinput {
background-image: var(--magnifying-glass-image);
}
@ -614,7 +623,6 @@ checkbox:-moz-focusring {
.devtools-rule-searchbox[filled] {
background-color: var(--searchbox-background-color);
border-color: var(--searchbox-border-color);
padding-inline-end: 23px;
}
.devtools-style-searchbox-no-match {

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

@ -37,56 +37,44 @@
#layout-main {
position: relative;
box-sizing: border-box;
/* The regions are semi-transparent, so the white background is partly
visible */
background-color: white;
color: var(--theme-selection-color);
color: #1A1C22;
/* Make sure there is some space between the window's edges and the regions */
margin: 0 14px 4px 14px;
width: calc(100% - 2 * 14px);
}
.layout-margin,
.layout-size {
color: var(--theme-highlight-blue);
}
/* Regions are 3 nested elements with wide borders and outlines */
#layout-content {
height: 18px;
}
#layout-margins,
#layout-borders,
#layout-padding {
border-color: hsla(210,100%,85%,0.2);
border-width: 18px;
border-style: solid;
outline: dotted 1px hsl(210,100%,85%);
}
#layout-margins {
/* This opacity applies to all of the regions, since they are nested */
opacity: .8;
border-width: 2px;
border-radius: 2px;
padding: 16px;
}
/* Regions colors */
#layout-margins {
border-color: #edff64;
background-color: #9ebce1;
border-color: #2f5b8e;
}
#layout-borders {
border-color: #444444;
background-color: #b3c8e2;
border-color: #3b72b3;
}
#layout-padding {
border-color: #6a5acd;
background-color: #7fe3cc;
border-color: #00c79a;
}
#layout-content {
background-color: #87ceeb;
background-color: #f0f7ff;
border: 2px solid #eaf4ff;
height: 18px;
}
.theme-firebug #layout-main,
@ -103,6 +91,7 @@
.theme-firebug #layout-main {
color: var(--theme-body-color);
font-size: var(--theme-toolbar-font-size);
background-color: var(--theme-body-color);
}
.theme-firebug #layout-header {
@ -128,32 +117,32 @@
.layout-top,
.layout-bottom {
width: calc(100% - 2px);
width: 100%;
text-align: center;
}
.layout-padding.layout-top {
top: 37px;
top: 38px;
}
.layout-padding.layout-bottom {
bottom: 38px;
bottom: 39px;
}
.layout-border.layout-top {
top: 19px;
top: 20px;
}
.layout-border.layout-bottom {
bottom: 20px;
bottom: 21px;
}
.layout-margin.layout-top {
top: 1px;
top: 2px;
}
.layout-margin.layout-bottom {
bottom: 2px;
bottom: 3px;
}
.layout-size,
@ -163,12 +152,12 @@
.layout-border.layout-right,
.layout-padding.layout-left,
.layout-padding.layout-right {
top: 22px;
top: 23px;
line-height: 80px;
}
.layout-size {
width: calc(100% - 2px);
width: 100%;
}
.layout-margin.layout-right,
@ -177,23 +166,23 @@
.layout-border.layout-right,
.layout-padding.layout-right,
.layout-padding.layout-left {
width: 21px;
width: 20px;
}
.layout-padding.layout-left {
left: 35px;
left: 36px;
}
.layout-padding.layout-right {
right: 35px;
right: 36px;
}
.layout-border.layout-left {
left: 16px;
left: 18px;
}
.layout-border.layout-right {
right: 17px;
right: 18px;
}
.layout-margin.layout-right {
@ -206,24 +195,23 @@
.layout-rotate.layout-left:not(.layout-editing) {
transform: rotate(-90deg);
left: -2px;
}
.layout-rotate.layout-right:not(.layout-editing) {
transform: rotate(90deg);
right: -2px;
}
/* Legend: displayed inside regions */
.layout-legend {
color: #1A1C22;
position: absolute;
margin: 2px 6px;
margin: 3px 6px;
z-index: 1;
}
.layout-legend[data-box="margin"] {
color: var(--theme-highlight-blue);
}
/* Editable fields */
.layout-editable {

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

@ -50,7 +50,7 @@ function test() {
collapsible: true,
stacktrace: stack,
}, {
text: "An invalid or illegal string was specified",
text: "SyntaxError: 'buggy;selector' is not a valid selector",
category: CATEGORY_JS,
severity: SEVERITY_ERROR,
collapsible: true,

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

@ -58,9 +58,8 @@ var inputTests = [
// 4
{
input: "testDOMException()",
output: 'DOMException [SyntaxError: "An invalid or illegal string was ' +
'specified"',
printOutput: '"SyntaxError: An invalid or illegal string was specified"',
output: `DOMException [SyntaxError: "'foo;()bar!' is not a valid selector"`,
printOutput: `"SyntaxError: 'foo;()bar!' is not a valid selector"`,
inspectable: true,
variablesViewLabel: "SyntaxError",
},

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

@ -29,7 +29,7 @@
:-moz-native-anonymous .highlighter-container {
--highlighter-guide-color: #08c;
--highlighter-content-color: #87ceeb;
--highlighter-content-color: #f0f7ff;
--highlighter-bubble-text-color: hsl(216, 33%, 97%);
--highlighter-bubble-background-color: hsl(214, 13%, 24%);
--highlighter-bubble-border-color: rgba(255, 255, 255, 0.2);
@ -55,7 +55,7 @@
cursor: grabbing;
}
/* Box model highlighter */
/* Box Model Highlighter */
:-moz-native-anonymous .box-model-regions {
opacity: 0.6;
@ -72,15 +72,15 @@
}
:-moz-native-anonymous .box-model-padding {
fill: #6a5acd;
fill: #7fe3cc;
}
:-moz-native-anonymous .box-model-border {
fill: #444444;
fill: #b3c8e2;
}
:-moz-native-anonymous .box-model-margin {
fill: #edff64;
fill: #9ebce1;
}
:-moz-native-anonymous .box-model-content,
@ -99,9 +99,9 @@
shape-rendering: crispEdges;
}
/* Highlighter - Node Infobar */
/* Highlighter - Infobar */
:-moz-native-anonymous .box-model-nodeinfobar-container {
:-moz-native-anonymous [class$=infobar-container] {
position: absolute;
max-width: 95%;
@ -109,10 +109,10 @@
font-size: 11px;
}
:-moz-native-anonymous .box-model-nodeinfobar {
:-moz-native-anonymous [class$=infobar] {
position: relative;
/* Centering the nodeinfobar in the container */
/* Centering the infobar in the container */
left: -50%;
padding: 5px;
@ -127,24 +127,24 @@
border: 1px solid var(--highlighter-bubble-border-color);
}
:-moz-native-anonymous .box-model-nodeinfobar-container[hide-arrow] > .box-model-nodeinfobar {
:-moz-native-anonymous [class$=infobar-container][hide-arrow] > [class$=infobar] {
margin: 7px 0;
}
/* Arrows */
:-moz-native-anonymous .box-model-nodeinfobar-container > .box-model-nodeinfobar:before {
:-moz-native-anonymous [class$=infobar-container] > [class$=infobar]:before {
left: calc(50% - 8px);
border: 8px solid var(--highlighter-bubble-border-color);
}
:-moz-native-anonymous .box-model-nodeinfobar-container > .box-model-nodeinfobar:after {
:-moz-native-anonymous [class$=infobar-container] > [class$=infobar]:after {
left: calc(50% - 7px);
border: 7px solid var(--highlighter-bubble-background-color);
}
:-moz-native-anonymous .box-model-nodeinfobar-container > .box-model-nodeinfobar:before,
:-moz-native-anonymous .box-model-nodeinfobar-container > .box-model-nodeinfobar:after {
:-moz-native-anonymous [class$=infobar-container] > [class$=infobar]:before,
:-moz-native-anonymous [class$=infobar-container] > [class$=infobar]:after {
content: "";
display: none;
position: absolute;
@ -154,23 +154,23 @@
border-right-color: transparent;
}
:-moz-native-anonymous .box-model-nodeinfobar-container[position="top"]:not([hide-arrow]) > .box-model-nodeinfobar:before,
:-moz-native-anonymous .box-model-nodeinfobar-container[position="top"]:not([hide-arrow]) > .box-model-nodeinfobar:after {
:-moz-native-anonymous [class$=infobar-container][position="top"]:not([hide-arrow]) > [class$=infobar]:before,
:-moz-native-anonymous [class$=infobar-container][position="top"]:not([hide-arrow]) > [class$=infobar]:after {
border-bottom: 0;
top: 100%;
display: block;
}
:-moz-native-anonymous .box-model-nodeinfobar-container[position="bottom"]:not([hide-arrow]) > .box-model-nodeinfobar:before,
:-moz-native-anonymous .box-model-nodeinfobar-container[position="bottom"]:not([hide-arrow]) > .box-model-nodeinfobar:after {
:-moz-native-anonymous [class$=infobar-container][position="bottom"]:not([hide-arrow]) > [class$=infobar]:before,
:-moz-native-anonymous [class$=infobar-container][position="bottom"]:not([hide-arrow]) > [class$=infobar]:after {
border-top: 0;
bottom: 100%;
display: block;
}
/* Text container */
/* Text Container */
:-moz-native-anonymous .box-model-nodeinfobar-text {
:-moz-native-anonymous [class$=infobar-text] {
overflow: hidden;
white-space: nowrap;
direction: ltr;
@ -178,31 +178,54 @@
display: flex;
}
:-moz-native-anonymous .box-model-nodeinfobar-tagname {
:-moz-native-anonymous .box-model-infobar-tagname {
color: hsl(285,100%, 75%);
}
:-moz-native-anonymous .box-model-nodeinfobar-id {
:-moz-native-anonymous .box-model-infobar-id {
color: hsl(103, 46%, 54%);
overflow: hidden;
text-overflow: ellipsis;
}
:-moz-native-anonymous .box-model-nodeinfobar-classes,
:-moz-native-anonymous .box-model-nodeinfobar-pseudo-classes {
:-moz-native-anonymous .box-model-infobar-classes,
:-moz-native-anonymous .box-model-infobar-pseudo-classes {
color: hsl(200, 74%, 57%);
overflow: hidden;
text-overflow: ellipsis;
}
:-moz-native-anonymous .box-model-nodeinfobar-dimensions {
:-moz-native-anonymous [class$=infobar-dimensions] {
color: hsl(210, 30%, 85%);
border-inline-start: 1px solid #5a6169;
margin-inline-start: 6px;
padding-inline-start: 6px;
}
/* Css transform highlighter */
/* CSS Grid Highlighter */
:-moz-native-anonymous .css-grid-canvas {
position: absolute;
pointer-events: none;
top: 0;
left: 0;
image-rendering: -moz-crisp-edges;
}
:-moz-native-anonymous .css-grid-regions {
opacity: 0.6;
}
:-moz-native-anonymous .css-grid-areas {
fill: #CEC0ED;
stroke: none;
}
:-moz-native-anonymous .css-grid-infobar-areaname {
color: hsl(285,100%, 75%);
}
/* CSS Transform Highlighter */
:-moz-native-anonymous .css-transform-transformed {
fill: var(--highlighter-content-color);
@ -222,7 +245,7 @@
stroke-width: 2;
}
/* Rect highlighter */
/* Rect Highlighter */
:-moz-native-anonymous .highlighted-rect {
position: absolute;
@ -230,7 +253,7 @@
opacity: 0.8;
}
/* Element geometry highlighter */
/* Element Geometry Highlighter */
:-moz-native-anonymous .geometry-editor-root {
/* The geometry editor can be interacted with, so it needs to react to
@ -300,7 +323,7 @@
dominant-baseline: middle;
}
/* Rulers highlighter */
/* Rulers Highlighter */
:-moz-native-anonymous .rulers-highlighter-elements {
shape-rendering: crispEdges;
@ -344,7 +367,7 @@
text-anchor: end;
}
/* Measuring Tool highlighter */
/* Measuring Tool Highlighter */
:-moz-native-anonymous .measuring-tool-highlighter-root {
position: absolute;
@ -404,17 +427,7 @@
shape-rendering: crispEdges;
}
/* CSS Grid highlighter */
:-moz-native-anonymous .css-grid-canvas {
position: absolute;
pointer-events: none;
top: 0;
left: 0;
image-rendering: -moz-crisp-edges;
}
/* Eye dropper */
/* Eye Dropper */
:-moz-native-anonymous .eye-dropper-root {
--magnifier-width: 96px;

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

@ -7,11 +7,10 @@
const { extend } = require("sdk/core/heritage");
const { AutoRefreshHighlighter } = require("./auto-refresh");
const {
CanvasFrameAnonymousContentHelper,
CanvasFrameAnonymousContentHelper, moveInfobar,
getBindingElementAndPseudo, hasPseudoClassLock, getComputedStyle,
createSVGNode, createNode, isNodeValid } = require("./utils/markup");
const { getCurrentZoom,
setIgnoreLayoutChanges } = require("devtools/shared/layout/utils");
const { setIgnoreLayoutChanges } = require("devtools/shared/layout/utils");
const inspector = require("devtools/server/actors/inspector");
// Note that the order of items in this array is important because it is used
@ -20,10 +19,6 @@ const BOX_MODEL_REGIONS = ["margin", "border", "padding", "content"];
const BOX_MODEL_SIDES = ["top", "right", "bottom", "left"];
// Width of boxmodelhighlighter guides
const GUIDE_STROKE_WIDTH = 1;
// How high is the nodeinfobar (px).
const NODE_INFOBAR_HEIGHT = 34;
// What's the size of the nodeinfobar arrow (px).
const NODE_INFOBAR_ARROW_SIZE = 9;
// FIXME: add ":visited" and ":link" after bug 713106 is fixed
const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
@ -73,17 +68,17 @@ const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
* <line class="box-model-guide-bottom" x1="..." y1="..." x2="..." y2="..." />
* <line class="box-model-guide-left" x1="..." y1="..." x2="..." y2="..." />
* </svg>
* <div class="box-model-nodeinfobar-container">
* <div class="box-model-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top" />
* <div class="box-model-nodeinfobar">
* <div class="box-model-nodeinfobar-text" align="center">
* <span class="box-model-nodeinfobar-tagname">Node name</span>
* <span class="box-model-nodeinfobar-id">Node id</span>
* <span class="box-model-nodeinfobar-classes">.someClass</span>
* <span class="box-model-nodeinfobar-pseudo-classes">:hover</span>
* <div class="box-model-infobar-container">
* <div class="box-model-infobar-arrow highlighter-infobar-arrow-top" />
* <div class="box-model-infobar">
* <div class="box-model-infobar-text" align="center">
* <span class="box-model-infobar-tagname">Node name</span>
* <span class="box-model-infobar-id">Node id</span>
* <span class="box-model-infobar-classes">.someClass</span>
* <span class="box-model-infobar-pseudo-classes">:hover</span>
* </div>
* </div>
* <div class="box-model-nodeinfobar-arrow box-model-nodeinfobar-arrow-bottom"/>
* <div class="box-model-infobar-arrow box-model-infobar-arrow-bottom"/>
* </div>
* </div>
* </div>
@ -186,26 +181,26 @@ BoxModelHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
let infobarContainer = createNode(this.win, {
parent: rootWrapper,
attributes: {
"class": "nodeinfobar-container",
"id": "nodeinfobar-container",
"class": "infobar-container",
"id": "infobar-container",
"position": "top",
"hidden": "true"
},
prefix: this.ID_CLASS_PREFIX
});
let nodeInfobar = createNode(this.win, {
let infobar = createNode(this.win, {
parent: infobarContainer,
attributes: {
"class": "nodeinfobar"
"class": "infobar"
},
prefix: this.ID_CLASS_PREFIX
});
let texthbox = createNode(this.win, {
parent: nodeInfobar,
parent: infobar,
attributes: {
"class": "nodeinfobar-text"
"class": "infobar-text"
},
prefix: this.ID_CLASS_PREFIX
});
@ -213,8 +208,8 @@ BoxModelHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
nodeType: "span",
parent: texthbox,
attributes: {
"class": "nodeinfobar-tagname",
"id": "nodeinfobar-tagname"
"class": "infobar-tagname",
"id": "infobar-tagname"
},
prefix: this.ID_CLASS_PREFIX
});
@ -222,8 +217,8 @@ BoxModelHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
nodeType: "span",
parent: texthbox,
attributes: {
"class": "nodeinfobar-id",
"id": "nodeinfobar-id"
"class": "infobar-id",
"id": "infobar-id"
},
prefix: this.ID_CLASS_PREFIX
});
@ -231,8 +226,8 @@ BoxModelHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
nodeType: "span",
parent: texthbox,
attributes: {
"class": "nodeinfobar-classes",
"id": "nodeinfobar-classes"
"class": "infobar-classes",
"id": "infobar-classes"
},
prefix: this.ID_CLASS_PREFIX
});
@ -240,8 +235,8 @@ BoxModelHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
nodeType: "span",
parent: texthbox,
attributes: {
"class": "nodeinfobar-pseudo-classes",
"id": "nodeinfobar-pseudo-classes"
"class": "infobar-pseudo-classes",
"id": "infobar-pseudo-classes"
},
prefix: this.ID_CLASS_PREFIX
});
@ -249,8 +244,8 @@ BoxModelHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
nodeType: "span",
parent: texthbox,
attributes: {
"class": "nodeinfobar-dimensions",
"id": "nodeinfobar-dimensions"
"class": "infobar-dimensions",
"id": "infobar-dimensions"
},
prefix: this.ID_CLASS_PREFIX
});
@ -350,14 +345,14 @@ BoxModelHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
* Hide the infobar
*/
_hideInfobar: function () {
this.getElement("nodeinfobar-container").setAttribute("hidden", "true");
this.getElement("infobar-container").setAttribute("hidden", "true");
},
/**
* Show the infobar
*/
_showInfobar: function () {
this.getElement("nodeinfobar-container").removeAttribute("hidden");
this.getElement("infobar-container").removeAttribute("hidden");
this._updateInfobar();
},
@ -379,7 +374,7 @@ BoxModelHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
* Calculate an outer quad based on the quads returned by getAdjustedQuads.
* The BoxModelHighlighter may highlight more than one boxes, so in this case
* create a new quad that "contains" all of these quads.
* This is useful to position the guides and nodeinfobar.
* This is useful to position the guides and infobar.
* This may happen if the BoxModelHighlighter is used to highlight an inline
* element that spans line breaks.
* @param {String} region The box-model region to get the outer quad for.
@ -676,11 +671,11 @@ BoxModelHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
" \u00D7 " +
parseFloat(rect.height.toPrecision(6));
this.getElement("nodeinfobar-tagname").setTextContent(displayName);
this.getElement("nodeinfobar-id").setTextContent(id);
this.getElement("nodeinfobar-classes").setTextContent(classList);
this.getElement("nodeinfobar-pseudo-classes").setTextContent(pseudos);
this.getElement("nodeinfobar-dimensions").setTextContent(dim);
this.getElement("infobar-tagname").setTextContent(displayName);
this.getElement("infobar-id").setTextContent(id);
this.getElement("infobar-classes").setTextContent(classList);
this.getElement("infobar-pseudo-classes").setTextContent(pseudos);
this.getElement("infobar-dimensions").setTextContent(dim);
this._moveInfobar();
},
@ -690,57 +685,9 @@ BoxModelHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
*/
_moveInfobar: function () {
let bounds = this._getOuterBounds();
let winHeight = this.win.innerHeight * getCurrentZoom(this.win);
let winWidth = this.win.innerWidth * getCurrentZoom(this.win);
let winScrollY = this.win.scrollY;
let container = this.getElement("infobar-container");
// Ensure that containerBottom and containerTop are at least zero to avoid
// showing tooltips outside the viewport.
let containerBottom = Math.max(0, bounds.bottom) + NODE_INFOBAR_ARROW_SIZE;
let containerTop = Math.min(winHeight, bounds.top);
let container = this.getElement("nodeinfobar-container");
// Can the bar be above the node?
let top;
if (containerTop < NODE_INFOBAR_HEIGHT) {
// No. Can we move the bar under the node?
if (containerBottom + NODE_INFOBAR_HEIGHT > winHeight) {
// No. Let's move it inside. Can we show it at the top of the element?
if (containerTop < winScrollY) {
// No. Window is scrolled past the top of the element.
top = 0;
} else {
// Yes. Show it at the top of the element
top = containerTop;
}
container.setAttribute("position", "overlap");
} else {
// Yes. Let's move it under the node.
top = containerBottom;
container.setAttribute("position", "bottom");
}
} else {
// Yes. Let's move it on top of the node.
top = containerTop - NODE_INFOBAR_HEIGHT;
container.setAttribute("position", "top");
}
// Align the bar with the box's center if possible.
let left = bounds.right - bounds.width / 2;
// Make sure the while infobar is visible.
let buffer = 100;
if (left < buffer) {
left = buffer;
container.setAttribute("hide-arrow", "true");
} else if (left > winWidth - buffer) {
left = winWidth - buffer;
container.setAttribute("hide-arrow", "true");
} else {
container.removeAttribute("hide-arrow");
}
let style = "top:" + top + "px;left:" + left + "px;";
container.setAttribute("style", style);
moveInfobar(container, bounds, this.win);
}
});
exports.BoxModelHighlighter = BoxModelHighlighter;

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

@ -6,7 +6,12 @@
const { extend } = require("sdk/core/heritage");
const { AutoRefreshHighlighter } = require("./auto-refresh");
const { CanvasFrameAnonymousContentHelper, createNode } = require("./utils/markup");
const {
CanvasFrameAnonymousContentHelper,
createNode,
createSVGNode,
moveInfobar,
} = require("./utils/markup");
const {
getCurrentZoom,
setIgnoreLayoutChanges
@ -42,10 +47,35 @@ const GRID_LINES_PROPERTIES = {
* h.destroy();
*
* Available Options:
* - showGridLineNumbers {Boolean}
* Displays the grid line numbers
* - showInfiniteLines {Boolean}
* Displays an infinite line to represent the grid lines
* - showGridArea(areaName)
* @param {String} areaName
* Shows the grid area highlight for the given area name.
* - showAllGridAreas
* Shows all the grid area highlights for the current grid.
* - showGridLineNumbers(isShown)
* @param {Boolean}
* Displays the grid line numbers on the grid lines if isShown is true.
* - showInfiniteLines(isShown)
* @param {Boolean} isShown
* Displays an infinite line to represent the grid lines if isShown is true.
*
* Structure:
* <div class="highlighter-container">
* <canvas id="css-grid-canvas" class="css-grid-canvas">
* <svg class="css-grid-elements" hidden="true">
* <g class="css-grid-regions">
* <path class="css-grid-areas" points="..." />
* </g>
* </svg>
* <div class="css-grid-infobar-container">
* <div class="css-grid-infobar">
* <div class="css-grid-infobar-text">
* <span class="css-grid-infobar-areaname">Grid Area Name</span>
* <span class="css-grid-infobar-dimensions"Grid Area Dimensions></span>
* </div>
* </div>
* </div>
* </div>
*/
function CssGridHighlighter(highlighterEnv) {
AutoRefreshHighlighter.call(this, highlighterEnv);
@ -80,6 +110,84 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
prefix: this.ID_CLASS_PREFIX
});
// Build the SVG element
let svg = createSVGNode(this.win, {
nodeType: "svg",
parent: container,
attributes: {
"id": "elements",
"width": "100%",
"height": "100%",
"hidden": "true"
},
prefix: this.ID_CLASS_PREFIX
});
let regions = createSVGNode(this.win, {
nodeType: "g",
parent: svg,
attributes: {
"class": "regions"
},
prefix: this.ID_CLASS_PREFIX
});
createSVGNode(this.win, {
nodeType: "path",
parent: regions,
attributes: {
"class": "areas",
"id": "areas"
},
prefix: this.ID_CLASS_PREFIX
});
// Building the grid infobar markup
let infobarContainer = createNode(this.win, {
parent: container,
attributes: {
"class": "infobar-container",
"id": "infobar-container",
"position": "top",
"hidden": "true"
},
prefix: this.ID_CLASS_PREFIX
});
let infobar = createNode(this.win, {
parent: infobarContainer,
attributes: {
"class": "infobar"
},
prefix: this.ID_CLASS_PREFIX
});
let textbox = createNode(this.win, {
parent: infobar,
attributes: {
"class": "infobar-text"
},
prefix: this.ID_CLASS_PREFIX
});
createNode(this.win, {
nodeType: "span",
parent: textbox,
attributes: {
"class": "infobar-areaname",
"id": "infobar-areaname"
},
prefix: this.ID_CLASS_PREFIX
});
createNode(this.win, {
nodeType: "span",
parent: textbox,
attributes: {
"class": "infobar-dimensions",
"id": "infobar-dimensions"
},
prefix: this.ID_CLASS_PREFIX
});
return container;
},
@ -109,6 +217,33 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
return this._update();
},
/**
* Shows the grid area highlight for the given area name.
*
* @param {String} areaName
* Grid area name.
*/
showGridArea(areaName) {
this.renderGridArea(areaName);
this._showGridArea();
},
/**
* Shows all the grid area highlights for the current grid.
*/
showAllGridAreas() {
this.renderGridArea();
this._showGridArea();
},
/**
* Clear the grid area highlights.
*/
clearGridAreas() {
let box = this.getElement("areas");
box.setAttribute("d", "");
},
/**
* Checks if the current node has a CSS Grid layout.
*
@ -137,27 +272,90 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
/**
* Update the highlighter on the current highlighted node (the one that was
* passed as an argument to show(node)).
* Should be called whenever node's geometry or grid changes
* Should be called whenever node's geometry or grid changes.
*/
_update() {
setIgnoreLayoutChanges(true);
// Clear the canvas.
// Clear the canvas the grid area highlights.
this.clearCanvas();
this.clearGridAreas();
// And start drawing the fragments.
// Start drawing the grid fragments.
for (let i = 0; i < this.gridData.length; i++) {
let fragment = this.gridData[i];
let quad = this.currentQuads.content[i];
this.renderFragment(fragment, quad);
}
// Display the grid area highlights if needed.
if (this.options.showAllGridAreas) {
this.showAllGridAreas();
} else if (this.options.showGridArea) {
this.showGridArea(this.options.showGridArea);
}
this._showGrid();
setIgnoreLayoutChanges(false, this.currentNode.ownerDocument.documentElement);
return true;
},
/**
* Update the grid information displayed in the grid info bar.
*
* @param {GridArea} area
* The grid area object.
* @param {Number} x1
* The first x-coordinate of the grid area rectangle.
* @param {Number} x2
* The second x-coordinate of the grid area rectangle.
* @param {Number} y1
* The first y-coordinate of the grid area rectangle.
* @param {Number} y2
* The second y-coordinate of the grid area rectangle.
*/
_updateInfobar(area, x1, x2, y1, y2) {
let width = x2 - x1;
let height = y2 - y1;
let dim = parseFloat(width.toPrecision(6)) +
" \u00D7 " +
parseFloat(height.toPrecision(6));
this.getElement("infobar-areaname").setTextContent(area.name);
this.getElement("infobar-dimensions").setTextContent(dim);
this._moveInfobar(x1, x2, y1, y2);
},
/**
* Move the grid infobar to the right place in the highlighter.
*
* @param {Number} x1
* The first x-coordinate of the grid area rectangle.
* @param {Number} x2
* The second x-coordinate of the grid area rectangle.
* @param {Number} y1
* The first y-coordinate of the grid area rectangle.
* @param {Number} y2
* The second y-coordinate of the grid area rectangle.
*/
_moveInfobar(x1, x2, y1, y2) {
let bounds = {
bottom: y2,
height: y2 - y1,
left: x1,
right: x2,
top: y1,
width: x2 - x1,
x: x1,
y: y1,
};
let container = this.getElement("infobar-container");
moveInfobar(container, bounds, this.win);
},
clearCanvas() {
let ratio = parseFloat((this.win.devicePixelRatio || 1).toFixed(2));
let width = this.win.innerWidth;
@ -332,9 +530,64 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
}
},
/**
* Render the grid area highlight for the given area name or for all the grid areas.
*
* @param {String} areaName
* Name of the grid area to be highlighted. If no area name is provided, all
* the grid areas should be highlighted.
*/
renderGridArea(areaName) {
let paths = [];
let currentZoom = getCurrentZoom(this.win);
for (let i = 0; i < this.gridData.length; i++) {
let fragment = this.gridData[i];
let {bounds} = this.currentQuads.content[i];
for (let area of fragment.areas) {
if (areaName && areaName != area.name) {
continue;
}
let rowStart = fragment.rows.lines[area.rowStart - 1];
let rowEnd = fragment.rows.lines[area.rowEnd - 1];
let columnStart = fragment.cols.lines[area.columnStart - 1];
let columnEnd = fragment.cols.lines[area.columnEnd - 1];
let x1 = columnStart.start + columnStart.breadth +
(bounds.left / currentZoom);
let x2 = columnEnd.start + (bounds.left / currentZoom);
let y1 = rowStart.start + rowStart.breadth +
(bounds.top / currentZoom);
let y2 = rowEnd.start + (bounds.top / currentZoom);
let path = "M" + x1 + "," + y1 + " " +
"L" + x2 + "," + y1 + " " +
"L" + x2 + "," + y2 + " " +
"L" + x1 + "," + y2;
paths.push(path);
// Update and show the info bar when only displaying a single grid area.
if (areaName) {
this._updateInfobar(area, x1, x2, y1, y2);
this._showInfoBar();
}
}
}
let box = this.getElement("areas");
box.setAttribute("d", paths.join(" "));
},
/**
* Hide the highlighter, the canvas and the infobar.
*/
_hide() {
setIgnoreLayoutChanges(true);
this._hideGrid();
this._hideGridArea();
this._hideInfoBar();
setIgnoreLayoutChanges(false, this.currentNode.ownerDocument.documentElement);
},
@ -344,7 +597,24 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
_showGrid() {
this.getElement("canvas").removeAttribute("hidden");
}
},
_hideGridArea() {
this.getElement("elements").setAttribute("hidden", "true");
},
_showGridArea() {
this.getElement("elements").removeAttribute("hidden");
},
_hideInfoBar() {
this.getElement("infobar-container").setAttribute("hidden", "true");
},
_showInfoBar() {
this.getElement("infobar-container").removeAttribute("hidden");
},
});
exports.CssGridHighlighter = CssGridHighlighter;

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

@ -37,6 +37,10 @@ const SVG_NS = "http://www.w3.org/2000/svg";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const STYLESHEET_URI = "resource://devtools/server/actors/" +
"highlighters.css";
// How high is the infobar (px).
const INFOBAR_HEIGHT = 34;
// What's the size of the infobar arrow (px).
const INFOBAR_ARROW_SIZE = 9;
const _tokens = Symbol("classList/tokens");
@ -517,3 +521,74 @@ CanvasFrameAnonymousContentHelper.prototype = {
}
};
exports.CanvasFrameAnonymousContentHelper = CanvasFrameAnonymousContentHelper;
/**
* Move the infobar to the right place in the highlighter. This helper method is utilized
* in both css-grid.js and box-model.js to help position the infobar in an appropriate
* space over the highlighted node element or grid area. The infobar is used to display
* relevant information about the highlighted item (ex, node or grid name and dimensions).
*
* This method will first try to position the infobar to top or bottom of the container
* such that it has enough space for the height of the infobar. Afterwards, it will try
* to horizontally center align with the container element if possible.
*
* @param {DOMNode} container
* The container element which will be used to position the infobar.
* @param {Object} bounds
* The content bounds of the container element.
* @param {Window} win
* The window object.
*/
function moveInfobar(container, bounds, win) {
let winHeight = win.innerHeight * getCurrentZoom(win);
let winWidth = win.innerWidth * getCurrentZoom(win);
let winScrollY = win.scrollY;
// Ensure that containerBottom and containerTop are at least zero to avoid
// showing tooltips outside the viewport.
let containerBottom = Math.max(0, bounds.bottom) + INFOBAR_ARROW_SIZE;
let containerTop = Math.min(winHeight, bounds.top);
// Can the bar be above the node?
let top;
if (containerTop < INFOBAR_HEIGHT) {
// No. Can we move the bar under the node?
if (containerBottom + INFOBAR_HEIGHT > winHeight) {
// No. Let's move it inside. Can we show it at the top of the element?
if (containerTop < winScrollY) {
// No. Window is scrolled past the top of the element.
top = 0;
} else {
// Yes. Show it at the top of the element
top = containerTop;
}
container.setAttribute("position", "overlap");
} else {
// Yes. Let's move it under the node.
top = containerBottom;
container.setAttribute("position", "bottom");
}
} else {
// Yes. Let's move it on top of the node.
top = containerTop - INFOBAR_HEIGHT;
container.setAttribute("position", "top");
}
// Align the bar with the box's center if possible.
let left = bounds.right - bounds.width / 2;
// Make sure the while infobar is visible.
let buffer = 100;
if (left < buffer) {
left = buffer;
container.setAttribute("hide-arrow", "true");
} else if (left > winWidth - buffer) {
left = winWidth - buffer;
container.setAttribute("hide-arrow", "true");
} else {
container.removeAttribute("hide-arrow");
}
let style = "top:" + top + "px;left:" + left + "px;";
container.setAttribute("style", style);
}
exports.moveInfobar = moveInfobar;

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

@ -1,55 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
var gClient;
var gMgr;
var gRefMemory;
function run_test() {
gMgr = Cc["@mozilla.org/memory-reporter-manager;1"].getService(Ci.nsIMemoryReporterManager);
Cu.forceGC();
gRefMemory = gMgr.residentUnique;
add_test(init_server);
add_test(add_browser_actors);
add_test(connect_client);
add_test(list_tabs);
add_test(close_client);
run_next_test();
}
function check_footprint(step, max) {
var footprint = (gMgr.residentUnique - gRefMemory) / 1024;
let msg = "Footprint after " + step + " is " + footprint + " kB (should be less than " + max + " kB).\n" +
"!!! The devtools server's memory usage increased either significantly, or slowly over time.\n" +
"!!! If your patch doesn't cause a big increase, feel free to raise the thresholds for this test as needed.";
ok(footprint < max, msg);
run_next_test();
}
function init_server() {
DebuggerServer.init();
check_footprint("DebuggerServer.init()", 150);
}
function add_browser_actors() {
DebuggerServer.addBrowserActors();
check_footprint("DebuggerServer.addBrowserActors()", 2600);
}
function connect_client() {
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient.connect().then(function onConnect() {
check_footprint("DebuggerClient.connect()", 3200);
});
}
function list_tabs() {
gClient.listTabs(function onListTabs(aResponse) {
check_footprint("DebuggerClient.listTabs()", 3800);
});
}
function close_client() {
gClient.close().then(run_next_test);
}

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

@ -257,10 +257,6 @@ reason = bug 937197
[test_get-executable-lines-source-map.js]
[test_xpcshell_debugging.js]
support-files = xpcshell_debugging_script.js
[test_memory_footprint.js]
run-sequentially = measure memory, has to be run solo
skip-if = os != 'linux' || debug || asan
reason = bug 1014071
[test_setBreakpoint-on-column.js]
[test_setBreakpoint-on-column-in-gcd-script.js]
[test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js]

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

@ -92,12 +92,12 @@ let checkQuerySelectorAllException = Task.async(function*() {
let response = yield evaluateJS("$$(':foo')");
checkObject(response, {
input: "$$(':foo')",
exceptionMessage: "SyntaxError: An invalid or illegal string was specified",
exceptionMessage: "SyntaxError: ':foo' is not a valid selector",
exception: {
preview: {
kind: "DOMException",
name: "SyntaxError",
message: "An invalid or illegal string was specified"
message: "':foo' is not a valid selector"
}
}
});

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

@ -2706,7 +2706,10 @@ nsINode::ParseSelectorList(const nsAString& aSelectorString,
if (haveCachedList) {
if (!selectorList) {
// Invalid selector.
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR,
NS_LITERAL_CSTRING("'") + NS_ConvertUTF16toUTF8(aSelectorString) +
NS_LITERAL_CSTRING("' is not a valid selector")
);
}
return selectorList;
}
@ -2723,6 +2726,13 @@ nsINode::ParseSelectorList(const nsAString& aSelectorString,
// of selectors, but it sees if we can parse them first.)
MOZ_ASSERT(aRv.ErrorCodeIs(NS_ERROR_DOM_SYNTAX_ERR),
"Unexpected error, so cached version won't return it");
// Change the error message to match above.
aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR,
NS_LITERAL_CSTRING("'") + NS_ConvertUTF16toUTF8(aSelectorString) +
NS_LITERAL_CSTRING("' is not a valid selector")
);
cache.CacheList(aSelectorString, nullptr);
return nullptr;
}

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

@ -1076,6 +1076,9 @@ MediaDecoderStateMachine::ExitState()
case DECODER_STATE_COMPLETED:
mSentPlaybackEndedEvent = false;
break;
case DECODER_STATE_SHUTDOWN:
MOZ_DIAGNOSTIC_ASSERT(false, "Shouldn't escape the SHUTDOWN state.");
break;
default:
break;
}
@ -1224,12 +1227,10 @@ MediaDecoderStateMachine::Shutdown()
{
MOZ_ASSERT(OnTaskQueue());
// Once we've entered the shutdown state here there's no going back.
// Change state before issuing shutdown request to threads so those
// threads can start exiting cleanly during the Shutdown call.
ScheduleStateMachine();
SetState(DECODER_STATE_SHUTDOWN);
mDelayedScheduler.Reset();
mBufferedUpdateRequest.DisconnectIfExists();
mQueuedSeek.RejectIfExists(__func__);
@ -1255,6 +1256,40 @@ MediaDecoderStateMachine::Shutdown()
mMediaSink->Shutdown();
// Prevent dangling pointers by disconnecting the listeners.
mAudioQueueListener.Disconnect();
mVideoQueueListener.Disconnect();
mMetadataManager.Disconnect();
// Disconnect canonicals and mirrors before shutting down our task queue.
mBuffered.DisconnectIfConnected();
mIsReaderSuspended.DisconnectIfConnected();
mEstimatedDuration.DisconnectIfConnected();
mExplicitDuration.DisconnectIfConnected();
mPlayState.DisconnectIfConnected();
mNextPlayState.DisconnectIfConnected();
mVolume.DisconnectIfConnected();
mLogicalPlaybackRate.DisconnectIfConnected();
mPreservesPitch.DisconnectIfConnected();
mSameOriginMedia.DisconnectIfConnected();
mMediaPrincipalHandle.DisconnectIfConnected();
mPlaybackBytesPerSecond.DisconnectIfConnected();
mPlaybackRateReliable.DisconnectIfConnected();
mDecoderPosition.DisconnectIfConnected();
mMediaSeekable.DisconnectIfConnected();
mMediaSeekableOnlyInBufferedRanges.DisconnectIfConnected();
mIsVisible.DisconnectIfConnected();
mDuration.DisconnectAll();
mIsShutdown.DisconnectAll();
mNextFrameStatus.DisconnectAll();
mCurrentPosition.DisconnectAll();
mPlaybackOffset.DisconnectAll();
mIsAudioDataAudible.DisconnectAll();
// Shut down the watch manager to stop further notifications.
mWatchManager.Shutdown();
DECODER_LOG("Shutdown started");
// Put a task in the decode queue to shutdown the reader.
@ -1963,16 +1998,8 @@ void
MediaDecoderStateMachine::DecodeError()
{
MOZ_ASSERT(OnTaskQueue());
if (IsShutdown()) {
// Already shutdown.
return;
}
MOZ_ASSERT(!IsShutdown());
DECODER_WARN("Decode error");
// Change state to SHUTDOWN so we have no more processing.
SetState(DECODER_STATE_SHUTDOWN);
// Notify the decode error and MediaDecoder will shut down MDSM.
mOnPlaybackEvent.Notify(MediaEventType::DecodeError);
}
@ -2222,45 +2249,6 @@ RefPtr<ShutdownPromise>
MediaDecoderStateMachine::FinishShutdown()
{
MOZ_ASSERT(OnTaskQueue());
// The reader's listeners hold references to the state machine,
// creating a cycle which keeps the state machine and its shared
// thread pools alive. So break it here.
// Prevent dangling pointers by disconnecting the listeners.
mAudioQueueListener.Disconnect();
mVideoQueueListener.Disconnect();
mMetadataManager.Disconnect();
// Disconnect canonicals and mirrors before shutting down our task queue.
mBuffered.DisconnectIfConnected();
mIsReaderSuspended.DisconnectIfConnected();
mEstimatedDuration.DisconnectIfConnected();
mExplicitDuration.DisconnectIfConnected();
mPlayState.DisconnectIfConnected();
mNextPlayState.DisconnectIfConnected();
mVolume.DisconnectIfConnected();
mLogicalPlaybackRate.DisconnectIfConnected();
mPreservesPitch.DisconnectIfConnected();
mSameOriginMedia.DisconnectIfConnected();
mMediaPrincipalHandle.DisconnectIfConnected();
mPlaybackBytesPerSecond.DisconnectIfConnected();
mPlaybackRateReliable.DisconnectIfConnected();
mDecoderPosition.DisconnectIfConnected();
mMediaSeekable.DisconnectIfConnected();
mMediaSeekableOnlyInBufferedRanges.DisconnectIfConnected();
mIsVisible.DisconnectIfConnected();
mDuration.DisconnectAll();
mIsShutdown.DisconnectAll();
mNextFrameStatus.DisconnectAll();
mCurrentPosition.DisconnectAll();
mPlaybackOffset.DisconnectAll();
mIsAudioDataAudible.DisconnectAll();
// Shut down the watch manager before shutting down our task queue.
mWatchManager.Shutdown();
MOZ_ASSERT(mState == DECODER_STATE_SHUTDOWN,
"How did we escape from the shutdown state?");
DECODER_LOG("Shutting down state machine task queue");
@ -2623,13 +2611,13 @@ MediaDecoderStateMachine::ScheduleStateMachineIn(int64_t aMicroseconds)
TimeStamp now = TimeStamp::Now();
TimeStamp target = now + TimeDuration::FromMicroseconds(aMicroseconds);
SAMPLE_LOG("Scheduling state machine for %lf ms from now", (target - now).ToMilliseconds());
RefPtr<MediaDecoderStateMachine> self = this;
mDelayedScheduler.Ensure(target, [self] () {
self->OnDelayedSchedule();
}, [self] () {
self->NotReached();
// It is OK to capture 'this' without causing UAF because the callback
// always happens before shutdown.
mDelayedScheduler.Ensure(target, [this] () {
mDelayedScheduler.CompleteRequest();
RunStateMachine();
}, [] () {
MOZ_DIAGNOSTIC_ASSERT(false);
});
}
@ -2673,7 +2661,8 @@ void MediaDecoderStateMachine::PreservesPitchChanged()
mMediaSink->SetPreservesPitch(mPreservesPitch);
}
bool MediaDecoderStateMachine::IsShutdown()
bool
MediaDecoderStateMachine::IsShutdown() const
{
MOZ_ASSERT(OnTaskQueue());
return mIsShutdown;

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

@ -319,23 +319,13 @@ private:
// request is discarded.
void ScheduleStateMachineIn(int64_t aMicroseconds);
void OnDelayedSchedule()
{
MOZ_ASSERT(OnTaskQueue());
mDelayedScheduler.CompleteRequest();
ScheduleStateMachine();
}
void NotReached() { MOZ_DIAGNOSTIC_ASSERT(false); }
// Discard audio/video data that are already played by MSG.
void DiscardStreamData();
bool HaveEnoughDecodedAudio();
bool HaveEnoughDecodedVideo();
// Returns true if the state machine has shutdown or is in the process of
// shutting down. The decoder monitor must be held while calling this.
bool IsShutdown();
// True if shutdown process has begun.
bool IsShutdown() const;
// Returns true if we're currently playing. The decoder monitor must
// be held.

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

@ -14,6 +14,7 @@
#include "mp4_demuxer/ResourceStream.h"
#include "mp4_demuxer/BufferStream.h"
#include "mp4_demuxer/Index.h"
#include "nsPrintfCString.h"
// Used for telemetry
#include "mozilla/Telemetry.h"
@ -27,6 +28,8 @@ mozilla::LogModule* GetDemuxerLog() {
return gMediaDemuxerLog;
}
#define LOG(arg, ...) MOZ_LOG(gMediaDemuxerLog, mozilla::LogLevel::Debug, ("MP4Demuxer(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
namespace mozilla {
class MP4TrackDemuxer : public MediaTrackDemuxer
@ -55,7 +58,7 @@ public:
private:
friend class MP4Demuxer;
void NotifyDataArrived();
void UpdateSamples(nsTArray<RefPtr<MediaRawData>>& aSamples);
already_AddRefed<MediaRawData> GetNextSample();
void EnsureUpToDateIndex();
void SetNextKeyFrameTime();
RefPtr<MP4Demuxer> mParent;
@ -68,6 +71,7 @@ private:
RefPtr<MediaRawData> mQueuedSample;
bool mNeedReIndex;
bool mNeedSPSForTelemetry;
bool mIsH264 = false;
};
@ -241,6 +245,7 @@ MP4TrackDemuxer::MP4TrackDemuxer(MP4Demuxer* aParent,
if (videoInfo &&
(mInfo->mMimeType.EqualsLiteral("video/mp4") ||
mInfo->mMimeType.EqualsLiteral("video/avc"))) {
mIsH264 = true;
RefPtr<MediaByteBuffer> extraData = videoInfo->mExtraData;
mNeedSPSForTelemetry = AccumulateSPSTelemetry(extraData);
mp4_demuxer::SPSData spsdata;
@ -289,15 +294,76 @@ MP4TrackDemuxer::Seek(media::TimeUnit aTime)
mIterator->Seek(seekTime);
// Check what time we actually seeked to.
mQueuedSample = mIterator->GetNext();
if (mQueuedSample) {
seekTime = mQueuedSample->mTime;
}
RefPtr<MediaRawData> sample;
do {
sample = GetNextSample();
if (!sample) {
return SeekPromise::CreateAndReject(DemuxerFailureReason::END_OF_STREAM, __func__);
}
if (!sample->Size()) {
// This sample can't be decoded, continue searching.
continue;
}
if (sample->mKeyframe) {
mQueuedSample = sample;
seekTime = mQueuedSample->mTime;
}
} while (!mQueuedSample);
SetNextKeyFrameTime();
return SeekPromise::CreateAndResolve(media::TimeUnit::FromMicroseconds(seekTime), __func__);
}
already_AddRefed<MediaRawData>
MP4TrackDemuxer::GetNextSample()
{
RefPtr<MediaRawData> sample = mIterator->GetNext();
if (!sample) {
return nullptr;
}
if (mInfo->GetAsVideoInfo()) {
sample->mExtraData = mInfo->GetAsVideoInfo()->mExtraData;
if (mIsH264) {
mp4_demuxer::H264::FrameType type =
mp4_demuxer::H264::GetFrameType(sample);
switch (type) {
case mp4_demuxer::H264::FrameType::I_FRAME: MOZ_FALLTHROUGH;
case mp4_demuxer::H264::FrameType::OTHER:
{
bool keyframe = type == mp4_demuxer::H264::FrameType::I_FRAME;
if (sample->mKeyframe != keyframe) {
NS_WARNING(nsPrintfCString("Frame incorrectly marked as %skeyframe @ pts:%lld dur:%u dts:%lld",
keyframe ? "" : "non-",
sample->mTime,
sample->mDuration,
sample->mTimecode).get());
sample->mKeyframe = keyframe;
}
break;
}
case mp4_demuxer::H264::FrameType::INVALID:
NS_WARNING(nsPrintfCString("Invalid H264 frame @ pts:%lld dur:%u dts:%lld",
sample->mTime,
sample->mDuration,
sample->mTimecode).get());
// We could reject the sample now, however demuxer errors are fatal.
// So we keep the invalid frame, relying on the H264 decoder to
// handle the error later.
// TODO: make demuxer errors non-fatal.
break;
}
}
}
if (sample->mCrypto.mValid) {
nsAutoPtr<MediaRawDataWriter> writer(sample->CreateWriter());
writer->mCrypto.mMode = mInfo->mCrypto.mMode;
writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize;
writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId);
}
return sample.forget();
}
RefPtr<MP4TrackDemuxer::SamplesPromise>
MP4TrackDemuxer::GetSamples(int32_t aNumSamples)
{
@ -308,12 +374,14 @@ MP4TrackDemuxer::GetSamples(int32_t aNumSamples)
}
if (mQueuedSample) {
MOZ_ASSERT(mQueuedSample->mKeyframe,
"mQueuedSample must be a keyframe");
samples->mSamples.AppendElement(mQueuedSample);
mQueuedSample = nullptr;
aNumSamples--;
}
RefPtr<MediaRawData> sample;
while (aNumSamples && (sample = mIterator->GetNext())) {
while (aNumSamples && (sample = GetNextSample())) {
if (!sample->Size()) {
continue;
}
@ -324,7 +392,19 @@ MP4TrackDemuxer::GetSamples(int32_t aNumSamples)
if (samples->mSamples.IsEmpty()) {
return SamplesPromise::CreateAndReject(DemuxerFailureReason::END_OF_STREAM, __func__);
} else {
UpdateSamples(samples->mSamples);
for (const auto& sample : samples->mSamples) {
// Collect telemetry from h264 Annex B SPS.
if (mNeedSPSForTelemetry && mp4_demuxer::AnnexB::HasSPS(sample)) {
RefPtr<MediaByteBuffer> extradata =
mp4_demuxer::AnnexB::ExtractExtraData(sample);
mNeedSPSForTelemetry = AccumulateSPSTelemetry(extradata);
}
}
if (mNextKeyframeTime.isNothing() ||
samples->mSamples.LastElement()->mTime >= mNextKeyframeTime.value().ToMicroseconds()) {
SetNextKeyFrameTime();
}
return SamplesPromise::CreateAndResolve(samples, __func__);
}
}
@ -349,33 +429,6 @@ MP4TrackDemuxer::Reset()
SetNextKeyFrameTime();
}
void
MP4TrackDemuxer::UpdateSamples(nsTArray<RefPtr<MediaRawData>>& aSamples)
{
for (size_t i = 0; i < aSamples.Length(); i++) {
MediaRawData* sample = aSamples[i];
// Collect telemetry from h264 Annex B SPS.
if (mNeedSPSForTelemetry && mp4_demuxer::AnnexB::HasSPS(sample)) {
RefPtr<MediaByteBuffer> extradata =
mp4_demuxer::AnnexB::ExtractExtraData(sample);
mNeedSPSForTelemetry = AccumulateSPSTelemetry(extradata);
}
if (sample->mCrypto.mValid) {
nsAutoPtr<MediaRawDataWriter> writer(sample->CreateWriter());
writer->mCrypto.mMode = mInfo->mCrypto.mMode;
writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize;
writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId);
}
if (mInfo->GetAsVideoInfo()) {
sample->mExtraData = mInfo->GetAsVideoInfo()->mExtraData;
}
}
if (mNextKeyframeTime.isNothing() ||
aSamples.LastElement()->mTime >= mNextKeyframeTime.value().ToMicroseconds()) {
SetNextKeyFrameTime();
}
}
nsresult
MP4TrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
{
@ -397,7 +450,7 @@ MP4TrackDemuxer::SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold)
uint32_t parsed = 0;
bool found = false;
RefPtr<MediaRawData> sample;
while (!found && (sample = mIterator->GetNext())) {
while (!found && (sample = GetNextSample())) {
parsed++;
if (sample->mKeyframe && sample->mTime >= aTimeThreshold.ToMicroseconds()) {
found = true;
@ -441,3 +494,5 @@ MP4TrackDemuxer::BreakCycles()
}
} // namespace mozilla
#undef LOG

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

@ -59,6 +59,14 @@ H264Converter::Input(MediaRawData* aSample)
}
if (mInitPromiseRequest.Exists()) {
if (mNeedKeyframe) {
if (!aSample->mKeyframe) {
// Frames dropped, we need a new one.
mCallback->InputExhausted();
return NS_OK;
}
mNeedKeyframe = false;
}
mMediaRawSamples.AppendElement(aSample);
return NS_OK;
}
@ -72,6 +80,7 @@ H264Converter::Input(MediaRawData* aSample)
if (rv == NS_ERROR_NOT_INITIALIZED) {
// We are missing the required SPS to create the decoder.
// Ignore for the time being, the MediaRawData will be dropped.
mCallback->InputExhausted();
return NS_OK;
}
} else {
@ -79,11 +88,18 @@ H264Converter::Input(MediaRawData* aSample)
}
NS_ENSURE_SUCCESS(rv, rv);
if (mNeedKeyframe && !aSample->mKeyframe) {
mCallback->InputExhausted();
return NS_OK;
}
if (!mNeedAVCC &&
!mp4_demuxer::AnnexB::ConvertSampleToAnnexB(aSample)) {
return NS_ERROR_FAILURE;
}
mNeedKeyframe = false;
aSample->mExtraData = mCurrentConfig.mExtraData;
return mDecoder->Input(aSample);
@ -92,6 +108,7 @@ H264Converter::Input(MediaRawData* aSample)
nsresult
H264Converter::Flush()
{
mNeedKeyframe = true;
if (mDecoder) {
return mDecoder->Flush();
}
@ -101,6 +118,7 @@ H264Converter::Flush()
nsresult
H264Converter::Drain()
{
mNeedKeyframe = true;
if (mDecoder) {
return mDecoder->Drain();
}
@ -175,6 +193,9 @@ H264Converter::CreateDecoder(DecoderDoctorDiagnostics* aDiagnostics)
mLastError = NS_ERROR_FAILURE;
return NS_ERROR_FAILURE;
}
mNeedKeyframe = true;
return NS_OK;
}
@ -208,11 +229,22 @@ void
H264Converter::OnDecoderInitDone(const TrackType aTrackType)
{
mInitPromiseRequest.Complete();
bool gotInput = false;
for (uint32_t i = 0 ; i < mMediaRawSamples.Length(); i++) {
if (NS_FAILED(mDecoder->Input(mMediaRawSamples[i]))) {
const RefPtr<MediaRawData>& sample = mMediaRawSamples[i];
if (mNeedKeyframe) {
if (!sample->mKeyframe) {
continue;
}
mNeedKeyframe = false;
}
if (NS_FAILED(mDecoder->Input(sample))) {
mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
}
}
if (!gotInput) {
mCallback->InputExhausted();
}
mMediaRawSamples.Clear();
}

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

@ -66,6 +66,7 @@ private:
RefPtr<GMPCrashHelper> mGMPCrashHelper;
bool mNeedAVCC;
nsresult mLastError;
bool mNeedKeyframe = true;
};
} // namespace mozilla

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

@ -463,7 +463,7 @@ PresentationService::HandleSessionRequest(nsIPresentationSessionRequest* aReques
info->SetControlChannel(ctrlChannel);
info->SetDevice(device);
return static_cast<PresentationPresentingInfo*>(
info.get())->NotifyResponderReady();
info.get())->DoReconnect();
}
// This is the case for a new session.

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

@ -405,11 +405,16 @@ PresentationSessionInfo::ContinueTermination()
NS_IMETHODIMP
PresentationSessionInfo::NotifyTransportReady()
{
PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
MOZ_ASSERT(NS_IsMainThread());
if (mState != nsIPresentationSessionListener::STATE_CONNECTING &&
mState != nsIPresentationSessionListener::STATE_CONNECTED) {
return NS_OK;
}
mIsTransportReady = true;
// Established RTCDataChannel implies responder is ready.
@ -484,11 +489,15 @@ PresentationSessionInfo::NotifyData(const nsACString& aData)
NS_IMETHODIMP
PresentationSessionInfo::OnSessionTransport(nsIPresentationSessionTransport* transport)
{
PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
SetBuilder(nullptr);
if (mState != nsIPresentationSessionListener::STATE_CONNECTING) {
return NS_ERROR_FAILURE;
}
// The session transport is managed by content process
if (!transport) {
return NS_OK;
@ -1195,6 +1204,7 @@ nsresult
PresentationPresentingInfo::InitTransportAndSendAnswer()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CONNECTING);
uint8_t type = 0;
nsresult rv = mRequesterDescription->GetType(&type);
@ -1308,8 +1318,8 @@ PresentationPresentingInfo::IsAccessible(base::ProcessId aProcessId)
nsresult
PresentationPresentingInfo::NotifyResponderReady()
{
PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
if (mTimer) {
mTimer->Cancel();
@ -1344,6 +1354,19 @@ PresentationPresentingInfo::NotifyResponderFailure()
return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
}
nsresult
PresentationPresentingInfo::DoReconnect()
{
PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CLOSED);
SetStateWithReason(nsIPresentationSessionListener::STATE_CONNECTING, NS_OK);
return NotifyResponderReady();
}
// nsIPresentationControlChannelListener
NS_IMETHODIMP
PresentationPresentingInfo::OnOffer(nsIPresentationChannelDescription* aDescription)

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

@ -254,6 +254,8 @@ public:
bool IsAccessible(base::ProcessId aProcessId) override;
nsresult DoReconnect();
private:
~PresentationPresentingInfo()
{

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

@ -240,19 +240,19 @@ const mockedSessionTransport = {
return this._selfAddress;
},
buildTCPSenderTransport: function(transport, listener) {
sendAsyncMessage('data-transport-initialized');
this._listener = listener;
this._role = Ci.nsIPresentationService.ROLE_CONTROLLER;
this._listener.onSessionTransport(this);
this._listener = null;
sendAsyncMessage('data-transport-initialized');
setTimeout(()=>{
this._listener.onSessionTransport(this);
this._listener = null;
this.simulateTransportReady();
}, 0);
},
buildTCPReceiverTransport: function(description, listener) {
this._listener = listener;
this._role = Ci.nsIPresentationService.ROLE_CONTROLLER;
this._role = Ci.nsIPresentationService.ROLE_RECEIVER;
var addresses = description.QueryInterface(Ci.nsIPresentationChannelDescription).tcpAddress;
this._selfAddress = {
@ -269,7 +269,6 @@ const mockedSessionTransport = {
},
// in-process case
buildDataChannelTransport: function(role, window, listener) {
dump("PresentationSessionChromeScript: build data channel transport\n");
this._listener = listener;
this._role = role;

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

@ -42,318 +42,437 @@ function setup() {
}
function testStartConnectionCancelPrompt() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-cancel', SpecialPowers.Cr.NS_ERROR_DOM_NOT_ALLOWED_ERR);
});
info('--- testStartConnectionCancelPrompt ---');
return Promise.all([
new Promise((resolve) => {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-cancel', SpecialPowers.Cr.NS_ERROR_DOM_NOT_ALLOWED_ERR);
resolve();
});
}),
request.start().then(
function(aConnection) {
ok(false, "|start| shouldn't succeed in this case.");
aReject();
},
function(aError) {
is(aError.name, "NotAllowedError", "NotAllowedError is expected when the prompt is canceled.");
aResolve();
}
);
});
),
]);
}
function testStartConnectionNoDevice() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-cancel', SpecialPowers.Cr.NS_ERROR_DOM_NOT_FOUND_ERR);
});
info('--- testStartConnectionNoDevice ---');
return Promise.all([
new Promise((resolve) => {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-cancel', SpecialPowers.Cr.NS_ERROR_DOM_NOT_FOUND_ERR);
resolve();
});
}),
request.start().then(
function(aConnection) {
ok(false, "|start| shouldn't succeed in this case.");
aReject();
},
function(aError) {
is(aError.name, "NotFoundError", "NotFoundError is expected when no available device.");
aResolve();
}
);
});
),
]);
}
function testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-select');
});
info('--- testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit ---');
return Promise.all([
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
info("A control channel is established.");
gScript.sendAsyncMessage('trigger-control-channel-open');
});
new Promise((resolve) => {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-select');
resolve();
});
}),
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
info("The control channel is opened.");
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
info("A control channel is established.");
gScript.sendAsyncMessage('trigger-control-channel-open');
resolve();
});
}),
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
info("The control channel is closed. " + aReason);
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
info("The control channel is opened.");
resolve();
});
}),
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
gScript.removeMessageListener('offer-sent', offerSentHandler);
ok(aIsValid, "A valid offer is sent out.");
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_ERROR_FAILURE);
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
info("The control channel is closed. " + aReason);
is(aReason, SpecialPowers.Cr.NS_ERROR_FAILURE, "The control channel is closed with NS_ERROR_FAILURE");
resolve();
});
}),
new Promise((resolve) => {
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
gScript.removeMessageListener('offer-sent', offerSentHandler);
ok(aIsValid, "A valid offer is sent out.");
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_ERROR_FAILURE);
resolve();
});
}),
request.start().then(
function(aConnection) {
is(aConnection.state, "connecting", "The initial state should be connecting.");
aConnection.onclose = function() {
aConnection.onclose = null;
is(aConnection.state, "closed", "Connection should be closed.");
aResolve();
};
return new Promise((resolve) => {
aConnection.onclose = function() {
aConnection.onclose = null;
is(aConnection.state, "closed", "Connection should be closed.");
resolve();
};
});
},
function(aError) {
ok(false, "Error occurred when establishing a connection: " + aError);
teardown();
aReject();
}
);
});
),
]);
}
function testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportInit() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-select');
});
info('--- testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportInit ---');
return Promise.all([
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
info("A control channel is established.");
gScript.sendAsyncMessage('trigger-control-channel-open');
});
new Promise((resolve) => {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-select');
resolve();
});
}),
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
info("The control channel is opened.");
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
info("A control channel is established.");
gScript.sendAsyncMessage('trigger-control-channel-open');
resolve();
});
}),
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
info("The control channel is closed. " + aReason);
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
info("The control channel is opened.");
resolve();
});
}),
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
gScript.removeMessageListener('offer-sent', offerSentHandler);
ok(aIsValid, "A valid offer is sent out.");
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
info("The control channel is closed. " + aReason);
is(aReason, SpecialPowers.Cr.NS_OK, "The control channel is closed with NS_OK");
resolve();
});
}),
new Promise((resolve) => {
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
gScript.removeMessageListener('offer-sent', offerSentHandler);
ok(aIsValid, "A valid offer is sent out.");
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
resolve();
});
}),
request.start().then(
function(aConnection) {
is(aConnection.state, "connecting", "The initial state should be connecting.");
aConnection.onclose = function() {
aConnection.onclose = null;
is(aConnection.state, "closed", "Connection should be closed.");
aResolve();
};
return new Promise((resolve) => {
aConnection.onclose = function() {
aConnection.onclose = null;
is(aConnection.state, "closed", "Connection should be closed.");
resolve();
};
});
},
function(aError) {
ok(false, "Error occurred when establishing a connection: " + aError);
teardown();
aReject();
}
);
});
),
]);
}
function testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportReady() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-select');
});
info('--- testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportReady ---');
return Promise.all([
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
info("A control channel is established.");
gScript.sendAsyncMessage('trigger-control-channel-open');
});
new Promise((resolve) => {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-select');
resolve();
});
}),
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
info("The control channel is opened.");
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
info("A control channel is established.");
gScript.sendAsyncMessage('trigger-control-channel-open');
resolve();
});
}),
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
info("The control channel is closed. " + aReason);
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
info("The control channel is opened.");
resolve();
});
}),
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
gScript.removeMessageListener('offer-sent', offerSentHandler);
ok(aIsValid, "A valid offer is sent out.");
gScript.sendAsyncMessage('trigger-incoming-transport');
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
is(aReason, SpecialPowers.Cr.NS_ERROR_ABORT, "The control channel is closed with NS_ERROR_ABORT");
resolve();
});
}),
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
info("Data transport channel is initialized.");
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_ERROR_ABORT);
});
new Promise((resolve) => {
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
gScript.removeMessageListener('offer-sent', offerSentHandler);
ok(aIsValid, "A valid offer is sent out.");
gScript.sendAsyncMessage('trigger-incoming-transport');
resolve();
});
}),
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
info("The data transport is closed. " + aReason);
});
new Promise((resolve) => {
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
info("Data transport channel is initialized.");
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_ERROR_ABORT);
resolve();
});
}),
new Promise((resolve) => {
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
info("The data transport is closed. " + aReason);
resolve();
});
}),
request.start().then(
function(aConnection) {
is(aConnection.state, "connecting", "The initial state should be connecting.");
aConnection.onclose = function() {
aConnection.onclose = null;
is(aConnection.state, "closed", "Connection should be closed.");
aResolve();
};
return new Promise((resolve) => {
aConnection.onclose = function() {
aConnection.onclose = null;
is(aConnection.state, "closed", "Connection should be closed.");
resolve();
};
});
},
function(aError) {
ok(false, "Error occurred when establishing a connection: " + aError);
teardown();
aReject();
}
);
});
),
]);
}
function testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportReady() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-select');
});
info('--- testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportReady -- ');
return Promise.all([
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
info("A control channel is established.");
gScript.sendAsyncMessage('trigger-control-channel-open');
});
new Promise((resolve) => {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-select');
resolve();
});
}),
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
info("The control channel is opened.");
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
info("A control channel is established.");
gScript.sendAsyncMessage('trigger-control-channel-open');
resolve();
});
}),
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
info("The control channel is closed. " + aReason);
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
info("The control channel is opened.");
resolve();
});
}),
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
gScript.removeMessageListener('offer-sent', offerSentHandler);
ok(aIsValid, "A valid offer is sent out.");
gScript.sendAsyncMessage('trigger-incoming-transport');
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
info("The control channel is closed. " + aReason);
is(aReason, SpecialPowers.Cr.NS_OK, "The control channel is closed with NS_OK");
resolve();
});
}),
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
info("Data transport channel is initialized.");
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
});
new Promise((resolve) => {
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
gScript.removeMessageListener('offer-sent', offerSentHandler);
ok(aIsValid, "A valid offer is sent out.");
gScript.sendAsyncMessage('trigger-incoming-transport');
resolve();
});
}),
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
info("The data transport is closed. " + aReason);
});
new Promise((resolve) => {
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
info("Data transport channel is initialized.");
gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
resolve();
});
}),
new Promise((resolve) => {
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
info("The data transport is closed. " + aReason);
resolve();
});
}),
request.start().then(
function(aConnection) {
is(aConnection.state, "connecting", "The initial state should be connecting.");
aConnection.onclose = function() {
aConnection.onclose = null;
is(aConnection.state, "closed", "Connection should be closed.");
aResolve();
};
return new Promise((resolve) => {
aConnection.onclose = function() {
aConnection.onclose = null;
is(aConnection.state, "closed", "Connection should be closed.");
resolve();
};
});
},
function(aError) {
ok(false, "Error occurred when establishing a connection: " + aError);
teardown();
aReject();
}
);
});
),
]);
}
function testStartConnectionUnexpectedDataTransportClose() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-select');
});
info('--- testStartConnectionUnexpectedDataTransportClose ---');
return Promise.all([
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
info("A control channel is established.");
gScript.sendAsyncMessage('trigger-control-channel-open');
});
new Promise((resolve) => {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
gScript.removeMessageListener('device-prompt', devicePromptHandler);
info("Device prompt is triggered.");
gScript.sendAsyncMessage('trigger-device-prompt-select');
resolve();
});
}),
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
info("The control channel is opened.");
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
info("A control channel is established.");
gScript.sendAsyncMessage('trigger-control-channel-open');
resolve();
});
}),
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
info("The control channel is closed. " + aReason);
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
info("The control channel is opened.");
resolve();
});
}),
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
gScript.removeMessageListener('offer-sent', offerSentHandler);
ok(aIsValid, "A valid offer is sent out.");
gScript.sendAsyncMessage('trigger-incoming-transport');
});
new Promise((resolve) => {
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
info("The control channel is closed. " + aReason);
resolve();
});
}),
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
info("Data transport channel is initialized.");
gScript.sendAsyncMessage('trigger-data-transport-close', SpecialPowers.Cr.NS_ERROR_UNEXPECTED);
});
new Promise((resolve) => {
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
gScript.removeMessageListener('offer-sent', offerSentHandler);
ok(aIsValid, "A valid offer is sent out.");
info("recv offer-sent.");
gScript.sendAsyncMessage('trigger-incoming-transport');
resolve();
});
}),
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
info("The data transport is closed. " + aReason);
});
new Promise((resolve) => {
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
info("Data transport channel is initialized.");
gScript.sendAsyncMessage('trigger-data-transport-close', SpecialPowers.Cr.NS_ERROR_UNEXPECTED);
resolve();
});
}),
new Promise((resolve) => {
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
info("The data transport is closed. " + aReason);
resolve();
});
}),
request.start().then(
function(aConnection) {
is(aConnection.state, "connecting", "The initial state should be connecting.");
aConnection.onclose = function() {
aConnection.onclose = null;
is(aConnection.state, "closed", "Connection should be closed.");
aResolve();
};
return new Promise((resolve) => {
aConnection.onclose = function() {
aConnection.onclose = null;
is(aConnection.state, "closed", "Connection should be closed.");
resolve();
};
});
},
function(aError) {
ok(false, "Error occurred when establishing a connection: " + aError);
teardown();
aReject();
}
);
});
),
]);
}
function teardown() {

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

@ -488,4 +488,41 @@ H264::ComputeMaxRefFrames(const mozilla::MediaByteBuffer* aExtraData)
return maxRefFrames;
}
/* static */ H264::FrameType
H264::GetFrameType(const mozilla::MediaRawData* aSample)
{
if (!AnnexB::IsAVCC(aSample)) {
// We must have a valid AVCC frame with extradata.
return FrameType::INVALID;
}
MOZ_ASSERT(aSample->Data());
int nalLenSize = ((*aSample->mExtraData)[4] & 3) + 1;
ByteReader reader(aSample->Data(), aSample->Size());
while (reader.Remaining() >= nalLenSize) {
uint32_t nalLen;
switch (nalLenSize) {
case 1: nalLen = reader.ReadU8(); break;
case 2: nalLen = reader.ReadU16(); break;
case 3: nalLen = reader.ReadU24(); break;
case 4: nalLen = reader.ReadU32(); break;
}
if (!nalLen) {
continue;
}
const uint8_t* p = reader.Read(nalLen);
if (!p) {
return FrameType::INVALID;
}
if ((p[0] & 0x1f) == 5) {
// IDR NAL.
return FrameType::I_FRAME;
}
}
return FrameType::OTHER;
}
} // namespace mp4_demuxer

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

@ -350,6 +350,17 @@ public:
// clamped to be in the range of [4, 16]; otherwise return 4.
static uint32_t ComputeMaxRefFrames(const mozilla::MediaByteBuffer* aExtraData);
enum class FrameType
{
I_FRAME,
OTHER,
INVALID,
};
// Returns the frame type. Returns I_FRAME if the sample is an IDR
// (Instantaneous Decoding Refresh) Picture.
static FrameType GetFrameType(const mozilla::MediaRawData* aSample);
private:
static void vui_parameters(BitReader& aBr, SPSData& aDest);
// Read HRD parameters, all data is ignored.

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

@ -140,6 +140,7 @@ public class Tab {
mPluginViews = new ArrayList<View>();
mState = shouldShowProgress(url) ? STATE_LOADING : STATE_SUCCESS;
mLoadProgress = LOAD_PROGRESS_INIT;
mIconRequestBuilder = Icons.with(mAppContext).pageUrl(mUrl);
updateBookmark();
}
@ -452,13 +453,6 @@ public class Tab {
return;
}
if (mIconRequestBuilder == null) {
// For the first internal homepage we might want to load a favicon without ever receiving
// a location change event first. In this case we didn't start to build a request yet.
// Let's do that now.
mIconRequestBuilder = Icons.with(mAppContext).pageUrl(mUrl);
}
mRunningIconRequest = mIconRequestBuilder
.build()
.execute(new IconCallback() {

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

@ -312,6 +312,7 @@
@BINPATH@/components/addonManager.js
@BINPATH@/components/amContentHandler.js
@BINPATH@/components/amInstallTrigger.js
@BINPATH@/components/amWebAPI.js
@BINPATH@/components/amWebInstallListener.js
@BINPATH@/components/nsBlocklistService.js
#ifndef RELEASE_BUILD

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

@ -638,8 +638,7 @@ class TaskCache(CacheManager):
# extract the build ID; we use the .ini files embedded in the
# downloaded artifact for this. We could also use the uploaded
# public/build/buildprops.json for this purpose.
replDict = {'taskId': taskId, 'name': artifact_name}
url = self._queue.buildUrl('getLatestArtifact', replDict=replDict)
url = self._queue.buildUrl('getLatestArtifact', taskId, artifact_name)
urls.append(url)
if not urls:
raise ValueError('Task for {key} existed, but no artifacts found!'.format(key=key))

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

@ -122,7 +122,6 @@ UNIFIED_SOURCES += [
'nsPKCS11Slot.cpp',
'nsPKCS12Blob.cpp',
'nsProtectedAuthThread.cpp',
'nsPSMBackgroundThread.cpp',
'nsRandomGenerator.cpp',
'nsSecureBrowserUIImpl.cpp',
'nsSecurityHeaderParser.cpp',

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

@ -1,90 +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/. */
#include "nsPSMBackgroundThread.h"
#include "nsThreadUtils.h"
using namespace mozilla;
void nsPSMBackgroundThread::nsThreadRunner(void *arg)
{
nsPSMBackgroundThread *self = static_cast<nsPSMBackgroundThread *>(arg);
PR_SetCurrentThreadName(self->mName.BeginReading());
self->Run();
}
nsPSMBackgroundThread::nsPSMBackgroundThread()
: mThreadHandle(nullptr),
mMutex("nsPSMBackgroundThread.mMutex"),
mCond(mMutex, "nsPSMBackgroundThread.mCond"),
mExitState(ePSMThreadRunning)
{
}
nsresult nsPSMBackgroundThread::startThread(const nsCSubstring & name)
{
mName = name;
mThreadHandle = PR_CreateThread(PR_USER_THREAD, nsThreadRunner, static_cast<void*>(this),
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
NS_ASSERTION(mThreadHandle, "Could not create nsPSMBackgroundThread\n");
if (!mThreadHandle)
return NS_ERROR_OUT_OF_MEMORY;
return NS_OK;
}
nsPSMBackgroundThread::~nsPSMBackgroundThread()
{
}
bool
nsPSMBackgroundThread::exitRequested(const MutexAutoLock & /*proofOfLock*/) const
{
return exitRequestedNoLock();
}
nsresult
nsPSMBackgroundThread::postStoppedEventToMainThread(
MutexAutoLock const & /*proofOfLock*/)
{
NS_ASSERTION(PR_GetCurrentThread() == mThreadHandle,
"Background thread stopped from another thread");
mExitState = ePSMThreadStopped;
// requestExit is waiting for an event, so give it one.
return NS_DispatchToMainThread(new Runnable());
}
void nsPSMBackgroundThread::requestExit()
{
NS_ASSERTION(NS_IsMainThread(),
"nsPSMBackgroundThread::requestExit called off main thread.");
if (!mThreadHandle)
return;
{
MutexAutoLock threadLock(mMutex);
if (mExitState < ePSMThreadStopRequested) {
mExitState = ePSMThreadStopRequested;
mCond.NotifyAll();
}
}
nsCOMPtr<nsIThread> mainThread = do_GetCurrentThread();
for (;;) {
{
MutexAutoLock threadLock(mMutex);
if (mExitState == ePSMThreadStopped)
break;
}
NS_ProcessPendingEvents(mainThread, PR_MillisecondsToInterval(50));
}
PR_JoinThread(mThreadHandle);
mThreadHandle = nullptr;
}

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

@ -1,56 +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/. */
#ifndef _NSPSMBACKGROUNDTHREAD_H_
#define _NSPSMBACKGROUNDTHREAD_H_
#include "nspr.h"
#include "nscore.h"
#include "mozilla/CondVar.h"
#include "mozilla/Mutex.h"
#include "nsNSSComponent.h"
class nsPSMBackgroundThread
{
protected:
static void nsThreadRunner(void *arg);
virtual void Run(void) = 0;
// used to join the thread
PRThread *mThreadHandle;
// Shared mutex used for condition variables,
// and to protect access to mExitState.
// Derived classes may use it to protect additional
// resources.
mozilla::Mutex mMutex;
// Used to signal the thread's Run loop when a job is added
// and/or exit is requested.
mozilla::CondVar mCond;
bool exitRequested(::mozilla::MutexAutoLock const & proofOfLock) const;
bool exitRequestedNoLock() const { return mExitState != ePSMThreadRunning; }
nsresult postStoppedEventToMainThread(::mozilla::MutexAutoLock const & proofOfLock);
private:
enum {
ePSMThreadRunning = 0,
ePSMThreadStopRequested = 1,
ePSMThreadStopped = 2
} mExitState;
// The thread's name.
nsCString mName;
public:
nsPSMBackgroundThread();
virtual ~nsPSMBackgroundThread();
nsresult startThread(const nsCSubstring & name);
void requestExit();
};
#endif

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

@ -150,7 +150,7 @@
"Entrust Root Certification Authority - EC1",
"Entrust Root Certification Authority - G2",
"Entrust.net Premium 2048 Secure Server CA",
"Equifax Secure CA",
// "Equifax Secure Certificate Authority",
"GeoTrust Global CA",
"GeoTrust Global CA 2",
"GeoTrust Primary Certification Authority",

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

@ -57,14 +57,31 @@ function downloadRoots() {
return roots;
}
function makeFormattedNickname(cert) {
if (cert.nickname.startsWith("Builtin Object Token:")) {
return `"${cert.nickname.substring("Builtin Object Token:".length)}"`;
}
// Otherwise, this isn't a built-in and we have to comment it out.
if (cert.commonName) {
return `// "${cert.commonName}"`;
}
if (cert.organizationalUnit) {
return `// "${cert.organizationalUnit}"`;
}
if (cert.organization) {
return `// "${cert.organization}"`;
}
throw new Error(`couldn't make nickname for ${cert.subjectName}`);
}
var roots = downloadRoots();
var rootNicknames = [];
for (var root of roots) {
rootNicknames.push(root.nickname.substring("Builtin Object Token:".length));
rootNicknames.push(makeFormattedNickname(root));
}
rootNicknames.sort(function(rootA, rootB) {
let rootALowercase = rootA.toLowerCase();
let rootBLowercase = rootB.toLowerCase();
let rootALowercase = rootA.toLowerCase().replace(/(^[^"]*")|"/g, "");
let rootBLowercase = rootB.toLowerCase().replace(/(^[^"]*")|"/g, "");
if (rootALowercase < rootBLowercase) {
return -1;
}
@ -82,7 +99,7 @@ for (var nickname of rootNicknames) {
dump(",\n");
}
first = false;
dump(" \"" + nickname + "\"");
dump(" " + nickname);
}
dump("\n");
dump(" ]\n");

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

@ -20,7 +20,7 @@
this.EXPORTED_SYMBOLS = ["loadKinto"];
/*
* Version 4.0.3 - 8100433
* Version 4.0.4 - 03f82da
*/
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.loadKinto = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
@ -1805,15 +1805,7 @@ class Collection {
});
}).then(syncResultObject => {
syncResultObject.lastModified = changeObject.lastModified;
// Don't persist lastModified value if any conflict or error occured
if (!syncResultObject.ok) {
return syncResultObject;
}
// No conflict occured, persist collection's lastModified value
return this.db.saveLastModified(syncResultObject.lastModified).then(lastModified => {
this._lastModified = lastModified;
return syncResultObject;
});
return syncResultObject;
});
}
@ -2216,7 +2208,18 @@ class Collection {
// Avoid redownloading our own changes during the last pull.
const pullOpts = _extends({}, options, { exclude: result.published });
return this.pullChanges(client, result, pullOpts);
}).then(syncResultObject => {
// Don't persist lastModified value if any conflict or error occured
if (!syncResultObject.ok) {
return syncResultObject;
}
// No conflict occured, persist collection's lastModified value
return this.db.saveLastModified(syncResultObject.lastModified).then(lastModified => {
this._lastModified = lastModified;
return syncResultObject;
});
});
// Ensure API default remote is reverted if a custom one's been used
return (0, _utils.pFinally)(syncPromise, () => this.api.remote = previousRemote);
}

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

@ -0,0 +1,201 @@
/* 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 Cu = Components.utils;
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/bookmark_utils.js");
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-sync/main.js");
this.EXPORTED_SYMBOLS = ["CollectionValidator", "CollectionProblemData"];
class CollectionProblemData {
constructor() {
this.missingIDs = 0;
this.duplicates = [];
this.clientMissing = [];
this.serverMissing = [];
this.serverDeleted = [];
this.serverUnexpected = [];
this.differences = [];
}
/**
* Produce a list summarizing problems found. Each entry contains {name, count},
* where name is the field name for the problem, and count is the number of times
* the problem was encountered.
*
* Validation has failed if all counts are not 0.
*/
getSummary() {
return [
{ name: "clientMissing", count: this.clientMissing.length },
{ name: "serverMissing", count: this.serverMissing.length },
{ name: "serverDeleted", count: this.serverDeleted.length },
{ name: "serverUnexpected", count: this.serverUnexpected.length },
{ name: "differences", count: this.differences.length },
{ name: "missingIDs", count: this.missingIDs },
{ name: "duplicates", count: this.duplicates.length }
];
}
}
class CollectionValidator {
// Construct a generic collection validator. This is intended to be called by
// subclasses.
// - name: Name of the engine
// - idProp: Property that identifies a record. That is, if a client and server
// record have the same value for the idProp property, they should be
// compared against eachother.
// - props: Array of properties that should be compared
constructor(name, idProp, props) {
this.name = name;
this.props = props;
this.idProp = idProp;
}
// Should a custom ProblemData type be needed, return it here.
emptyProblemData() {
return new CollectionProblemData();
}
getServerItems(engine) {
let collection = engine._itemSource();
let collectionKey = engine.service.collectionKeys.keyForCollection(engine.name);
collection.full = true;
let items = [];
collection.recordHandler = function(item) {
item.decrypt(collectionKey);
items.push(item.cleartext);
};
collection.get();
return items;
}
// Should return a promise that resolves to an array of client items.
getClientItems() {
return Promise.reject("Must implement");
}
// Turn the client item into something that can be compared with the server item,
// and is also safe to mutate.
normalizeClientItem(item) {
return Cu.cloneInto(item, {});
}
// Turn the server item into something that can be easily compared with the client
// items.
normalizeServerItem(item) {
return item;
}
// Return whether or not a server item should be present on the client. Expected
// to be overridden.
clientUnderstands(item) {
return true;
}
// Return whether or not a client item should be present on the server. Expected
// to be overridden
syncedByClient(item) {
return true;
}
// Compare the server item and the client item, and return a list of property
// names that are different. Can be overridden if needed.
getDifferences(client, server) {
let differences = [];
for (let prop of this.props) {
let clientProp = client[prop];
let serverProp = server[prop];
if ((clientProp || "") !== (serverProp || "")) {
differences.push(prop);
}
}
return differences;
}
// Returns an object containing
// problemData: an instance of the class returned by emptyProblemData(),
// clientRecords: Normalized client records
// records: Normalized server records,
// deletedRecords: Array of ids that were marked as deleted by the server.
compareClientWithServer(clientItems, serverItems) {
clientItems = clientItems.map(item => this.normalizeClientItem(item));
serverItems = serverItems.map(item => this.normalizeServerItem(item));
let problems = this.emptyProblemData();
let seenServer = new Map();
let serverDeleted = new Set();
let allRecords = new Map();
for (let record of serverItems) {
let id = record[this.idProp];
if (!id) {
++problems.missingIDs;
continue;
}
if (record.deleted) {
serverDeleted.add(record);
} else {
let possibleDupe = seenServer.get(id);
if (possibleDupe) {
problems.duplicates.push(id);
} else {
seenServer.set(id, record);
allRecords.set(id, { server: record, client: null, });
}
record.understood = this.clientUnderstands(record);
}
}
let recordPairs = [];
let seenClient = new Map();
for (let record of clientItems) {
let id = record[this.idProp];
record.shouldSync = this.syncedByClient(record);
seenClient.set(id, record);
let combined = allRecords.get(id);
if (combined) {
combined.client = record;
} else {
allRecords.set(id, { client: record, server: null });
}
}
for (let [id, { server, client }] of allRecords) {
if (!client && !server) {
throw new Error("Impossible: no client or server record for " + id);
} else if (server && !client) {
if (server.understood) {
problems.clientMissing.push(id);
}
} else if (client && !server) {
if (client.shouldSync) {
problems.serverMissing.push(id);
}
} else {
if (!client.shouldSync) {
if (!problems.serverUnexpected.includes(id)) {
problems.serverUnexpected.push(id);
}
continue;
}
let differences = this.getDifferences(client, server);
if (differences && differences.length) {
problems.differences.push({ id, differences });
}
}
}
return {
problemData: problems,
clientRecords: clientItems,
records: serverItems,
deletedRecords: [...serverDeleted]
};
}
}

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

@ -2,12 +2,13 @@
* 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/. */
this.EXPORTED_SYMBOLS = ['PasswordEngine', 'LoginRec'];
this.EXPORTED_SYMBOLS = ['PasswordEngine', 'LoginRec', 'PasswordValidator'];
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/collection_validator.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-common/async.js");
@ -325,3 +326,46 @@ PasswordTracker.prototype = {
}
},
};
class PasswordValidator extends CollectionValidator {
constructor() {
super("passwords", "id", [
"hostname",
"formSubmitURL",
"httpRealm",
"password",
"passwordField",
"username",
"usernameField",
]);
}
getClientItems() {
let logins = Services.logins.getAllLogins({});
let syncHosts = Utils.getSyncCredentialsHosts()
let result = logins.map(l => l.QueryInterface(Ci.nsILoginMetaInfo))
.filter(l => !syncHosts.has(l.hostname));
return Promise.resolve(result);
}
normalizeClientItem(item) {
return {
id: item.guid,
guid: item.guid,
hostname: item.hostname,
formSubmitURL: item.formSubmitURL,
httpRealm: item.httpRealm,
password: item.password,
passwordField: item.passwordField,
username: item.username,
usernameField: item.usernameField,
original: item,
}
}
normalizeServerItem(item) {
return Object.assign({ guid: item.id }, item);
}
}

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

@ -22,6 +22,7 @@ EXTRA_JS_MODULES['services-sync'] += [
'modules/bookmark_utils.js',
'modules/bookmark_validator.js',
'modules/browserid_identity.js',
'modules/collection_validator.js',
'modules/engines.js',
'modules/FxaMigrator.jsm',
'modules/identity.js',

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

@ -0,0 +1,158 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource://services-sync/engines/passwords.js");
function getDummyServerAndClient() {
return {
server: [
{
id: "11111",
guid: "11111",
hostname: "https://www.11111.com",
formSubmitURL: "https://www.11111.com/login",
password: "qwerty123",
passwordField: "pass",
username: "foobar",
usernameField: "user",
httpRealm: null,
},
{
id: "22222",
guid: "22222",
hostname: "https://www.22222.org",
formSubmitURL: "https://www.22222.org/login",
password: "hunter2",
passwordField: "passwd",
username: "baz12345",
usernameField: "user",
httpRealm: null,
},
{
id: "33333",
guid: "33333",
hostname: "https://www.33333.com",
formSubmitURL: "https://www.33333.com/login",
password: "p4ssw0rd",
passwordField: "passwad",
username: "quux",
usernameField: "user",
httpRealm: null,
},
],
client: [
{
id: "11111",
guid: "11111",
hostname: "https://www.11111.com",
formSubmitURL: "https://www.11111.com/login",
password: "qwerty123",
passwordField: "pass",
username: "foobar",
usernameField: "user",
httpRealm: null,
},
{
id: "22222",
guid: "22222",
hostname: "https://www.22222.org",
formSubmitURL: "https://www.22222.org/login",
password: "hunter2",
passwordField: "passwd",
username: "baz12345",
usernameField: "user",
httpRealm: null,
},
{
id: "33333",
guid: "33333",
hostname: "https://www.33333.com",
formSubmitURL: "https://www.33333.com/login",
password: "p4ssw0rd",
passwordField: "passwad",
username: "quux",
usernameField: "user",
httpRealm: null,
}
]
};
}
add_test(function test_valid() {
let { server, client } = getDummyServerAndClient();
let validator = new PasswordValidator();
let { problemData, clientRecords, records, deletedRecords } =
validator.compareClientWithServer(client, server);
equal(clientRecords.length, 3);
equal(records.length, 3)
equal(deletedRecords.length, 0);
deepEqual(problemData, validator.emptyProblemData());
run_next_test();
});
add_test(function test_missing() {
let validator = new PasswordValidator();
{
let { server, client } = getDummyServerAndClient();
client.pop();
let { problemData, clientRecords, records, deletedRecords } =
validator.compareClientWithServer(client, server);
equal(clientRecords.length, 2);
equal(records.length, 3)
equal(deletedRecords.length, 0);
let expected = validator.emptyProblemData();
expected.clientMissing.push("33333");
deepEqual(problemData, expected);
}
{
let { server, client } = getDummyServerAndClient();
server.pop();
let { problemData, clientRecords, records, deletedRecords } =
validator.compareClientWithServer(client, server);
equal(clientRecords.length, 3);
equal(records.length, 2)
equal(deletedRecords.length, 0);
let expected = validator.emptyProblemData();
expected.serverMissing.push("33333");
deepEqual(problemData, expected);
}
run_next_test();
});
add_test(function test_deleted() {
let { server, client } = getDummyServerAndClient();
let deletionRecord = { id: "444444", guid: "444444", deleted: true };
server.push(deletionRecord);
let validator = new PasswordValidator();
let { problemData, clientRecords, records, deletedRecords } =
validator.compareClientWithServer(client, server);
equal(clientRecords.length, 3);
equal(records.length, 4);
deepEqual(deletedRecords, [deletionRecord]);
let expected = validator.emptyProblemData();
deepEqual(problemData, expected);
run_next_test();
});
function run_test() {
run_next_test();
}

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

@ -170,6 +170,7 @@ skip-if = debug
skip-if = debug
[test_places_guid_downgrade.js]
[test_password_store.js]
[test_password_validator.js]
[test_password_tracker.js]
# Too many intermittent "ASSERTION: thread pool wasn't shutdown: '!mPool'" (bug 804479)
skip-if = debug

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

@ -24,6 +24,7 @@ Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/main.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/bookmark_validator.js");
Cu.import("resource://services-sync/engines/passwords.js");
// TPS modules
Cu.import("resource://tps/logger.jsm");
@ -112,6 +113,7 @@ var TPS = {
_usSinceEpoch: 0,
_requestedQuit: false,
shouldValidateBookmarks: false,
shouldValidatePasswords: false,
_init: function TPS__init() {
// Check if Firefox Accounts is enabled
@ -416,6 +418,7 @@ var TPS = {
},
HandlePasswords: function (passwords, action) {
this.shouldValidatePasswords = true;
try {
for (let password of passwords) {
let password_id = -1;
@ -656,14 +659,47 @@ var TPS = {
Logger.logInfo("Bookmark validation finished");
},
ValidatePasswords() {
let serverRecordDumpStr;
try {
Logger.logInfo("About to perform password validation");
let pwEngine = Weave.Service.engineManager.get("passwords");
let validator = new PasswordValidator();
let serverRecords = validator.getServerItems(pwEngine);
let clientRecords = Async.promiseSpinningly(validator.getClientItems());
serverRecordDumpStr = JSON.stringify(serverRecords);
let { problemData } = validator.compareClientWithServer(clientRecords, serverRecords);
for (let { name, count } of problemData.getSummary()) {
if (count) {
Logger.logInfo(`Validation problem: "${name}": ${JSON.stringify(problemData[name])}`);
}
Logger.AssertEqual(count, 0, `Password validation error of type ${name}`);
}
} catch (e) {
// Dump the client records (should always be doable)
DumpPasswords();
// Dump the server records if gotten them already.
if (serverRecordDumpStr) {
Logger.logInfo("Server password records:\n" + serverRecordDumpStr + "\n");
}
this.DumpError("Password validation failed", e);
}
Logger.logInfo("Password validation finished");
},
RunNextTestAction: function() {
try {
if (this._currentAction >=
this._phaselist[this._currentPhase].length) {
// Run necessary validations and then finish up
if (this.shouldValidateBookmarks) {
// Run bookmark validation and then finish up
this.ValidateBookmarks();
}
if (this.shouldValidatePasswords) {
this.ValidatePasswords();
}
// we're all done
Logger.logInfo("test phase " + this._currentPhase + ": " +
(this._errors ? "FAIL" : "PASS"));
@ -1100,6 +1136,9 @@ var Passwords = {
},
verifyNot: function Passwords__verifyNot(passwords) {
this.HandlePasswords(passwords, ACTION_VERIFY_NOT);
},
skipValidation() {
TPS.shouldValidatePasswords = false;
}
};

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

@ -25,6 +25,7 @@ task:
scopes:
- 'docker-worker:cache:level-{{level}}-hg-shared'
- 'docker-worker:cache:level-{{level}}-checkouts'
- 'secrets:get:project/taskcluster/gecko/hgfingerprint'
payload:
# Thirty minutes should be enough for lint checks
@ -39,6 +40,9 @@ task:
GECKO_HEAD_REPOSITORY: '{{head_repository}}'
GECKO_HEAD_REV: '{{head_rev}}'
features:
taskclusterProxy: true
extra:
build_product: '{{build_product}}'
build_name: {{build_name}}

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

@ -19,11 +19,16 @@ import argparse
import datetime
import errno
import grp
import json
import os
import pwd
import re
import subprocess
import sys
import urllib2
FINGERPRINT_URL = 'http://taskcluster/secrets/v1/secret/project/taskcluster/gecko/hgfingerprint'
def print_line(prefix, m):
@ -88,8 +93,28 @@ def vcs_checkout(args):
print('revision is not specified for checkout')
sys.exit(1)
# Obtain certificate fingerprints.
try:
print_line(b'vcs', 'fetching hg.mozilla.org fingerprint from %s\n' %
FINGERPRINT_URL)
res = urllib2.urlopen(FINGERPRINT_URL, timeout=10)
secret = res.read()
except urllib2.URLError as e:
print('error retrieving hg fingerprint: %s' % e)
sys.exit(1)
try:
secret = json.loads(secret, encoding='utf-8')
except ValueError:
print('invalid JSON in hg fingerprint secret')
sys.exit(1)
hgmo_fingerprint = secret['secret']['fingerprints'].encode('ascii')
res = run_and_prefix_output(b'vcs', [
b'/usr/bin/hg', b'robustcheckout',
b'/usr/bin/hg',
b'--config', b'hostsecurity.hg.mozilla.org:fingerprints=%s' % hgmo_fingerprint,
b'robustcheckout',
b'--sharebase', b'/home/worker/hg-shared',
b'--purge',
b'--upstream', base_repo,

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

@ -101,8 +101,7 @@ def fetch_artifact(queue, task_id, run_id, name, dest_dir):
write it to a file in dest_dir, and return the path to the written
file.
'''
replDict = {'taskId': task_id, 'runId': run_id, 'name': name}
url = queue.buildUrl('getArtifact', replDict=replDict)
url = queue.buildUrl('getArtifact', task_id, run_id, name)
fn = os.path.join(dest_dir, os.path.basename(name))
print('Fetching %s...' % name)
try:

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

@ -0,0 +1,14 @@
[mediasource-sequencemode-append-buffer.html]
type: testharness
prefs: [media.mediasource.enabled:true]
[Test sequence AppendMode appendBuffer(first media segment)]
expected:
if (os == "win") and (version == "5.1.2600"): FAIL
[Test sequence AppendMode appendBuffer(second media segment)]
expected:
if (os == "win") and (version == "5.1.2600"): FAIL
[Test sequence AppendMode appendBuffer(second media segment, then first media segment)]
expected: FAIL

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

@ -261,8 +261,8 @@
removeAppendedDataTests(function(test, mediaSource, sourceBuffer, duration, subType, segmentInfo)
{
var expectations = {
webm: ("{ [3.187, " + duration + ") }"),
mp4: ("{ [3.187, " + duration + ") }"),
webm: ("{ [3.315, " + duration + ") }"),
mp4: ("{ [3.298, " + duration + ") }"),
};
// Note: Range doesn't start exactly at the end of the remove range because there isn't
@ -274,8 +274,8 @@
{
var start = Math.max(segmentInfo.media[0].timev, segmentInfo.media[0].timea).toFixed(3);
var expectations = {
webm: ("{ [" + start + ", 1.012) [3.187, " + duration + ") }"),
mp4: ("{ [" + start + ", 0.996) [3.187, " + duration + ") }"),
webm: ("{ [" + start + ", 1.005) [3.315, " + duration + ") }"),
mp4: ("{ [" + start + ", 0.997) [3.298, " + duration + ") }"),
};
// Note: The first resulting range ends slightly after start because the removal algorithm only removes
@ -288,7 +288,7 @@
{
var start = Math.max(segmentInfo.media[0].timev, segmentInfo.media[0].timea).toFixed(3);
var expectations = {
webm: "{ [" + start + ", 1.029) }",
webm: "{ [" + start + ", 1.013) }",
mp4: "{ [" + start + ", 1.022) }",
};

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

@ -60,7 +60,7 @@
// Prior to EOS, the buffered range end time may not have fully reached the next media
// segment's timecode (adjusted by any timestampOffset). It should not exceed it though.
// Therefore, an exact assertBufferedEquals() will not work here.
assert_equals(sourceBuffer.buffered.length, 1, "sourceBuffer.buffered has 1 range before EOS");
assert_greater_than(sourceBuffer.buffered.length, 0, "sourceBuffer.buffered has at least 1 range before EOS");
assert_equals(threeDecimalPlaces(sourceBuffer.buffered.start(0)),
threeDecimalPlaces(expectedBufferedRangeStartTime),
"sourceBuffer.buffered range begins where expected before EOS");

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

@ -1,63 +1,38 @@
(function(window) {
var SEGMENT_INFO_LIST = [
{
url: 'mp4/test.mp4',
type: 'video/mp4; codecs="mp4a.40.2,avc1.4d400d"',
duration: 6.0756,
init: { offset: 0, size: 1197 },
duration: 6.549,
init: { offset: 0, size: 1413 },
media: [
{ offset: 1241, size: 17845, timev: 0.033200, timea: 0, endtimev: 0.531200, endtimea: 0.510839 },
{ offset: 19130, size: 5551, timev: 0.464800, timea: 0.510839, endtimev: 0.796800, endtimea: 0.812698 },
{ offset: 24725, size: 10944, timev: 0.796800, timea: 0.812698, endtimev: 0.929600, endtimea: 0.905578 },
{ offset: 35713, size: 7131, timev: 0.863200, timea: 0.905578, endtimev: 1.195200, endtimea: 1.184217 },
{ offset: 42888, size: 2513, timev: 1.128800, timea: 1.184217, endtimev: 1.328000, endtimea: 1.300317 },
{ offset: 45457, size: 3022, timev: 1.261600, timea: 1.300317, endtimev: 1.460800, endtimea: 1.509297 },
{ offset: 48479, size: 815, timev: 1.494000, timea: 1.509297, endtimev: 1.527200, endtimea: 1.532517 },
{ offset: 49338, size: 2818, timev: 1.460800, timea: 1.532517, endtimev: 1.626800, endtimea: 1.648616 },
{ offset: 52200, size: 11581, timev: 1.626800, timea: 1.648616, endtimev: 1.792800, endtimea: 1.764716 },
{ offset: 63825, size: 3003, timev: 1.726400, timea: 1.764716, endtimev: 1.925600, endtimea: 1.973696 },
{ offset: 66872, size: 6390, timev: 1.925600, timea: 1.973696, endtimev: 2.191200, endtimea: 2.159455 },
{ offset: 73306, size: 3740, timev: 2.124800, timea: 2.159455, endtimev: 2.390400, endtimea: 2.368435 },
{ offset: 77102, size: 11779, timev: 2.324000, timea: 2.368435, endtimev: 2.523200, endtimea: 2.577414 },
{ offset: 88881, size: 851, timev: 2.556400, timea: 2.577414, endtimev: 2.589600, endtimea: 2.600634 },
{ offset: 89776, size: 4236, timev: 2.523200, timea: 2.600634, endtimev: 2.788800, endtimea: 2.832834 },
{ offset: 94056, size: 9538, timev: 2.788800, timea: 2.832834, endtimev: 3.187200, endtimea: 3.204353 },
{ offset: 103638, size: 13295, timev: 3.187200, timea: 3.204353, endtimev: 3.452800, endtimea: 3.436553 },
{ offset: 116977, size: 309, timev: 3.386400, timea: 3.436553, endtimev: 3.419600, endtimea: 3.506213 },
{ offset: 117330, size: 5806, timev: 3.452800, timea: 3.506213, endtimev: 3.784800, endtimea: 3.831292 },
{ offset: 123180, size: 4392, timev: 3.784800, timea: 3.831292, endtimev: 4.017200, endtimea: 4.040272 },
{ offset: 127616, size: 15408, timev: 4.017200, timea: 4.040272, endtimev: 4.249600, endtimea: 4.295691 },
{ offset: 143068, size: 9899, timev: 4.249600, timea: 4.295691, endtimev: 4.814000, endtimea: 4.829750 },
{ offset: 153011, size: 11562, timev: 4.814000, timea: 4.829750, endtimev: 4.980000, endtimea: 5.015510 },
{ offset: 164617, size: 7398, timev: 4.980000, timea: 5.015510, endtimev: 5.245600, endtimea: 5.294149 },
{ offset: 172059, size: 5698, timev: 5.245600, timea: 5.294149, endtimev: 5.577600, endtimea: 5.549569 },
{ offset: 177801, size: 11682, timev: 5.511200, timea: 5.549569, endtimev: 5.710400, endtimea: 5.758548 },
{ offset: 189527, size: 3023, timev: 5.710400, timea: 5.758548, endtimev: 5.909600, endtimea: 5.897868 },
{ offset: 192594, size: 5726, timev: 5.843200, timea: 5.897868, endtimev: 6.075600, endtimea: 6.037188 },
{ offset: 1413, size: 24034, timev: 0.095000, timea: 0, endtimev: 0.896666, endtimea: 0.882358 },
{ offset: 25447, size: 21757, timev: 0.896666, timea: 0.882358, endtimev: 1.696666, endtimea: 1.671836 },
{ offset: 47204, size: 23591, timev: 1.696666, timea: 1.671836, endtimev: 2.498333, endtimea: 2.461315 },
{ offset: 70795, size: 22614, timev: 2.498333, timea: 2.461315, endtimev: 3.298333, endtimea: 3.297233 },
{ offset: 93409, size: 18353, timev: 3.298333, timea: 3.297233, endtimev: 4.100000, endtimea: 4.086712},
{ offset: 111762, size: 23935, timev: 4.100000, timea: 4.086712, endtimev: 4.900000, endtimea: 4.876190 },
{ offset: 135697, size: 21911, timev: 4.900000, timea: 4.876190, endtimev: 5.701666, endtimea: 5.665668 },
{ offset: 157608, size: 23776, timev: 5.701666, timea: 5.665668, endtimev: 6.501666, endtimea: 6.501587 },
{ offset: 181384, size: 5843, timev: 6.501666, timea: 6.501587, endtimev: 6.501666, endtimea: 6.501678 },
]
},
{
url: 'webm/test.webm',
type: 'video/webm; codecs="vp8, vorbis"',
duration: 6.042,
init: { offset: 0, size: 4357 },
duration: 6.552,
init: { offset: 0, size: 4116 },
media: [
{ offset: 4357, size: 11830, timev: 0, timea: 0, endtimev: 0.398000, endtimea: 0.384000 },
{ offset: 16187, size: 12588, timev: 0.398000, timea: 0.385000, endtimev: 0.798000, endtimea: 0.779000 },
{ offset: 28775, size: 14588, timev: 0.797000, timea: 0.779000, endtimev: 1.195000, endtimea: 1.174000 },
{ offset: 43363, size: 13023, timev: 1.195000, timea: 1.174000, endtimev: 1.593000, endtimea: 1.592000 },
{ offset: 56386, size: 13127, timev: 1.594000, timea: 1.592000, endtimev: 1.992000, endtimea: 1.988000 },
{ offset: 69513, size: 14456, timev: 1.992000, timea: 1.987000, endtimev: 2.390000, endtimea: 2.381000 },
{ offset: 83969, size: 13458, timev: 2.390000, timea: 2.381000, endtimev: 2.790000, endtimea: 2.776000 },
{ offset: 97427, size: 14566, timev: 2.789000, timea: 2.776000, endtimev: 3.187000, endtimea: 3.171000 },
{ offset: 111993, size: 13201, timev: 3.187000, timea: 3.171000, endtimev: 3.585000, endtimea: 3.565000 },
{ offset: 125194, size: 14061, timev: 3.586000, timea: 3.566000, endtimev: 3.984000, endtimea: 3.960000 },
{ offset: 139255, size: 15353, timev: 3.984000, timea: 3.960000, endtimev: 4.382000, endtimea: 4.378000 },
{ offset: 154608, size: 13618, timev: 4.382000, timea: 4.378000, endtimev: 4.782000, endtimea: 4.773000 },
{ offset: 168226, size: 15094, timev: 4.781000, timea: 4.773000, endtimev: 5.179000, endtimea: 5.169000 },
{ offset: 183320, size: 13069, timev: 5.179000, timea: 5.168000, endtimev: 5.577000, endtimea: 5.562000 },
{ offset: 196389, size: 13788, timev: 5.578000, timea: 5.563000, endtimev: 5.976000, endtimea: 5.957000 },
{ offset: 210177, size: 9009, timev: 5.976000, timea: 5.957000, endtimev: 6.042000, endtimea: 6.050000 },
{ offset: 4116, size: 26583, timev: 0.112000, timea: 0, endtimev: 0.913000, endtimea: 0.912000 },
{ offset: 30699, size: 20555, timev: 0.913000, timea: 0.912000, endtimev: 1.714000, endtimea: 1.701000 },
{ offset: 51254, size: 22668, timev: 1.714000, timea: 1.701000, endtimev: 2.515000, endtimea: 2.514000 },
{ offset: 73922, size: 21943, timev: 2.515000, timea: 2.514000, endtimev: 3.315000, endtimea: 3.303000 },
{ offset: 95865, size: 23015, timev: 3.315000, timea: 3.303000, endtimev: 4.116000, endtimea: 4.093000},
{ offset: 118880, size: 20406, timev: 4.116000, timea: 4.093000, endtimev: 4.917000, endtimea: 4.906000 },
{ offset: 139286, size: 21537, timev: 4.917000, timea: 4.906000, endtimev: 5.718000, endtimea: 5.695000 },
{ offset: 160823, size: 24027, timev: 5.718000, timea: 5.695000, endtimev: 6.519000, endtimea: 6.508000 },
{ offset: 184850, size: 5955, timev: 6.519000, timea: 6.508000, endtimev: 6.577000, endtimea: 6.577000},
],
}
];

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

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

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

@ -269,22 +269,22 @@ var ExtensionTestUtils = {
addonManagerStarted: false,
startAddonManager() {
if (this.addonManagerStarted) {
return;
}
this.addonManagerStarted = true;
let appInfo = {};
Cu.import("resource://testing-common/AppInfo.jsm", appInfo);
appInfo.updateAppInfo({
mockAppInfo() {
const {updateAppInfo} = Cu.import("resource://testing-common/AppInfo.jsm", {});
updateAppInfo({
ID: "xpcshell@tests.mozilla.org",
name: "XPCShell",
version: "48",
platformVersion: "48",
});
},
startAddonManager() {
if (this.addonManagerStarted) {
return;
}
this.addonManagerStarted = true;
this.mockAppInfo();
let manager = Cc["@mozilla.org/addons/integration;1"].getService(Ci.nsIObserver)
.QueryInterface(Ci.nsITimerCallback);

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

@ -2,8 +2,24 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
XPCOMUtils.defineLazyGetter(this, "strBundle", function() {
const stringSvc = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
return stringSvc.createBundle("chrome://global/locale/extensions.properties");
});
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "promptService",
"@mozilla.org/embedcomp/prompt-service;1",
"nsIPromptService");
function _(key, ...args) {
if (args.length) {
return strBundle.formatStringFromName(key, args, args.length);
}
return strBundle.GetStringFromName(key);
}
function installType(addon) {
if (addon.temporarilyInstalled) {
@ -52,11 +68,42 @@ extensions.registerSchemaAPI("management", "addon_parent", context => {
}
resolve(extInfo);
} catch (e) {
reject(e);
} catch (err) {
reject(err);
}
}));
},
uninstallSelf: function(options) {
return new Promise((resolve, reject) => {
if (options && options.showConfirmDialog) {
let message = _("uninstall.confirmation.message", extension.name);
if (options.dialogMessage) {
message = `${options.dialogMessage}\n${message}`;
}
let title = _("uninstall.confirmation.title", extension.name);
let buttonFlags = promptService.BUTTON_POS_0 * promptService.BUTTON_TITLE_IS_STRING +
promptService.BUTTON_POS_1 * promptService.BUTTON_TITLE_IS_STRING;
let button0Title = _("uninstall.confirmation.button-0.label");
let button1Title = _("uninstall.confirmation.button-1.label");
let response = promptService.confirmEx(null, title, message, buttonFlags, button0Title, button1Title, null, null, {value: 0});
if (response == 1) {
return reject({message: "User cancelled uninstall of extension"});
}
}
AddonManager.getAddonByID(extension.id, addon => {
let canUninstall = Boolean(addon.permissions & AddonManager.PERM_CAN_UNINSTALL);
if (!canUninstall) {
return reject({message: "The add-on cannot be uninstalled"});
}
try {
addon.uninstall();
} catch (err) {
return reject(err);
}
});
});
},
},
};
});

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

@ -76,6 +76,12 @@ extensions.registerSchemaAPI("runtime", "addon_parent", context => {
return context.lastError;
},
getBrowserInfo: function() {
const {name, vendor, version, appBuildID} = Services.appinfo;
const info = {name, vendor, version, buildID: appBuildID};
return Promise.resolve(info);
},
getPlatformInfo: function() {
return Promise.resolve(ExtensionUtils.PlatformInfo);
},

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

@ -217,7 +217,6 @@
{
"name": "uninstallSelf",
"type": "function",
"unsupported": true,
"description": "Uninstalls the calling extension. Note: This function can be used without requesting the 'management' permission in the manifest.",
"async": "callback",
"parameters": [
@ -230,6 +229,11 @@
"type": "boolean",
"optional": true,
"description": "Whether or not a confirm-uninstall dialog should prompt the user. Defaults to false."
},
"dialogMessage": {
"type": "string",
"optional": true,
"description": "The message to display to a user when being asked to confirm removal of the extension."
}
}
},

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

@ -89,6 +89,29 @@
}
}
},
{
"id": "BrowserInfo",
"type": "object",
"description": "An object containing information about the current browser.",
"properties": {
"name": {
"type": "string",
"description": "The name of the browser, for example 'Firefox'."
},
"vendor": {
"type": "string",
"description": "The name of the browser vendor, for example 'Mozilla'."
},
"version": {
"type": "string",
"description": "The browser's version, for example '42.0.0' or '0.8.1pre'."
},
"buildID": {
"type": "string",
"description": "The browser's build ID/date, for example '20160101'."
}
}
},
{
"id": "RequestUpdateCheckStatus",
"type": "string",
@ -367,6 +390,25 @@
}
]
},
{
"name": "getBrowserInfo",
"type": "function",
"description": "Returns information about the current browser.",
"async": "callback",
"parameters": [
{
"type": "function",
"name": "callback",
"description": "Called with results",
"parameters": [
{
"name": "browserInfo",
"$ref": "BrowserInfo"
}
]
}
]
},
{
"name": "getPlatformInfo",
"type": "function",

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

@ -0,0 +1,137 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://testing-common/AddonTestUtils.jsm");
Cu.import("resource://testing-common/MockRegistrar.jsm");
const {promiseAddonByID} = AddonTestUtils;
const id = "uninstall_self_test@tests.mozilla.com";
const manifest = {
applications: {
gecko: {
id,
},
},
name: "test extension name",
version: "1.0",
};
const waitForUninstalled = new Promise(resolve => {
const listener = {
onUninstalled: (addon) => {
equal(addon.id, id, "The expected add-on has been uninstalled");
AddonManager.getAddonByID(addon.id, checkedAddon => {
equal(checkedAddon, null, "Add-on no longer exists");
AddonManager.removeAddonListener(listener);
resolve();
});
},
};
AddonManager.addAddonListener(listener);
});
let promptService = {
_response: null,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptService]),
confirmEx: function(...args) {
this._confirmExArgs = args;
return this._response;
},
};
add_task(function* setup() {
let fakePromptService = MockRegistrar.register("@mozilla.org/embedcomp/prompt-service;1", promptService);
do_register_cleanup(() => {
MockRegistrar.unregister(fakePromptService);
});
yield ExtensionTestUtils.startAddonManager();
});
add_task(function* test_management_uninstall_no_prompt() {
function background() {
browser.test.onMessage.addListener(msg => {
browser.management.uninstallSelf();
});
}
let extension = ExtensionTestUtils.loadExtension({
manifest,
background,
useAddonManager: "temporary",
});
yield extension.startup();
let addon = yield promiseAddonByID(id);
notEqual(addon, null, "Add-on is installed");
extension.sendMessage("uninstall");
yield waitForUninstalled;
yield extension.markUnloaded();
Services.obs.notifyObservers(extension.extension.file, "flush-cache-entry", null);
});
add_task(function* test_management_uninstall_prompt_uninstall() {
promptService._response = 0;
function background() {
browser.test.onMessage.addListener(msg => {
browser.management.uninstallSelf({showConfirmDialog: true});
});
}
let extension = ExtensionTestUtils.loadExtension({
manifest,
background,
useAddonManager: "temporary",
});
yield extension.startup();
let addon = yield promiseAddonByID(id);
notEqual(addon, null, "Add-on is installed");
extension.sendMessage("uninstall");
yield waitForUninstalled;
yield extension.markUnloaded();
// Test localization strings
equal(promptService._confirmExArgs[1], `Uninstall ${manifest.name}`);
equal(promptService._confirmExArgs[2],
`The extension “${manifest.name}” is requesting to be uninstalled. What would you like to do?`);
equal(promptService._confirmExArgs[4], "Uninstall");
equal(promptService._confirmExArgs[5], "Keep Installed");
Services.obs.notifyObservers(extension.extension.file, "flush-cache-entry", null);
});
add_task(function* test_management_uninstall_prompt_keep() {
promptService._response = 1;
function background() {
browser.test.onMessage.addListener(msg => {
browser.management.uninstallSelf({showConfirmDialog: true}).then(() => {
browser.test.fail("uninstallSelf rejects when user declines uninstall");
}, error => {
browser.test.assertEq("User cancelled uninstall of extension",
error.message,
"Expected rejection when user declines uninstall");
browser.test.sendMessage("uninstall-rejected");
});
});
}
let extension = ExtensionTestUtils.loadExtension({
manifest,
background,
useAddonManager: "temporary",
});
yield extension.startup();
let addon = yield promiseAddonByID(id);
notEqual(addon, null, "Add-on is installed");
extension.sendMessage("uninstall");
yield extension.awaitMessage("uninstall-rejected");
addon = yield promiseAddonByID(id);
notEqual(addon, null, "Add-on remains installed");
yield extension.unload();
Services.obs.notifyObservers(extension.extension.file, "flush-cache-entry", null);
});

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

@ -0,0 +1,26 @@
/* 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";
add_task(function* setup() {
ExtensionTestUtils.mockAppInfo();
});
add_task(function* test_getBrowserInfo() {
function background() {
browser.runtime.getBrowserInfo().then(info => {
browser.test.assertEq(info.name, "XPCShell", "name is valid");
browser.test.assertEq(info.vendor, "Mozilla", "vendor is Mozilla");
browser.test.assertEq(info.version, "48", "version is correct");
browser.test.assertEq(info.buildID, "20160315", "buildID is correct");
browser.test.notifyPass("runtime.getBrowserInfo");
});
}
const extension = ExtensionTestUtils.loadExtension({background});
yield extension.startup();
yield extension.awaitFinish("runtime.getBrowserInfo");
yield extension.unload();
});

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

@ -38,11 +38,13 @@ skip-if = release_build
[test_ext_json_parser.js]
[test_ext_localStorage.js]
[test_ext_management.js]
[test_ext_management_uninstall_self.js]
[test_ext_manifest_content_security_policy.js]
[test_ext_manifest_incognito.js]
[test_ext_manifest_minimum_chrome_version.js]
[test_ext_onmessage_removelistener.js]
[test_ext_runtime_connect_no_receiver.js]
[test_ext_runtime_getBrowserInfo.js]
[test_ext_runtime_getPlatformInfo.js]
[test_ext_runtime_sendMessage.js]
[test_ext_runtime_sendMessage_errors.js]

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

@ -10,6 +10,9 @@
// Commas at the end of the line not the start
"comma-style": 2,
// No using undeclared variables
"no-undef": 2,
// Don't allow unused local variables unless they match the pattern
"no-unused-vars": [2, {"args": "none", "vars": "local", "varsIgnorePattern": "^(ids|ignored|unused)$"}],
}

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

@ -3,31 +3,94 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*** =================== SAVED SIGNONS CODE =================== ***/
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
"resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
var kSignonBundle;
var showingPasswords = false;
var dateFormatter = new Intl.DateTimeFormat(undefined,
let kSignonBundle;
// Default value for signon table sorting
let lastSignonSortColumn = "hostname";
let lastSignonSortAscending = true;
let showingPasswords = false;
// password-manager lists
let signons = [];
let deletedSignons = [];
// Elements that would be used frequently
let filterField;
let togglePasswordsButton;
let signonsIntro;
let removeButton;
let removeAllButton;
let signonsTree;
let signonReloadDisplay = {
observe: function(subject, topic, data) {
if (topic == "passwordmgr-storage-changed") {
switch (data) {
case "addLogin":
case "modifyLogin":
case "removeLogin":
case "removeAllLogins":
if (!signonsTree) {
return;
}
signons.length = 0;
LoadSignons();
// apply the filter if needed
if (filterField && filterField.value != "") {
FilterPasswords();
}
break;
}
Services.obs.notifyObservers(null, "passwordmgr-dialog-updated", null);
}
}
}
// Formatter for localization.
let dateFormatter = new Intl.DateTimeFormat(undefined,
{ day: "numeric", month: "short", year: "numeric" });
var dateAndTimeFormatter = new Intl.DateTimeFormat(undefined,
let dateAndTimeFormatter = new Intl.DateTimeFormat(undefined,
{ day: "numeric", month: "short", year: "numeric",
hour: "numeric", minute: "numeric" });
function SignonsStartup() {
kSignonBundle = document.getElementById("signonBundle");
document.getElementById("togglePasswords").label = kSignonBundle.getString("showPasswords");
document.getElementById("togglePasswords").accessKey = kSignonBundle.getString("showPasswordsAccessKey");
document.getElementById("signonsIntro").textContent = kSignonBundle.getString("loginsDescriptionAll");
function Startup() {
// be prepared to reload the display if anything changes
Services.obs.addObserver(signonReloadDisplay, "passwordmgr-storage-changed", false);
let treecols = document.getElementsByTagName("treecols")[0];
treecols.addEventListener("click", HandleTreeColumnClick.bind(null, SignonColumnSort));
signonsTree = document.getElementById("signonsTree");
kSignonBundle = document.getElementById("signonBundle");
filterField = document.getElementById("filter");
togglePasswordsButton = document.getElementById("togglePasswords");
signonsIntro = document.getElementById("signonsIntro");
removeButton = document.getElementById("removeSignon");
removeAllButton = document.getElementById("removeAllSignons");
togglePasswordsButton.label = kSignonBundle.getString("showPasswords");
togglePasswordsButton.accessKey = kSignonBundle.getString("showPasswordsAccessKey");
signonsIntro.textContent = kSignonBundle.getString("loginsDescriptionAll");
document.getElementsByTagName("treecols")[0].addEventListener("click", (event) => {
let { target, button } = event;
let sortField = target.getAttribute("data-field-name");
if (target.nodeName != "treecol" || button != 0 || !sortField) {
return;
}
SignonColumnSort(sortField);
Services.telemetry.getKeyedHistogramById("PWMGR_MANAGE_SORTED").add(sortField);
});
LoadSignons();
@ -44,12 +107,16 @@ function SignonsStartup() {
FocusFilterBox();
}
function setFilter(aFilterString) {
document.getElementById("filter").value = aFilterString;
_filterPasswords();
function Shutdown() {
Services.obs.removeObserver(signonReloadDisplay, "passwordmgr-storage-changed");
}
var signonsTreeView = {
function setFilter(aFilterString) {
filterField.value = aFilterString;
FilterPasswords();
}
let signonsTreeView = {
// Keep track of which favicons we've fetched or started fetching.
// Maps a login origin to a favicon URL.
_faviconMap: new Map(),
@ -90,8 +157,8 @@ var signonsTreeView = {
getProgressMode(row, column) {},
getCellValue(row, column) {},
getCellText(row, column) {
var time;
var signon = this._filterSet.length ? this._filterSet[row] : signons[row];
let time;
let signon = this._filterSet.length ? this._filterSet[row] : signons[row];
switch (column.id) {
case "siteCol":
return signon.httpRealm ?
@ -144,7 +211,7 @@ var signonsTreeView = {
let existingLogin = table[row].clone();
table[row][field] = value;
table[row].timePasswordChanged = Date.now();
passwordmanager.modifyLogin(existingLogin, table[row]);
Services.logins.modifyLogin(existingLogin, table[row]);
signonsTree.treeBoxObject.invalidateRow(row);
}
@ -160,15 +227,78 @@ var signonsTreeView = {
},
};
function SortTree(column, ascending) {
let table = signonsTreeView._filterSet.length ? signonsTreeView._filterSet : signons;
// remember which item was selected so we can restore it after the sort
let selections = GetTreeSelections();
let selectedNumber = selections.length ? table[selections[0]].number : -1;
function compareFunc(a, b) {
let valA, valB;
switch (column) {
case "hostname":
let realmA = a.httpRealm;
let realmB = b.httpRealm;
realmA = realmA == null ? "" : realmA.toLowerCase();
realmB = realmB == null ? "" : realmB.toLowerCase();
valA = a[column].toLowerCase() + realmA;
valB = b[column].toLowerCase() + realmB;
break;
case "username":
case "password":
valA = a[column].toLowerCase();
valB = b[column].toLowerCase();
break;
default:
valA = a[column];
valB = b[column];
}
if (valA < valB)
return -1;
if (valA > valB)
return 1;
return 0;
}
// do the sort
table.sort(compareFunc);
if (!ascending) {
table.reverse();
}
// restore the selection
let selectedRow = -1;
if (selectedNumber >= 0 && false) {
for (let s = 0; s < table.length; s++) {
if (table[s].number == selectedNumber) {
// update selection
// note: we need to deselect before reselecting in order to trigger ...Selected()
signonsTree.view.selection.select(-1);
signonsTree.view.selection.select(s);
selectedRow = s;
break;
}
}
}
// display the results
signonsTree.treeBoxObject.invalidate();
if (selectedRow >= 0) {
signonsTree.treeBoxObject.ensureRowIsVisible(selectedRow);
}
}
function LoadSignons() {
// loads signons into table
try {
signons = passwordmanager.getAllLogins();
signons = Services.logins.getAllLogins();
} catch (e) {
signons = [];
}
signons.forEach(login => login.QueryInterface(Components.interfaces.nsILoginMetaInfo));
signons.forEach(login => login.QueryInterface(Ci.nsILoginMetaInfo));
signonsTreeView.rowCount = signons.length;
// sort and display the table
@ -180,42 +310,97 @@ function LoadSignons() {
SignonColumnSort(lastSignonSortColumn);
// disable "remove all signons" button if there are no signons
var element = document.getElementById("removeAllSignons");
var toggle = document.getElementById("togglePasswords");
if (signons.length == 0) {
element.setAttribute("disabled", "true");
toggle.setAttribute("disabled", "true");
removeAllButton.setAttribute("disabled", "true");
togglePasswordsButton.setAttribute("disabled", "true");
} else {
element.removeAttribute("disabled");
toggle.removeAttribute("disabled");
removeAllButton.removeAttribute("disabled");
togglePasswordsButton.removeAttribute("disabled");
}
return true;
}
function GetTreeSelections() {
let selections = [];
let select = signonsTree.view.selection;
if (select) {
let count = select.getRangeCount();
let min = new Object();
let max = new Object();
for (let i = 0; i < count; i++) {
select.getRangeAt(i, min, max);
for (let k = min.value; k <= max.value; k++) {
if (k != -1) {
selections[selections.length] = k;
}
}
}
}
return selections;
}
function SignonSelected() {
var selections = GetTreeSelections(signonsTree);
let selections = GetTreeSelections();
if (selections.length) {
document.getElementById("removeSignon").removeAttribute("disabled");
removeButton.removeAttribute("disabled");
} else {
document.getElementById("removeSignon").setAttribute("disabled", true);
removeButton.setAttribute("disabled", true);
}
}
function DeleteSignon() {
var syncNeeded = (signonsTreeView._filterSet.length != 0);
DeleteSelectedItemFromTree(signonsTree, signonsTreeView,
signonsTreeView._filterSet.length ? signonsTreeView._filterSet : signons,
deletedSignons, "removeSignon", "removeAllSignons");
let filterSet = signonsTreeView._filterSet;
let syncNeeded = (filterSet.length != 0);
let tree = signonsTree;
let view = signonsTreeView;
let table = filterSet.length ? filterSet : signons;
// Turn off tree selection notifications during the deletion
tree.view.selection.selectEventsSuppressed = true;
// remove selected items from list (by setting them to null) and place in deleted list
let selections = GetTreeSelections();
for (let s = selections.length - 1; s >= 0; s--) {
let i = selections[s];
deletedSignons.push(table[i]);
table[i] = null;
}
// collapse list by removing all the null entries
for (let j = 0; j < table.length; j++) {
if (table[j] == null) {
let k = j;
while ((k < table.length) && (table[k] == null)) {
k++;
}
table.splice(j, k - j);
view.rowCount -= k - j;
tree.treeBoxObject.rowCountChanged(j, j - k);
}
}
// update selection and/or buttons
if (table.length) {
// update selection
let nextSelection = (selections[0] < table.length) ? selections[0] : table.length-1;
tree.view.selection.select(nextSelection);
tree.treeBoxObject.ensureRowIsVisible(nextSelection);
} else {
// disable buttons
removeButton.setAttribute("disabled", "true");
removeAllButton.setAttribute("disabled", "true");
}
tree.view.selection.selectEventsSuppressed = false;
FinalizeSignonDeletions(syncNeeded);
}
function DeleteAllSignons() {
var prompter = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
let prompter = Cc["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Ci.nsIPromptService);
// Confirm the user wants to remove all passwords
var dummy = { value: false };
let dummy = { value: false };
if (prompter.confirmEx(window,
kSignonBundle.getString("removeAllPasswordsTitle"),
kSignonBundle.getString("removeAllPasswordsPrompt"),
@ -223,10 +408,30 @@ function DeleteAllSignons() {
null, null, null, null, dummy) == 1) // 1 == "No" button
return;
var syncNeeded = (signonsTreeView._filterSet.length != 0);
DeleteAllFromTree(signonsTree, signonsTreeView,
signonsTreeView._filterSet.length ? signonsTreeView._filterSet : signons,
deletedSignons, "removeSignon", "removeAllSignons");
let filterSet = signonsTreeView._filterSet;
let syncNeeded = (filterSet.length != 0);
let view = signonsTreeView;
let table = filterSet.length ? filterSet : signons;
// remove all items from table and place in deleted table
for (let i = 0; i < table.length; i++) {
deletedSignons.push(table[i]);
}
table.length = 0;
// clear out selections
view.selection.select(-1);
// update the tree view and notify the tree
view.rowCount = 0;
let box = signonsTree.treeBoxObject;
box.rowCountChanged(0, -deletedSignons.length);
box.invalidate();
// disable buttons
removeButton.setAttribute("disabled", "true");
removeAllButton.setAttribute("disabled", "true");
FinalizeSignonDeletions(syncNeeded);
Services.telemetry.getHistogramById("PWMGR_MANAGE_DELETED_ALL").add(1);
}
@ -234,23 +439,21 @@ function DeleteAllSignons() {
function TogglePasswordVisible() {
if (showingPasswords || masterPasswordLogin(AskUserShowPasswords)) {
showingPasswords = !showingPasswords;
document.getElementById("togglePasswords").label = kSignonBundle.getString(showingPasswords ? "hidePasswords" : "showPasswords");
document.getElementById("togglePasswords").accessKey = kSignonBundle.getString(showingPasswords ? "hidePasswordsAccessKey" : "showPasswordsAccessKey");
togglePasswordsButton.label = kSignonBundle.getString(showingPasswords ? "hidePasswords" : "showPasswords");
togglePasswordsButton.accessKey = kSignonBundle.getString(showingPasswords ? "hidePasswordsAccessKey" : "showPasswordsAccessKey");
document.getElementById("passwordCol").hidden = !showingPasswords;
_filterPasswords();
FilterPasswords();
}
// Notify observers that the password visibility toggling is
// completed. (Mostly useful for tests)
Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService)
.notifyObservers(null, "passwordmgr-password-toggle-complete", null);
Services.obs.notifyObservers(null, "passwordmgr-password-toggle-complete", null);
Services.telemetry.getHistogramById("PWMGR_MANAGE_VISIBILITY_TOGGLED").add(showingPasswords);
}
function AskUserShowPasswords() {
var prompter = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
var dummy = { value: false };
let prompter = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
let dummy = { value: false };
// Confirm the user wants to display passwords
return prompter.confirmEx(window,
@ -260,15 +463,15 @@ function AskUserShowPasswords() {
}
function FinalizeSignonDeletions(syncNeeded) {
for (var s = 0; s < deletedSignons.length; s++) {
passwordmanager.removeLogin(deletedSignons[s]);
for (let s = 0; s < deletedSignons.length; s++) {
Services.logins.removeLogin(deletedSignons[s]);
Services.telemetry.getHistogramById("PWMGR_MANAGE_DELETED").add(1);
}
// If the deletion has been performed in a filtered view, reflect the deletion in the unfiltered table.
// See bug 405389.
if (syncNeeded) {
try {
signons = passwordmanager.getAllLogins();
signons = Services.logins.getAllLogins();
} catch (e) {
signons = [];
}
@ -281,9 +484,9 @@ function HandleSignonKeyPress(e) {
if (signonsTree.getAttribute("editing")) {
return;
}
if (e.keyCode == KeyEvent.DOM_VK_DELETE ||
if (e.keyCode == KeyboardEvent.DOM_VK_DELETE ||
(AppConstants.platform == "macosx" &&
e.keyCode == KeyEvent.DOM_VK_BACK_SPACE))
e.keyCode == KeyboardEvent.DOM_VK_BACK_SPACE))
{
DeleteSignon();
}
@ -309,30 +512,28 @@ function getColumnByName(column) {
return undefined;
}
var lastSignonSortColumn = "hostname";
var lastSignonSortAscending = true;
function SignonColumnSort(column) {
let sortedCol = getColumnByName(column);
let lastSortedCol = getColumnByName(lastSignonSortColumn);
// clear out the sortDirection attribute on the old column
var lastSortedCol = getColumnByName(lastSignonSortColumn);
lastSortedCol.removeAttribute("sortDirection");
// determine if sort is to be ascending or descending
lastSignonSortAscending = (column == lastSignonSortColumn) ? !lastSignonSortAscending : true;
// sort
lastSignonSortAscending =
SortTree(signonsTree, signonsTreeView,
signonsTreeView._filterSet.length ? signonsTreeView._filterSet : signons,
column, lastSignonSortColumn, lastSignonSortAscending);
lastSignonSortColumn = column;
SortTree(lastSignonSortColumn, lastSignonSortAscending);
// set the sortDirection attribute to get the styling going
// first we need to get the right element
var sortedCol = getColumnByName(column);
sortedCol.setAttribute("sortDirection", lastSignonSortAscending ?
"ascending" : "descending");
}
function SignonClearFilter() {
var singleSelection = (signonsTreeView.selection.count == 1);
let singleSelection = (signonsTreeView.selection.count == 1);
// Clear the Tree Display
signonsTreeView.rowCount = 0;
@ -346,7 +547,7 @@ function SignonClearFilter() {
if (singleSelection) {
signonsTreeView.selection.clearSelection();
for (let i = 0; i < signonsTreeView._lastSelectedRanges.length; ++i) {
var range = signonsTreeView._lastSelectedRanges[i];
let range = signonsTreeView._lastSelectedRanges[i];
signonsTreeView.selection.rangedSelect(range.min, range.max, true);
}
} else {
@ -354,13 +555,13 @@ function SignonClearFilter() {
}
signonsTreeView._lastSelectedRanges = [];
document.getElementById("signonsIntro").textContent = kSignonBundle.getString("loginsDescriptionAll");
signonsIntro.textContent = kSignonBundle.getString("loginsDescriptionAll");
}
function FocusFilterBox() {
var filterBox = document.getElementById("filter");
if (filterBox.getAttribute("focused") != "true")
filterBox.focus();
if (filterField.getAttribute("focused") != "true") {
filterField.focus();
}
}
function SignonMatchesFilter(aSignon, aFilterValue) {
@ -379,31 +580,30 @@ function SignonMatchesFilter(aSignon, aFilterValue) {
return false;
}
function FilterPasswords(aFilterValue, view) {
function _filterPasswords(aFilterValue, view) {
aFilterValue = aFilterValue.toLowerCase();
return signons.filter(s => SignonMatchesFilter(s, aFilterValue));
}
function SignonSaveState() {
// Save selection
var seln = signonsTreeView.selection;
let seln = signonsTreeView.selection;
signonsTreeView._lastSelectedRanges = [];
var rangeCount = seln.getRangeCount();
for (var i = 0; i < rangeCount; ++i) {
var min = {}; var max = {};
let rangeCount = seln.getRangeCount();
for (let i = 0; i < rangeCount; ++i) {
let min = {}; let max = {};
seln.getRangeAt(i, min, max);
signonsTreeView._lastSelectedRanges.push({ min: min.value, max: max.value });
}
}
function _filterPasswords() {
var filter = document.getElementById("filter").value;
if (filter == "") {
function FilterPasswords() {
if (filterField.value == "") {
SignonClearFilter();
return;
}
var newFilterSet = FilterPasswords(filter, signonsTreeView);
let newFilterSet = _filterPasswords(filterField.value, signonsTreeView);
if (!signonsTreeView._filterSet.length) {
// Save Display Info for the Non-Filtered mode when we first
// enter Filtered mode.
@ -423,7 +623,7 @@ function _filterPasswords() {
if (signonsTreeView.rowCount > 0)
signonsTreeView.selection.select(0);
document.getElementById("signonsIntro").textContent = kSignonBundle.getString("loginsDescriptionFiltered");
signonsIntro.textContent = kSignonBundle.getString("loginsDescriptionFiltered");
}
function CopyPassword() {
@ -432,20 +632,20 @@ function CopyPassword() {
if (!showingPasswords && !masterPasswordLogin())
return;
// Copy selected signon's password to clipboard
var clipboard = Components.classes["@mozilla.org/widget/clipboardhelper;1"].
getService(Components.interfaces.nsIClipboardHelper);
var row = document.getElementById("signonsTree").currentIndex;
var password = signonsTreeView.getCellText(row, {id : "passwordCol" });
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
getService(Ci.nsIClipboardHelper);
let row = signonsTree.currentIndex;
let password = signonsTreeView.getCellText(row, {id : "passwordCol" });
clipboard.copyString(password);
Services.telemetry.getHistogramById("PWMGR_MANAGE_COPIED_PASSWORD").add(1);
}
function CopyUsername() {
// Copy selected signon's username to clipboard
var clipboard = Components.classes["@mozilla.org/widget/clipboardhelper;1"].
getService(Components.interfaces.nsIClipboardHelper);
var row = document.getElementById("signonsTree").currentIndex;
var username = signonsTreeView.getCellText(row, {id : "userCol" });
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
getService(Ci.nsIClipboardHelper);
let row = signonsTree.currentIndex;
let username = signonsTreeView.getCellText(row, {id : "userCol" });
clipboard.copyString(username);
Services.telemetry.getHistogramById("PWMGR_MANAGE_COPIED_USERNAME").add(1);
}
@ -493,9 +693,9 @@ function UpdateContextMenu() {
function masterPasswordLogin(noPasswordCallback) {
// This doesn't harm if passwords are not encrypted
var tokendb = Components.classes["@mozilla.org/security/pk11tokendb;1"]
.createInstance(Components.interfaces.nsIPK11TokenDB);
var token = tokendb.getInternalKeyToken();
let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"]
.createInstance(Ci.nsIPK11TokenDB);
let token = tokendb.getInternalKeyToken();
// If there is no master password, still give the user a chance to opt-out of displaying passwords
if (token.checkPassword(""))

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

@ -11,12 +11,11 @@
<window id="SignonViewerDialog"
windowtype="Toolkit:PasswordManager"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="Startup(); SignonsStartup();"
onload="Startup();"
onunload="Shutdown();"
title="&savedLogins.title;"
persist="width height screenX screenY">
<script type="application/javascript" src="chrome://passwordmgr/content/passwordManagerCommon.js"/>
<script type="application/javascript" src="chrome://passwordmgr/content/passwordManager.js"/>
<stringbundle id="signonBundle"
@ -59,7 +58,7 @@
<label accesskey="&filter.accesskey;" control="filter">&filter.label;</label>
<textbox id="filter" flex="1" type="search"
aria-controls="signonsTree"
oncommand="_filterPasswords();"/>
oncommand="FilterPasswords();"/>
</hbox>
<label control="signonsTree" id="signonsIntro"/>

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

@ -1,231 +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/. */
/*** =================== INITIALISATION CODE =================== ***/
var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
Components.utils.import("resource://gre/modules/Services.jsm");
var kObserverService;
// interface variables
var passwordmanager = null;
// password-manager lists
var signons = [];
var deletedSignons = [];
var signonsTree;
var showingPasswords = false;
function Startup() {
// xpconnect to password manager interfaces
passwordmanager = Components.classes["@mozilla.org/login-manager;1"]
.getService(Components.interfaces.nsILoginManager);
// be prepared to reload the display if anything changes
kObserverService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
kObserverService.addObserver(signonReloadDisplay, "passwordmgr-storage-changed", false);
signonsTree = document.getElementById("signonsTree");
}
function Shutdown() {
kObserverService.removeObserver(signonReloadDisplay, "passwordmgr-storage-changed");
}
var signonReloadDisplay = {
observe: function(subject, topic, data) {
if (topic == "passwordmgr-storage-changed") {
switch (data) {
case "addLogin":
case "modifyLogin":
case "removeLogin":
case "removeAllLogins":
if (!signonsTree) {
return;
}
signons.length = 0;
LoadSignons();
// apply the filter if needed
if (document.getElementById("filter") && document.getElementById("filter").value != "") {
_filterPasswords();
}
break;
}
kObserverService.notifyObservers(null, "passwordmgr-dialog-updated", null);
}
}
}
/*** =================== GENERAL CODE =================== ***/
function DeleteAllFromTree(tree, view, table, deletedTable, removeButton, removeAllButton) {
// remove all items from table and place in deleted table
for (var i=0; i<table.length; i++) {
deletedTable[deletedTable.length] = table[i];
}
table.length = 0;
// clear out selections
view.selection.select(-1);
// update the tree view and notify the tree
view.rowCount = 0;
var box = tree.treeBoxObject;
box.rowCountChanged(0, -deletedTable.length);
box.invalidate();
// disable buttons
document.getElementById(removeButton).setAttribute("disabled", "true")
document.getElementById(removeAllButton).setAttribute("disabled", "true");
}
function DeleteSelectedItemFromTree
(tree, view, table, deletedTable, removeButton, removeAllButton) {
// Turn off tree selection notifications during the deletion
tree.view.selection.selectEventsSuppressed = true;
// remove selected items from list (by setting them to null) and place in deleted list
var selections = GetTreeSelections(tree);
for (var s=selections.length-1; s>= 0; s--) {
var i = selections[s];
deletedTable[deletedTable.length] = table[i];
table[i] = null;
}
// collapse list by removing all the null entries
for (var j=0; j<table.length; j++) {
if (table[j] == null) {
var k = j;
while ((k < table.length) && (table[k] == null)) {
k++;
}
table.splice(j, k-j);
view.rowCount -= k - j;
tree.treeBoxObject.rowCountChanged(j, j - k);
}
}
// update selection and/or buttons
if (table.length) {
// update selection
var nextSelection = (selections[0] < table.length) ? selections[0] : table.length-1;
tree.view.selection.select(nextSelection);
tree.treeBoxObject.ensureRowIsVisible(nextSelection);
} else {
// disable buttons
document.getElementById(removeButton).setAttribute("disabled", "true")
document.getElementById(removeAllButton).setAttribute("disabled", "true");
}
tree.view.selection.selectEventsSuppressed = false;
}
function GetTreeSelections(tree) {
var selections = [];
var select = tree.view.selection;
if (select) {
var count = select.getRangeCount();
var min = new Object();
var max = new Object();
for (var i=0; i<count; i++) {
select.getRangeAt(i, min, max);
for (var k=min.value; k<=max.value; k++) {
if (k != -1) {
selections[selections.length] = k;
}
}
}
}
return selections;
}
function HandleTreeColumnClick(sortFunction, event) {
if (event.target.nodeName != "treecol" || event.button != 0) {
return;
}
let sortField = event.target.getAttribute("data-field-name");
if (!sortField) {
return;
}
sortFunction(sortField);
Services.telemetry.getKeyedHistogramById("PWMGR_MANAGE_SORTED").add(sortField);
}
function SortTree(tree, view, table, column, lastSortColumn, lastSortAscending, updateSelection) {
// remember which item was selected so we can restore it after the sort
var selections = GetTreeSelections(tree);
var selectedNumber = selections.length ? table[selections[0]].number : -1;
// determine if sort is to be ascending or descending
var ascending = (column == lastSortColumn) ? !lastSortAscending : true;
function compareFunc(a, b) {
var valA, valB;
switch (column) {
case "hostname":
var realmA = a.httpRealm;
var realmB = b.httpRealm;
realmA = realmA == null ? "" : realmA.toLowerCase();
realmB = realmB == null ? "" : realmB.toLowerCase();
valA = a[column].toLowerCase() + realmA;
valB = b[column].toLowerCase() + realmB;
break;
case "username":
case "password":
valA = a[column].toLowerCase();
valB = b[column].toLowerCase();
break;
default:
valA = a[column];
valB = b[column];
}
if (valA < valB)
return -1;
if (valA > valB)
return 1;
return 0;
}
// do the sort
table.sort(compareFunc);
if (!ascending)
table.reverse();
// restore the selection
var selectedRow = -1;
if (selectedNumber>=0 && updateSelection) {
for (var s=0; s<table.length; s++) {
if (table[s].number == selectedNumber) {
// update selection
// note: we need to deselect before reselecting in order to trigger ...Selected()
tree.view.selection.select(-1);
tree.view.selection.select(s);
selectedRow = s;
break;
}
}
}
// display the results
tree.treeBoxObject.invalidate();
if (selectedRow >= 0) {
tree.treeBoxObject.ensureRowIsVisible(selectedRow)
}
return ascending;
}

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

@ -7,5 +7,4 @@ toolkit.jar:
content/passwordmgr/login.xml (content/login.xml)
* content/passwordmgr/passwordManager.xul (content/passwordManager.xul)
content/passwordmgr/passwordManager.js (content/passwordManager.js)
content/passwordmgr/passwordManagerCommon.js (content/passwordManagerCommon.js)
content/passwordmgr/recipes.json (content/recipes.json)

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