Merge autoland to mozilla-central r=merge a=merge

This commit is contained in:
Margareta Eliza Balazs 2017-11-27 23:55:26 +02:00
Родитель a04e49663b 399a7f38c1
Коммит 5e413fc090
199 изменённых файлов: 3052 добавлений и 1296 удалений

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

@ -65,7 +65,7 @@ tasks:
- "tc-treeherder.v2.${repository.project}.${push.revision}.${push.pushlog_id}"
- "tc-treeherder-stage.v2.${repository.project}.${push.revision}.${push.pushlog_id}"
- $if: 'tasks_for == "action"'
then: "index.gecko.v2.${repository.project}.pushlog-id.${push.pushlog_id}.actions.$ownTaskId"
then: "index.gecko.v2.${repository.project}.pushlog-id.${push.pushlog_id}.actions.${ownTaskId}"
else: "index.gecko.v2.${repository.project}.latest.firefox.decision-${cron.job_name}"
scopes:

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

@ -1674,7 +1674,7 @@ pref("print.use_simplify_page", true);
// Space separated list of URLS that are allowed to send objects (instead of
// only strings) through webchannels. This list is duplicated in mobile/android/app/mobile.js
pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");
pref("webchannel.allowObject.urlWhitelist", "https://content.cdn.mozilla.net https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");
// Whether or not the browser should scan for unsubmitted
// crash reports, and then show a notification for submitting

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

