Merge autoland to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2016-08-23 10:01:31 -04:00
Родитель 738e1f1ef3 1db439cf3b
Коммит 3625d814ad
115 изменённых файлов: 1980 добавлений и 547 удалений

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

@ -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();
}
}

8
dom/media/test/external/mach_commands.py поставляемый
Просмотреть файл

@ -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,

Двоичные данные
dom/media/test/gizmo-noaudio.mp4 Normal file

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

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

@ -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

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