Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2017-06-23 11:44:21 +02:00
Родитель 2924991bf6 5f51e5596e
Коммит 7af37a52bd
367 изменённых файлов: 8908 добавлений и 9033 удалений

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

@ -479,6 +479,11 @@ pref("browser.tabs.drawInTitlebar", false);
pref("browser.tabs.drawInTitlebar", true);
#endif
// false - disable the tabbar session restore button
// true - enable the tabbar session restore button
// To be enabled with shield
pref("browser.tabs.restorebutton", false);
// When tabs opened by links in other tabs via a combination of
// browser.link.open_newwindow being set to 3 and target="_blank" etc are
// closed:

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

@ -8168,16 +8168,47 @@ var RestoreLastSessionObserver = {
init() {
if (SessionStore.canRestoreLastSession &&
!PrivateBrowsingUtils.isWindowPrivate(window)) {
if (Services.prefs.getBoolPref("browser.tabs.restorebutton")) {
let {restoreTabsButton} = gBrowser.tabContainer;
let restoreTabsButtonWrapper = restoreTabsButton.parentNode;
restoreTabsButtonWrapper.setAttribute("session-exists", "true");
gBrowser.tabContainer.updateSessionRestoreVisibility();
gBrowser.tabContainer.addEventListener("TabOpen", this);
}
Services.obs.addObserver(this, "sessionstore-last-session-cleared", true);
goSetCommandEnabled("Browser:RestoreLastSession", true);
}
},
handleEvent(event) {
switch (event.type) {
case "TabOpen":
this.removeRestoreButton();
break;
}
},
removeRestoreButton() {
let {restoreTabsButton, restoreTabsButtonWrapperWidth} = gBrowser.tabContainer;
let restoreTabsButtonWrapper = restoreTabsButton.parentNode;
restoreTabsButtonWrapper.removeAttribute("session-exists");
gBrowser.tabContainer.addEventListener("transitionend", function maxWidthTransitionHandler(e) {
if (e.propertyName == "max-width") {
gBrowser.tabContainer.updateSessionRestoreVisibility();
gBrowser.tabContainer.removeEventListener("transitionend", maxWidthTransitionHandler);
}
});
restoreTabsButton.style.maxWidth = `${restoreTabsButtonWrapperWidth}px`;
requestAnimationFrame(() => restoreTabsButton.style.maxWidth = 0);
gBrowser.tabContainer.removeEventListener("TabOpen", this);
},
observe() {
// The last session can only be restored once so there's
// no way we need to re-enable our menu item.
Services.obs.removeObserver(this, "sessionstore-last-session-cleared");
goSetCommandEnabled("Browser:RestoreLastSession", false);
this.removeRestoreButton();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,

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

@ -5886,6 +5886,13 @@
onmouseover="document.getBindingParent(this)._enterNewTab();"
onmouseout="document.getBindingParent(this)._leaveNewTab();"
tooltip="dynamic-shortcut-tooltip"/>
<xul:hbox class="restore-tabs-button-wrapper"
anonid="restore-tabs-button-wrapper">
<xul:toolbarbutton anonid="restore-tabs-button"
class="restore-tabs-button"
onclick="SessionStore.restoreLastSession();"/>
</xul:hbox>
<xul:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer"
style="width: 0;"/>
</xul:arrowscrollbox>
@ -5896,6 +5903,9 @@
<![CDATA[
this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
let { restoreTabsButton } = this;
restoreTabsButton.setAttribute("label", this.tabbrowser.mStringBundle.getString("tabs.restoreLastTabs"));
var tab = this.firstChild;
tab.label = this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle");
tab.setAttribute("onerror", "this.removeAttribute('image');");
@ -5938,6 +5948,56 @@
<field name="_beforeHoveredTab">null</field>
<field name="_afterHoveredTab">null</field>
<field name="_hoveredTab">null</field>
<field name="restoreTabsButton">
document.getAnonymousElementByAttribute(this, "anonid", "restore-tabs-button");
</field>
<field name="_restoreTabsButtonWrapperWidth">0</field>
<field name="windowUtils">
window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
</field>
<property name="restoreTabsButtonWrapperWidth" readonly="true">
<getter>
if (!this._restoreTabsButtonWrapperWidth) {
this._restoreTabsButtonWrapperWidth = this.windowUtils
.getBoundsWithoutFlushing(this.restoreTabsButton.parentNode)
.width;
}
return this._restoreTabsButtonWrapperWidth;
</getter>
</property>
<method name="updateSessionRestoreVisibility">
<body><![CDATA[
let {restoreTabsButton, restoreTabsButtonWrapperWidth, windowUtils, mTabstripWidth} = this;
let restoreTabsButtonWrapper = restoreTabsButton.parentNode;
if (!restoreTabsButtonWrapper.getAttribute("session-exists")) {
restoreTabsButtonWrapper.removeAttribute("shown");
return;
}
let newTabButton = document.getAnonymousElementByAttribute(
this, "anonid", "tabs-newtab-button");
// If there are no pinned tabs it will multiply by 0 and result in 0
let pinnedTabsWidth = windowUtils.getBoundsWithoutFlushing(this.firstChild).width * this._lastNumPinned;
let numUnpinnedTabs = this.childNodes.length - this._lastNumPinned;
let unpinnedTabsWidth = windowUtils.getBoundsWithoutFlushing(this.lastChild).width * numUnpinnedTabs;
let tabbarUsedSpace = pinnedTabsWidth + unpinnedTabsWidth
+ windowUtils.getBoundsWithoutFlushing(newTabButton).width;
// Subtract the elements' widths from the available space to ensure
// that showing the restoreTabsButton won't cause any overflow.
if ((mTabstripWidth - tabbarUsedSpace) > restoreTabsButtonWrapperWidth) {
restoreTabsButtonWrapper.setAttribute("shown", "true");
} else {
restoreTabsButtonWrapper.removeAttribute("shown");
}
]]></body>
</method>
<method name="observe">
<parameter name="aSubject"/>
@ -6468,6 +6528,7 @@
this.adjustTabstrip();
this._handleTabSelect(false);
this.mTabstripWidth = width;
this.updateSessionRestoreVisibility();
}
break;
case "mouseout":

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

@ -31,7 +31,7 @@ add_task(async function() {
// Now check that opening a link that does create a new tab works,
// and also that it nulls out the opener.
let pageLoadPromise = BrowserTestUtils.browserLoaded(appTab.linkedBrowser, "http://example.com/");
let pageLoadPromise = BrowserTestUtils.browserLoaded(appTab.linkedBrowser, false, "http://example.com/");
await BrowserTestUtils.loadURI(appTab.linkedBrowser, "http://example.com/");
info("Started loading example.com");
await pageLoadPromise;

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

@ -11,9 +11,9 @@ add_task(async function() {
partialURLTab = BrowserTestUtils.addTab(gBrowser);
testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
let loaded1 = BrowserTestUtils.browserLoaded(deletedURLTab.linkedBrowser, testURL);
let loaded2 = BrowserTestUtils.browserLoaded(fullURLTab.linkedBrowser, testURL);
let loaded3 = BrowserTestUtils.browserLoaded(partialURLTab.linkedBrowser, testURL);
let loaded1 = BrowserTestUtils.browserLoaded(deletedURLTab.linkedBrowser, false, testURL);
let loaded2 = BrowserTestUtils.browserLoaded(fullURLTab.linkedBrowser, false, testURL);
let loaded3 = BrowserTestUtils.browserLoaded(partialURLTab.linkedBrowser, false, testURL);
deletedURLTab.linkedBrowser.loadURI(testURL);
fullURLTab.linkedBrowser.loadURI(testURL);
partialURLTab.linkedBrowser.loadURI(testURL);

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

@ -27,7 +27,7 @@ add_task(async function() {
await cxmenuPromise;
let menuitem = document.getAnonymousElementByAttribute(textBox,
"anonid", "paste-and-go");
let browserLoadedPromise = BrowserTestUtils.browserLoaded(browser, url.replace(/\n/g, ""));
let browserLoadedPromise = BrowserTestUtils.browserLoaded(browser, false, url.replace(/\n/g, ""));
EventUtils.synthesizeMouseAtCenter(menuitem, {});
// Using toSource in order to get the newlines escaped:
info("Paste and go, loading " + url.toSource());

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

@ -51,7 +51,7 @@ add_task(async function openKeywordBookmarkWithWindowOpen() {
let browser = tab.linkedBrowser;
if (!browser.currentURI || browser.currentURI.spec != TEST_URL) {
info("Waiting for browser load");
await BrowserTestUtils.browserLoaded(browser);
await BrowserTestUtils.browserLoaded(browser, false, TEST_URL);
}
is(browser.currentURI && browser.currentURI.spec, TEST_URL, "Tab with expected URL loaded.");
info("Waiting to remove tab");

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

@ -180,6 +180,41 @@
<label class="header-name" flex="1">&panePrivacySecurity.title;</label>
</hbox>
<!-- Permissions -->
<groupbox id="permissionsGroup" data-category="panePrivacy" hidden="true">
<caption><label>&permissions.label;</label></caption>
<separator class="thin"/>
<hbox align="start">
<checkbox id="popupPolicy" preference="dom.disable_open_during_load"
label="&blockPopups.label;" accesskey="&blockPopups.accesskey;"
onsyncfrompreference="return gPrivacyPane.updateButtons('popupPolicyButton',
'dom.disable_open_during_load');"
flex="1" />
<button id="popupPolicyButton"
class="accessory-button"
label="&popupExceptions.label;"
accesskey="&popupExceptions.accesskey;"
searchkeywords="&address.label; &button.cancel.label; &button.ok.label;"/>
</hbox>
<hbox id="addonInstallBox">
<checkbox id="warnAddonInstall"
label="&warnOnAddonInstall.label;"
accesskey="&warnOnAddonInstall.accesskey;"
preference="xpinstall.whitelist.required"
onsyncfrompreference="return gPrivacyPane.readWarnAddonInstall();"
flex="1" />
<button id="addonExceptions"
class="accessory-button"
label="&addonExceptions.label;"
accesskey="&addonExceptions.accesskey;"
searchkeywords="&address.label;
&allow.label;
&removepermission.label;
&removeallpermissions.label;
&button.cancel.label;
&button.ok.label;"/>
</hbox>
</groupbox>
<!-- History -->
<groupbox id="historyGroup" data-category="panePrivacy" hidden="true">
@ -431,23 +466,6 @@
</vbox>
</groupbox>
<!-- Pop-ups -->
<groupbox id="miscGroup" data-category="panePrivacy" hidden="true">
<caption><label>&popups.label;</label></caption>
<hbox align="start">
<checkbox id="popupPolicy" preference="dom.disable_open_during_load"
label="&blockPopups.label;" accesskey="&blockPopups.accesskey;"
onsyncfrompreference="return gPrivacyPane.updateButtons('popupPolicyButton',
'dom.disable_open_during_load');"
flex="1" />
<button id="popupPolicyButton"
class="accessory-button"
label="&popupExceptions.label;"
accesskey="&popupExceptions.accesskey;"
searchkeywords="&address.label; &button.cancel.label; &button.ok.label;"/>
</hbox>
</groupbox>
<!-- Notifications -->
<groupbox id="notificationsGroup" data-category="panePrivacy" hidden="true">
<caption><label>&notificationsPolicy.label;</label></caption>
@ -504,30 +522,9 @@
</label>
</groupbox>
<!-- addons, forgery (phishing) UI Security -->
<groupbox id="addonsPhishingGroup" data-category="panePrivacy" hidden="true">
<!-- Forgery (phishing) UI Security -->
<groupbox id="phishingGroup" data-category="panePrivacy" hidden="true">
<caption><label>&security.label;</label></caption>
<hbox id="addonInstallBox">
<checkbox id="warnAddonInstall"
label="&warnOnAddonInstall.label;"
accesskey="&warnOnAddonInstall.accesskey;"
preference="xpinstall.whitelist.required"
onsyncfrompreference="return gPrivacyPane.readWarnAddonInstall();"
flex="1" />
<button id="addonExceptions"
class="accessory-button"
label="&addonExceptions.label;"
accesskey="&addonExceptions.accesskey;"
searchkeywords="&address.label;
&allow.label;
&removepermission.label;
&removeallpermissions.label;
&button.cancel.label;
&button.ok.label;"/>
</hbox>
<separator class="thin"/>
<checkbox id="enableSafeBrowsing"
label="&enableSafeBrowsing.label;"
accesskey="&enableSafeBrowsing.accesskey;" />

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

@ -42,7 +42,7 @@ add_task(async function() {
*/
add_task(async function() {
await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
evaluateSearchResults("open pop-up windows", "miscGroup");
evaluateSearchResults("open pop-up windows", "permissionsGroup");
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

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

@ -15,7 +15,7 @@ add_task(async function() {
*/
add_task(async function() {
await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
evaluateSearchResults("allowed to install add-ons", "addonsPhishingGroup");
evaluateSearchResults("allowed to install add-ons", "permissionsGroup");
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

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

@ -4,4 +4,3 @@
browser.jar:
content/browser/content-UITour.js
content/browser/UITour-lib.js

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

@ -111,6 +111,8 @@ AutofillProfileAutoCompleteSearch.prototype = {
if (this.forceStop) {
return;
}
// Sort addresses by timeLastUsed for showing the lastest used address at top.
addresses.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
let allFieldNames = FormAutofillContent.getAllFieldNames(focusedInput);
let result = new ProfileAutoCompleteResult(searchString,

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

@ -128,20 +128,19 @@ FormAutofillHandler.prototype = {
}
this.changeFieldState(fieldDetail, "AUTO_FILLED");
} else if (element instanceof Ci.nsIDOMHTMLSelectElement) {
for (let option of element.options) {
if (value === option.textContent || value === option.value) {
// Do not change value if the option is already selected.
// Use case for multiple select is not considered here.
if (option.selected) {
break;
}
option.selected = true;
element.dispatchEvent(new element.ownerGlobal.UIEvent("input", {bubbles: true}));
element.dispatchEvent(new element.ownerGlobal.Event("change", {bubbles: true}));
this.changeFieldState(fieldDetail, "AUTO_FILLED");
break;
}
let option = FormAutofillUtils.findSelectOption(element, profile, fieldDetail.fieldName);
if (!option) {
continue;
}
// Do not change value or dispatch events if the option is already selected.
// Use case for multiple select is not considered here.
if (!option.selected) {
option.selected = true;
element.dispatchEvent(new element.ownerGlobal.UIEvent("input", {bubbles: true}));
element.dispatchEvent(new element.ownerGlobal.Event("change", {bubbles: true}));
}
// Autofill highlight appears regardless if value is changed or not
this.changeFieldState(fieldDetail, "AUTO_FILLED");
}
// Unlike using setUserInput directly, FormFillController dispatches an
@ -210,13 +209,25 @@ FormAutofillHandler.prototype = {
let element = fieldDetail.elementWeakRef.get();
let value = profile[fieldDetail.fieldName] || "";
// Skip the field that is null or already has text entered
if (!element || element.value) {
// Skip the field that is null
if (!element) {
continue;
}
element.previewValue = value;
this.changeFieldState(fieldDetail, value ? "PREVIEW" : "NORMAL");
if (element instanceof Ci.nsIDOMHTMLSelectElement) {
// Unlike text input, select element is always previewed even if
// the option is already selected.
let option = FormAutofillUtils.findSelectOption(element, profile, fieldDetail.fieldName);
element.previewValue = option ? option.text : "";
this.changeFieldState(fieldDetail, option ? "PREVIEW" : "NORMAL");
} else {
// Skip the field if it already has text entered
if (element.value) {
continue;
}
element.previewValue = value;
this.changeFieldState(fieldDetail, value ? "PREVIEW" : "NORMAL");
}
}
},

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

@ -13,6 +13,8 @@ const NAME_REFERENCES = "chrome://formautofill/content/nameReferences.js";
this.EXPORTED_SYMBOLS = ["FormAutofillNameUtils"];
Cu.import("resource://formautofill/FormAutofillUtils.jsm");
// FormAutofillNameUtils is initially translated from
// https://cs.chromium.org/chromium/src/components/autofill/core/browser/autofill_data_util.cc?rcl=b861deff77abecff11ae6a9f6946e9cc844b9817
var FormAutofillNameUtils = {
@ -204,10 +206,7 @@ var FormAutofillNameUtils = {
if (this._dataLoaded) {
return;
}
let sandbox = {};
let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
scriptLoader.loadSubScript(NAME_REFERENCES, sandbox, "utf-8");
let sandbox = FormAutofillUtils.loadDataFromScript(NAME_REFERENCES);
Object.assign(this, sandbox.nameReferences);
this._dataLoaded = true;

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

@ -8,6 +8,8 @@ this.EXPORTED_SYMBOLS = ["FormAutofillUtils"];
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
const ADDRESS_REFERENCES = "chrome://formautofill/content/addressReferences.js";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
this.FormAutofillUtils = {
@ -32,6 +34,7 @@ this.FormAutofillUtils = {
"cc-exp-month": "creditCard",
"cc-exp-year": "creditCard",
},
_addressDataLoaded: false,
isAddressField(fieldName) {
return !!this._fieldNameInfo[fieldName] && !this.isCreditCardField(fieldName);
@ -159,8 +162,117 @@ this.FormAutofillUtils = {
return [];
},
loadDataFromScript(url, sandbox = {}) {
let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
scriptLoader.loadSubScript(url, sandbox, "utf-8");
return sandbox;
},
/**
* Find the option element from select element.
* 1. Try to find the locale using the country from profile.
* 2. First pass try to find exact match.
* 3. Second pass try to identify values from profile value and options,
* and look for a match.
* @param {DOMElement} selectEl
* @param {object} profile
* @param {string} fieldName
* @returns {DOMElement}
*/
findSelectOption(selectEl, profile, fieldName) {
let value = profile[fieldName];
if (!value) {
return null;
}
// Load the addressData if needed
if (!this._addressDataLoaded) {
Object.assign(this, this.loadDataFromScript(ADDRESS_REFERENCES));
this._addressDataLoaded = true;
}
// Set dataset to "data/US" as fallback
let dataset = this.addressData[`data/${profile.country}`] ||
this.addressData["data/US"];
let collator = new Intl.Collator(dataset.lang, {sensitivity: "base", ignorePunctuation: true});
for (let option of selectEl.options) {
if (this.strCompare(value, option.value, collator) ||
this.strCompare(value, option.text, collator)) {
return option;
}
}
if (fieldName === "address-level1") {
if (!Array.isArray(dataset.sub_keys)) {
dataset.sub_keys = dataset.sub_keys.split("~");
}
if (!Array.isArray(dataset.sub_names)) {
dataset.sub_names = dataset.sub_names.split("~");
}
let keys = dataset.sub_keys;
let names = dataset.sub_names;
let identifiedValue = this.identifyValue(keys, names, value, collator);
// No point going any further if we cannot identify value from profile
if (identifiedValue === undefined) {
return null;
}
// Go through options one by one to find a match.
// Also check if any option contain the address-level1 key.
let pattern = new RegExp(`\\b${identifiedValue}\\b`, "i");
for (let option of selectEl.options) {
let optionValue = this.identifyValue(keys, names, option.value, collator);
let optionText = this.identifyValue(keys, names, option.text, collator);
if (identifiedValue === optionValue || identifiedValue === optionText || pattern.test(option.value)) {
return option;
}
}
}
if (fieldName === "country") {
// TODO: Support matching countries (Bug 1375382)
}
return null;
},
/**
* Try to match value with keys and names, but always return the key.
* @param {array<string>} keys
* @param {array<string>} names
* @param {string} value
* @param {object} collator
* @returns {string}
*/
identifyValue(keys, names, value, collator) {
let resultKey = keys.find(key => this.strCompare(value, key, collator));
if (resultKey) {
return resultKey;
}
let index = names.findIndex(name => this.strCompare(value, name, collator));
if (index !== -1) {
return keys[index];
}
return null;
},
/**
* Compare if two strings are the same.
* @param {string} a
* @param {string} b
* @param {object} collator
* @returns {boolean}
*/
strCompare(a = "", b = "", collator) {
return !collator.compare(a, b);
},
};
this.log = null;
this.FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);

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

@ -0,0 +1,15 @@
/* 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/. */
/* exported addressData */
/* eslint max-len: 0 */
"use strict";
// The data below is initially copied from
// https://chromium-i18n.appspot.com/ssl-aggregate-address
var addressData = {
"data/US": {"lang": "en", "upper": "CS", "sub_zipexs": "35000,36999~99500,99999~96799~85000,86999~71600,72999~34000,34099~09000,09999~96200,96699~90000,96199~80000,81999~06000,06999~19700,19999~20000,56999~32000,34999~30000,39901~96910,96932~96700,96899~83200,83999~60000,62999~46000,47999~50000,52999~66000,67999~40000,42799~70000,71599~03900,04999~96960,96979~20600,21999~01000,05544~48000,49999~96941,96944~55000,56799~38600,39799~63000,65999~59000,59999~68000,69999~88900,89999~03000,03899~07000,08999~87000,88499~10000,00544~27000,28999~58000,58999~96950,96952~43000,45999~73000,74999~97000,97999~96940~15000,19699~00600,00999~02800,02999~29000,29999~57000,57999~37000,38599~75000,73344~84000,84999~05000,05999~00800,00899~20100,24699~98000,99499~24700,26999~53000,54999~82000,83414", "zipex": "95014,22162-1010", "name": "UNITED STATES", "zip": "(\\d{5})(?:[ \\-](\\d{4}))?", "zip_name_type": "zip", "fmt": "%N%n%O%n%A%n%C, %S %Z", "state_name_type": "state", "id": "data/US", "languages": "en", "sub_keys": "AL~AK~AS~AZ~AR~AA~AE~AP~CA~CO~CT~DE~DC~FL~GA~GU~HI~ID~IL~IN~IA~KS~KY~LA~ME~MH~MD~MA~MI~FM~MN~MS~MO~MT~NE~NV~NH~NJ~NM~NY~NC~ND~MP~OH~OK~OR~PW~PA~PR~RI~SC~SD~TN~TX~UT~VT~VI~VA~WA~WV~WI~WY", "key": "US", "posturl": "https://tools.usps.com/go/ZipLookupAction!input.action", "require": "ACSZ", "sub_names": "Alabama~Alaska~American Samoa~Arizona~Arkansas~Armed Forces (AA)~Armed Forces (AE)~Armed Forces (AP)~California~Colorado~Connecticut~Delaware~District of Columbia~Florida~Georgia~Guam~Hawaii~Idaho~Illinois~Indiana~Iowa~Kansas~Kentucky~Louisiana~Maine~Marshall Islands~Maryland~Massachusetts~Michigan~Micronesia~Minnesota~Mississippi~Missouri~Montana~Nebraska~Nevada~New Hampshire~New Jersey~New Mexico~New York~North Carolina~North Dakota~Northern Mariana Islands~Ohio~Oklahoma~Oregon~Palau~Pennsylvania~Puerto Rico~Rhode Island~South Carolina~South Dakota~Tennessee~Texas~Utah~Vermont~Virgin Islands~Virginia~Washington~West Virginia~Wisconsin~Wyoming", "sub_zips": "3[56]~99[5-9]~96799~8[56]~71[6-9]|72~340~09~96[2-6]~9[0-5]|96[01]~8[01]~06~19[7-9]~20[02-5]|569~3[23]|34[1-9]~3[01]|398|39901~969([1-2]\\d|3[12])~967[0-8]|9679[0-8]|968~83[2-9]~6[0-2]~4[67]~5[0-2]~6[67]~4[01]|42[0-7]~70|71[0-5]~039|04~969[67]~20[6-9]|21~01|02[0-7]|05501|05544~4[89]~9694[1-4]~55|56[0-7]~38[6-9]|39[0-7]~6[3-5]~59~6[89]~889|89~03[0-8]~0[78]~87|88[0-4]~1[0-4]|06390|00501|00544~2[78]~58~9695[0-2]~4[3-5]~7[34]~97~969(39|40)~1[5-8]|19[0-6]~00[679]~02[89]~29~57~37|38[0-5]~7[5-9]|885|73301|73344~84~05~008~201|2[23]|24[0-6]~98|99[0-4]~24[7-9]|2[56]~5[34]~82|83[01]|83414"},
};

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

@ -16,7 +16,7 @@ function EditDialog() {
EditDialog.prototype = {
init() {
this.refs = {
this._elements = {
controlsContainer: document.getElementById("controls-container"),
cancel: document.getElementById("cancel"),
save: document.getElementById("save"),
@ -24,6 +24,11 @@ EditDialog.prototype = {
this.attachEventListeners();
},
uninit() {
this.detachEventListeners();
this._elements = null;
},
/**
* Asks FormAutofillParent to save or update an address.
* @param {object} data
@ -84,12 +89,20 @@ EditDialog.prototype = {
// Toggle disabled attribute on the save button based on
// whether the form is filled or empty.
if (Object.keys(this.buildAddressObject()).length == 0) {
this.refs.save.setAttribute("disabled", true);
this._elements.save.setAttribute("disabled", true);
} else {
this.refs.save.removeAttribute("disabled");
this._elements.save.removeAttribute("disabled");
}
break;
}
case "unload": {
this.uninit();
break;
}
case "keypress": {
this.handleKeyPress(event);
break;
}
}
},
@ -99,11 +112,10 @@ EditDialog.prototype = {
* @param {DOMEvent} event
*/
handleClick(event) {
if (event.target == this.refs.cancel) {
this.detachEventListeners();
if (event.target == this._elements.cancel) {
window.close();
}
if (event.target == this.refs.save) {
if (event.target == this._elements.save) {
if (this._address) {
this.saveAddress({
guid: this._address.guid,
@ -114,7 +126,17 @@ EditDialog.prototype = {
address: this.buildAddressObject(),
});
}
this.detachEventListeners();
window.close();
}
},
/**
* Handle key press events
*
* @param {DOMEvent} event
*/
handleKeyPress(event) {
if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
window.close();
}
},
@ -123,7 +145,8 @@ EditDialog.prototype = {
* Attach event listener
*/
attachEventListeners() {
this.refs.controlsContainer.addEventListener("click", this);
window.addEventListener("keypress", this);
this._elements.controlsContainer.addEventListener("click", this);
document.addEventListener("input", this);
},
@ -131,10 +154,10 @@ EditDialog.prototype = {
* Remove event listener
*/
detachEventListeners() {
this.refs.controlsContainer.removeEventListener("click", this);
window.removeEventListener("keypress", this);
this._elements.controlsContainer.removeEventListener("click", this);
document.removeEventListener("input", this);
},
};
// Pass in argument from openDialog
new EditDialog();

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

@ -217,6 +217,10 @@ ManageProfileDialog.prototype = {
this.uninit();
break;
}
case "keypress": {
this.handleKeyPress(event);
break;
}
}
},
@ -235,6 +239,17 @@ ManageProfileDialog.prototype = {
}
},
/**
* Handle key press events
*
* @param {DOMEvent} event
*/
handleKeyPress(event) {
if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
window.close();
}
},
observe(subject, topic, data) {
switch (topic) {
case "formautofill-storage-changed": {
@ -252,6 +267,7 @@ ManageProfileDialog.prototype = {
*/
attachEventListeners() {
window.addEventListener("unload", this, {once: true});
window.addEventListener("keypress", this);
this._elements.addresses.addEventListener("change", this);
this._elements.controlsContainer.addEventListener("click", this);
Services.obs.addObserver(this, "formautofill-storage-changed");
@ -261,6 +277,7 @@ ManageProfileDialog.prototype = {
* Remove event listener
*/
detachEventListeners() {
window.removeEventListener("keypress", this);
this._elements.addresses.removeEventListener("change", this);
this._elements.controlsContainer.removeEventListener("click", this);
Services.obs.removeObserver(this, "formautofill-storage-changed");

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

@ -13,6 +13,19 @@ add_task(async function test_cancelEditProfileDialog() {
});
});
add_task(async function test_cancelEditProfileDialogWithESC() {
await new Promise(resolve => {
let win = window.openDialog(EDIT_PROFILE_DIALOG_URL);
win.addEventListener("load", () => {
win.addEventListener("unload", () => {
ok(true, "Edit profile dialog is closed with ESC key");
resolve();
}, {once: true});
EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
}, {once: true});
});
});
add_task(async function test_saveAddress() {
await new Promise(resolve => {
let win = window.openDialog(EDIT_PROFILE_DIALOG_URL, null, null, null);

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

@ -35,6 +35,19 @@ add_task(async function test_manageProfilesInitialState() {
});
});
add_task(async function test_cancelManageProfileDialogWithESC() {
await new Promise(resolve => {
let win = window.openDialog(MANAGE_PROFILES_DIALOG_URL);
win.addEventListener("load", () => {
win.addEventListener("unload", () => {
ok(true, "Manage profiles dialog is closed with ESC key");
resolve();
}, {once: true});
EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
}, {once: true});
});
});
add_task(async function test_removingSingleAndMultipleProfiles() {
await saveAddress(TEST_ADDRESS_1);
await saveAddress(TEST_ADDRESS_2);

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

@ -5,6 +5,7 @@
"use strict";
let formFillChromeScript;
let expectingPopup = null;
function setInput(selector, value) {
let input = document.querySelector("input" + selector);
@ -98,6 +99,28 @@ async function checkAddresses(expectedAddresses) {
});
}
// Utils for registerPopupShownListener(in satchel_common.js) that handles dropdown popup
// Please call "initPopupListener()" in your test and "await expectPopup()"
// if you want to wait for dropdown menu displayed.
function expectPopup() {
info("expecting a popup");
return new Promise(resolve => {
expectingPopup = resolve;
});
}
function popupShownListener() {
info("popup shown for test ");
if (expectingPopup) {
expectingPopup();
expectingPopup = null;
}
}
function initPopupListener() {
registerPopupShownListener(popupShownListener);
}
function formAutoFillCommonSetup() {
let chromeURL = SimpleTest.getTestFileURL("formautofill_parent_utils.js");
formFillChromeScript = SpecialPowers.loadChromeScript(chromeURL);
@ -111,6 +134,7 @@ function formAutoFillCommonSetup() {
SimpleTest.registerCleanupFunction(() => {
formFillChromeScript.sendAsyncMessage("cleanup");
formFillChromeScript.destroy();
expectingPopup = null;
});
}

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

@ -19,7 +19,6 @@ Form autofill test: autocomplete on an autofocus form
"use strict";
let expectingPopup = null;
let MOCK_STORAGE = [{
organization: "Sesame Street",
"street-address": "123 Sesame Street.",
@ -30,20 +29,7 @@ let MOCK_STORAGE = [{
tel: "1-650-903-0800",
}];
function expectPopup() {
info("expecting a popup");
return new Promise(resolve => {
expectingPopup = resolve;
});
}
function popupShownListener() {
info("popup shown for test ");
if (expectingPopup) {
expectingPopup();
expectingPopup = null;
}
}
initPopupListener();
async function setupAddressStorage() {
await addAddress(MOCK_STORAGE[0]);
@ -59,8 +45,6 @@ add_task(async function check_autocomplete_on_autofocus_field() {
));
});
registerPopupShownListener(popupShownListener);
</script>
<p id="display"></p>

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

@ -19,34 +19,20 @@ Form autofill test: simple form address autofill
"use strict";
let expectingPopup = null;
let MOCK_STORAGE = [{
organization: "Sesame Street",
"street-address": "123 Sesame Street.",
tel: "1-345-345-3456",
country: "US",
"address-level1": "NY",
}, {
organization: "Mozilla",
"street-address": "331 E. Evelyn Avenue",
tel: "1-650-903-0800",
country: "US",
"address-level1": "CA",
}];
function expectPopup() {
info("expecting a popup");
return new Promise(resolve => {
expectingPopup = resolve;
});
}
function popupShownListener() {
info("popup shown for test ");
if (expectingPopup) {
expectingPopup();
expectingPopup = null;
}
}
function checkElementFilled(element, expectedvalue) {
return [
new Promise(resolve => {
@ -100,6 +86,8 @@ async function setupFormHistory() {
]);
}
initPopupListener();
// Form with history only.
add_task(async function history_only_menu_checking() {
await setupFormHistory();
@ -176,8 +164,6 @@ add_task(async function check_form_autofill_resume() {
));
});
registerPopupShownListener(popupShownListener);
</script>
<p id="display"></p>
@ -193,7 +179,13 @@ registerPopupShownListener(popupShownListener);
<p><label>country: <select id="country" name="country" autocomplete="country">
<option/>
<option value="US">United States</option>
</label></p>
</select></label></p>
<p><label>states: <select id="address-level1" name="address-level1" autocomplete="address-level1">
<option/>
<option value="CA">California</option>
<option value="NY">New York</option>
<option value="WA">Washington</option>
</select></label></p>
</form>
</div>

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

@ -19,7 +19,6 @@ Form autofill test: preview and highlight
"use strict";
let expectingPopup = null;
let defaultTextColor;
const MOCK_STORAGE = [{
organization: "Sesame Street",
@ -33,21 +32,6 @@ const MOCK_STORAGE = [{
tel: "2-222-333-444",
}];
function expectPopup() {
info("expecting a popup");
return new Promise(resolve => {
expectingPopup = resolve;
});
}
function popupShownListener() {
info("popup shown for test ");
if (expectingPopup) {
expectingPopup();
expectingPopup = null;
}
}
// We could not get ManuallyManagedState of element now, so directly check if
// filter and text color style are applied.
function checkFieldPreview(elem, expectedText) {
@ -102,6 +86,8 @@ function confirmAllFieldsFilled(address) {
return Promise.all(pendingPromises);
}
initPopupListener();
add_task(async function setup_storage() {
defaultTextColor = window.getComputedStyle(document.querySelector("input")).getPropertyValue("color");
@ -153,8 +139,6 @@ add_task(async function check_filled_highlight() {
checkFormFilledFields(MOCK_STORAGE[0]);
});
registerPopupShownListener(popupShownListener);
</script>
<p id="display"></p>

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

@ -29,7 +29,9 @@ let TEST_ADDRESSES = [{
tel: "1-650-903-0800",
}];
// Autofill the address from dropdown menu.
initPopupListener();
// Submit first address for saving.
add_task(async function check_storage_after_form_submitted() {
// We already verified the first time use case in browser test
await SpecialPowers.pushPrefEnv({
@ -50,6 +52,32 @@ add_task(async function check_storage_after_form_submitted() {
ok(matching, "Address saved as expected");
});
// Submit another new address.
add_task(async function check_storage_after_another_address_submitted() {
document.querySelector("form").reset();
for (let key in TEST_ADDRESSES[1]) {
await setInput("#" + key, TEST_ADDRESSES[1][key]);
}
clickOnElement("input[type=submit]");
// The 2nd test address should be on the top since it's the last used one.
let addressesInMenu = TEST_ADDRESSES.slice(1);
addressesInMenu.push(TEST_ADDRESSES[0]);
// let expectedAddresses = TEST_ADDRESSES.slice(0);
await onAddressChanged("add");
let matching = await checkAddresses(TEST_ADDRESSES);
ok(matching, "New address saved as expected");
await setInput("#organization", "");
doKey("down");
await expectPopup();
checkMenuEntries(addressesInMenu.map(address =>
JSON.stringify({primary: address.organization, secondary: address["street-address"]})
));
});
</script>
<div>

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

@ -250,6 +250,63 @@ const TESTCASES_INPUT_UNCHANGED = [
},
];
const TESTCASES_US_STATES = [
{
description: "Form with US states select elements; with lower case state key",
document: `<form><select id="state" autocomplete="shipping address-level1">
<option value=""></option>
<option value="CA">California</option>
</select></form>`,
fieldDetails: [
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level1", "element": {}},
],
profileData: {
"guid": "123",
"country": "US",
"address-level1": "ca",
},
expectedResult: {
"state": "CA",
},
},
{
description: "Form with US states select elements; with state name and extra spaces",
document: `<form><select id="state" autocomplete="shipping address-level1">
<option value=""></option>
<option value="CA">CA</option>
</select></form>`,
fieldDetails: [
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level1", "element": {}},
],
profileData: {
"guid": "123",
"country": "US",
"address-level1": " California ",
},
expectedResult: {
"state": "CA",
},
},
{
description: "Form with US states select elements; with partial state key match",
document: `<form><select id="state" autocomplete="shipping address-level1">
<option value=""></option>
<option value="US-WA">WA-Washington</option>
</select></form>`,
fieldDetails: [
{"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level1", "element": {}},
],
profileData: {
"guid": "123",
"country": "US",
"address-level1": "WA",
},
expectedResult: {
"state": "US-WA",
},
},
];
function do_test(testcases, testFn) {
for (let tc of testcases) {
(function() {
@ -327,3 +384,16 @@ do_test(TESTCASES_INPUT_UNCHANGED, (testcase, element) => {
}),
];
});
do_test(TESTCASES_US_STATES, (testcase, element) => {
let id = element.id;
return [
new Promise(resolve => {
element.addEventListener("input", () => {
Assert.equal(element.value, testcase.expectedResult[id],
"Check the " + id + " field was filled with correct data");
resolve();
}, {once: true});
}),
];
});

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

@ -6,7 +6,7 @@
"use strict";
document.getElementById("onboarding-overlay-dialog")
document.getElementById("onboarding-overlay")
.addEventListener("click", evt => {
switch (evt.target.id) {
case "onboarding-tour-addons-button":
@ -24,5 +24,13 @@ document.getElementById("onboarding-overlay-dialog")
case "onboarding-tour-search-button":
Mozilla.UITour.openSearchPanel(() => {});
break;
case "onboarding-overlay":
case "onboarding-overlay-close-btn":
// Dismiss any highlights if a user tries to close the dialog.
Mozilla.UITour.hideHighlight();
}
// Dismiss any highlights if a user tries to change to other tours.
if (evt.target.classList.contains("onboarding-tour-item")) {
Mozilla.UITour.hideHighlight();
}
});

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

@ -14,7 +14,7 @@ const ONBOARDING_CSS_URL = "resource://onboarding/onboarding.css";
const ABOUT_HOME_URL = "about:home";
const ABOUT_NEWTAB_URL = "about:newtab";
const BUNDLE_URI = "chrome://onboarding/locale/onboarding.properties";
const UITOUR_JS_URI = "chrome://browser/content/UITour-lib.js";
const UITOUR_JS_URI = "resource://onboarding/lib/UITour-lib.js";
const TOUR_AGENT_JS_URI = "resource://onboarding/onboarding-tour-agent.js";
const BRAND_SHORT_NAME = Services.strings
.createBundle("chrome://branding/locale/brand.properties")

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

@ -5,3 +5,7 @@
[features/onboarding@mozilla.org] chrome.jar:
% resource onboarding %content/
content/ (content/*)
# Package UITour-lib.js in here rather than under
# /browser/components/uitour to avoid "unreferenced files" error when
# Onboarding extension is not built.
content/lib/UITour-lib.js (/browser/components/uitour/UITour-lib.js)

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

@ -128,7 +128,7 @@ VIAddVersionKey "ProductVersion" "${AppVersion}"
!define NOW_INSTALLING_TOP_DU 70u
!define INSTALL_BLURB_TOP_DU 137u
!define INSTALL_FOOTER_TOP_DU -48u
!define INSTALL_FOOTER_WIDTH_DU 300u
!define INSTALL_FOOTER_WIDTH_DU 250u
!define PROGRESS_BAR_TOP_DU 112u
!define APPNAME_BMP_EDGE_DU 19u
!define APPNAME_BMP_TOP_DU 12u

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

@ -833,15 +833,17 @@ Function createInstall
; In some locales, the footer message may be too long to fit on one line.
; Figure out how much height it needs and give it that much.
${GetTextExtent} "$(STUB_BLURB_FOOTER)" $FontFooter $R1 $R2
StrCpy $1 0
${While} $R1 > 0
IntOp $1 $1 + $R2
IntOp $R1 $R1 - ${INSTALL_FOOTER_WIDTH_DU}
${EndWhile}
nsDialogs::CreateControl STATIC ${DEFAULT_STYLES}|${SS_NOTIFY}|${SS_RIGHT} \
${WS_EX_TRANSPARENT} -320u ${INSTALL_FOOTER_TOP_DU} \
${INSTALL_FOOTER_WIDTH_DU} "$1u" "$(STUB_BLURB_FOOTER)"
${GetTextWidthHeight} "$(STUB_BLURB_FOOTER)" $FontFooter \
${INSTALL_FOOTER_WIDTH_DU} $R1 $R2
!ifdef ${AB_CD}_rtl
nsDialogs::CreateControl STATIC ${DEFAULT_STYLES}|${SS_NOTIFY} \
${WS_EX_TRANSPARENT} 30u ${INSTALL_FOOTER_TOP_DU} ${INSTALL_FOOTER_WIDTH_DU} "$R2u" \
"$(STUB_BLURB_FOOTER)"
!else
nsDialogs::CreateControl STATIC ${DEFAULT_STYLES}|${SS_NOTIFY}|${SS_RIGHT} \
${WS_EX_TRANSPARENT} 175u ${INSTALL_FOOTER_TOP_DU} ${INSTALL_FOOTER_WIDTH_DU} "$R2u" \
"$(STUB_BLURB_FOOTER)"
!endif
Pop $0
SendMessage $0 ${WM_SETFONT} $FontFooter 0
SetCtlColors $0 ${INSTALL_BLURB_TEXT_COLOR} transparent

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

@ -2,7 +2,6 @@
- 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/. -->
<!ENTITY popups.label "Pop-ups">
<!ENTITY blockPopups.label "Block pop-up windows">
<!ENTITY blockPopups.accesskey "B">

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

@ -29,6 +29,7 @@
<!ENTITY doNotTrack.post.label ".">
<!ENTITY history.label "History">
<!ENTITY permissions.label "Permissions">
<!ENTITY locationBar.label "Location Bar">

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

@ -159,6 +159,7 @@ XPCOMUtils.defineLazyGetter(this, "ALL_BUILTIN_ITEMS", function() {
"BMB_bookmarksToolbarPopup",
"search-go-button",
"soundplaying-icon",
"restore-tabs-button",
]
return DEFAULT_ITEMS.concat(PALETTE_ITEMS)
.concat(SPECIAL_CASES);

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

@ -1284,3 +1284,8 @@ notification.pluginVulnerable > .notification-inner > .messageCloseButton:not(:h
.webextension-popup-browser {
border-radius: inherit;
}
/* Prevent movement in the restore-tabs-button when it's clicked. */
.restore-tabs-button:hover:active:not([disabled="true"]) {
padding: 3px;
}

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

@ -14,8 +14,6 @@
%include ../shared/browser.inc.css
:root {
--tabs-toolbar-color: #333;
--toolbarbutton-vertical-text-padding: calc(var(--toolbarbutton-inner-padding) + 1px);
%ifdef MOZ_PHOTON_THEME
@ -122,7 +120,11 @@ toolbar:-moz-lwtheme {
}
#main-window:not(:-moz-lwtheme) > #titlebar {
%ifdef MOZ_PHOTON_THEME
background-color: #232323;
%else
-moz-appearance: -moz-window-titlebar;
%endif
}
#main-window:not([tabsintitlebar]) > #titlebar {
@ -162,9 +164,11 @@ toolbar:-moz-lwtheme {
margin-top: 3px;
}
%ifndef MOZ_PHOTON_THEME
#main-window[customize-entered] > #titlebar {
-moz-appearance: none;
}
%endif
/** End titlebar **/
@ -1524,7 +1528,11 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
.tabbrowser-tab[visuallyselected=true]:not(:-moz-lwtheme) {
/* overriding tabbox.css */
%ifdef MOZ_PHOTON_THEME
color: hsl(240, 5%, 5%);
%else
color: inherit;
%endif
}
.tabbrowser-tab[visuallyselected=true] {
@ -1556,10 +1564,17 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
}
#TabsToolbar:not(:-moz-lwtheme) {
color: var(--tabs-toolbar-color);
color: #333;
text-shadow: @loweredShadow@;
}
%ifdef MOZ_PHOTON_THEME
:root[tabsintitlebar] #TabsToolbar:not(:-moz-lwtheme) {
color: hsl(240, 9%, 98%);
text-shadow: none;
}
%endif
%ifndef MOZ_PHOTON_THEME
#navigator-toolbox[inFullscreen] > #TabsToolbar {
padding-top: var(--space-above-tabbar);

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

@ -32,7 +32,6 @@
--chrome-selection-background-color: #5675B9;
/* Tabs */
--tabs-toolbar-color: #F5F7FA;
--tab-background-color: #272b35;
--tab-hover-background-color: #07090a;
--tab-selection-color: #f5f7fa;

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

@ -604,3 +604,56 @@
.alltabs-endimage[blocked] {
list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio-blocked.svg);
}
.restore-tabs-button-wrapper {
visibility: hidden;
position: fixed; /* so the button does not take up actual space and cause overflow buttons in the tabbar when hidden */
}
.restore-tabs-button-wrapper[shown] {
visibility: visible;
position: initial;
}
.restore-tabs-button {
box-sizing: border-box;
-moz-appearance: none;
background-color: hsl(0,0%,0%,.04);
border: 1px solid hsla(0,0%,16%,.2);
border-radius: 3px;
margin: 3px;
margin-inline-start: 9px;
transition: max-width 300ms;
}
.restore-tabs-button:hover {
background-color: hsl(0,0%,0%,.08);
}
.restore-tabs-button:active {
background-color: hsl(0,0%,0%,.11);
}
#TabsToolbar[brighttext] .restore-tabs-button {
background-color: hsl(0,0%,100%,.07);
border-color:currentColor;
color: currentColor;
opacity: .7;
}
#TabsToolbar[brighttext] .restore-tabs-button:hover {
background-color: hsl(0,0%,100%,.17);
}
#TabsToolbar[brighttext] .restore-tabs-button:active {
background-color: hsl(0,0%,100%,.27);
}
.restore-tabs-button > .toolbarbutton-icon {
display: none;
}
.restore-tabs-button > .toolbarbutton-text {
display: -moz-box;
padding: 0 5px;
}

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

@ -2059,3 +2059,8 @@ notification.pluginVulnerable > .notification-inner > .messageCloseButton {
padding-top: .9167em;
padding-bottom: .9167em;
}
/* Prevent movement in the restore-tabs-button when it's clicked. */
.restore-tabs-button:hover:active:not([disabled="true"]) {
padding: 3px;
}

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

@ -6,16 +6,6 @@ dnl Add compiler specific options
AC_DEFUN([MOZ_DEFAULT_COMPILER],
[
dnl set DEVELOPER_OPTIONS early; MOZ_DEFAULT_COMPILER is usually the first non-setup directive
if test -z "$MOZILLA_OFFICIAL"; then
DEVELOPER_OPTIONS=1
fi
MOZ_ARG_ENABLE_BOOL(release,
[ --enable-release Build with more conservative, release engineering-oriented options.
This may slow down builds.],
DEVELOPER_OPTIONS=,
DEVELOPER_OPTIONS=1)
dnl Default to MSVC for win32 and gcc-4.2 for darwin
dnl ==============================================================
if test -z "$CROSS_COMPILE"; then

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

@ -38,7 +38,6 @@ MACH_MODULES = [
'build/valgrind/mach_commands.py',
'devtools/shared/css/generated/mach_commands.py',
'dom/bindings/mach_commands.py',
'dom/media/test/external/mach_commands.py',
'layout/tools/reftest/mach_commands.py',
'python/mach_commands.py',
'python/mach/mach/commands/commandinfo.py',

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

@ -213,7 +213,6 @@ def old_configure_options(*options):
'--enable-raw',
'--enable-readline',
'--enable-reflow-perf',
'--enable-release',
'--enable-safe-browsing',
'--enable-sandbox',
'--enable-signmar',

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

@ -1046,3 +1046,18 @@ option(env='RUSTFLAGS',
nargs=1,
help='Rust compiler flags')
set_config('RUSTFLAGS', depends('RUSTFLAGS')(lambda flags: flags))
imply_option('--enable-release', mozilla_official)
imply_option('--enable-release', depends_if('MOZ_AUTOMATION')(lambda x: True))
js_option('--enable-release',
help='Build with more conservative, release engineering-oriented '
'options. This may slow down builds.')
@depends('--enable-release')
def developer_options(value):
if not value:
return True
add_old_configure_assignment('DEVELOPER_OPTIONS', developer_options)

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

@ -14,8 +14,6 @@ mk_add_options AUTOCLOBBER=1
ac_add_options --enable-crashreporter
ac_add_options --enable-release
# Tell the build system where to find llvm-config for builds on automation.
export LLVM_CONFIG="${TOOLTOOL_DIR:-$topsrcdir}/clang/bin/llvm-config"

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

@ -28,7 +28,6 @@ mozilla.pth:build/pymake
mozilla.pth:config
mozilla.pth:dom/bindings
mozilla.pth:dom/bindings/parser
mozilla.pth:dom/media/test/external
mozilla.pth:layout/tools/reftest
mozilla.pth:other-licenses/ply/
mozilla.pth:taskcluster

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

@ -81,6 +81,7 @@ skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keybo
[browser_markup_anonymous_02.js]
skip-if = e10s # scratchpad.xul is not loading in e10s window
[browser_markup_anonymous_03.js]
skip-if = stylo # Stylo doesn't support shadow DOM yet, bug 1293844
[browser_markup_anonymous_04.js]
[browser_markup_copy_image_data.js]
subsuite = clipboard

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

@ -22,6 +22,8 @@ window.onload = function () {
require("devtools/shared/dom-node-filter-constants");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const isStylo = SpecialPowers.getBoolPref("layout.css.servo.enabled");
SpecialPowers.pushPrefEnv({"set": [
["dom.webcomponents.enabled", true]
]});
@ -144,6 +146,12 @@ window.onload = function () {
});
addAsyncTest(function* testShadowAnonymous() {
// Stylo doesn't currently support shadow DOM (bug 1293844)
if (isStylo) {
runNextTest();
return;
}
info("Testing shadow DOM content.");
let shadow = yield gWalker.querySelector(gWalker.rootNode, "#shadow");

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

@ -2864,7 +2864,9 @@ ArrayBufferClient.prototype = {
valid: true,
slice: DebuggerClient.requester({
type: "slice"
type: "slice",
start: arg(0),
count: arg(1)
}),
};

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

@ -18,7 +18,7 @@ using namespace mozilla;
using namespace mozilla::dom;
FormData::FormData(nsISupports* aOwner)
: HTMLFormSubmission(WrapNotNull(UTF_8_ENCODING), nullptr)
: HTMLFormSubmission(UTF_8_ENCODING, nullptr)
, mOwner(aOwner)
{
}
@ -402,7 +402,7 @@ NS_IMETHODIMP
FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
nsACString& aContentTypeWithCharset, nsACString& aCharset)
{
FSMultipartFormData fs(WrapNotNull(UTF_8_ENCODING), nullptr);
FSMultipartFormData fs(UTF_8_ENCODING, nullptr);
for (uint32_t i = 0; i < mFormData.Length(); ++i) {
if (mFormData[i].wasNullBlob) {

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

@ -6535,9 +6535,8 @@ nsIDocument::ImportNode(nsINode& aNode, bool aDeep, ErrorResult& rv) const
case nsIDOMNode::DOCUMENT_TYPE_NODE:
{
nsCOMPtr<nsINode> newNode;
nsCOMArray<nsINode> nodesWithProperties;
rv = nsNodeUtils::Clone(imported, aDeep, mNodeInfoManager,
nodesWithProperties, getter_AddRefs(newNode));
rv = nsNodeUtils::Clone(imported, aDeep, mNodeInfoManager, nullptr,
getter_AddRefs(newNode));
if (rv.Failed()) {
return nullptr;
}

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

@ -408,9 +408,7 @@ nsNodeUtils::CloneNodeImpl(nsINode *aNode, bool aDeep, nsINode **aResult)
*aResult = nullptr;
nsCOMPtr<nsINode> newNode;
nsCOMArray<nsINode> nodesWithProperties;
nsresult rv = Clone(aNode, aDeep, nullptr, nodesWithProperties,
getter_AddRefs(newNode));
nsresult rv = Clone(aNode, aDeep, nullptr, nullptr, getter_AddRefs(newNode));
NS_ENSURE_SUCCESS(rv, rv);
newNode.forget(aResult);
@ -422,7 +420,7 @@ nsresult
nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep,
nsNodeInfoManager *aNewNodeInfoManager,
JS::Handle<JSObject*> aReparentScope,
nsCOMArray<nsINode> &aNodesWithProperties,
nsCOMArray<nsINode> *aNodesWithProperties,
nsINode *aParent, nsINode **aResult)
{
NS_PRECONDITION((!aClone && aNewNodeInfoManager) || !aReparentScope,
@ -653,10 +651,10 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep,
}
#endif
if (aNode->HasProperties()) {
bool ok = aNodesWithProperties.AppendObject(aNode);
if (aNodesWithProperties && aNode->HasProperties()) {
bool ok = aNodesWithProperties->AppendObject(aNode);
if (aClone) {
ok = ok && aNodesWithProperties.AppendObject(clone);
ok = ok && aNodesWithProperties->AppendObject(clone);
}
NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);

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

@ -181,12 +181,13 @@ public:
* shouldn't be changed.
* @param aNodesWithProperties All nodes (from amongst aNode and its
* descendants) with properties. Every node will
* be followed by its clone.
* be followed by its clone. Null can be passed to
* prevent this from being used.
* @param aResult *aResult will contain the cloned node.
*/
static nsresult Clone(nsINode *aNode, bool aDeep,
nsNodeInfoManager *aNewNodeInfoManager,
nsCOMArray<nsINode> &aNodesWithProperties,
nsCOMArray<nsINode> *aNodesWithProperties,
nsINode **aResult)
{
return CloneAndAdopt(aNode, true, aDeep, aNewNodeInfoManager,
@ -198,9 +199,8 @@ public:
*/
static nsresult Clone(nsINode *aNode, bool aDeep, nsINode **aResult)
{
nsCOMArray<nsINode> dummyNodeWithProperties;
return CloneAndAdopt(aNode, true, aDeep, nullptr, nullptr,
dummyNodeWithProperties, aNode->GetParent(), aResult);
return CloneAndAdopt(aNode, true, aDeep, nullptr, nullptr, nullptr,
aNode->GetParent(), aResult);
}
/**
@ -226,7 +226,7 @@ public:
{
nsCOMPtr<nsINode> node;
nsresult rv = CloneAndAdopt(aNode, false, true, aNewNodeInfoManager,
aReparentScope, aNodesWithProperties,
aReparentScope, &aNodesWithProperties,
nullptr, getter_AddRefs(node));
nsMutationGuard::DidMutate();
@ -299,7 +299,8 @@ private:
* @param aNodesWithProperties All nodes (from amongst aNode and its
* descendants) with properties. If aClone is
* true every node will be followed by its
* clone.
* clone. Null can be passed to prevent this from
* being populated.
* @param aParent If aClone is true the cloned node will be appended to
* aParent's children. May be null. If not null then aNode
* must be an nsIContent.
@ -309,7 +310,7 @@ private:
static nsresult CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep,
nsNodeInfoManager *aNewNodeInfoManager,
JS::Handle<JSObject*> aReparentScope,
nsCOMArray<nsINode> &aNodesWithProperties,
nsCOMArray<nsINode> *aNodesWithProperties,
nsINode *aParent, nsINode **aResult);
enum class AnimationMutationType

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

@ -53,6 +53,10 @@ ChooseValidatorCompileOptions(const ShBuiltInResources& resources,
// Work around that Mac drivers handle struct scopes incorrectly.
options |= SH_REGENERATE_STRUCT_NAMES;
options |= SH_INIT_OUTPUT_VARIABLES;
// Work around that Intel drivers on Mac OSX handle for-loop incorrectly.
if (gl->Vendor() == gl::GLVendor::Intel) {
options |= SH_ADD_AND_TRUE_TO_LOOP_CONDITION;
#endif
if (!gl->IsANGLE() && gl->Vendor() == gl::GLVendor::Intel) {

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

@ -889,7 +889,7 @@ GetSubmitEncoding(nsGenericHTMLElement* aForm)
if (doc) {
return Encoding::ForName(doc->GetDocumentCharacterSet());
}
return WrapNotNull(UTF_8_ENCODING);
return UTF_8_ENCODING;
}
void

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

@ -536,7 +536,7 @@ ContentChild::RecvSetXPCOMProcessAttributes(const XPCOMInitData& aXPCOMInit,
{
mLookAndFeelCache = aLookAndFeelIntCache;
InitXPCOM(aXPCOMInit, aInitialData);
InitGraphicsDeviceData();
InitGraphicsDeviceData(aXPCOMInit.contentDeviceData());
#ifdef NS_PRINTING
// Force the creation of the nsPrintingProxy so that it's IPC counterpart,
@ -998,11 +998,11 @@ ContentChild::AppendProcessId(nsACString& aName)
}
void
ContentChild::InitGraphicsDeviceData()
ContentChild::InitGraphicsDeviceData(const ContentDeviceData& aData)
{
// Initialize the graphics platform. This may contact the parent process
// to read device preferences.
gfxPlatform::GetPlatform();
gfxPlatform::InitChild(aData);
}
void

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

@ -107,7 +107,7 @@ public:
void InitXPCOM(const XPCOMInitData& aXPCOMInit,
const mozilla::dom::ipc::StructuredCloneData& aInitialData);
void InitGraphicsDeviceData();
void InitGraphicsDeviceData(const ContentDeviceData& aData);
static ContentChild* GetSingleton()
{

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

@ -2290,6 +2290,8 @@ ContentParent::InitInternal(ProcessPriority aInitialPriority,
SerializeURI(nullptr, xpcomInit.userContentSheetURL());
}
gfxPlatform::GetPlatform()->BuildContentDeviceData(&xpcomInit.contentDeviceData());
nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
if (gfxInfo) {
for (int32_t i = 1; i <= nsIGfxInfo::FEATURE_MAX_VALUE; ++i) {

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

@ -268,6 +268,7 @@ struct XPCOMInitData
FontFamilyListEntry[] fontFamilies;
OptionalURIParams userContentSheetURL;
PrefSetting[] prefs;
ContentDeviceData contentDeviceData;
GfxInfoFeatureStatus[] gfxFeatureStatus;
DataStorageEntry[] dataStorage;
nsCString[] appLocales;

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

@ -2243,20 +2243,36 @@ MediaCacheStream::SetPlaybackRate(uint32_t aBytesPerSecond)
}
nsresult
MediaCacheStream::SeekInternal(int64_t aOffset)
MediaCacheStream::Seek(int32_t aWhence, int64_t aOffset)
{
if (aOffset < 0) {
return NS_ERROR_FAILURE;
}
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
mMediaCache->GetReentrantMonitor().AssertCurrentThreadIn();
if (mClosed) {
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
if (mClosed)
return NS_ERROR_FAILURE;
}
int64_t oldOffset = mStreamOffset;
mStreamOffset = aOffset;
int64_t newOffset = mStreamOffset;
switch (aWhence) {
case PR_SEEK_END:
if (mStreamLength < 0)
return NS_ERROR_FAILURE;
newOffset = mStreamLength + aOffset;
break;
case PR_SEEK_CUR:
newOffset += aOffset;
break;
case PR_SEEK_SET:
newOffset = aOffset;
break;
default:
NS_ERROR("Unknown whence");
return NS_ERROR_FAILURE;
}
if (newOffset < 0)
return NS_ERROR_FAILURE;
mStreamOffset = newOffset;
LOG("Stream %p Seek to %" PRId64, this, mStreamOffset);
mMediaCache->NoteSeek(this, oldOffset);
@ -2285,10 +2301,11 @@ MediaCacheStream::Tell()
}
nsresult
MediaCacheStream::ReadInternal(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
{
mMediaCache->GetReentrantMonitor().AssertCurrentThreadIn();
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
if (mClosed)
return NS_ERROR_FAILURE;
@ -2357,7 +2374,7 @@ MediaCacheStream::ReadInternal(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
}
// No data has been read yet, so block
mMediaCache->GetReentrantMonitor().Wait();
mon.Wait();
if (mClosed) {
// We may have successfully read some data, but let's just throw
// that out.
@ -2402,9 +2419,9 @@ MediaCacheStream::ReadAt(int64_t aOffset, char* aBuffer,
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
nsresult rv = SeekInternal(aOffset);
nsresult rv = Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
if (NS_FAILED(rv)) return rv;
return ReadInternal(aBuffer, aCount, aBytes);
return Read(aBuffer, aCount, aBytes);
}
nsresult

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

@ -335,12 +335,17 @@ public:
// These methods must be called on a different thread from the main
// thread. They should always be called on the same thread for a given
// stream.
// This can fail when aWhence is NS_SEEK_END and no stream length
// is known.
nsresult Seek(int32_t aWhence, int64_t aOffset);
int64_t Tell();
// Seeks to aOffset in the stream then performs a Read operation.
// *aBytes gets the number of bytes that were actually read. This can
// be less than aCount. If the first byte of data is not in the cache,
// this will block until the data is available or the stream is
// closed, otherwise it won't block.
nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
// Seeks to aOffset in the stream then performs a Read operation. See
// 'Read' for argument and return details.
nsresult ReadAt(int64_t aOffset, char* aBuffer,
uint32_t aCount, uint32_t* aBytes);
@ -434,13 +439,6 @@ private:
// Update mPrincipal given that data has been received from aPrincipal
bool UpdatePrincipal(nsIPrincipal* aPrincipal);
nsresult SeekInternal(int64_t aOffset);
// *aBytes gets the number of bytes that were actually read. This can
// be less than aCount. If the first byte of data is not in the cache,
// this will block until the data is available or the stream is
// closed, otherwise it won't block.
nsresult ReadInternal(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
// Instance of MediaCache to use with this MediaCacheStream.
RefPtr<MediaCache> mMediaCache;

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

@ -9,6 +9,7 @@
#include "WidevineUtils.h"
#include "WidevineFileIO.h"
#include <stdarg.h>
#include "base/time.h"
using namespace cdm;
using namespace std;
@ -238,11 +239,7 @@ WidevineDecryptor::SetTimer(int64_t aDelayMs, void* aContext)
Time
WidevineDecryptor::GetCurrentWallTime()
{
GMPTimestamp gmpTime = 0;
GMPGetCurrentTime(&gmpTime);
double t = (double)gmpTime / 1e3;
CDM_LOG("Decryptor::GetCurrentWallTime()= %lf", t);
return t;
return base::Time::Now().ToDoubleT();
}
void

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

@ -29,3 +29,5 @@ LOCAL_INCLUDES += [
if CONFIG['CLANG_CXX']:
CXXFLAGS += ['-Wno-error=shadow']
include('/ipc/chromium/chromium-config.mozbuild')

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

@ -796,7 +796,7 @@ CamerasParent::RecvStartCapture(const CaptureEngine& aCapEngine,
new CallbackHelper(static_cast<CaptureEngine>(aCapEngine), capnum, self));
engine = self->mEngines[aCapEngine];
engine->WithEntry(capnum, [capnum, &engine, &error, &ipcCaps, &cbh](VideoEngine::CaptureEntry& cap) {
engine->WithEntry(capnum, [&engine, &error, &ipcCaps, &cbh](VideoEngine::CaptureEntry& cap) {
error = 0;
webrtc::VideoCaptureCapability capability;
capability.width = ipcCaps.width();
@ -841,7 +841,7 @@ CamerasParent::StopCapture(const CaptureEngine& aCapEngine,
const int& capnum)
{
if (auto engine = EnsureInitialized(aCapEngine)) {
engine->WithEntry(capnum,[capnum](VideoEngine::CaptureEntry& cap){
engine->WithEntry(capnum,[](VideoEngine::CaptureEntry& cap){
if (cap.VideoCapture()) {
cap.VideoCapture()->StopCapture();
cap.VideoCapture()->DeRegisterCaptureDataCallback();

4
dom/media/test/external/MANIFEST.in поставляемый
Просмотреть файл

@ -1,4 +0,0 @@
exclude MANIFEST.in
include requirements.txt
recursive-include external_media_harness *
recursive-include external_media_tests *

5
dom/media/test/external/README.md поставляемый
Просмотреть файл

@ -1,5 +0,0 @@
external-media-tests
===================
Documentation for this library has moved to https://developer.mozilla.org/en-US/docs/Mozilla/QA/external-media-tests.

3
dom/media/test/external/docs/.hgignore поставляемый
Просмотреть файл

@ -1,3 +0,0 @@
_build
_static
_templates

216
dom/media/test/external/docs/Makefile поставляемый
Просмотреть файл

@ -1,216 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
.PHONY: clean
clean:
rm -rf $(BUILDDIR)/*
.PHONY: html
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
.PHONY: dirhtml
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
.PHONY: singlehtml
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
.PHONY: pickle
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
.PHONY: json
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
.PHONY: qthelp
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ExternalMediaTests.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ExternalMediaTests.qhc"
.PHONY: applehelp
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
.PHONY: devhelp
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/ExternalMediaTests"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ExternalMediaTests"
@echo "# devhelp"
.PHONY: epub
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
.PHONY: latex
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
.PHONY: latexpdf
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: latexpdfja
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: text
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
.PHONY: man
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
.PHONY: texinfo
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
.PHONY: info
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
.PHONY: gettext
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
.PHONY: changes
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
.PHONY: linkcheck
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
.PHONY: doctest
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
.PHONY: coverage
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
.PHONY: xml
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
.PHONY: pseudoxml
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

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

@ -1,297 +0,0 @@
# -*- coding: utf-8 -*-
#
# External Media Tests documentation build configuration file, created by
# sphinx-quickstart on Tue Mar 15 15:58:18 2016.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.viewcode',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'External Media Tests'
copyright = u'2015-2016, Mozilla, Inc.'
author = u'Syd Polk and Maja Frydrychowicz'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = u'0.1'
# The full version, including alpha/beta/rc tags.
release = u'0.1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd:
try:
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
except ImportError:
pass
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (relative to this directory) to use as a favicon of
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
#html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
#html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'ExternalMediaTestsdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# Latex figure (float) alignment
#'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'ExternalMediaTests.tex', u'External Media Tests Documentation',
u'Syd Polk and Maja Frydrychowicz', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'externalmediatests', u'External Media Tests Documentation',
[author], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'ExternalMediaTests', u'External Media Tests Documentation',
author, 'ExternalMediaTests', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False

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

@ -1,19 +0,0 @@
external_media_harness package
==============================
Test case classes for use in video tests
external_media_harness.testcase module
--------------------------------------
.. autoclass:: external_media_harness.testcase.MediaTestCase
:members:
:show-inheritance:
.. autoclass:: external_media_harness.testcase.NetworkBandwidthTestCase
:members:
:show-inheritance:
.. autoclass:: external_media_harness.testcase.VideoPlaybackTestsMixin
:members:
:show-inheritance:

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

@ -1,27 +0,0 @@
VideoPuppeteer
==============
video_puppeteer.VideoPuppeteer
------------------------------
.. autoclass:: external_media_tests.media_utils.video_puppeteer.VideoPuppeteer
:members:
:show-inheritance:
video_puppeteer.VideoException
------------------------------
.. autoexception:: external_media_tests.media_utils.video_puppeteer.VideoException
video_puppeteer.playback_started
--------------------------------
.. autofunction:: external_media_tests.media_utils.video_puppeteer.playback_started
video_puppeteer.playback_done
-----------------------------
.. autofunction:: external_media_tests.media_utils.video_puppeteer.playback_done

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

@ -1,26 +0,0 @@
YoutubePuppeteer
================
youtube_puppeteer.YouTubePuppeteer
----------------------------------
.. autoclass:: external_media_tests.media_utils.youtube_puppeteer.YouTubePuppeteer
:members:
:show-inheritance:
youtube_puppeteer.playback_started
----------------------------------
.. autofunction:: external_media_tests.media_utils.youtube_puppeteer.playback_started
youtube_puppeteer.playback_done
-------------------------------
.. autofunction:: external_media_tests.media_utils.youtube_puppeteer.playback_done
youtbue_puppeteer.wait_for_almost_done
--------------------------------------
.. autofunction:: external_media_tests.media_utils.youtube_puppeteer.wait_for_almost_done

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

@ -1,26 +0,0 @@
external_media_tests package
============================
This document highlights the utility classes for tests. In general, the indvidiual tests are not documented here.
Test pacakges
-------------
.. toctree::
external_media_tests.media_tests.video_puppeteer
external_media_tests.media_tests.youtube_puppeteer
external_media_tests.utils.verbose_until
----------------------------------------
.. autofunction:: external_media_tests.utils.verbose_until
external_media_tests.utils.save_memory_report
---------------------------------------------
.. autofunction:: external_media_tests.utils.save_memory_report

33
dom/media/test/external/docs/index.rst поставляемый
Просмотреть файл

@ -1,33 +0,0 @@
.. py:currentmodule:: external_media_tests
External Media Tests
====================
External Media Tests is a library built on top of `Firefox Puppeter`_ and the `Marionette python client`_. It is designed to test playback of video elements embedded in web pages, independent of vendor. Using this library, you can write tests which play, pause, and stop videos, as well as inspect properties such as currentTime().
.. _Marionette python client: http://marionette-client.readthedocs.org/en/latest
.. _Firefox Puppeter: http://firefox-puppeteer.readthedocs.org/en/latest/
Installation
------------
External Media Tests lives in `External Media Tests Source`_. Documentation for installation and usage lives on `External Media Tests`_; this documentation is API documentation for the various pieces of the test library.
.. _External Media Tests Source: https://hg.mozilla.org/dom/media/test/external
.. _External Media Tests: https://developer.mozilla.org/en-US/docs/Mozilla/QA/external-media-tests
Contents
--------
.. toctree::
external_media_harness
external_media_tests
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

263
dom/media/test/external/docs/make.bat поставляемый
Просмотреть файл

@ -1,263 +0,0 @@
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. xml to make Docutils-native XML files
echo. pseudoxml to make pseudoxml-XML files for display purposes
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
echo. coverage to run coverage check of the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
REM Check if sphinx-build is available and fallback to Python version if any
%SPHINXBUILD% 1>NUL 2>NUL
if errorlevel 9009 goto sphinx_python
goto sphinx_ok
:sphinx_python
set SPHINXBUILD=python -m sphinx.__init__
%SPHINXBUILD% 2> nul
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
:sphinx_ok
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\ExternalMediaTests.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ExternalMediaTests.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdf" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdfja" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf-ja
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
if "%1" == "coverage" (
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
if errorlevel 1 exit /b 1
echo.
echo.Testing of coverage in the sources finished, look at the ^
results in %BUILDDIR%/coverage/python.txt.
goto end
)
if "%1" == "xml" (
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The XML files are in %BUILDDIR%/xml.
goto end
)
if "%1" == "pseudoxml" (
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
goto end
)
:end

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

@ -1,5 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from runtests import cli

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

@ -1,103 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os
import sys
import mozlog
from manifestparser import read_ini
from marionette_harness import (
BaseMarionetteTestRunner,
BaseMarionetteArguments,
BrowserMobProxyArguments,
)
from marionette_harness.runtests import MarionetteHarness, cli as mn_cli
import external_media_tests
from testcase import MediaTestCase
from external_media_tests.media_utils.video_puppeteer import debug_script
class MediaTestArgumentsBase(object):
name = 'Firefox Media Tests'
args = [
[['--urls'], {
'help': 'ini file of urls to make available to all tests',
'default': os.path.join(external_media_tests.urls, 'default.ini'),
}],
]
def verify_usage_handler(self, args):
if args.urls:
if not os.path.isfile(args.urls):
raise ValueError('--urls must provide a path to an ini file')
else:
path = os.path.abspath(args.urls)
args.video_urls = MediaTestArgumentsBase.get_urls(path)
if not args.video_urls:
raise ValueError('list of video URLs cannot be empty')
def parse_args_handler(self, args):
if not args.tests:
args.tests = [external_media_tests.manifest]
@staticmethod
def get_urls(manifest):
with open(manifest, 'r'):
return [line[0] for line in read_ini(manifest)]
class MediaTestArguments(BaseMarionetteArguments):
def __init__(self, **kwargs):
BaseMarionetteArguments.__init__(self, **kwargs)
self.register_argument_container(MediaTestArgumentsBase())
self.register_argument_container(BrowserMobProxyArguments())
class MediaTestRunner(BaseMarionetteTestRunner):
def __init__(self, **kwargs):
BaseMarionetteTestRunner.__init__(self, **kwargs)
if not self.server_root:
self.server_root = external_media_tests.resources
# pick up prefs from marionette_driver.geckoinstance.DesktopInstance
self.app = 'fxdesktop'
self.test_handlers = [MediaTestCase]
# Used in HTML report (--log-html)
def gather_media_debug(test, status):
rv = {}
marionette = test._marionette_weakref()
if marionette.session is not None:
try:
with marionette.using_context(marionette.CONTEXT_CHROME):
debug_lines = marionette.execute_script(debug_script)
if debug_lines:
name = 'mozMediaSourceObject.mozDebugReaderData'
rv[name] = '\n'.join(debug_lines)
else:
logger = mozlog.get_default_logger()
logger.info('No data available about '
'mozMediaSourceObject')
except:
logger = mozlog.get_default_logger()
logger.warning('Failed to gather test failure media debug',
exc_info=True)
return rv
self.result_callbacks.append(gather_media_debug)
class FirefoxMediaHarness(MarionetteHarness):
def parse_args(self, *args, **kwargs):
return MarionetteHarness.parse_args(self, {'mach': sys.stdout})
def cli():
mn_cli(MediaTestRunner, MediaTestArguments, FirefoxMediaHarness)
if __name__ == '__main__':
cli()

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

@ -1,335 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import re
import os
from marionette_driver import Wait
from marionette_driver.errors import TimeoutException
from marionette_harness import (
BrowserMobProxyTestCaseMixin,
MarionetteTestCase,
Marionette,
SkipTest,
)
from firefox_puppeteer import PuppeteerMixin
from external_media_tests.utils import (timestamp_now, verbose_until)
from external_media_tests.media_utils.video_puppeteer import (
VideoException,
VideoPuppeteer
)
class MediaTestCase(PuppeteerMixin, MarionetteTestCase):
"""
Necessary methods for MSE playback
:param video_urls: Urls you are going to play as part of the tests.
"""
def __init__(self, *args, **kwargs):
self.video_urls = kwargs.pop('video_urls', False)
super(MediaTestCase, self).__init__(*args, **kwargs)
def save_screenshot(self):
"""
Make a screenshot of the window that is currently playing the video
element.
"""
screenshot_dir = os.path.join(self.marionette.instance.workspace or '',
'screenshots')
filename = ''.join([self.id().replace(' ', '-'),
'_',
str(timestamp_now()),
'.png'])
path = os.path.join(screenshot_dir, filename)
if not os.path.exists(screenshot_dir):
os.makedirs(screenshot_dir)
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
img_data = self.marionette.screenshot()
with open(path, 'wb') as f:
f.write(img_data.decode('base64'))
self.logger.info('Screenshot saved in {}'.format(os.path.abspath(path)))
def log_video_debug_lines(self, video):
"""
Log the debugging information that Firefox provides for video elements.
"""
with self.marionette.using_context(Marionette.CONTEXT_CHROME):
debug_lines = video.get_debug_lines()
if debug_lines:
self.logger.info('\n'.join(debug_lines))
def run_playback(self, video):
"""
Play the video all of the way through, or for the requested duration,
whichever comes first. Raises if the video stalls for too long.
:param video: VideoPuppeteer instance to play.
"""
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
self.logger.info(video.test_url)
try:
verbose_until(Wait(video, interval=video.interval,
timeout=video.expected_duration * 1.3 +
video.stall_wait_time),
video, VideoPuppeteer.playback_done)
except VideoException as e:
raise self.failureException(e)
def check_playback_starts(self, video):
"""
Check to see if a given video will start. Raises if the video does not
start.
:param video: VideoPuppeteer instance to play.
"""
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
self.logger.info(video.test_url)
try:
verbose_until(Wait(video, timeout=video.timeout),
video, VideoPuppeteer.playback_started)
except TimeoutException as e:
raise self.failureException(e)
def skipTest(self, reason):
"""
Skip this test.
Skip with marionette.marionette_test import SkipTest so that it
gets recognized a skip in marionette.marionette_test.CommonTestCase.run
"""
raise SkipTest(reason)
class NetworkBandwidthTestCase(MediaTestCase):
"""
Test MSE playback while network bandwidth is limited. Uses browsermobproxy
(https://bmp.lightbody.net/). Please see
https://developer.mozilla.org/en-US/docs/Mozilla/QA/external-media-tests
for more information on setting up browsermob_proxy.
"""
def __init__(self, *args, **kwargs):
super(NetworkBandwidthTestCase, self).__init__(*args, **kwargs)
BrowserMobProxyTestCaseMixin.__init__(self, *args, **kwargs)
self.proxy = None
def setUp(self):
super(NetworkBandwidthTestCase, self).setUp()
BrowserMobProxyTestCaseMixin.setUp(self)
self.proxy = self.create_browsermob_proxy()
def tearDown(self):
super(NetworkBandwidthTestCase, self).tearDown()
BrowserMobProxyTestCaseMixin.tearDown(self)
self.proxy = None
def run_videos(self, timeout=60):
"""
Run each of the videos in the video list. Raises if something goes
wrong in playback.
"""
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
for url in self.video_urls:
video = VideoPuppeteer(self.marionette, url, stall_wait_time=60,
set_duration=60, timeout=timeout)
self.run_playback(video)
class VideoPlaybackTestsMixin(object):
"""
Test MSE playback in HTML5 video element.
These tests should pass on any site where a single video element plays
upon loading and is uninterrupted (by ads, for example).
This tests both starting videos and performing partial playback at one
minute each, and is the test that should be run frequently in automation.
"""
def test_playback_starts(self):
"""
Test to make sure that playback of the video element starts for each
video.
"""
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
for url in self.video_urls:
try:
video = VideoPuppeteer(self.marionette, url, timeout=60)
# Second playback_started check in case video._start_time
# is not 0
self.check_playback_starts(video)
video.pause()
except TimeoutException as e:
raise self.failureException(e)
def test_video_playback_partial(self):
"""
Test to make sure that playback of 60 seconds works for each video.
"""
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
for url in self.video_urls:
video = VideoPuppeteer(self.marionette, url,
stall_wait_time=10,
set_duration=60)
self.run_playback(video)
class NetworkBandwidthTestsMixin(object):
"""
Test video urls with various bandwidth settings.
"""
def test_playback_limiting_bandwidth_250(self):
self.proxy.limits({'downstream_kbps': 250})
self.run_videos(timeout=120)
def test_playback_limiting_bandwidth_500(self):
self.proxy.limits({'downstream_kbps': 500})
self.run_videos(timeout=120)
def test_playback_limiting_bandwidth_1000(self):
self.proxy.limits({'downstream_kbps': 1000})
self.run_videos(timeout=120)
reset_widevine_gmp_script = """
navigator.requestMediaKeySystemAccess('com.widevine.alpha',
[{initDataTypes: ['cenc']}]).then(
function(access) {
marionetteScriptFinished('success');
},
function(ex) {
marionetteScriptFinished(ex);
}
);
"""
class EMESetupMixin(object):
"""
An object that needs to use the Widevine GMP system must inherit
from this class, and then call check_eme_system() to insure that everything
is setup correctly.
"""
version_needs_reset = True
def check_eme_system(self):
"""
Download the most current version of the Widevine GMP
Plugins. Verify that all MSE and EME prefs are set correctly. Raises
if things are not OK.
"""
self.set_eme_prefs()
self.reset_GMP_version()
assert(self.check_eme_prefs())
def set_eme_prefs(self):
with self.marionette.using_context(Marionette.CONTEXT_CHROME):
# https://bugzilla.mozilla.org/show_bug.cgi?id=1187471#c28
# 2015-09-28 cpearce says this is no longer necessary, but in case
# we are working with older firefoxes...
self.marionette.set_pref('media.gmp.trial-create.enabled', False)
def reset_GMP_version(self):
if EMESetupMixin.version_needs_reset:
with self.marionette.using_context(Marionette.CONTEXT_CHROME):
if self.marionette.get_pref('media.gmp-widevinecdm.version'):
self.marionette.reset_pref('media.gmp-widevinecdm.version')
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
widevine_result = self.marionette.execute_async_script(
reset_widevine_gmp_script,
script_timeout=60000)
if not widevine_result == 'success':
raise VideoException(
'ERROR: Resetting Widevine GMP failed {}'
.format(widevine_result))
EMESetupMixin.version_needs_reset = False
def check_and_log_boolean_pref(self, pref_name, expected_value):
with self.marionette.using_context(Marionette.CONTEXT_CHROME):
pref_value = self.marionette.get_pref(pref_name)
if pref_value is None:
self.logger.info('Pref {} has no value.'.format(pref_name))
return False
else:
self.logger.info('Pref {} = {}'.format(pref_name, pref_value))
if pref_value != expected_value:
self.logger.info('Pref {} has unexpected value.'
.format(pref_name))
return False
return True
def check_and_log_integer_pref(self, pref_name, minimum_value=0):
with self.marionette.using_context(Marionette.CONTEXT_CHROME):
pref_value = self.marionette.get_pref(pref_name)
if pref_value is None:
self.logger.info('Pref {} has no value.'.format(pref_name))
return False
else:
self.logger.info('Pref {} = {}'.format(pref_name, pref_value))
match = re.search('^\d+$', pref_value)
if not match:
self.logger.info('Pref {} is not an integer'
.format(pref_name))
return False
return pref_value >= minimum_value
def chceck_and_log_version_string_pref(self, pref_name, minimum_value='0'):
"""
Compare a pref made up of integers separated by stops .s, with a
version string of the same format. The number of integers in each
string does not need to match. The comparison is done by converting
each to an integer array and comparing those. Both version strings
must be made up of only integers, or this method will raise an
unhandled exception of type ValueError when the conversion to int
fails.
"""
with self.marionette.using_context(Marionette.CONTEXT_CHROME):
pref_value = self.marionette.get_pref(pref_name)
if pref_value is None:
self.logger.info('Pref {} has no value.'.format(pref_name))
return False
else:
self.logger.info('Pref {} = {}'.format(pref_name, pref_value))
match = re.search('^\d(.\d+)*$', pref_value)
if not match:
self.logger.info('Pref {} is not a version string'
.format(pref_name))
return False
pref_ints = [int(n) for n in pref_value.split('.')]
minumum_ints = [int(n) for n in minimum_value.split('.')]
return pref_ints >= minumum_ints
def check_eme_prefs(self):
with self.marionette.using_context(Marionette.CONTEXT_CHROME):
return all([
self.check_and_log_boolean_pref(
'media.mediasource.enabled', True),
self.check_and_log_boolean_pref(
'media.eme.enabled', True),
self.check_and_log_boolean_pref(
'media.mediasource.mp4.enabled', True),
self.check_and_log_boolean_pref(
'media.gmp-widevinecdm.enabled', True),
self.chceck_and_log_version_string_pref(
'media.gmp-widevinecdm.version', '1.0.0.0')
])

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

@ -1,10 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os
root = os.path.abspath(os.path.dirname(__file__))
manifest = os.path.join(root, 'manifest.ini')
resources = os.path.join(root, 'resources')
urls = os.path.join(root, 'urls')

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

@ -1 +0,0 @@
[include:playback/manifest.ini]

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

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

@ -1,192 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from collections import namedtuple
from time import clock
from marionette_driver import By, expected, Wait
from marionette_harness import Marionette
from video_puppeteer import VideoPuppeteer, TimeRanges
from external_media_tests.utils import verbose_until
class TwitchPuppeteer(VideoPuppeteer):
"""
Wrapper around a Twitch .player element.
Note that the twitch stream needs to be playing for the puppeteer to work
correctly. Since twitch will load a player element for streams that are
not currently playing, the puppeteer will still detect a player element,
however the time ranges will not increase, and tests will fail.
Compared to video puppeteer, this class has the benefit of accessing the
twitch player element as well as the video element. The twitch player
element reports additional information to aid in testing -- such as if an
ad is playing. However, the attributes of this element do not appear to be
documented, which may leave them vulnerable to undocumented changes in
behaviour.
"""
_player_var_script = (
'var player_data_screen = '
'arguments[1].wrappedJSObject.getAttribute("data-screen");'
)
"""
A string containing JS that will assign player state to
variables. This is similar to `_video_var_script` from
`VideoPuppeteer`. See `_video_var_script` for more information on the
motivation for this method.
"""
def __init__(self, marionette, url, autostart=True,
set_duration=10.0, **kwargs):
self.player = None
self._last_seen_player_state = None
super(TwitchPuppeteer,
self).__init__(marionette, url, set_duration=set_duration,
autostart=False, **kwargs)
wait = Wait(self.marionette, timeout=30)
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
verbose_until(wait, self,
expected.element_present(By.CLASS_NAME,
'player'))
self.player = self.marionette.find_element(By.CLASS_NAME,
'player')
self.marionette.execute_script("log('.player "
"element obtained');")
if autostart:
self.start()
def _update_expected_duration(self):
if 0 < self._set_duration:
self.expected_duration = self._set_duration
else:
# If we don't have a set duration we don't know how long is left
# in the stream.
self.expected_duration = float("inf")
def _calculate_remaining_time(self, played_ranges):
"""
Override of video_puppeteer's _calculate_remaining_time. See that
method for primary documentation.
This override is in place to adjust how remaining time is handled.
Twitch ads can cause small stutters which result in multiple played
ranges, despite no seeks being initiated by the tests. As such, when
calculating the remaining time, the start time is the min of all
played start times, and the end time is the max of played end times.
This being sensible behaviour relies on the tests not attempting seeks.
:param played_ranges: A TimeRanges object containing played ranges.
:return: The remaining time expected for this puppeteer.
"""
min_played_time = min(
[played_ranges.start(i) for i in range(0, played_ranges.length)])
max_played_time = max(
[played_ranges.end(i) for i in range(0, played_ranges.length)])
played_duration = max_played_time - min_played_time
return self.expected_duration - played_duration
def _execute_twitch_script(self, script):
"""
Execute JS script in content context with access to video element and
Twitch .player element.
:param script: script to be executed.
:return: value returned by script
"""
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
return self.marionette.execute_script(script,
script_args=[self.video,
self.player])
@staticmethod
def _twitch_state_named_tuple():
"""
Create a named tuple class that can be used to store state snapshots
of the wrapped twitch player. The fields in the tuple should be used
as follows:
player_data_screen: the current displayed content, appears to be set
to nothing if no ad has been played, 'advertisement' during ad
playback, and 'content' following ad playback.
"""
return namedtuple('player_state_info',
['player_data_screen'])
def _create_player_state_info(self, **player_state_info_kwargs):
"""
Create an instance of the state info named tuple. This function
expects a dictionary containing the following keys:
player_data_screen.
For more information on the above keys and their values see
`_twitch_state_named_tuple`.
:return: A named tuple 'player_state_info', derived from arguments and
state information from the puppeteer.
"""
# Create player snapshot
state_info = self._twitch_state_named_tuple()
return state_info(**player_state_info_kwargs)
@property
def _fetch_state_script(self):
if not self._fetch_state_script_string:
self._fetch_state_script_string = (
self._video_var_script +
self._player_var_script +
'return ['
'baseURI,'
'currentTime,'
'duration,'
'[buffered.length, bufferedRanges],'
'[played.length, playedRanges],'
'totalFrames,'
'droppedFrames,'
'corruptedFrames,'
'player_data_screen];')
return self._fetch_state_script_string
def _refresh_state(self):
"""
Refresh the snapshot of the underlying video and player state. We do
this all in one so that the state doesn't change in between queries.
We also store information thouat can be derived from the snapshotted
information, such as lag. This is stored in the last seen state to
stress that it's based on the snapshot.
"""
values = self._execute_twitch_script(self._fetch_state_script)
video_keys = ['base_uri', 'current_time', 'duration',
'raw_buffered_ranges', 'raw_played_ranges',
'total_frames', 'dropped_frames', 'corrupted_frames']
player_keys = ['player_data_screen']
# Get video state
self._last_seen_video_state = (
self._create_video_state_info(**dict(
zip(video_keys, values[:len(video_keys)]))))
# Get player state
self._last_seen_player_state = (
self._create_player_state_info(**dict(
zip(player_keys, values[-len(player_keys):]))))
def __str__(self):
messages = [super(TwitchPuppeteer, self).__str__()]
if not self.player:
messages += ['\t.player: None']
return '\n'.join(messages)
if not self._last_seen_player_state:
messages += ['\t.player: No last seen state']
return '\n'.join(messages)
messages += ['.player: {']
for field in self._last_seen_player_state._fields:
# For compatibility with different test environments we force
# ascii
field_ascii = (
unicode(getattr(self._last_seen_player_state, field)).encode(
'ascii', 'replace'))
messages += [('\t{}: {}'.format(field, field_ascii))]
messages += '}'
return '\n'.join(messages)

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

@ -1,459 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from collections import namedtuple
from time import clock, sleep
from marionette_driver import By, expected, Wait
from marionette_harness import Marionette
from external_media_tests.utils import verbose_until
# Adapted from
# https://github.com/gavinsharp/aboutmedia/blob/master/chrome/content/aboutmedia.xhtml
debug_script = """
var mainWindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebNavigation)
.QueryInterface(Components.interfaces.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindow);
var tabbrowser = mainWindow.gBrowser;
for (var i=0; i < tabbrowser.browsers.length; ++i) {
var b = tabbrowser.getBrowserAtIndex(i);
var media = b.contentDocumentAsCPOW.getElementsByTagName('video');
for (var j=0; j < media.length; ++j) {
var ms = media[j].mozMediaSourceObject;
if (ms) {
debugLines = ms.mozDebugReaderData.split(\"\\n\");
return debugLines;
}
}
}"""
class VideoPuppeteer(object):
"""
Wrapper to control and introspect HTML5 video elements.
A note about properties like current_time and duration:
These describe whatever stream is playing when the state is checked.
It is possible that many different streams are dynamically spliced
together, so the video stream that is currently playing might be the main
video or it might be something else, like an ad, for example.
:param marionette: The marionette instance this runs in.
:param url: the URL of the page containing the video element.
:param video_selector: the selector of the element that we want to watch.
This is set by default to 'video', which is what most sites use, but
others should work.
:param interval: The polling interval that is used to check progress.
:param set_duration: When set to >0, the polling and checking will stop at
the number of seconds specified. Otherwise, this will stop at the end
of the video.
:param stall_wait_time: The amount of time to wait to see if a stall has
cleared. If 0, do not check for stalls.
:param timeout: The amount of time to wait until the video starts.
"""
_video_var_script = (
'var video = arguments[0];'
'var baseURI = video.baseURI;'
'var currentTime = video.wrappedJSObject.currentTime;'
'var duration = video.wrappedJSObject.duration;'
'var buffered = video.wrappedJSObject.buffered;'
'var bufferedRanges = [];'
'for (var i = 0; i < buffered.length; i++) {'
'bufferedRanges.push([buffered.start(i), buffered.end(i)]);'
'}'
'var played = video.wrappedJSObject.played;'
'var playedRanges = [];'
'for (var i = 0; i < played.length; i++) {'
'playedRanges.push([played.start(i), played.end(i)]);'
'}'
'var totalFrames = '
'video.getVideoPlaybackQuality()["totalVideoFrames"];'
'var droppedFrames = '
'video.getVideoPlaybackQuality()["droppedVideoFrames"];'
'var corruptedFrames = '
'video.getVideoPlaybackQuality()["corruptedVideoFrames"];'
)
"""
A string containing JS that assigns video state to variables.
The purpose of this string script is to be appended to by this and
any inheriting classes to return these and other variables. In the case
of an inheriting class the script can be added to in order to fetch
further relevant variables -- keep in mind we only want one script
execution to prevent races, so it wouldn't do to have child classes
run this script then their own, as there is potential for lag in
between.
This script assigns a subset of the vars used later by the
`_video_state_named_tuple` function. Please see that function's
documentation for further information on these variables.
"""
def __init__(self, marionette, url, video_selector='video', interval=1,
set_duration=0, stall_wait_time=0, timeout=60,
autostart=True):
self.marionette = marionette
self.test_url = url
self.interval = interval
self.stall_wait_time = stall_wait_time
self.timeout = timeout
self._set_duration = set_duration
self.video = None
self.expected_duration = 0
self._first_seen_time = 0
self._first_seen_wall_time = 0
self._fetch_state_script_string = None
self._last_seen_video_state = None
wait = Wait(self.marionette, timeout=self.timeout)
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
self.marionette.navigate(self.test_url)
print('URL: {}'.format(self.test_url))
verbose_until(wait, self,
expected.element_present(By.TAG_NAME, 'video'))
videos_found = self.marionette.find_elements(By.CSS_SELECTOR,
video_selector)
if len(videos_found) > 1:
print('{}: multiple video elements found. Using first.'.format(type(self).__name__))
if len(videos_found) <= 0:
print('{}: no video elements found.'.format(type(self).__name__))
return
self.video = videos_found[0]
print('video element obtained')
if autostart:
self.start()
def start(self):
# To get an accurate expected_duration, playback must have started
self._refresh_state()
wait = Wait(self, timeout=self.timeout)
verbose_until(wait, self, VideoPuppeteer.playback_started,
"Check if video has played some range")
self._first_seen_time = self._last_seen_video_state.current_time
self._first_seen_wall_time = clock()
self._update_expected_duration()
def get_debug_lines(self):
"""
Get Firefox internal debugging for the video element.
:return: A text string that has Firefox-internal debugging information.
"""
with self.marionette.using_context('chrome'):
debug_lines = self.marionette.execute_script(debug_script)
return debug_lines
def play(self):
"""
Tell the video element to Play.
"""
self._execute_video_script('arguments[0].wrappedJSObject.play();')
def pause(self):
"""
Tell the video element to Pause.
"""
self._execute_video_script('arguments[0].wrappedJSObject.pause();')
def playback_started(self):
"""
Determine if video has started
:param self: The VideoPuppeteer instance that we are interested in.
:return: True if is playing; False otherwise
"""
self._refresh_state()
try:
played_ranges = self._last_seen_video_state.played
return (
played_ranges.length > 0 and
played_ranges.start(0) < played_ranges.end(0) and
played_ranges.end(0) > 0.0
)
except Exception as e:
print ('Got exception {}'.format(e))
return False
def playback_done(self):
"""
If we are near the end and there is still a video element, then
we are essentially done. If this happens to be last time we are polled
before the video ends, we won't get another chance.
:param self: The VideoPuppeteer instance that we are interested in.
:return: True if we are close enough to the end of playback; False
otherwise.
"""
self._refresh_state()
if self._last_seen_video_state.remaining_time < self.interval:
return True
# Check to see if the video has stalled. Accumulate the amount of lag
# since the video started, and if it is too high, then raise.
if (self.stall_wait_time and
self._last_seen_video_state.lag > self.stall_wait_time):
raise VideoException('Video {} stalled.\n{}'
.format(self._last_seen_video_state.base_uri,
self))
# We are cruising, so we are not done.
return False
def _update_expected_duration(self):
"""
Update the duration of the target video at self.test_url (in seconds).
This is based on the last seen state, so the state should be,
refreshed at least once before this is called.
expected_duration represents the following: how long do we expect
playback to last before we consider the video to be 'complete'?
If we only want to play the first n seconds of the video,
expected_duration is set to n.
"""
# self._last_seen_video_state.duration is the duration of whatever was
# playing when the state was checked. In this case, we assume the video
# element always shows the same stream throughout playback (i.e. the
# are no ads spliced into the main video, for example), so
# self._last_seen_video_state.duration is the duration of the main
# video.
video_duration = self._last_seen_video_state.duration
# Do our best to figure out where the video started playing
played_ranges = self._last_seen_video_state.played
if played_ranges.length > 0:
# If we have a range we should only have on continuous range
assert played_ranges.length == 1
start_position = played_ranges.start(0)
else:
# If we don't have a range we should have a current time
start_position = self._first_seen_time
# In case video starts at t > 0, adjust target time partial playback
remaining_video = video_duration - start_position
if 0 < self._set_duration < remaining_video:
self.expected_duration = self._set_duration
else:
self.expected_duration = remaining_video
def _calculate_remaining_time(self, played_ranges):
"""
Calculate the remaining time expected for this puppeteer. Note that
this method accepts a played range rather than reading from the last
seen state. This is so when building a new state we are not forced to
read from the last one, and can use the played ranges associated with
that new state to calculate the remaining time.
:param played_ranges: A TimeRanges object containing played ranges.
For video_puppeteer we expect a single played range, but overrides may
expect different behaviour.
:return: The remaining time expected for this puppeteer.
"""
played_duration = played_ranges.end(0) - played_ranges.start(0)
return self.expected_duration - played_duration
@staticmethod
def _video_state_named_tuple():
"""
Create a named tuple class that can be used to store state snapshots
of the wrapped element. The fields in the tuple should be used as
follows:
base_uri: the baseURI attribute of the wrapped element.
current_time: the current time of the wrapped element.
duration: the duration of the wrapped element.
buffered: the buffered ranges of the wrapped element. In its raw form
this is as a list where the first element is the length and the second
element is a list of 2 item lists, where each two items are a buffered
range. Once assigned to the tuple this data should be wrapped in the
TimeRanges class.
played: the played ranges of the wrapped element. In its raw form this
is as a list where the first element is the length and the second
element is a list of 2 item lists, where each two items are a played
range. Once assigned to the tuple this data should be wrapped in the
TimeRanges class.
lag: the difference in real world time and wrapped element time.
Calculated as real world time passed - element time passed.
totalFrames: number of total frames for the wrapped element
droppedFrames: number of dropped frames for the wrapped element.
corruptedFrames: number of corrupted frames for the wrapped.
video_src: the src attribute of the wrapped element.
:return: A 'video_state_info' named tuple class.
"""
return namedtuple('video_state_info',
['base_uri',
'current_time',
'duration',
'remaining_time',
'buffered',
'played',
'lag',
'total_frames',
'dropped_frames',
'corrupted_frames',
'video_src'])
def _create_video_state_info(self, **video_state_info_kwargs):
"""
Create an instance of the video_state_info named tuple. This function
expects a dictionary populated with the following keys: current_time,
duration, raw_played_ranges, total_frames, dropped_frames, and
corrupted_frames.
Aside from raw_played_ranges, see `_video_state_named_tuple` for more
information on the above keys and values. For raw_played_ranges a
list is expected that can be consumed to make a TimeRanges object.
:return: A named tuple 'video_state_info' derived from arguments and
state information from the puppeteer.
"""
raw_buffered_ranges = video_state_info_kwargs['raw_buffered_ranges']
raw_played_ranges = video_state_info_kwargs['raw_played_ranges']
# Remove raw ranges from dict as they are not used in the final named
# tuple and will provide an unexpected kwarg if kept.
del video_state_info_kwargs['raw_buffered_ranges']
del video_state_info_kwargs['raw_played_ranges']
# Create buffered ranges
video_state_info_kwargs['buffered'] = (
TimeRanges(raw_buffered_ranges[0], raw_buffered_ranges[1]))
# Create played ranges
video_state_info_kwargs['played'] = (
TimeRanges(raw_played_ranges[0], raw_played_ranges[1]))
# Calculate elapsed times
elapsed_current_time = (video_state_info_kwargs['current_time'] -
self._first_seen_time)
elapsed_wall_time = clock() - self._first_seen_wall_time
# Calculate lag
video_state_info_kwargs['lag'] = (
elapsed_wall_time - elapsed_current_time)
# Calculate remaining time
played_ranages = video_state_info_kwargs['played']
if played_ranages.length > 0:
video_state_info_kwargs['remaining_time'] = (
self._calculate_remaining_time(played_ranages))
else:
# No playback has happened yet, remaining time is duration
video_state_info_kwargs['remaining_time'] = self.expected_duration
# Fetch non time critical source information
video_state_info_kwargs['video_src'] = self.video.get_attribute('src')
# Create video state snapshot
state_info = self._video_state_named_tuple()
return state_info(**video_state_info_kwargs)
@property
def _fetch_state_script(self):
if not self._fetch_state_script_string:
self._fetch_state_script_string = (
self._video_var_script +
'return ['
'baseURI,'
'currentTime,'
'duration,'
'[buffered.length, bufferedRanges],'
'[played.length, playedRanges],'
'totalFrames,'
'droppedFrames,'
'corruptedFrames];')
return self._fetch_state_script_string
def _refresh_state(self):
"""
Refresh the snapshot of the underlying video state. We do this all
in one so that the state doesn't change in between queries.
We also store information that can be derived from the snapshotted
information, such as lag. This is stored in the last seen state to
stress that it's based on the snapshot.
"""
keys = ['base_uri', 'current_time', 'duration', 'raw_buffered_ranges',
'raw_played_ranges', 'total_frames', 'dropped_frames',
'corrupted_frames']
values = self._execute_video_script(self._fetch_state_script)
self._last_seen_video_state = (
self._create_video_state_info(**dict(zip(keys, values))))
def _measure_progress(self):
self._refresh_state()
initial = self._last_seen_video_state.current_time
sleep(1)
self._refresh_state()
return self._last_seen_video_state.current_time - initial
def _execute_video_script(self, script):
"""
Execute JS script in content context with access to video element.
:param script: script to be executed
:return: value returned by script
"""
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
return self.marionette.execute_script(script,
script_args=[self.video])
def __str__(self):
messages = ['{} - test url: {}: '
.format(type(self).__name__, self.test_url)]
if not self.video:
messages += ['\tvideo: None']
return '\n'.join(messages)
if not self._last_seen_video_state:
messages += ['\tvideo: No last seen state']
return '\n'.join(messages)
# Have video and state info
messages += [
'{',
'\t(video)'
]
messages += ['\tinterval: {}'.format(self.interval)]
messages += ['\texpected duration: {}'.format(self.expected_duration)]
messages += ['\tstall wait time: {}'.format(self.stall_wait_time)]
messages += ['\ttimeout: {}'.format(self.timeout)]
# Print each field on its own line
for field in self._last_seen_video_state._fields:
# For compatibility with different test environments we force ascii
field_ascii = (
unicode(getattr(self._last_seen_video_state, field))
.encode('ascii','replace'))
messages += [('\t{}: {}'.format(field, field_ascii))]
messages += '}'
return '\n'.join(messages)
class VideoException(Exception):
"""
Exception class to use for video-specific error processing.
"""
pass
class TimeRanges:
"""
Class to represent the TimeRanges data returned by played(). Exposes a
similar interface to the JavaScript TimeRanges object.
"""
def __init__(self, length, ranges):
# These should be the same,. Theoretically we don't need the length,
# but since this should be used to consume data coming back from
# JS exec, this is a valid sanity check.
assert length == len(ranges)
self.length = length
self.ranges = [(pair[0], pair[1]) for pair in ranges]
def __repr__(self):
return (
'TimeRanges: length: {}, ranges: {}'
.format(self.length, self.ranges)
)
def start(self, index):
return self.ranges[index][0]
def end(self, index):
return self.ranges[index][1]

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

@ -1,490 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import re
from collections import namedtuple
from json import loads
from time import sleep
from marionette_driver import By, expected, Wait
from marionette_driver.errors import TimeoutException, NoSuchElementException
from marionette_harness import Marionette
from video_puppeteer import VideoPuppeteer, VideoException
from external_media_tests.utils import verbose_until
class YouTubePuppeteer(VideoPuppeteer):
"""
Wrapper around a YouTube .html5-video-player element.
Can be used with youtube videos or youtube videos at embedded URLS. E.g.
both https://www.youtube.com/watch?v=AbAACm1IQE0 and
https://www.youtube.com/embed/AbAACm1IQE0 should work.
Using an embedded video has the advantage of not auto-playing more videos
while a test is running.
Compared to video puppeteer, this class has the benefit of accessing the
youtube player object as well as the video element. The YT player will
report information for the underlying video even if an add is playing (as
opposed to the video element behaviour, which will report on whatever
is play at the time of query), and can also report if an ad is playing.
Partial reference: https://developers.google.com/youtube/iframe_api_reference.
This reference is useful for site-specific features such as interacting
with ads, or accessing YouTube's debug data.
"""
_player_var_script = (
'var player_duration = arguments[1].wrappedJSObject.getDuration();'
'var player_current_time = '
'arguments[1].wrappedJSObject.getCurrentTime();'
'var player_playback_quality = '
'arguments[1].wrappedJSObject.getPlaybackQuality();'
'var player_movie_id = '
'arguments[1].wrappedJSObject.getVideoData()["video_id"];'
'var player_movie_title = '
'arguments[1].wrappedJSObject.getVideoData()["title"];'
'var player_url = '
'arguments[1].wrappedJSObject.getVideoUrl();'
'var player_state = '
'arguments[1].wrappedJSObject.getPlayerState();'
'var player_ad_state = arguments[1].wrappedJSObject.getAdState();'
'var player_breaks_count = '
'arguments[1].wrappedJSObject.getOption("ad", "breakscount");'
)
"""
A string containing JS that will assign player state to
variables. This is similar to `_video_var_script` from
`VideoPuppeteer`. See `_video_var_script` for more information on the
motivation for this method.
This script assigns a subset of the vars used later by the
`_yt_state_named_tuple` function. Please see that functions
documentation for further information on these variables.
"""
_yt_player_state = {
'UNSTARTED': -1,
'ENDED': 0,
'PLAYING': 1,
'PAUSED': 2,
'BUFFERING': 3,
'CUED': 5
}
_yt_player_state_name = {v: k for k, v in _yt_player_state.items()}
_time_pattern = re.compile('(?P<minute>\d+):(?P<second>\d+)')
def __init__(self, marionette, url, autostart=True, **kwargs):
self.player = None
self._last_seen_player_state = None
super(YouTubePuppeteer,
self).__init__(marionette, url,
video_selector='.html5-video-player video',
autostart=False,
**kwargs)
wait = Wait(self.marionette, timeout=30)
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
verbose_until(wait, self,
expected.element_present(By.CLASS_NAME,
'html5-video-player'))
self.player = self.marionette.find_element(By.CLASS_NAME,
'html5-video-player')
print '.html5-video-player element obtained'
# When an ad is playing, self.player_duration indicates the duration
# of the spliced-in ad stream, not the duration of the main video, so
# we attempt to skip the ad first.
for attempt in range(5):
sleep(1)
self.process_ad()
if (self._last_seen_player_state.player_ad_inactive and
self._last_seen_video_state.duration and not
self._last_seen_player_state.player_buffering):
break
self._update_expected_duration()
if autostart:
self.start()
def player_play(self):
"""
Play via YouTube API.
"""
self._execute_yt_script('arguments[1].wrappedJSObject.playVideo();')
def player_pause(self):
"""
Pause via YouTube API.
"""
self._execute_yt_script('arguments[1].wrappedJSObject.pauseVideo();')
def _player_measure_progress(self):
"""
Determine player progress. Refreshes state.
:return: Playback progress in seconds via YouTube API with snapshots.
"""
self._refresh_state()
initial = self._last_seen_player_state.player_current_time
sleep(1)
self._refresh_state()
return self._last_seen_player_state.player_current_time - initial
def _get_player_debug_dict(self):
text = self._execute_yt_script('return arguments[1].'
'wrappedJSObject.getDebugText();')
if text:
try:
return loads(text)
except ValueError:
print 'Error loading JSON: DebugText'
def _execute_yt_script(self, script):
"""
Execute JS script in content context with access to video element and
YouTube .html5-video-player element.
:param script: script to be executed.
:return: value returned by script
"""
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
return self.marionette.execute_script(script,
script_args=[self.video,
self.player])
def _check_if_ad_ended(self):
self._refresh_state()
return self._last_seen_player_state.player_ad_ended
def process_ad(self):
"""
Wait for this ad to finish. Refreshes state.
"""
self._refresh_state()
if self._last_seen_player_state.player_ad_inactive:
return
ad_timeout = (self._search_ad_duration() or 30) + 5
wait = Wait(self, timeout=ad_timeout, interval=1)
try:
print('process_ad: waiting {}s for ad'.format(ad_timeout))
verbose_until(wait,
self,
YouTubePuppeteer._check_if_ad_ended,
"Check if ad ended")
except TimeoutException:
print('Waiting for ad to end timed out')
def _search_ad_duration(self):
"""
Try and determine ad duration. Refreshes state.
:return: ad duration in seconds, if currently displayed in player
"""
self._refresh_state()
if not (self._last_seen_player_state.player_ad_playing or
self._player_measure_progress() == 0):
return None
if (self._last_seen_player_state.player_ad_playing and
self._last_seen_video_state.duration):
return self._last_seen_video_state.duration
selector = '.html5-video-player .videoAdUiAttribution'
wait = Wait(self.marionette, timeout=5)
try:
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
wait.until(expected.element_present(By.CSS_SELECTOR,
selector))
countdown = self.marionette.find_element(By.CSS_SELECTOR,
selector)
ad_time = self._time_pattern.search(countdown.text)
if ad_time:
ad_minutes = int(ad_time.group('minute'))
ad_seconds = int(ad_time.group('second'))
return 60 * ad_minutes + ad_seconds
except (TimeoutException, NoSuchElementException):
print('Could not obtain element {}'.format(selector))
return None
def _player_stalled(self):
"""
Checks if the player has stalled. Refreshes state.
:return: True if playback is not making progress for 4-9 seconds. This
excludes ad breaks. Note that the player might just be busy with
buffering due to a slow network.
"""
# `current_time` stands still while ad is playing
def condition():
# no ad is playing and current_time stands still
return (not self._last_seen_player_state.player_ad_playing and
self._measure_progress() < 0.1 and
self._player_measure_progress() < 0.1 and
(self._last_seen_player_state.player_playing or
self._last_seen_player_state.player_buffering))
if condition():
sleep(2)
self._refresh_state()
if self._last_seen_player_state.player_buffering:
sleep(5)
self._refresh_state()
return condition()
else:
return False
@staticmethod
def _yt_state_named_tuple():
"""
Create a named tuple class that can be used to store state snapshots
of the wrapped youtube player. The fields in the tuple should be used
as follows:
player_duration: the duration as fetched from the wrapped player.
player_current_time: the current playback time as fetched from the
wrapped player.
player_remaining_time: the remaining time as calculated based on the
puppeteers expected time and the players current time.
player_playback_quality: the playback quality as fetched from the
wrapped player. See:
https://developers.google.com/youtube/js_api_reference#Playback_quality
player_movie_id: the movie id fetched from the wrapped player.
player_movie_title: the title fetched from the wrapped player.
player_url: the self reported url fetched from the wrapped player.
player_state: the current state of playback as fetch from the wrapped
player. See:
https://developers.google.com/youtube/js_api_reference#Playback_status
player_unstarted, player_ended, player_playing, player_paused,
player_buffering, and player_cued: these are all shortcuts to the
player state, only one should be true at any time.
player_ad_state: as player_state, but reports for the current ad.
player_ad_state, player_ad_inactive, player_ad_playing, and
player_ad_ended: these are all shortcuts to the ad state, only one
should be true at any time.
player_breaks_count: the number of ads as fetched from the wrapped
player. This includes both played and unplayed ads, and includes
streaming ads as well as pop up ads.
:return: A 'player_state_info' named tuple class.
"""
return namedtuple('player_state_info',
['player_duration',
'player_current_time',
'player_remaining_time',
'player_playback_quality',
'player_movie_id',
'player_movie_title',
'player_url',
'player_state',
'player_unstarted',
'player_ended',
'player_playing',
'player_paused',
'player_buffering',
'player_cued',
'player_ad_state',
'player_ad_inactive',
'player_ad_playing',
'player_ad_ended',
'player_breaks_count'
])
def _create_player_state_info(self, **player_state_info_kwargs):
"""
Create an instance of the state info named tuple. This function
expects a dictionary containing the following keys:
player_duration, player_current_time, player_playback_quality,
player_movie_id, player_movie_title, player_url, player_state,
player_ad_state, and player_breaks_count.
For more information on the above keys and their values see
`_yt_state_named_tuple`.
:return: A named tuple 'player_state_info', derived from arguments and
state information from the puppeteer.
"""
player_state_info_kwargs['player_remaining_time'] = (
self.expected_duration -
player_state_info_kwargs['player_current_time'])
# Calculate player state convenience info
player_state = player_state_info_kwargs['player_state']
player_state_info_kwargs['player_unstarted'] = (
player_state == self._yt_player_state['UNSTARTED'])
player_state_info_kwargs['player_ended'] = (
player_state == self._yt_player_state['ENDED'])
player_state_info_kwargs['player_playing'] = (
player_state == self._yt_player_state['PLAYING'])
player_state_info_kwargs['player_paused'] = (
player_state == self._yt_player_state['PAUSED'])
player_state_info_kwargs['player_buffering'] = (
player_state == self._yt_player_state['BUFFERING'])
player_state_info_kwargs['player_cued'] = (
player_state == self._yt_player_state['CUED'])
# Calculate ad state convenience info
player_ad_state = player_state_info_kwargs['player_ad_state']
player_state_info_kwargs['player_ad_inactive'] = (
player_ad_state == self._yt_player_state['UNSTARTED'])
player_state_info_kwargs['player_ad_playing'] = (
player_ad_state == self._yt_player_state['PLAYING'])
player_state_info_kwargs['player_ad_ended'] = (
player_ad_state == self._yt_player_state['ENDED'])
# Create player snapshot
state_info = self._yt_state_named_tuple()
return state_info(**player_state_info_kwargs)
@property
def _fetch_state_script(self):
if not self._fetch_state_script_string:
self._fetch_state_script_string = (
self._video_var_script +
self._player_var_script +
'return ['
'baseURI,'
'currentTime,'
'duration,'
'[buffered.length, bufferedRanges],'
'[played.length, playedRanges],'
'totalFrames,'
'droppedFrames,'
'corruptedFrames,'
'player_duration,'
'player_current_time,'
'player_playback_quality,'
'player_movie_id,'
'player_movie_title,'
'player_url,'
'player_state,'
'player_ad_state,'
'player_breaks_count];')
return self._fetch_state_script_string
def _refresh_state(self):
"""
Refresh the snapshot of the underlying video and player state. We do
this all in one so that the state doesn't change in between queries.
We also store information that can be derived from the snapshotted
information, such as lag. This is stored in the last seen state to
stress that it's based on the snapshot.
"""
values = self._execute_yt_script(self._fetch_state_script)
video_keys = ['base_uri', 'current_time', 'duration',
'raw_buffered_ranges', 'raw_played_ranges',
'total_frames', 'dropped_frames', 'corrupted_frames']
player_keys = ['player_duration', 'player_current_time',
'player_playback_quality', 'player_movie_id',
'player_movie_title', 'player_url', 'player_state',
'player_ad_state', 'player_breaks_count']
# Get video state
self._last_seen_video_state = (
self._create_video_state_info(**dict(
zip(video_keys, values[:len(video_keys)]))))
# Get player state
self._last_seen_player_state = (
self._create_player_state_info(**dict(
zip(player_keys, values[-len(player_keys):]))))
def mse_enabled(self):
"""
Check if the video source indicates mse usage for current video.
Refreshes state.
:return: True if MSE is being used, False if not.
"""
self._refresh_state()
return self._last_seen_video_state.video_src.startswith('blob')
def playback_started(self):
"""
Check whether playback has started. Refreshes state.
:return: True if play back has started, False if not.
"""
self._refresh_state()
# usually, ad is playing during initial buffering
if (self._last_seen_player_state.player_playing or
self._last_seen_player_state.player_buffering):
return True
if (self._last_seen_video_state.current_time > 0 or
self._last_seen_player_state.player_current_time > 0):
return True
return False
def playback_done(self):
"""
Check whether playback is done. Refreshes state.
:return: True if play back has ended, False if not.
"""
# in case ad plays at end of video
self._refresh_state()
if self._last_seen_player_state.player_ad_playing:
return False
return (self._last_seen_player_state.player_ended or
self._last_seen_player_state.player_remaining_time < 1)
def wait_for_almost_done(self, final_piece=120):
"""
Allow the given video to play until only `final_piece` seconds remain,
skipping ads mid-way as much as possible.
`final_piece` should be short enough to not be interrupted by an ad.
Depending on the length of the video, check the ad status every 10-30
seconds, skip an active ad if possible.
This call refreshes state.
:param final_piece: The length in seconds of the desired remaining time
to wait until.
"""
self._refresh_state()
rest = 10
duration = remaining_time = self.expected_duration
if duration < final_piece:
# video is short so don't attempt to skip more ads
return duration
elif duration > 600:
# for videos that are longer than 10 minutes
# wait longer between checks
rest = duration / 50
while remaining_time > final_piece:
if self._player_stalled():
if self._last_seen_player_state.player_buffering:
# fall back on timeout in 'wait' call that comes after this
# in test function
print('Buffering and no playback progress')
break
else:
message = '\n'.join(['Playback stalled', str(self)])
raise VideoException(message)
if self._last_seen_player_state.player_breaks_count > 0:
self.process_ad()
if remaining_time > 1.5 * rest:
sleep(rest)
else:
sleep(rest / 2)
# TODO during an ad, remaining_time will be based on ad's
# current_time rather than current_time of target video
remaining_time = self._last_seen_player_state.player_remaining_time
return remaining_time
def __str__(self):
messages = [super(YouTubePuppeteer, self).__str__()]
if not self.player:
messages += ['\t.html5-media-player: None']
return '\n'.join(messages)
if not self._last_seen_player_state:
messages += ['\t.html5-media-player: No last seen state']
return '\n'.join(messages)
messages += ['.html5-media-player: {']
for field in self._last_seen_player_state._fields:
# For compatibility with different test environments we force ascii
field_ascii = (
unicode(getattr(self._last_seen_player_state, field)).encode(
'ascii', 'replace'))
messages += [('\t{}: {}'.format(field, field_ascii))]
messages += '}'
return '\n'.join(messages)

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

@ -1 +0,0 @@
[test_eme_playback.py]

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

@ -1,2 +0,0 @@
[test_playback_limiting_bandwidth.py]
[test_ultra_low_bandwidth.py]

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

@ -1 +0,0 @@
[test_video_playback.py]

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

@ -1 +0,0 @@
[test_eme_playback_limiting_bandwidth.py]

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

@ -1,18 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from external_media_harness.testcase import (
MediaTestCase,
VideoPlaybackTestsMixin,
EMESetupMixin
)
class TestEMEPlayback(MediaTestCase, VideoPlaybackTestsMixin, EMESetupMixin):
def setUp(self):
super(TestEMEPlayback, self).setUp()
self.check_eme_system()
# Tests are implemented in VideoPlaybackTestsMixin

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

@ -1,24 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette_harness import BrowserMobProxyTestCaseMixin
from external_media_harness.testcase import (
EMESetupMixin,
NetworkBandwidthTestCase,
NetworkBandwidthTestsMixin,
)
class TestEMEPlaybackLimitingBandwidth(NetworkBandwidthTestCase,
BrowserMobProxyTestCaseMixin,
NetworkBandwidthTestsMixin,
EMESetupMixin):
def setUp(self):
super(TestEMEPlaybackLimitingBandwidth, self).setUp()
self.check_eme_system()
# Tests in NetworkBandwidthTestsMixin

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

@ -1,25 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette_harness import Marionette
from external_media_harness.testcase import MediaTestCase
from external_media_tests.media_utils.video_puppeteer import VideoPuppeteer
class TestFullPlayback(MediaTestCase):
""" Test MSE playback in HTML5 video element.
These tests should pass on any site where a single video element plays
upon loading and is uninterrupted (by ads, for example). This will play
the full videos, so it could take a while depending on the videos playing.
It should be run much less frequently in automated systems.
"""
def test_video_playback_full(self):
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
for url in self.video_urls:
video = VideoPuppeteer(self.marionette, url,
stall_wait_time=10)
self.run_playback(video)

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

@ -1,17 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette_harness import BrowserMobProxyTestCaseMixin
from external_media_harness.testcase import (
NetworkBandwidthTestCase, NetworkBandwidthTestsMixin
)
class TestPlaybackLimitingBandwidth(NetworkBandwidthTestCase,
NetworkBandwidthTestsMixin,
BrowserMobProxyTestCaseMixin):
# Tests are in NetworkBandwidthTestsMixin
pass

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

@ -1,42 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette_harness import Marionette
from external_media_harness.testcase import MediaTestCase
from external_media_tests.media_utils.video_puppeteer import VideoPuppeteer
class TestShakaPlayback(MediaTestCase):
""" Test Widevine playback in shaka-player
This test takes manifest URLs rather than URLs for pages with videos. These
manifests are loaded with shaka-player
"""
def test_video_playback_partial(self):
""" Plays 60 seconds of the video from the manifest URLs given
"""
shakaUrl = "http://shaka-player-demo.appspot.com"
self.marionette.set_pref('media.mediasource.webm.enabled', True)
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
for manifestUrl in self.video_urls:
vp = VideoPuppeteer(self.marionette,
shakaUrl,
stall_wait_time=10,
set_duration=60,
video_selector="video#video",
autostart=False)
manifestInput = self.marionette.find_element("id",
"manifestUrlInput")
manifestInput.clear()
manifestInput.send_keys(manifestUrl)
loadButton = self.marionette.find_element("id", "loadButton")
loadButton.click()
vp.start()
self.run_playback(vp)

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

@ -1,15 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette_harness import BrowserMobProxyTestCaseMixin
from external_media_harness.testcase import NetworkBandwidthTestCase
class TestUltraLowBandwidth(NetworkBandwidthTestCase,
BrowserMobProxyTestCaseMixin):
def test_playback_limiting_bandwidth_160(self):
self.proxy.limits({'downstream_kbps': 160})
self.run_videos(timeout=120)

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

@ -1,15 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from external_media_harness.testcase import (
MediaTestCase,
VideoPlaybackTestsMixin
)
class TestVideoPlayback(MediaTestCase, VideoPlaybackTestsMixin):
# Tests are actually implemented in VideoPlaybackTestsMixin.
pass

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

@ -1 +0,0 @@
[test_basic_stream_playback.py]

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

@ -1,30 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette_driver.errors import TimeoutException
from marionette_harness import Marionette
from external_media_harness.testcase import MediaTestCase
from external_media_tests.media_utils.twitch_puppeteer import TwitchPuppeteer
class TestBasicStreamPlayback(MediaTestCase):
def test_video_playback_partial(self):
"""
Test to make sure that playback of 60 seconds works for each video.
"""
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
for url in self.video_urls:
stream = TwitchPuppeteer(self.marionette, url,
stall_wait_time=10,
set_duration=60)
self.run_playback(stream)
def test_playback_starts(self):
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
for url in self.video_urls:
try:
TwitchPuppeteer(self.marionette, url, timeout=60)
except TimeoutException as e:
raise self.failureException(e)

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

@ -1 +0,0 @@
[test_basic_playback.py ]

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

@ -1,72 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette_driver import Wait
from marionette_driver.errors import TimeoutException
from marionette_harness import Marionette
from external_media_tests.utils import verbose_until
from external_media_harness.testcase import MediaTestCase
from external_media_tests.media_utils.video_puppeteer import VideoException
from external_media_tests.media_utils.youtube_puppeteer import YouTubePuppeteer
class TestBasicYouTubePlayback(MediaTestCase):
def test_mse_is_enabled_by_default(self):
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
youtube = YouTubePuppeteer(self.marionette, self.video_urls[0],
timeout=60)
wait = Wait(youtube,
timeout=min(300, youtube.expected_duration * 1.3),
interval=1)
try:
verbose_until(wait, youtube,
YouTubePuppeteer.mse_enabled,
"Failed to find 'blob' in video src url.")
except TimeoutException as e:
raise self.failureException(e)
def test_video_playing_in_one_tab(self):
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
for url in self.video_urls:
self.logger.info(url)
youtube = YouTubePuppeteer(self.marionette, url)
self.logger.info('Expected duration: {}'
.format(youtube.expected_duration))
final_piece = 60
try:
time_left = youtube.wait_for_almost_done(
final_piece=final_piece)
except VideoException as e:
raise self.failureException(e)
duration = abs(youtube.expected_duration) + 1
if duration > 1:
self.logger.info('Almost done: {} - {} seconds left.'
.format(url, time_left))
if time_left > final_piece:
self.logger.warn('time_left greater than '
'final_piece - {}'
.format(time_left))
self.save_screenshot()
else:
self.logger.warn('Duration close to 0 - {}'
.format(youtube))
self.save_screenshot()
try:
verbose_until(Wait(youtube,
timeout=max(100, time_left) * 1.3,
interval=1),
youtube,
YouTubePuppeteer.playback_done)
except TimeoutException as e:
raise self.failureException(e)
def test_playback_starts(self):
with self.marionette.using_context(Marionette.CONTEXT_CONTENT):
for url in self.video_urls:
try:
YouTubePuppeteer(self.marionette, url, timeout=60)
except TimeoutException as e:
raise self.failureException(e)

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

@ -1,46 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from external_media_harness.testcase import MediaTestCase
from marionette_driver import Wait
from external_media_tests.utils import verbose_until
from external_media_tests.media_utils.youtube_puppeteer import YouTubePuppeteer
class TestMediaSourcePrefs(MediaTestCase):
def setUp(self):
MediaTestCase.setUp(self)
self.test_urls = self.video_urls[:2]
self.max_timeout = 60
def tearDown(self):
MediaTestCase.tearDown(self)
def test_mse_prefs(self):
""" mediasource should only be used if MSE prefs are enabled."""
self.set_mse_enabled_prefs(False)
self.check_mse_src(False, self.test_urls[0])
self.set_mse_enabled_prefs(True)
self.check_mse_src(True, self.test_urls[0])
def set_mse_enabled_prefs(self, value):
with self.marionette.using_context('chrome'):
self.marionette.set_pref('media.mediasource.enabled', value)
self.marionette.set_pref('media.mediasource.mp4.enabled', value)
self.marionette.set_pref('media.mediasource.webm.enabled', value)
def check_mse_src(self, mse_expected, url):
with self.marionette.using_context('content'):
youtube = YouTubePuppeteer(self.marionette, url)
wait = Wait(youtube,
timeout=min(self.max_timeout,
youtube.expected_duration * 1.3),
interval=1)
def cond(y):
return y.mse_enabled == mse_expected
verbose_until(wait, youtube, cond)

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

@ -1,45 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<title>Mozilla</title>
<link rel="shortcut icon" type="image/ico" href="../images/mozilla_favicon.ico" />
</head>
<body>
<a href="mozilla.html">
<img id="mozilla_logo" src="../images/mozilla_logo.jpg" />
</a>
<a href="#community">RARARARARARA</a> |
<a href="#project">Project</a> |
<a href="#organization">Organization</a>
<div id="content">
<h1 id="page-title">
<strong>RARARARARARA</strong> that the internet should be public,
open and accessible.
</h1>
<h2><a name="community">RARARARARARA</a></h2>
<p id="community">
We're a global community of thousands who believe in the power
of technology to enrich people's lives.
<a href="mozilla_community.html">More</a>
</p>
<h2><a name="project">Project</a></h2>
<p id="project">
We're an open source project whose code is used for some of the
Internet's most innovative applications.
<a href="mozilla_projects.html">More</a>
</p>
<h2><a name="organization">Organization</a></h2>
<p id="organization">
We're a public benefit organization dedicated to making the
Internet better for everyone.
<a href="mozilla_mission.html">More</a>
</p>
</div>
</body>
</html>

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