@ -5863,22 +5863,14 @@
<method name="_updateNewTabVisibility">
<body><![CDATA[
let isCustomizing = this.tabContainer.parentNode.getAttribute("customizing") == "true";
// Helper functions to help deal with customize mode wrapping some items
let wrap = n => n.parentNode.localName == "toolbarpaletteitem" ? n.parentNode : n;
let unwrap = n => n && n.localName == "toolbarpaletteitem" ? n.firstElementChild : n;
// Confusingly, the <tabs> are never wrapped in <toolbarpaletteitem>s in customize mode,
// but the other items will be.
let sib = this.tabContainer.nextElementSibling;
if (isCustomizing) {
sib = sib && sib.firstElementChild;
}
while (sib && sib.hidden) {
if (isCustomizing) {
sib = sib.parentNode.nextElementSibling;
sib = sib && sib.firstElementChild;
} else {
sib = sib.nextElementSibling;
}
}
let sib = this.tabContainer;
do {
sib = unwrap(wrap(sib).nextElementSibling);
} while (sib && sib.hidden);
const kAttr = "hasadjacentnewtabbutton";
if (sib && sib.id == "new-tab-button") {

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

@ -466,7 +466,8 @@ function openLinkIn(url, where, params) {
loadInBackground = !loadInBackground;
// fall through
case "tab":
focusUrlBar = !loadInBackground && w.isBlankPageURL(url);
focusUrlBar = !loadInBackground && w.isBlankPageURL(url)
&& !aboutNewTabService.willNotifyUser;
let tabUsedForLoad = w.gBrowser.loadOneTab(url, {
referrerURI: aReferrerURI,

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

@ -683,3 +683,28 @@
label="&customizeMode.autoHideDownloadsButton.label;" checked="true"
oncommand="gCustomizeMode.onDownloadsAutoHideChange(event)"/>
</panel>
<panel id="extension-notification-panel"
role="group"
type="arrow"
hidden="true"
flip="slide"
position="bottomcenter topright"
tabspecific="true">
<popupnotification id="extension-new-tab-notification"
popupid="extension-new-tab"
label="&newTabControlled.header.message;"
buttonlabel="&newTabControlled.keepButton.label;"
buttonaccesskey="&newTabControlled.keepButton.accesskey;"
secondarybuttonlabel="&newTabControlled.restoreButton.label;"
secondarybuttonaccesskey="&newTabControlled.restoreButton.accesskey;"
closebuttonhidden="true"
dropmarkerhidden="true"
checkboxhidden="true">
<popupnotificationcontent orient="vertical">
<description id="extension-new-tab-notification-description">
&newTabControlled.message;
</description>
</popupnotificationcontent>
</popupnotification>
</panel>

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

@ -36,7 +36,7 @@ add_task(async function() {
let historyItems = document.getElementById("appMenu_historyMenu");
let historyItemForURL = historyItems.querySelector("toolbarbutton.bookmark-item[label='Happy History Hero']");
ok(historyItemForURL, "Should have a history item for the history we just made.");
historyItemForURL.click();
EventUtils.synthesizeMouseAtCenter(historyItemForURL, {});
await browserLoaded;
is(gBrowser.currentURI.spec, TEST_PATH + "dummy_history_item.html", "Should have expected page load");

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

@ -104,3 +104,22 @@ add_task(async function addremove_before_newtab_api() {
CustomizableUI.reset();
ok(CustomizableUI.inDefaultState, "Should be in default state");
});
/**
* Reset to defaults in customize mode to see if that doesn't break things.
*/
add_task(async function reset_before_newtab_customizemode() {
await startCustomizing();
simulateItemDrag(document.getElementById("home-button"), kGlobalNewTabButton);
ok(!gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
"tabs should no longer have the adjacent newtab attribute");
await endCustomizing();
assertNewTabButton("global");
await startCustomizing();
await gCustomizeMode.reset();
ok(gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
"tabs should have the adjacent newtab attribute again");
await endCustomizing();
assertNewTabButton("inner");
ok(CustomizableUI.inDefaultState, "Should be in default state");
});

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

@ -1,11 +1,16 @@
/* 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-globals-from ext-browser.js */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSettingsStore",
"resource://gre/modules/ExtensionSettingsStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
@ -13,13 +18,102 @@ XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
const STORE_TYPE = "url_overrides";
const NEW_TAB_SETTING_NAME = "newTabURL";
const NEW_TAB_CONFIRMED_TYPE = "newTabNotification";
function userWasNotified(extensionId) {
let setting = ExtensionSettingsStore.getSetting(NEW_TAB_CONFIRMED_TYPE, extensionId);
return setting && setting.value;
}
async function handleNewTabOpened() {
// We don't need to open the doorhanger again until the controlling add-on changes.
// eslint-disable-next-line no-use-before-define
removeNewTabObserver();
let item = ExtensionSettingsStore.getSetting(STORE_TYPE, NEW_TAB_SETTING_NAME);
if (!item || !item.id || userWasNotified(item.id)) {
return;
}
// Find the elements we need.
let win = windowTracker.getCurrentWindow({});
let doc = win.document;
let panel = doc.getElementById("extension-notification-panel");
// Setup the command handler.
let handleCommand = async (event) => {
if (event.originalTarget.getAttribute("anonid") == "button") {
// Main action is to keep changes.
await ExtensionSettingsStore.addSetting(
item.id, NEW_TAB_CONFIRMED_TYPE, item.id, true, () => false);
} else {
// Secondary action is to restore settings.
ExtensionSettingsStore.removeSetting(NEW_TAB_CONFIRMED_TYPE, item.id);
let addon = await AddonManager.getAddonByID(item.id);
addon.userDisabled = true;
}
panel.hidePopup();
win.gURLBar.focus();
};
panel.addEventListener("command", handleCommand);
panel.addEventListener("popuphidden", () => {
panel.removeEventListener("command", handleCommand);
}, {once: true});
// Look for a browserAction on the toolbar.
let action = CustomizableUI.getWidget(
`${global.makeWidgetId(item.id)}-browser-action`);
if (action) {
action = action.areaType == "toolbar" && action.forWindow(win).node;
}
// Anchor to a toolbar browserAction if found, otherwise use the menu button.
let anchor = doc.getAnonymousElementByAttribute(
action || doc.getElementById("PanelUI-menu-button"),
"class", "toolbarbutton-icon");
panel.hidden = false;
panel.openPopup(anchor);
}
let newTabOpenedListener = {
observe(subject, topic, data) {
// Do this work in an idle callback to avoid interfering with new tab performance tracking.
windowTracker
.getCurrentWindow({})
.requestIdleCallback(handleNewTabOpened);
},
};
function removeNewTabObserver() {
if (aboutNewTabService.willNotifyUser) {
Services.obs.removeObserver(newTabOpenedListener, "browser-open-newtab-start");
aboutNewTabService.willNotifyUser = false;
}
}
function addNewTabObserver(extensionId) {
if (!aboutNewTabService.willNotifyUser && extensionId && !userWasNotified(extensionId)) {
Services.obs.addObserver(newTabOpenedListener, "browser-open-newtab-start");
aboutNewTabService.willNotifyUser = true;
}
}
function setNewTabURL(extensionId, url) {
aboutNewTabService.newTabURL = url;
if (aboutNewTabService.overridden) {
addNewTabObserver(extensionId);
} else {
removeNewTabObserver();
}
}
this.urlOverrides = class extends ExtensionAPI {
processNewTabSetting(action) {
let {extension} = this;
let item = ExtensionSettingsStore[action](extension.id, STORE_TYPE, NEW_TAB_SETTING_NAME);
if (item) {
aboutNewTabService.newTabURL = item.value || item.initialValue;
setNewTabURL(item.id, item.value || item.initialValue);
}
}
@ -33,6 +127,11 @@ this.urlOverrides = class extends ExtensionAPI {
// Set up the shutdown code for the setting.
extension.callOnClose({
close: () => {
if (extension.shutdownReason == "ADDON_DISABLE"
|| extension.shutdownReason == "ADDON_UNINSTALL") {
ExtensionSettingsStore.removeSetting(
extension.id, NEW_TAB_CONFIRMED_TYPE, extension.id);
}
switch (extension.shutdownReason) {
case "ADDON_DISABLE":
this.processNewTabSetting("disable");
@ -65,7 +164,7 @@ this.urlOverrides = class extends ExtensionAPI {
// Set the newTabURL to the current value of the setting.
if (item) {
aboutNewTabService.newTabURL = item.value || item.initialValue;
setNewTabURL(extension.id, item.value || item.initialValue);
}
}
}

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

@ -95,6 +95,18 @@ add_task(async function() {
return gURLBar.popup.richlistbox.children[index];
}
async function promiseClickOnItem(item, details) {
// The Address Bar panel is animated and updated on a timer, thus it may not
// yet be listening to events when we try to click on it. This uses a
// polling strategy to repeat the click, if it doesn't go through.
let clicked = false;
item.addEventListener("mousedown", () => { clicked = true; }, {once: true});
while (!clicked) {
EventUtils.synthesizeMouseAtCenter(item, details);
await new Promise(r => window.requestIdleCallback(r, {timeout: 1000}));
}
}
let inputSessionSerial = 0;
async function startInputSession(indexToWaitFor) {
gURLBar.focus();
@ -198,12 +210,12 @@ add_task(async function() {
is(item.getAttribute("displayurl"), `${keyword} ${text}`,
`Expected heuristic result to have displayurl: "${keyword} ${text}".`);
EventUtils.synthesizeMouseAtCenter(item, {});
await expectEvent("on-input-entered-fired", {
let promiseEvent = expectEvent("on-input-entered-fired", {
text,
disposition: "currentTab",
});
await promiseClickOnItem(item, {});
await promiseEvent;
}
async function testDisposition(suggestionIndex, expectedDisposition, expectedText) {
@ -214,19 +226,20 @@ add_task(async function() {
EventUtils.synthesizeKey("VK_DOWN", {});
}
let item = gURLBar.popup.richlistbox.children[suggestionIndex];
if (expectedDisposition == "currentTab") {
EventUtils.synthesizeMouseAtCenter(item, {});
} else if (expectedDisposition == "newForegroundTab") {
EventUtils.synthesizeMouseAtCenter(item, {accelKey: true});
} else if (expectedDisposition == "newBackgroundTab") {
EventUtils.synthesizeMouseAtCenter(item, {shiftKey: true, accelKey: true});
}
await expectEvent("on-input-entered-fired", {
let promiseEvent = expectEvent("on-input-entered-fired", {
text: expectedText,
disposition: expectedDisposition,
});
let item = gURLBar.popup.richlistbox.children[suggestionIndex];
if (expectedDisposition == "currentTab") {
await promiseClickOnItem(item, {});
} else if (expectedDisposition == "newForegroundTab") {
await promiseClickOnItem(item, {accelKey: true});
} else if (expectedDisposition == "newBackgroundTab") {
await promiseClickOnItem(item, {shiftKey: true, accelKey: true});
}
await promiseEvent;
}
async function testSuggestions(info) {
@ -250,11 +263,12 @@ add_task(async function() {
info.suggestions.forEach(expectSuggestion);
EventUtils.synthesizeMouseAtCenter(gURLBar.popup.richlistbox.children[0], {});
await expectEvent("on-input-entered-fired", {
let promiseEvent = expectEvent("on-input-entered-fired", {
text,
disposition: "currentTab",
});
await promiseClickOnItem(gURLBar.popup.richlistbox.children[0], {});
await promiseEvent;
}
await extension.startup();

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

@ -21,7 +21,7 @@ add_task(async function testLastAccessed() {
let now = Date.now();
browser.test.assertTrue(past < tab1.lastAccessed,
browser.test.assertTrue(past <= tab1.lastAccessed,
"lastAccessed of tab 1 is later than the test start time.");
browser.test.assertTrue(tab1.lastAccessed < tab2.lastAccessed,
"lastAccessed of tab 2 is later than lastAccessed of tab 1.");

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

@ -3,9 +3,44 @@
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSettingsStore",
"resource://gre/modules/ExtensionSettingsStore.jsm");
const NEWTAB_URI_1 = "webext-newtab-1.html";
add_task(async function test_sending_message_from_newtab_page() {
function getNotificationSetting(extensionId) {
return ExtensionSettingsStore.getSetting("newTabNotification", extensionId);
}
function getNewTabDoorhanger() {
return document.getElementById("extension-new-tab-notification");
}
function clickKeepChanges(notification) {
let button = document.getAnonymousElementByAttribute(
notification, "anonid", "button");
button.click();
}
function clickRestoreSettings(notification) {
let button = document.getAnonymousElementByAttribute(
notification, "anonid", "secondarybutton");
button.click();
}
function waitForNewTab() {
let eventName = "browser-open-newtab-start";
return new Promise(resolve => {
function observer() {
Services.obs.removeObserver(observer, eventName);
resolve();
}
Services.obs.addObserver(observer, eventName);
});
}
add_task(async function test_new_tab_opens() {
let panel = getNewTabDoorhanger().closest("panel");
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"chrome_url_overrides": {
@ -36,28 +71,266 @@ add_task(async function test_sending_message_from_newtab_page() {
await extension.startup();
// Simulate opening the newtab open as a user would.
let popupShown = promisePopupShown(panel);
BrowserOpenTab();
await popupShown;
let url = await extension.awaitMessage("from-newtab-page");
ok(url.endsWith(NEWTAB_URI_1),
"Newtab url is overriden by the extension.");
"Newtab url is overridden by the extension.");
// This will show a confirmation doorhanger, make sure we don't leave it open.
let popupHidden = promisePopupHidden(panel);
panel.hidePopup();
await popupHidden;
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
await extension.unload();
});
add_task(async function test_new_tab_ignore_settings() {
await ExtensionSettingsStore.initialize();
let notification = getNewTabDoorhanger();
let panel = notification.closest("panel");
let extensionId = "newtabignore@mochi.test";
let extension = ExtensionTestUtils.loadExtension({
manifest: {
applications: {gecko: {id: extensionId}},
browser_action: {default_popup: "ignore.html"},
chrome_url_overrides: {newtab: "ignore.html"},
},
files: {"ignore.html": '<h1 id="extension-new-tab">New Tab!</h1>'},
useAddonManager: "temporary",
});
ok(panel.getAttribute("panelopen") != "true",
"The notification panel is initially closed");
await extension.startup();
// Simulate opening the New Tab as a user would.
let popupShown = promisePopupShown(panel);
BrowserOpenTab();
await popupShown;
// Ensure the doorhanger is shown and the setting isn't set yet.
is(panel.getAttribute("panelopen"), "true",
"The notification panel is open after opening New Tab");
is(gURLBar.focused, false, "The URL bar is not focused with a doorhanger");
is(getNotificationSetting(extensionId), null,
"The New Tab notification is not set for this extension");
is(panel.anchorNode.closest("toolbarbutton").id,
"newtabignore_mochi_test-browser-action",
"The doorhanger is anchored to the browser action");
// Manually close the panel, as if the user ignored it.
let popupHidden = promisePopupHidden(panel);
panel.hidePopup();
await popupHidden;
// Ensure panel is closed and the setting still isn't set.
ok(panel.getAttribute("panelopen") != "true",
"The notification panel is closed");
is(getNotificationSetting(extensionId), null,
"The New Tab notification is not set after ignoring the doorhanger");
// Close the first tab and open another new tab.
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
let newTabOpened = waitForNewTab();
BrowserOpenTab();
await newTabOpened;
// Verify the doorhanger is not shown a second time.
ok(panel.getAttribute("panelopen") != "true",
"The notification panel doesn't open after ignoring the doorhanger");
is(gURLBar.focused, true, "The URL bar is focused with no doorhanger");
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
await extension.unload();
});
add_task(async function test_new_tab_keep_settings() {
await ExtensionSettingsStore.initialize();
let notification = getNewTabDoorhanger();
let panel = notification.closest("panel");
let extensionId = "newtabkeep@mochi.test";
let manifest = {
version: "1.0",
applications: {gecko: {id: extensionId}},
chrome_url_overrides: {newtab: "keep.html"},
};
let files = {
"keep.html": '<script src="newtab.js"></script><h1 id="extension-new-tab">New Tab!</h1>',
"newtab.js": () => { window.onload = browser.test.sendMessage("newtab"); },
};
let extension = ExtensionTestUtils.loadExtension({
manifest,
files,
useAddonManager: "permanent",
});
ok(panel.getAttribute("panelopen") != "true",
"The notification panel is initially closed");
await extension.startup();
// Simulate opening the New Tab as a user would.
let popupShown = promisePopupShown(panel);
BrowserOpenTab();
await extension.awaitMessage("newtab");
await popupShown;
// Ensure the panel is open and the setting isn't saved yet.
is(panel.getAttribute("panelopen"), "true",
"The notification panel is open after opening New Tab");
is(getNotificationSetting(extensionId), null,
"The New Tab notification is not set for this extension");
is(panel.anchorNode.closest("toolbarbutton").id, "PanelUI-menu-button",
"The doorhanger is anchored to the menu icon");
// Click the Keep Changes button.
let popupHidden = promisePopupHidden(panel);
clickKeepChanges(notification);
await popupHidden;
// Ensure panel is closed and setting is updated.
ok(panel.getAttribute("panelopen") != "true",
"The notification panel is closed after click");
is(getNotificationSetting(extensionId).value, true,
"The New Tab notification is set after keeping the changes");
// Close the first tab and open another new tab.
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
BrowserOpenTab();
await extension.awaitMessage("newtab");
// Verify the doorhanger is not shown a second time.
ok(panel.getAttribute("panelopen") != "true",
"The notification panel is not opened after keeping the changes");
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
let upgradedExtension = ExtensionTestUtils.loadExtension({
manifest: Object.assign({}, manifest, {version: "2.0"}),
files,
useAddonManager: "permanent",
});
await upgradedExtension.startup();
BrowserOpenTab();
await upgradedExtension.awaitMessage("newtab");
// Ensure panel is closed and setting is still set.
ok(panel.getAttribute("panelopen") != "true",
"The notification panel is closed after click");
is(getNotificationSetting(extensionId).value, true,
"The New Tab notification is set after keeping the changes");
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
await extension.unload();
await upgradedExtension.unload();
});
add_task(async function test_new_tab_restore_settings() {
await ExtensionSettingsStore.initialize();
let notification = getNewTabDoorhanger();
let panel = notification.closest("panel");
let extensionId = "newtabrestore@mochi.test";
let extension = ExtensionTestUtils.loadExtension({
manifest: {
applications: {gecko: {id: extensionId}},
chrome_url_overrides: {newtab: "restore.html"},
},
files: {"restore.html": '<h1 id="extension-new-tab">New Tab!</h1>'},
useAddonManager: "temporary",
});
ok(panel.getAttribute("panelopen") != "true",
"The notification panel is initially closed");
is(getNotificationSetting(extensionId), null,
"The New Tab notification is not initially set for this extension");
await extension.startup();
// Simulate opening the newtab open as a user would.
let popupShown = promisePopupShown(panel);
BrowserOpenTab();
await popupShown;
// Verify that the panel is open and add-on is enabled.
let addon = await AddonManager.getAddonByID(extensionId);
is(addon.userDisabled, false, "The add-on is enabled at first");
is(panel.getAttribute("panelopen"), "true",
"The notification panel is open after opening New Tab");
is(getNotificationSetting(extensionId), null,
"The New Tab notification is not set for this extension");
// Click the Restore Changes button.
let addonDisabled = new Promise(resolve => {
let listener = {
onDisabled(disabledAddon) {
if (disabledAddon.id == addon.id) {
resolve();
AddonManager.removeAddonListener(listener);
}
},
};
AddonManager.addAddonListener(listener);
});
let popupHidden = promisePopupHidden(panel);
clickRestoreSettings(notification);
await popupHidden;
await addonDisabled;
// Ensure panel is closed, settings haven't changed and add-on is disabled.
ok(panel.getAttribute("panelopen") != "true",
"The notification panel is closed after click");
is(getNotificationSetting(extensionId), null,
"The New Tab notification is not set after resorting the settings");
is(addon.userDisabled, true, "The extension is now disabled");
// Reopen a browser tab and verify that there's no doorhanger.
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
let newTabOpened = waitForNewTab();
BrowserOpenTab();
await newTabOpened;
ok(panel.getAttribute("panelopen") != "true",
"The notification panel is not opened after keeping the changes");
// FIXME: We need to enable the add-on so it gets cleared from the
// ExtensionSettingsStore for now. See bug 1408226.
let addonEnabled = new Promise(resolve => {
let listener = {
onEnabled(enabledAddon) {
if (enabledAddon.id == addon.id) {
AddonManager.removeAddonListener(listener);
resolve();
}
},
};
AddonManager.addAddonListener(listener);
});
addon.userDisabled = false;
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
await addonEnabled;
await extension.unload();
});
/**
* Ensure we don't show the extension URL in the URL bar temporarily in new tabs
* while we're switching remoteness (when the URL we're loading and the
* default content principal are different).
*/
add_task(async function dontTemporarilyShowAboutExtensionPath() {
await ExtensionSettingsStore.initialize();
let extension = ExtensionTestUtils.loadExtension({
manifest: {
name: "Test Extension",
applications: {
gecko: {
id: "newtab@mochi.test",
id: "newtaburl@mochi.test",
},
},
chrome_url_overrides: {

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

@ -95,6 +95,7 @@ AboutNewTabService.prototype = {
_activityStreamPath: "",
_activityStreamDebug: false,
_overridden: false,
willNotifyUser: false,
classID: Components.ID("{dfcd2adc-7867-4d3a-ba70-17501f208142}"),
QueryInterface: XPCOMUtils.generateQI([

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

@ -23,6 +23,11 @@ interface nsIAboutNewTabService : nsISupports
*/
attribute ACString defaultURL;
/**
* Returns true if opening the New Tab page will notify the user of a change.
*/
attribute bool willNotifyUser;
/**
* Returns true if the default resource got overridden.
*/

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

@ -2124,8 +2124,8 @@ this.PlacesPanelview = class extends PlacesViewBase {
handleEvent(event) {
switch (event.type) {
case "click":
// For left and middle clicks, fall through to the command handler.
if (event.button >= 2) {
// For middle clicks, fall through to the command handler.
if (event.button != 1) {
break;
}
case "command":
@ -2154,8 +2154,22 @@ this.PlacesPanelview = class extends PlacesViewBase {
if (!button._placesNode)
return;
let modifKey = AppConstants.platform === "macosx" ? event.metaKey
: event.ctrlKey;
if (!PlacesUIUtils.openInTabClosesMenu && modifKey) {
// If 'Recent Bookmarks' in Bookmarks Panel.
if (button.parentNode.id == "panelMenu_bookmarksMenu") {
button.setAttribute("closemenu", "none");
}
} else {
button.removeAttribute("closemenu");
}
PlacesUIUtils.openNodeWithEvent(button._placesNode, event);
this.panelMultiView.closest("panel").hidePopup();
// Unlike left-click, middle-click requires manual menu closing.
if (button.parentNode.id != "panelMenu_bookmarksMenu" ||
(event.type == "click" && event.button == 1 && PlacesUIUtils.openInTabClosesMenu)) {
this.panelMultiView.closest("panel").hidePopup();
}
}
_onDragEnd() {

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

@ -5,20 +5,14 @@
// and contextmenu's "Open in a new tab" click.
async function locateBookmarkAndTestCtrlClick(menupopup) {
let menuitem = null;
for (let node of menupopup.childNodes) {
if (node.label == "Test1") {
menuitem = node;
let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, null);
EventUtils.synthesizeMouseAtCenter(menuitem,
AppConstants.platform === "macosx" ? {metaKey: true} : {ctrlKey: true});
let newTab = await promiseTabOpened;
ok(true, "Bookmark ctrl-click opened new tab.");
await BrowserTestUtils.removeTab(newTab);
break;
}
}
return menuitem;
let testMenuitem = [...menupopup.childNodes].find(node => node.label == "Test1");
ok(testMenuitem, "Found test bookmark.");
let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, null);
EventUtils.synthesizeMouseAtCenter(testMenuitem, {accelKey: true});
let newTab = await promiseTabOpened;
ok(true, "Bookmark ctrl-click opened new tab.");
await BrowserTestUtils.removeTab(newTab);
return testMenuitem;
}
async function testContextmenu(menuitem) {
@ -122,6 +116,43 @@ add_task(async function testStayopenBookmarksClicks() {
info("Closing menu");
await BrowserTestUtils.removeTab(newTab);
// Test App Menu's Bookmarks Library stayopen clicks.
let appMenu = document.getElementById("PanelUI-menu-button");
let appMenuPopup = document.getElementById("appMenu-popup");
let PopupShownPromise = BrowserTestUtils.waitForEvent(appMenuPopup, "popupshown");
appMenu.click();
await PopupShownPromise;
let libView = document.getElementById("appMenu-libraryView");
let libraryBtn = document.getElementById("appMenu-library-button");
let ViewShownPromise = BrowserTestUtils.waitForEvent(libView, "ViewShown");
libraryBtn.click();
await ViewShownPromise;
info("Library panel shown.");
let bookmarks = document.getElementById("appMenu-library-bookmarks-button");
let BMview = document.getElementById("PanelUI-bookmarks");
ViewShownPromise = BrowserTestUtils.waitForEvent(BMview, "ViewShown");
bookmarks.click();
await ViewShownPromise;
info("Library's bookmarks panel shown.");
// Test App Menu's Bookmarks Library stayopen clicks: Ctrl-click.
let menu = document.getElementById("panelMenu_bookmarksMenu");
var testMenuitem = await locateBookmarkAndTestCtrlClick(menu);
ok(appMenu.open, "Menu should remain open.");
// Test App Menu's Bookmarks Library stayopen clicks: middle-click.
promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, null);
EventUtils.synthesizeMouseAtCenter(testMenuitem, {button: 1});
newTab = await promiseTabOpened;
ok(true, "Bookmark middle-click opened new tab.");
await BrowserTestUtils.removeTab(newTab);
is(PanelUI.multiView.current.id, "PanelUI-bookmarks", "Should still show the bookmarks subview");
ok(appMenu.open, "Menu should remain open.");
// Close the App Menu
appMenuPopup.hidePopup();
ok(!appMenu.open, "The menu should now be closed.");
// Disable the rest of the tests on Mac due to Mac's handling of menus being
// slightly different to the other platforms.
if (AppConstants.platform === "macosx") {

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

@ -393,11 +393,8 @@ class FormAutofillSection {
(element != focusedInput && !element.value)) {
element.setUserInput(value);
this.changeFieldState(fieldDetail, FIELD_STATES.AUTO_FILLED);
continue;
}
}
if (ChromeUtils.getClassName(element) === "HTMLSelectElement") {
} else if (ChromeUtils.getClassName(element) === "HTMLSelectElement") {
let cache = this._cacheValue.matchingSelectOption.get(element) || {};
let option = cache[value] && cache[value].get();
if (!option) {
@ -413,6 +410,9 @@ class FormAutofillSection {
// Autofill highlight appears regardless if value is changed or not
this.changeFieldState(fieldDetail, FIELD_STATES.AUTO_FILLED);
}
if (fieldDetail.state == FIELD_STATES.AUTO_FILLED) {
element.addEventListener("input", this);
}
}
}
@ -554,18 +554,10 @@ class FormAutofillSection {
fieldDetail.state = nextState;
}
clearFieldState(focusedInput) {
let fieldDetail = this.getFieldDetailByElement(focusedInput);
this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
let targetSet = this._getTargetSet(focusedInput);
if (!targetSet.fieldDetails.some(detail => detail.state == FIELD_STATES.AUTO_FILLED)) {
targetSet.filledRecordGUID = null;
}
}
resetFieldStates() {
for (let fieldDetail of this._validDetails) {
const element = fieldDetail.elementWeakRef.get();
element.removeEventListener("input", this);
this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
}
this.address.filledRecordGUID = null;
@ -743,6 +735,26 @@ class FormAutofillSection {
Services.cpmm.sendAsyncMessage("FormAutofill:GetDecryptedString", {cipherText, reauth});
});
}
handleEvent(event) {
switch (event.type) {
case "input": {
if (!event.isTrusted) {
return;
}
const target = event.target;
const fieldDetail = this.getFieldDetailByElement(target);
const targetSet = this._getTargetSet(target);
this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
if (!targetSet.fieldDetails.some(detail => detail.state == FIELD_STATES.AUTO_FILLED)) {
targetSet.filledRecordGUID = null;
}
target.removeEventListener("input", this);
break;
}
}
}
}
/**
@ -928,25 +940,18 @@ class FormAutofillHandler {
* card field.
*/
async autofillFormFields(profile, focusedInput) {
let noFilledSections = !this.hasFilledSection();
let noFilledSectionsPreviously = !this.hasFilledSection();
await this.getSectionByElement(focusedInput).autofillFields(profile, focusedInput);
// Handle the highlight style resetting caused by user's correction afterward.
log.debug("register change handler for filled form:", this.form);
const onChangeHandler = e => {
if (!e.isTrusted) {
return;
}
if (e.type == "input") {
let section = this.getSectionByElement(e.target);
section.clearFieldState(e.target);
} else if (e.type == "reset") {
if (e.type == "reset") {
for (let section of this.sections) {
section.resetFieldStates();
}
}
// Unregister listeners once no field is in AUTO_FILLED state.
if (!this.hasFilledSection()) {
this.form.rootElement.removeEventListener("input", onChangeHandler);
@ -954,7 +959,9 @@ class FormAutofillHandler {
}
};
if (noFilledSections) {
if (noFilledSectionsPreviously) {
// Handle the highlight style resetting caused by user's correction afterward.
log.debug("register change handler for filled form:", this.form);
this.form.rootElement.addEventListener("input", onChangeHandler);
this.form.rootElement.addEventListener("reset", onChangeHandler);
}

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

@ -16,6 +16,314 @@ XPCOMUtils.defineLazyModuleGetters(this, {
XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
"@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");
// Flag to control if we want to send new/old telemetry
// TODO: remove this flag and the legacy code in Bug 1419996
const NEW_TABLE = false;
// Validate the content has non-empty string
function hasString(str) {
return typeof str == "string" && str.length > 0;
}
// Validate the content is an empty string
function isEmptyString(str) {
return typeof str == "string" && str === "";
}
// Validate the content is an interger
function isInteger(i) {
return Number.isInteger(i);
}
// Validate the content is a positive interger
function isPositiveInteger(i) {
return Number.isInteger(i) && i > 0;
}
// Validate the number is -1
function isMinusOne(num) {
return num === -1;
}
// Validate the category value is within the list
function isValidCategory(category) {
return ["logo-interactions", "onboarding-interactions",
"overlay-interactions", "notification-interactions"]
.includes(category);
}
// Validate the page value is within the list
function isValidPage(page) {
return ["about:newtab", "about:home"].includes(page);
}
// Validate the tour_type value is within the list
function isValidTourType(type) {
return ["new", "update"].includes(type);
}
// Validate the bubble state value is within the list
function isValidBubbleState(str) {
return ["bubble", "dot", "hide"].includes(str);
}
// Validate the logo state value is within the list
function isValidLogoState(str) {
return ["logo", "watermark"].includes(str);
}
// Validate the notification state value is within the list
function isValidNotificationState(str) {
return ["show", "hide", "finished"].includes(str);
}
// Validate the column must be defined per ping
function definePerPing(column) {
return function() {
throw new Error(`Must define the '${column}' validator per ping because it is not the same for all pings`);
};
}
// Basic validators for session pings
// client_id, locale are added by PingCentre, IP is added by server
// so no need check these column here
const BASIC_SESSION_SCHEMA = {
addon_version: hasString,
category: isValidCategory,
page: isValidPage,
parent_session_id: hasString,
root_session_id: hasString,
session_begin: isInteger,
session_end: isInteger,
session_id: hasString,
tour_type: isValidTourType,
type: hasString,
};
// Basic validators for event pings
// client_id, locale are added by PingCentre, IP is added by server
// so no need check these column here
const BASIC_EVENT_SCHEMA = {
addon_version: hasString,
bubble_state: definePerPing("bubble_state"),
category: isValidCategory,
current_tour_id: definePerPing("current_tour_id"),
logo_state: definePerPing("logo_state"),
notification_impression: definePerPing("notification_impression"),
notification_state: definePerPing("notification_state"),
page: isValidPage,
parent_session_id: hasString,
root_session_id: hasString,
target_tour_id: definePerPing("target_tour_id"),
timestamp: isInteger,
tour_type: isValidTourType,
type: hasString,
width: isPositiveInteger,
};
/**
* We send 2 kinds (firefox-onboarding-event2, firefox-onboarding-session2) of pings to ping centre
* server (they call it `topic`). The `internal` state in `topic` field means this event is used internaly to
* track states and will not send out any message.
*
* To save server space and make query easier, we track session begin and end but only send pings
* when session end. Therefore the server will get single "onboarding/overlay/notification-session"
* event which includes both `session_begin` and `session_end` timestamp.
*
* We send `session_begin` and `session_end` timestamps instead of `session_duration` diff because
* of analytics engineer's request.
*/
const EVENT_WHITELIST = {
// track when a notification appears.
"notification-appear": {
topic: "firefox-onboarding-event2",
category: "notification-interactions",
validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
bubble_state: isValidBubbleState,
current_tour_id: hasString,
logo_state: isValidLogoState,
notification_impression: isPositiveInteger,
notification_state: isValidNotificationState,
target_tour_id: isEmptyString,
}),
},
// track when a user clicks close notification button
"notification-close-button-click": {
topic: "firefox-onboarding-event2",
category: "notification-interactions",
validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
bubble_state: isValidBubbleState,
current_tour_id: hasString,
logo_state: isValidLogoState,
notification_impression: isPositiveInteger,
notification_state: isValidNotificationState,
target_tour_id: hasString,
}),
},
// track when a user clicks notification's Call-To-Action button
"notification-cta-click": {
topic: "firefox-onboarding-event2",
category: "notification-interactions",
validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
bubble_state: isValidBubbleState,
current_tour_id: hasString,
logo_state: isValidLogoState,
notification_impression: isPositiveInteger,
notification_state: isValidNotificationState,
target_tour_id: hasString,
}),
},
// track the start and end time of the notification session
"notification-session": {
topic: "firefox-onboarding-session2",
category: "notification-interactions",
validators: BASIC_SESSION_SCHEMA,
},
// track the start of a notification
"notification-session-begin": {topic: "internal"},
// track the end of a notification
"notification-session-end": {topic: "internal"},
// track when a user clicks the Firefox logo
"onboarding-logo-click": {
topic: "firefox-onboarding-event2",
category: "logo-interactions",
validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
bubble_state: isValidBubbleState,
current_tour_id: isEmptyString,
logo_state: isValidLogoState,
notification_impression: isMinusOne,
notification_state: isValidNotificationState,
target_tour_id: isEmptyString,
}),
},
// track when the onboarding is not visisble due to small screen in the 1st load
"onboarding-noshow-smallscreen": {
topic: "firefox-onboarding-event2",
category: "onboarding-interactions",
validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
bubble_state: isEmptyString,
current_tour_id: isEmptyString,
logo_state: isEmptyString,
notification_impression: isMinusOne,
notification_state: isEmptyString,
target_tour_id: isEmptyString,
}),
},
// init onboarding session with session_key, page url, and tour_type
"onboarding-register-session": {topic: "internal"},
// track the start and end time of the onboarding session
"onboarding-session": {
topic: "firefox-onboarding-session2",
category: "onboarding-interactions",
validators: BASIC_SESSION_SCHEMA,
},
// track onboarding start time (when user loads about:home or about:newtab)
"onboarding-session-begin": {topic: "internal"},
// track onboarding end time (when user unloads about:home or about:newtab)
"onboarding-session-end": {topic: "internal"},
// track when a user clicks the close overlay button
"overlay-close-button-click": {
topic: "firefox-onboarding-event2",
category: "overlay-interactions",
validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
bubble_state: isEmptyString,
current_tour_id: hasString,
logo_state: isEmptyString,
notification_impression: isMinusOne,
notification_state: isEmptyString,
target_tour_id: hasString,
}),
},
// track when a user clicks outside the overlay area to end the tour
"overlay-close-outside-click": {
topic: "firefox-onboarding-event2",
category: "overlay-interactions",
validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
bubble_state: isEmptyString,
current_tour_id: hasString,
logo_state: isEmptyString,
notification_impression: isMinusOne,
notification_state: isEmptyString,
target_tour_id: hasString,
}),
},
// track when a user clicks overlay's Call-To-Action button
"overlay-cta-click": {
topic: "firefox-onboarding-event2",
category: "overlay-interactions",
validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
bubble_state: isEmptyString,
current_tour_id: hasString,
logo_state: isEmptyString,
notification_impression: isMinusOne,
notification_state: isEmptyString,
target_tour_id: hasString,
}),
},
// track when a tour is shown in the overlay
"overlay-current-tour": {
topic: "firefox-onboarding-event2",
category: "overlay-interactions",
validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
bubble_state: isEmptyString,
current_tour_id: hasString,
logo_state: isEmptyString,
notification_impression: isMinusOne,
notification_state: isEmptyString,
target_tour_id: isEmptyString,
}),
},
// track when an overlay is opened and disappeared because the window is resized too small
"overlay-disapear-resize": {
topic: "firefox-onboarding-event2",
category: "overlay-interactions",
validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
bubble_state: isEmptyString,
current_tour_id: isEmptyString,
logo_state: isEmptyString,
notification_impression: isMinusOne,
notification_state: isEmptyString,
target_tour_id: isEmptyString,
}),
},
// track when a user clicks a navigation button in the overlay
"overlay-nav-click": {
topic: "firefox-onboarding-event2",
category: "overlay-interactions",
validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
bubble_state: isEmptyString,
current_tour_id: hasString,
logo_state: isEmptyString,
notification_impression: isMinusOne,
notification_state: isEmptyString,
target_tour_id: hasString,
}),
},
// track the start and end time of the overlay session
"overlay-session": {
topic: "firefox-onboarding-session2",
category: "overlay-interactions",
validators: BASIC_SESSION_SCHEMA,
},
// track the start of an overlay session
"overlay-session-begin": {topic: "internal"},
// track the end of an overlay session
"overlay-session-end": {topic: "internal"},
// track when a user clicks 'Skip Tour' button in the overlay
"overlay-skip-tour": {
topic: "firefox-onboarding-event2",
category: "overlay-interactions",
validators: Object.assign({}, BASIC_EVENT_SCHEMA, {
bubble_state: isEmptyString,
current_tour_id: hasString,
logo_state: isEmptyString,
notification_impression: isMinusOne,
notification_state: isEmptyString,
target_tour_id: isEmptyString,
}),
},
};
/**
* We send 2 kinds (firefox-onboarding-event, firefox-onboarding-session) of pings to ping centre
* server (they call it `topic`). The `internal` state in `topic` field means this event is used internaly to
@ -28,7 +336,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
* We send `session_begin` and `session_end` timestamps instead of `session_duration` diff because
* of analytics engineer's request.
*/
const EVENT_WHITELIST = {
const OLD_EVENT_WHITELIST = {
// track when click the notification close button
"notification-close-button-click": {topic: "firefox-onboarding-event", category: "notification-interactions"},
// track when click the notification Call-To-Action button
@ -88,11 +396,18 @@ let OnboardingTelemetry = {
},
process(data) {
let { event, session_key } = data;
if (NEW_TABLE) {
throw new Error("Will implement in bug 1413830");
} else {
this.processOldPings(data);
}
},
let topic = EVENT_WHITELIST[event] && EVENT_WHITELIST[event].topic;
processOldPings(data) {
let { event, session_key } = data;
let topic = OLD_EVENT_WHITELIST[event] && OLD_EVENT_WHITELIST[event].topic;
if (!topic) {
throw new Error(`ping-centre doesn't know ${event}, only knows ${Object.keys(EVENT_WHITELIST)}`);
throw new Error(`ping-centre doesn't know ${event}, only knows ${Object.keys(OLD_EVENT_WHITELIST)}`);
}
if (event === "onboarding-register-session") {
@ -116,12 +431,12 @@ let OnboardingTelemetry = {
break;
}
} else {
this._send(topic, data);
this._sendOldPings(topic, data);
}
},
// send out pings by topic
_send(topic, data) {
_sendOldPings(topic, data) {
let {
addon_version,
} = this.state;
@ -138,7 +453,7 @@ let OnboardingTelemetry = {
session_id,
tour_type,
} = this.state.sessions[session_key];
let category = EVENT_WHITELIST[event].category;
let category = OLD_EVENT_WHITELIST[event].category;
// the field is used to identify how user open the overlay (through default logo or watermark),
// the number of open from notification can be retrieved via `notification-cta-click` event
let tour_source = Services.prefs.getStringPref("browser.onboarding.state", "default");
@ -203,5 +518,36 @@ let OnboardingTelemetry = {
}, {filter: ONBOARDING_ID});
break;
}
},
// validate data sanitation and make sure correct ping params are sent
_validatePayload(payload) {
let event = payload.type;
let { validators } = EVENT_WHITELIST[event];
if (!validators) {
throw new Error(`Event ${event} without validators should not be sent.`);
}
let validatorKeys = Object.keys(validators);
// Not send with undefined column
if (Object.keys(payload).length > validatorKeys.length) {
throw new Error(`Event ${event} want to send more columns than expect, should not be sent.`);
}
let results = {};
let failed = false;
// Per column validation
for (let key of validatorKeys) {
if (payload[key] !== undefined) {
results[key] = validators[key](payload[key]);
if (!results[key]) {
failed = true;
}
} else {
results[key] = false;
failed = true;
}
}
if (failed) {
throw new Error(`Event ${event} contains incorrect data: ${JSON.stringify(results)}, should not be sent.`);
}
}
};

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

@ -967,6 +967,13 @@ you can use these alternative items. Otherwise, their values should be empty. -
<!ENTITY updateRestart.cancelButton.accesskey "N">
<!ENTITY updateRestart.panelUI.label2 "Restart to update &brandShorterName;">
<!ENTITY newTabControlled.message "An extension has changed the page you see when you open a New Tab. You can restore your settings if you do not want this change.">
<!ENTITY newTabControlled.header.message "Your New Tab has changed.">
<!ENTITY newTabControlled.keepButton.label "Keep Changes">
<!ENTITY newTabControlled.keepButton.accesskey "K">
<!ENTITY newTabControlled.restoreButton.label "Restore Settings">
<!ENTITY newTabControlled.restoreButton.accesskey "R">
<!ENTITY pageActionButton.tooltip "Page actions">
<!ENTITY pageAction.addToUrlbar.label "Add to Address Bar">
<!ENTITY pageAction.removeFromUrlbar.label "Remove from Address Bar">

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

@ -47,6 +47,7 @@
border: none;
}
#PanelUI-menu-button[badge-status="extension-new-tab"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
#PanelUI-menu-button[badge-status="download-success"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
display: none;
}
@ -326,6 +327,30 @@ panelview:not([mainview]) .toolbarbutton-text,
padding: 4px 0;
}
/* START notification popups for extension controlled content */
#extension-notification-panel > .panel-arrowcontainer > .panel-arrowcontent {
padding: 0;
}
#extension-new-tab-notification > .popup-notification-body-container > .popup-notification-body {
width: 30em;
}
#extension-new-tab-notification > .popup-notification-body-container > .popup-notification-body > hbox > vbox > .popup-notification-description {
font-size: 1.3em;
font-weight: lighter;
}
#extension-new-tab-notification-description {
margin-bottom: 0;
}
#extension-new-tab-notification > .popup-notification-body-container > .popup-notification-body > .popup-notification-warning,
#extension-new-tab-notification > .popup-notification-body-container > .popup-notification-icon {
display: none;
}
/* END notification popups for extension controlled content */
#appMenu-popup > .panel-arrowcontainer > .panel-arrowcontent,
panel[photon] > .panel-arrowcontainer > .panel-arrowcontent {
padding: 0;

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

@ -40,17 +40,13 @@ p {
}
.list-row > ul > li {
float: left;
float: inline-start;
width: 16em;
line-height: 2em;
margin-inline-start: 1em;
margin-bottom: 0;
}
.list-row > ul > li:dir(rtl) {
float: right;
}
.title {
background-image: url("chrome://browser/skin/privatebrowsing/private-browsing.svg");
background-position: left center;
@ -153,12 +149,7 @@ a.button {
}
.toggle:checked + .toggle-btn::after {
left: 21px;
}
.toggle:checked + .toggle-btn:dir(rtl)::after {
left: auto;
right: 16px;
offset-inline-start: 21px;
}
.toggle:-moz-focusring + .toggle-btn {

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

@ -212,6 +212,7 @@ skip-if = true # Bug 1403188
[browser_jsterm_completion.js]
[browser_jsterm_copy_command.js]
[browser_jsterm_dollar.js]
[browser_jsterm_history.js]
[browser_jsterm_history_persist.js]
[browser_jsterm_inspect.js]
[browser_jsterm_no_autocompletion_on_defined_variables.js]
@ -290,8 +291,6 @@ skip-if = true # Bug 1404392
[browser_webconsole_highlighter_console_helper.js]
skip-if = true # Bug 1404853
# old console skip-if = true # Requires direct access to content nodes
[browser_webconsole_history.js]
skip-if = true # Bug 1408938
[browser_webconsole_history_arrow_keys.js]
skip-if = true # Bug 1408939
[browser_webconsole_history_nav.js]

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

@ -0,0 +1,56 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests the console history feature accessed via the up and down arrow keys.
"use strict";
const TEST_URI = "data:text/html;charset=UTF-8,test";
const HISTORY_BACK = -1;
const HISTORY_FORWARD = 1;
const COMMANDS = ["document", "window", "window.location"];
add_task(async function () {
const { jsterm } = await openNewTabAndConsole(TEST_URI);
const { inputNode } = jsterm;
jsterm.clearOutput();
for (let command of COMMANDS) {
info(`Executing command ${command}`);
jsterm.setInputValue(command);
await jsterm.execute();
}
for (let x = COMMANDS.length - 1; x != -1; x--) {
jsterm.historyPeruse(HISTORY_BACK);
is(inputNode.value, COMMANDS[x], "check history previous idx:" + x);
}
jsterm.historyPeruse(HISTORY_BACK);
is(inputNode.value, COMMANDS[0], "test that item is still index 0");
jsterm.historyPeruse(HISTORY_BACK);
is(inputNode.value, COMMANDS[0], "test that item is still still index 0");
for (let i = 1; i < COMMANDS.length; i++) {
jsterm.historyPeruse(HISTORY_FORWARD);
is(inputNode.value, COMMANDS[i], "check history next idx:" + i);
}
jsterm.historyPeruse(HISTORY_FORWARD);
is(inputNode.value, "", "check input is empty again");
// Simulate pressing Arrow_Down a few times and then if Arrow_Up shows
// the previous item from history again.
jsterm.historyPeruse(HISTORY_FORWARD);
jsterm.historyPeruse(HISTORY_FORWARD);
jsterm.historyPeruse(HISTORY_FORWARD);
is(inputNode.value, "", "check input is still empty");
let idxLast = COMMANDS.length - 1;
jsterm.historyPeruse(HISTORY_BACK);
is(inputNode.value, COMMANDS[idxLast], "check history next idx:" + idxLast);
});

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

@ -1,62 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests the console history feature accessed via the up and down arrow keys.
"use strict";
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
"test/test-console.html";
// Constants used for defining the direction of JSTerm input history navigation.
const HISTORY_BACK = -1;
const HISTORY_FORWARD = 1;
add_task(function* () {
yield loadTab(TEST_URI);
let hud = yield openConsole();
hud.jsterm.clearOutput();
let jsterm = hud.jsterm;
let input = jsterm.inputNode;
let executeList = ["document", "window", "window.location"];
for (let item of executeList) {
input.value = item;
yield jsterm.execute();
}
for (let x = executeList.length - 1; x != -1; x--) {
jsterm.historyPeruse(HISTORY_BACK);
is(input.value, executeList[x], "check history previous idx:" + x);
}
jsterm.historyPeruse(HISTORY_BACK);
is(input.value, executeList[0], "test that item is still index 0");
jsterm.historyPeruse(HISTORY_BACK);
is(input.value, executeList[0], "test that item is still still index 0");
for (let i = 1; i < executeList.length; i++) {
jsterm.historyPeruse(HISTORY_FORWARD);
is(input.value, executeList[i], "check history next idx:" + i);
}
jsterm.historyPeruse(HISTORY_FORWARD);
is(input.value, "", "check input is empty again");
// Simulate pressing Arrow_Down a few times and then if Arrow_Up shows
// the previous item from history again.
jsterm.historyPeruse(HISTORY_FORWARD);
jsterm.historyPeruse(HISTORY_FORWARD);
jsterm.historyPeruse(HISTORY_FORWARD);
is(input.value, "", "check input is still empty");
let idxLast = executeList.length - 1;
jsterm.historyPeruse(HISTORY_BACK);
is(input.value, executeList[idxLast], "check history next idx:" + idxLast);
});

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

@ -112,12 +112,12 @@ public:
AnimationPlayState PlayState() const;
bool Pending() const { return mPendingState != PendingState::NotPending; }
virtual Promise* GetReady(ErrorResult& aRv);
virtual Promise* GetFinished(ErrorResult& aRv);
Promise* GetFinished(ErrorResult& aRv);
void Cancel();
virtual void Finish(ErrorResult& aRv);
void Finish(ErrorResult& aRv);
virtual void Play(ErrorResult& aRv, LimitBehavior aLimitBehavior);
virtual void Pause(ErrorResult& aRv);
virtual void Reverse(ErrorResult& aRv);
void Reverse(ErrorResult& aRv);
bool IsRunningOnCompositor() const;
IMPL_EVENT_HANDLER(finish);
IMPL_EVENT_HANDLER(cancel);

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

@ -306,6 +306,14 @@ waitForAllPaints(() => {
return;
}
// Skip this test on Android since this test have been failing
// intermittently.
// Bug 1413817: We should audit this test still fails once we have the
// conformant Promise micro task.
if (isAndroid) {
return;
}
await SpecialPowers.pushPrefEnv({ set: [["ui.showHideScrollbars", 1]] });
var parentElement = addDiv(null,

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

@ -1979,7 +1979,12 @@ void HTMLMediaElement::DoLoad()
return;
}
// Detect if user has interacted with element so that play will not be
// blocked when initiated by a script. This enables sites to capture user
// intent to play by calling load() in the click handler of a "catalog
// view" of a gallery of videos.
if (EventStateManager::IsHandlingUserInput()) {
mHasUserInteractedLoadOrSeek = true;
// Mark the channel as urgent-start when autopaly so that it will play the
// media from src after loading enough resource.
if (HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) {
@ -2749,6 +2754,12 @@ HTMLMediaElement::Seek(double aTime,
return nullptr;
}
// Detect if user has interacted with element by seeking so that
// play will not be blocked when initiated by a script.
if (EventStateManager::IsHandlingUserInput()) {
mHasUserInteractedLoadOrSeek = true;
}
StopSuspendingAfterFirstFrame();
if (mSrcStream) {
@ -4032,6 +4043,7 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
mIsEncrypted(false),
mWaitingForKey(NOT_WAITING_FOR_KEY),
mDisableVideo(false),
mHasUserInteractedLoadOrSeek(false),
mFirstFrameLoaded(false),
mDefaultPlaybackStartPosition(0.0),
mHasSuspendTaint(false),

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

@ -741,6 +741,13 @@ public:
void NotifyCueDisplayStatesChanged();
bool GetAndClearHasUserInteractedLoadOrSeek()
{
bool result = mHasUserInteractedLoadOrSeek;
mHasUserInteractedLoadOrSeek = false;
return result;
}
// A method to check whether we are currently playing.
bool IsCurrentlyPlaying() const;
@ -1778,6 +1785,10 @@ private:
// Total time a video has (or would have) spent in video-decode-suspend mode.
TimeDurationAccumulator mVideoDecodeSuspendTime;
// True if user has called load() or seek() via user input.
// It's *only* use for checking autoplay policy
bool mHasUserInteractedLoadOrSeek;
// True if the first frame has been successfully loaded.
bool mFirstFrameLoaded;

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

@ -38,7 +38,10 @@ AutoplayPolicy::IsMediaElementAllowedToPlay(NotNull<HTMLMediaElement*> aElement)
// TODO : this old way would be removed when user-gestures-needed becomes
// as a default option to block autoplay.
return EventStateManager::IsHandlingUserInput();
// If user triggers load() or seek() before play(), we would also allow the
// following play().
return aElement->GetAndClearHasUserInteractedLoadOrSeek() ||
EventStateManager::IsHandlingUserInput();
}
} // namespace dom

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

@ -849,13 +849,13 @@ ChannelMediaResource::UpdatePrincipal()
}
void
ChannelMediaResource::CacheClientNotifySuspendedStatusChanged()
ChannelMediaResource::CacheClientNotifySuspendedStatusChanged(bool aSuspended)
{
mCallback->AbstractMainThread()->Dispatch(NewRunnableMethod<bool>(
"MediaResourceCallback::NotifySuspendedStatusChanged",
mCallback.get(),
&MediaResourceCallback::NotifySuspendedStatusChanged,
IsSuspendedByCache()));
aSuspended));
}
nsresult
@ -943,12 +943,6 @@ ChannelMediaResource::IsDataCachedToEndOfResource(int64_t aOffset)
return mCacheStream.IsDataCachedToEndOfStream(aOffset);
}
bool
ChannelMediaResource::IsSuspendedByCache()
{
return mCacheStream.AreAllStreamsForResourceSuspended();
}
bool
ChannelMediaResource::IsSuspended()
{

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

@ -88,7 +88,7 @@ public:
// Notify that the principal for the cached resource changed.
void CacheClientNotifyPrincipalChanged();
// Notify the decoder that the cache suspended status changed.
void CacheClientNotifySuspendedStatusChanged();
void CacheClientNotifySuspendedStatusChanged(bool aSuspended);
// These are called on the main thread by MediaCache. These shouldn't block,
// but they may grab locks --- the media cache is not holding its lock
@ -192,7 +192,6 @@ public:
protected:
nsresult Seek(int64_t aOffset, bool aResume);
bool IsSuspendedByCache();
// These are called on the main thread by Listener.
nsresult OnStartRequest(nsIRequest* aRequest, int64_t aRequestOffset);
nsresult OnStopRequest(nsIRequest* aRequest, nsresult aStatus);

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

@ -833,6 +833,7 @@ MediaCache::FindBlockForIncomingData(TimeStamp aNow,
MediaCacheStream* aStream,
int32_t aStreamBlockIndex)
{
MOZ_ASSERT(sThread->IsOnCurrentThread());
mReentrantMonitor.AssertCurrentThreadIn();
int32_t blockIndex =
@ -863,6 +864,8 @@ MediaCache::FindBlockForIncomingData(TimeStamp aNow,
bool
MediaCache::BlockIsReusable(int32_t aBlockIndex)
{
mReentrantMonitor.AssertCurrentThreadIn();
Block* block = &mIndex[aBlockIndex];
for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
MediaCacheStream* stream = block->mOwners[i].mStream;
@ -904,6 +907,7 @@ MediaCache::FindReusableBlock(TimeStamp aNow,
int32_t aForStreamBlock,
int32_t aMaxSearchBlockIndex)
{
MOZ_ASSERT(sThread->IsOnCurrentThread());
mReentrantMonitor.AssertCurrentThreadIn();
uint32_t length = std::min(uint32_t(aMaxSearchBlockIndex), uint32_t(mIndex.Length()));
@ -968,6 +972,8 @@ MediaCache::FindReusableBlock(TimeStamp aNow,
MediaCache::BlockList*
MediaCache::GetListForBlock(BlockOwner* aBlock)
{
mReentrantMonitor.AssertCurrentThreadIn();
switch (aBlock->mClass) {
case METADATA_BLOCK:
NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
@ -987,6 +993,8 @@ MediaCache::GetListForBlock(BlockOwner* aBlock)
MediaCache::BlockOwner*
MediaCache::GetBlockOwner(int32_t aBlockIndex, MediaCacheStream* aStream)
{
mReentrantMonitor.AssertCurrentThreadIn();
Block* block = &mIndex[aBlockIndex];
for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
if (block->mOwners[i].mStream == aStream)
@ -1042,6 +1050,8 @@ MediaCache::SwapBlocks(int32_t aBlockIndex1, int32_t aBlockIndex2)
void
MediaCache::RemoveBlockOwner(int32_t aBlockIndex, MediaCacheStream* aStream)
{
mReentrantMonitor.AssertCurrentThreadIn();
Block* block = &mIndex[aBlockIndex];
for (uint32_t i = 0; i < block->mOwners.Length(); ++i) {
BlockOwner* bo = &block->mOwners[i];
@ -1062,6 +1072,8 @@ MediaCache::AddBlockOwnerAsReadahead(int32_t aBlockIndex,
MediaCacheStream* aStream,
int32_t aStreamBlockIndex)
{
mReentrantMonitor.AssertCurrentThreadIn();
Block* block = &mIndex[aBlockIndex];
if (block->mOwners.IsEmpty()) {
mFreeBlocks.RemoveBlock(aBlockIndex);
@ -1102,6 +1114,7 @@ MediaCache::FreeBlock(int32_t aBlock)
TimeDuration
MediaCache::PredictNextUse(TimeStamp aNow, int32_t aBlock)
{
MOZ_ASSERT(sThread->IsOnCurrentThread());
mReentrantMonitor.AssertCurrentThreadIn();
NS_ASSERTION(!IsBlockFree(aBlock), "aBlock is free");
@ -1157,6 +1170,7 @@ MediaCache::PredictNextUse(TimeStamp aNow, int32_t aBlock)
TimeDuration
MediaCache::PredictNextUseForIncomingData(MediaCacheStream* aStream)
{
MOZ_ASSERT(sThread->IsOnCurrentThread());
mReentrantMonitor.AssertCurrentThreadIn();
int64_t bytesAhead = aStream->mChannelOffset - aStream->mStreamOffset;
@ -1537,7 +1551,8 @@ MediaCache::Update()
for (uint32_t i = 0; i < mSuspendedStatusToNotify.Length(); ++i) {
MediaCache::ResourceStreamIterator iter(this, mSuspendedStatusToNotify[i]);
while (MediaCacheStream* stream = iter.Next()) {
stream->mClient->CacheClientNotifySuspendedStatusChanged();
stream->mClient->CacheClientNotifySuspendedStatusChanged(
stream->AreAllStreamsForResourceSuspended());
}
}
mSuspendedStatusToNotify.Clear();
@ -1627,8 +1642,7 @@ MediaCache::Verify()
#endif
void
MediaCache::InsertReadaheadBlock(BlockOwner* aBlockOwner,
int32_t aBlockIndex)
MediaCache::InsertReadaheadBlock(BlockOwner* aBlockOwner, int32_t aBlockIndex)
{
mReentrantMonitor.AssertCurrentThreadIn();
@ -1659,6 +1673,7 @@ MediaCache::AllocateAndWriteBlock(MediaCacheStream* aStream,
Span<const uint8_t> aData1,
Span<const uint8_t> aData2)
{
MOZ_ASSERT(sThread->IsOnCurrentThread());
mReentrantMonitor.AssertCurrentThreadIn();
// Remove all cached copies of this block
@ -2105,6 +2120,8 @@ void
MediaCacheStream::FlushPartialBlockInternal(bool aNotifyAll,
ReentrantMonitorAutoEnter& aReentrantMonitor)
{
MOZ_ASSERT(OwnerThread()->IsOnCurrentThread());
int32_t blockIndex = OffsetToBlockIndexUnchecked(mChannelOffset);
int32_t blockOffset = OffsetInBlock(mChannelOffset);
if (blockOffset > 0) {
@ -2133,27 +2150,12 @@ MediaCacheStream::FlushPartialBlockInternal(bool aNotifyAll,
}
}
void
MediaCacheStream::FlushPartialBlock()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
// Write the current partial block to memory.
// Note: This writes a full block, so if data is not at the end of the
// stream, the decoder must subsequently choose correct start and end offsets
// for reading/seeking.
FlushPartialBlockInternal(false, mon);
mMediaCache->QueueUpdate();
}
void
MediaCacheStream::NotifyDataEndedInternal(uint32_t aLoadID,
nsresult aStatus,
bool aReopenOnError)
{
MOZ_ASSERT(OwnerThread()->IsOnCurrentThread());
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
if (mClosed || aLoadID != mLoadID) {
@ -2289,7 +2291,9 @@ MediaCacheStream::~MediaCacheStream()
bool
MediaCacheStream::AreAllStreamsForResourceSuspended()
{
MOZ_ASSERT(!NS_IsMainThread());
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
MediaCache::ResourceStreamIterator iter(mMediaCache, mResourceID);
// Look for a stream that's able to read the data we need
int64_t dataOffset = -1;
@ -2340,6 +2344,7 @@ MediaCacheStream::Close()
void
MediaCacheStream::Pin()
{
// TODO: Assert non-main thread.
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
++mPinCount;
// Queue an Update since we may no longer want to read more into the
@ -2350,6 +2355,7 @@ MediaCacheStream::Pin()
void
MediaCacheStream::Unpin()
{
// TODO: Assert non-main thread.
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
NS_ASSERTION(mPinCount > 0, "Unbalanced Unpin");
--mPinCount;
@ -2361,6 +2367,7 @@ MediaCacheStream::Unpin()
int64_t
MediaCacheStream::GetLength()
{
// TODO: Assert non-main thread.
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
return mStreamLength;
}
@ -2368,6 +2375,7 @@ MediaCacheStream::GetLength()
int64_t
MediaCacheStream::GetOffset() const
{
// TODO: Assert non-main thread.
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
return mChannelOffset;
}
@ -2375,6 +2383,7 @@ MediaCacheStream::GetOffset() const
int64_t
MediaCacheStream::GetNextCachedData(int64_t aOffset)
{
MOZ_ASSERT(!NS_IsMainThread());
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
return GetNextCachedDataInternal(aOffset);
}
@ -2382,6 +2391,7 @@ MediaCacheStream::GetNextCachedData(int64_t aOffset)
int64_t
MediaCacheStream::GetCachedDataEnd(int64_t aOffset)
{
// TODO: Assert non-main thread.
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
return GetCachedDataEndInternal(aOffset);
}
@ -2389,6 +2399,7 @@ MediaCacheStream::GetCachedDataEnd(int64_t aOffset)
bool
MediaCacheStream::IsDataCachedToEndOfStream(int64_t aOffset)
{
MOZ_ASSERT(!NS_IsMainThread());
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
if (mStreamLength < 0)
return false;
@ -2473,6 +2484,7 @@ MediaCacheStream::GetNextCachedDataInternal(int64_t aOffset)
void
MediaCacheStream::SetReadMode(ReadMode aMode)
{
// TODO: Assert non-main thread.
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
if (aMode == mCurrentMode)
return;
@ -2612,8 +2624,7 @@ MediaCacheStream::ReadBlockFromCache(int64_t aOffset,
nsresult
MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
{
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
MOZ_ASSERT(!NS_IsMainThread());
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
// Cache the offset in case it is changed again when we are waiting for the
@ -2710,8 +2721,7 @@ nsresult
MediaCacheStream::ReadAt(int64_t aOffset, char* aBuffer,
uint32_t aCount, uint32_t* aBytes)
{
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
MOZ_ASSERT(!NS_IsMainThread());
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
nsresult rv = Seek(aOffset);
if (NS_FAILED(rv)) return rv;
@ -2721,6 +2731,7 @@ MediaCacheStream::ReadAt(int64_t aOffset, char* aBuffer,
nsresult
MediaCacheStream::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount)
{
MOZ_ASSERT(!NS_IsMainThread());
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
// The buffer we are about to fill.
@ -2858,6 +2869,7 @@ MediaCacheStream::OwnerThread() const
nsresult MediaCacheStream::GetCachedRanges(MediaByteRangeSet& aRanges)
{
MOZ_ASSERT(!NS_IsMainThread());
// Take the monitor, so that the cached data ranges can't grow while we're
// trying to loop over them.
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());

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

@ -269,9 +269,6 @@ public:
void NotifyDataReceived(uint32_t aLoadID,
uint32_t aCount,
const uint8_t* aData);
// Notifies the cache that the current bytes should be written to disk.
// Called on the main thread.
void FlushPartialBlock();
// Set the load ID so the following NotifyDataEnded() call can work properly.
// Used in some rare cases where NotifyDataEnded() is called without the
@ -448,8 +445,7 @@ private:
// This method assumes that the cache monitor is held and can be called on
// any thread.
int64_t GetNextCachedDataInternal(int64_t aOffset);
// Writes |mPartialBlock| to disk.
// Used by |NotifyDataEnded| and |FlushPartialBlock|.
// Used by |NotifyDataEnded| to write |mPartialBlock| to disk.
// If |aNotifyAll| is true, this function will wake up readers who may be
// waiting on the media cache monitor. Called on the main thread only.
void FlushPartialBlockInternal(bool aNotify, ReentrantMonitorAutoEnter& aReentrantMonitor);

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

@ -529,6 +529,10 @@ MediaDecoder::OnPlaybackEvent(MediaEventType aEvent)
case MediaEventType::SeekStarted:
SeekingStarted();
break;
case MediaEventType::Loop:
GetOwner()->DispatchAsyncEvent(NS_LITERAL_STRING("seeking"));
GetOwner()->DispatchAsyncEvent(NS_LITERAL_STRING("seeked"));
break;
case MediaEventType::Invalidate:
Invalidate();
break;

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

@ -608,27 +608,7 @@ public:
mOnVideoPopped.DisconnectIfExists();
}
void Step() override
{
if (mMaster->mPlayState != MediaDecoder::PLAY_STATE_PLAYING &&
mMaster->IsPlaying()) {
// We're playing, but the element/decoder is in paused state. Stop
// playing!
mMaster->StopPlayback();
}
// Start playback if necessary so that the clock can be properly queried.
if (!mIsPrerolling) {
mMaster->MaybeStartPlayback();
}
mMaster->UpdatePlaybackPositionPeriodically();
MOZ_ASSERT(!mMaster->IsPlaying() || mMaster->IsStateMachineScheduled(),
"Must have timer scheduled");
MaybeStartBuffering();
}
void Step() override;
State GetState() const override
{
@ -1940,6 +1920,8 @@ public:
if (!mSentPlaybackEndedEvent) {
auto clockTime =
std::max(mMaster->AudioEndTime(), mMaster->VideoEndTime());
// Correct the time over the end once looping was turned on.
Reader()->AdjustByLooping(clockTime);
if (mMaster->mDuration.Ref()->IsInfinite()) {
// We have a finite duration when playback reaches the end.
mMaster->mDuration = Some(clockTime);
@ -2198,6 +2180,13 @@ DecodeMetadataState::OnMetadataRead(MetadataHolder&& aMetadata)
Move(aMetadata.mTags),
MediaDecoderEventVisibility::Observable);
// Check whether the media satisfies the requirement of seamless looing.
// (Before checking the media is audio only, we need to get metadata first.)
mMaster->mSeamlessLoopingAllowed = MediaPrefs::SeamlessLooping() &&
mMaster->HasAudio() &&
!mMaster->HasVideo();
mMaster->LoopingChanged();
SetState<DecodingFirstFrameState>();
}
@ -2302,6 +2291,57 @@ DecodingState::Enter()
}
}
void
MediaDecoderStateMachine::
DecodingState::Step()
{
if (mMaster->mPlayState != MediaDecoder::PLAY_STATE_PLAYING &&
mMaster->IsPlaying()) {
// We're playing, but the element/decoder is in paused state. Stop
// playing!
mMaster->StopPlayback();
}
// Start playback if necessary so that the clock can be properly queried.
if (!mIsPrerolling) {
mMaster->MaybeStartPlayback();
}
TimeUnit before = mMaster->GetMediaTime();
mMaster->UpdatePlaybackPositionPeriodically();
// Fire the `seeking` and `seeked` events to meet the HTML spec
// when the media is looped back from the end to the beginning.
if (before > mMaster->GetMediaTime()) {
MOZ_ASSERT(mMaster->mLooping);
mMaster->mOnPlaybackEvent.Notify(MediaEventType::Loop);
// After looping is cancelled, the time won't be corrected, and therefore we
// can check it to see if the end of the media track is reached. Make sure
// the media is started before comparing the time, or it's meaningless.
// Without checking IsStarted(), the media will be terminated immediately
// after seeking forward. When the state is just transited from seeking state,
// GetClock() is smaller than GetMediaTime(), since GetMediaTime() is updated
// upon seek is completed while GetClock() will be updated after the media is
// started again.
} else if (mMaster->mMediaSink->IsStarted() && !mMaster->mLooping) {
TimeUnit adjusted = mMaster->GetClock();
Reader()->AdjustByLooping(adjusted);
if (adjusted < before) {
mMaster->StopPlayback();
mMaster->mAudioDataRequest.DisconnectIfExists();
AudioQueue().Finish();
mMaster->mAudioCompleted = true;
SetState<CompletedState>();
return;
}
}
MOZ_ASSERT(!mMaster->IsPlaying() || mMaster->IsStateMachineScheduled(),
"Must have timer scheduled");
MaybeStartBuffering();
}
void
MediaDecoderStateMachine::
DecodingState::HandleEndOfAudio()
@ -2621,6 +2661,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
mOutputStreamManager(new OutputStreamManager()),
mVideoDecodeMode(VideoDecodeMode::Normal),
mIsMSE(aDecoder->IsMSE()),
mSeamlessLoopingAllowed(false),
INIT_MIRROR(mBuffered, TimeIntervals()),
INIT_MIRROR(mPlayState, MediaDecoder::PLAY_STATE_LOADING),
INIT_MIRROR(mVolume, 1.0),
@ -2677,6 +2718,7 @@ MediaDecoderStateMachine::InitializationTask(MediaDecoder* aDecoder)
mWatchManager.Watch(mPreservesPitch,
&MediaDecoderStateMachine::PreservesPitchChanged);
mWatchManager.Watch(mPlayState, &MediaDecoderStateMachine::PlayStateChanged);
mWatchManager.Watch(mLooping, &MediaDecoderStateMachine::LoopingChanged);
MOZ_ASSERT(!mStateObj);
auto* s = new DecodeMetadataState(this);
@ -3464,7 +3506,13 @@ MediaDecoderStateMachine::UpdatePlaybackPositionPeriodically()
// advance the clock to after the media end time.
if (VideoEndTime() > TimeUnit::Zero() || AudioEndTime() > TimeUnit::Zero()) {
const auto clockTime = GetClock();
auto clockTime = GetClock();
// Once looping was turned on, the time is probably larger than the duration
// of the media track, so the time over the end should be corrected.
mReader->AdjustByLooping(clockTime);
bool loopback = clockTime < GetMediaTime() && mLooping;
// Skip frames up to the frame at the playback position, and figure out
// the time remaining until it's time to display the next frame and drop
// the current frame.
@ -3476,7 +3524,7 @@ MediaDecoderStateMachine::UpdatePlaybackPositionPeriodically()
auto t = std::min(clockTime, maxEndTime);
// FIXME: Bug 1091422 - chained ogg files hit this assertion.
//MOZ_ASSERT(t >= GetMediaTime());
if (t > GetMediaTime()) {
if (loopback || t > GetMediaTime()) {
UpdatePlaybackPosition(t);
}
}
@ -3560,6 +3608,15 @@ void MediaDecoderStateMachine::PreservesPitchChanged()
mMediaSink->SetPreservesPitch(mPreservesPitch);
}
void
MediaDecoderStateMachine::LoopingChanged()
{
MOZ_ASSERT(OnTaskQueue());
if (mSeamlessLoopingAllowed) {
mReader->SetSeamlessLoopingEnabled(mLooping);
}
}
TimeUnit
MediaDecoderStateMachine::AudioEndTime() const
{

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

@ -120,6 +120,7 @@ enum class MediaEventType : int8_t
PlaybackStopped,
PlaybackEnded,
SeekStarted,
Loop,
Invalidate,
EnterVideoSuspend,
ExitVideoSuspend,
@ -359,6 +360,7 @@ protected:
void VolumeChanged();
void SetPlaybackRate(double aPlaybackRate);
void PreservesPitchChanged();
void LoopingChanged();
MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; }
MediaQueue<VideoData>& VideoQueue() { return mVideoQueue; }
@ -666,6 +668,8 @@ private:
const bool mIsMSE;
bool mSeamlessLoopingAllowed;
private:
// The buffered range. Mirrored from the decoder thread.
Mirror<media::TimeIntervals> mBuffered;

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

@ -203,6 +203,8 @@ private:
DECL_MEDIA_PREF("media.cubeb.sandbox", CubebSandbox, bool, false);
DECL_MEDIA_PREF("media.videocontrols.lock-video-orientation", VideoOrientationLockEnabled, bool, false);
// Media Seamless Looping
DECL_MEDIA_PREF("media.seamless-looping", SeamlessLooping, bool, true);
public:
// Manage the singleton:
static MediaPrefs& GetSingleton();

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

@ -19,6 +19,8 @@ ReaderProxy::ReaderProxy(AbstractThread* aOwnerThread,
, mDuration(aReader->OwnerThread(),
media::NullableTimeUnit(),
"ReaderProxy::mDuration (Mirror)")
, mSeamlessLoopingBlocked(false)
, mSeamlessLoopingEnabled(false)
{
// Must support either heuristic buffering or WaitForData().
MOZ_ASSERT(mReader->UseBufferingHeuristics() ||
@ -32,7 +34,6 @@ media::TimeUnit
ReaderProxy::StartTime() const
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
MOZ_ASSERT(!mShutdown);
return mStartTime.ref();
}
@ -52,26 +53,79 @@ ReaderProxy::ReadMetadata()
&ReaderProxy::OnMetadataNotRead);
}
RefPtr<ReaderProxy::AudioDataPromise>
ReaderProxy::OnAudioDataRequestCompleted(RefPtr<AudioData> aAudio)
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
// Subtract the start time and add the looping-offset time.
int64_t offset =
StartTime().ToMicroseconds() - mLoopingOffset.ToMicroseconds();
aAudio->AdjustForStartTime(offset);
mLastAudioEndTime = aAudio->mTime;
return AudioDataPromise::CreateAndResolve(aAudio.forget(), __func__);
}
RefPtr<ReaderProxy::AudioDataPromise>
ReaderProxy::OnAudioDataRequestFailed(const MediaResult& aError)
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
if (mSeamlessLoopingBlocked || !mSeamlessLoopingEnabled ||
aError.Code() != NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
return AudioDataPromise::CreateAndReject(aError, __func__);
}
// The data time in the audio queue is assumed to be increased linearly,
// so we need to add the last ending time as the offset to correct the
// audio data time in the next round when seamless looping is enabled.
mLoopingOffset = mLastAudioEndTime;
// Save the duration of the audio track if it hasn't been set.
if (!mAudioDuration.IsValid()) {
mAudioDuration = mLastAudioEndTime;
}
// For seamless looping, the demuxer is sought to the beginning and then
// keep requesting decoded data in advance, upon receiving EOS.
// The MDSM will not be aware of the EOS and keep receiving decoded data
// as usual while looping is on.
RefPtr<ReaderProxy> self = this;
RefPtr<MediaFormatReader> reader = mReader;
ResetDecode(TrackInfo::kAudioTrack);
return SeekInternal(SeekTarget(media::TimeUnit::Zero(), SeekTarget::Accurate))
->Then(mReader->OwnerThread(),
__func__,
[reader]() { return reader->RequestAudioData(); },
[](const SeekRejectValue& aReject) {
return AudioDataPromise::CreateAndReject(aReject.mError, __func__);
})
->Then(mOwnerThread,
__func__,
[self](RefPtr<AudioData> aAudio) {
return self->OnAudioDataRequestCompleted(aAudio.forget());
},
[](const MediaResult& aError) {
return AudioDataPromise::CreateAndReject(aError, __func__);
});
}
RefPtr<ReaderProxy::AudioDataPromise>
ReaderProxy::RequestAudioData()
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
MOZ_ASSERT(!mShutdown);
int64_t startTime = StartTime().ToMicroseconds();
mSeamlessLoopingBlocked = false;
return InvokeAsync(mReader->OwnerThread(),
mReader.get(),
__func__,
&MediaFormatReader::RequestAudioData)
->Then(mOwnerThread,
__func__,
[startTime](RefPtr<AudioData> aAudio) {
aAudio->AdjustForStartTime(startTime);
return AudioDataPromise::CreateAndResolve(aAudio.forget(), __func__);
},
[](const MediaResult& aError) {
return AudioDataPromise::CreateAndReject(aError, __func__);
});
this,
&ReaderProxy::OnAudioDataRequestCompleted,
&ReaderProxy::OnAudioDataRequestFailed);
}
RefPtr<ReaderProxy::VideoDataPromise>
@ -80,6 +134,7 @@ ReaderProxy::RequestVideoData(const media::TimeUnit& aTimeThreshold)
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
MOZ_ASSERT(!mShutdown);
mSeamlessLoopingBlocked = false;
const auto threshold = aTimeThreshold > media::TimeUnit::Zero()
? aTimeThreshold + StartTime()
: aTimeThreshold;
@ -104,6 +159,18 @@ ReaderProxy::RequestVideoData(const media::TimeUnit& aTimeThreshold)
RefPtr<ReaderProxy::SeekPromise>
ReaderProxy::Seek(const SeekTarget& aTarget)
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
mSeamlessLoopingBlocked = true;
// Reset the members for seamless looping if the seek is triggered outside.
mLoopingOffset = media::TimeUnit::Zero();
mLastAudioEndTime = media::TimeUnit::Zero();
mAudioDuration = media::TimeUnit::Invalid();
return SeekInternal(aTarget);
}
RefPtr<ReaderProxy::SeekPromise>
ReaderProxy::SeekInternal(const SeekTarget& aTarget)
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
SeekTarget adjustedTarget = aTarget;
@ -222,4 +289,22 @@ ReaderProxy::SetCanonicalDuration(
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
}
void
ReaderProxy::SetSeamlessLoopingEnabled(bool aEnabled)
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
mSeamlessLoopingEnabled = aEnabled;
}
void
ReaderProxy::AdjustByLooping(media::TimeUnit& aTime)
{
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
MOZ_ASSERT(!mShutdown);
MOZ_ASSERT(!mSeamlessLoopingEnabled || !mSeamlessLoopingBlocked);
if (mAudioDuration.IsValid() && mAudioDuration.IsPositive()) {
aTime = aTime % mAudioDuration.ToMicroseconds();
}
}
} // namespace mozilla

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

@ -84,11 +84,21 @@ public:
void SetCanonicalDuration(
AbstractCanonical<media::NullableTimeUnit>* aCanonical);
void SetSeamlessLoopingEnabled(bool aEnabled);
void AdjustByLooping(media::TimeUnit& aTime);
private:
~ReaderProxy();
RefPtr<MetadataPromise> OnMetadataRead(MetadataHolder&& aMetadata);
RefPtr<MetadataPromise> OnMetadataNotRead(const MediaResult& aError);
void UpdateDuration();
RefPtr<SeekPromise> SeekInternal(const SeekTarget& aTarget);
RefPtr<ReaderProxy::AudioDataPromise> OnAudioDataRequestCompleted(
RefPtr<AudioData> aAudio);
RefPtr<ReaderProxy::AudioDataPromise> OnAudioDataRequestFailed(
const MediaResult& aError);
const RefPtr<AbstractThread> mOwnerThread;
const RefPtr<MediaFormatReader> mReader;
@ -101,6 +111,18 @@ private:
// Duration, mirrored from the state machine task queue.
Mirror<media::NullableTimeUnit> mDuration;
// The total duration of audio looping in previous rounds.
media::TimeUnit mLoopingOffset = media::TimeUnit::Zero();
// To keep tracking the latest time of decoded audio data.
media::TimeUnit mLastAudioEndTime = media::TimeUnit::Zero();
// The duration of the audio track.
media::TimeUnit mAudioDuration = media::TimeUnit::Invalid();
// To prevent seamless looping while seeking.
bool mSeamlessLoopingBlocked;
// Indicates whether we should loop the media.
bool mSeamlessLoopingEnabled;
};
} // namespace mozilla

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

@ -5,7 +5,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/TextTrackRegion.h"
#include "mozilla/dom/VTTRegionBinding.h"
namespace mozilla {
namespace dom {
@ -45,6 +44,7 @@ TextTrackRegion::TextTrackRegion(nsISupports* aGlobal)
, mRegionAnchorY(100)
, mViewportAnchorX(0)
, mViewportAnchorY(100)
, mScroll(ScrollSetting::_empty)
{
}

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

@ -12,6 +12,7 @@
#include "nsWrapperCache.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/TextTrack.h"
#include "mozilla/dom/VTTRegionBinding.h"
#include "mozilla/Preferences.h"
namespace mozilla {
@ -47,9 +48,13 @@ public:
return mLines;
}
void SetLines(double aLines)
void SetLines(double aLines, ErrorResult& aRv)
{
mLines = aLines;
if (aLines < 0) {
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
} else {
mLines = aLines;
}
}
double Width() const
@ -112,19 +117,16 @@ public:
}
}
void GetScroll(nsAString& aScroll) const
ScrollSetting Scroll() const
{
aScroll = mScroll;
return mScroll;
}
void SetScroll(const nsAString& aScroll, ErrorResult& aRv)
void SetScroll(const ScrollSetting& aScroll)
{
if (!aScroll.EqualsLiteral("") && !aScroll.EqualsLiteral("up")) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
if (aScroll == ScrollSetting::_empty || aScroll == ScrollSetting::Up) {
mScroll = aScroll;
}
mScroll = aScroll;
}
void GetId(nsAString& aId) const
@ -145,10 +147,6 @@ public:
void CopyValues(TextTrackRegion& aRegion);
// -----helpers-------
const nsAString& Scroll() const
{
return mScroll;
}
const nsAString& Id() const
{
return mId;
@ -165,7 +163,7 @@ private:
double mRegionAnchorY;
double mViewportAnchorX;
double mViewportAnchorY;
nsString mScroll;
ScrollSetting mScroll;
// Helper to ensure new value is in the range: 0.0% - 100.0%; throws
// an IndexSizeError otherwise.

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

@ -177,6 +177,10 @@ public:
{
return TimeUnit(aUnit.mValue / aVal);
}
friend TimeUnit operator%(const TimeUnit& aUnit, int aVal)
{
return TimeUnit(aUnit.mValue % aVal);
}
bool IsValid() const { return mValue.isValid(); }

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

@ -52,11 +52,11 @@ void* CreateCdmInstance(int cdm_interface_version,
GetCdmHostFunc get_cdm_host_func,
void* user_data)
{
if (cdm_interface_version != cdm::ContentDecryptionModule_8::kVersion) {
// Only support CDM version 8 currently.
if (cdm_interface_version != cdm::ContentDecryptionModule_9::kVersion) {
// Only support CDM version 9 currently.
return nullptr;
}
cdm::Host_8* host = static_cast<cdm::Host_8*>(
cdm::Host_9* host = static_cast<cdm::Host_9*>(
get_cdm_host_func(cdm_interface_version, user_data));
return new FakeDecryptor(host);
}

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

@ -76,7 +76,7 @@ private:
set<string> mTestIDs;
};
FakeDecryptor::FakeDecryptor(cdm::Host_8* aHost)
FakeDecryptor::FakeDecryptor(cdm::Host_9* aHost)
: mHost(aHost)
{
MOZ_ASSERT(!sInstance);
@ -92,9 +92,7 @@ FakeDecryptor::Message(const std::string& aMessage)
sid.size(),
cdm::MessageType::kLicenseRequest,
aMessage.c_str(),
aMessage.size(),
nullptr,
0);
aMessage.size());
}
std::vector<std::string>

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

@ -10,15 +10,20 @@
#include <string>
#include "mozilla/Attributes.h"
class FakeDecryptor : public cdm::ContentDecryptionModule_8 {
class FakeDecryptor : public cdm::ContentDecryptionModule_9 {
public:
explicit FakeDecryptor(cdm::Host_8* aHost);
explicit FakeDecryptor(cdm::Host_9* aHost);
void Initialize(bool aAllowDistinctiveIdentifier,
bool aAllowPersistentState) override
{
}
void GetStatusForPolicy(uint32_t aPromiseId,
const cdm::Policy& aPolicy) override
{
}
void SetServerCertificate(uint32_t aPromiseId,
const uint8_t* aServerCertificateData,
uint32_t aServerCertificateDataSize)
@ -115,6 +120,12 @@ public:
{
}
void OnStorageId(uint32_t aVersion,
const uint8_t* aStorageId,
uint32_t aStorageIdSize) override
{
}
void Destroy() override
{
delete this;
@ -123,7 +134,7 @@ public:
static void Message(const std::string& aMessage);
cdm::Host_8* mHost;
cdm::Host_9* mHost;
static FakeDecryptor* sInstance;

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

@ -46,7 +46,7 @@ public:
Done(aStatus);
}
void Do(const string& aName, Host_8* aHost)
void Do(const string& aName, Host_9* aHost)
{
// Initialize the FileIO.
mFileIO = aHost->CreateFileIO(this);
@ -82,7 +82,7 @@ private:
};
void
WriteRecord(Host_8* aHost,
WriteRecord(Host_9* aHost,
const std::string& aRecordName,
const uint8_t* aData,
uint32_t aNumBytes,
@ -98,7 +98,7 @@ WriteRecord(Host_8* aHost,
}
void
WriteRecord(Host_8* aHost,
WriteRecord(Host_9* aHost,
const std::string& aRecordName,
const std::string& aData,
function<void()> &&aOnSuccess,
@ -141,7 +141,7 @@ public:
{
}
void Do(const string& aName, Host_8* aHost)
void Do(const string& aName, Host_9* aHost)
{
mFileIO = aHost->CreateFileIO(this);
mFileIO->Open(aName.c_str(), aName.size());
@ -176,7 +176,7 @@ private:
};
void
ReadRecord(Host_8* aHost,
ReadRecord(Host_9* aHost,
const std::string& aRecordName,
function<void(bool, const uint8_t*, uint32_t)>&& aOnReadComplete)
{
@ -208,7 +208,7 @@ public:
{
}
void Do(const string& aName, Host_8* aHost)
void Do(const string& aName, Host_9* aHost)
{
// Initialize the FileIO.
mFileIO = aHost->CreateFileIO(this);
@ -242,7 +242,7 @@ private:
};
void
OpenRecord(Host_8* aHost,
OpenRecord(Host_9* aHost,
const std::string& aRecordName,
function<void(bool)>&& aOpenComplete)
{

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

@ -25,20 +25,20 @@ public:
uint32_t aDataSize) = 0;
};
void WriteRecord(cdm::Host_8* aHost,
void WriteRecord(cdm::Host_9* aHost,
const std::string& aRecordName,
const std::string& aData,
std::function<void()>&& aOnSuccess,
std::function<void()>&& aOnFailure);
void WriteRecord(cdm::Host_8* aHost,
void WriteRecord(cdm::Host_9* aHost,
const std::string& aRecordName,
const uint8_t* aData,
uint32_t aNumBytes,
std::function<void()>&& aOnSuccess,
std::function<void()>&& aOnFailure);
void ReadRecord(cdm::Host_8* aHost,
void ReadRecord(cdm::Host_9* aHost,
const std::string& aRecordName,
std::function<void(bool, const uint8_t*, uint32_t)>&& aOnReadComplete);
@ -48,7 +48,7 @@ public:
virtual void operator()(bool aSuccess) = 0;
};
void OpenRecord(cdm::Host_8* aHost,
void OpenRecord(cdm::Host_9* aHost,
const std::string& aRecordName,
std::function<void(bool)>&& aOpenComplete);
#endif // TEST_CDM_STORAGE_H__

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

@ -3,7 +3,7 @@
"description": "Fake CDM Plugin",
"version": "1",
"x-cdm-module-versions": "4",
"x-cdm-interface-versions": "8",
"x-cdm-host-versions": "8",
"x-cdm-interface-versions": "9",
"x-cdm-host-versions": "9",
"x-cdm-codecs": ""
}

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

@ -11,6 +11,7 @@
#include "GMPVideoEncoderChild.h"
#include "GMPVideoHost.h"
#include "nsDebugImpl.h"
#include "nsExceptionHandler.h"
#include "nsIFile.h"
#include "nsXULAppAPI.h"
#include "gmp-video-decode.h"
@ -545,7 +546,19 @@ GMPChild::AnswerStartPlugin(const nsString& aAdapter)
nsCString libPath;
if (!GetUTF8LibPath(libPath)) {
return IPC_FAIL_NO_REASON(this);
CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("GMPLibraryPath"),
NS_ConvertUTF16toUTF8(mPluginPath));
#ifdef XP_WIN
return IPC_FAIL(
this,
nsPrintfCString("Failed to get lib path with error(%d).", GetLastError())
.get());
#else
return IPC_FAIL(
this,
"Failed to get lib path.");
#endif
}
auto platformAPI = new GMPPlatformAPI();
@ -556,7 +569,7 @@ GMPChild::AnswerStartPlugin(const nsString& aAdapter)
if (!mGMPLoader->CanSandbox()) {
LOGD("%s Can't sandbox GMP, failing", __FUNCTION__);
delete platformAPI;
return IPC_FAIL_NO_REASON(this);
return IPC_FAIL(this, "Can't sandbox GMP.");
}
#endif
bool isChromium = aAdapter.EqualsLiteral("chromium");
@ -568,7 +581,10 @@ GMPChild::AnswerStartPlugin(const nsString& aAdapter)
if (!SetMacSandboxInfo(pluginType)) {
NS_WARNING("Failed to set Mac GMP sandbox info");
delete platformAPI;
return IPC_FAIL_NO_REASON(this);
return IPC_FAIL(
this,
nsPrintfCString("Failed to set Mac GMP sandbox info with plugin type %d.",
pluginType).get());
}
#endif
@ -585,7 +601,19 @@ GMPChild::AnswerStartPlugin(const nsString& aAdapter)
adapter)) {
NS_WARNING("Failed to load GMP");
delete platformAPI;
return IPC_FAIL_NO_REASON(this);
CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("GMPLibraryPath"),
NS_ConvertUTF16toUTF8(mPluginPath));
#ifdef XP_WIN
return IPC_FAIL(
this,
nsPrintfCString("Failed to load GMP with error(%d).", GetLastError())
.get());
#else
return IPC_FAIL(
this,
"Failed to load GMP.");
#endif
}
return IPC_OK();
@ -623,21 +651,25 @@ GMPChild::ActorDestroy(ActorDestroyReason aWhy)
void
GMPChild::ProcessingError(Result aCode, const char* aReason)
{
if (!aReason) {
aReason = "";
}
switch (aCode) {
case MsgDropped:
_exit(0); // Don't trigger a crash report.
case MsgNotKnown:
MOZ_CRASH("aborting because of MsgNotKnown");
MOZ_CRASH_UNSAFE_PRINTF("aborting because of MsgNotKnown, reason(%s)", aReason);
case MsgNotAllowed:
MOZ_CRASH("aborting because of MsgNotAllowed");
MOZ_CRASH_UNSAFE_PRINTF("aborting because of MsgNotAllowed, reason(%s)", aReason);
case MsgPayloadError:
MOZ_CRASH("aborting because of MsgPayloadError");
MOZ_CRASH_UNSAFE_PRINTF("aborting because of MsgPayloadError, reason(%s)", aReason);
case MsgProcessingError:
MOZ_CRASH("aborting because of MsgProcessingError");
MOZ_CRASH_UNSAFE_PRINTF("aborting because of MsgProcessingError, reason(%s)", aReason);
case MsgRouteError:
MOZ_CRASH("aborting because of MsgRouteError");
MOZ_CRASH_UNSAFE_PRINTF("aborting because of MsgRouteError, reason(%s)", aReason);
case MsgValueError:
MOZ_CRASH("aborting because of MsgValueError");
MOZ_CRASH_UNSAFE_PRINTF("aborting because of MsgValueError, reason(%s)", aReason);
default:
MOZ_CRASH("not reached");
}

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

@ -52,7 +52,7 @@ ResolutionFeasibilityDistance(int32_t candidate, int32_t requested)
if (candidate >= requested) {
distance = (candidate - requested) * 1000 / std::max(candidate, requested);
} else {
distance = (UINT32_MAX / 2) + (requested - candidate) *
distance = 10000 + (requested - candidate) *
1000 / std::max(candidate, requested);
}
return distance;
@ -862,14 +862,14 @@ CamerasParent::RecvStartCapture(const CaptureEngine& aCapEngine,
capability.codecType = static_cast<webrtc::VideoCodecType>(ipcCaps.codecType());
capability.interlaced = ipcCaps.interlaced();
if (aCapEngine == CameraEngine) {
#ifdef DEBUG
auto deviceUniqueID = sDeviceUniqueIDs.find(capnum);
MOZ_ASSERT(deviceUniqueID == sDeviceUniqueIDs.end());
auto deviceUniqueID = sDeviceUniqueIDs.find(capnum);
MOZ_ASSERT(deviceUniqueID == sDeviceUniqueIDs.end());
#endif
sDeviceUniqueIDs.emplace(capnum, cap.VideoCapture()->CurrentDeviceName());
sAllRequestedCapabilities.emplace(capnum, capability);
sDeviceUniqueIDs.emplace(capnum, cap.VideoCapture()->CurrentDeviceName());
sAllRequestedCapabilities.emplace(capnum, capability);
if (aCapEngine == CameraEngine) {
for (const auto &it : sDeviceUniqueIDs) {
if (strcmp(it.second, cap.VideoCapture()->CurrentDeviceName()) == 0) {
capability.width = std::max(
@ -908,6 +908,16 @@ CamerasParent::RecvStartCapture(const CaptureEngine& aCapEngine,
}
MOZ_ASSERT(minIdx != -1);
capability = candidateCapabilities->second[minIdx];
} else if (aCapEngine == ScreenEngine ||
aCapEngine == BrowserEngine ||
aCapEngine == WinEngine ||
aCapEngine == AppEngine) {
for (const auto &it : sDeviceUniqueIDs) {
if (strcmp(it.second, cap.VideoCapture()->CurrentDeviceName()) == 0) {
capability.maxFPS = std::max(
capability.maxFPS, sAllRequestedCapabilities[it.first].maxFPS);
}
}
}
error = cap.VideoCapture()->StartCapture(capability);
@ -949,16 +959,14 @@ CamerasParent::StopCapture(const CaptureEngine& aCapEngine,
mCallbacks[i - 1]->mStreamId == (uint32_t)capnum) {
CallbackHelper* cbh = mCallbacks[i-1];
engine->WithEntry(capnum,[cbh, &capnum, &aCapEngine](VideoEngine::CaptureEntry& cap){
engine->WithEntry(capnum,[cbh, &capnum](VideoEngine::CaptureEntry& cap){
if (cap.VideoCapture()) {
cap.VideoCapture()->DeRegisterCaptureDataCallback(
static_cast<rtc::VideoSinkInterface<webrtc::VideoFrame>*>(cbh));
cap.VideoCapture()->StopCaptureIfAllClientsClose();
if (aCapEngine == CameraEngine) {
sDeviceUniqueIDs.erase(capnum);
sAllRequestedCapabilities.erase(capnum);
}
sDeviceUniqueIDs.erase(capnum);
sAllRequestedCapabilities.erase(capnum);
}
});

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

@ -217,6 +217,7 @@ public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AllocationHandle);
protected:
~AllocationHandle() {}
static uint64_t sId;
public:
AllocationHandle(const dom::MediaTrackConstraints& aConstraints,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
@ -226,12 +227,14 @@ public:
: mConstraints(aConstraints),
mPrincipalInfo(aPrincipalInfo),
mPrefs(aPrefs),
mDeviceId(aDeviceId) {}
mDeviceId(aDeviceId),
mId(sId++) {}
public:
NormalizedConstraints mConstraints;
mozilla::ipc::PrincipalInfo mPrincipalInfo;
MediaEnginePrefs mPrefs;
nsString mDeviceId;
uint64_t mId;
};
/* Release the device back to the system. */
@ -366,6 +369,7 @@ protected:
virtual nsresult
UpdateSingleSource(const AllocationHandle* aHandle,
const NormalizedConstraints& aNetConstraints,
const NormalizedConstraints& aNewConstraint,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint) {
@ -394,6 +398,7 @@ protected:
// aHandle and/or aConstraintsUpdate may be nullptr (see below)
AutoTArray<const NormalizedConstraints*, 10> allConstraints;
AutoTArray<const NormalizedConstraints*, 1> updatedConstraint;
for (auto& registered : mRegisteredHandles) {
if (aConstraintsUpdate && registered.get() == aHandle) {
continue; // Don't count old constraints
@ -402,9 +407,13 @@ protected:
}
if (aConstraintsUpdate) {
allConstraints.AppendElement(aConstraintsUpdate);
updatedConstraint.AppendElement(aConstraintsUpdate);
} else if (aHandle) {
// In the case of AddShareOfSingleSource, the handle isn't registered yet.
allConstraints.AppendElement(&aHandle->mConstraints);
updatedConstraint.AppendElement(&aHandle->mConstraints);
} else {
updatedConstraint.AppendElements(allConstraints);
}
NormalizedConstraints netConstraints(allConstraints);
@ -413,7 +422,8 @@ protected:
return NS_ERROR_FAILURE;
}
nsresult rv = UpdateSingleSource(aHandle, netConstraints, aPrefs, aDeviceId,
NormalizedConstraints newConstraint(updatedConstraint);
nsresult rv = UpdateSingleSource(aHandle, netConstraints, newConstraint, aPrefs, aDeviceId,
aOutBadConstraint);
if (NS_FAILED(rv)) {
return rv;

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

@ -25,10 +25,20 @@ bool MediaEngineCameraVideoSource::AppendToTrack(SourceMediaStream* aSource,
const PrincipalHandle& aPrincipalHandle)
{
MOZ_ASSERT(aSource);
MOZ_ASSERT(aImage);
if (!aImage) {
return 0;
}
VideoSegment segment;
RefPtr<layers::Image> image = aImage;
IntSize size(image ? mWidth : 0, image ? mHeight : 0);
IntSize size = image->GetSize();
if (!size.width || !size.height) {
return 0;
}
segment.AppendFrame(image.forget(), delta, size, aPrincipalHandle);
// This is safe from any thread, and is safe if the track is Finished
@ -54,6 +64,19 @@ MediaEngineCameraVideoSource::GetCapability(size_t aIndex,
aOut = mHardcodedCapabilities.SafeElementAt(aIndex, webrtc::CaptureCapability());
}
uint32_t
MediaEngineCameraVideoSource::GetDistance(
const webrtc::CaptureCapability& aCandidate,
const NormalizedConstraintSet &aConstraints,
const nsString& aDeviceId,
const DistanceCalculation aCalculate) const
{
if (aCalculate == kFeasibility) {
return GetFeasibilityDistance(aCandidate, aConstraints, aDeviceId);
}
return GetFitnessDistance(aCandidate, aConstraints, aDeviceId);
}
uint32_t
MediaEngineCameraVideoSource::GetFitnessDistance(
const webrtc::CaptureCapability& aCandidate,
@ -75,6 +98,27 @@ MediaEngineCameraVideoSource::GetFitnessDistance(
return uint32_t(std::min(distance, uint64_t(UINT32_MAX)));
}
uint32_t
MediaEngineCameraVideoSource::GetFeasibilityDistance(
const webrtc::CaptureCapability& aCandidate,
const NormalizedConstraintSet &aConstraints,
const nsString& aDeviceId) const
{
// Treat width|height|frameRate == 0 on capability as "can do any".
// This allows for orthogonal capabilities that are not in discrete steps.
uint64_t distance =
uint64_t(FitnessDistance(aDeviceId, aConstraints.mDeviceId)) +
uint64_t(FitnessDistance(mFacingMode, aConstraints.mFacingMode)) +
uint64_t(aCandidate.width? FeasibilityDistance(int32_t(aCandidate.width),
aConstraints.mWidth) : 0) +
uint64_t(aCandidate.height? FeasibilityDistance(int32_t(aCandidate.height),
aConstraints.mHeight) : 0) +
uint64_t(aCandidate.maxFPS? FeasibilityDistance(double(aCandidate.maxFPS),
aConstraints.mFrameRate) : 0);
return uint32_t(std::min(distance, uint64_t(UINT32_MAX)));
}
// Find best capability by removing inferiors. May leave >1 of equal distance
/* static */ void
@ -218,7 +262,9 @@ bool
MediaEngineCameraVideoSource::ChooseCapability(
const NormalizedConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId)
const nsString& aDeviceId,
webrtc::CaptureCapability& aCapability,
const DistanceCalculation aCalculate)
{
if (MOZ_LOG_TEST(GetMediaManagerLog(), LogLevel::Debug)) {
LOG(("ChooseCapability: prefs: %dx%d @%dfps",
@ -246,7 +292,7 @@ MediaEngineCameraVideoSource::ChooseCapability(
auto& candidate = candidateSet[i];
webrtc::CaptureCapability cap;
GetCapability(candidate.mIndex, cap);
candidate.mDistance = GetFitnessDistance(cap, aConstraints, aDeviceId);
candidate.mDistance = GetDistance(cap, aConstraints, aDeviceId, aCalculate);
LogCapability("Capability", cap, candidate.mDistance);
if (candidate.mDistance == UINT32_MAX) {
candidateSet.RemoveElementAt(i);
@ -268,7 +314,7 @@ MediaEngineCameraVideoSource::ChooseCapability(
auto& candidate = candidateSet[i];
webrtc::CaptureCapability cap;
GetCapability(candidate.mIndex, cap);
if (GetFitnessDistance(cap, cs, aDeviceId) == UINT32_MAX) {
if (GetDistance(cap, cs, aDeviceId, aCalculate) == UINT32_MAX) {
rejects.AppendElement(candidate);
candidateSet.RemoveElementAt(i);
} else {
@ -299,7 +345,7 @@ MediaEngineCameraVideoSource::ChooseCapability(
for (auto& candidate : candidateSet) {
webrtc::CaptureCapability cap;
GetCapability(candidate.mIndex, cap);
candidate.mDistance = GetFitnessDistance(cap, normPrefs, aDeviceId);
candidate.mDistance = GetDistance(cap, normPrefs, aDeviceId, aCalculate);
}
TrimLessFitCandidates(candidateSet);
}
@ -315,13 +361,13 @@ MediaEngineCameraVideoSource::ChooseCapability(
if (cap.rawType == webrtc::RawVideoType::kVideoI420 ||
cap.rawType == webrtc::RawVideoType::kVideoYUY2 ||
cap.rawType == webrtc::RawVideoType::kVideoYV12) {
mCapability = cap;
aCapability = cap;
found = true;
break;
}
}
if (!found) {
GetCapability(candidateSet[0].mIndex, mCapability);
GetCapability(candidateSet[0].mIndex, aCapability);
}
LogCapability("Chosen capability", mCapability, sameDistance);

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

@ -24,6 +24,19 @@ namespace webrtc {
namespace mozilla {
// Fitness distance is defined in
// https://www.w3.org/TR/2017/CR-mediacapture-streams-20171003/#dfn-selectsettings
// The main difference of feasibility and fitness distance is that if the
// constraint is required ('max', or 'exact'), and the settings dictionary's value
// for the constraint does not satisfy the constraint, the fitness distance is
// positive infinity. Given a continuous space of settings dictionaries comprising
// all discrete combinations of dimension and frame-rate related properties,
// the feasibility distance is still in keeping with the constraints algorithm.
enum DistanceCalculation {
kFitness,
kFeasibility
};
class MediaEngineCameraVideoSource : public MediaEngineVideoSource
{
public:
@ -86,9 +99,16 @@ protected:
TrackID aID,
StreamTime delta,
const PrincipalHandle& aPrincipalHandle);
uint32_t GetDistance(const webrtc::CaptureCapability& aCandidate,
const NormalizedConstraintSet &aConstraints,
const nsString& aDeviceId,
const DistanceCalculation aCalculate) const;
uint32_t GetFitnessDistance(const webrtc::CaptureCapability& aCandidate,
const NormalizedConstraintSet &aConstraints,
const nsString& aDeviceId) const;
uint32_t GetFeasibilityDistance(const webrtc::CaptureCapability& aCandidate,
const NormalizedConstraintSet &aConstraints,
const nsString& aDeviceId) const;
static void TrimLessFitCandidates(CapabilitySet& set);
static void LogConstraints(const NormalizedConstraintSet& aConstraints);
static void LogCapability(const char* aHeader,
@ -96,9 +116,13 @@ protected:
uint32_t aDistance);
virtual size_t NumCapabilities() const;
virtual void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut) const;
virtual bool ChooseCapability(const NormalizedConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId);
virtual bool ChooseCapability(
const NormalizedConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
webrtc::CaptureCapability& aCapability,
const DistanceCalculation aCalculate
);
void SetName(nsString aName);
void SetUUID(const char* aUUID);
const nsCString& GetUUID() const; // protected access
@ -116,6 +140,9 @@ protected:
nsTArray<RefPtr<SourceMediaStream>> mSources; // When this goes empty, we shut down HW
nsTArray<PrincipalHandle> mPrincipalHandles; // Directly mapped to mSources.
RefPtr<layers::Image> mImage;
nsTArray<RefPtr<layers::Image>> mImages;
nsTArray<webrtc::CaptureCapability> mTargetCapabilities;
nsTArray<uint64_t> mHandleIds;
RefPtr<layers::ImageContainer> mImageContainer;
// end of data protected by mMonitor
@ -125,6 +152,8 @@ protected:
TrackID mTrackID;
webrtc::CaptureCapability mCapability;
webrtc::CaptureCapability mTargetCapability;
uint64_t mHandleId;
mutable nsTArray<webrtc::CaptureCapability> mHardcodedCapabilities;
private:

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

@ -10,6 +10,9 @@
#include "nsIPrefService.h"
#include "MediaTrackConstraints.h"
#include "CamerasChild.h"
#include "VideoFrameUtils.h"
#include "webrtc/api/video/i420_buffer.h"
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
extern mozilla::LogModule* GetMediaManagerLog();
#define LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
@ -17,6 +20,8 @@ extern mozilla::LogModule* GetMediaManagerLog();
namespace mozilla {
uint64_t MediaEngineCameraVideoSource::AllocationHandle::sId = 0;
// These need a definition somewhere because template
// code is allowed to take their address, and they aren't
// guaranteed to have one without this.
@ -80,6 +85,9 @@ MediaEngineRemoteVideoSource::Shutdown()
empty = mSources.IsEmpty();
if (empty) {
MOZ_ASSERT(mPrincipalHandles.IsEmpty());
MOZ_ASSERT(mTargetCapabilities.IsEmpty());
MOZ_ASSERT(mHandleIds.IsEmpty());
MOZ_ASSERT(mImages.IsEmpty());
break;
}
source = mSources[0];
@ -126,6 +134,9 @@ MediaEngineRemoteVideoSource::Allocate(
MonitorAutoLock lock(mMonitor);
if (mSources.IsEmpty()) {
MOZ_ASSERT(mPrincipalHandles.IsEmpty());
MOZ_ASSERT(mTargetCapabilities.IsEmpty());
MOZ_ASSERT(mHandleIds.IsEmpty());
MOZ_ASSERT(mImages.IsEmpty());
LOG(("Video device %d reallocated", mCaptureIndex));
} else {
LOG(("Video device %d allocated shared", mCaptureIndex));
@ -168,11 +179,21 @@ MediaEngineRemoteVideoSource::Start(SourceMediaStream* aStream, TrackID aID,
return NS_ERROR_FAILURE;
}
mImageContainer =
layers::LayerManager::CreateImageContainer(layers::ImageContainer::ASYNCHRONOUS);
{
MonitorAutoLock lock(mMonitor);
mSources.AppendElement(aStream);
mPrincipalHandles.AppendElement(aPrincipalHandle);
mTargetCapabilities.AppendElement(mTargetCapability);
mHandleIds.AppendElement(mHandleId);
mImages.AppendElement(mImageContainer->CreatePlanarYCbCrImage());
MOZ_ASSERT(mSources.Length() == mPrincipalHandles.Length());
MOZ_ASSERT(mSources.Length() == mTargetCapabilities.Length());
MOZ_ASSERT(mSources.Length() == mHandleIds.Length());
MOZ_ASSERT(mSources.Length() == mImages.Length());
}
aStream->AddTrack(aID, 0, new VideoSegment(), SourceMediaStream::ADDTRACK_QUEUED);
@ -180,8 +201,6 @@ MediaEngineRemoteVideoSource::Start(SourceMediaStream* aStream, TrackID aID,
if (mState == kStarted) {
return NS_OK;
}
mImageContainer =
layers::LayerManager::CreateImageContainer(layers::ImageContainer::ASYNCHRONOUS);
mState = kStarted;
mTrackID = aID;
@ -218,8 +237,14 @@ MediaEngineRemoteVideoSource::Stop(mozilla::SourceMediaStream* aSource,
}
MOZ_ASSERT(mSources.Length() == mPrincipalHandles.Length());
MOZ_ASSERT(mSources.Length() == mTargetCapabilities.Length());
MOZ_ASSERT(mSources.Length() == mHandleIds.Length());
MOZ_ASSERT(mSources.Length() == mImages.Length());
mSources.RemoveElementAt(i);
mPrincipalHandles.RemoveElementAt(i);
mTargetCapabilities.RemoveElementAt(i);
mHandleIds.RemoveElementAt(i);
mImages.RemoveElementAt(i);
aSource->EndTrack(aID);
@ -262,18 +287,21 @@ nsresult
MediaEngineRemoteVideoSource::UpdateSingleSource(
const AllocationHandle* aHandle,
const NormalizedConstraints& aNetConstraints,
const NormalizedConstraints& aNewConstraint,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint)
{
if (!ChooseCapability(aNetConstraints, aPrefs, aDeviceId)) {
*aOutBadConstraint = FindBadConstraint(aNetConstraints, *this, aDeviceId);
return NS_ERROR_FAILURE;
}
switch (mState) {
case kReleased:
MOZ_ASSERT(aHandle);
mHandleId = aHandle->mId;
if (!ChooseCapability(aNetConstraints, aPrefs, aDeviceId, mCapability, kFitness)) {
*aOutBadConstraint = FindBadConstraint(aNetConstraints, *this, aDeviceId);
return NS_ERROR_FAILURE;
}
mTargetCapability = mCapability;
if (camera::GetChildAndCall(&camera::CamerasChild::AllocateCaptureDevice,
mCapEngine, GetUUID().get(),
kMaxUniqueIdLength, mCaptureIndex,
@ -286,18 +314,47 @@ MediaEngineRemoteVideoSource::UpdateSingleSource(
break;
case kStarted:
if (mCapability != mLastCapability) {
camera::GetChildAndCall(&camera::CamerasChild::StopCapture,
mCapEngine, mCaptureIndex);
if (camera::GetChildAndCall(&camera::CamerasChild::StartCapture,
mCapEngine, mCaptureIndex, mCapability,
this)) {
LOG(("StartCapture failed"));
{
size_t index = mHandleIds.NoIndex;
if (aHandle) {
mHandleId = aHandle->mId;
index = mHandleIds.IndexOf(mHandleId);
}
if (!ChooseCapability(aNewConstraint, aPrefs, aDeviceId, mTargetCapability,
kFitness)) {
*aOutBadConstraint = FindBadConstraint(aNewConstraint, *this, aDeviceId);
return NS_ERROR_FAILURE;
}
SetLastCapability(mCapability);
if (index != mHandleIds.NoIndex) {
MonitorAutoLock lock(mMonitor);
mTargetCapabilities[index] = mTargetCapability;
MOZ_ASSERT(mSources.Length() == mPrincipalHandles.Length());
MOZ_ASSERT(mSources.Length() == mTargetCapabilities.Length());
MOZ_ASSERT(mSources.Length() == mHandleIds.Length());
MOZ_ASSERT(mSources.Length() == mImages.Length());
}
if (!ChooseCapability(aNetConstraints, aPrefs, aDeviceId, mCapability,
kFeasibility)) {
*aOutBadConstraint = FindBadConstraint(aNetConstraints, *this, aDeviceId);
return NS_ERROR_FAILURE;
}
if (mCapability != mLastCapability) {
camera::GetChildAndCall(&camera::CamerasChild::StopCapture,
mCapEngine, mCaptureIndex);
if (camera::GetChildAndCall(&camera::CamerasChild::StartCapture,
mCapEngine, mCaptureIndex, mCapability,
this)) {
LOG(("StartCapture failed"));
return NS_ERROR_FAILURE;
}
SetLastCapability(mCapability);
}
break;
}
break;
default:
LOG(("Video device %d in ignored state %d", mCaptureIndex, mState));
@ -343,18 +400,22 @@ MediaEngineRemoteVideoSource::NotifyPull(MediaStreamGraph* aGraph,
TrackID aID, StreamTime aDesiredTime,
const PrincipalHandle& aPrincipalHandle)
{
VideoSegment segment;
StreamTime delta = 0;
size_t i;
MonitorAutoLock lock(mMonitor);
if (mState != kStarted) {
return;
}
StreamTime delta = aDesiredTime - aSource->GetEndOfAppendedData(aID);
i = mSources.IndexOf(aSource);
if (i == mSources.NoIndex) {
return;
}
delta = aDesiredTime - aSource->GetEndOfAppendedData(aID);
if (delta > 0) {
// nullptr images are allowed
AppendToTrack(aSource, mImage, aID, delta, aPrincipalHandle);
AppendToTrack(aSource, mImages[i], aID, delta, aPrincipalHandle);
}
}
@ -377,11 +438,12 @@ MediaEngineRemoteVideoSource::FrameSizeChange(unsigned int w, unsigned int h)
}
int
MediaEngineRemoteVideoSource::DeliverFrame(uint8_t* aBuffer ,
MediaEngineRemoteVideoSource::DeliverFrame(uint8_t* aBuffer,
const camera::VideoFrameProperties& aProps)
{
MonitorAutoLock lock(mMonitor);
// Check for proper state.
if (mState != kStarted) {
if (mState != kStarted || !mImageContainer) {
LOG(("DeliverFrame: video not started"));
return 0;
}
@ -389,51 +451,114 @@ MediaEngineRemoteVideoSource::DeliverFrame(uint8_t* aBuffer ,
// Update the dimensions
FrameSizeChange(aProps.width(), aProps.height());
layers::PlanarYCbCrData data;
RefPtr<layers::PlanarYCbCrImage> image;
{
// We grab the lock twice, but don't hold it across the (long) CopyData
MonitorAutoLock lock(mMonitor);
if (!mImageContainer) {
LOG(("DeliverFrame() called after Stop()!"));
return 0;
}
// Create a video frame and append it to the track.
image = mImageContainer->CreatePlanarYCbCrImage();
MOZ_ASSERT(mSources.Length() == mPrincipalHandles.Length());
MOZ_ASSERT(mSources.Length() == mTargetCapabilities.Length());
MOZ_ASSERT(mSources.Length() == mHandleIds.Length());
MOZ_ASSERT(mSources.Length() == mImages.Length());
for (uint32_t i = 0; i < mTargetCapabilities.Length(); i++ ) {
int32_t req_max_width = mTargetCapabilities[i].width & 0xffff;
int32_t req_max_height = mTargetCapabilities[i].height & 0xffff;
int32_t req_ideal_width = (mTargetCapabilities[i].width >> 16) & 0xffff;
int32_t req_ideal_height = (mTargetCapabilities[i].height >> 16) & 0xffff;
int32_t dest_max_width = std::min(req_max_width, mWidth);
int32_t dest_max_height = std::min(req_max_height, mHeight);
// This logic works for both camera and screen sharing case.
// for camera case, req_ideal_width and req_ideal_height is 0.
// The following snippet will set dst_width to dest_max_width and dst_height to dest_max_height
int32_t dst_width = std::min(req_ideal_width > 0 ? req_ideal_width : mWidth, dest_max_width);
int32_t dst_height = std::min(req_ideal_height > 0 ? req_ideal_height : mHeight, dest_max_height);
int dst_stride_y = dst_width;
int dst_stride_uv = (dst_width + 1) / 2;
camera::VideoFrameProperties properties;
uint8_t* frame;
bool needReScale = !((dst_width == mWidth && dst_height == mHeight) ||
(dst_width > mWidth || dst_height > mHeight));
if (!needReScale) {
dst_width = mWidth;
dst_height = mHeight;
frame = aBuffer;
} else {
rtc::scoped_refptr<webrtc::I420Buffer> i420Buffer;
i420Buffer = webrtc::I420Buffer::Create(mWidth, mHeight, mWidth,
(mWidth + 1) / 2, (mWidth + 1) / 2);
const int conversionResult = webrtc::ConvertToI420(webrtc::kI420,
aBuffer,
0, 0, // No cropping
mWidth, mHeight,
mWidth * mHeight * 3 / 2,
webrtc::kVideoRotation_0,
i420Buffer.get());
webrtc::VideoFrame captureFrame(i420Buffer, 0, 0, webrtc::kVideoRotation_0);
if (conversionResult < 0) {
return 0;
}
rtc::scoped_refptr<webrtc::I420Buffer> scaledBuffer;
scaledBuffer = webrtc::I420Buffer::Create(dst_width, dst_height, dst_stride_y,
dst_stride_uv, dst_stride_uv);
scaledBuffer->CropAndScaleFrom(*captureFrame.video_frame_buffer().get());
webrtc::VideoFrame scaledFrame(scaledBuffer, 0, 0, webrtc::kVideoRotation_0);
VideoFrameUtils::InitFrameBufferProperties(scaledFrame, properties);
frame = new unsigned char[properties.bufferSize()];
if (!frame) {
return 0;
}
VideoFrameUtils::CopyVideoFrameBuffers(frame,
properties.bufferSize(), scaledFrame);
}
// Create a video frame and append it to the track.
RefPtr<layers::PlanarYCbCrImage> image = mImageContainer->CreatePlanarYCbCrImage();
uint8_t* frame = static_cast<uint8_t*> (aBuffer);
const uint8_t lumaBpp = 8;
const uint8_t chromaBpp = 4;
layers::PlanarYCbCrData data;
// Take lots of care to round up!
data.mYChannel = frame;
data.mYSize = IntSize(mWidth, mHeight);
data.mYStride = (mWidth * lumaBpp + 7)/ 8;
data.mCbCrStride = (mWidth * chromaBpp + 7) / 8;
data.mCbChannel = frame + mHeight * data.mYStride;
data.mCrChannel = data.mCbChannel + ((mHeight+1)/2) * data.mCbCrStride;
data.mCbCrSize = IntSize((mWidth+1)/ 2, (mHeight+1)/ 2);
data.mYSize = IntSize(dst_width, dst_height);
data.mYStride = (dst_width * lumaBpp + 7) / 8;
data.mCbCrStride = (dst_width * chromaBpp + 7) / 8;
data.mCbChannel = frame + dst_height * data.mYStride;
data.mCrChannel = data.mCbChannel + ((dst_height + 1) / 2) * data.mCbCrStride;
data.mCbCrSize = IntSize((dst_width + 1) / 2, (dst_height + 1) / 2);
data.mPicX = 0;
data.mPicY = 0;
data.mPicSize = IntSize(mWidth, mHeight);
data.mPicSize = IntSize(dst_width, dst_height);
data.mStereoMode = StereoMode::MONO;
}
if (!image->CopyData(data)) {
MOZ_ASSERT(false);
return 0;
}
if (!image->CopyData(data)) {
MOZ_ASSERT(false);
return 0;
}
if (needReScale && frame) {
delete frame;
frame = nullptr;
}
MonitorAutoLock lock(mMonitor);
#ifdef DEBUG
static uint32_t frame_num = 0;
LOGFRAME(("frame %d (%dx%d); timeStamp %u, ntpTimeMs %" PRIu64 ", renderTimeMs %" PRIu64,
frame_num++, mWidth, mHeight,
aProps.timeStamp(), aProps.ntpTimeMs(), aProps.renderTimeMs()));
static uint32_t frame_num = 0;
LOGFRAME(("frame %d (%dx%d); timeStamp %u, ntpTimeMs %" PRIu64 ", renderTimeMs %" PRIu64,
frame_num++, mWidth, mHeight,
aProps.timeStamp(), aProps.ntpTimeMs(), aProps.renderTimeMs()));
#endif
// implicitly releases last image
mImage = image.forget();
// implicitly releases last image
mImages[i] = image.forget();
}
// We'll push the frame into the MSG on the next NotifyPull. This will avoid
// swamping the MSG with frames should it be taking longer than normal to run
@ -464,7 +589,9 @@ bool
MediaEngineRemoteVideoSource::ChooseCapability(
const NormalizedConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId)
const nsString& aDeviceId,
webrtc::CaptureCapability& aCapability,
const DistanceCalculation aCalculate)
{
AssertIsOnOwningThread();
@ -477,15 +604,16 @@ MediaEngineRemoteVideoSource::ChooseCapability(
// time (and may in fact change over time), so as a hack, we push ideal
// and max constraints down to desktop_capture_impl.cc and finish the
// algorithm there.
mCapability.width = (c.mWidth.mIdeal.valueOr(0) & 0xffff) << 16 |
(c.mWidth.mMax & 0xffff);
mCapability.height = (c.mHeight.mIdeal.valueOr(0) & 0xffff) << 16 |
(c.mHeight.mMax & 0xffff);
mCapability.maxFPS = c.mFrameRate.Clamp(c.mFrameRate.mIdeal.valueOr(aPrefs.mFPS));
aCapability.width =
(c.mWidth.mIdeal.valueOr(0) & 0xffff) << 16 | (c.mWidth.mMax & 0xffff);
aCapability.height =
(c.mHeight.mIdeal.valueOr(0) & 0xffff) << 16 | (c.mHeight.mMax & 0xffff);
aCapability.maxFPS =
c.mFrameRate.Clamp(c.mFrameRate.mIdeal.valueOr(aPrefs.mFPS));
return true;
}
default:
return MediaEngineCameraVideoSource::ChooseCapability(aConstraints, aPrefs, aDeviceId);
return MediaEngineCameraVideoSource::ChooseCapability(aConstraints, aPrefs, aDeviceId, aCapability, aCalculate);
}
}

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

@ -84,9 +84,12 @@ public:
return mMediaSource;
}
bool ChooseCapability(const NormalizedConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId) override;
bool ChooseCapability(
const NormalizedConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId,
webrtc::CaptureCapability& aCapability,
const DistanceCalculation aCalculate) override;
void Refresh(int aIndex);
@ -107,6 +110,7 @@ private:
nsresult
UpdateSingleSource(const AllocationHandle* aHandle,
const NormalizedConstraints& aNetConstraints,
const NormalizedConstraints& aNewConstraint,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint) override;

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

@ -566,6 +566,7 @@ private:
nsresult
UpdateSingleSource(const AllocationHandle* aHandle,
const NormalizedConstraints& aNetConstraints,
const NormalizedConstraints& aNewConstraint,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint) override;

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

@ -279,6 +279,7 @@ nsresult
MediaEngineWebRTCMicrophoneSource::UpdateSingleSource(
const AllocationHandle* aHandle,
const NormalizedConstraints& aNetConstraints,
const NormalizedConstraints& aNewConstraint, /* Ignored */
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId,
const char** aOutBadConstraint)

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

@ -417,6 +417,28 @@ MediaConstraintsHelper::FitnessDistance(ValueType aN,
std::max(std::abs(aN), std::abs(aRange.mIdeal.value()))));
}
template<class ValueType, class NormalizedRange>
/* static */ uint32_t
MediaConstraintsHelper::FeasibilityDistance(ValueType aN,
const NormalizedRange& aRange)
{
if (aRange.mMin > aN) {
return UINT32_MAX;
}
// We prefer larger resolution because now we support downscaling
if (aN == aRange.mIdeal.valueOr(aN)) {
return 0;
}
if (aN > aRange.mIdeal.value()) {
return uint32_t(ValueType((std::abs(aN - aRange.mIdeal.value()) * 1000) /
std::max(std::abs(aN), std::abs(aRange.mIdeal.value()))));
}
return 10000 + uint32_t(ValueType((std::abs(aN - aRange.mIdeal.value()) * 1000) /
std::max(std::abs(aN), std::abs(aRange.mIdeal.value()))));
}
// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
/* static */ uint32_t

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

@ -85,12 +85,19 @@ public:
return mMax >= aOther.mMin && mMin <= aOther.mMax;
}
void Intersect(const Range& aOther) {
MOZ_ASSERT(Intersects(aOther));
mMin = std::max(mMin, aOther.mMin);
mMax = std::min(mMax, aOther.mMax);
if (Intersects(aOther)) {
mMax = std::min(mMax, aOther.mMax);
} else {
// If there is no intersection, we will down-scale or drop frame
mMax = std::max(mMax, aOther.mMax);
}
}
bool Merge(const Range& aOther) {
if (!Intersects(aOther)) {
if (strcmp(mName, "width") != 0 &&
strcmp(mName, "height") != 0 &&
strcmp(mName, "frameRate") != 0 &&
!Intersects(aOther)) {
return false;
}
Intersect(aOther);
@ -297,6 +304,8 @@ class MediaConstraintsHelper
protected:
template<class ValueType, class NormalizedRange>
static uint32_t FitnessDistance(ValueType aN, const NormalizedRange& aRange);
template<class ValueType, class NormalizedRange>
static uint32_t FeasibilityDistance(ValueType aN, const NormalizedRange& aRange);
static uint32_t FitnessDistance(nsString aN,
const NormalizedConstraintSet::StringRange& aConstraint);

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

@ -1189,7 +1189,7 @@ var interfaceNamesInGlobalScope =
// IMPORTANT: Do not change this list without review from a DOM peer!
"VTTCue",
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "VTTRegion", disabled: true},
"VTTRegion",
// IMPORTANT: Do not change this list without review from a DOM peer!
"WaveShaperNode",
// IMPORTANT: Do not change this list without review from a DOM peer!

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

@ -4,17 +4,21 @@
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* http://dev.w3.org/html5/webvtt/#extension-of-the-texttrack-interface-for-region-support
* https://w3c.github.io/webvtt/#the-vttregion-interface
*/
enum ScrollSetting {
"",
"up"
};
[Constructor, Pref="media.webvtt.regions.enabled"]
interface VTTRegion {
attribute DOMString id;
[SetterThrows]
attribute double width;
[SetterThrows]
attribute long lines;
[SetterThrows]
attribute double regionAnchorX;
[SetterThrows]
@ -23,6 +27,6 @@ interface VTTRegion {
attribute double viewportAnchorX;
[SetterThrows]
attribute double viewportAnchorY;
[SetterThrows]
attribute DOMString scroll;
attribute ScrollSetting scroll;
};

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

@ -466,6 +466,9 @@ TextEditor::CreateBRImpl(nsCOMPtr<nsIDOMNode>* aInOutParent,
pointToInsertBrNode.Set(node);
Unused << newLeftNode;
}
// Lock the offset of pointToInsertBrNode because it'll be referred after
// inserting a new <br> node before it.
Unused << pointToInsertBrNode.Offset();
// create br
brNode = CreateNode(nsGkAtoms::br, pointToInsertBrNode);
if (NS_WARN_IF(!brNode)) {

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

@ -650,6 +650,7 @@ private:
DECL_GFX_PREF(Live, "layout.display-list.build-twice", LayoutDisplayListBuildTwice, bool, false);
DECL_GFX_PREF(Live, "layout.display-list.retain", LayoutRetainDisplayList, bool, true);
DECL_GFX_PREF(Live, "layout.display-list.retain.verify", LayoutVerifyRetainDisplayList, bool, false);
DECL_GFX_PREF(Live, "layout.display-list.rebuild-frame-limit", LayoutRebuildFrameLimit, uint32_t, 500);
DECL_GFX_PREF(Live, "layout.display-list.dump", LayoutDumpDisplayList, bool, false);
DECL_GFX_PREF(Live, "layout.display-list.dump-content", LayoutDumpDisplayListContent, bool, false);

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

@ -16,7 +16,6 @@
#include "mozilla/Attributes.h"
#include "mozilla/LinkedList.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/RestyleManager.h"
#include "nsCOMPtr.h"
#include "nsILayoutHistoryState.h"

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

@ -128,6 +128,7 @@
#include "mozilla/layers/WebRenderLayerManager.h"
#include "prenv.h"
#include "RetainedDisplayListBuilder.h"
#include "DisplayListChecker.h"
#include "TextDrawTarget.h"
#include "nsDeckFrame.h"
#include "nsIEffectiveTLDService.h" // for IsInStyloBlocklist
@ -3847,16 +3848,26 @@ nsLayoutUtils::PaintFrame(gfxContext* aRenderingContext, nsIFrame* aFrame,
builder.IsBuildingLayerEventRegions() &&
nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(presShell));
DisplayListChecker beforeMergeChecker;
DisplayListChecker afterMergeChecker;
// Attempt to do a partial build and merge into the existing list.
// This calls BuildDisplayListForStacking context on a subset of the
// viewport.
bool merged = false;
if (useRetainedBuilder) {
if (gfxPrefs::LayoutVerifyRetainDisplayList()) {
beforeMergeChecker.Set(&list, "BM");
}
merged = retainedBuilder->AttemptPartialUpdate(aBackstop);
if (merged && beforeMergeChecker) {
afterMergeChecker.Set(&list, "AM");
}
}
if (merged && gfxPrefs::LayoutDisplayListBuildTwice()) {
if (merged &&
(gfxPrefs::LayoutDisplayListBuildTwice() || afterMergeChecker)) {
merged = false;
if (gfxPrefs::LayersDrawFPS()) {
if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) {
@ -3877,6 +3888,22 @@ nsLayoutUtils::PaintFrame(gfxContext* aRenderingContext, nsIFrame* aFrame,
AddExtraBackgroundItems(builder, list, aFrame, canvasArea, visibleRegion, aBackstop);
builder.LeavePresShell(aFrame, &list);
if (afterMergeChecker) {
DisplayListChecker nonRetainedChecker(&list, "NR");
std::stringstream ss;
ss << "**** Differences between retained-after-merged (AM) and "
<< "non-retained (NR) display lists:";
if (!nonRetainedChecker.CompareList(afterMergeChecker, ss)) {
ss << "\n\n*** non-retained display items:";
nonRetainedChecker.Dump(ss);
ss << "\n\n*** before-merge retained display items:";
beforeMergeChecker.Dump(ss);
ss << "\n\n*** after-merge retained display items:";
afterMergeChecker.Dump(ss);
fprintf(stderr, "%s\n\n", ss.str().c_str());
}
}
}
}

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

@ -3631,19 +3631,23 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder,
if (couldBuildLayer) {
// Make sure that APZ will dispatch events back to content so we can create
// a displayport for this frame. We'll add the item later on.
nsDisplayLayerEventRegions* inactiveRegionItem = nullptr;
if (aBuilder->IsPaintingToWindow() &&
!mWillBuildScrollableLayer &&
aBuilder->IsBuildingLayerEventRegions())
{
inactiveRegionItem = new (aBuilder) nsDisplayLayerEventRegions(aBuilder, mScrolledFrame, 1);
inactiveRegionItem->AddInactiveScrollPort(mScrolledFrame, mScrollPort + aBuilder->ToReferenceFrame(mOuter));
}
if (inactiveRegionItem) {
if (!mWillBuildScrollableLayer) {
int32_t zIndex =
MaxZIndexInListOfItemsContainedInFrame(scrolledContent.PositionedDescendants(), mOuter);
AppendInternalItemToTop(scrolledContent, inactiveRegionItem, zIndex);
if (aBuilder->BuildCompositorHitTestInfo()) {
CompositorHitTestInfo info = CompositorHitTestInfo::eVisibleToHitTest
| CompositorHitTestInfo::eDispatchToContent;
nsDisplayCompositorHitTestInfo* hitInfo =
new (aBuilder) nsDisplayCompositorHitTestInfo(aBuilder, mScrolledFrame, info, 1);
hitInfo->SetArea(mScrollPort + aBuilder->ToReferenceFrame(mOuter));
AppendInternalItemToTop(scrolledContent, hitInfo, zIndex);
}
if (aBuilder->IsBuildingLayerEventRegions()) {
nsDisplayLayerEventRegions* inactiveRegionItem =
new (aBuilder) nsDisplayLayerEventRegions(aBuilder, mScrolledFrame, 1);
inactiveRegionItem->AddInactiveScrollPort(mScrolledFrame, mScrollPort + aBuilder->ToReferenceFrame(mOuter));
AppendInternalItemToTop(scrolledContent, inactiveRegionItem, zIndex);
}
}
if (aBuilder->ShouldBuildScrollInfoItemsForHoisting()) {

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

@ -0,0 +1,371 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DisplayListChecker.h"
#include "nsDisplayList.h"
namespace mozilla {
class DisplayItemBlueprint;
// Stack node used during tree visits, to store the path to a display item.
struct DisplayItemBlueprintStack
{
const DisplayItemBlueprintStack* mPrevious;
const DisplayItemBlueprint* mItem;
// Output stack to aSs, with format "name#index > ... > name#index".
// Returns true if anything was output, false if empty.
bool Output(std::stringstream& aSs) const;
};
// Object representing a list of display items (either the top of the tree, or
// an item's children), with just enough information to compare with another
// tree and output useful debugging information.
class DisplayListBlueprint
{
public:
DisplayListBlueprint(nsDisplayList* aList, const char* aName)
: DisplayListBlueprint(aList, 0, aName)
{
}
DisplayListBlueprint(nsDisplayList* aList,
const char* aName,
unsigned& aIndex)
{
processChildren(aList, aName, aIndex);
}
// Find a display item with the given frame and per-frame key.
// Returns empty string if not found.
std::string Find(const nsIFrame* aFrame, uint32_t aPerFrameKey) const
{
const DisplayItemBlueprintStack stack{ nullptr, nullptr };
return Find(aFrame, aPerFrameKey, stack);
}
std::string Find(const nsIFrame* aFrame,
uint32_t aPerFrameKey,
const DisplayItemBlueprintStack& aStack) const;
// Compare this list with another one, output differences between the two
// into aDiff.
// Differences include: Display items from one tree for which a corresponding
// item (same frame and per-frame key) cannot be found under corresponding
// parent items.
// Returns true if trees are similar, false if different.
bool CompareList(const DisplayListBlueprint& aOther,
std::stringstream& aDiff) const
{
const DisplayItemBlueprintStack stack{ nullptr, nullptr };
const bool ab = CompareList(*this, aOther, aOther, aDiff, stack, stack);
const bool ba =
aOther.CompareList(aOther, *this, *this, aDiff, stack, stack);
return ab && ba;
}
bool CompareList(const DisplayListBlueprint& aRoot,
const DisplayListBlueprint& aOther,
const DisplayListBlueprint& aOtherRoot,
std::stringstream& aDiff,
const DisplayItemBlueprintStack& aStack,
const DisplayItemBlueprintStack& aStackOther) const;
// Output this tree to aSs.
void Dump(std::stringstream& aSs) const { Dump(aSs, 0); }
void Dump(std::stringstream& aSs, unsigned aDepth) const;
private:
// Only used by first constructor, to call the 2nd constructor with an index
// variable on the stack.
DisplayListBlueprint(nsDisplayList* aList, unsigned aIndex, const char* aName)
: DisplayListBlueprint(aList, aName, aIndex)
{
}
void processChildren(nsDisplayList* aList,
const char* aName,
unsigned& aIndex);
std::vector<DisplayItemBlueprint> mItems;
};
// Object representing one display item, with just enough information to
// compare with another item and output useful debugging information.
class DisplayItemBlueprint
{
public:
DisplayItemBlueprint(nsDisplayItem& aItem,
const char* aName,
unsigned& aIndex)
: mListName(aName)
, mIndex(++aIndex)
, mIndexString(WriteIndex(aName, aIndex))
, mIndexStringFW(WriteIndexFW(aName, aIndex))
, mDisplayItemPointer(WriteDisplayItemPointer(aItem))
, mDescription(WriteDescription(aName, aIndex, aItem))
, mFrame(aItem.HasDeletedFrame() ? nullptr : aItem.Frame())
, mPerFrameKey(aItem.GetPerFrameKey())
, mChildren(aItem.GetChildren(), aName, aIndex)
{
}
// Compare this item with another one, based on frame and per-frame key.
// Not recursive! I.e., children are not examined.
bool CompareItem(const DisplayItemBlueprint& aOther,
std::stringstream& aDiff) const
{
return mFrame == aOther.mFrame && mPerFrameKey == aOther.mPerFrameKey;
}
void Dump(std::stringstream& aSs, unsigned aDepth) const;
const char* mListName;
const unsigned mIndex;
const std::string mIndexString;
const std::string mIndexStringFW;
const std::string mDisplayItemPointer;
const std::string mDescription;
// For pointer comparison only, do not dereference!
const nsIFrame* const mFrame;
const uint32_t mPerFrameKey;
const DisplayListBlueprint mChildren;
private:
static std::string WriteIndex(const char* aName, unsigned aIndex)
{
return nsPrintfCString("%s#%u", aName, aIndex).get();
}
static std::string WriteIndexFW(const char* aName, unsigned aIndex)
{
return nsPrintfCString("%s#%4u", aName, aIndex).get();
}
static std::string WriteDisplayItemPointer(nsDisplayItem& aItem)
{
return nsPrintfCString("0x%p", &aItem).get();
}
static std::string WriteDescription(const char* aName,
unsigned aIndex,
nsDisplayItem& aItem)
{
if (aItem.HasDeletedFrame()) {
return nsPrintfCString(
"%s %s#%u 0x%p f=0x0", aItem.Name(), aName, aIndex, &aItem)
.get();
}
const nsIFrame* f = aItem.Frame();
nsAutoString contentData;
#ifdef DEBUG_FRAME_DUMP
f->GetFrameName(contentData);
#endif
nsIContent* content = f->GetContent();
if (content) {
nsString tmp;
if (content->GetID()) {
content->GetID()->ToString(tmp);
contentData.AppendLiteral(" id:");
contentData.Append(tmp);
}
const nsAttrValue* classes =
content->IsElement() ? content->AsElement()->GetClasses() : nullptr;
if (classes) {
classes->ToString(tmp);
contentData.AppendLiteral(" class:");
contentData.Append(tmp);
}
}
return nsPrintfCString("%s %s#%u p=0x%p f=0x%p(%s) key=%" PRIu32,
aItem.Name(),
aName,
aIndex,
&aItem,
f,
NS_ConvertUTF16toUTF8(contentData).get(),
aItem.GetPerFrameKey())
.get();
}
};
void
DisplayListBlueprint::processChildren(nsDisplayList* aList,
const char* aName,
unsigned& aIndex)
{
if (!aList) {
return;
}
const uint32_t n = aList->Count();
if (n == 0) {
return;
}
mItems.reserve(n);
for (nsDisplayItem* item = aList->GetBottom(); item;
item = item->GetAbove()) {
mItems.emplace_back(*item, aName, aIndex);
}
MOZ_ASSERT(mItems.size() == n);
}
bool
DisplayItemBlueprintStack::Output(std::stringstream& aSs) const
{
const bool output = mPrevious ? mPrevious->Output(aSs) : false;
if (mItem) {
if (output) {
aSs << " > ";
}
aSs << mItem->mIndexString;
return true;
}
return output;
}
std::string
DisplayListBlueprint::Find(const nsIFrame* aFrame,
uint32_t aPerFrameKey,
const DisplayItemBlueprintStack& aStack) const
{
for (const DisplayItemBlueprint& item : mItems) {
if (item.mFrame == aFrame && item.mPerFrameKey == aPerFrameKey) {
std::stringstream ss;
if (aStack.Output(ss)) {
ss << " > ";
}
ss << item.mDescription;
return ss.str();
}
const DisplayItemBlueprintStack stack = { &aStack, &item };
std::string s = item.mChildren.Find(aFrame, aPerFrameKey, stack);
if (!s.empty()) {
return s;
}
}
return "";
}
bool
DisplayListBlueprint::CompareList(
const DisplayListBlueprint& aRoot,
const DisplayListBlueprint& aOther,
const DisplayListBlueprint& aOtherRoot,
std::stringstream& aDiff,
const DisplayItemBlueprintStack& aStack,
const DisplayItemBlueprintStack& aStackOther) const
{
bool same = true;
for (const DisplayItemBlueprint& itemBefore : mItems) {
bool found = false;
for (const DisplayItemBlueprint& itemAfter : aOther.mItems) {
if (itemBefore.CompareItem(itemAfter, aDiff)) {
found = true;
const DisplayItemBlueprintStack stack = { &aStack, &itemBefore };
const DisplayItemBlueprintStack stackOther = { &aStackOther,
&itemAfter };
if (!itemBefore.mChildren.CompareList(aRoot,
itemAfter.mChildren,
aOtherRoot,
aDiff,
stack,
stackOther)) {
same = false;
}
break;
}
}
if (!found) {
same = false;
aDiff << "\n";
if (aStack.Output(aDiff)) {
aDiff << " > ";
}
aDiff << itemBefore.mDescription;
aDiff << "\n * Cannot find corresponding item under ";
if (!aStackOther.Output(aDiff)) {
if (!aOtherRoot.mItems.empty()) {
aDiff << aOtherRoot.mItems[0].mListName;
} else {
aDiff << "other root";
}
}
std::string elsewhere =
aOtherRoot.Find(itemBefore.mFrame, itemBefore.mPerFrameKey);
if (!elsewhere.empty()) {
aDiff << "\n * But found: " << elsewhere;
}
}
}
return same;
}
void
DisplayListBlueprint::Dump(std::stringstream& aSs, unsigned aDepth) const
{
for (const DisplayItemBlueprint& item : mItems) {
item.Dump(aSs, aDepth);
}
}
void
DisplayItemBlueprint::Dump(std::stringstream& aSs, unsigned aDepth) const
{
aSs << "\n" << mIndexStringFW << " ";
for (unsigned i = 0; i < aDepth; ++i) {
aSs << " ";
}
aSs << mDescription;
mChildren.Dump(aSs, aDepth + 1);
}
DisplayListChecker::DisplayListChecker()
: mBlueprint(nullptr)
{
}
DisplayListChecker::DisplayListChecker(nsDisplayList* aList, const char* aName)
: mBlueprint(MakeUnique<DisplayListBlueprint>(aList, aName))
{
}
DisplayListChecker::~DisplayListChecker() = default;
void
DisplayListChecker::Set(nsDisplayList* aList, const char* aName)
{
mBlueprint = MakeUnique<DisplayListBlueprint>(aList, aName);
}
// Compare this list with another one, output differences between the two
// into aDiff.
// Differences include: Display items from one tree for which a corresponding
// item (same frame and per-frame key) cannot be found under corresponding
// parent items.
// Returns true if trees are similar, false if different.
bool
DisplayListChecker::CompareList(const DisplayListChecker& aOther,
std::stringstream& aDiff) const
{
MOZ_ASSERT(mBlueprint);
MOZ_ASSERT(aOther.mBlueprint);
return mBlueprint->CompareList(*aOther.mBlueprint, aDiff);
}
void
DisplayListChecker::Dump(std::stringstream& aSs) const
{
MOZ_ASSERT(mBlueprint);
mBlueprint->Dump(aSs);
}
} // namespace mozilla

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

@ -0,0 +1,49 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DisplayListChecker_h__
#define DisplayListChecker_h__
#include <sstream>
#include <mozilla/UniquePtr.h>
class nsDisplayList;
namespace mozilla {
class DisplayListBlueprint;
class DisplayListChecker
{
public:
DisplayListChecker();
DisplayListChecker(nsDisplayList* aList, const char* aName);
~DisplayListChecker();
void Set(nsDisplayList* aList, const char* aName);
explicit operator bool() const { return mBlueprint.get(); }
// Compare this list with another one, output differences between the two
// into aDiff.
// Differences include: Display items from one tree for which a corresponding
// item (same frame and per-frame key) cannot be found under corresponding
// parent items.
// Returns true if trees are similar, false if different.
bool CompareList(const DisplayListChecker& aOther,
std::stringstream& aDiff) const;
// Output this tree to aSs.
void Dump(std::stringstream& aSs) const;
private:
UniquePtr<DisplayListBlueprint> mBlueprint;
};
} // namespace mozilla
#endif // DisplayListChecker_h__

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

@ -35,6 +35,7 @@ UNIFIED_SOURCES += [
'DisplayItemClip.cpp',
'DisplayItemClipChain.cpp',
'DisplayItemScrollClip.cpp',
'DisplayListChecker.cpp',
'DisplayListClipState.cpp',
'DottedCornerFinder.cpp',
'FrameLayerBuilder.cpp',

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

@ -4870,9 +4870,11 @@ nsDisplayEventReceiver::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder&
nsDisplayCompositorHitTestInfo::nsDisplayCompositorHitTestInfo(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame,
mozilla::gfx::CompositorHitTestInfo aHitTestInfo)
mozilla::gfx::CompositorHitTestInfo aHitTestInfo,
uint32_t aIndex)
: nsDisplayEventReceiver(aBuilder, aFrame)
, mHitTestInfo(aHitTestInfo)
, mIndex(aIndex)
{
MOZ_COUNT_CTOR(nsDisplayCompositorHitTestInfo);
// We should never even create this display item if we're not building
@ -4889,6 +4891,12 @@ nsDisplayCompositorHitTestInfo::nsDisplayCompositorHitTestInfo(nsDisplayListBuil
}
}
void
nsDisplayCompositorHitTestInfo::SetArea(const nsRect& aArea)
{
mArea = Some(aArea);
}
bool
nsDisplayCompositorHitTestInfo::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
mozilla::wr::IpcResourceUpdateQueue& aResources,
@ -4896,26 +4904,31 @@ nsDisplayCompositorHitTestInfo::CreateWebRenderCommands(mozilla::wr::DisplayList
mozilla::layers::WebRenderLayerManager* aManager,
nsDisplayListBuilder* aDisplayListBuilder)
{
nsRect borderBox;
nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(mFrame);
if (scrollFrame) {
// If the frame is content of a scrollframe, then we need to pick up the
// area corresponding to the overflow rect as well. Otherwise the parts of
// the overflow that are not occupied by descendants get skipped and the
// APZ code sends touch events to the content underneath instead.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1127773#c15.
borderBox = mFrame->GetScrollableOverflowRect();
} else {
borderBox = nsRect(nsPoint(0, 0), mFrame->GetSize());
}
if (borderBox.IsEmpty()) {
return true;
if (mArea.isNothing()) {
nsRect borderBox;
nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(mFrame);
if (scrollFrame) {
// If the frame is content of a scrollframe, then we need to pick up the
// area corresponding to the overflow rect as well. Otherwise the parts of
// the overflow that are not occupied by descendants get skipped and the
// APZ code sends touch events to the content underneath instead.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1127773#c15.
borderBox = mFrame->GetScrollableOverflowRect();
} else {
borderBox = nsRect(nsPoint(0, 0), mFrame->GetSize());
}
if (borderBox.IsEmpty()) {
return true;
}
mArea = Some(borderBox + aDisplayListBuilder->ToReferenceFrame(mFrame));
}
MOZ_ASSERT(mArea.isSome());
wr::LayoutRect rect = aSc.ToRelativeLayoutRect(
LayoutDeviceRect::FromAppUnits(
borderBox + aDisplayListBuilder->ToReferenceFrame(mFrame),
*mArea,
mFrame->PresContext()->AppUnitsPerDevPixel()));
// XXX: eventually this scrollId computation and the SetHitTestInfo
@ -4945,6 +4958,24 @@ nsDisplayCompositorHitTestInfo::WriteDebugInfo(std::stringstream& aStream)
aStream << nsPrintfCString(" (hitTestInfo 0x%x)", (int)mHitTestInfo).get();
}
uint32_t
nsDisplayCompositorHitTestInfo::GetPerFrameKey() const
{
return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
}
int32_t
nsDisplayCompositorHitTestInfo::ZIndex() const
{
return mOverrideZIndex ? *mOverrideZIndex : nsDisplayItem::ZIndex();
}
void
nsDisplayCompositorHitTestInfo::SetOverrideZIndex(int32_t aZIndex)
{
mOverrideZIndex = Some(aZIndex);
}
void
nsDisplayLayerEventRegions::AddFrame(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame)

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

@ -4343,7 +4343,8 @@ public:
class nsDisplayCompositorHitTestInfo : public nsDisplayEventReceiver {
public:
nsDisplayCompositorHitTestInfo(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
mozilla::gfx::CompositorHitTestInfo aHitTestInfo);
mozilla::gfx::CompositorHitTestInfo aHitTestInfo,
uint32_t aIndex = 0);
#ifdef NS_BUILD_REFCNT_LOGGING
virtual ~nsDisplayCompositorHitTestInfo()
@ -4353,6 +4354,7 @@ public:
#endif
mozilla::gfx::CompositorHitTestInfo HitTestInfo() const { return mHitTestInfo; }
void SetArea(const nsRect& aArea);
bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
mozilla::wr::IpcResourceUpdateQueue& aResources,
@ -4360,12 +4362,18 @@ public:
mozilla::layers::WebRenderLayerManager* aManager,
nsDisplayListBuilder* aDisplayListBuilder) override;
void WriteDebugInfo(std::stringstream& aStream) override;
uint32_t GetPerFrameKey() const override;
int32_t ZIndex() const override;
void SetOverrideZIndex(int32_t aZIndex);
NS_DISPLAY_DECL_NAME("CompositorHitTestInfo", TYPE_COMPOSITOR_HITTEST_INFO)
private:
mozilla::gfx::CompositorHitTestInfo mHitTestInfo;
mozilla::Maybe<mozilla::layers::FrameMetrics::ViewID> mScrollTarget;
mozilla::Maybe<nsRect> mArea;
uint32_t mIndex;
mozilla::Maybe<int32_t> mOverrideZIndex;
};
/**

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

@ -262,9 +262,7 @@ function runOMTATest(aTestFunction, aOnSkip, specialPowersForPrefs) {
utils.advanceTimeAndRefresh(0);
div.style.animation = animationName + " 10s";
// Trigger style flush
div.clientTop;
return waitForPaints();
return waitForPaintsFlushed();
}).then(function() {
var opacity = utils.getOMTAStyle(div, "opacity");
cleanUp();
@ -285,12 +283,6 @@ function runOMTATest(aTestFunction, aOnSkip, specialPowersForPrefs) {
});
}
function waitForPaints() {
return new Promise(function(resolve, reject) {
waitForAllPaintsFlushed(resolve);
});
}
function loadPaintListener() {
return new Promise(function(resolve, reject) {
if (typeof(window.waitForAllPaints) !== "function") {

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

@ -4,7 +4,7 @@
using namespace cdm;
ClearKeyCDM::ClearKeyCDM(Host_8* aHost)
ClearKeyCDM::ClearKeyCDM(Host_9* aHost)
{
mHost = aHost;
mSessionManager = new ClearKeySessionManager(mHost);
@ -18,6 +18,15 @@ ClearKeyCDM::Initialize(bool aAllowDistinctiveIdentifier,
aAllowPersistentState);
}
void
ClearKeyCDM::GetStatusForPolicy(uint32_t aPromiseId,
const Policy& aPolicy)
{
// MediaKeys::GetStatusForPolicy checks the keysystem and
// reject the promise with NS_ERROR_DOM_NOT_SUPPORTED_ERR without calling CDM.
// This function should never be called and is not supported.
assert(false);
}
void
ClearKeyCDM::SetServerCertificate(uint32_t aPromiseId,
const uint8_t* aServerCertificateData,
@ -183,6 +192,15 @@ ClearKeyCDM::OnQueryOutputProtectionStatus(QueryResult aResult,
assert(false);
}
void
ClearKeyCDM::OnStorageId(uint32_t aVersion,
const uint8_t* aStorageId,
uint32_t aStorageIdSize)
{
// This function should never be called and is not supported.
assert(false);
}
void
ClearKeyCDM::Destroy()
{
@ -194,4 +212,4 @@ ClearKeyCDM::Destroy()
}
#endif
delete this;
}
}

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

@ -13,7 +13,7 @@
#include "VideoDecoder.h"
#endif
class ClearKeyCDM : public cdm::ContentDecryptionModule_8
class ClearKeyCDM : public cdm::ContentDecryptionModule_9
{
private:
RefPtr<ClearKeySessionManager> mSessionManager;
@ -22,14 +22,17 @@ private:
#endif
protected:
cdm::Host_8* mHost;
cdm::Host_9* mHost;
public:
explicit ClearKeyCDM(cdm::Host_8* mHost);
explicit ClearKeyCDM(cdm::Host_9* mHost);
void Initialize(bool aAllowDistinctiveIdentifier,
bool aAllowPersistentState) override;
void GetStatusForPolicy(uint32_t aPromiseId,
const cdm::Policy& aPolicy) override;
void SetServerCertificate(uint32_t aPromiseId,
const uint8_t* aServerCertificateData,
uint32_t aServerCertificateDataSize)
@ -92,7 +95,11 @@ public:
uint32_t aLinkMask,
uint32_t aOutputProtectionMask) override;
void OnStorageId(uint32_t aVersion,
const uint8_t* aStorageId,
uint32_t aStorageIdSize) override;
void Destroy() override;
};
#endif
#endif

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

@ -101,7 +101,7 @@ ClearKeyPersistence::WriteIndex() {
}
ClearKeyPersistence::ClearKeyPersistence(Host_8* aHost)
ClearKeyPersistence::ClearKeyPersistence(Host_9* aHost)
{
this->mHost = aHost;
}

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

@ -41,7 +41,7 @@ enum PersistentKeyState {
class ClearKeyPersistence : public RefCounted
{
public:
explicit ClearKeyPersistence(cdm::Host_8* aHost);
explicit ClearKeyPersistence(cdm::Host_9* aHost);
void EnsureInitialized(bool aPersistentStateAllowed,
std::function<void()>&& aOnInitialized);
@ -54,7 +54,7 @@ public:
void PersistentSessionRemoved(std::string& aSid);
private:
cdm::Host_8* mHost = nullptr;
cdm::Host_9* mHost = nullptr;
PersistentKeyState mPersistentKeyState = PersistentKeyState::UNINITIALIZED;

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

@ -33,7 +33,7 @@
using namespace std;
using namespace cdm;
ClearKeySessionManager::ClearKeySessionManager(Host_8* aHost)
ClearKeySessionManager::ClearKeySessionManager(Host_9* aHost)
: mDecryptionManager(ClearKeyDecryptionManager::Get())
{
CK_LOGD("ClearKeySessionManager ctor %p", this);
@ -117,7 +117,7 @@ ClearKeySessionManager::CreateSession(uint32_t aPromiseId,
string message = "initDataType is not supported by ClearKey";
mHost->OnRejectPromise(aPromiseId,
Error::kNotSupportedError,
Exception::kExceptionNotSupportedError,
0,
message.c_str(),
message.size());
@ -137,7 +137,7 @@ ClearKeySessionManager::CreateSession(uint32_t aPromiseId,
const static char* message = "Failed to initialize session";
mHost->OnRejectPromise(aPromiseId,
Error::kUnknownError,
Exception::kExceptionInvalidStateError,
0,
message,
strlen(message));
@ -178,9 +178,7 @@ ClearKeySessionManager::CreateSession(uint32_t aPromiseId,
sessionId.size(),
MessageType::kLicenseRequest,
request.c_str(),
request.size(),
nullptr,
0);
request.size());
}
void
@ -356,7 +354,7 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
CK_LOGW("ClearKey CDM couldn't resolve session ID in UpdateSession.");
CK_LOGD("Unable to find session: %s", sessionId.c_str());
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
Exception::kExceptionTypeError,
0,
nullptr,
0);
@ -371,7 +369,7 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
CK_LOGD("Failed to parse response for session %s", sessionId.c_str());
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
Exception::kExceptionTypeError,
0,
nullptr,
0);
@ -388,7 +386,7 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
Exception::kExceptionTypeError,
0,
nullptr,
0);
@ -442,7 +440,7 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
static const char* message = "Couldn't store cenc key init data";
self->mHost->OnRejectPromise(aPromiseId,
Error::kInvalidStateError,
Exception::kExceptionInvalidStateError,
0,
message,
strlen(message));
@ -503,7 +501,7 @@ ClearKeySessionManager::CloseSession(uint32_t aPromiseId,
if (itr == mSessions.end()) {
CK_LOGW("ClearKey CDM couldn't close non-existent session.");
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
Exception::kExceptionTypeError,
0,
nullptr,
0);
@ -563,7 +561,7 @@ ClearKeySessionManager::RemoveSession(uint32_t aPromiseId,
CK_LOGW("ClearKey CDM couldn't remove non-existent session.");
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
Exception::kExceptionTypeError,
0,
nullptr,
0);
@ -601,7 +599,7 @@ ClearKeySessionManager::RemoveSession(uint32_t aPromiseId,
}
static const char* message = "Could not remove session";
self->mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
Exception::kExceptionTypeError,
0,
message,
strlen(message));
@ -618,7 +616,7 @@ ClearKeySessionManager::SetServerCertificate(uint32_t aPromiseId,
// ClearKey CDM doesn't support this method by spec.
CK_LOGD("ClearKeySessionManager::SetServerCertificate");
mHost->OnRejectPromise(aPromiseId,
Error::kNotSupportedError,
Exception::kExceptionNotSupportedError,
0,
nullptr /* message */,
0 /* messageLen */);

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

@ -36,7 +36,7 @@
class ClearKeySessionManager final : public RefCounted
{
public:
explicit ClearKeySessionManager(cdm::Host_8* aHost);
explicit ClearKeySessionManager(cdm::Host_9* aHost);
void Init(bool aDistinctiveIdentifierAllowed,
bool aPersistentStateAllowed);
@ -91,7 +91,7 @@ private:
RefPtr<ClearKeyDecryptionManager> mDecryptionManager;
RefPtr<ClearKeyPersistence> mPersistence;
cdm::Host_8* mHost = nullptr;
cdm::Host_9* mHost = nullptr;
std::set<KeyId> mKeyIds;
std::map<std::string, ClearKeySession*> mSessions;

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

@ -37,7 +37,7 @@ public:
* This function will take the memory ownership of the parameters and
* delete them when done.
*/
static void Write(Host_8* aHost,
static void Write(Host_9* aHost,
string& aRecordName,
const vector<uint8_t>& aData,
function<void()>&& aOnSuccess,
@ -82,7 +82,7 @@ private:
, mOnFailure(move(aOnFailure))
, mData(aData) {}
void Do(const string& aName, Host_8* aHost)
void Do(const string& aName, Host_9* aHost)
{
// Initialize the FileIO.
mFileIO = aHost->CreateFileIO(this);
@ -118,7 +118,7 @@ private:
};
void
WriteData(Host_8* aHost,
WriteData(Host_9* aHost,
string& aRecordName,
const vector<uint8_t>& aData,
function<void()>&& aOnSuccess,
@ -138,7 +138,7 @@ public:
* This function will take the memory ownership of the parameters and
* delete them when done.
*/
static void Read(Host_8* aHost,
static void Read(Host_9* aHost,
string& aRecordName,
function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
function<void()>&& aOnFailure)
@ -179,7 +179,7 @@ private:
, mOnFailure(move(aOnFailure))
{}
void Do(const string& aName, Host_8* aHost)
void Do(const string& aName, Host_9* aHost)
{
mFileIO = aHost->CreateFileIO(this);
mFileIO->Open(aName.c_str(), aName.size());
@ -214,7 +214,7 @@ private:
};
void
ReadData(Host_8* mHost,
ReadData(Host_9* mHost,
string& aRecordName,
function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
function<void()>&& aOnFailure)

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

@ -28,14 +28,14 @@
#define IO_FAILED(x) ((x) != cdm::FileIOClient::Status::kSuccess)
// Writes data to a file and fires the appropriate callback when complete.
void WriteData(cdm::Host_8* aHost,
void WriteData(cdm::Host_9* aHost,
std::string& aRecordName,
const std::vector<uint8_t>& aData,
std::function<void()>&& aOnSuccess,
std::function<void()>&& aOnFailure);
// Reads data from a file and fires the appropriate callback when complete.
void ReadData(cdm::Host_8* aHost,
void ReadData(cdm::Host_9* aHost,
std::string& aRecordName,
std::function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
std::function<void()>&& aOnFailure);

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

@ -27,7 +27,7 @@
using namespace wmf;
using namespace cdm;
VideoDecoder::VideoDecoder(Host_8 *aHost)
VideoDecoder::VideoDecoder(Host_9 *aHost)
: mHost(aHost)
, mHasShutdown(false)
{

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

@ -30,7 +30,7 @@
class VideoDecoder : public RefCounted
{
public:
explicit VideoDecoder(cdm::Host_8 *aHost);
explicit VideoDecoder(cdm::Host_9 *aHost);
cdm::Status InitDecode(const cdm::VideoDecoderConfig& aConfig);
@ -64,7 +64,7 @@ private:
int32_t aFrameHeight,
cdm::VideoFrame* aVideoFrame);
cdm::Host_8* mHost;
cdm::Host_9* mHost;
wmf::AutoPtr<wmf::WMFH264Decoder> mDecoder;
std::queue<wmf::CComPtr<IMFSample>> mOutputQueue;

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

@ -56,7 +56,7 @@ void* CreateCdmInstance(int cdm_interface_version,
CK_LOGE("ClearKey CreateCDMInstance");
if (cdm_interface_version != cdm::ContentDecryptionModule_8::kVersion) {
if (cdm_interface_version != cdm::ContentDecryptionModule_9::kVersion) {
CK_LOGE("ClearKey CreateCDMInstance failed due to requesting unsupported version %d.",
cdm_interface_version);
return nullptr;
@ -75,7 +75,7 @@ void* CreateCdmInstance(int cdm_interface_version,
}
#endif
cdm::Host_8* host = static_cast<cdm::Host_8*>(
cdm::Host_9* host = static_cast<cdm::Host_9*>(
get_cdm_host_func(cdm_interface_version, user_data));
ClearKeyCDM* clearKey = new ClearKeyCDM(host);

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

@ -3,8 +3,8 @@
"description": "ClearKey Gecko Media Plugin",
"version": "1",
"x-cdm-module-versions": "4",
"x-cdm-interface-versions": "8",
"x-cdm-host-versions": "8",
"x-cdm-interface-versions": "9",
"x-cdm-host-versions": "9",
#ifdef ENABLE_WMF
"x-cdm-codecs": "avc1"
#else

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

@ -5,4 +5,4 @@ Makefile.in build files for the Mozilla build system.
The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
The git commit ID used was cf5ddc5316dd1ab3ee7f54b2dcbcc9980e556d13 (2017-10-26 09:48:04 +1300)
The git commit ID used was 8a0a30091cd7a7c71042f9dd25ba851ac3964466 (2017-11-15 14:03:13 +1300)

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

@ -112,7 +112,7 @@ to_string(io_side side)
typedef uint32_t device_flags_value;
enum device_flags {
DEV_UKNOWN = 0x00, /* Unkown */
DEV_UNKNOWN = 0x00, /* Unknown */
DEV_INPUT = 0x01, /* Record device like mic */
DEV_OUTPUT = 0x02, /* Playback device like speakers */
DEV_SYSTEM_DEFAULT = 0x04, /* System default device */
@ -121,7 +121,7 @@ enum device_flags {
struct device_info {
AudioDeviceID id = kAudioObjectUnknown;
device_flags_value flags = DEV_UKNOWN;
device_flags_value flags = DEV_UNKNOWN;
};
struct cubeb_stream {
@ -716,21 +716,16 @@ audiounit_property_listener_callback(AudioObjectID id, UInt32 address_count,
return noErr;
}
stm->switching_device = true;
device_flags_value switch_side = DEV_UKNOWN;
LOG("(%p) Audio device changed, %u events.", stm, (unsigned int) address_count);
for (UInt32 i = 0; i < address_count; i++) {
switch(addresses[i].mSelector) {
case kAudioHardwarePropertyDefaultOutputDevice: {
LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice for id=%d", (unsigned int) i, id);
// Allow restart to choose the new default
switch_side |= DEV_OUTPUT;
}
break;
case kAudioHardwarePropertyDefaultInputDevice: {
LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice for id=%d", (unsigned int) i, id);
// Allow restart to choose the new default
switch_side |= DEV_INPUT;
}
break;
case kAudioDevicePropertyDeviceIsAlive: {
@ -742,18 +737,10 @@ audiounit_property_listener_callback(AudioObjectID id, UInt32 address_count,
stm->switching_device = false;
return noErr;
}
// Allow restart to choose the new default. Event register only for input.
switch_side |= DEV_INPUT;
}
break;
case kAudioDevicePropertyDataSource: {
LOG("Event[%u] - mSelector == kAudioHardwarePropertyDataSource for id=%d", (unsigned int) i, id);
if (stm->input_unit) {
switch_side |= DEV_INPUT;
}
if (stm->output_unit) {
switch_side |= DEV_OUTPUT;
}
}
break;
default:
@ -763,6 +750,15 @@ audiounit_property_listener_callback(AudioObjectID id, UInt32 address_count,
}
}
// Allow restart to choose the new default
device_flags_value switch_side = DEV_UNKNOWN;
if (has_input(stm)) {
switch_side |= DEV_INPUT;
}
if (has_input(stm)) {
switch_side |= DEV_OUTPUT;
}
for (UInt32 i = 0; i < address_count; i++) {
switch(addresses[i].mSelector) {
case kAudioHardwarePropertyDefaultOutputDevice:
@ -784,9 +780,9 @@ audiounit_property_listener_callback(AudioObjectID id, UInt32 address_count,
dispatch_async(stm->context->serial_queue, ^() {
if (audiounit_reinit_stream(stm, switch_side) != CUBEB_OK) {
if (audiounit_uninstall_system_changed_callback(stm) != CUBEB_OK) {
LOG("(%p) Could not uninstall the device changed callback", stm);
LOG("(%p) Could not uninstall system changed callback", stm);
}
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
LOG("(%p) Could not reopen the stream after switching.", stm);
}
stm->switching_device = false;
@ -1683,6 +1679,8 @@ audiounit_activate_clock_drift_compensation(const AudioDeviceID aggregate_device
}
static int audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID * aggregate_device_id);
static void audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope scope,
uint32_t * min, uint32_t * max, uint32_t * def);
/*
* Aggregate Device is a virtual audio interface which utilizes inputs and outputs
@ -1706,6 +1704,26 @@ static int audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDevi
static int
audiounit_create_aggregate_device(cubeb_stream * stm)
{
uint32_t input_min_rate = 0;
uint32_t input_max_rate = 0;
uint32_t input_nominal_rate = 0;
audiounit_get_available_samplerate(stm->input_device.id, kAudioObjectPropertyScopeGlobal,
&input_min_rate, &input_max_rate, &input_nominal_rate);
LOG("(%p) Input device %u min: %u, max: %u, nominal: %u rate", stm, stm->input_device.id
, input_min_rate, input_max_rate, input_nominal_rate);
uint32_t output_min_rate = 0;
uint32_t output_max_rate = 0;
uint32_t output_nominal_rate = 0;
audiounit_get_available_samplerate(stm->output_device.id, kAudioObjectPropertyScopeGlobal,
&output_min_rate, &output_max_rate, &output_nominal_rate);
LOG("(%p) Output device %u min: %u, max: %u, nominal: %u rate", stm, stm->output_device.id
, output_min_rate, output_max_rate, output_nominal_rate);
if ((output_nominal_rate < input_min_rate || output_nominal_rate > output_max_rate)
|| (input_nominal_rate < output_min_rate || input_nominal_rate > output_max_rate)){
return CUBEB_ERROR;
}
int r = audiounit_create_blank_aggregate_device(&stm->plugin_id, &stm->aggregate_device_id);
if (r != CUBEB_OK) {
LOG("(%p) Failed to create blank aggregate device", stm);
@ -1734,6 +1752,24 @@ audiounit_create_aggregate_device(cubeb_stream * stm)
return CUBEB_ERROR;
}
if (input_nominal_rate != output_nominal_rate) {
Float64 rate = std::min(input_nominal_rate, output_nominal_rate);
AudioObjectPropertyAddress addr = {kAudioDevicePropertyNominalSampleRate,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
OSStatus rv = AudioObjectSetPropertyData(stm->aggregate_device_id,
&addr,
0,
nullptr,
sizeof(Float64),
&rate);
if (rv != noErr) {
LOG("AudioObjectSetPropertyData/kAudioDevicePropertyNominalSampleRate, rv=%d", rv);
return CUBEB_ERROR;
}
}
return CUBEB_OK;
}

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

@ -14,6 +14,7 @@
#include "WebrtcGmpVideoCodec.h"
#include "webrtc/media/base/videoadapter.h"
#include "webrtc/media/base/videosinkinterface.h"
#include "MockCall.h"
@ -35,7 +36,12 @@ public:
mInWidth = in_width;
mInHeight = in_height;
mInTimestampNs = in_timestamp_ns;
return true;
return cricket::VideoAdapter::AdaptFrameResolution(in_width, in_height,
in_timestamp_ns,
cropped_width,
cropped_height,
out_width,
out_height);
}
void OnResolutionRequest(rtc::Optional<int> max_pixel_count,
@ -43,11 +49,13 @@ public:
{
mMaxPixelCount = max_pixel_count.value_or(-1);
mMaxPixelCountStepUp = max_pixel_count_step_up.value_or(-1);
cricket::VideoAdapter::OnResolutionRequest(max_pixel_count, max_pixel_count_step_up);
}
void OnScaleResolutionBy(rtc::Optional<float> scale_resolution_by) override
{
mScaleResolutionBy = scale_resolution_by.value_or(-1.0);
cricket::VideoAdapter::OnScaleResolutionBy(scale_resolution_by);
}
int mInWidth;
@ -58,6 +66,19 @@ public:
int mScaleResolutionBy;
};
class MockVideoSink : public rtc::VideoSinkInterface<webrtc::VideoFrame>
{
public:
~MockVideoSink() override = default;
void OnFrame(const webrtc::VideoFrame& frame) override
{
mVideoFrame = frame;
}
webrtc::VideoFrame mVideoFrame;
};
class VideoConduitTest : public ::testing::Test {
public:
@ -76,7 +97,8 @@ public:
~VideoConduitTest() override = default;
MediaConduitErrorCode SendVideoFrame(unsigned short width,
unsigned short height)
unsigned short height,
uint64_t capture_time_ms)
{
unsigned int yplane_length = width*height;
unsigned int cbcrplane_length = (width*height + 1)/2;
@ -85,7 +107,8 @@ public:
memset(buffer, 0x10, yplane_length);
memset(buffer + yplane_length, 0x80, cbcrplane_length);
return mVideoConduit->SendVideoFrame(buffer, video_length, width, height,
VideoType::kVideoI420, 1);
VideoType::kVideoI420,
capture_time_ms);
}
MockCall* mCall;
@ -421,7 +444,7 @@ TEST_F(VideoConduitTest, TestConfigureSendMediaCodecMaxMbps)
ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfig);
ASSERT_EQ(ec, kMediaConduitNoError);
mVideoConduit->StartTransmitting();
SendVideoFrame(640, 480);
SendVideoFrame(640, 480, 1);
std::vector<webrtc::VideoStream> videoStreams;
videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(640, 480, mCall->mEncoderConfig);
ASSERT_EQ(videoStreams.size(), 1U);
@ -434,7 +457,7 @@ TEST_F(VideoConduitTest, TestConfigureSendMediaCodecMaxMbps)
ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfig2);
ASSERT_EQ(ec, kMediaConduitNoError);
mVideoConduit->StartTransmitting();
SendVideoFrame(640, 480);
SendVideoFrame(640, 480, 1);
videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(640, 480, mCall->mEncoderConfig);
ASSERT_EQ(videoStreams.size(), 1U);
ASSERT_EQ(videoStreams[0].max_framerate, 8);
@ -460,7 +483,7 @@ TEST_F(VideoConduitTest, TestConfigureSendMediaCodecDefaults)
ASSERT_EQ(videoStreams[0].max_bitrate_bps, static_cast<int32_t>(WebrtcVideoConduit::kDefaultMaxBitrate_bps));
// SelectBitrates not called until we send a frame
SendVideoFrame(1280, 720);
SendVideoFrame(1280, 720, 1);
videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(1280, 720, mCall->mEncoderConfig);
ASSERT_EQ(videoStreams.size(), 1U);
// These values come from a table and are determined by resolution
@ -484,7 +507,7 @@ TEST_F(VideoConduitTest, TestConfigureSendMediaCodecTias)
ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfigTias);
ASSERT_EQ(ec, kMediaConduitNoError);
mVideoConduit->StartTransmitting();
SendVideoFrame(1280, 720);
SendVideoFrame(1280, 720, 1);
videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(1280, 720, mCall->mEncoderConfig);
ASSERT_EQ(videoStreams.size(), 1U);
ASSERT_EQ(videoStreams[0].min_bitrate_bps, 600000);
@ -499,7 +522,7 @@ TEST_F(VideoConduitTest, TestConfigureSendMediaCodecTias)
ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfigTiasLow);
ASSERT_EQ(ec, kMediaConduitNoError);
mVideoConduit->StartTransmitting();
SendVideoFrame(1280, 720);
SendVideoFrame(1280, 720, 1);
videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(1280, 720, mCall->mEncoderConfig);
ASSERT_EQ(videoStreams.size(), 1U);
ASSERT_EQ(videoStreams[0].min_bitrate_bps, 30000);
@ -521,7 +544,7 @@ TEST_F(VideoConduitTest, TestConfigureSendMediaCodecMaxBr)
ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfig);
ASSERT_EQ(ec, kMediaConduitNoError);
mVideoConduit->StartTransmitting();
SendVideoFrame(1280, 720);
SendVideoFrame(1280, 720, 1);
videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(1280, 720, mCall->mEncoderConfig);
ASSERT_EQ(videoStreams.size(), 1U);
ASSERT_LE(videoStreams[0].min_bitrate_bps, 50000);
@ -549,7 +572,7 @@ TEST_F(VideoConduitTest, TestConfigureSendMediaCodecScaleResolutionBy)
ASSERT_EQ(ec, kMediaConduitNoError);
mVideoConduit->StartTransmitting();
ASSERT_EQ(mAdapter->mScaleResolutionBy, 2);
SendVideoFrame(640, 360);
SendVideoFrame(640, 360, 1);
videoStreams = mCall->mEncoderConfig.video_stream_factory->CreateEncoderStreams(640, 360, mCall->mEncoderConfig);
ASSERT_EQ(videoStreams.size(), 2U);
ASSERT_EQ(videoStreams[0].width, 320U);
@ -714,4 +737,136 @@ TEST_F(VideoConduitTest, TestOnSinkWantsChanged)
ASSERT_EQ(mAdapter->mMaxPixelCount, 64000);
}
TEST_F(VideoConduitTest, TestVideoEncode)
{
MediaConduitErrorCode ec;
EncodingConstraints constraints;
VideoCodecConfig::SimulcastEncoding encoding;
std::vector<webrtc::VideoStream> videoStreams;
VideoCodecConfig codecConfig(120, "VP8", constraints);
codecConfig.mSimulcastEncodings.push_back(encoding);
ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfig);
ASSERT_EQ(ec, kMediaConduitNoError);
UniquePtr<MockVideoSink> sink(new MockVideoSink());
rtc::VideoSinkWants wants;
mVideoConduit->AddOrUpdateSink(sink.get(), wants);
mVideoConduit->StartTransmitting();
SendVideoFrame(1280, 720, 1);
ASSERT_EQ(sink->mVideoFrame.width(), 1280);
ASSERT_EQ(sink->mVideoFrame.height(), 720);
ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U);
SendVideoFrame(640, 360, 2);
ASSERT_EQ(sink->mVideoFrame.width(), 640);
ASSERT_EQ(sink->mVideoFrame.height(), 360);
ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U);
SendVideoFrame(1920, 1280, 3);
ASSERT_EQ(sink->mVideoFrame.width(), 1920);
ASSERT_EQ(sink->mVideoFrame.height(), 1280);
ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U);
mVideoConduit->StopTransmitting();
mVideoConduit->RemoveSink(sink.get());
}
TEST_F(VideoConduitTest, TestVideoEncodeMaxFs)
{
MediaConduitErrorCode ec;
EncodingConstraints constraints;
VideoCodecConfig::SimulcastEncoding encoding;
std::vector<webrtc::VideoStream> videoStreams;
VideoCodecConfig codecConfig(120, "VP8", constraints);
codecConfig.mEncodingConstraints.maxFs = 3600;
codecConfig.mSimulcastEncodings.push_back(encoding);
ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfig);
ASSERT_EQ(ec, kMediaConduitNoError);
UniquePtr<MockVideoSink> sink(new MockVideoSink());
rtc::VideoSinkWants wants;
mVideoConduit->AddOrUpdateSink(sink.get(), wants);
mVideoConduit->StartTransmitting();
SendVideoFrame(1280, 720, 1);
ASSERT_EQ(sink->mVideoFrame.width(), 1280);
ASSERT_EQ(sink->mVideoFrame.height(), 720);
ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U);
SendVideoFrame(640, 360, 2);
ASSERT_EQ(sink->mVideoFrame.width(), 640);
ASSERT_EQ(sink->mVideoFrame.height(), 360);
ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U);
SendVideoFrame(1920, 1280, 3);
ASSERT_EQ(sink->mVideoFrame.width(), 960);
ASSERT_EQ(sink->mVideoFrame.height(), 640);
ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U);
// maxFs should not force pixel count above what a sink has requested.
// We set 3600 macroblocks (16x16 pixels), so we request 3500 here.
wants.max_pixel_count = rtc::Optional<int>(3500*16*16);
mVideoConduit->AddOrUpdateSink(sink.get(), wants);
SendVideoFrame(1280, 720, 4);
ASSERT_EQ(sink->mVideoFrame.width(), 960);
ASSERT_EQ(sink->mVideoFrame.height(), 540);
ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 4000U);
SendVideoFrame(640, 360, 5);
ASSERT_EQ(sink->mVideoFrame.width(), 640);
ASSERT_EQ(sink->mVideoFrame.height(), 360);
ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 5000U);
SendVideoFrame(1920, 1280, 6);
ASSERT_EQ(sink->mVideoFrame.width(), 960);
ASSERT_EQ(sink->mVideoFrame.height(), 640);
ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 6000U);
mVideoConduit->StopTransmitting();
mVideoConduit->RemoveSink(sink.get());
}
// Disabled: See Bug 1420493
TEST_F(VideoConduitTest, DISABLED_TestVideoEncodeMaxWidthAndHeight)
{
MediaConduitErrorCode ec;
EncodingConstraints constraints;
VideoCodecConfig::SimulcastEncoding encoding;
std::vector<webrtc::VideoStream> videoStreams;
VideoCodecConfig codecConfig(120, "VP8", constraints);
codecConfig.mEncodingConstraints.maxWidth = 1280;
codecConfig.mEncodingConstraints.maxHeight = 720;
codecConfig.mSimulcastEncodings.push_back(encoding);
ec = mVideoConduit->ConfigureSendMediaCodec(&codecConfig);
ASSERT_EQ(ec, kMediaConduitNoError);
UniquePtr<MockVideoSink> sink(new MockVideoSink());
rtc::VideoSinkWants wants;
mVideoConduit->AddOrUpdateSink(sink.get(), wants);
mVideoConduit->StartTransmitting();
SendVideoFrame(1280, 720, 1);
ASSERT_EQ(sink->mVideoFrame.width(), 1280);
ASSERT_EQ(sink->mVideoFrame.height(), 720);
ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 1000U);
SendVideoFrame(640, 360, 2);
ASSERT_EQ(sink->mVideoFrame.width(), 640);
ASSERT_EQ(sink->mVideoFrame.height(), 360);
ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 2000U);
SendVideoFrame(1920, 1280, 3);
ASSERT_EQ(sink->mVideoFrame.width(), 1080);
ASSERT_EQ(sink->mVideoFrame.height(), 720);
ASSERT_EQ(sink->mVideoFrame.timestamp_us(), 3000U);
mVideoConduit->StopTransmitting();
mVideoConduit->RemoveSink(sink.get());
}
} // End namespace test.

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

@ -582,45 +582,7 @@ int32_t DesktopCaptureImpl::IncomingFrame(uint8_t* videoFrame,
return -1;
}
int32_t req_max_width = _requestedCapability.width & 0xffff;
int32_t req_max_height = _requestedCapability.height & 0xffff;
int32_t req_ideal_width = (_requestedCapability.width >> 16) & 0xffff;
int32_t req_ideal_height = (_requestedCapability.height >> 16) & 0xffff;
int32_t dest_max_width = std::min(req_max_width, target_width);
int32_t dest_max_height = std::min(req_max_height, target_height);
int32_t dst_width = std::min(req_ideal_width > 0 ? req_ideal_width : target_width, dest_max_width);
int32_t dst_height = std::min(req_ideal_height > 0 ? req_ideal_height : target_height, dest_max_height);
// scale to average of portrait and landscape
float scale_width = (float)dst_width / (float)target_width;
float scale_height = (float)dst_height / (float)target_height;
float scale = (scale_width + scale_height) / 2;
dst_width = (int)(scale * target_width);
dst_height = (int)(scale * target_height);
// if scaled rectangle exceeds max rectangle, scale to minimum of portrait and landscape
if (dst_width > dest_max_width || dst_height > dest_max_height) {
scale_width = (float)dest_max_width / (float)dst_width;
scale_height = (float)dest_max_height / (float)dst_height;
scale = std::min(scale_width, scale_height);
dst_width = (int)(scale * dst_width);
dst_height = (int)(scale * dst_height);
}
int dst_stride_y = dst_width;
int dst_stride_uv = (dst_width + 1) / 2;
if (dst_width == target_width && dst_height == target_height) {
DeliverCapturedFrame(captureFrame, captureTime);
} else {
rtc::scoped_refptr<webrtc::I420Buffer> buffer;
buffer = I420Buffer::Create(dst_width, dst_height, dst_stride_y,
dst_stride_uv, dst_stride_uv);
buffer->ScaleFrom(*captureFrame.video_frame_buffer().get());
webrtc::VideoFrame scaledFrame(buffer, 0, 0, kVideoRotation_0);
DeliverCapturedFrame(scaledFrame, captureTime);
}
DeliverCapturedFrame(captureFrame, captureTime);
} else {
assert(false);
return -1;

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

@ -152,6 +152,7 @@ public class Tabs implements BundleEventListener {
"Content:SecurityChange",
"Content:StateChange",
"Content:LoadError",
"Content:DOMContentLoaded",
"Content:PageShow",
"Content:DOMTitleChanged",
"DesktopMode:Changed",
@ -637,6 +638,10 @@ public class Tabs implements BundleEventListener {
tab.handleContentLoaded();
notifyListeners(tab, Tabs.TabEvents.LOAD_ERROR);
} else if ("Content:DOMContentLoaded".equals(event)) {
tab.handleContentLoaded();
notifyListeners(tab, TabEvents.LOADED);
} else if ("Content:PageShow".equals(event)) {
tab.setLoadedFromCache(message.getBoolean("fromCache"));
tab.updateUserRequested(message.getString("userRequested"));

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

@ -85,7 +85,7 @@ abstract class AboutHomeTest extends PixelTest {
View bookmark = getDisplayedBookmark(url);
if (bookmark != null) {
Actions.EventExpecter contentEventExpecter =
mActions.expectGlobalEvent(Actions.EventType.GECKO, "Content:DOMContentLoaded");
mActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded");
mSolo.clickOnView(bookmark);
contentEventExpecter.blockForEvent();
contentEventExpecter.unregisterListener();

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

@ -158,7 +158,7 @@ abstract class OldBaseTest extends BaseRobocopTest {
protected final void hitEnterAndWait() {
Actions.EventExpecter contentEventExpecter =
mActions.expectGlobalEvent(Actions.EventType.GECKO, "Content:DOMContentLoaded");
mActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded");
mActions.sendSpecialKey(Actions.SpecialKey.ENTER);
// wait for screen to load
contentEventExpecter.blockForEvent();
@ -202,7 +202,7 @@ abstract class OldBaseTest extends BaseRobocopTest {
*/
protected final void loadUrlAndWait(final String url) {
Actions.EventExpecter contentEventExpecter =
mActions.expectGlobalEvent(Actions.EventType.GECKO, "Content:DOMContentLoaded");
mActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded");
loadUrl(url);
contentEventExpecter.blockForEvent();
contentEventExpecter.unregisterListener();

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

@ -120,7 +120,7 @@ public final class WaitHelper {
// Wait for the page load and title changed event.
final EventExpecter[] eventExpecters = new EventExpecter[] {
sActions.expectGlobalEvent(Actions.EventType.GECKO, "Content:DOMContentLoaded"),
sActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded"),
sActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMTitleChanged")
};

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

@ -30,7 +30,7 @@ public class testAboutPage extends PixelTest {
// Set up listeners to catch the page load we're about to do.
Actions.EventExpecter tabEventExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "Tab:Added");
Actions.EventExpecter contentEventExpecter = mActions.expectGlobalEvent(Actions.EventType.GECKO, "Content:DOMContentLoaded");
Actions.EventExpecter contentEventExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded");
selectSettingsItem(mStringHelper.MOZILLA_SECTION_LABEL, mStringHelper.ABOUT_LABEL);

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

@ -30,7 +30,7 @@ public class testAddonManager extends PixelTest {
// Set up listeners to catch the page load we're about to do
tabEventExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "Tab:Added");
contentEventExpecter = mActions.expectGlobalEvent(Actions.EventType.GECKO, "Content:DOMContentLoaded");
contentEventExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded");
// Wait for the new tab and page to load
tabEventExpecter.blockForEvent();
@ -51,7 +51,7 @@ public class testAddonManager extends PixelTest {
// Setup wait for tab to spawn and load
tabEventExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "Tab:Added");
contentEventExpecter = mActions.expectGlobalEvent(Actions.EventType.GECKO, "Content:DOMContentLoaded");
contentEventExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded");
// Open a new tab
final String blankURL = getAbsoluteUrl(mStringHelper.ROBOCOP_BLANK_PAGE_01_URL);

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

@ -436,6 +436,9 @@ pref("media.suspend-bkgnd-video.delay-ms", 10000);
// reduce the resume latency and improve the user experience.
pref("media.resume-bkgnd-video-on-tabhover", true);;
// Whether to enable media seamless looping.
pref("media.seamless-looping", true);
#ifdef MOZ_WEBRTC
pref("media.navigator.enabled", true);
pref("media.navigator.video.enabled", true);
@ -545,7 +548,7 @@ pref("media.getusermedia.screensharing.enabled", true);
pref("media.getusermedia.audiocapture.enabled", false);
// TextTrack WebVTT Region extension support.
pref("media.webvtt.regions.enabled", false);
pref("media.webvtt.regions.enabled", true);
// WebVTT pseudo element and class support.
pref("media.webvtt.pseudo.enabled", true);

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

@ -6,5 +6,7 @@ module.exports = {
],
"rules": {
"no-throw-literal": 2,
"mozilla/use-services": "error",
},
}

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

@ -381,8 +381,6 @@ async function updatePinningList(records) {
if (!Services.prefs.getBoolPref(PREF_BLOCKLIST_PINNING_ENABLED)) {
return;
}
const appInfo = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo);
const siteSecurityService = Cc["@mozilla.org/ssservice;1"]
.getService(Ci.nsISiteSecurityService);
@ -394,7 +392,7 @@ async function updatePinningList(records) {
for (let item of records) {
try {
const {pinType, pins = [], versions} = item;
if (versions.indexOf(appInfo.version) != -1) {
if (versions.indexOf(Services.appinfo.version) != -1) {
if (pinType == "KeyPin" && pins.length) {
siteSecurityService.setKeyPins(item.hostName,
item.includeSubdomains,

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

@ -10,6 +10,7 @@ var Cr = Components.results;
var Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
/**
* A service for adding, removing and notifying observers of notifications.
@ -36,7 +37,7 @@ this.Observers = {
add(topic, callback, thisObject) {
let observer = new Observer(topic, callback, thisObject);
this._cache.push(observer);
this._service.addObserver(observer, topic, true);
Services.obs.addObserver(observer, topic, true);
return observer;
},
@ -62,7 +63,7 @@ this.Observers = {
v.callback == callback &&
v.thisObject == thisObject);
if (observer) {
this._service.removeObserver(observer, topic);
Services.obs.removeObserver(observer, topic);
this._cache.splice(this._cache.indexOf(observer), 1);
} else {
throw new Error("Attempt to remove non-existing observer");
@ -88,12 +89,9 @@ this.Observers = {
notify(topic, subject, data) {
subject = (typeof subject == "undefined") ? null : new Subject(subject);
data = (typeof data == "undefined") ? null : data;
this._service.notifyObservers(subject, topic, data);
Services.obs.notifyObservers(subject, topic, data);
},
_service: Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService),
/**
* A cache of observers that have been added.
*

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

@ -7,7 +7,7 @@ Cu.import("resource://testing-common/httpd.js");
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream", "setInputStream");
// First, we need to setup appInfo or we can't do version checks
// First, we need to setup Services.appinfo or we can't do version checks
var id = "xpcshell@tests.mozilla.org";
var appName = "XPCShell";
var version = "1";
@ -166,9 +166,6 @@ function run_test() {
// get a response for a given request from sample data
function getSampleResponse(req, port) {
const appInfo = Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo);
const responses = {
"OPTIONS": {
"sampleHeaders": [
@ -216,7 +213,7 @@ function getSampleResponse(req, port) {
"expires": new Date().getTime() + 1000000,
"pins": ["cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=",
"M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="],
"versions": [appInfo.version],
"versions": [Services.appinfo.version],
"id": "78cf8900-fdea-4ce5-f8fb-b78710617718",
"last_modified": 3000
}]})
@ -237,7 +234,7 @@ function getSampleResponse(req, port) {
"expires": new Date().getTime() + 1000000,
"pins": ["cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=",
"M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="],
"versions": [appInfo.version],
"versions": [Services.appinfo.version],
"id": "dabafde9-df4a-ddba-2548-748da04cc02c",
"last_modified": 4000
}, {
@ -247,7 +244,7 @@ function getSampleResponse(req, port) {
"expires": new Date().getTime() + 1000000,
"pins": ["cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=",
"M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE="],
"versions": [appInfo.version, "some other version that won't match"],
"versions": [Services.appinfo.version, "some other version that won't match"],
"id": "dabafde9-df4a-ddba-2548-748da04cc02d",
"last_modified": 4000
}, {
@ -265,7 +262,7 @@ function getSampleResponse(req, port) {
"hostName": "five.example.com",
"includeSubdomains": false,
"expires": new Date().getTime() + 1000000,
"versions": [appInfo.version, "some version that won't match"],
"versions": [Services.appinfo.version, "some version that won't match"],
"id": "dabafde9-df4a-ddba-2548-748da04cc032",
"last_modified": 4000
}]})
@ -299,7 +296,7 @@ function getSampleResponse(req, port) {
"hostName": "missingpins.example.com",
"includeSubdomains": false,
"expires": new Date().getTime() + 1000000,
"versions": [appInfo.version],
"versions": [Services.appinfo.version],
"id": "dabafde9-df4a-ddba-2548-748da04cc031",
"last_modified": 5000
}, {
@ -307,7 +304,7 @@ function getSampleResponse(req, port) {
"hostName": "five.example.com",
"includeSubdomains": true,
"expires": new Date().getTime() + 1000000,
"versions": [appInfo.version, "some version that won't match"],
"versions": [Services.appinfo.version, "some version that won't match"],
"id": "dabafde9-df4a-ddba-2548-748da04cc032",
"last_modified": 5000
}]})

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

@ -9,6 +9,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
try {
// In the context of xpcshell tests, there won't be a default AppInfo
// eslint-disable-next-line mozilla/use-services
Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
} catch (ex) {

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

@ -68,22 +68,6 @@ this.FxAccountsConfig = {
}
// Reset the webchannel.
EnsureFxAccountsWebChannel();
if (!Services.prefs.prefHasUserValue("webchannel.allowObject.urlWhitelist")) {
return;
}
let whitelistValue = Services.prefs.getCharPref("webchannel.allowObject.urlWhitelist");
if (whitelistValue.startsWith(autoconfigURL + " ")) {
whitelistValue = whitelistValue.slice(autoconfigURL.length + 1);
// Check and see if the value will be the default, and just clear the pref if it would
// to avoid it showing up as changed in about:config.
let defaultWhitelist = Services.prefs.getDefaultBranch("webchannel.allowObject.").getCharPref("urlWhitelist", "");
if (defaultWhitelist === whitelistValue) {
Services.prefs.clearUserPref("webchannel.allowObject.urlWhitelist");
} else {
Services.prefs.setCharPref("webchannel.allowObject.urlWhitelist", whitelistValue);
}
}
},
getAutoConfigURL() {
@ -163,11 +147,6 @@ this.FxAccountsConfig = {
Services.prefs.setCharPref("identity.fxaccounts.remote.email.uri", rootURL + "/?service=sync&context=" + contextParam + "&action=email");
Services.prefs.setCharPref("identity.fxaccounts.remote.force_auth.uri", rootURL + "/force_auth?service=sync&context=" + contextParam);
let whitelistValue = Services.prefs.getCharPref("webchannel.allowObject.urlWhitelist");
if (!whitelistValue.includes(rootURL)) {
whitelistValue = `${rootURL} ${whitelistValue}`;
Services.prefs.setCharPref("webchannel.allowObject.urlWhitelist", whitelistValue);
}
// Ensure the webchannel is pointed at the correct uri
EnsureFxAccountsWebChannel();
} catch (e) {

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