зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to m-c. a=merge
This commit is contained in:
Коммит
3625d814ad
|
@ -319,7 +319,7 @@ ifdef MOZ_CRASHREPORTER
|
|||
grep 'sym' $(SYMBOL_INDEX_NAME) > $(SYMBOL_INDEX_NAME).tmp && \
|
||||
mv $(SYMBOL_INDEX_NAME).tmp $(SYMBOL_INDEX_NAME)
|
||||
cd $(DIST)/crashreporter-symbols && \
|
||||
zip -r5D '../$(PKG_PATH)$(SYMBOL_ARCHIVE_BASENAME).zip' . -i '*.sym' -i '*.txt' -x '*test*' -x '*Test*'
|
||||
zip -r5D '../$(PKG_PATH)$(SYMBOL_ARCHIVE_BASENAME).zip' . -i '*.sym' -i '*.txt'
|
||||
endif # MOZ_CRASHREPORTER
|
||||
|
||||
uploadsymbols:
|
||||
|
|
|
@ -500,7 +500,8 @@ var ClickEventHandler = {
|
|||
ctrlKey: event.ctrlKey, metaKey: event.metaKey,
|
||||
altKey: event.altKey, href: null, title: null,
|
||||
bookmark: false, referrerPolicy: referrerPolicy,
|
||||
originAttributes: principal ? principal.originAttributes : {} };
|
||||
originAttributes: principal ? principal.originAttributes : {},
|
||||
isContentWindowPrivate: PrivateBrowsingUtils.isContentWindowPrivate(ownerDoc.defaultView)};
|
||||
|
||||
if (href) {
|
||||
try {
|
||||
|
|
|
@ -284,6 +284,7 @@ subsuite = clipboard
|
|||
skip-if = true # Disabled due to the clipboard not supporting real file types yet (bug 1288773)
|
||||
[browser_contentAreaClick.js]
|
||||
skip-if = e10s # Clicks in content don't go through contentAreaClick with e10s.
|
||||
[browser_contentAltClick.js]
|
||||
[browser_contextmenu.js]
|
||||
subsuite = clipboard
|
||||
tags = fullscreen
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Test for Bug 1109146.
|
||||
* The tests opens a new tab and alt + clicks to download files
|
||||
* and confirms those files are on the download list.
|
||||
*
|
||||
* The difference between this and the test "browser_contentAreaClick.js" is that
|
||||
* the code path in e10s uses ContentClick.jsm instead of browser.js::contentAreaClick() util.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
|
||||
"resource://gre/modules/Downloads.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
|
||||
"resource://testing-common/PlacesTestUtils.jsm");
|
||||
|
||||
function setup(){
|
||||
gPrefService.setBoolPref("browser.altClickSave", true);
|
||||
|
||||
let testPage =
|
||||
'data:text/html,' +
|
||||
'<p><a id="commonlink" href="http://mochi.test/moz/">Common link</a></p>' +
|
||||
'<p><math id="mathxlink" xmlns="http://www.w3.org/1998/Math/MathML" xlink:type="simple" xlink:href="http://mochi.test/moz/"><mtext>MathML XLink</mtext></math></p>' +
|
||||
'<p><svg id="svgxlink" xmlns="http://www.w3.org/2000/svg" width="100px" height="50px" version="1.1"><a xlink:type="simple" xlink:href="http://mochi.test/moz/"><text transform="translate(10, 25)">SVG XLink</text></a></svg></p>';
|
||||
|
||||
return BrowserTestUtils.openNewForegroundTab(gBrowser, testPage);
|
||||
}
|
||||
|
||||
function* clean_up() {
|
||||
// Remove downloads.
|
||||
let downloadList = yield Downloads.getList(Downloads.ALL);
|
||||
let downloads = yield downloadList.getAll();
|
||||
for (let download of downloads) {
|
||||
yield downloadList.remove(download);
|
||||
yield download.finalize(true);
|
||||
}
|
||||
// Remove download history.
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
|
||||
gPrefService.clearUserPref("browser.altClickSave");
|
||||
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
}
|
||||
|
||||
add_task(function* test_alt_click()
|
||||
{
|
||||
yield setup();
|
||||
|
||||
let downloadList = yield Downloads.getList(Downloads.ALL);
|
||||
let downloads = [];
|
||||
let downloadView;
|
||||
// When 1 download has been attempted then resolve the promise.
|
||||
let finishedAllDownloads = new Promise( (resolve)=> {
|
||||
downloadView = {
|
||||
onDownloadAdded: function (aDownload) {
|
||||
downloads.push(aDownload);
|
||||
resolve();
|
||||
},
|
||||
};
|
||||
});
|
||||
yield downloadList.addView(downloadView);
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter("#commonlink", {altKey: true}, gBrowser.selectedBrowser);
|
||||
|
||||
// Wait for all downloads to be added to the download list.
|
||||
yield finishedAllDownloads;
|
||||
yield downloadList.removeView(downloadView);
|
||||
|
||||
is(downloads.length, 1, "1 downloads");
|
||||
is(downloads[0].source.url, "http://mochi.test/moz/", "Downloaded #commonlink element");
|
||||
|
||||
yield* clean_up();
|
||||
});
|
||||
|
||||
add_task(function* test_alt_click_on_xlinks()
|
||||
{
|
||||
yield setup();
|
||||
|
||||
let downloadList = yield Downloads.getList(Downloads.ALL);
|
||||
let downloads = [];
|
||||
let downloadView;
|
||||
// When all 2 downloads have been attempted then resolve the promise.
|
||||
let finishedAllDownloads = new Promise( (resolve)=> {
|
||||
downloadView = {
|
||||
onDownloadAdded: function (aDownload) {
|
||||
downloads.push(aDownload);
|
||||
if (downloads.length == 2) {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
yield downloadList.addView(downloadView);
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter("#mathxlink", {altKey: true}, gBrowser.selectedBrowser);
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter("#svgxlink", {altKey: true}, gBrowser.selectedBrowser);
|
||||
|
||||
// Wait for all downloads to be added to the download list.
|
||||
yield finishedAllDownloads;
|
||||
yield downloadList.removeView(downloadView);
|
||||
|
||||
is(downloads.length, 2, "2 downloads");
|
||||
is(downloads[0].source.url, "http://mochi.test/moz/", "Downloaded #mathxlink element");
|
||||
is(downloads[1].source.url, "http://mochi.test/moz/", "Downloaded #svgxlink element");
|
||||
|
||||
yield* clean_up();
|
||||
});
|
|
@ -224,13 +224,20 @@ function openLinkIn(url, where, params) {
|
|||
var aIndicateErrorPageLoad = params.indicateErrorPageLoad;
|
||||
|
||||
if (where == "save") {
|
||||
if (!aInitiatingDoc) {
|
||||
Components.utils.reportError("openUILink/openLinkIn was called with " +
|
||||
"where == 'save' but without initiatingDoc. See bug 814264.");
|
||||
return;
|
||||
}
|
||||
// TODO(1073187): propagate referrerPolicy.
|
||||
saveURL(url, null, null, true, null, aNoReferrer ? null : aReferrerURI, aInitiatingDoc);
|
||||
|
||||
// ContentClick.jsm passes isContentWindowPrivate for saveURL instead of passing a CPOW initiatingDoc
|
||||
if ("isContentWindowPrivate" in params) {
|
||||
saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, null, params.isContentWindowPrivate);
|
||||
}
|
||||
else {
|
||||
if (!aInitiatingDoc) {
|
||||
Components.utils.reportError("openUILink/openLinkIn was called with " +
|
||||
"where == 'save' but without initiatingDoc. See bug 814264.");
|
||||
return;
|
||||
}
|
||||
saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, aInitiatingDoc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -508,20 +508,32 @@ this.DownloadsCommon = {
|
|||
// or the file doesn't exist), try using the parent if we have it.
|
||||
let parent = aFile.parent;
|
||||
if (parent) {
|
||||
try {
|
||||
// Open the parent directory to show where the file should be.
|
||||
parent.launch();
|
||||
} catch (ex) {
|
||||
// If launch also fails (probably because it's not implemented), let
|
||||
// the OS handler try to open the parent.
|
||||
Cc["@mozilla.org/uriloader/external-protocol-service;1"]
|
||||
.getService(Ci.nsIExternalProtocolService)
|
||||
.loadUrl(NetUtil.newURI(parent));
|
||||
}
|
||||
this.showDirectory(parent);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the specified folder in the system file manager.
|
||||
*
|
||||
* @param aDirectory
|
||||
* a directory to be opened with system file manager.
|
||||
*/
|
||||
showDirectory(aDirectory) {
|
||||
if (!(aDirectory instanceof Ci.nsIFile)) {
|
||||
throw new Error("aDirectory must be a nsIFile object");
|
||||
}
|
||||
try {
|
||||
aDirectory.launch();
|
||||
} catch (ex) {
|
||||
// If launch fails (probably because it's not implemented), let
|
||||
// the OS handler try to open the directory.
|
||||
Cc["@mozilla.org/uriloader/external-protocol-service;1"]
|
||||
.getService(Ci.nsIExternalProtocolService)
|
||||
.loadUrl(NetUtil.newURI(aDirectory));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays an alert message box which asks the user if they want to
|
||||
* unblock the downloaded file or not.
|
||||
|
|
|
@ -19,7 +19,7 @@ richlistitem[type="download"].download-state[state="1"]:not([exists]) .downloadS
|
|||
|
||||
#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryProgress,
|
||||
#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryDetails,
|
||||
#downloadsFooter[showingsummary] > #downloadsHistory,
|
||||
#downloadsFooter[showingsummary] > #downloadsFooterButtons,
|
||||
#downloadsFooter:not([showingsummary]) > #downloadsSummary {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -367,6 +367,23 @@ const DownloadsPanel = {
|
|||
this._state = this.kStateHidden;
|
||||
},
|
||||
|
||||
onFooterPopupShowing(aEvent) {
|
||||
let itemClearList = document.getElementById("downloadsDropdownItemClearList");
|
||||
if (DownloadsCommon.getData(window).canRemoveFinished) {
|
||||
itemClearList.removeAttribute("hidden");
|
||||
} else {
|
||||
itemClearList.setAttribute("hidden", "true");
|
||||
}
|
||||
|
||||
document.getElementById("downloadsFooterButtonsSplitter").classList
|
||||
.add("downloadsDropmarkerSplitterExtend");
|
||||
},
|
||||
|
||||
onFooterPopupHidden(aEvent) {
|
||||
document.getElementById("downloadsFooterButtonsSplitter").classList
|
||||
.remove("downloadsDropmarkerSplitterExtend");
|
||||
},
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// Related operations
|
||||
|
||||
|
@ -382,6 +399,13 @@ const DownloadsPanel = {
|
|||
BrowserDownloadsUI();
|
||||
},
|
||||
|
||||
openDownloadsFolder() {
|
||||
Downloads.getPreferredDownloadsDirectory().then(downloadsPath => {
|
||||
DownloadsCommon.showDirectory(new FileUtils.File(downloadsPath));
|
||||
}).catch(Cu.reportError);
|
||||
this.hidePanel();
|
||||
},
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// Internal functions
|
||||
|
||||
|
@ -1188,6 +1212,9 @@ const DownloadsViewController = {
|
|||
//// nsIController
|
||||
|
||||
supportsCommand(aCommand) {
|
||||
if (aCommand === "downloadsCmd_clearList") {
|
||||
return true;
|
||||
}
|
||||
// Firstly, determine if this is a command that we can handle.
|
||||
if (!DownloadsViewUI.isCommandName(aCommand)) {
|
||||
return false;
|
||||
|
|
|
@ -104,8 +104,8 @@
|
|||
<menuseparator/>
|
||||
|
||||
<menuitem command="downloadsCmd_clearList"
|
||||
label="&cmd.clearList.label;"
|
||||
accesskey="&cmd.clearList.accesskey;"/>
|
||||
label="&cmd.clearList2.label;"
|
||||
accesskey="&cmd.clearList2.accesskey;"/>
|
||||
</menupopup>
|
||||
|
||||
<panelmultiview id="downloadsPanel-multiView"
|
||||
|
@ -148,11 +148,31 @@
|
|||
crop="end"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<button id="downloadsHistory"
|
||||
class="plain downloadsPanelFooterButton"
|
||||
label="&downloadsHistory.label;"
|
||||
accesskey="&downloadsHistory.accesskey;"
|
||||
oncommand="DownloadsPanel.showDownloadsHistory();"/>
|
||||
<hbox id="downloadsFooterButtons">
|
||||
<button id="downloadsHistory"
|
||||
class="plain downloadsPanelFooterButton"
|
||||
label="&downloadsHistory.label;"
|
||||
accesskey="&downloadsHistory.accesskey;"
|
||||
flex="1"
|
||||
oncommand="DownloadsPanel.showDownloadsHistory();"/>
|
||||
<toolbarseparator id="downloadsFooterButtonsSplitter"
|
||||
class="downloadsDropmarkerSplitter"/>
|
||||
<button id="downloadsFooterDropmarker"
|
||||
class="plain downloadsPanelFooterButton downloadsDropmarker"
|
||||
type="menu">
|
||||
<menupopup id="downloadSubPanel"
|
||||
onpopupshowing="DownloadsPanel.onFooterPopupShowing(event);"
|
||||
onpopuphidden="DownloadsPanel.onFooterPopupHidden(event);"
|
||||
position="after_end">
|
||||
<menuitem id="downloadsDropdownItemClearList"
|
||||
command="downloadsCmd_clearList"
|
||||
label="&cmd.clearList2.label;"/>
|
||||
<menuitem id="downloadsDropdownItemOpenDownloadsFolder"
|
||||
oncommand="DownloadsPanel.openDownloadsFolder();"
|
||||
label="&openDownloadsFolder.label;"/>
|
||||
</menupopup>
|
||||
</button>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</panelview>
|
||||
|
||||
|
|
|
@ -10,3 +10,4 @@ skip-if = os == "linux" # Bug 952422
|
|||
[browser_confirm_unblock_download.js]
|
||||
[browser_iframe_gone_mid_download.js]
|
||||
[browser_downloads_panel_block.js]
|
||||
[browser_downloads_panel_footer.js]
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
"use strict";
|
||||
|
||||
function *task_openDownloadsSubPanel() {
|
||||
let downloadSubPanel = document.getElementById("downloadSubPanel");
|
||||
let popupShownPromise = BrowserTestUtils.waitForEvent(downloadSubPanel, "popupshown");
|
||||
|
||||
let downloadsDropmarker = document.getElementById("downloadsFooterDropmarker");
|
||||
EventUtils.synthesizeMouseAtCenter(downloadsDropmarker, {}, window);
|
||||
|
||||
yield popupShownPromise;
|
||||
}
|
||||
|
||||
add_task(function* test_openDownloadsFolder() {
|
||||
yield task_openPanel();
|
||||
|
||||
yield task_openDownloadsSubPanel();
|
||||
|
||||
yield new Promise(resolve => {
|
||||
sinon.stub(DownloadsCommon, "showDirectory", file => {
|
||||
resolve(Downloads.getPreferredDownloadsDirectory().then(downloadsPath => {
|
||||
is(file.path, downloadsPath, "Check the download folder path.");
|
||||
}));
|
||||
});
|
||||
|
||||
let itemOpenDownloadsFolder =
|
||||
document.getElementById("downloadsDropdownItemOpenDownloadsFolder");
|
||||
EventUtils.synthesizeMouseAtCenter(itemOpenDownloadsFolder, {}, window);
|
||||
});
|
||||
|
||||
yield task_resetState();
|
||||
});
|
||||
|
||||
add_task(function* test_clearList() {
|
||||
const kTestCases = [{
|
||||
downloads: [
|
||||
{ state: nsIDM.DOWNLOAD_NOTSTARTED },
|
||||
{ state: nsIDM.DOWNLOAD_FINISHED },
|
||||
{ state: nsIDM.DOWNLOAD_FAILED },
|
||||
{ state: nsIDM.DOWNLOAD_CANCELED },
|
||||
],
|
||||
expectClearListShown: true,
|
||||
expectedItemNumber: 0,
|
||||
},{
|
||||
downloads: [
|
||||
{ state: nsIDM.DOWNLOAD_NOTSTARTED },
|
||||
{ state: nsIDM.DOWNLOAD_FINISHED },
|
||||
{ state: nsIDM.DOWNLOAD_FAILED },
|
||||
{ state: nsIDM.DOWNLOAD_PAUSED },
|
||||
{ state: nsIDM.DOWNLOAD_CANCELED },
|
||||
],
|
||||
expectClearListShown: true,
|
||||
expectedItemNumber: 1,
|
||||
},{
|
||||
downloads: [
|
||||
{ state: nsIDM.DOWNLOAD_PAUSED },
|
||||
],
|
||||
expectClearListShown: false,
|
||||
expectedItemNumber: 1,
|
||||
}];
|
||||
|
||||
for (let testCase of kTestCases) {
|
||||
yield verify_clearList(testCase);
|
||||
}
|
||||
});
|
||||
|
||||
function *verify_clearList(testCase) {
|
||||
let downloads = testCase.downloads;
|
||||
yield task_addDownloads(downloads);
|
||||
|
||||
yield task_openPanel();
|
||||
is(DownloadsView._downloads.length, downloads.length,
|
||||
"Expect the number of download items");
|
||||
|
||||
yield task_openDownloadsSubPanel();
|
||||
|
||||
let itemClearList = document.getElementById("downloadsDropdownItemClearList");
|
||||
let itemNumberPromise = BrowserTestUtils.waitForCondition(() => {
|
||||
return DownloadsView._downloads.length === testCase.expectedItemNumber;
|
||||
});
|
||||
if (testCase.expectClearListShown) {
|
||||
isnot("true", itemClearList.getAttribute("hidden"),
|
||||
"Should show Clear Preview Panel button");
|
||||
EventUtils.synthesizeMouseAtCenter(itemClearList, {}, window);
|
||||
} else {
|
||||
is("true", itemClearList.getAttribute("hidden"),
|
||||
"Should not show Clear Preview Panel button");
|
||||
}
|
||||
|
||||
yield itemNumberPromise;
|
||||
is(DownloadsView._downloads.length, testCase.expectedItemNumber,
|
||||
"Download items remained.");
|
||||
|
||||
yield task_resetState();
|
||||
}
|
|
@ -24,8 +24,17 @@ const nsIDM = Ci.nsIDownloadManager;
|
|||
|
||||
var gTestTargetFile = FileUtils.getFile("TmpD", ["dm-ui-test.file"]);
|
||||
gTestTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
|
||||
|
||||
// Load mocking/stubbing library, sinon
|
||||
// docs: http://sinonjs.org/docs/
|
||||
Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
gTestTargetFile.remove(false);
|
||||
|
||||
delete window.sinon;
|
||||
delete window.setImmediate;
|
||||
delete window.clearImmediate;
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -135,12 +135,16 @@ function* runTests(options) {
|
|||
});
|
||||
});
|
||||
|
||||
yield SpecialPowers.pushPrefEnv({set: [["general.useragent.locale", "es-ES"]]});
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
yield awaitFinish;
|
||||
|
||||
yield extension.unload();
|
||||
|
||||
yield SpecialPowers.popPrefEnv();
|
||||
|
||||
let node = document.getElementById(pageActionId);
|
||||
is(node, null, "pageAction image removed from document");
|
||||
|
||||
|
@ -182,6 +186,18 @@ add_task(function* testTabSwitchContext() {
|
|||
},
|
||||
},
|
||||
|
||||
"_locales/es_ES/messages.json": {
|
||||
"popup": {
|
||||
"message": "default.html",
|
||||
"description": "Popup",
|
||||
},
|
||||
|
||||
"title": {
|
||||
"message": "T\u00edtulo",
|
||||
"description": "Title",
|
||||
},
|
||||
},
|
||||
|
||||
"default.png": imageBuffer,
|
||||
"1.png": imageBuffer,
|
||||
"2.png": imageBuffer,
|
||||
|
@ -191,16 +207,16 @@ add_task(function* testTabSwitchContext() {
|
|||
let details = [
|
||||
{"icon": browser.runtime.getURL("default.png"),
|
||||
"popup": browser.runtime.getURL("default.html"),
|
||||
"title": "Default Title \u263a"},
|
||||
"title": "Default T\u00edtulo \u263a"},
|
||||
{"icon": browser.runtime.getURL("1.png"),
|
||||
"popup": browser.runtime.getURL("default.html"),
|
||||
"title": "Default Title \u263a"},
|
||||
"title": "Default T\u00edtulo \u263a"},
|
||||
{"icon": browser.runtime.getURL("2.png"),
|
||||
"popup": browser.runtime.getURL("2.html"),
|
||||
"title": "Title 2"},
|
||||
{"icon": browser.runtime.getURL("2.png"),
|
||||
"popup": browser.runtime.getURL("2.html"),
|
||||
"title": "Default Title \u263a"},
|
||||
"title": "Default T\u00edtulo \u263a"},
|
||||
];
|
||||
|
||||
let promiseTabLoad = details => {
|
||||
|
|
|
@ -62,8 +62,8 @@
|
|||
<!ENTITY cmd.copyDownloadLink.accesskey "L">
|
||||
<!ENTITY cmd.removeFromHistory.label "Remove From History">
|
||||
<!ENTITY cmd.removeFromHistory.accesskey "e">
|
||||
<!ENTITY cmd.clearList.label "Clear List">
|
||||
<!ENTITY cmd.clearList.accesskey "a">
|
||||
<!ENTITY cmd.clearList2.label "Clear Preview Panel">
|
||||
<!ENTITY cmd.clearList2.accesskey "a">
|
||||
<!ENTITY cmd.clearDownloads.label "Clear Downloads">
|
||||
<!ENTITY cmd.clearDownloads.accesskey "D">
|
||||
<!-- LOCALIZATION NOTE (cmd.unblock2.label):
|
||||
|
@ -109,6 +109,8 @@
|
|||
<!ENTITY downloadsHistory.label "Show All Downloads">
|
||||
<!ENTITY downloadsHistory.accesskey "S">
|
||||
|
||||
<!ENTITY openDownloadsFolder.label "Open Downloads Folder">
|
||||
|
||||
<!ENTITY clearDownloadsButton.label "Clear Downloads">
|
||||
<!ENTITY clearDownloadsButton.tooltip "Clears completed, canceled and failed downloads">
|
||||
|
||||
|
|
|
@ -81,7 +81,8 @@ var ContentClick = {
|
|||
referrerURI: browser.documentURI,
|
||||
referrerPolicy: json.referrerPolicy,
|
||||
noReferrer: json.noReferrer,
|
||||
allowMixedContent: json.allowMixedContent };
|
||||
allowMixedContent: json.allowMixedContent,
|
||||
isContentWindowPrivate: json.isContentWindowPrivate};
|
||||
|
||||
// The new tab/window must use the same userContextId.
|
||||
if (json.originAttributes.userContextId) {
|
||||
|
|
|
@ -30,7 +30,8 @@
|
|||
}
|
||||
|
||||
#emptyDownloads {
|
||||
padding: 10px 20px;
|
||||
padding: 16px 25px;
|
||||
margin: 0;
|
||||
/* The panel can be wider than this description after the blocked subview is
|
||||
shown, so center the text. */
|
||||
text-align: center;
|
||||
|
@ -41,7 +42,7 @@
|
|||
border-top: 1px solid var(--panel-separator-color);
|
||||
}
|
||||
|
||||
.downloadsPanelFooter > toolbarseparator {
|
||||
.downloadsPanelFooter toolbarseparator {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
min-width: 0;
|
||||
|
@ -55,6 +56,7 @@
|
|||
color: inherit;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
|
@ -63,7 +65,8 @@
|
|||
background-color: hsla(210,4%,10%,.07);
|
||||
}
|
||||
|
||||
.downloadsPanelFooterButton:hover:active {
|
||||
.downloadsPanelFooterButton:hover:active,
|
||||
.downloadsPanelFooterButton[open="true"] {
|
||||
outline: 1px solid hsla(210,4%,10%,.12);
|
||||
background-color: hsla(210,4%,10%,.12);
|
||||
box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
|
||||
|
@ -82,6 +85,47 @@
|
|||
background-color: #0568ba;
|
||||
}
|
||||
|
||||
#downloadsPanel[hasdownloads] #downloadsHistory {
|
||||
padding-left: 58px !important;
|
||||
}
|
||||
|
||||
toolbarseparator.downloadsDropmarkerSplitter {
|
||||
margin: 7px 0;
|
||||
}
|
||||
|
||||
#downloadsFooter:hover toolbarseparator.downloadsDropmarkerSplitter,
|
||||
#downloadsFooter toolbarseparator.downloadsDropmarkerSplitterExtend {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.downloadsDropmarker {
|
||||
padding: 0 19px !important;
|
||||
}
|
||||
|
||||
.downloadsDropmarker > .button-box > hbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.downloadsDropmarker > .button-box > .button-menu-dropmarker {
|
||||
/* This is to override the linux !important */
|
||||
-moz-appearance: none !important;
|
||||
display: -moz-box;
|
||||
}
|
||||
|
||||
.downloadsDropmarker > .button-box > .button-menu-dropmarker > .dropmarker-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
list-style-image: url("chrome://browser/skin/downloads/menubutton-dropmarker.svg");
|
||||
filter: url("chrome://browser/skin/filters.svg#fill");
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
/* Override default icon size which is too small for this dropdown */
|
||||
.downloadsDropmarker > .button-box > .button-menu-dropmarker {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
#downloadsSummary {
|
||||
--summary-padding-end: 38px;
|
||||
--summary-padding-start: 12px;
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?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="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="m 2,6 6,6 6,-6 -1.5,-1.5 -4.5,4.5 -4.5,-4.5 z" />
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 391 B |
|
@ -54,6 +54,7 @@
|
|||
skin/classic/browser/customizableui/whimsy@2x.png (../shared/customizableui/whimsy@2x.png)
|
||||
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/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)
|
||||
|
|
|
@ -199,7 +199,6 @@ def old_configure_options(*options):
|
|||
'--enable-memory-sanitizer',
|
||||
'--enable-mobile-optimize',
|
||||
'--enable-mozril-geoloc',
|
||||
'--enable-necko-protocols',
|
||||
'--enable-necko-wifi',
|
||||
'--enable-negotiateauth',
|
||||
'--enable-nfc',
|
||||
|
|
|
@ -18,129 +18,11 @@ const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
|
|||
// We prefix all our local storage items with this.
|
||||
const PREFIX = "Services.prefs:";
|
||||
|
||||
/**
|
||||
* Create a new preference object.
|
||||
*
|
||||
* @param {PrefBranch} branch the branch holding this preference
|
||||
* @param {String} name the base name of this preference
|
||||
* @param {String} fullName the fully-qualified name of this preference
|
||||
*/
|
||||
function Preference(branch, name, fullName) {
|
||||
this.branch = branch;
|
||||
this.name = name;
|
||||
this.fullName = fullName;
|
||||
this.defaultValue = null;
|
||||
this.hasUserValue = false;
|
||||
this.userValue = null;
|
||||
this.type = null;
|
||||
}
|
||||
|
||||
Preference.prototype = {
|
||||
/**
|
||||
* Return this preference's current value.
|
||||
*
|
||||
* @return {Any} The current value of this preference. This may
|
||||
* return a string, a number, or a boolean depending on the
|
||||
* preference's type.
|
||||
*/
|
||||
get: function () {
|
||||
if (this.hasUserValue) {
|
||||
return this.userValue;
|
||||
}
|
||||
return this.defaultValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the preference's value. The new value is assumed to be a
|
||||
* user value. After setting the value, this function emits a
|
||||
* change notification.
|
||||
*
|
||||
* @param {Any} value the new value
|
||||
*/
|
||||
set: function (value) {
|
||||
if (!this.hasUserValue || value !== this.userValue) {
|
||||
this.userValue = value;
|
||||
this.hasUserValue = true;
|
||||
this.saveAndNotify();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the default value for this preference, and emit a
|
||||
* notification if this results in a visible change.
|
||||
*
|
||||
* @param {Any} value the new default value
|
||||
*/
|
||||
setDefault: function (value) {
|
||||
if (this.defaultValue !== value) {
|
||||
this.defaultValue = value;
|
||||
if (!this.hasUserValue) {
|
||||
this.saveAndNotify();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* If this preference has a user value, clear it. If a change was
|
||||
* made, emit a change notification.
|
||||
*/
|
||||
clearUserValue: function () {
|
||||
if (this.hasUserValue) {
|
||||
this.userValue = null;
|
||||
this.hasUserValue = false;
|
||||
this.saveAndNotify();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function to write the preference's value to local storage
|
||||
* and then emit a change notification.
|
||||
*/
|
||||
saveAndNotify: function () {
|
||||
let store = {
|
||||
type: this.type,
|
||||
defaultValue: this.defaultValue,
|
||||
hasUserValue: this.hasUserValue,
|
||||
userValue: this.userValue,
|
||||
};
|
||||
|
||||
localStorage.setItem(PREFIX + this.fullName, JSON.stringify(store));
|
||||
this.branch._notify(this.name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Change this preference's value without writing it back to local
|
||||
* storage. This is used to handle changes to local storage that
|
||||
* were made externally.
|
||||
*
|
||||
* @param {Number} type one of the PREF_* values
|
||||
* @param {Any} userValue the user value to use if the pref does not exist
|
||||
* @param {Any} defaultValue the default value to use if the pref
|
||||
* does not exist
|
||||
* @param {Boolean} hasUserValue if a new pref is created, whether
|
||||
* the default value is also a user value
|
||||
* @param {Object} store the new value of the preference. It should
|
||||
* be of the form {type, defaultValue, hasUserValue, userValue};
|
||||
* where |type| is one of the PREF_* type constants; |defaultValue|
|
||||
* and |userValue| are the default and user values, respectively;
|
||||
* and |hasUserValue| is a boolean indicating whether the user value
|
||||
* is valid
|
||||
*/
|
||||
storageUpdated: function (type, userValue, hasUserValue, defaultValue) {
|
||||
this.type = type;
|
||||
this.defaultValue = defaultValue;
|
||||
this.hasUserValue = hasUserValue;
|
||||
this.userValue = userValue;
|
||||
// There's no need to write this back to local storage, since it
|
||||
// came from there; and this avoids infinite event loops.
|
||||
this.branch._notify(this.name);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new preference branch. This object conforms largely to
|
||||
* nsIPrefBranch and nsIPrefService, though it only implements the
|
||||
* subset needed by devtools.
|
||||
* subset needed by devtools. A preference branch can hold child
|
||||
* preferences while also holding a preference value itself.
|
||||
*
|
||||
* @param {PrefBranch} parent the parent branch, or null for the root
|
||||
* branch.
|
||||
|
@ -154,6 +36,12 @@ function PrefBranch(parent, name, fullName) {
|
|||
this._observers = {};
|
||||
this._children = {};
|
||||
|
||||
// Properties used when this branch has a value as well.
|
||||
this._defaultValue = null;
|
||||
this._hasUserValue = false;
|
||||
this._userValue = null;
|
||||
this._type = PREF_INVALID;
|
||||
|
||||
if (!parent) {
|
||||
this._initializeRoot();
|
||||
}
|
||||
|
@ -172,16 +60,16 @@ PrefBranch.prototype = {
|
|||
|
||||
/** @see nsIPrefBranch.getPrefType. */
|
||||
getPrefType: function (prefName) {
|
||||
return this._findPref(prefName).type;
|
||||
return this._findPref(prefName)._type;
|
||||
},
|
||||
|
||||
/** @see nsIPrefBranch.getBoolPref. */
|
||||
getBoolPref: function (prefName) {
|
||||
let thePref = this._findPref(prefName);
|
||||
if (thePref.type !== PREF_BOOL) {
|
||||
if (thePref._type !== PREF_BOOL) {
|
||||
throw new Error(`${prefName} does not have bool type`);
|
||||
}
|
||||
return thePref.get();
|
||||
return thePref._get();
|
||||
},
|
||||
|
||||
/** @see nsIPrefBranch.setBoolPref. */
|
||||
|
@ -190,19 +78,19 @@ PrefBranch.prototype = {
|
|||
throw new Error("non-bool passed to setBoolPref");
|
||||
}
|
||||
let thePref = this._findOrCreatePref(prefName, value, true, value);
|
||||
if (thePref.type !== PREF_BOOL) {
|
||||
if (thePref._type !== PREF_BOOL) {
|
||||
throw new Error(`${prefName} does not have bool type`);
|
||||
}
|
||||
thePref.set(value);
|
||||
thePref._set(value);
|
||||
},
|
||||
|
||||
/** @see nsIPrefBranch.getCharPref. */
|
||||
getCharPref: function (prefName) {
|
||||
let thePref = this._findPref(prefName);
|
||||
if (thePref.type !== PREF_STRING) {
|
||||
if (thePref._type !== PREF_STRING) {
|
||||
throw new Error(`${prefName} does not have string type`);
|
||||
}
|
||||
return thePref.get();
|
||||
return thePref._get();
|
||||
},
|
||||
|
||||
/** @see nsIPrefBranch.setCharPref. */
|
||||
|
@ -211,19 +99,19 @@ PrefBranch.prototype = {
|
|||
throw new Error("non-string passed to setCharPref");
|
||||
}
|
||||
let thePref = this._findOrCreatePref(prefName, value, true, value);
|
||||
if (thePref.type !== PREF_STRING) {
|
||||
if (thePref._type !== PREF_STRING) {
|
||||
throw new Error(`${prefName} does not have string type`);
|
||||
}
|
||||
thePref.set(value);
|
||||
thePref._set(value);
|
||||
},
|
||||
|
||||
/** @see nsIPrefBranch.getIntPref. */
|
||||
getIntPref: function (prefName) {
|
||||
let thePref = this._findPref(prefName);
|
||||
if (thePref.type !== PREF_INT) {
|
||||
if (thePref._type !== PREF_INT) {
|
||||
throw new Error(`${prefName} does not have int type`);
|
||||
}
|
||||
return thePref.get();
|
||||
return thePref._get();
|
||||
},
|
||||
|
||||
/** @see nsIPrefBranch.setIntPref. */
|
||||
|
@ -232,22 +120,22 @@ PrefBranch.prototype = {
|
|||
throw new Error("non-number passed to setIntPref");
|
||||
}
|
||||
let thePref = this._findOrCreatePref(prefName, value, true, value);
|
||||
if (thePref.type !== PREF_INT) {
|
||||
if (thePref._type !== PREF_INT) {
|
||||
throw new Error(`${prefName} does not have int type`);
|
||||
}
|
||||
thePref.set(value);
|
||||
thePref._set(value);
|
||||
},
|
||||
|
||||
/** @see nsIPrefBranch.clearUserPref */
|
||||
clearUserPref: function (prefName) {
|
||||
let thePref = this._findPref(prefName);
|
||||
thePref.clearUserValue();
|
||||
thePref._clearUserValue();
|
||||
},
|
||||
|
||||
/** @see nsIPrefBranch.prefHasUserValue */
|
||||
prefHasUserValue: function (prefName) {
|
||||
let thePref = this._findPref(prefName);
|
||||
return thePref.hasUserValue;
|
||||
return thePref._hasUserValue;
|
||||
},
|
||||
|
||||
/** @see nsIPrefBranch.addObserver */
|
||||
|
@ -294,6 +182,106 @@ PrefBranch.prototype = {
|
|||
return this._findPref(prefRoot);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return this preference's current value.
|
||||
*
|
||||
* @return {Any} The current value of this preference. This may
|
||||
* return a string, a number, or a boolean depending on the
|
||||
* preference's type.
|
||||
*/
|
||||
_get: function () {
|
||||
if (this._hasUserValue) {
|
||||
return this._userValue;
|
||||
}
|
||||
return this._defaultValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the preference's value. The new value is assumed to be a
|
||||
* user value. After setting the value, this function emits a
|
||||
* change notification.
|
||||
*
|
||||
* @param {Any} value the new value
|
||||
*/
|
||||
_set: function (value) {
|
||||
if (!this._hasUserValue || value !== this._userValue) {
|
||||
this._userValue = value;
|
||||
this._hasUserValue = true;
|
||||
this._saveAndNotify();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the default value for this preference, and emit a
|
||||
* notification if this results in a visible change.
|
||||
*
|
||||
* @param {Any} value the new default value
|
||||
*/
|
||||
_setDefault: function (value) {
|
||||
if (this._defaultValue !== value) {
|
||||
this._defaultValue = value;
|
||||
if (!this._hasUserValue) {
|
||||
this._saveAndNotify();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* If this preference has a user value, clear it. If a change was
|
||||
* made, emit a change notification.
|
||||
*/
|
||||
_clearUserValue: function () {
|
||||
if (this._hasUserValue) {
|
||||
this._userValue = null;
|
||||
this._hasUserValue = false;
|
||||
this._saveAndNotify();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function to write the preference's value to local storage
|
||||
* and then emit a change notification.
|
||||
*/
|
||||
_saveAndNotify: function () {
|
||||
let store = {
|
||||
type: this._type,
|
||||
defaultValue: this._defaultValue,
|
||||
hasUserValue: this._hasUserValue,
|
||||
userValue: this._userValue,
|
||||
};
|
||||
|
||||
localStorage.setItem(PREFIX + this.fullName, JSON.stringify(store));
|
||||
this._parent._notify(this._name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Change this preference's value without writing it back to local
|
||||
* storage. This is used to handle changes to local storage that
|
||||
* were made externally.
|
||||
*
|
||||
* @param {Number} type one of the PREF_* values
|
||||
* @param {Any} userValue the user value to use if the pref does not exist
|
||||
* @param {Any} defaultValue the default value to use if the pref
|
||||
* does not exist
|
||||
* @param {Boolean} hasUserValue if a new pref is created, whether
|
||||
* the default value is also a user value
|
||||
* @param {Object} store the new value of the preference. It should
|
||||
* be of the form {type, defaultValue, hasUserValue, userValue};
|
||||
* where |type| is one of the PREF_* type constants; |defaultValue|
|
||||
* and |userValue| are the default and user values, respectively;
|
||||
* and |hasUserValue| is a boolean indicating whether the user value
|
||||
* is valid
|
||||
*/
|
||||
_storageUpdated: function (type, userValue, hasUserValue, defaultValue) {
|
||||
this._type = type;
|
||||
this._defaultValue = defaultValue;
|
||||
this._hasUserValue = hasUserValue;
|
||||
this._userValue = userValue;
|
||||
// There's no need to write this back to local storage, since it
|
||||
// came from there; and this avoids infinite event loops.
|
||||
this._parent._notify(this._name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function to find either a Preference or PrefBranch object
|
||||
* given its name. If the name is not found, throws an exception.
|
||||
|
@ -378,36 +366,34 @@ PrefBranch.prototype = {
|
|||
* the default value is also a user value
|
||||
*/
|
||||
_findOrCreatePref: function (keyName, userValue, hasUserValue, defaultValue) {
|
||||
let branchName = keyName.split(".");
|
||||
let prefName = branchName.pop();
|
||||
let branch = this._createBranch(keyName.split("."));
|
||||
|
||||
let branch = this._createBranch(branchName);
|
||||
if (!(prefName in branch._children)) {
|
||||
if (hasUserValue && typeof (userValue) !== typeof (defaultValue)) {
|
||||
throw new Error("inconsistent values when creating " + keyName);
|
||||
}
|
||||
|
||||
let type;
|
||||
switch (typeof (defaultValue)) {
|
||||
case "boolean":
|
||||
type = PREF_BOOL;
|
||||
break;
|
||||
case "number":
|
||||
type = PREF_INT;
|
||||
break;
|
||||
case "string":
|
||||
type = PREF_STRING;
|
||||
break;
|
||||
default:
|
||||
throw new Error("unhandled argument type: " + typeof (defaultValue));
|
||||
}
|
||||
|
||||
let thePref = new Preference(branch, prefName, keyName);
|
||||
thePref.storageUpdated(type, userValue, hasUserValue, defaultValue);
|
||||
branch._children[prefName] = thePref;
|
||||
if (hasUserValue && typeof (userValue) !== typeof (defaultValue)) {
|
||||
throw new Error("inconsistent values when creating " + keyName);
|
||||
}
|
||||
|
||||
return branch._children[prefName];
|
||||
let type;
|
||||
switch (typeof (defaultValue)) {
|
||||
case "boolean":
|
||||
type = PREF_BOOL;
|
||||
break;
|
||||
case "number":
|
||||
type = PREF_INT;
|
||||
break;
|
||||
case "string":
|
||||
type = PREF_STRING;
|
||||
break;
|
||||
default:
|
||||
throw new Error("unhandled argument type: " + typeof (defaultValue));
|
||||
}
|
||||
|
||||
if (branch._type === PREF_INVALID) {
|
||||
branch._storageUpdated(type, userValue, hasUserValue, defaultValue);
|
||||
} else if (branch._type !== type) {
|
||||
throw new Error("attempt to change type of pref " + keyName);
|
||||
}
|
||||
|
||||
return branch;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -432,7 +418,7 @@ PrefBranch.prototype = {
|
|||
this._findOrCreatePref(event.key, userValue, hasUserValue, defaultValue);
|
||||
} else {
|
||||
let thePref = this._findPref(event.key);
|
||||
thePref.storageUpdated(type, userValue, hasUserValue, defaultValue);
|
||||
thePref._storageUpdated(type, userValue, hasUserValue, defaultValue);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -592,7 +578,7 @@ const Services = {
|
|||
*/
|
||||
function pref(name, value) {
|
||||
let thePref = Services.prefs._findOrCreatePref(name, value, true, value);
|
||||
thePref.setDefault(value);
|
||||
thePref._setDefault(value);
|
||||
}
|
||||
|
||||
module.exports = Services;
|
||||
|
|
|
@ -218,6 +218,9 @@ function do_tests() {
|
|||
"someotherstring": true
|
||||
}, "pref worked");
|
||||
|
||||
// Regression test for bug 1296427.
|
||||
pref("devtools.hud.loglimit", 1000);
|
||||
pref("devtools.hud.loglimit.network", 1000);
|
||||
|
||||
// Clean up.
|
||||
localStorage.clear();
|
||||
|
|
|
@ -395,6 +395,10 @@ html, body, #app, #memory-tool {
|
|||
padding-inline-end: 5px;
|
||||
}
|
||||
|
||||
.children-pointer:dir(rtl) {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Heap tree view columns
|
||||
*/
|
||||
|
|
|
@ -27,7 +27,6 @@ function VariablesViewLink(props) {
|
|||
onClick: openVariablesView.bind(null, object),
|
||||
className: "cm-variable",
|
||||
draggable: false,
|
||||
href: "#"
|
||||
}, children)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -44,7 +44,10 @@ const chromeRDPEnums = {
|
|||
// Undocumented in Chrome RDP, but is used for evaluation results.
|
||||
RESULT: "result",
|
||||
// Undocumented in Chrome RDP, but is used for input.
|
||||
COMMAND: "command"
|
||||
COMMAND: "command",
|
||||
// Undocumented in Chrome RDP, but is used for messages that should not
|
||||
// output anything (e.g. `console.time()` calls).
|
||||
NULL_MESSAGE: "nullMessage",
|
||||
},
|
||||
MESSAGE_LEVEL: {
|
||||
LOG: "log",
|
||||
|
|
|
@ -21,7 +21,11 @@ function messages(state = new MessageState(), action) {
|
|||
case constants.MESSAGE_ADD:
|
||||
let newMessage = action.message;
|
||||
|
||||
if (newMessage.type === "clear") {
|
||||
if (newMessage.type === constants.MESSAGE_TYPE.NULL_MESSAGE) {
|
||||
return state;
|
||||
}
|
||||
|
||||
if (newMessage.type === constants.MESSAGE_TYPE.CLEAR) {
|
||||
return state.set("messagesById", Immutable.List([newMessage]));
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,27 @@ describe("ConsoleAPICall component:", () => {
|
|||
expect(messageBody.textContent).toBe(message.messageText);
|
||||
});
|
||||
});
|
||||
|
||||
describe("console.time", () => {
|
||||
it("does not show anything", () => {
|
||||
const message = stubConsoleMessages.get("console.time('bar')");
|
||||
const rendered = renderComponent(ConsoleApiCall, {message, onViewSourceInDebugger});
|
||||
|
||||
const messageBody = getMessageBody(rendered);
|
||||
expect(messageBody.textContent).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("console.timeEnd", () => {
|
||||
it("renders as expected", () => {
|
||||
const message = stubConsoleMessages.get("console.timeEnd('bar')");
|
||||
const rendered = renderComponent(ConsoleApiCall, {message, onViewSourceInDebugger});
|
||||
|
||||
const messageBody = getMessageBody(rendered);
|
||||
expect(messageBody.textContent).toBe(message.messageText);
|
||||
expect(messageBody.textContent).toMatch(/^bar: \d+(\.\d+)?ms$/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getMessageBody(rendered) {
|
||||
|
@ -56,4 +77,4 @@ function getMessageBody(rendered) {
|
|||
function getRepeatNode(rendered) {
|
||||
const repeatPath = "span > span.message-flex-body > span.message-body.devtools-monospace + span.message-repeats";
|
||||
return rendered.querySelectorAll(repeatPath);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,11 +12,10 @@ const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-cons
|
|||
|
||||
let stubs = [];
|
||||
|
||||
snippets.forEach((code, key) => {
|
||||
add_task(function* () {
|
||||
let tempFilePath = OS.Path.join(`${BASE_PATH}/stub-generators`, "test-tempfile.js");
|
||||
add_task(function* () {
|
||||
let tempFilePath = OS.Path.join(`${BASE_PATH}/stub-generators`, "test-tempfile.js");
|
||||
for (var [key, {keys, code}] of snippets) {
|
||||
OS.File.writeAtomic(tempFilePath, `function triggerPacket() {${code}}`);
|
||||
|
||||
let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
|
||||
let hud = toolbox.getCurrentPanel().hud;
|
||||
let {ui} = hud;
|
||||
|
@ -24,17 +23,23 @@ snippets.forEach((code, key) => {
|
|||
ok(ui.jsterm, "jsterm exists");
|
||||
ok(ui.newConsoleOutput, "newConsoleOutput exists");
|
||||
|
||||
toolbox.target.client.addListener("consoleAPICall", (type, res) => {
|
||||
stubs.push(formatStub(key, res));
|
||||
if (stubs.length == snippets.size) {
|
||||
let filePath = OS.Path.join(`${BASE_PATH}/stubs`, "consoleApi.js");
|
||||
OS.File.writeAtomic(filePath, formatFile(stubs));
|
||||
OS.File.writeAtomic(tempFilePath, "");
|
||||
}
|
||||
let received = new Promise(resolve => {
|
||||
let i = 0;
|
||||
toolbox.target.client.addListener("consoleAPICall", (type, res) => {
|
||||
stubs.push(formatStub(keys[i], res));
|
||||
if(++i === keys.length ){
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
|
||||
content.wrappedJSObject.triggerPacket();
|
||||
});
|
||||
});
|
||||
|
||||
yield received;
|
||||
}
|
||||
let filePath = OS.Path.join(`${BASE_PATH}/stubs`, "consoleApi.js");
|
||||
OS.File.writeAtomic(filePath, formatFile(stubs));
|
||||
OS.File.writeAtomic(tempFilePath, "");
|
||||
});
|
||||
|
|
|
@ -15,10 +15,12 @@ const consoleApiCommands = [
|
|||
"console.count('bar')",
|
||||
];
|
||||
|
||||
let consoleApi = new Map(consoleApiCommands.map(cmd => [cmd, cmd]));
|
||||
let consoleApi = new Map(consoleApiCommands.map(
|
||||
cmd => [cmd, {keys: [cmd], code: cmd}]));
|
||||
|
||||
consoleApi.set("console.trace()",
|
||||
`
|
||||
consoleApi.set("console.trace()", {
|
||||
keys: ["console.trace()"],
|
||||
code: `
|
||||
function bar() {
|
||||
console.trace()
|
||||
}
|
||||
|
@ -27,13 +29,14 @@ function foo() {
|
|||
}
|
||||
|
||||
foo()
|
||||
`);
|
||||
`});
|
||||
|
||||
consoleApi.set("console.time()",
|
||||
`
|
||||
console.time()
|
||||
console.timeEnd()
|
||||
`);
|
||||
consoleApi.set("console.time('bar')", {
|
||||
keys: ["console.time('bar')", "console.timeEnd('bar')"],
|
||||
code: `
|
||||
console.time("bar");
|
||||
console.timeEnd("bar");
|
||||
`});
|
||||
|
||||
// Evaluation Result
|
||||
|
||||
|
|
|
@ -196,5 +196,41 @@ stubConsoleMessages.set("console.trace()", new ConsoleMessage({
|
|||
}
|
||||
}));
|
||||
|
||||
stubConsoleMessages.set("console.time('bar')", new ConsoleMessage({
|
||||
"id": "1",
|
||||
"allowRepeating": true,
|
||||
"source": "console-api",
|
||||
"type": "nullMessage",
|
||||
"level": "log",
|
||||
"messageText": null,
|
||||
"parameters": null,
|
||||
"repeat": 1,
|
||||
"repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"nullMessage\",\"level\":\"log\",\"messageText\":null,\"parameters\":null,\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js\",\"line\":2,\"column\":1}}",
|
||||
"stacktrace": null,
|
||||
"frame": {
|
||||
"source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
|
||||
"line": 2,
|
||||
"column": 1
|
||||
}
|
||||
}));
|
||||
|
||||
stubConsoleMessages.set("console.timeEnd('bar')", new ConsoleMessage({
|
||||
"id": "1",
|
||||
"allowRepeating": true,
|
||||
"source": "console-api",
|
||||
"type": "timeEnd",
|
||||
"level": "log",
|
||||
"messageText": "bar: 3.87ms",
|
||||
"parameters": null,
|
||||
"repeat": 1,
|
||||
"repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"timeEnd\",\"level\":\"log\",\"messageText\":\"bar: 3.87ms\",\"parameters\":null,\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js\",\"line\":3,\"column\":1}}",
|
||||
"stacktrace": null,
|
||||
"frame": {
|
||||
"source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
|
||||
"line": 3,
|
||||
"column": 1
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
module.exports = stubConsoleMessages
|
|
@ -96,4 +96,14 @@ describe("Message reducer:", () => {
|
|||
expect(messages.first().parameters[0]).toBe(`message num 2`);
|
||||
expect(messages.last().parameters[0]).toBe(`message num ${logLimit + 1}`);
|
||||
});
|
||||
|
||||
it("does not add null messages to the store", () => {
|
||||
const { dispatch, getState } = setupStore([]);
|
||||
|
||||
const message = stubConsoleMessages.get("console.time('bar')");
|
||||
dispatch(actions.messageAdd(message));
|
||||
|
||||
const messages = getAllMessages(getState());
|
||||
expect(messages.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -45,6 +45,7 @@ function transformPacket(packet) {
|
|||
let type = message.level;
|
||||
let level = getLevelFromType(type);
|
||||
let messageText = null;
|
||||
const timer = message.timer;
|
||||
|
||||
// Special per-type conversion.
|
||||
switch (type) {
|
||||
|
@ -60,6 +61,23 @@ function transformPacket(packet) {
|
|||
messageText = `${label}: ${counter.count}`;
|
||||
parameters = null;
|
||||
break;
|
||||
case "time":
|
||||
// We don't show anything for console.time calls to match Chrome's behaviour.
|
||||
parameters = null;
|
||||
type = MESSAGE_TYPE.NULL_MESSAGE;
|
||||
break;
|
||||
case "timeEnd":
|
||||
parameters = null;
|
||||
if (timer) {
|
||||
// We show the duration to users when calls console.timeEnd() is called,
|
||||
// if corresponding console.time() was called before.
|
||||
let duration = Math.round(timer.duration * 100) / 100;
|
||||
messageText = l10n.getFormatStr("timeEnd", [timer.name, duration]);
|
||||
} else {
|
||||
// If the `timer` property does not exists, we don't output anything.
|
||||
type = MESSAGE_TYPE.NULL_MESSAGE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const frame = {
|
||||
|
|
|
@ -125,7 +125,6 @@ WebGLContext::WebGLContext()
|
|||
, mNeedsFakeNoDepth(false)
|
||||
, mNeedsFakeNoStencil(false)
|
||||
, mNeedsEmulatedLoneDepthStencil(false)
|
||||
, mVRPresentationActive(false)
|
||||
{
|
||||
mGeneration = 0;
|
||||
mInvalidated = false;
|
||||
|
@ -1307,9 +1306,8 @@ public:
|
|||
HTMLCanvasElement* canvas = userdata->mCanvas;
|
||||
WebGLContext* webgl = static_cast<WebGLContext*>(canvas->GetContextAtIndex(0));
|
||||
|
||||
// Present our screenbuffer, if needed.
|
||||
webgl->PresentScreenBuffer();
|
||||
webgl->mDrawCallsSinceLastFlush = 0;
|
||||
// Prepare the context for composition
|
||||
webgl->BeginComposition();
|
||||
}
|
||||
|
||||
/** DidTransactionCallback gets called by the Layers code everytime the WebGL canvas gets composite,
|
||||
|
@ -1320,10 +1318,8 @@ public:
|
|||
HTMLCanvasElement* canvas = userdata->mCanvas;
|
||||
WebGLContext* webgl = static_cast<WebGLContext*>(canvas->GetContextAtIndex(0));
|
||||
|
||||
// Mark ourselves as no longer invalidated.
|
||||
webgl->MarkContextClean();
|
||||
|
||||
webgl->UpdateLastUseIndex();
|
||||
// Clean up the context after composition
|
||||
webgl->EndComposition();
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -1612,6 +1608,24 @@ WebGLContext::PresentScreenBuffer()
|
|||
return true;
|
||||
}
|
||||
|
||||
// Prepare the context for capture before compositing
|
||||
void
|
||||
WebGLContext::BeginComposition()
|
||||
{
|
||||
// Present our screenbuffer, if needed.
|
||||
PresentScreenBuffer();
|
||||
mDrawCallsSinceLastFlush = 0;
|
||||
}
|
||||
|
||||
// Clean up the context after captured for compositing
|
||||
void
|
||||
WebGLContext::EndComposition()
|
||||
{
|
||||
// Mark ourselves as no longer invalidated.
|
||||
MarkContextClean();
|
||||
UpdateLastUseIndex();
|
||||
}
|
||||
|
||||
void
|
||||
WebGLContext::DummyReadFramebufferOperation(const char* funcName)
|
||||
{
|
||||
|
@ -2340,40 +2354,43 @@ WebGLContext::GetUnpackSize(bool isFunc3D, uint32_t width, uint32_t height,
|
|||
already_AddRefed<layers::SharedSurfaceTextureClient>
|
||||
WebGLContext::GetVRFrame()
|
||||
{
|
||||
VRManagerChild *vrmc = VRManagerChild::Get();
|
||||
if (!vrmc) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PresentScreenBuffer();
|
||||
mDrawCallsSinceLastFlush = 0;
|
||||
|
||||
MarkContextClean();
|
||||
UpdateLastUseIndex();
|
||||
|
||||
gl::GLScreenBuffer* screen = gl->Screen();
|
||||
if (!screen) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<SharedSurfaceTextureClient> sharedSurface = screen->Front();
|
||||
if (!sharedSurface) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (sharedSurface && sharedSurface->GetAllocator() != vrmc) {
|
||||
RefPtr<SharedSurfaceTextureClient> dest =
|
||||
screen->Factory()->NewTexClient(sharedSurface->GetSize());
|
||||
if (!dest) {
|
||||
return nullptr;
|
||||
VRManagerChild* vrmc = VRManagerChild::Get();
|
||||
if (!vrmc) {
|
||||
return nullptr;
|
||||
}
|
||||
gl::SharedSurface* destSurf = dest->Surf();
|
||||
destSurf->ProducerAcquire();
|
||||
SharedSurface::ProdCopy(sharedSurface->Surf(), dest->Surf(), screen->Factory());
|
||||
destSurf->ProducerRelease();
|
||||
|
||||
return dest.forget();
|
||||
}
|
||||
/**
|
||||
* Swap buffers as though composition has occurred.
|
||||
* We will then share the resulting front buffer to be submitted to the VR
|
||||
* compositor.
|
||||
*/
|
||||
BeginComposition();
|
||||
EndComposition();
|
||||
|
||||
gl::GLScreenBuffer* screen = gl->Screen();
|
||||
if (!screen) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<SharedSurfaceTextureClient> sharedSurface = screen->Front();
|
||||
if (!sharedSurface) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (sharedSurface && sharedSurface->GetAllocator() != vrmc) {
|
||||
RefPtr<SharedSurfaceTextureClient> dest =
|
||||
screen->Factory()->NewTexClient(sharedSurface->GetSize());
|
||||
if (!dest) {
|
||||
return nullptr;
|
||||
}
|
||||
gl::SharedSurface* destSurf = dest->Surf();
|
||||
destSurf->ProducerAcquire();
|
||||
SharedSurface::ProdCopy(sharedSurface->Surf(), dest->Surf(),
|
||||
screen->Factory());
|
||||
destSurf->ProducerRelease();
|
||||
|
||||
return dest.forget();
|
||||
}
|
||||
|
||||
return sharedSurface.forget();
|
||||
}
|
||||
|
@ -2381,26 +2398,25 @@ WebGLContext::GetVRFrame()
|
|||
bool
|
||||
WebGLContext::StartVRPresentation()
|
||||
{
|
||||
VRManagerChild *vrmc = VRManagerChild::Get();
|
||||
if (!vrmc) {
|
||||
return false;
|
||||
}
|
||||
gl::GLScreenBuffer* screen = gl->Screen();
|
||||
if (!screen) {
|
||||
return false;
|
||||
}
|
||||
gl::SurfaceCaps caps = screen->mCaps;
|
||||
VRManagerChild* vrmc = VRManagerChild::Get();
|
||||
if (!vrmc) {
|
||||
return false;
|
||||
}
|
||||
gl::GLScreenBuffer* screen = gl->Screen();
|
||||
if (!screen) {
|
||||
return false;
|
||||
}
|
||||
gl::SurfaceCaps caps = screen->mCaps;
|
||||
|
||||
UniquePtr<gl::SurfaceFactory> factory =
|
||||
gl::GLScreenBuffer::CreateFactory(gl,
|
||||
caps,
|
||||
vrmc,
|
||||
vrmc->GetBackendType(),
|
||||
TextureFlags::ORIGIN_BOTTOM_LEFT);
|
||||
UniquePtr<gl::SurfaceFactory> factory =
|
||||
gl::GLScreenBuffer::CreateFactory(gl,
|
||||
caps,
|
||||
vrmc,
|
||||
vrmc->GetBackendType(),
|
||||
TextureFlags::ORIGIN_BOTTOM_LEFT);
|
||||
|
||||
screen->Morph(Move(factory));
|
||||
mVRPresentationActive = true;
|
||||
return true;
|
||||
screen->Morph(Move(factory));
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -355,6 +355,11 @@ public:
|
|||
|
||||
bool PresentScreenBuffer();
|
||||
|
||||
// Prepare the context for capture before compositing
|
||||
void BeginComposition();
|
||||
// Clean up the context after captured for compositing
|
||||
void EndComposition();
|
||||
|
||||
// a number that increments every time we have an event that causes
|
||||
// all context resources to be lost.
|
||||
uint32_t Generation() { return mGeneration.value(); }
|
||||
|
@ -1517,7 +1522,6 @@ protected:
|
|||
bool mNeedsFakeNoDepth;
|
||||
bool mNeedsFakeNoStencil;
|
||||
bool mNeedsEmulatedLoneDepthStencil;
|
||||
bool mVRPresentationActive;
|
||||
|
||||
bool HasTimestampBits() const;
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ DragEvent::InitDragEvent(const nsAString& aType,
|
|||
aView, aDetail, aScreenX, aScreenY,
|
||||
aClientX, aClientY, aCtrlKey, aAltKey,
|
||||
aShiftKey, aMetaKey, aButton, aRelatedTarget);
|
||||
if (mEventIsInternal && mEvent) {
|
||||
if (mEventIsInternal) {
|
||||
mEvent->AsDragEvent()->mDataTransfer = aDataTransfer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -907,6 +907,7 @@ void HTMLMediaElement::ShutdownDecoder()
|
|||
{
|
||||
RemoveMediaElementFromURITable();
|
||||
NS_ASSERTION(mDecoder, "Must have decoder to shut down");
|
||||
mWaitingForKeyListener.DisconnectIfExists();
|
||||
mDecoder->Shutdown();
|
||||
mDecoder = nullptr;
|
||||
}
|
||||
|
@ -983,6 +984,7 @@ void HTMLMediaElement::AbortExistingLoads()
|
|||
#ifdef MOZ_EME
|
||||
mPendingEncryptedInitData.mInitDatas.Clear();
|
||||
#endif // MOZ_EME
|
||||
mWaitingForKey = false;
|
||||
mSourcePointer = nullptr;
|
||||
|
||||
mTags = nullptr;
|
||||
|
@ -2509,6 +2511,7 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
|
|||
mMediaSecurityVerified(false),
|
||||
mCORSMode(CORS_NONE),
|
||||
mIsEncrypted(false),
|
||||
mWaitingForKey(false),
|
||||
mDownloadSuspendedByCache(false, "HTMLMediaElement::mDownloadSuspendedByCache"),
|
||||
mAudioChannelVolume(1.0),
|
||||
mPlayingThroughTheAudioChannel(false),
|
||||
|
@ -3439,6 +3442,13 @@ nsresult HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder,
|
|||
}
|
||||
#endif
|
||||
|
||||
MediaEventSource<void>* waitingForKeyProducer = mDecoder->WaitingForKeyEvent();
|
||||
// Not every decoder will produce waitingForKey events, only add ones that can
|
||||
if (waitingForKeyProducer) {
|
||||
mWaitingForKeyListener = waitingForKeyProducer->Connect(
|
||||
AbstractThread::MainThread(), this, &HTMLMediaElement::CannotDecryptWaitingForKey);
|
||||
}
|
||||
|
||||
if (mChannelLoader) {
|
||||
mChannelLoader->Done();
|
||||
mChannelLoader = nullptr;
|
||||
|
@ -4501,6 +4511,7 @@ void HTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
|
|||
if (oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
|
||||
mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
|
||||
IsPotentiallyPlaying()) {
|
||||
mWaitingForKey = false;
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
|
||||
}
|
||||
|
||||
|
@ -5886,6 +5897,27 @@ HTMLMediaElement::GetTopLevelPrincipal()
|
|||
}
|
||||
#endif // MOZ_EME
|
||||
|
||||
void
|
||||
HTMLMediaElement::CannotDecryptWaitingForKey()
|
||||
{
|
||||
// See: http://w3c.github.io/encrypted-media/#dom-evt-waitingforkey
|
||||
// Spec: 7.5.4 Queue a "waitingforkey" Event
|
||||
// Spec: 1. Let the media element be the specified HTMLMediaElement object.
|
||||
|
||||
// Note, existing code will handle the ready state of this element, as
|
||||
// such this function does not handle changing or checking mReadyState.
|
||||
|
||||
// Spec: 2. If the media element's waiting for key value is true, abort these steps.
|
||||
if (!mWaitingForKey) {
|
||||
// Spec: 3. Set the media element's waiting for key value to true.
|
||||
// Spec: 4. Queue a task to fire a simple event named waitingforkey at the media element.
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("waitingforkey"));
|
||||
mWaitingForKey = true;
|
||||
// No need to explicitly suspend playback, it happens automatically when
|
||||
// it's starving for decoded frames.
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP HTMLMediaElement::WindowAudioCaptureChanged(bool aCapture)
|
||||
{
|
||||
MOZ_ASSERT(mAudioChannelAgent);
|
||||
|
|
|
@ -641,6 +641,8 @@ public:
|
|||
bool ContainsRestrictedContent();
|
||||
#endif // MOZ_EME
|
||||
|
||||
void CannotDecryptWaitingForKey();
|
||||
|
||||
bool MozAutoplayEnabled() const
|
||||
{
|
||||
return mAutoplayEnabled;
|
||||
|
@ -1537,6 +1539,14 @@ protected:
|
|||
// True if the media has encryption information.
|
||||
bool mIsEncrypted;
|
||||
|
||||
// True when the CDM cannot decrypt the current block, and the
|
||||
// waitingforkey event has been fired. Back to false when keys have become
|
||||
// available and we can advance the current playback position.
|
||||
bool mWaitingForKey;
|
||||
|
||||
// Listens for waitingForKey events from the owned decoder.
|
||||
MediaEventListener mWaitingForKeyListener;
|
||||
|
||||
#ifdef MOZ_EME
|
||||
// Init Data that needs to be sent in 'encrypted' events in MetadataLoaded().
|
||||
EncryptionInfo mPendingEncryptedInitData;
|
||||
|
|
|
@ -72,6 +72,18 @@ public:
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
// Notify the media decoder that a decryption key is required before emitting
|
||||
// further output. This only needs to be overridden for decoders that expect
|
||||
// encryption, such as the MediaSource decoder.
|
||||
virtual void NotifyWaitingForKey() {}
|
||||
|
||||
// Return an event that will be notified when a decoder is waiting for a
|
||||
// decryption key before it can return more output.
|
||||
virtual MediaEventSource<void>* WaitingForKeyEvent()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void UpdateEstimatedMediaDuration(int64_t aDuration) {};
|
||||
public:
|
||||
|
|
|
@ -1063,21 +1063,46 @@ MediaDecoderStateMachine::ToStateStr()
|
|||
return ToStateStr(mState);
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::SetState(State aState)
|
||||
void
|
||||
MediaDecoderStateMachine::SetState(State aState)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
if (mState == aState) {
|
||||
return;
|
||||
}
|
||||
DECODER_LOG("Change machine state from %s to %s",
|
||||
ToStateStr(), ToStateStr(aState));
|
||||
|
||||
DECODER_LOG("MDSM state: %s -> %s", ToStateStr(), ToStateStr(aState));
|
||||
|
||||
ExitState(mState);
|
||||
mState = aState;
|
||||
EnterState(mState);
|
||||
}
|
||||
|
||||
mIsShutdown = mState == DECODER_STATE_ERROR || mState == DECODER_STATE_SHUTDOWN;
|
||||
void
|
||||
MediaDecoderStateMachine::ExitState(State aState)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
switch (aState) {
|
||||
case DECODER_STATE_COMPLETED:
|
||||
mSentPlaybackEndedEvent = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear state-scoped state.
|
||||
mSentPlaybackEndedEvent = false;
|
||||
void
|
||||
MediaDecoderStateMachine::EnterState(State aState)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
switch (aState) {
|
||||
case DECODER_STATE_ERROR:
|
||||
case DECODER_STATE_SHUTDOWN:
|
||||
mIsShutdown = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::VolumeChanged()
|
||||
|
|
|
@ -372,6 +372,8 @@ protected:
|
|||
virtual ~MediaDecoderStateMachine();
|
||||
|
||||
void SetState(State aState);
|
||||
void ExitState(State aState);
|
||||
void EnterState(State aState);
|
||||
|
||||
void BufferedRangeUpdated();
|
||||
|
||||
|
|
|
@ -1542,6 +1542,14 @@ MediaFormatReader::DropDecodedSamples(TrackType aTrack)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
MediaFormatReader::WaitingForKey(TrackType aTrack)
|
||||
{
|
||||
if (mDecoder) {
|
||||
mDecoder->NotifyWaitingForKey();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MediaFormatReader::SkipVideoDemuxToNextKeyFrame(media::TimeUnit aTimeThreshold)
|
||||
{
|
||||
|
|
|
@ -185,6 +185,7 @@ private:
|
|||
void Reset(TrackType aTrack);
|
||||
void DrainComplete(TrackType aTrack);
|
||||
void DropDecodedSamples(TrackType aTrack);
|
||||
void WaitingForKey(TrackType aTrack);
|
||||
|
||||
bool ShouldSkip(bool aSkipToNextKeyframe, media::TimeUnit aTimeThreshold);
|
||||
|
||||
|
@ -219,6 +220,9 @@ private:
|
|||
bool OnReaderTaskQueue() override {
|
||||
return mReader->OnTaskQueue();
|
||||
}
|
||||
void WaitingForKey() override {
|
||||
mReader->WaitingForKey(mType);
|
||||
}
|
||||
|
||||
private:
|
||||
MediaFormatReader* mReader;
|
||||
|
|
|
@ -316,6 +316,18 @@ MediaSourceDecoder::CanPlayThrough()
|
|||
return GetBuffered().Contains(ClampIntervalToEnd(interval));
|
||||
}
|
||||
|
||||
void
|
||||
MediaSourceDecoder::NotifyWaitingForKey()
|
||||
{
|
||||
mWaitingForKeyEvent.Notify();
|
||||
}
|
||||
|
||||
MediaEventSource<void>*
|
||||
MediaSourceDecoder::WaitingForKeyEvent()
|
||||
{
|
||||
return &mWaitingForKeyEvent;
|
||||
}
|
||||
|
||||
TimeInterval
|
||||
MediaSourceDecoder::ClampIntervalToEnd(const TimeInterval& aInterval)
|
||||
{
|
||||
|
|
|
@ -80,6 +80,10 @@ public:
|
|||
MediaDecoderOwner::NextFrameStatus NextFrameBufferedStatus() override;
|
||||
bool CanPlayThrough() override;
|
||||
|
||||
void NotifyWaitingForKey() override;
|
||||
|
||||
MediaEventSource<void>* WaitingForKeyEvent() override;
|
||||
|
||||
private:
|
||||
void DoSetMediaSourceDuration(double aDuration);
|
||||
media::TimeInterval ClampIntervalToEnd(const media::TimeInterval& aInterval);
|
||||
|
@ -90,6 +94,7 @@ private:
|
|||
dom::MediaSource* mMediaSource;
|
||||
RefPtr<MediaSourceDemuxer> mDemuxer;
|
||||
RefPtr<MediaFormatReader> mReader;
|
||||
MediaEventProducer<void> mWaitingForKeyEvent;
|
||||
|
||||
bool mEnded;
|
||||
};
|
||||
|
|
|
@ -35,50 +35,6 @@ SourceBufferResource::Close()
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SourceBufferResource::ReadInternal(char* aBuffer, uint32_t aCount, uint32_t* aBytes, bool aMayBlock)
|
||||
{
|
||||
mMonitor.AssertCurrentThreadIn();
|
||||
MOZ_ASSERT_IF(!aMayBlock, aBytes);
|
||||
|
||||
// Cache the offset for the read in case mOffset changes while waiting on the
|
||||
// monitor below. It's basically impossible to implement these API semantics
|
||||
// sanely. :-(
|
||||
uint64_t readOffset = mOffset;
|
||||
|
||||
while (aMayBlock &&
|
||||
!mEnded &&
|
||||
readOffset + aCount > static_cast<uint64_t>(GetLength())) {
|
||||
SBR_DEBUGV("waiting for data");
|
||||
mMonitor.Wait();
|
||||
// The callers of this function should have checked this, but it's
|
||||
// possible that we had an eviction while waiting on the monitor.
|
||||
if (readOffset < mInputBuffer.GetOffset()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t available = GetLength() - readOffset;
|
||||
uint32_t count = std::min(aCount, available);
|
||||
SBR_DEBUGV("readOffset=%llu GetLength()=%u available=%u count=%u mEnded=%d",
|
||||
readOffset, GetLength(), available, count, mEnded);
|
||||
if (available == 0) {
|
||||
SBR_DEBUGV("reached EOF");
|
||||
*aBytes = 0;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mInputBuffer.CopyData(readOffset, count, aBuffer);
|
||||
*aBytes = count;
|
||||
|
||||
// From IRC:
|
||||
// <@cpearce>bholley: *this* is why there should only every be a ReadAt() and
|
||||
// no Read() on a Stream abstraction! there's no good answer, they all suck.
|
||||
mOffset = readOffset + count;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SourceBufferResource::ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes)
|
||||
{
|
||||
|
@ -93,18 +49,8 @@ SourceBufferResource::ReadAtInternal(int64_t aOffset, char* aBuffer, uint32_t aC
|
|||
bool aMayBlock)
|
||||
{
|
||||
mMonitor.AssertCurrentThreadIn();
|
||||
nsresult rv = SeekInternal(aOffset);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return ReadInternal(aBuffer, aCount, aBytes, aMayBlock);
|
||||
}
|
||||
|
||||
nsresult
|
||||
SourceBufferResource::SeekInternal(int64_t aOffset)
|
||||
{
|
||||
mMonitor.AssertCurrentThreadIn();
|
||||
MOZ_ASSERT_IF(!aMayBlock, aBytes);
|
||||
|
||||
if (mClosed ||
|
||||
aOffset < 0 ||
|
||||
|
@ -113,7 +59,36 @@ SourceBufferResource::SeekInternal(int64_t aOffset)
|
|||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mOffset = aOffset;
|
||||
while (aMayBlock &&
|
||||
!mEnded &&
|
||||
aOffset + aCount > GetLength()) {
|
||||
SBR_DEBUGV("waiting for data");
|
||||
mMonitor.Wait();
|
||||
// The callers of this function should have checked this, but it's
|
||||
// possible that we had an eviction while waiting on the monitor.
|
||||
if (uint64_t(aOffset) < mInputBuffer.GetOffset()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t available = GetLength() - aOffset;
|
||||
uint32_t count = std::min(aCount, available);
|
||||
|
||||
// Keep the position of the last read to have Tell() approximately give us
|
||||
// the position we're up to in the stream.
|
||||
mOffset = aOffset + count;
|
||||
|
||||
SBR_DEBUGV("offset=%llu GetLength()=%u available=%u count=%u mEnded=%d",
|
||||
aOffset, GetLength(), available, count, mEnded);
|
||||
if (available == 0) {
|
||||
SBR_DEBUGV("reached EOF");
|
||||
*aBytes = 0;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mInputBuffer.CopyData(aOffset, count, aBuffer);
|
||||
*aBytes = count;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -124,9 +99,7 @@ SourceBufferResource::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCo
|
|||
aBuffer, aOffset, aCount);
|
||||
ReentrantMonitorAutoEnter mon(mMonitor);
|
||||
uint32_t bytesRead;
|
||||
int64_t oldOffset = mOffset;
|
||||
nsresult rv = ReadAtInternal(aOffset, aBuffer, aCount, &bytesRead, /* aMayBlock = */ false);
|
||||
mOffset = oldOffset; // ReadFromCache isn't supposed to affect the seek position.
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// ReadFromCache return failure if not all the data is cached.
|
||||
|
@ -154,10 +127,9 @@ SourceBufferResource::EvictBefore(uint64_t aOffset, ErrorResult& aRv)
|
|||
{
|
||||
SBR_DEBUG("EvictBefore(aOffset=%llu)", aOffset);
|
||||
ReentrantMonitorAutoEnter mon(mMonitor);
|
||||
// If aOffset is past the current playback offset we don't evict.
|
||||
if (aOffset < mOffset) {
|
||||
mInputBuffer.EvictBefore(aOffset, aRv);
|
||||
}
|
||||
|
||||
mInputBuffer.EvictBefore(aOffset, aRv);
|
||||
|
||||
// Wake up any waiting threads in case a ReadInternal call
|
||||
// is now invalid.
|
||||
mon.NotifyAll();
|
||||
|
|
|
@ -136,8 +136,6 @@ public:
|
|||
|
||||
private:
|
||||
virtual ~SourceBufferResource();
|
||||
nsresult SeekInternal(int64_t aOffset);
|
||||
nsresult ReadInternal(char* aBuffer, uint32_t aCount, uint32_t* aBytes, bool aMayBlock);
|
||||
nsresult ReadAtInternal(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes, bool aMayBlock);
|
||||
|
||||
const nsCString mType;
|
||||
|
|
|
@ -179,9 +179,15 @@ public:
|
|||
|
||||
virtual void DrainComplete() = 0;
|
||||
|
||||
virtual void ReleaseMediaResources() {};
|
||||
virtual void ReleaseMediaResources() {}
|
||||
|
||||
virtual bool OnReaderTaskQueue() = 0;
|
||||
|
||||
// Denotes that a pending encryption key is preventing more input being fed
|
||||
// into the decoder. This only needs to be overridden for callbacks that
|
||||
// handle encryption. E.g. benchmarking does not use eme, so this need
|
||||
// not be overridden in that case.
|
||||
virtual void WaitingForKey() {}
|
||||
};
|
||||
|
||||
// MediaDataDecoder is the interface exposed by decoders created by the
|
||||
|
|
|
@ -34,7 +34,8 @@ public:
|
|||
, mCallback(aCallback)
|
||||
, mTaskQueue(aDecodeTaskQueue)
|
||||
, mProxy(aProxy)
|
||||
, mSamplesWaitingForKey(new SamplesWaitingForKey(this, mTaskQueue, mProxy))
|
||||
, mSamplesWaitingForKey(new SamplesWaitingForKey(this, this->mCallback,
|
||||
mTaskQueue, mProxy))
|
||||
, mIsShutdown(false)
|
||||
{
|
||||
}
|
||||
|
@ -171,7 +172,8 @@ public:
|
|||
CDMProxy* aProxy,
|
||||
TaskQueue* aTaskQueue)
|
||||
: MediaDataDecoderProxy(Move(aProxyThread), aCallback)
|
||||
, mSamplesWaitingForKey(new SamplesWaitingForKey(this, aTaskQueue, aProxy))
|
||||
, mSamplesWaitingForKey(new SamplesWaitingForKey(this, aCallback,
|
||||
aTaskQueue, aProxy))
|
||||
, mProxy(aProxy)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -12,10 +12,12 @@
|
|||
namespace mozilla {
|
||||
|
||||
SamplesWaitingForKey::SamplesWaitingForKey(MediaDataDecoder* aDecoder,
|
||||
MediaDataDecoderCallback* aCallback,
|
||||
TaskQueue* aTaskQueue,
|
||||
CDMProxy* aProxy)
|
||||
: mMutex("SamplesWaitingForKey")
|
||||
, mDecoder(aDecoder)
|
||||
, mDecoderCallback(aCallback)
|
||||
, mTaskQueue(aTaskQueue)
|
||||
, mProxy(aProxy)
|
||||
{
|
||||
|
@ -38,6 +40,7 @@ SamplesWaitingForKey::WaitIfKeyNotUsable(MediaRawData* aSample)
|
|||
MutexAutoLock lock(mMutex);
|
||||
mSamples.AppendElement(aSample);
|
||||
}
|
||||
mDecoderCallback->WaitingForKey();
|
||||
caps.NotifyWhenKeyIdUsable(aSample->mCrypto.mKeyId, this);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -18,13 +18,14 @@ typedef nsTArray<uint8_t> CencKeyId;
|
|||
class CDMProxy;
|
||||
|
||||
// Encapsulates the task of waiting for the CDMProxy to have the necessary
|
||||
// keys to decypt a given sample.
|
||||
// keys to decrypt a given sample.
|
||||
class SamplesWaitingForKey {
|
||||
public:
|
||||
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SamplesWaitingForKey)
|
||||
|
||||
explicit SamplesWaitingForKey(MediaDataDecoder* aDecoder,
|
||||
MediaDataDecoderCallback* aCallback,
|
||||
TaskQueue* aTaskQueue,
|
||||
CDMProxy* aProxy);
|
||||
|
||||
|
@ -46,6 +47,7 @@ protected:
|
|||
private:
|
||||
Mutex mMutex;
|
||||
RefPtr<MediaDataDecoder> mDecoder;
|
||||
MediaDataDecoderCallback* mDecoderCallback;
|
||||
RefPtr<TaskQueue> mTaskQueue;
|
||||
RefPtr<CDMProxy> mProxy;
|
||||
nsTArray<RefPtr<MediaRawData>> mSamples;
|
||||
|
|
|
@ -95,6 +95,11 @@ public:
|
|||
return mProxyCallback->OnReaderTaskQueue();
|
||||
}
|
||||
|
||||
void WaitingForKey() override
|
||||
{
|
||||
mProxyCallback->WaitingForKey();
|
||||
}
|
||||
|
||||
private:
|
||||
MediaDataDecoderProxy* mProxyDecoder;
|
||||
MediaDataDecoderCallback* mProxyCallback;
|
||||
|
|
|
@ -18,10 +18,16 @@ function startTest(test) {
|
|||
* @param {string} url video src.
|
||||
* @returns {HTMLMediaElement} The created video element.
|
||||
*/
|
||||
function appendVideoToDoc(url, token) {
|
||||
function appendVideoToDoc(url, token, width, height) {
|
||||
// Default size of (160, 120) is used by other media tests.
|
||||
if (width === undefined) { width = 160; }
|
||||
if (height === undefined) { height = 3*width/4; }
|
||||
|
||||
let v = document.createElement('video');
|
||||
v.token = token;
|
||||
document.body.appendChild(v);
|
||||
v.width = width;
|
||||
v.height = height;
|
||||
v.src = url;
|
||||
return v;
|
||||
}
|
||||
|
@ -106,4 +112,4 @@ function waitTil(video, time) {
|
|||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -249,7 +249,7 @@ function LoadTest(test, elem, token, loadParams)
|
|||
Log(token, "sourceopen");
|
||||
return Promise.all(test.tracks.map(function(track) {
|
||||
return AppendTrack(test, ms, track, token, loadParams);
|
||||
})).then(function(){
|
||||
})).then(function() {
|
||||
if (loadParams && loadParams.noEndOfStream) {
|
||||
Log(token, "Tracks loaded");
|
||||
} else {
|
||||
|
@ -257,6 +257,8 @@ function LoadTest(test, elem, token, loadParams)
|
|||
ms.endOfStream();
|
||||
}
|
||||
resolve();
|
||||
}).catch(function() {
|
||||
Log(token, "error while loading tracks");
|
||||
});
|
||||
})
|
||||
});
|
||||
|
@ -299,7 +301,7 @@ function SetupEME(test, token, params)
|
|||
// Log events dispatched to make debugging easier...
|
||||
[ "canplay", "canplaythrough", "ended", "error", "loadeddata",
|
||||
"loadedmetadata", "loadstart", "pause", "play", "playing", "progress",
|
||||
"stalled", "suspend", "waiting",
|
||||
"stalled", "suspend", "waiting", "waitingforkey",
|
||||
].forEach(function (e) {
|
||||
v.addEventListener(e, function(event) {
|
||||
Log(token, "" + e);
|
||||
|
@ -314,13 +316,28 @@ function SetupEME(test, token, params)
|
|||
: bail(token + " Failed to set MediaKeys on <video> element");
|
||||
|
||||
// null: No session management in progress, just go ahead and update the session.
|
||||
// [...]: Session management in progress, add [initDataType, initData] to
|
||||
// [...]: Session management in progress, add {initDataType, initData} to
|
||||
// this queue to get it processed when possible.
|
||||
var initDataQueue = [];
|
||||
function pushInitData(ev)
|
||||
{
|
||||
if (initDataQueue === null) {
|
||||
initDataQueue = [];
|
||||
}
|
||||
initDataQueue.push(ev);
|
||||
if (params && params.onInitDataQueued) {
|
||||
params.onInitDataQueued(ev, ev.initDataType, StringToHex(ArrayBufferToString(ev.initData)));
|
||||
}
|
||||
}
|
||||
|
||||
function processInitDataQueue()
|
||||
{
|
||||
if (initDataQueue === null) { return; }
|
||||
if (initDataQueue.length === 0) { initDataQueue = null; return; }
|
||||
// If we're processed all our init data null the queue to indicate encrypted event handled.
|
||||
if (initDataQueue.length === 0) {
|
||||
initDataQueue = null;
|
||||
return;
|
||||
}
|
||||
var ev = initDataQueue.shift();
|
||||
|
||||
var sessionType = (params && params.sessionType) ? params.sessionType : "temporary";
|
||||
|
@ -356,14 +373,19 @@ function SetupEME(test, token, params)
|
|||
return x ? x.type : undefined;
|
||||
}
|
||||
|
||||
// All 'initDataType's should be the same.
|
||||
// null indicates no 'encrypted' event received yet.
|
||||
var initDataType = null;
|
||||
// If sessions are to be delayed we won't peform any processing until the
|
||||
// callback the assigned here is called by the test.
|
||||
if (params && params.delaySessions) {
|
||||
params.ProcessSessions = processInitDataQueue;
|
||||
}
|
||||
|
||||
// Is this the first piece of init data we're processing?
|
||||
var firstInitData = true;
|
||||
v.addEventListener("encrypted", function(ev) {
|
||||
if (initDataType === null) {
|
||||
if (firstInitData) {
|
||||
Log(token, "got first encrypted(" + ev.initDataType + ", " + StringToHex(ArrayBufferToString(ev.initData)) + "), setup session");
|
||||
initDataType = ev.initDataType;
|
||||
initDataQueue.push(ev);
|
||||
firstInitData = false;
|
||||
pushInitData(ev);
|
||||
|
||||
function chain(promise, onReject) {
|
||||
return promise.then(function(value) {
|
||||
|
@ -400,20 +422,23 @@ function SetupEME(test, token, params)
|
|||
|
||||
.then(function() {
|
||||
Log(token, "set MediaKeys on <video> element ok");
|
||||
processInitDataQueue();
|
||||
if (params && params.onMediaKeysSet) {
|
||||
params.onMediaKeysSet();
|
||||
}
|
||||
if (!(params && params.delaySessions)) {
|
||||
processInitDataQueue();
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (ev.initDataType !== initDataType) {
|
||||
return bail(token + ": encrypted(" + ev.initDataType + ", " +
|
||||
StringToHex(ArrayBufferToString(ev.initData)) + ")")
|
||||
("expected " + initDataType);
|
||||
}
|
||||
if (initDataQueue !== null) {
|
||||
if (params && params.delaySessions) {
|
||||
Log(token, "got encrypted(" + ev.initDataType + ", " + StringToHex(ArrayBufferToString(ev.initData)) + ") event, queue it in because we're delaying sessions");
|
||||
pushInitData(ev);
|
||||
} else if (initDataQueue !== null) {
|
||||
Log(token, "got encrypted(" + ev.initDataType + ", " + StringToHex(ArrayBufferToString(ev.initData)) + ") event, queue it for later session update");
|
||||
initDataQueue.push(ev);
|
||||
pushInitData(ev);
|
||||
} else {
|
||||
Log(token, "got encrypted(" + ev.initDataType + ", " + StringToHex(ArrayBufferToString(ev.initData)) + ") event, update session now");
|
||||
initDataQueue = [ev];
|
||||
pushInitData(ev);
|
||||
processInitDataQueue();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,10 @@ from mach.decorators import (
|
|||
|
||||
def setup_argument_parser():
|
||||
from external_media_harness.runtests import MediaTestArguments
|
||||
return MediaTestArguments()
|
||||
from mozlog.structured import commandline
|
||||
parser = MediaTestArguments()
|
||||
commandline.add_logging_group(parser)
|
||||
return parser
|
||||
|
||||
|
||||
def run_external_media_test(tests, testtype=None, topsrcdir=None, **kwargs):
|
||||
|
@ -34,8 +37,7 @@ def run_external_media_test(tests, testtype=None, topsrcdir=None, **kwargs):
|
|||
|
||||
from argparse import Namespace
|
||||
|
||||
parser = MediaTestArguments()
|
||||
commandline.add_logging_group(parser)
|
||||
parser = setup_argument_parser()
|
||||
|
||||
if not tests:
|
||||
tests = [os.path.join(topsrcdir,
|
||||
|
|
Двоичный файл не отображается.
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-store
|
|
@ -1426,6 +1426,13 @@ var gEMENonMSEFailTests = [
|
|||
},
|
||||
];
|
||||
|
||||
// These are files that are used for video decode suspend in
|
||||
// background tabs tests.
|
||||
var gDecodeSuspendTests = [
|
||||
{ name:"gizmo.mp4", type:"video/mp4", duration:5.56 },
|
||||
{ name:"gizmo-noaudio.mp4", type:"video/mp4", duration:5.56 }
|
||||
];
|
||||
|
||||
function checkMetadata(msg, e, test) {
|
||||
if (test.width) {
|
||||
is(e.videoWidth, test.width, msg + " video width");
|
||||
|
|
|
@ -421,6 +421,8 @@ support-files =
|
|||
fragment_play.js
|
||||
gizmo.mp4
|
||||
gizmo.mp4^headers^
|
||||
gizmo-noaudio.mp4
|
||||
gizmo-noaudio.mp4^headers^
|
||||
huge-id3.mp3
|
||||
huge-id3.mp3^headers^
|
||||
id3tags.mp3
|
||||
|
@ -675,6 +677,8 @@ skip-if = toolkit == 'android' || toolkit == 'gonk' # android: bug 1149374; gonk
|
|||
[test_eme_stream_capture_blocked_case3.html]
|
||||
tags=msg capturestream
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk' # android: bug 1149374; gonk: bug 1193351
|
||||
[test_eme_waitingforkey.html]
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk' # android: bug 1149374; gonk: bug 1193351
|
||||
[test_empty_resource.html]
|
||||
[test_error_in_video_document.html]
|
||||
[test_error_on_404.html]
|
||||
|
@ -889,11 +893,10 @@ tags = webvtt
|
|||
[test_fragment_play.html]
|
||||
|
||||
[test_background_video_suspend.html]
|
||||
skip-if = (os == 'win' && os_version == '5.1')
|
||||
tags = suspend
|
||||
[test_background_video_suspend_ends.html]
|
||||
tags = suspend
|
||||
[test_background_video_no_suspend_short_vid.html]
|
||||
skip-if = (os == 'win' && os_version == '5.1')
|
||||
tags = suspend
|
||||
[test_background_video_no_suspend_disabled.html]
|
||||
skip-if = (os == 'win' && os_version == '5.1')
|
||||
tags = suspend
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
<script src="manifest.js"></script>
|
||||
<script src="background_video.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
|
||||
<p id="display">
|
||||
<div id="content" style="display:none"></div>
|
||||
<pre id="test">
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
|
@ -20,7 +17,7 @@ startTest({
|
|||
[ 'media.suspend-bkgnd-video.enabled', false ],
|
||||
[ 'media.suspend-bkgnd-video.delay-ms', 0 ]
|
||||
],
|
||||
tests: [ { name: "gizmo.mp4" } ],
|
||||
tests: gDecodeSuspendTests,
|
||||
runTest: (test, token) => {
|
||||
let v = appendVideoToDoc(test.name, token);
|
||||
manager.started(token);
|
||||
|
@ -36,5 +33,4 @@ startTest({
|
|||
manager.finished(token); });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</script>
|
|
@ -5,9 +5,6 @@
|
|||
<script src="manifest.js"></script>
|
||||
<script src="background_video.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
|
||||
<p id="display">
|
||||
<div id="content" style="display:none"></div>
|
||||
<pre id="test">
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
|
@ -21,7 +18,7 @@ startTest({
|
|||
// Gizmo.mp4 is about 5.6s
|
||||
[ 'media.suspend-bkgnd-video.delay-ms', 10000 ]
|
||||
],
|
||||
tests: [ { name: "gizmo.mp4" } ],
|
||||
tests: gDecodeSuspendTests,
|
||||
runTest: (test, token) => {
|
||||
let v = appendVideoToDoc(test.name, token);
|
||||
manager.started(token);
|
||||
|
@ -38,5 +35,4 @@ startTest({
|
|||
manager.finished(token); });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</script>
|
|
@ -5,9 +5,6 @@
|
|||
<script src="manifest.js"></script>
|
||||
<script src="background_video.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
|
||||
<p id="display">
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test">
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
|
@ -28,7 +25,7 @@ startTest({
|
|||
// of video.
|
||||
[ "media.suspend-bkgnd-video.delay-ms", 1000 ]
|
||||
],
|
||||
tests: [ { name: "gizmo.mp4" } ],
|
||||
tests: gDecodeSuspendTests,
|
||||
runTest: (test, token) => {
|
||||
let v = appendVideoToDoc(test.name, token);
|
||||
manager.started(token);
|
||||
|
@ -44,5 +41,4 @@ startTest({
|
|||
.then(() => { manager.finished(token); });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</script>
|
|
@ -0,0 +1,39 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<title>Test Background Suspended Video Fires 'ended' Event</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="manifest.js"></script>
|
||||
<script src="background_video.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
var manager = new MediaTestManager;
|
||||
|
||||
startTest({
|
||||
desc: "Test Background Suspended Video Fires 'ended' Event",
|
||||
prefs: [
|
||||
[ "media.test.setVisible", true ],
|
||||
[ "media.suspend-bkgnd-video.enabled", true ],
|
||||
// User a short delay to ensure video decode suspend happens before end
|
||||
// of video.
|
||||
[ "media.suspend-bkgnd-video.delay-ms", 1000 ]
|
||||
],
|
||||
tests: gDecodeSuspendTests,
|
||||
runTest: (test, token) => {
|
||||
let v = appendVideoToDoc(test.name, token);
|
||||
manager.started(token);
|
||||
|
||||
// This test checks that 'ended' event is received for videos with
|
||||
// suspended video decoding. This is important for looping video logic
|
||||
// handling in HTMLMediaElement.
|
||||
waitUntilPlaying(v)
|
||||
.then(() => testVideoSuspendsWhenHidden(v))
|
||||
.then(() => waitUntilEnded(v))
|
||||
.then(() => {
|
||||
ok(v.currentTime >= v.duration, 'current time approximates duration.');
|
||||
manager.finished(token);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,111 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Encrypted Media Extensions</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script type="text/javascript" src="manifest.js"></script>
|
||||
<script type="text/javascript" src="http://test1.mochi.test:8888/tests/dom/media/test/eme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
var manager = new MediaTestManager;
|
||||
|
||||
function startTest(test, token)
|
||||
{
|
||||
// Test if the appropriate preconditions are met such that we can start
|
||||
// prcoessing delayed sessions.
|
||||
function TestIfDoneDelaying()
|
||||
{
|
||||
var got = "Got:";
|
||||
if (loaded) { got += " loaded,"; }
|
||||
got += " " + gotEncrypted + "/" + test.sessionCount + " sessions,";
|
||||
got += " " + gotWaitingForKey + " waiting for key events"
|
||||
if (loaded && gotEncrypted == test.sessionCount && gotWaitingForKey > 0) {
|
||||
Log(token, got + " -> Update sessions with keys");
|
||||
params.ProcessSessions();
|
||||
} else {
|
||||
Log(token, got + " -> Wait for more...");
|
||||
}
|
||||
}
|
||||
|
||||
manager.started(token);
|
||||
|
||||
var updatedSessionsCount = 0;
|
||||
var loaded = false;
|
||||
|
||||
var params = {
|
||||
// params will be populated with a ProcessSessions() callback, that can be
|
||||
// called to process delayed sessions.
|
||||
delaySessions: true,
|
||||
// Function to be called once we start processing and updating sessions.
|
||||
// This should only be called once the preconditions in TestIfDoneDealying
|
||||
// are met.
|
||||
onsessionupdated: function(session) {
|
||||
updatedSessionsCount += 1;
|
||||
if (updatedSessionsCount == test.sessionCount) {
|
||||
info(TimeStamp(token) + " Updated all sessions, loading complete -> Play");
|
||||
v.play();
|
||||
} else {
|
||||
info(TimeStamp(token) + " Updated " + updatedSessionsCount + "/" + test.sessionCount + " sessions so far");
|
||||
}
|
||||
},
|
||||
};
|
||||
var v = SetupEME(test, token, params);
|
||||
|
||||
document.body.appendChild(v);
|
||||
|
||||
var gotEncrypted = 0;
|
||||
var gotWaitingForKey = 0;
|
||||
|
||||
v.addEventListener("encrypted", function() {
|
||||
gotEncrypted += 1;
|
||||
TestIfDoneDelaying();
|
||||
});
|
||||
|
||||
v.addEventListener("waitingforkey", function() {
|
||||
gotWaitingForKey += 1;
|
||||
TestIfDoneDelaying()
|
||||
});
|
||||
|
||||
v.addEventListener("loadedmetadata", function() {
|
||||
ok(SpecialPowers.do_lookupGetter(v, "isEncrypted").apply(v),
|
||||
TimeStamp(token) + " isEncrypted should be true");
|
||||
is(v.isEncrypted, undefined, "isEncrypted should not be accessible from content");
|
||||
});
|
||||
|
||||
v.addEventListener("ended", function() {
|
||||
ok(true, TimeStamp(token) + " got ended event");
|
||||
// We expect only one waitingForKey as we delay until all sessions are ready.
|
||||
// I.e. one waitingForKey should be fired, after which, we process all sessions,
|
||||
// so it should not be possible to be blocked by a key after that point.
|
||||
ok(gotWaitingForKey == 1, "Expected number 1 wait, got: " + gotWaitingForKey);
|
||||
|
||||
v.closeSessions().then(() => manager.finished(token));
|
||||
});
|
||||
|
||||
LoadTest(test, v, token)
|
||||
.then(function() {
|
||||
loaded = true;
|
||||
TestIfDoneDelaying();
|
||||
}).catch(function() {
|
||||
ok(false, token + " failed to load");
|
||||
manager.finished(token);
|
||||
});
|
||||
}
|
||||
|
||||
function beginTest() {
|
||||
manager.runTests(gEMETests, startTest);
|
||||
}
|
||||
|
||||
if (!IsMacOSSnowLeopardOrEarlier()) {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SetupEMEPref(beginTest);
|
||||
} else {
|
||||
todo(false, "Test disabled on this platform.");
|
||||
}
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -41,6 +41,7 @@ ID2D1Factory1 *D2DFactory1()
|
|||
DrawTargetD2D1::DrawTargetD2D1()
|
||||
: mPushedLayers(1)
|
||||
, mUsedCommandListsSincePurge(0)
|
||||
, mDidComplexBlendWithListInList(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1320,6 +1321,13 @@ DrawTargetD2D1::FinalizeDrawing(CompositionOp aOp, const Pattern &aPattern)
|
|||
blendEffect->SetValue(D2D1_BLEND_PROP_MODE, D2DBlendMode(aOp));
|
||||
|
||||
mDC->DrawImage(blendEffect, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_COMPOSITE_MODE_BOUNDED_SOURCE_COPY);
|
||||
|
||||
// This may seem a little counter intuitive. If this is false, we go through the regular
|
||||
// codepaths and set it to true. When this was true, GetImageForLayerContent will return
|
||||
// a bitmap for the current command list and we will no longer have a complex blend
|
||||
// with a list for tmpImage. Therefore we can set it to false again.
|
||||
mDidComplexBlendWithListInList = !mDidComplexBlendWithListInList;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1418,11 +1426,24 @@ DrawTargetD2D1::GetImageForLayerContent()
|
|||
mDC->SetTarget(CurrentTarget());
|
||||
list->Close();
|
||||
|
||||
RefPtr<ID2D1Bitmap1> tmpBitmap;
|
||||
if (mDidComplexBlendWithListInList) {
|
||||
mDC->CreateBitmap(mBitmap->GetPixelSize(), nullptr, 0, &D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_TARGET, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)), getter_AddRefs(tmpBitmap));
|
||||
mDC->SetTransform(D2D1::IdentityMatrix());
|
||||
mDC->SetTarget(tmpBitmap);
|
||||
mDC->DrawImage(list, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_COMPOSITE_MODE_BOUNDED_SOURCE_COPY);
|
||||
mDC->SetTarget(CurrentTarget());
|
||||
}
|
||||
|
||||
DCCommandSink sink(mDC);
|
||||
list->Stream(&sink);
|
||||
|
||||
PushAllClips();
|
||||
|
||||
if (mDidComplexBlendWithListInList) {
|
||||
return tmpBitmap.forget();
|
||||
}
|
||||
|
||||
return list.forget();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -276,6 +276,12 @@ private:
|
|||
TargetSet mDependingOnTargets;
|
||||
|
||||
uint32_t mUsedCommandListsSincePurge;
|
||||
// When a BlendEffect has been drawn to a command list, and that command list is
|
||||
// subsequently used -again- as an input to a blend effect for a command list,
|
||||
// this causes an infinite recursion inside D2D as it tries to resolve the bounds.
|
||||
// If we resolve the current command list before this happens
|
||||
// we can avoid the subsequent hang. (See bug 1293586)
|
||||
bool mDidComplexBlendWithListInList;
|
||||
|
||||
static ID2D1Factory1 *mFactory;
|
||||
static IDWriteFactory *mDWriteFactory;
|
||||
|
|
|
@ -673,6 +673,7 @@ public:
|
|||
|
||||
public:
|
||||
GLXDisplay() : mGLContext(nullptr)
|
||||
, mXDisplay(nullptr)
|
||||
, mSetupLock("GLXVsyncSetupLock")
|
||||
, mVsyncThread("GLXVsyncThread")
|
||||
, mVsyncTask(nullptr)
|
||||
|
@ -705,15 +706,22 @@ public:
|
|||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(!mGLContext, "GLContext already setup!");
|
||||
|
||||
_XDisplay* display = gfxPlatformGtk::GetPlatform()->GetCompositorDisplay();
|
||||
// Create video sync timer on a separate Display to prevent locking the
|
||||
// main thread X display.
|
||||
mXDisplay = XOpenDisplay(nullptr);
|
||||
if (!mXDisplay) {
|
||||
lock.NotifyAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// Most compositors wait for vsync events on the root window.
|
||||
Window root = DefaultRootWindow(display);
|
||||
int screen = DefaultScreen(display);
|
||||
Window root = DefaultRootWindow(mXDisplay);
|
||||
int screen = DefaultScreen(mXDisplay);
|
||||
|
||||
ScopedXFree<GLXFBConfig> cfgs;
|
||||
GLXFBConfig config;
|
||||
int visid;
|
||||
if (!gl::GLContextGLX::FindFBConfigForWindow(display, screen, root,
|
||||
if (!gl::GLContextGLX::FindFBConfigForWindow(mXDisplay, screen, root,
|
||||
&cfgs, &config, &visid)) {
|
||||
lock.NotifyAll();
|
||||
return;
|
||||
|
@ -724,7 +732,7 @@ public:
|
|||
gl::SurfaceCaps::Any(),
|
||||
nullptr,
|
||||
false,
|
||||
display,
|
||||
mXDisplay,
|
||||
root,
|
||||
config,
|
||||
false);
|
||||
|
@ -846,10 +854,12 @@ public:
|
|||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
mGLContext = nullptr;
|
||||
XCloseDisplay(mXDisplay);
|
||||
}
|
||||
|
||||
// Owned by the vsync thread.
|
||||
RefPtr<gl::GLContextGLX> mGLContext;
|
||||
_XDisplay* mXDisplay;
|
||||
Monitor mSetupLock;
|
||||
base::Thread mVsyncThread;
|
||||
RefPtr<Runnable> mVsyncTask;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Masking: Reference for clip-path's inset function 001</title>
|
||||
<link rel="author" title="Manish Goregaokar" href="mailto:manish@mozilla.com">
|
||||
<style type="text/css">
|
||||
#square {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: solid green 40px;
|
||||
background-color: green;
|
||||
position: relative;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>The test passes if there is a green square not touching the edges</p>
|
||||
<div id="square"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,26 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Masking: Test clip-path property and inset function with 10px on all sides</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-shapes/#typedef-basic-shape">
|
||||
<link rel="match" href="clip-path-inset-001-ref.html">
|
||||
<link rel="author" title="Manish Goregaokar" href="mailto:manish@mozilla.com">
|
||||
<style type="text/css">
|
||||
#square {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: solid green 50px;
|
||||
background-color: green;
|
||||
clip-path: inset(10px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>The test passes if there is a green square not touching the edges</p>
|
||||
<div id="square"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,28 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Masking: Test clip-path property and inset function with different insets on each side</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-shapes/#typedef-basic-shape">
|
||||
<link rel="match" href="clip-path-inset-001-ref.html">
|
||||
<link rel="author" title="Manish Goregaokar" href="mailto:manish@mozilla.com">
|
||||
<style type="text/css">
|
||||
#square {
|
||||
width: 120px;
|
||||
height: 100px;
|
||||
border: solid green 50px;
|
||||
background-color: green;
|
||||
clip-path: inset(10px 20px);
|
||||
position: relative;
|
||||
left: -10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>The test passes if there is a green square not touching the edges</p>
|
||||
<div id="square"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,29 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Masking: Test clip-path property and inset function with different insets for all 4 sides</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-shapes/#typedef-basic-shape">
|
||||
<link rel="match" href="clip-path-inset-001-ref.html">
|
||||
<link rel="author" title="Manish Goregaokar" href="mailto:manish@mozilla.com">
|
||||
<style type="text/css">
|
||||
#square {
|
||||
width: 110px;
|
||||
height: 100px;
|
||||
border: solid green 50px;
|
||||
background-color: green;
|
||||
clip-path: inset(5px 10px 15px 20px);
|
||||
position: relative;
|
||||
left: -10px;
|
||||
top: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>The test passes if there is a green square not touching the edges</p>
|
||||
<div id="square"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,26 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Masking: Reference for clip-path's inset function (with rounded corners) 002</title>
|
||||
<link rel="author" title="Manish Goregaokar" href="mailto:manish@mozilla.com">
|
||||
<style type="text/css">
|
||||
#square {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
background-color: green;
|
||||
position: relative;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>The test passes if there is a green rect not touching the sides with rounded corners</p>
|
||||
<div id="square"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,26 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Masking: Test clip-path property and inset function with uniformly rounded corners</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-shapes/#typedef-basic-shape">
|
||||
<link rel="match" href="clip-path-inset-002-ref.html">
|
||||
<link rel="author" title="Manish Goregaokar" href="mailto:manish@mozilla.com">
|
||||
<style type="text/css">
|
||||
#square {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: solid green 50px;
|
||||
background-color: green;
|
||||
clip-path: inset(10px round 20px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>The test passes if there is a green rect not touching the sides with rounded corners</p>
|
||||
<div id="square"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,26 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Masking: Test clip-path property and inset function with uniformly rounded corners</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-shapes/#typedef-basic-shape">
|
||||
<link rel="match" href="clip-path-inset-002-ref.html">
|
||||
<link rel="author" title="Manish Goregaokar" href="mailto:manish@mozilla.com">
|
||||
<style type="text/css">
|
||||
#square {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: solid green 50px;
|
||||
background-color: green;
|
||||
clip-path: inset(10px round 20px 20px 20px 20px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>The test passes if there is a green rect not touching the sides with rounded corners</p>
|
||||
<div id="square"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,26 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Masking: Test clip-path property and inset function with uniformly rounded corners</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-shapes/#typedef-basic-shape">
|
||||
<link rel="match" href="clip-path-inset-002-ref.html">
|
||||
<link rel="author" title="Manish Goregaokar" href="mailto:manish@mozilla.com">
|
||||
<style type="text/css">
|
||||
#square {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: solid green 50px;
|
||||
background-color: green;
|
||||
clip-path: inset(10px round 20px / 20px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>The test passes if there is a green rect not touching the sides with rounded corners</p>
|
||||
<div id="square"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,26 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Masking: Ref test for clip-path's inset function (with different rounded corners) 003</title>
|
||||
<link rel="author" title="Manish Goregaokar" href="mailto:manish@mozilla.com">
|
||||
<style type="text/css">
|
||||
#square {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
background-color: green;
|
||||
position: relative;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
border-radius: 10px 20px 30px 40px / 50px 60px 70px 80px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>The test passes if there is a green rect not touching the sides with rounded corners, with different radii</p>
|
||||
<div id="square"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,26 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Masking: Test clip-path property and inset function with different rounded corners</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-shapes/#typedef-basic-shape">
|
||||
<link rel="match" href="clip-path-inset-003-ref.html">
|
||||
<link rel="author" title="Manish Goregaokar" href="mailto:manish@mozilla.com">
|
||||
<style type="text/css">
|
||||
#square {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: solid green 50px;
|
||||
background-color: green;
|
||||
clip-path: inset(10px round 10px 20px 30px 40px / 50px 60px 70px 80px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>The test passes if there is a green rect not touching the sides with rounded corners, with different radii</p>
|
||||
<div id="square"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -47,3 +47,12 @@ default-preferences pref(layout.css.clip-path-shapes.enabled,true)
|
|||
== clip-path-ellipse-006.html clip-path-ellipse-001-ref.html
|
||||
== clip-path-ellipse-007.html clip-path-ellipse-001-ref.html
|
||||
== clip-path-ellipse-008.html clip-path-ellipse-001-ref.html
|
||||
|
||||
== clip-path-inset-001a.html clip-path-inset-001-ref.html
|
||||
== clip-path-inset-001b.html clip-path-inset-001-ref.html
|
||||
== clip-path-inset-001c.html clip-path-inset-001-ref.html
|
||||
# Anti-aliasing behavior for masking and borders is different
|
||||
fuzzy(64,146) == clip-path-inset-002a.html clip-path-inset-002-ref.html
|
||||
fuzzy(64,146) == clip-path-inset-002b.html clip-path-inset-002-ref.html
|
||||
fuzzy(64,146) == clip-path-inset-002c.html clip-path-inset-002-ref.html
|
||||
fuzzy(64,340) == clip-path-inset-003.html clip-path-inset-003-ref.html
|
|
@ -101,7 +101,7 @@ nsCSSClipPathInstance::CreateClipPath(DrawTarget* aDrawTarget)
|
|||
case StyleBasicShapeType::Polygon:
|
||||
return CreateClipPathPolygon(aDrawTarget, r);
|
||||
case StyleBasicShapeType::Inset:
|
||||
// XXXkrit support all basic shapes
|
||||
return CreateClipPathInset(aDrawTarget, r);
|
||||
break;
|
||||
default:
|
||||
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected shape type");
|
||||
|
@ -239,3 +239,41 @@ nsCSSClipPathInstance::CreateClipPathPolygon(DrawTarget* aDrawTarget,
|
|||
builder->Close();
|
||||
return builder->Finish();
|
||||
}
|
||||
|
||||
already_AddRefed<Path>
|
||||
nsCSSClipPathInstance::CreateClipPathInset(DrawTarget* aDrawTarget,
|
||||
const nsRect& aRefBox)
|
||||
{
|
||||
StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape();
|
||||
const nsTArray<nsStyleCoord>& coords = basicShape->Coordinates();
|
||||
MOZ_ASSERT(coords.Length() == 4, "wrong number of arguments");
|
||||
|
||||
RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
|
||||
|
||||
nscoord appUnitsPerDevPixel =
|
||||
mTargetFrame->PresContext()->AppUnitsPerDevPixel();
|
||||
|
||||
nsMargin inset(nsRuleNode::ComputeCoordPercentCalc(coords[0], aRefBox.height),
|
||||
nsRuleNode::ComputeCoordPercentCalc(coords[1], aRefBox.width),
|
||||
nsRuleNode::ComputeCoordPercentCalc(coords[2], aRefBox.height),
|
||||
nsRuleNode::ComputeCoordPercentCalc(coords[3], aRefBox.width));
|
||||
|
||||
nsRect insetRect(aRefBox);
|
||||
insetRect.Deflate(inset);
|
||||
const Rect insetRectPixels = NSRectToRect(insetRect, appUnitsPerDevPixel);
|
||||
const nsStyleCorners& radius = basicShape->GetRadius();
|
||||
|
||||
nscoord appUnitsRadii[8];
|
||||
|
||||
if (nsIFrame::ComputeBorderRadii(radius, insetRect.Size(), aRefBox.Size(),
|
||||
Sides(), appUnitsRadii)) {
|
||||
RectCornerRadii corners;
|
||||
nsCSSRendering::ComputePixelRadii(appUnitsRadii,
|
||||
appUnitsPerDevPixel, &corners);
|
||||
|
||||
AppendRoundedRectToPath(builder, insetRectPixels, corners, true);
|
||||
} else {
|
||||
AppendRectToPath(builder, insetRectPixels, true);
|
||||
}
|
||||
return builder->Finish();
|
||||
}
|
||||
|
|
|
@ -45,6 +45,8 @@ private:
|
|||
already_AddRefed<Path> CreateClipPathPolygon(DrawTarget* aDrawTarget,
|
||||
const nsRect& aRefBox);
|
||||
|
||||
already_AddRefed<Path> CreateClipPathInset(DrawTarget* aDrawTarget,
|
||||
const nsRect& aRefBox);
|
||||
/**
|
||||
* The frame for the element that is currently being clipped.
|
||||
*/
|
||||
|
|
|
@ -390,6 +390,7 @@ class IceTestPeer : public sigslot::has_slots<> {
|
|||
enable_tcp, allow_link_local,
|
||||
hide_non_default)),
|
||||
candidates_(),
|
||||
shutting_down_(false),
|
||||
gathering_complete_(false),
|
||||
ready_ct_(0),
|
||||
ice_complete_(false),
|
||||
|
@ -970,6 +971,7 @@ class IceTestPeer : public sigslot::has_slots<> {
|
|||
|
||||
void Shutdown() {
|
||||
std::cerr << name_ << " Shutdown" << std::endl;
|
||||
shutting_down_ = true;
|
||||
for (auto s = controlled_trickle_candidates_.begin();
|
||||
s != controlled_trickle_candidates_.end();
|
||||
++s) {
|
||||
|
@ -1004,7 +1006,9 @@ class IceTestPeer : public sigslot::has_slots<> {
|
|||
// Handle events
|
||||
void GatheringStateChange(NrIceCtx* ctx,
|
||||
NrIceCtx::GatheringState state) {
|
||||
(void)ctx;
|
||||
if (shutting_down_) {
|
||||
return;
|
||||
}
|
||||
if (state != NrIceCtx::ICE_CTX_GATHER_COMPLETE) {
|
||||
return;
|
||||
}
|
||||
|
@ -1399,6 +1403,7 @@ class IceTestPeer : public sigslot::has_slots<> {
|
|||
// Maps from stream id to list of remote trickle candidates
|
||||
std::map<size_t, std::vector<SchedulableTrickleCandidate*> >
|
||||
controlled_trickle_candidates_;
|
||||
bool shutting_down_;
|
||||
bool gathering_complete_;
|
||||
int ready_ct_;
|
||||
bool ice_complete_;
|
||||
|
|
|
@ -2366,7 +2366,6 @@ MOZ_XUL=1
|
|||
MOZ_ZIPWRITER=1
|
||||
MOZ_NO_SMART_CARDS=
|
||||
NECKO_COOKIES=1
|
||||
NECKO_PROTOCOLS_DEFAULT="about data file ftp http res viewsource websocket wyciwyg device"
|
||||
BUILD_CTYPES=1
|
||||
MOZ_USE_NATIVE_POPUP_WINDOWS=
|
||||
MOZ_EXCLUDE_HYPHENATION_DICTIONARIES=
|
||||
|
@ -5488,33 +5487,6 @@ dnl ========================================================
|
|||
dnl necko configuration options
|
||||
dnl ========================================================
|
||||
|
||||
dnl
|
||||
dnl option to disable various necko protocols
|
||||
dnl
|
||||
MOZ_ARG_ENABLE_STRING(necko-protocols,
|
||||
[ --enable-necko-protocols[={http,ftp,default,all,none}]
|
||||
Enable/disable specific protocol handlers],
|
||||
[ for option in `echo $enableval | sed 's/,/ /g'`; do
|
||||
if test "$option" = "yes" -o "$option" = "all"; then
|
||||
NECKO_PROTOCOLS="$NECKO_PROTOCOLS $NECKO_PROTOCOLS_DEFAULT"
|
||||
elif test "$option" = "no" -o "$option" = "none"; then
|
||||
NECKO_PROTOCOLS=""
|
||||
elif test "$option" = "default"; then
|
||||
NECKO_PROTOCOLS="$NECKO_PROTOCOLS $NECKO_PROTOCOLS_DEFAULT"
|
||||
elif test `echo "$option" | grep -c \^-` != 0; then
|
||||
option=`echo $option | sed 's/^-//'`
|
||||
NECKO_PROTOCOLS=`echo "$NECKO_PROTOCOLS" | sed "s/ ${option}//"`
|
||||
else
|
||||
NECKO_PROTOCOLS="$NECKO_PROTOCOLS $option"
|
||||
fi
|
||||
done],
|
||||
NECKO_PROTOCOLS="$NECKO_PROTOCOLS_DEFAULT")
|
||||
AC_SUBST_SET(NECKO_PROTOCOLS)
|
||||
for p in $NECKO_PROTOCOLS; do
|
||||
AC_DEFINE_UNQUOTED(NECKO_PROTOCOL_$p)
|
||||
_NON_GLOBAL_ACDEFINES="$_NON_GLOBAL_ACDEFINES NECKO_PROTOCOL_$p"
|
||||
done
|
||||
|
||||
dnl
|
||||
dnl option to disable necko's wifi scanner
|
||||
dnl
|
||||
|
|
|
@ -44,6 +44,7 @@ PYTHON_UNIT_TESTS += [
|
|||
'mozbuild/mozbuild/test/configure/test_options.py',
|
||||
'mozbuild/mozbuild/test/configure/test_toolchain_configure.py',
|
||||
'mozbuild/mozbuild/test/configure/test_toolchain_helpers.py',
|
||||
'mozbuild/mozbuild/test/configure/test_toolkit_moz_configure.py',
|
||||
'mozbuild/mozbuild/test/configure/test_util.py',
|
||||
'mozbuild/mozbuild/test/controller/test_ccachestats.py',
|
||||
'mozbuild/mozbuild/test/controller/test_clobber.py',
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
# 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/.
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from buildconfig import topsrcdir
|
||||
from common import BaseConfigureTest
|
||||
from mozunit import main
|
||||
|
||||
|
||||
class TestToolkitMozConfigure(BaseConfigureTest):
|
||||
def test_necko_protocols(self):
|
||||
def get_value(arg):
|
||||
sandbox = self.get_sandbox({}, {}, [arg])
|
||||
return sandbox._value_for(sandbox['necko_protocols'])
|
||||
|
||||
default_protocols = get_value('')
|
||||
self.assertNotEqual(default_protocols, ())
|
||||
|
||||
# Backwards compatibility
|
||||
self.assertEqual(get_value('--enable-necko-protocols'),
|
||||
default_protocols)
|
||||
|
||||
self.assertEqual(get_value('--enable-necko-protocols=yes'),
|
||||
default_protocols)
|
||||
|
||||
self.assertEqual(get_value('--enable-necko-protocols=all'),
|
||||
default_protocols)
|
||||
|
||||
self.assertEqual(get_value('--enable-necko-protocols=default'),
|
||||
default_protocols)
|
||||
|
||||
self.assertEqual(get_value('--enable-necko-protocols='), ())
|
||||
|
||||
self.assertEqual(get_value('--enable-necko-protocols=no'), ())
|
||||
|
||||
self.assertEqual(get_value('--enable-necko-protocols=none'), ())
|
||||
|
||||
self.assertEqual(get_value('--disable-necko-protocols'), ())
|
||||
|
||||
self.assertEqual(get_value('--enable-necko-protocols=http'),
|
||||
('http',))
|
||||
|
||||
self.assertEqual(get_value('--enable-necko-protocols=http,about'),
|
||||
('about', 'http'))
|
||||
|
||||
self.assertEqual(get_value('--enable-necko-protocols=http,none'), ())
|
||||
|
||||
self.assertEqual(get_value('--enable-necko-protocols=-http'), ())
|
||||
|
||||
self.assertEqual(get_value('--enable-necko-protocols=none,http'),
|
||||
('http',))
|
||||
|
||||
self.assertEqual(
|
||||
get_value('--enable-necko-protocols=all,-http,-about'),
|
||||
tuple(p for p in default_protocols if p not in ('http', 'about')))
|
||||
|
||||
self.assertEqual(
|
||||
get_value('--enable-necko-protocols=default,-http,-about'),
|
||||
tuple(p for p in default_protocols if p not in ('http', 'about')))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -32,6 +32,16 @@ class NullTerminal(object):
|
|||
class StylishFormatter(object):
|
||||
"""Formatter based on the eslint default."""
|
||||
|
||||
# Colors later on in the list are fallbacks in case the terminal
|
||||
# doesn't support colors earlier in the list.
|
||||
# See http://www.calmar.ws/vim/256-xterm-24bit-rgb-color-chart.html
|
||||
_colors = {
|
||||
'grey': [247, 8, 7],
|
||||
'red': [1],
|
||||
'yellow': [3],
|
||||
'brightred': [9, 1],
|
||||
'brightyellow': [11, 3],
|
||||
}
|
||||
fmt = " {c1}{lineno}{column} {c2}{level}{normal} {message} {c1}{rule}({linter}){normal}"
|
||||
fmt_summary = "{t.bold}{c}\u2716 {problem} ({error}, {warning}){t.normal}"
|
||||
|
||||
|
@ -40,6 +50,13 @@ class StylishFormatter(object):
|
|||
self.term = NullTerminal()
|
||||
else:
|
||||
self.term = blessings.Terminal()
|
||||
self.num_colors = self.term.number_of_colors
|
||||
|
||||
def color(self, color):
|
||||
for num in self._colors[color]:
|
||||
if num < self.num_colors:
|
||||
return self.term.color(num)
|
||||
return ''
|
||||
|
||||
def _reset_max(self):
|
||||
self.max_lineno = 0
|
||||
|
@ -81,8 +98,8 @@ class StylishFormatter(object):
|
|||
for err in errors:
|
||||
message.append(self.fmt.format(
|
||||
normal=self.term.normal,
|
||||
c1=self.term.color(8),
|
||||
c2=self.term.color(1) if err.level == 'error' else self.term.color(3),
|
||||
c1=self.color('grey'),
|
||||
c2=self.color('red') if err.level == 'error' else self.color('yellow'),
|
||||
lineno=str(err.lineno).rjust(self.max_lineno),
|
||||
column=(":" + str(err.column).ljust(self.max_column)) if err.column else "",
|
||||
level=err.level.ljust(self.max_level),
|
||||
|
@ -96,7 +113,7 @@ class StylishFormatter(object):
|
|||
# Print a summary
|
||||
message.append(self.fmt_summary.format(
|
||||
t=self.term,
|
||||
c=self.term.color(9) if num_errors else self.term.color(11),
|
||||
c=self.color('brightred') if num_errors else self.color('brightyellow'),
|
||||
problem=self._pluralize('problem', num_errors + num_warnings),
|
||||
error=self._pluralize('error', num_errors),
|
||||
warning=self._pluralize('warning', num_warnings),
|
||||
|
|
|
@ -25,6 +25,10 @@ class FilterPath(object):
|
|||
self.path, find_executables=False, ignore=self.exclude)
|
||||
return self._finder
|
||||
|
||||
@property
|
||||
def ext(self):
|
||||
return os.path.splitext(self.path)[1]
|
||||
|
||||
@property
|
||||
def exists(self):
|
||||
return os.path.exists(self.path)
|
||||
|
@ -93,9 +97,17 @@ def filterpaths(paths, linter, **lintargs):
|
|||
includeglobs = [p for p in include if not p.exists]
|
||||
excludeglobs = [p for p in exclude if not p.exists]
|
||||
|
||||
extensions = linter.get('extensions')
|
||||
keep = set()
|
||||
discard = set()
|
||||
for path in map(FilterPath, paths):
|
||||
# Exclude bad file extensions
|
||||
if extensions and path.isfile and path.ext not in extensions:
|
||||
continue
|
||||
|
||||
if path.match(excludeglobs):
|
||||
continue
|
||||
|
||||
# First handle include/exclude directives
|
||||
# that exist (i.e don't have globs)
|
||||
for inc in includepaths:
|
||||
|
@ -129,13 +141,12 @@ def filterpaths(paths, linter, **lintargs):
|
|||
# by an exclude directive.
|
||||
if not path.match(includeglobs):
|
||||
continue
|
||||
elif path.match(excludeglobs):
|
||||
continue
|
||||
|
||||
keep.add(path)
|
||||
elif path.isdir:
|
||||
# If the specified path is a directory, use a
|
||||
# FileFinder to resolve all relevant globs.
|
||||
path.exclude = excludeglobs
|
||||
path.exclude = [e.path for e in excludeglobs]
|
||||
for pattern in includeglobs:
|
||||
for p, f in path.finder.find(pattern.path):
|
||||
keep.add(path.join(p))
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
# Oh no.. we called this variable foobar, bad!
|
||||
foobar = "a string"
|
|
@ -22,9 +22,9 @@ LINTER = {
|
|||
'name': "ExternalLinter",
|
||||
'description': "It's bad to have the string foobar in js files.",
|
||||
'include': [
|
||||
'**/*.js',
|
||||
'**/*.jsm',
|
||||
'python/mozlint/test/files',
|
||||
],
|
||||
'type': 'external',
|
||||
'extensions': ['.js', '.jsm'],
|
||||
'payload': lint,
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@ class TestLintRoller(TestCase):
|
|||
def __init__(self, *args, **kwargs):
|
||||
TestCase.__init__(self, *args, **kwargs)
|
||||
|
||||
filedir = os.path.join(here, 'files')
|
||||
self.files = [os.path.join(filedir, f) for f in os.listdir(filedir)]
|
||||
self.filedir = os.path.join(here, 'files')
|
||||
self.files = [os.path.join(self.filedir, f) for f in os.listdir(self.filedir)]
|
||||
self.lintdir = os.path.join(here, 'linters')
|
||||
|
||||
names = ('string.lint', 'regex.lint', 'external.lint')
|
||||
|
@ -70,6 +70,11 @@ class TestLintRoller(TestCase):
|
|||
|
||||
self.assertEqual(len(result), 0)
|
||||
|
||||
def test_roll_with_invalid_extension(self):
|
||||
self.lint.read(os.path.join(self.lintdir, 'external.lint'))
|
||||
result = self.lint.roll(os.path.join(self.filedir, 'foobar.py'))
|
||||
self.assertEqual(len(result), 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -71,7 +71,7 @@ class TestLinterTypes(TestCase):
|
|||
|
||||
self.lint.lintargs['use_filters'] = False
|
||||
result = self.lint.roll(self.files)
|
||||
self.assertEqual(len(result), 1)
|
||||
self.assertEqual(len(result), 2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -537,7 +537,6 @@ public:
|
|||
case __NR_rename:
|
||||
case __NR_symlink:
|
||||
case __NR_quotactl:
|
||||
case __NR_utimes:
|
||||
case __NR_link:
|
||||
case __NR_unlink:
|
||||
CASES_FOR_fchown:
|
||||
|
@ -638,9 +637,7 @@ public:
|
|||
CASES_FOR_getresgid:
|
||||
return Allow();
|
||||
|
||||
case __NR_umask:
|
||||
case __NR_kill:
|
||||
case __NR_wait4:
|
||||
#ifdef __NR_arch_prctl
|
||||
case __NR_arch_prctl:
|
||||
#endif
|
||||
|
|
|
@ -20,12 +20,18 @@ from mach.decorators import (
|
|||
|
||||
def setup_argument_parser_functional():
|
||||
from firefox_ui_harness.arguments.base import FirefoxUIArguments
|
||||
return FirefoxUIArguments()
|
||||
from mozlog.structured import commandline
|
||||
parser = FirefoxUIArguments()
|
||||
commandline.add_logging_group(parser)
|
||||
return parser
|
||||
|
||||
|
||||
def setup_argument_parser_update():
|
||||
from firefox_ui_harness.arguments.update import UpdateArguments
|
||||
return UpdateArguments()
|
||||
from mozlog.structured import commandline
|
||||
parser = UpdateArguments()
|
||||
commandline.add_logging_group(parser)
|
||||
return parser
|
||||
|
||||
|
||||
def run_firefox_ui_test(testtype=None, topsrcdir=None, **kwargs):
|
||||
|
|
|
@ -43,13 +43,17 @@ def do_process_check(func, always=False):
|
|||
except (MarionetteException, IOError) as e:
|
||||
exc, val, tb = sys.exc_info()
|
||||
|
||||
# In case of no Marionette failures ensure to check for possible crashes.
|
||||
# Do it before checking for port disconnects, to avoid reporting of unrelated
|
||||
# crashes due to a forced shutdown of the application.
|
||||
if not isinstance(e, MarionetteException) or type(e) is MarionetteException:
|
||||
if not always:
|
||||
check_for_crash()
|
||||
|
||||
# In case of socket failures force a shutdown of the application
|
||||
if type(e) in (socket.error, socket.timeout):
|
||||
m.force_shutdown()
|
||||
|
||||
if not isinstance(e, MarionetteException) or type(e) is MarionetteException:
|
||||
if not always:
|
||||
check_for_crash()
|
||||
raise exc, val, tb
|
||||
finally:
|
||||
if always:
|
||||
|
|
|
@ -77,6 +77,8 @@ def mach_parsed_kwargs(logger):
|
|||
'avd': None,
|
||||
'avd_home': None,
|
||||
'binary': u'/path/to/firefox',
|
||||
'browsermob_port' : None,
|
||||
'browsermob_script' : None,
|
||||
'device_serial': None,
|
||||
'e10s': True,
|
||||
'emulator': False,
|
||||
|
@ -622,4 +624,4 @@ def test_catch_invalid_test_names(runner):
|
|||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.exit(pytest.main(['--verbose', __file__]))
|
||||
sys.exit(pytest.main(['--verbose', __file__]))
|
|
@ -69,8 +69,11 @@ class TestKeyActions(MarionetteTestCase):
|
|||
self.assertEqual(self.key_reporter_value, "")
|
||||
|
||||
def test_open_in_new_window_shortcut(self):
|
||||
el = self.marionette.find_element(By.ID, "updatediv")
|
||||
start_win = self.marionette.current_chrome_window_handle
|
||||
el = self.marionette.find_element(By.ID, "updatediv")
|
||||
# Ensure that the element is in the current view port because press() doesn't
|
||||
# handle that inside the action chain (bug 1295538).
|
||||
self.marionette.execute_script('arguments[0].scrollIntoView()', script_args=[el])
|
||||
(self.key_action.key_down(Keys.SHIFT)
|
||||
.press(el)
|
||||
.release()
|
||||
|
|
|
@ -24,20 +24,21 @@ def is_firefox_or_android(cls):
|
|||
return conditions.is_firefox(cls) or conditions.is_android(cls)
|
||||
|
||||
def setup_marionette_argument_parser():
|
||||
from marionette.runner.base import BaseMarionetteArguments
|
||||
return BaseMarionetteArguments()
|
||||
from marionette.runtests import MarionetteArguments
|
||||
from mozlog.structured import commandline
|
||||
parser = MarionetteArguments()
|
||||
commandline.add_logging_group(parser)
|
||||
return parser
|
||||
|
||||
def run_marionette(tests, binary=None, topsrcdir=None, **kwargs):
|
||||
from mozlog.structured import commandline
|
||||
|
||||
from marionette.runtests import (
|
||||
MarionetteTestRunner,
|
||||
BaseMarionetteArguments,
|
||||
MarionetteHarness
|
||||
)
|
||||
|
||||
parser = BaseMarionetteArguments()
|
||||
commandline.add_logging_group(parser)
|
||||
parser = setup_marionette_argument_parser()
|
||||
|
||||
if not tests:
|
||||
tests = [os.path.join(topsrcdir,
|
||||
|
@ -150,8 +151,11 @@ class MachCommands(MachCommandBase):
|
|||
tests.append(obj['file_relpath'])
|
||||
del kwargs['test_objects']
|
||||
|
||||
if not kwargs.get('binary') and conditions.is_firefox(self):
|
||||
kwargs['binary'] = self.get_binary_path('app')
|
||||
if conditions.is_firefox(self):
|
||||
bin_path = self.get_binary_path('app')
|
||||
if kwargs.get('binary') is not None:
|
||||
print "Warning: ignoring '--binary' option, using binary at " + bin_path
|
||||
kwargs['binary'] = bin_path
|
||||
return run_marionette(tests, topsrcdir=self.topsrcdir, **kwargs)
|
||||
|
||||
@Command('session-test', category='testing',
|
||||
|
@ -166,6 +170,5 @@ class MachCommands(MachCommandBase):
|
|||
tests.append(obj['file_relpath'])
|
||||
del kwargs['test_objects']
|
||||
|
||||
if not kwargs.get('binary') and conditions.is_firefox(self):
|
||||
kwargs['binary'] = self.get_binary_path('app')
|
||||
return run_session(tests, topsrcdir=self.topsrcdir, **kwargs)
|
||||
kwargs['binary'] = self.get_binary_path('app')
|
||||
return run_session(tests, topsrcdir=self.topsrcdir, **kwargs)
|
|
@ -0,0 +1,68 @@
|
|||
# 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/.
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
from mach.decorators import (
|
||||
CommandProvider,
|
||||
Command,
|
||||
)
|
||||
|
||||
parser = None
|
||||
|
||||
|
||||
def run_marionette(context, **kwargs):
|
||||
from marionette.runtests import (
|
||||
MarionetteTestRunner,
|
||||
MarionetteHarness
|
||||
)
|
||||
from mozlog.structured import commandline
|
||||
|
||||
|
||||
args = argparse.Namespace(**kwargs)
|
||||
|
||||
if not args.binary:
|
||||
args.binary = context.find_firefox()
|
||||
|
||||
test_root = os.path.join(context.package_root, 'marionette', 'tests')
|
||||
if not args.tests:
|
||||
args.tests = [os.path.join(test_root, 'testing', 'marionette', 'harness',
|
||||
'marionette', 'tests', 'unit-tests.ini')]
|
||||
|
||||
normalize = partial(context.normalize_test_path, test_root)
|
||||
args.tests = map(normalize, args.tests)
|
||||
|
||||
commandline.add_logging_group(parser)
|
||||
parser.verify_usage(args)
|
||||
|
||||
args.logger = commandline.setup_logging("Marionette Unit Tests",
|
||||
args,
|
||||
{"mach": sys.stdout})
|
||||
status = MarionetteHarness(MarionetteTestRunner, args=vars(args)).run()
|
||||
return 1 if status else 0
|
||||
|
||||
|
||||
def setup_marionette_argument_parser():
|
||||
from marionette.runner.base import BaseMarionetteArguments
|
||||
global parser
|
||||
parser = BaseMarionetteArguments()
|
||||
return parser
|
||||
|
||||
|
||||
@CommandProvider
|
||||
class MachCommands(object):
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
|
||||
@Command(
|
||||
'marionette-test', category='testing',
|
||||
description='Run a Marionette test (Check UI or the internal JavaScript '
|
||||
'using marionette).',
|
||||
parser=setup_marionette_argument_parser)
|
||||
def run_marionette_test(self, **kwargs):
|
||||
return run_marionette(self.context, **kwargs)
|
|
@ -356,7 +356,12 @@ class pathprefix(InstanceFilter):
|
|||
for test in tests:
|
||||
for tp in self.paths:
|
||||
tp = os.path.normpath(tp)
|
||||
if not os.path.normpath(test['relpath']).startswith(tp):
|
||||
|
||||
path = test['relpath']
|
||||
if os.path.isabs(tp):
|
||||
path = test['path']
|
||||
|
||||
if not os.path.normpath(path).startswith(tp):
|
||||
continue
|
||||
|
||||
# any test path that points to a single file will be run no
|
||||
|
|
|
@ -337,6 +337,7 @@ stage-marionette: make-stage-dir
|
|||
$(NSINSTALL) -D $(MARIONETTE_DIR)/client
|
||||
@(cd $(topsrcdir)/testing/marionette/harness && tar --exclude marionette/tests $(TAR_CREATE_FLAGS) - *) | (cd $(MARIONETTE_DIR)/ && tar -xf -)
|
||||
@(cd $(topsrcdir)/testing/marionette/client && tar $(TAR_CREATE_FLAGS) - *) | (cd $(MARIONETTE_DIR)/client && tar -xf -)
|
||||
cp $(topsrcdir)/testing/marionette/mach_test_package_commands.py $(MARIONETTE_DIR)
|
||||
$(PYTHON) $(topsrcdir)/testing/marionette/harness/marionette/tests/print-manifest-dirs.py \
|
||||
$(topsrcdir) \
|
||||
$(topsrcdir)/testing/marionette/harness/marionette/tests/unit-tests.ini \
|
||||
|
|
|
@ -42,6 +42,7 @@ SEARCH_PATHS = [
|
|||
|
||||
# Individual files providing mach commands.
|
||||
MACH_MODULES = [
|
||||
'marionette/mach_test_package_commands.py',
|
||||
'mochitest/mach_test_package_commands.py',
|
||||
'reftest/mach_test_package_commands.py',
|
||||
'tools/mach/mach/commands/commandinfo.py',
|
||||
|
|
|
@ -12,22 +12,6 @@
|
|||
[loading image <video poster>]
|
||||
disabled: true
|
||||
|
||||
[loading video <video>]
|
||||
expected:
|
||||
if (os == "win") and (version == "5.1.2600"): FAIL
|
||||
|
||||
[loading video <video><source>]
|
||||
expected:
|
||||
if (os == "win") and (version == "5.1.2600"): FAIL
|
||||
|
||||
[loading video <audio>]
|
||||
expected:
|
||||
if (os == "win") and (version == "5.1.2600"): FAIL
|
||||
|
||||
[loading video <audio><source>]
|
||||
expected:
|
||||
if (os == "win") and (version == "5.1.2600"): FAIL
|
||||
|
||||
[history.pushState]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -12,22 +12,6 @@
|
|||
[loading image <video poster>]
|
||||
disabled: true
|
||||
|
||||
[loading video <video>]
|
||||
expected:
|
||||
if (os == "win") and (version == "5.1.2600"): FAIL
|
||||
|
||||
[loading video <video><source>]
|
||||
expected:
|
||||
if (os == "win") and (version == "5.1.2600"): FAIL
|
||||
|
||||
[loading video <audio>]
|
||||
expected:
|
||||
if (os == "win") and (version == "5.1.2600"): FAIL
|
||||
|
||||
[loading video <audio><source>]
|
||||
expected:
|
||||
if (os == "win") and (version == "5.1.2600"): FAIL
|
||||
|
||||
[history.pushState]
|
||||
expected: FAIL
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче