зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound
This commit is contained in:
Коммит
e3c2ddf887
|
@ -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&utm_medium=firefox-browser&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&utm_medium=firefox-browser&utm_campaign=sync-preferences"><!--
|
||||
--><label class="iOSLink text-link" id="fxaMobilePromo-ios-hasFxaAccount"><!--
|
||||
-->&mobilePromo3.iOSLink;</label><!--
|
||||
-->&mobilePromo3.end;
|
||||
</label>
|
||||
|
|
Двоичные данные
browser/themes/linux/downloads/download-summary.png
Двоичные данные
browser/themes/linux/downloads/download-summary.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 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)
|
||||
|
|
Двоичные данные
browser/themes/osx/downloads/download-summary.png
Двоичные данные
browser/themes/osx/downloads/download-summary.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 751 B |
Двоичные данные
browser/themes/osx/downloads/download-summary@2x.png
Двоичные данные
browser/themes/osx/downloads/download-summary@2x.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 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)
|
||||
|
|
Двоичные данные
browser/themes/windows/downloads/download-summary.png
Двоичные данные
browser/themes/windows/downloads/download-summary.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 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},
|
||||
],
|
||||
}
|
||||
];
|
||||
|
|
Двоичные данные
testing/web-platform/tests/media-source/mp4/test.mp4
Двоичные данные
testing/web-platform/tests/media-source/mp4/test.mp4
Двоичный файл не отображается.
Двоичные данные
testing/web-platform/tests/media-source/webm/test.webm
Двоичные данные
testing/web-platform/tests/media-source/webm/test.webm
Двоичный файл не отображается.
|
@ -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)
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче