зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to mozilla-central r=merge a=merge
This commit is contained in:
Коммит
5e413fc090
|
@ -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) {
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче