Merge autoland to central, a=merge CLOSED TREE

MozReview-Commit-ID: HLPLchv8Lt7
This commit is contained in:
Wes Kocher 2017-06-06 16:18:16 -07:00
commit b7e2a83ab4
198 changed files with 7918 additions and 9372 deletions

View File

@ -136,6 +136,7 @@ GPATH
^testing/talos/talos/tests/tp5n
^testing/talos/talos/tests/devtools/damp.manifest.develop
^talos-venv
^py3venv
# Ignore files created when running a reftest.
^lextab.py$

View File

@ -2490,8 +2490,6 @@ ContentPermissionPrompt.prototype = {
var DefaultBrowserCheck = {
get OPTIONPOPUP() { return "defaultBrowserNotificationPopup" },
_setAsDefaultTimer: null,
_setAsDefaultButtonClickStartTime: 0,
closePrompt(aNode) {
if (this._notification) {
@ -2514,33 +2512,6 @@ var DefaultBrowserCheck = {
}
try {
ShellService.setDefaultBrowser(claimAllTypes, false);
if (this._setAsDefaultTimer) {
this._setAsDefaultTimer.cancel();
}
this._setAsDefaultButtonClickStartTime = Math.floor(Date.now() / 1000);
this._setAsDefaultTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._setAsDefaultTimer.init(() => {
let isDefault = false;
let isDefaultError = false;
try {
isDefault = ShellService.isDefaultBrowser(true, false);
} catch (ex) {
isDefaultError = true;
}
let now = Math.floor(Date.now() / 1000);
let runTime = now - this._setAsDefaultButtonClickStartTime;
if (isDefault || runTime > 600) {
this._setAsDefaultTimer.cancel();
this._setAsDefaultTimer = null;
Services.telemetry.getHistogramById("BROWSER_SET_DEFAULT_TIME_TO_COMPLETION_SECONDS")
.add(runTime);
}
Services.telemetry.getHistogramById("BROWSER_IS_USER_DEFAULT_ERROR")
.add(isDefaultError);
}, 1000, Ci.nsITimer.TYPE_REPEATING_SLACK);
} catch (ex) {
setAsDefaultError = true;
Cu.reportError(ex);

View File

@ -217,23 +217,22 @@
</vbox>
</hbox>
<vbox id="dialogOverlay" align="center" pack="center">
<groupbox id="dialogBox"
orient="vertical"
pack="end"
role="dialog"
aria-labelledby="dialogTitle">
<caption flex="1" align="center">
<label id="dialogTitle" flex="1"></label>
<button id="dialogClose"
class="close-icon"
aria-label="&preferencesCloseButton.label;"/>
</caption>
<browser id="dialogFrame"
name="dialogFrame"
autoscroll="false"
disablehistory="true"/>
</groupbox>
</vbox>
<stack id="dialogStack" hidden="true"/>
<vbox id="dialogTemplate" class="dialogOverlay" align="center" pack="center" topmost="true" hidden="true">
<groupbox class="dialogBox"
orient="vertical"
pack="end"
role="dialog"
aria-labelledby="dialogTitle">
<caption flex="1" align="center">
<label class="dialogTitle" flex="1"></label>
<button class="dialogClose close-icon"
aria-label="&preferencesCloseButton.label;"/>
</caption>
<browser class="dialogFrame"
autoscroll="false"
disablehistory="true"/>
</groupbox>
</vbox>
</stack>
</page>

View File

@ -7,11 +7,38 @@
"use strict";
var gSubDialog = {
/**
* SubDialog constructor creates a new subdialog from a template and appends
* it to the parentElement.
* @param {DOMNode} template: The template is copied to create a new dialog.
* @param {DOMNode} parentElement: New dialog is appended onto parentElement.
* @param {String} id: A unique identifier for the dialog.
*/
function SubDialog({template, parentElement, id}) {
this._id = id;
this._overlay = template.cloneNode(true);
this._frame = this._overlay.querySelector(".dialogFrame");
this._box = this._overlay.querySelector(".dialogBox");
this._closeButton = this._overlay.querySelector(".dialogClose");
this._titleElement = this._overlay.querySelector(".dialogTitle");
this._overlay.id = `dialogOverlay-${id}`;
this._frame.setAttribute("name", `dialogFrame-${id}`);
this._frameCreated = new Promise(resolve => {
this._frame.addEventListener("load", resolve, {once: true});
});
parentElement.appendChild(this._overlay);
this._overlay.hidden = false;
}
SubDialog.prototype = {
_closingCallback: null,
_closingEvent: null,
_isClosing: false,
_frame: null,
_frameCreated: null,
_overlay: null,
_box: null,
_openedURL: null,
@ -22,18 +49,15 @@ var gSubDialog = {
"chrome://browser/skin/preferences/in-content-new/dialog.css",
],
_resizeObserver: null,
init() {
this._frame = document.getElementById("dialogFrame");
this._overlay = document.getElementById("dialogOverlay");
this._box = document.getElementById("dialogBox");
this._closeButton = document.getElementById("dialogClose");
},
_template: null,
_id: null,
_titleElement: null,
_closeButton: null,
updateTitle(aEvent) {
if (aEvent.target != gSubDialog._frame.contentDocument)
if (aEvent.target != this._frame.contentDocument)
return;
document.getElementById("dialogTitle").textContent = gSubDialog._frame.contentDocument.title;
this._titleElement.textContent = this._frame.contentDocument.title;
},
injectXMLStylesheet(aStylesheetURL) {
@ -45,11 +69,9 @@ var gSubDialog = {
this._frame.contentDocument.documentElement);
},
open(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
// If we're already open/opening on this URL, do nothing.
if (this._openedURL == aURL && !this._isClosing) {
return;
}
async open(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
// Wait until frame is ready to prevent browser crash in tests
await this._frameCreated;
// If we're open on some (other) URL or we're closing, open when closing has finished.
if (this._openedURL || this._isClosing) {
if (!this._isClosing) {
@ -64,7 +86,7 @@ var gSubDialog = {
this._addDialogEventListeners();
let features = (aFeatures ? aFeatures + "," : "") + "resizable,dialog=no,centerscreen";
let dialog = window.openDialog(aURL, "dialogFrame", features, aParams);
let dialog = window.openDialog(aURL, `dialogFrame-${this._id}`, features, aParams);
if (aClosingCallback) {
this._closingCallback = aClosingCallback.bind(dialog);
}
@ -109,6 +131,11 @@ var gSubDialog = {
this._box.style.removeProperty("min-height");
this._box.style.removeProperty("min-width");
this._overlay.dispatchEvent(new CustomEvent("dialogclose", {
bubbles: true,
detail: { dialog: this },
}));
setTimeout(() => {
// Unload the dialog after the event listeners run so that the load of about:blank isn't
// cancelled by the ESC <key>.
@ -185,32 +212,32 @@ var gSubDialog = {
this._frame.contentWindow.addEventListener("dialogclosing", this);
let oldResizeBy = this._frame.contentWindow.resizeBy;
this._frame.contentWindow.resizeBy = function(resizeByWidth, resizeByHeight) {
this._frame.contentWindow.resizeBy = (resizeByWidth, resizeByHeight) => {
// Only handle resizeByHeight currently.
let frameHeight = gSubDialog._frame.clientHeight;
let boxMinHeight = parseFloat(getComputedStyle(gSubDialog._box).minHeight, 10);
let frameHeight = this._frame.clientHeight;
let boxMinHeight = parseFloat(getComputedStyle(this._box).minHeight, 10);
gSubDialog._frame.style.height = (frameHeight + resizeByHeight) + "px";
gSubDialog._box.style.minHeight = (boxMinHeight + resizeByHeight) + "px";
this._frame.style.height = (frameHeight + resizeByHeight) + "px";
this._box.style.minHeight = (boxMinHeight + resizeByHeight) + "px";
oldResizeBy.call(gSubDialog._frame.contentWindow, resizeByWidth, resizeByHeight);
oldResizeBy.call(this._frame.contentWindow, resizeByWidth, resizeByHeight);
};
// Make window.close calls work like dialog closing.
let oldClose = this._frame.contentWindow.close;
this._frame.contentWindow.close = function() {
var closingEvent = gSubDialog._closingEvent;
this._frame.contentWindow.close = () => {
var closingEvent = this._closingEvent;
if (!closingEvent) {
closingEvent = new CustomEvent("dialogclosing", {
bubbles: true,
detail: { button: null },
});
gSubDialog._frame.contentWindow.dispatchEvent(closingEvent);
this._frame.contentWindow.dispatchEvent(closingEvent);
}
gSubDialog.close(closingEvent);
oldClose.call(gSubDialog._frame.contentWindow);
this.close(closingEvent);
oldClose.call(this._frame.contentWindow);
};
// XXX: Hack to make focus during the dialog's load functions work. Make the element visible
@ -293,6 +320,10 @@ var gSubDialog = {
(boxVerticalBorder + groupBoxTitleHeight + boxVerticalPadding) +
"px + " + frameMinHeight + ")";
this._overlay.dispatchEvent(new CustomEvent("dialogopen", {
bubbles: true,
detail: { dialog: this },
}));
this._overlay.style.visibility = "visible";
this._overlay.style.opacity = ""; // XXX: focus hack continued from _onContentLoaded
@ -305,7 +336,7 @@ var gSubDialog = {
},
_onResize(mutations) {
let frame = gSubDialog._frame;
let frame = this._frame;
// The width and height styles are needed for the initial
// layout of the frame, but afterward they need to be removed
// or their presence will restrict the contents of the <browser>
@ -348,13 +379,13 @@ var gSubDialog = {
let fm = Services.focus;
function isLastFocusableElement(el) {
let isLastFocusableElement = el => {
// XXXgijs unfortunately there is no way to get the last focusable element without asking
// the focus manager to move focus to it.
let rv = el == fm.moveFocus(gSubDialog._frame.contentWindow, null, fm.MOVEFOCUS_LAST, 0);
let rv = el == fm.moveFocus(this._frame.contentWindow, null, fm.MOVEFOCUS_LAST, 0);
fm.setFocus(el, 0);
return rv;
}
};
let forward = !aEvent.shiftKey;
// check if focus is leaving the frame (incl. the close button):
@ -449,3 +480,104 @@ var gSubDialog = {
.chromeEventHandler;
},
};
var gSubDialog = {
/**
* New dialogs are stacked on top of the existing ones, and they are pushed
* to the end of the _dialogs array.
* @type {Array}
*/
_dialogs: [],
_dialogStack: null,
_dialogTemplate: null,
_nextDialogID: 0,
_preloadDialog: null,
get _topDialog() {
return this._dialogs.length > 0 ? this._dialogs[this._dialogs.length - 1] : undefined;
},
init() {
this._dialogStack = document.getElementById("dialogStack");
this._dialogTemplate = document.getElementById("dialogTemplate");
this._preloadDialog = new SubDialog({template: this._dialogTemplate,
parentElement: this._dialogStack,
id: this._nextDialogID++});
},
open(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
// If we're already open/opening on this URL, do nothing.
if (this._topDialog && this._topDialog._openedURL == aURL) {
return;
}
this._preloadDialog.open(aURL, aFeatures, aParams, aClosingCallback);
this._dialogs.push(this._preloadDialog);
this._preloadDialog = new SubDialog({template: this._dialogTemplate,
parentElement: this._dialogStack,
id: this._nextDialogID++});
if (this._dialogs.length == 1) {
this._dialogStack.hidden = false;
this._ensureStackEventListeners();
}
},
close() {
this._topDialog.close();
},
handleEvent(aEvent) {
switch (aEvent.type) {
case "dialogopen": {
this._onDialogOpen();
break;
}
case "dialogclose": {
this._onDialogClose(aEvent.detail.dialog);
break;
}
}
},
_onDialogOpen() {
let lowerDialog = this._dialogs.length > 1 ? this._dialogs[this._dialogs.length - 2] : undefined;
if (lowerDialog) {
lowerDialog._overlay.removeAttribute("topmost");
lowerDialog._removeDialogEventListeners();
}
},
_onDialogClose(dialog) {
let fm = Services.focus;
if (this._topDialog == dialog) {
// XXX: When a top-most dialog is closed, we reuse the closed dialog and
// remove the preloadDialog. This is a temporary solution before we
// rewrite all the test cases in Bug 1359023.
this._preloadDialog._overlay.remove();
this._preloadDialog = this._dialogs.pop();
} else {
dialog._overlay.remove();
this._dialogs.splice(this._dialogs.indexOf(dialog), 1);
}
if (this._topDialog) {
fm.moveFocus(this._topDialog._frame.contentWindow, null, fm.MOVEFOCUS_FIRST, fm.FLAG_BYKEY);
this._topDialog._overlay.setAttribute("topmost", true);
this._topDialog._addDialogEventListeners();
} else {
fm.moveFocus(window, null, fm.MOVEFOCUS_ROOT, fm.FLAG_BYKEY);
this._dialogStack.hidden = true;
this._removeStackEventListeners();
}
},
_ensureStackEventListeners() {
this._dialogStack.addEventListener("dialogopen", this);
this._dialogStack.addEventListener("dialogclose", this);
},
_removeStackEventListeners() {
this._dialogStack.removeEventListener("dialogopen", this);
this._dialogStack.removeEventListener("dialogclose", this);
},
};

View File

@ -114,7 +114,7 @@ add_task(async function() {
let doc = gBrowser.selectedBrowser.contentDocument;
let showBtn = doc.getElementById("showUpdateHistory");
let dialogOverlay = doc.getElementById("dialogOverlay");
let dialogOverlay = content.gSubDialog._preloadDialog._overlay;
// XXX: For unknown reasons, this mock cannot be loaded by
// XPCOMUtils.defineLazyServiceGetter() called in aboutDialog-appUpdater.js.
@ -125,11 +125,12 @@ add_task(async function() {
// Test the dialog window opens
is(dialogOverlay.style.visibility, "", "The dialog should be invisible");
let promiseSubDialogLoaded = promiseLoadSubDialog("chrome://mozapps/content/update/history.xul");
showBtn.doCommand();
await promiseLoadSubDialog("chrome://mozapps/content/update/history.xul");
await promiseSubDialogLoaded;
is(dialogOverlay.style.visibility, "visible", "The dialog should be visible");
let dialogFrame = doc.getElementById("dialogFrame");
let dialogFrame = dialogOverlay.querySelector(".dialogFrame");
let frameDoc = dialogFrame.contentDocument;
let updates = frameDoc.querySelectorAll("update");
@ -151,7 +152,7 @@ add_task(async function() {
}
// Test the dialog window closes
let closeBtn = doc.getElementById("dialogClose");
let closeBtn = dialogOverlay.querySelector(".dialogClose");
closeBtn.doCommand();
is(dialogOverlay.style.visibility, "", "The dialog should be invisible");

View File

@ -20,8 +20,9 @@ add_task(async function() {
let fontSizeField = doc.getElementById("defaultFontSize");
is(fontSizeField.value, defaultFontSize, "Font size should be set correctly.");
let promiseSubDialogLoaded = promiseLoadSubDialog("chrome://browser/content/preferences/fonts.xul");
doc.getElementById("advancedFonts").click();
let win = await promiseLoadSubDialog("chrome://browser/content/preferences/fonts.xul");
let win = await promiseSubDialogLoaded;
doc = win.document;
// Simulate a dumb font backend.

View File

@ -339,10 +339,12 @@ var testRunner = {
let historyMode = doc.getElementById("historyMode");
historyMode.value = "custom";
historyMode.doCommand();
let promiseSubDialogLoaded =
promiseLoadSubDialog("chrome://browser/content/preferences/permissions.xul");
doc.getElementById("cookieExceptions").doCommand();
let subDialogURL = "chrome://browser/content/preferences/permissions.xul";
promiseLoadSubDialog(subDialogURL).then(function(win) {
promiseSubDialogLoaded.then(function(win) {
helperFunctions.windowLoad(win);
});
});

View File

@ -111,8 +111,8 @@ add_task(async function() {
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
await updatedPromise;
await openSiteDataSettingsDialog();
let doc = gBrowser.selectedBrowser.contentDocument;
let dialogFrame = doc.getElementById("dialogFrame");
let dialog = content.gSubDialog._topDialog;
let dialogFrame = dialog._frame;
let frameDoc = dialogFrame.contentDocument;
let siteItems = frameDoc.getElementsByTagName("richlistitem");
@ -280,8 +280,8 @@ add_task(async function() {
await updatePromise;
await openSiteDataSettingsDialog();
let doc = gBrowser.selectedBrowser.contentDocument;
let dialogFrame = doc.getElementById("dialogFrame");
let dialog = content.gSubDialog._topDialog;
let dialogFrame = dialog._frame;
let frameDoc = dialogFrame.contentDocument;
let hostCol = frameDoc.getElementById("hostCol");
let usageCol = frameDoc.getElementById("usageCol");
@ -406,7 +406,7 @@ add_task(async function() {
await openSiteDataSettingsDialog();
let doc = gBrowser.selectedBrowser.contentDocument;
let frameDoc = doc.getElementById("dialogFrame").contentDocument;
let frameDoc = content.gSubDialog._topDialog._frame.contentDocument;
let searchBox = frameDoc.getElementById("searchBox");
searchBox.value = "xyz";

View File

@ -6,11 +6,11 @@ const { DownloadUtils } = Cu.import("resource://gre/modules/DownloadUtils.jsm",
function promiseSettingsDialogClose() {
return new Promise(resolve => {
let doc = gBrowser.selectedBrowser.contentDocument;
let dialogOverlay = doc.getElementById("dialogOverlay");
let win = content.gSubDialog._frame.contentWindow;
win.addEventListener("unload", function unload() {
if (win.document.documentURI === "chrome://browser/content/preferences/siteDataSettings.xul") {
let win = gBrowser.selectedBrowser.contentWindow;
let dialogOverlay = win.gSubDialog._topDialog._overlay;
let dialogWin = win.gSubDialog._topDialog._frame.contentWindow;
dialogWin.addEventListener("unload", function unload() {
if (dialogWin.document.documentURI === "chrome://browser/content/preferences/siteDataSettings.xul") {
isnot(dialogOverlay.style.visibility, "visible", "The Settings dialog should be hidden");
resolve();
}
@ -55,6 +55,7 @@ add_task(async function() {
await updatePromise;
await openSiteDataSettingsDialog();
let win = gBrowser.selectedBrowser.contentWindow;
let doc = gBrowser.selectedBrowser.contentDocument;
let frameDoc = null;
let saveBtn = null;
@ -66,7 +67,7 @@ add_task(async function() {
// Test the "Cancel" button
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
cancelBtn = frameDoc.getElementById("cancel");
removeAllSitesOneByOne();
assertAllSitesNotListed();
@ -78,7 +79,7 @@ add_task(async function() {
// Test the "Save Changes" button but cancelling save
let cancelPromise = promiseAlertDialogOpen("cancel");
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
saveBtn = frameDoc.getElementById("save");
removeAllSitesOneByOne();
assertAllSitesNotListed();
@ -92,7 +93,7 @@ add_task(async function() {
let acceptPromise = promiseAlertDialogOpen("accept");
settingsDialogClosePromise = promiseSettingsDialogClose();
updatePromise = promiseSiteDataManagerSitesUpdated();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
saveBtn = frameDoc.getElementById("save");
removeAllSitesOneByOne();
assertAllSitesNotListed();
@ -107,7 +108,7 @@ add_task(async function() {
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
function removeAllSitesOneByOne() {
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
let removeBtn = frameDoc.getElementById("removeSelected");
let sitesList = frameDoc.getElementById("sitesList");
let sites = sitesList.getElementsByTagName("richlistitem");
@ -118,7 +119,7 @@ add_task(async function() {
}
function assertAllSitesNotListed() {
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
let removeBtn = frameDoc.getElementById("removeSelected");
let removeAllBtn = frameDoc.getElementById("removeAll");
let sitesList = frameDoc.getElementById("sitesList");
@ -184,6 +185,7 @@ add_task(async function() {
await updatePromise;
await openSiteDataSettingsDialog();
let win = gBrowser.selectedBrowser.contentWindow;
let doc = gBrowser.selectedBrowser.contentDocument;
let frameDoc = null;
let saveBtn = null;
@ -196,7 +198,7 @@ add_task(async function() {
// Test the "Cancel" button
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
cancelBtn = frameDoc.getElementById("cancel");
removeSelectedSite(fakeHosts.slice(0, 2));
assertSitesListed(doc, fakeHosts.slice(2));
@ -208,7 +210,7 @@ add_task(async function() {
// Test the "Save Changes" button but canceling save
removeDialogOpenPromise = promiseWindowDialogOpen("cancel", REMOVE_DIALOG_URL);
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
saveBtn = frameDoc.getElementById("save");
removeSelectedSite(fakeHosts.slice(0, 2));
assertSitesListed(doc, fakeHosts.slice(2));
@ -221,7 +223,7 @@ add_task(async function() {
// Test the "Save Changes" button and accepting save
removeDialogOpenPromise = promiseWindowDialogOpen("accept", REMOVE_DIALOG_URL);
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
saveBtn = frameDoc.getElementById("save");
removeSelectedSite(fakeHosts.slice(0, 2));
assertSitesListed(doc, fakeHosts.slice(2));
@ -235,7 +237,7 @@ add_task(async function() {
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
function removeSelectedSite(hosts) {
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
let removeBtn = frameDoc.getElementById("removeSelected");
let sitesList = frameDoc.getElementById("sitesList");
hosts.forEach(host => {
@ -288,8 +290,9 @@ add_task(async function() {
await openSiteDataSettingsDialog();
// Search "foo" to only list foo.com sites
let win = gBrowser.selectedBrowser.contentWindow;
let doc = gBrowser.selectedBrowser.contentDocument;
let frameDoc = doc.getElementById("dialogFrame").contentDocument;
let frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
let searchBox = frameDoc.getElementById("searchBox");
searchBox.value = "xyz";
searchBox.doCommand();
@ -349,7 +352,8 @@ add_task(async function() {
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
await updatedPromise;
await openSiteDataSettingsDialog();
let dialogFrame = gBrowser.selectedBrowser.contentDocument.getElementById("dialogFrame");
let win = gBrowser.selectedBrowser.contentWindow;
let dialogFrame = win.gSubDialog._topDialog._frame;
let frameDoc = dialogFrame.contentDocument;
let siteItems = frameDoc.getElementsByTagName("richlistitem");

View File

@ -17,8 +17,8 @@ function open_subdialog_and_test_generic_start_state(browser, domcontentloadedFn
return ContentTask.spawn(browser, {url, domcontentloadedFnStr}, async function(args) {
let rv = { acceptCount: 0 };
let win = content.window;
let subdialog = win.gSubDialog;
subdialog.open(args.url, null, rv);
content.gSubDialog.open(args.url, null, rv);
let subdialog = content.gSubDialog._topDialog;
info("waiting for subdialog DOMFrameContentLoaded");
await ContentTaskUtils.waitForEvent(win, "DOMFrameContentLoaded", true);
@ -29,7 +29,7 @@ function open_subdialog_and_test_generic_start_state(browser, domcontentloadedFn
}
info("waiting for subdialog load");
await ContentTaskUtils.waitForEvent(subdialog._frame, "load");
await ContentTaskUtils.waitForEvent(subdialog._overlay, "dialogopen");
info("subdialog window is loaded");
let expectedStyleSheetURLs = subdialog._injectedStyleSheets.slice(0);
@ -52,9 +52,17 @@ function open_subdialog_and_test_generic_start_state(browser, domcontentloadedFn
}
async function close_subdialog_and_test_generic_end_state(browser, closingFn, closingButton, acceptCount, options) {
let getDialogsCount = () => {
return ContentTask.spawn(browser, null, () =>
content.window.gSubDialog._dialogs.length);
};
let getStackChildrenCount = () => {
return ContentTask.spawn(browser, null, () =>
content.window.gSubDialog._dialogStack.children.length);
};
let dialogclosingPromise = ContentTask.spawn(browser, {closingButton, acceptCount}, async function(expectations) {
let win = content.window;
let subdialog = win.gSubDialog;
let subdialog = win.gSubDialog._topDialog;
let frame = subdialog._frame;
info("waiting for dialogclosing");
let closingEvent =
@ -76,7 +84,8 @@ async function close_subdialog_and_test_generic_end_state(browser, closingFn, cl
Assert.equal(actualAcceptCount, expectations.acceptCount,
"should be 1 if accepted, 0 if canceled, undefined if closed w/out button");
});
let initialDialogsCount = await getDialogsCount();
let initialStackChildrenCount = await getStackChildrenCount();
if (options && options.runClosingFnOutsideOfContentTask) {
await closingFn();
} else {
@ -84,6 +93,12 @@ async function close_subdialog_and_test_generic_end_state(browser, closingFn, cl
}
await dialogclosingPromise;
let endDialogsCount = await getDialogsCount();
let endStackChildrenCount = await getStackChildrenCount();
Assert.equal(initialDialogsCount - 1, endDialogsCount,
"dialog count should decrease by 1");
Assert.equal(initialStackChildrenCount - 1, endStackChildrenCount,
"stack children count should decrease by 1");
}
let tab;
@ -97,27 +112,28 @@ add_task(async function check_titlebar_focus_returnval_titlechanges_accepting()
let domtitlechangedPromise = BrowserTestUtils.waitForEvent(tab.linkedBrowser, "DOMTitleChanged");
await ContentTask.spawn(tab.linkedBrowser, null, async function() {
let dialog = content.window.gSubDialog._frame.contentWindow;
let dialogTitleElement = content.document.getElementById("dialogTitle");
let dialog = content.window.gSubDialog._topDialog;
let dialogWin = dialog._frame.contentWindow;
let dialogTitleElement = dialog._titleElement;
Assert.equal(dialogTitleElement.textContent, "Sample sub-dialog",
"Title should be correct initially");
Assert.equal(dialog.document.activeElement.value, "Default text",
Assert.equal(dialogWin.document.activeElement.value, "Default text",
"Textbox with correct text is focused");
dialog.document.title = "Updated title";
dialogWin.document.title = "Updated title";
});
info("waiting for DOMTitleChanged event");
await domtitlechangedPromise;
ContentTask.spawn(tab.linkedBrowser, null, async function() {
let dialogTitleElement = content.document.getElementById("dialogTitle");
let dialogTitleElement = content.window.gSubDialog._topDialog._titleElement;
Assert.equal(dialogTitleElement.textContent, "Updated title",
"subdialog should have updated title");
});
// Accept the dialog
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.acceptDialog(); },
"accept", 1);
});
@ -126,7 +142,7 @@ add_task(async function check_canceling_dialog() {
info("canceling the dialog");
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentDocument.documentElement.cancelDialog(); },
function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.cancelDialog(); },
"cancel", 0);
});
@ -134,20 +150,40 @@ add_task(async function check_reopening_dialog() {
await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
info("opening another dialog which will close the first");
await open_subdialog_and_test_generic_start_state(tab.linkedBrowser, "", gDialogURL2);
info("closing as normal");
ContentTask.spawn(tab.linkedBrowser, null, async function() {
let win = content.window;
let dialogs = win.gSubDialog._dialogs;
let lowerDialog = dialogs[0];
let topDialog = dialogs[1];
Assert.equal(dialogs.length, 2, "There should be two visible dialogs");
Assert.equal(win.getComputedStyle(topDialog._overlay).visibility, "visible",
"The top dialog should be visible");
Assert.equal(win.getComputedStyle(lowerDialog._overlay).visibility, "visible",
"The lower dialog should be visible");
Assert.equal(win.getComputedStyle(topDialog._overlay).backgroundColor, "rgba(0, 0, 0, 0.5)",
"The top dialog should have a semi-transparent overlay");
Assert.equal(win.getComputedStyle(lowerDialog._overlay).backgroundColor, "rgba(0, 0, 0, 0)",
"The lower dialog should not have an overlay");
});
info("closing two dialogs");
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.acceptDialog(); },
"accept", 1);
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.acceptDialog(); },
"accept", 1);
});
add_task(async function check_opening_while_closing() {
await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
info("closing");
content.window.gSubDialog.close();
content.window.gSubDialog._topDialog.close();
info("reopening immediately after calling .close()");
await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.acceptDialog(); },
"accept", 1);
});
@ -157,7 +193,7 @@ add_task(async function window_close_on_dialog() {
info("canceling the dialog");
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
null, 0);
});
@ -166,7 +202,7 @@ add_task(async function click_close_button_on_dialog() {
info("canceling the dialog");
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { return BrowserTestUtils.synthesizeMouseAtCenter("#dialogClose", {}, tab.linkedBrowser); },
function() { return BrowserTestUtils.synthesizeMouseAtCenter(".dialogClose", {}, tab.linkedBrowser); },
null, 0, {runClosingFnOutsideOfContentTask: true});
});
@ -176,7 +212,7 @@ add_task(async function background_click_should_close_dialog() {
// Clicking on an inactive part of dialog itself should not close the dialog.
// Click the dialog title bar here to make sure nothing happens.
info("clicking the dialog title bar");
BrowserTestUtils.synthesizeMouseAtCenter("#dialogTitle", {}, tab.linkedBrowser);
BrowserTestUtils.synthesizeMouseAtCenter(".dialogTitle", {}, tab.linkedBrowser);
// Close the dialog by clicking on the overlay background. Simulate a click
// at point (2,2) instead of (0,0) so we are sure we're clicking on the
@ -193,7 +229,7 @@ add_task(async function back_navigation_on_subdialog_should_close_dialog() {
info("canceling the dialog");
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.goBack(); },
function() { content.window.gSubDialog._topDialog._frame.goBack(); },
null, undefined);
});
@ -219,7 +255,7 @@ add_task(async function correct_width_and_height_should_be_used_for_dialog() {
await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
await ContentTask.spawn(tab.linkedBrowser, null, async function() {
let frameStyle = content.window.gSubDialog._frame.style;
let frameStyle = content.window.gSubDialog._topDialog._frame.style;
Assert.equal(frameStyle.width, "32em",
"Width should be set on the frame from the dialog");
Assert.equal(frameStyle.height, "5em",
@ -227,13 +263,13 @@ add_task(async function correct_width_and_height_should_be_used_for_dialog() {
});
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
null, 0);
});
add_task(async function wrapped_text_in_dialog_should_have_expected_scrollHeight() {
let oldHeight = await open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
let frame = content.window.gSubDialog._frame;
let frame = content.window.gSubDialog._topDialog._frame;
let doc = frame.contentDocument;
let scrollHeight = doc.documentElement.scrollHeight;
doc.documentElement.style.removeProperty("height");
@ -253,7 +289,7 @@ add_task(async function wrapped_text_in_dialog_should_have_expected_scrollHeight
});
await ContentTask.spawn(tab.linkedBrowser, oldHeight, async function(contentOldHeight) {
let frame = content.window.gSubDialog._frame;
let frame = content.window.gSubDialog._topDialog._frame;
let docEl = frame.contentDocument.documentElement;
Assert.equal(frame.style.width, "32em",
"Width should be set on the frame from the dialog");
@ -264,37 +300,37 @@ add_task(async function wrapped_text_in_dialog_should_have_expected_scrollHeight
});
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
null, 0);
});
add_task(async function dialog_too_tall_should_get_reduced_in_height() {
await open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
let frame = content.window.gSubDialog._frame;
let frame = content.window.gSubDialog._topDialog._frame;
frame.contentDocument.documentElement.style.height = "100000px";
});
await ContentTask.spawn(tab.linkedBrowser, null, async function() {
let frame = content.window.gSubDialog._frame;
let frame = content.window.gSubDialog._topDialog._frame;
Assert.equal(frame.style.width, "32em", "Width should be set on the frame from the dialog");
Assert.ok(parseInt(frame.style.height, 10) < content.window.innerHeight,
"Height on the frame should be smaller than window's innerHeight");
});
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
null, 0);
});
add_task(async function scrollWidth_and_scrollHeight_from_subdialog_should_size_the_browser() {
await open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
let frame = content.window.gSubDialog._frame;
let frame = content.window.gSubDialog._topDialog._frame;
frame.contentDocument.documentElement.style.removeProperty("height");
frame.contentDocument.documentElement.style.removeProperty("width");
});
await ContentTask.spawn(tab.linkedBrowser, null, async function() {
let frame = content.window.gSubDialog._frame;
let frame = content.window.gSubDialog._topDialog._frame;
Assert.ok(frame.style.width.endsWith("px"),
"Width (" + frame.style.width + ") should be set to a px value of the scrollWidth from the dialog");
Assert.ok(frame.style.height.endsWith("px"),
@ -302,7 +338,7 @@ add_task(async function scrollWidth_and_scrollHeight_from_subdialog_should_size_
});
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
null, 0);
});

View File

@ -45,20 +45,20 @@ function openAndLoadSubDialog(aURL, aFeatures = null, aParams = null, aClosingCa
function promiseLoadSubDialog(aURL) {
return new Promise((resolve, reject) => {
content.gSubDialog._frame.addEventListener("load", function load(aEvent) {
if (aEvent.target.contentWindow.location == "about:blank")
content.gSubDialog._dialogStack.addEventListener("dialogopen", function dialogopen(aEvent) {
if (aEvent.detail.dialog._frame.contentWindow.location == "about:blank")
return;
content.gSubDialog._frame.removeEventListener("load", load);
content.gSubDialog._dialogStack.removeEventListener("dialogopen", dialogopen);
is(content.gSubDialog._frame.contentWindow.location.toString(), aURL,
is(aEvent.detail.dialog._frame.contentWindow.location.toString(), aURL,
"Check the proper URL is loaded");
// Check visibility
is_element_visible(content.gSubDialog._overlay, "Overlay is visible");
is_element_visible(aEvent.detail.dialog._overlay, "Overlay is visible");
// Check that stylesheets were injected
let expectedStyleSheetURLs = content.gSubDialog._injectedStyleSheets.slice(0);
for (let styleSheet of content.gSubDialog._frame.contentDocument.styleSheets) {
let expectedStyleSheetURLs = aEvent.detail.dialog._injectedStyleSheets.slice(0);
for (let styleSheet of aEvent.detail.dialog._frame.contentDocument.styleSheets) {
let i = expectedStyleSheetURLs.indexOf(styleSheet.href);
if (i >= 0) {
info("found " + styleSheet.href);
@ -67,7 +67,7 @@ function promiseLoadSubDialog(aURL) {
}
is(expectedStyleSheetURLs.length, 0, "All expectedStyleSheetURLs should have been found");
resolve(content.gSubDialog._frame.contentWindow);
resolve(aEvent.detail.dialog._frame.contentWindow);
});
});
}
@ -193,7 +193,7 @@ function promiseSiteDataManagerSitesUpdated() {
function openSiteDataSettingsDialog() {
let doc = gBrowser.selectedBrowser.contentDocument;
let settingsBtn = doc.getElementById("siteDataSettings");
let dialogOverlay = doc.getElementById("dialogOverlay");
let dialogOverlay = content.gSubDialog._preloadDialog._overlay;
let dialogLoadPromise = promiseLoadSubDialog("chrome://browser/content/preferences/siteDataSettings.xul");
let dialogInitPromise = TestUtils.topicObserved("sitedata-settings-init", () => true);
let fullyLoadPromise = Promise.all([ dialogLoadPromise, dialogInitPromise ]).then(() => {
@ -204,7 +204,7 @@ function openSiteDataSettingsDialog() {
}
function assertSitesListed(doc, hosts) {
let frameDoc = doc.getElementById("dialogFrame").contentDocument;
let frameDoc = content.gSubDialog._topDialog._frame.contentDocument;
let removeBtn = frameDoc.getElementById("removeSelected");
let removeAllBtn = frameDoc.getElementById("removeAll");
let sitesList = frameDoc.getElementById("sitesList");

View File

@ -202,23 +202,22 @@
</hbox>
<vbox id="dialogOverlay" align="center" pack="center">
<groupbox id="dialogBox"
orient="vertical"
pack="end"
role="dialog"
aria-labelledby="dialogTitle">
<caption flex="1" align="center">
<label id="dialogTitle" flex="1"></label>
<button id="dialogClose"
class="close-icon"
aria-label="&preferencesCloseButton.label;"/>
</caption>
<browser id="dialogFrame"
name="dialogFrame"
autoscroll="false"
disablehistory="true"/>
</groupbox>
</vbox>
<stack id="dialogStack" hidden="true"/>
<vbox id="dialogTemplate" class="dialogOverlay" align="center" pack="center" topmost="true" hidden="true">
<groupbox class="dialogBox"
orient="vertical"
pack="end"
role="dialog"
aria-labelledby="dialogTitle">
<caption flex="1" align="center">
<label class="dialogTitle" flex="1"></label>
<button class="dialogClose close-icon"
aria-label="&preferencesCloseButton.label;"/>
</caption>
<browser class="dialogFrame"
autoscroll="false"
disablehistory="true"/>
</groupbox>
</vbox>
</stack>
</page>

View File

@ -7,11 +7,38 @@
"use strict";
var gSubDialog = {
/**
* SubDialog constructor creates a new subdialog from a template and appends
* it to the parentElement.
* @param {DOMNode} template: The template is copied to create a new dialog.
* @param {DOMNode} parentElement: New dialog is appended onto parentElement.
* @param {String} id: A unique identifier for the dialog.
*/
function SubDialog({template, parentElement, id}) {
this._id = id;
this._overlay = template.cloneNode(true);
this._frame = this._overlay.querySelector(".dialogFrame");
this._box = this._overlay.querySelector(".dialogBox");
this._closeButton = this._overlay.querySelector(".dialogClose");
this._titleElement = this._overlay.querySelector(".dialogTitle");
this._overlay.id = `dialogOverlay-${id}`;
this._frame.setAttribute("name", `dialogFrame-${id}`);
this._frameCreated = new Promise(resolve => {
this._frame.addEventListener("load", resolve, {once: true});
});
parentElement.appendChild(this._overlay);
this._overlay.hidden = false;
}
SubDialog.prototype = {
_closingCallback: null,
_closingEvent: null,
_isClosing: false,
_frame: null,
_frameCreated: null,
_overlay: null,
_box: null,
_openedURL: null,
@ -22,18 +49,15 @@ var gSubDialog = {
"chrome://browser/skin/preferences/in-content/dialog.css",
],
_resizeObserver: null,
init() {
this._frame = document.getElementById("dialogFrame");
this._overlay = document.getElementById("dialogOverlay");
this._box = document.getElementById("dialogBox");
this._closeButton = document.getElementById("dialogClose");
},
_template: null,
_id: null,
_titleElement: null,
_closeButton: null,
updateTitle(aEvent) {
if (aEvent.target != gSubDialog._frame.contentDocument)
if (aEvent.target != this._frame.contentDocument)
return;
document.getElementById("dialogTitle").textContent = gSubDialog._frame.contentDocument.title;
this._titleElement.textContent = this._frame.contentDocument.title;
},
injectXMLStylesheet(aStylesheetURL) {
@ -45,11 +69,9 @@ var gSubDialog = {
this._frame.contentDocument.documentElement);
},
open(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
// If we're already open/opening on this URL, do nothing.
if (this._openedURL == aURL && !this._isClosing) {
return;
}
async open(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
// Wait until frame is ready to prevent browser crash in tests
await this._frameCreated;
// If we're open on some (other) URL or we're closing, open when closing has finished.
if (this._openedURL || this._isClosing) {
if (!this._isClosing) {
@ -64,7 +86,7 @@ var gSubDialog = {
this._addDialogEventListeners();
let features = (aFeatures ? aFeatures + "," : "") + "resizable,dialog=no,centerscreen";
let dialog = window.openDialog(aURL, "dialogFrame", features, aParams);
let dialog = window.openDialog(aURL, `dialogFrame-${this._id}`, features, aParams);
if (aClosingCallback) {
this._closingCallback = aClosingCallback.bind(dialog);
}
@ -109,6 +131,11 @@ var gSubDialog = {
this._box.style.removeProperty("min-height");
this._box.style.removeProperty("min-width");
this._overlay.dispatchEvent(new CustomEvent("dialogclose", {
bubbles: true,
detail: { dialog: this },
}));
setTimeout(() => {
// Unload the dialog after the event listeners run so that the load of about:blank isn't
// cancelled by the ESC <key>.
@ -185,32 +212,32 @@ var gSubDialog = {
this._frame.contentWindow.addEventListener("dialogclosing", this);
let oldResizeBy = this._frame.contentWindow.resizeBy;
this._frame.contentWindow.resizeBy = function(resizeByWidth, resizeByHeight) {
this._frame.contentWindow.resizeBy = (resizeByWidth, resizeByHeight) => {
// Only handle resizeByHeight currently.
let frameHeight = gSubDialog._frame.clientHeight;
let boxMinHeight = parseFloat(getComputedStyle(gSubDialog._box).minHeight, 10);
let frameHeight = this._frame.clientHeight;
let boxMinHeight = parseFloat(getComputedStyle(this._box).minHeight, 10);
gSubDialog._frame.style.height = (frameHeight + resizeByHeight) + "px";
gSubDialog._box.style.minHeight = (boxMinHeight + resizeByHeight) + "px";
this._frame.style.height = (frameHeight + resizeByHeight) + "px";
this._box.style.minHeight = (boxMinHeight + resizeByHeight) + "px";
oldResizeBy.call(gSubDialog._frame.contentWindow, resizeByWidth, resizeByHeight);
oldResizeBy.call(this._frame.contentWindow, resizeByWidth, resizeByHeight);
};
// Make window.close calls work like dialog closing.
let oldClose = this._frame.contentWindow.close;
this._frame.contentWindow.close = function() {
var closingEvent = gSubDialog._closingEvent;
this._frame.contentWindow.close = () => {
var closingEvent = this._closingEvent;
if (!closingEvent) {
closingEvent = new CustomEvent("dialogclosing", {
bubbles: true,
detail: { button: null },
});
gSubDialog._frame.contentWindow.dispatchEvent(closingEvent);
this._frame.contentWindow.dispatchEvent(closingEvent);
}
gSubDialog.close(closingEvent);
oldClose.call(gSubDialog._frame.contentWindow);
this.close(closingEvent);
oldClose.call(this._frame.contentWindow);
};
// XXX: Hack to make focus during the dialog's load functions work. Make the element visible
@ -293,6 +320,10 @@ var gSubDialog = {
(boxVerticalBorder + groupBoxTitleHeight + boxVerticalPadding) +
"px + " + frameMinHeight + ")";
this._overlay.dispatchEvent(new CustomEvent("dialogopen", {
bubbles: true,
detail: { dialog: this },
}));
this._overlay.style.visibility = "visible";
this._overlay.style.opacity = ""; // XXX: focus hack continued from _onContentLoaded
@ -305,7 +336,7 @@ var gSubDialog = {
},
_onResize(mutations) {
let frame = gSubDialog._frame;
let frame = this._frame;
// The width and height styles are needed for the initial
// layout of the frame, but afterward they need to be removed
// or their presence will restrict the contents of the <browser>
@ -348,13 +379,13 @@ var gSubDialog = {
let fm = Services.focus;
function isLastFocusableElement(el) {
let isLastFocusableElement = el => {
// XXXgijs unfortunately there is no way to get the last focusable element without asking
// the focus manager to move focus to it.
let rv = el == fm.moveFocus(gSubDialog._frame.contentWindow, null, fm.MOVEFOCUS_LAST, 0);
let rv = el == fm.moveFocus(this._frame.contentWindow, null, fm.MOVEFOCUS_LAST, 0);
fm.setFocus(el, 0);
return rv;
}
};
let forward = !aEvent.shiftKey;
// check if focus is leaving the frame (incl. the close button):
@ -449,3 +480,104 @@ var gSubDialog = {
.chromeEventHandler;
},
};
var gSubDialog = {
/**
* New dialogs are stacked on top of the existing ones, and they are pushed
* to the end of the _dialogs array.
* @type {Array}
*/
_dialogs: [],
_dialogStack: null,
_dialogTemplate: null,
_nextDialogID: 0,
_preloadDialog: null,
get _topDialog() {
return this._dialogs.length > 0 ? this._dialogs[this._dialogs.length - 1] : undefined;
},
init() {
this._dialogStack = document.getElementById("dialogStack");
this._dialogTemplate = document.getElementById("dialogTemplate");
this._preloadDialog = new SubDialog({template: this._dialogTemplate,
parentElement: this._dialogStack,
id: this._nextDialogID++});
},
open(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
// If we're already open/opening on this URL, do nothing.
if (this._topDialog && this._topDialog._openedURL == aURL) {
return;
}
this._preloadDialog.open(aURL, aFeatures, aParams, aClosingCallback);
this._dialogs.push(this._preloadDialog);
this._preloadDialog = new SubDialog({template: this._dialogTemplate,
parentElement: this._dialogStack,
id: this._nextDialogID++});
if (this._dialogs.length == 1) {
this._dialogStack.hidden = false;
this._ensureStackEventListeners();
}
},
close() {
this._topDialog.close();
},
handleEvent(aEvent) {
switch (aEvent.type) {
case "dialogopen": {
this._onDialogOpen();
break;
}
case "dialogclose": {
this._onDialogClose(aEvent.detail.dialog);
break;
}
}
},
_onDialogOpen() {
let lowerDialog = this._dialogs.length > 1 ? this._dialogs[this._dialogs.length - 2] : undefined;
if (lowerDialog) {
lowerDialog._overlay.removeAttribute("topmost");
lowerDialog._removeDialogEventListeners();
}
},
_onDialogClose(dialog) {
let fm = Services.focus;
if (this._topDialog == dialog) {
// XXX: When a top-most dialog is closed, we reuse the closed dialog and
// remove the preloadDialog. This is a temporary solution before we
// rewrite all the test cases in Bug 1359023.
this._preloadDialog._overlay.remove();
this._preloadDialog = this._dialogs.pop();
} else {
dialog._overlay.remove();
this._dialogs.splice(this._dialogs.indexOf(dialog), 1);
}
if (this._topDialog) {
fm.moveFocus(this._topDialog._frame.contentWindow, null, fm.MOVEFOCUS_FIRST, fm.FLAG_BYKEY);
this._topDialog._overlay.setAttribute("topmost", true);
this._topDialog._addDialogEventListeners();
} else {
fm.moveFocus(window, null, fm.MOVEFOCUS_ROOT, fm.FLAG_BYKEY);
this._dialogStack.hidden = true;
this._removeStackEventListeners();
}
},
_ensureStackEventListeners() {
this._dialogStack.addEventListener("dialogopen", this);
this._dialogStack.addEventListener("dialogclose", this);
},
_removeStackEventListeners() {
this._dialogStack.removeEventListener("dialogopen", this);
this._dialogStack.removeEventListener("dialogclose", this);
},
};

View File

@ -127,9 +127,10 @@ const cacheUsageGetter = {
};
function openSettingsDialog() {
let win = gBrowser.selectedBrowser.contentWindow;
let doc = gBrowser.selectedBrowser.contentDocument;
let settingsBtn = doc.getElementById("siteDataSettings");
let dialogOverlay = doc.getElementById("dialogOverlay");
let dialogOverlay = win.gSubDialog._preloadDialog._overlay;
let dialogLoadPromise = promiseLoadSubDialog("chrome://browser/content/preferences/siteDataSettings.xul");
let dialogInitPromise = TestUtils.topicObserved("sitedata-settings-init", () => true);
let fullyLoadPromise = Promise.all([ dialogLoadPromise, dialogInitPromise ]).then(() => {
@ -141,11 +142,11 @@ function openSettingsDialog() {
function promiseSettingsDialogClose() {
return new Promise(resolve => {
let doc = gBrowser.selectedBrowser.contentDocument;
let dialogOverlay = doc.getElementById("dialogOverlay");
let win = content.gSubDialog._frame.contentWindow;
win.addEventListener("unload", function unload() {
if (win.document.documentURI === "chrome://browser/content/preferences/siteDataSettings.xul") {
let win = gBrowser.selectedBrowser.contentWindow;
let dialogOverlay = win.gSubDialog._topDialog._overlay;
let dialogWin = win.gSubDialog._topDialog._frame.contentWindow;
dialogWin.addEventListener("unload", function unload() {
if (dialogWin.document.documentURI === "chrome://browser/content/preferences/siteDataSettings.xul") {
isnot(dialogOverlay.style.visibility, "visible", "The Settings dialog should be hidden");
resolve();
}
@ -164,7 +165,8 @@ function promiseCookiesCleared() {
}
function assertSitesListed(doc, hosts) {
let frameDoc = doc.getElementById("dialogFrame").contentDocument;
let win = gBrowser.selectedBrowser.contentWindow;
let frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
let removeBtn = frameDoc.getElementById("removeSelected");
let removeAllBtn = frameDoc.getElementById("removeAll");
let sitesList = frameDoc.getElementById("sitesList");
@ -221,7 +223,7 @@ add_task(async function() {
await openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
await updatedPromise;
await openSettingsDialog();
let dialogFrame = gBrowser.selectedBrowser.contentDocument.getElementById("dialogFrame");
let dialogFrame = content.gSubDialog._topDialog._frame;
let frameDoc = dialogFrame.contentDocument;
let siteItems = frameDoc.getElementsByTagName("richlistitem");
@ -262,8 +264,7 @@ add_task(async function() {
await openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
await updatedPromise;
await openSettingsDialog();
let doc = gBrowser.selectedBrowser.contentDocument;
let dialogFrame = doc.getElementById("dialogFrame");
let dialogFrame = content.gSubDialog._topDialog._frame;
let frameDoc = dialogFrame.contentDocument;
let siteItems = frameDoc.getElementsByTagName("richlistitem");
@ -431,8 +432,9 @@ add_task(async function() {
await updatePromise;
await openSettingsDialog();
let doc = gBrowser.selectedBrowser.contentDocument;
let dialogFrame = doc.getElementById("dialogFrame");
let win = gBrowser.selectedBrowser.contentWindow;
let dialog = win.gSubDialog._topDialog;
let dialogFrame = dialog._frame;
let frameDoc = dialogFrame.contentDocument;
let hostCol = frameDoc.getElementById("hostCol");
let usageCol = frameDoc.getElementById("usageCol");
@ -556,8 +558,9 @@ add_task(async function() {
await updatePromise;
await openSettingsDialog();
let win = gBrowser.selectedBrowser.contentWindow;
let doc = gBrowser.selectedBrowser.contentDocument;
let frameDoc = doc.getElementById("dialogFrame").contentDocument;
let frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
let searchBox = frameDoc.getElementById("searchBox");
searchBox.value = "xyz";
@ -613,6 +616,7 @@ add_task(async function() {
await updatePromise;
await openSettingsDialog();
let win = gBrowser.selectedBrowser.contentWindow;
let doc = gBrowser.selectedBrowser.contentDocument;
let frameDoc = null;
let saveBtn = null;
@ -624,7 +628,7 @@ add_task(async function() {
// Test the "Cancel" button
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
cancelBtn = frameDoc.getElementById("cancel");
removeAllSitesOneByOne();
assertAllSitesNotListed();
@ -636,7 +640,7 @@ add_task(async function() {
// Test the "Save Changes" button but cancelling save
let cancelPromise = promiseAlertDialogOpen("cancel");
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
saveBtn = frameDoc.getElementById("save");
removeAllSitesOneByOne();
assertAllSitesNotListed();
@ -650,7 +654,7 @@ add_task(async function() {
let acceptPromise = promiseAlertDialogOpen("accept");
settingsDialogClosePromise = promiseSettingsDialogClose();
updatePromise = promiseSitesUpdated();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
saveBtn = frameDoc.getElementById("save");
removeAllSitesOneByOne();
assertAllSitesNotListed();
@ -665,7 +669,7 @@ add_task(async function() {
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
function removeAllSitesOneByOne() {
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
let removeBtn = frameDoc.getElementById("removeSelected");
let sitesList = frameDoc.getElementById("sitesList");
let sites = sitesList.getElementsByTagName("richlistitem");
@ -676,7 +680,7 @@ add_task(async function() {
}
function assertAllSitesNotListed() {
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
let removeBtn = frameDoc.getElementById("removeSelected");
let removeAllBtn = frameDoc.getElementById("removeAll");
let sitesList = frameDoc.getElementById("sitesList");
@ -742,6 +746,7 @@ add_task(async function() {
await updatePromise;
await openSettingsDialog();
let win = gBrowser.selectedBrowser.contentWindow;
let doc = gBrowser.selectedBrowser.contentDocument;
let frameDoc = null;
let saveBtn = null;
@ -754,7 +759,7 @@ add_task(async function() {
// Test the "Cancel" button
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
cancelBtn = frameDoc.getElementById("cancel");
removeSelectedSite(fakeHosts.slice(0, 2));
assertSitesListed(doc, fakeHosts.slice(2));
@ -766,7 +771,7 @@ add_task(async function() {
// Test the "Save Changes" button but canceling save
removeDialogOpenPromise = promiseWindowDialogOpen("cancel", REMOVE_DIALOG_URL);
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
saveBtn = frameDoc.getElementById("save");
removeSelectedSite(fakeHosts.slice(0, 2));
assertSitesListed(doc, fakeHosts.slice(2));
@ -779,7 +784,7 @@ add_task(async function() {
// Test the "Save Changes" button and accepting save
removeDialogOpenPromise = promiseWindowDialogOpen("accept", REMOVE_DIALOG_URL);
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
saveBtn = frameDoc.getElementById("save");
removeSelectedSite(fakeHosts.slice(0, 2));
assertSitesListed(doc, fakeHosts.slice(2));
@ -793,7 +798,7 @@ add_task(async function() {
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
function removeSelectedSite(hosts) {
frameDoc = doc.getElementById("dialogFrame").contentDocument;
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
let removeBtn = frameDoc.getElementById("removeSelected");
let sitesList = frameDoc.getElementById("sitesList");
hosts.forEach(host => {
@ -846,8 +851,9 @@ add_task(async function() {
await openSettingsDialog();
// Search "foo" to only list foo.com sites
let win = gBrowser.selectedBrowser.contentWindow;
let doc = gBrowser.selectedBrowser.contentDocument;
let frameDoc = doc.getElementById("dialogFrame").contentDocument;
let frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
let searchBox = frameDoc.getElementById("searchBox");
searchBox.value = "xyz";
searchBox.doCommand();

View File

@ -116,15 +116,16 @@ add_task(async function() {
let doc = gBrowser.selectedBrowser.contentDocument;
let showBtn = doc.getElementById("showUpdateHistory");
let dialogOverlay = doc.getElementById("dialogOverlay");
let dialogOverlay = content.gSubDialog._preloadDialog._overlay;
// Test the dialog window opens
is(dialogOverlay.style.visibility, "", "The dialog should be invisible");
let promiseSubDialogLoaded = promiseLoadSubDialog("chrome://mozapps/content/update/history.xul");
showBtn.doCommand();
await promiseLoadSubDialog("chrome://mozapps/content/update/history.xul");
await promiseSubDialogLoaded;
is(dialogOverlay.style.visibility, "visible", "The dialog should be visible");
let dialogFrame = doc.getElementById("dialogFrame");
let dialogFrame = dialogOverlay.querySelector(".dialogFrame");
let frameDoc = dialogFrame.contentDocument;
let updates = frameDoc.querySelectorAll("update");
@ -146,7 +147,7 @@ add_task(async function() {
}
// Test the dialog window closes
let closeBtn = doc.getElementById("dialogClose");
let closeBtn = dialogOverlay.querySelector(".dialogClose");
closeBtn.doCommand();
is(dialogOverlay.style.visibility, "", "The dialog should be invisible");

View File

@ -20,8 +20,9 @@ add_task(async function() {
let fontSizeField = doc.getElementById("defaultFontSize");
is(fontSizeField.value, defaultFontSize, "Font size should be set correctly.");
let promiseSubDialogLoaded = promiseLoadSubDialog("chrome://browser/content/preferences/fonts.xul");
doc.getElementById("advancedFonts").click();
let win = await promiseLoadSubDialog("chrome://browser/content/preferences/fonts.xul");
let win = await promiseSubDialogLoaded;
doc = win.document;
// Simulate a dumb font backend.

View File

@ -339,10 +339,12 @@ var testRunner = {
let historyMode = doc.getElementById("historyMode");
historyMode.value = "custom";
historyMode.doCommand();
let promiseSubDialogLoaded =
promiseLoadSubDialog("chrome://browser/content/preferences/permissions.xul");
doc.getElementById("cookieExceptions").doCommand();
let subDialogURL = "chrome://browser/content/preferences/permissions.xul";
promiseLoadSubDialog(subDialogURL).then(function(win) {
promiseSubDialogLoaded.then(function(win) {
helperFunctions.windowLoad(win);
});
});

View File

@ -17,8 +17,8 @@ function open_subdialog_and_test_generic_start_state(browser, domcontentloadedFn
return ContentTask.spawn(browser, {url, domcontentloadedFnStr}, async function(args) {
let rv = { acceptCount: 0 };
let win = content.window;
let subdialog = win.gSubDialog;
subdialog.open(args.url, null, rv);
content.gSubDialog.open(args.url, null, rv);
let subdialog = content.gSubDialog._topDialog;
info("waiting for subdialog DOMFrameContentLoaded");
await ContentTaskUtils.waitForEvent(win, "DOMFrameContentLoaded", true);
@ -29,7 +29,7 @@ function open_subdialog_and_test_generic_start_state(browser, domcontentloadedFn
}
info("waiting for subdialog load");
await ContentTaskUtils.waitForEvent(subdialog._frame, "load");
await ContentTaskUtils.waitForEvent(subdialog._overlay, "dialogopen");
info("subdialog window is loaded");
let expectedStyleSheetURLs = subdialog._injectedStyleSheets.slice(0);
@ -52,9 +52,17 @@ function open_subdialog_and_test_generic_start_state(browser, domcontentloadedFn
}
async function close_subdialog_and_test_generic_end_state(browser, closingFn, closingButton, acceptCount, options) {
let getDialogsCount = () => {
return ContentTask.spawn(browser, null, () =>
content.window.gSubDialog._dialogs.length);
};
let getStackChildrenCount = () => {
return ContentTask.spawn(browser, null, () =>
content.window.gSubDialog._dialogStack.children.length);
};
let dialogclosingPromise = ContentTask.spawn(browser, {closingButton, acceptCount}, async function(expectations) {
let win = content.window;
let subdialog = win.gSubDialog;
let subdialog = win.gSubDialog._topDialog;
let frame = subdialog._frame;
info("waiting for dialogclosing");
let closingEvent =
@ -76,7 +84,8 @@ async function close_subdialog_and_test_generic_end_state(browser, closingFn, cl
Assert.equal(actualAcceptCount, expectations.acceptCount,
"should be 1 if accepted, 0 if canceled, undefined if closed w/out button");
});
let initialDialogsCount = await getDialogsCount();
let initialStackChildrenCount = await getStackChildrenCount();
if (options && options.runClosingFnOutsideOfContentTask) {
await closingFn();
} else {
@ -84,6 +93,12 @@ async function close_subdialog_and_test_generic_end_state(browser, closingFn, cl
}
await dialogclosingPromise;
let endDialogsCount = await getDialogsCount();
let endStackChildrenCount = await getStackChildrenCount();
Assert.equal(initialDialogsCount - 1, endDialogsCount,
"dialog count should decrease by 1");
Assert.equal(initialStackChildrenCount - 1, endStackChildrenCount,
"stack children count should decrease by 1");
}
let tab;
@ -97,27 +112,28 @@ add_task(async function check_titlebar_focus_returnval_titlechanges_accepting()
let domtitlechangedPromise = BrowserTestUtils.waitForEvent(tab.linkedBrowser, "DOMTitleChanged");
await ContentTask.spawn(tab.linkedBrowser, null, async function() {
let dialog = content.window.gSubDialog._frame.contentWindow;
let dialogTitleElement = content.document.getElementById("dialogTitle");
let dialog = content.window.gSubDialog._topDialog;
let dialogWin = dialog._frame.contentWindow;
let dialogTitleElement = dialog._titleElement;
Assert.equal(dialogTitleElement.textContent, "Sample sub-dialog",
"Title should be correct initially");
Assert.equal(dialog.document.activeElement.value, "Default text",
Assert.equal(dialogWin.document.activeElement.value, "Default text",
"Textbox with correct text is focused");
dialog.document.title = "Updated title";
dialogWin.document.title = "Updated title";
});
info("waiting for DOMTitleChanged event");
await domtitlechangedPromise;
ContentTask.spawn(tab.linkedBrowser, null, async function() {
let dialogTitleElement = content.document.getElementById("dialogTitle");
let dialogTitleElement = content.window.gSubDialog._topDialog._titleElement;
Assert.equal(dialogTitleElement.textContent, "Updated title",
"subdialog should have updated title");
});
// Accept the dialog
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.acceptDialog(); },
"accept", 1);
});
@ -126,7 +142,7 @@ add_task(async function check_canceling_dialog() {
info("canceling the dialog");
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentDocument.documentElement.cancelDialog(); },
function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.cancelDialog(); },
"cancel", 0);
});
@ -134,20 +150,40 @@ add_task(async function check_reopening_dialog() {
await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
info("opening another dialog which will close the first");
await open_subdialog_and_test_generic_start_state(tab.linkedBrowser, "", gDialogURL2);
info("closing as normal");
ContentTask.spawn(tab.linkedBrowser, null, async function() {
let win = content.window;
let dialogs = win.gSubDialog._dialogs;
let lowerDialog = dialogs[0];
let topDialog = dialogs[1];
Assert.equal(dialogs.length, 2, "There should be two visible dialogs");
Assert.equal(win.getComputedStyle(topDialog._overlay).visibility, "visible",
"The top dialog should be visible");
Assert.equal(win.getComputedStyle(lowerDialog._overlay).visibility, "visible",
"The lower dialog should be visible");
Assert.equal(win.getComputedStyle(topDialog._overlay).backgroundColor, "rgba(0, 0, 0, 0.5)",
"The top dialog should have a semi-transparent overlay");
Assert.equal(win.getComputedStyle(lowerDialog._overlay).backgroundColor, "rgba(0, 0, 0, 0)",
"The lower dialog should not have an overlay");
});
info("closing two dialogs");
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.acceptDialog(); },
"accept", 1);
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.acceptDialog(); },
"accept", 1);
});
add_task(async function check_opening_while_closing() {
await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
info("closing");
content.window.gSubDialog.close();
content.window.gSubDialog._topDialog.close();
info("reopening immediately after calling .close()");
await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.acceptDialog(); },
"accept", 1);
});
@ -157,7 +193,7 @@ add_task(async function window_close_on_dialog() {
info("canceling the dialog");
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
null, 0);
});
@ -166,7 +202,7 @@ add_task(async function click_close_button_on_dialog() {
info("canceling the dialog");
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { return BrowserTestUtils.synthesizeMouseAtCenter("#dialogClose", {}, tab.linkedBrowser); },
function() { return BrowserTestUtils.synthesizeMouseAtCenter(".dialogClose", {}, tab.linkedBrowser); },
null, 0, {runClosingFnOutsideOfContentTask: true});
});
@ -176,7 +212,7 @@ add_task(async function background_click_should_close_dialog() {
// Clicking on an inactive part of dialog itself should not close the dialog.
// Click the dialog title bar here to make sure nothing happens.
info("clicking the dialog title bar");
BrowserTestUtils.synthesizeMouseAtCenter("#dialogTitle", {}, tab.linkedBrowser);
BrowserTestUtils.synthesizeMouseAtCenter(".dialogTitle", {}, tab.linkedBrowser);
// Close the dialog by clicking on the overlay background. Simulate a click
// at point (2,2) instead of (0,0) so we are sure we're clicking on the
@ -193,7 +229,7 @@ add_task(async function back_navigation_on_subdialog_should_close_dialog() {
info("canceling the dialog");
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.goBack(); },
function() { content.window.gSubDialog._topDialog._frame.goBack(); },
null, undefined);
});
@ -219,7 +255,7 @@ add_task(async function correct_width_and_height_should_be_used_for_dialog() {
await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
await ContentTask.spawn(tab.linkedBrowser, null, async function() {
let frameStyle = content.window.gSubDialog._frame.style;
let frameStyle = content.window.gSubDialog._topDialog._frame.style;
Assert.equal(frameStyle.width, "32em",
"Width should be set on the frame from the dialog");
Assert.equal(frameStyle.height, "5em",
@ -227,13 +263,13 @@ add_task(async function correct_width_and_height_should_be_used_for_dialog() {
});
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
null, 0);
});
add_task(async function wrapped_text_in_dialog_should_have_expected_scrollHeight() {
let oldHeight = await open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
let frame = content.window.gSubDialog._frame;
let frame = content.window.gSubDialog._topDialog._frame;
let doc = frame.contentDocument;
let scrollHeight = doc.documentElement.scrollHeight;
doc.documentElement.style.removeProperty("height");
@ -253,7 +289,7 @@ add_task(async function wrapped_text_in_dialog_should_have_expected_scrollHeight
});
await ContentTask.spawn(tab.linkedBrowser, oldHeight, async function(contentOldHeight) {
let frame = content.window.gSubDialog._frame;
let frame = content.window.gSubDialog._topDialog._frame;
let docEl = frame.contentDocument.documentElement;
Assert.equal(frame.style.width, "32em",
"Width should be set on the frame from the dialog");
@ -264,37 +300,37 @@ add_task(async function wrapped_text_in_dialog_should_have_expected_scrollHeight
});
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
null, 0);
});
add_task(async function dialog_too_tall_should_get_reduced_in_height() {
await open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
let frame = content.window.gSubDialog._frame;
let frame = content.window.gSubDialog._topDialog._frame;
frame.contentDocument.documentElement.style.height = "100000px";
});
await ContentTask.spawn(tab.linkedBrowser, null, async function() {
let frame = content.window.gSubDialog._frame;
let frame = content.window.gSubDialog._topDialog._frame;
Assert.equal(frame.style.width, "32em", "Width should be set on the frame from the dialog");
Assert.ok(parseInt(frame.style.height, 10) < content.window.innerHeight,
"Height on the frame should be smaller than window's innerHeight");
});
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
null, 0);
});
add_task(async function scrollWidth_and_scrollHeight_from_subdialog_should_size_the_browser() {
await open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
let frame = content.window.gSubDialog._frame;
let frame = content.window.gSubDialog._topDialog._frame;
frame.contentDocument.documentElement.style.removeProperty("height");
frame.contentDocument.documentElement.style.removeProperty("width");
});
await ContentTask.spawn(tab.linkedBrowser, null, async function() {
let frame = content.window.gSubDialog._frame;
let frame = content.window.gSubDialog._topDialog._frame;
Assert.ok(frame.style.width.endsWith("px"),
"Width (" + frame.style.width + ") should be set to a px value of the scrollWidth from the dialog");
Assert.ok(frame.style.height.endsWith("px"),
@ -302,7 +338,7 @@ add_task(async function scrollWidth_and_scrollHeight_from_subdialog_should_size_
});
await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
function() { content.window.gSubDialog._topDialog._frame.contentWindow.window.close(); },
null, 0);
});

View File

@ -54,20 +54,20 @@ function openAndLoadSubDialog(aURL, aFeatures = null, aParams = null, aClosingCa
function promiseLoadSubDialog(aURL) {
return new Promise((resolve, reject) => {
content.gSubDialog._frame.addEventListener("load", function load(aEvent) {
if (aEvent.target.contentWindow.location == "about:blank")
content.gSubDialog._dialogStack.addEventListener("dialogopen", function dialogopen(aEvent) {
if (aEvent.detail.dialog._frame.contentWindow.location == "about:blank")
return;
content.gSubDialog._frame.removeEventListener("load", load);
content.gSubDialog._dialogStack.removeEventListener("dialogopen", dialogopen);
is(content.gSubDialog._frame.contentWindow.location.toString(), aURL,
is(aEvent.detail.dialog._frame.contentWindow.location.toString(), aURL,
"Check the proper URL is loaded");
// Check visibility
is_element_visible(content.gSubDialog._overlay, "Overlay is visible");
is_element_visible(aEvent.detail.dialog._overlay, "Overlay is visible");
// Check that stylesheets were injected
let expectedStyleSheetURLs = content.gSubDialog._injectedStyleSheets.slice(0);
for (let styleSheet of content.gSubDialog._frame.contentDocument.styleSheets) {
let expectedStyleSheetURLs = aEvent.detail.dialog._injectedStyleSheets.slice(0);
for (let styleSheet of aEvent.detail.dialog._frame.contentDocument.styleSheets) {
let i = expectedStyleSheetURLs.indexOf(styleSheet.href);
if (i >= 0) {
info("found " + styleSheet.href);
@ -76,7 +76,7 @@ function promiseLoadSubDialog(aURL) {
}
is(expectedStyleSheetURLs.length, 0, "All expectedStyleSheetURLs should have been found");
resolve(content.gSubDialog._frame.contentWindow);
resolve(aEvent.detail.dialog._frame.contentWindow);
});
});
}

View File

@ -41,10 +41,20 @@ var FormAutofillFrameScript = {
switch (evt.type) {
case "focusin": {
let element = evt.target;
let doc = element.ownerDocument;
if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
return;
}
FormAutofillContent.identifyAutofillFields(element.ownerDocument);
let doIdentifyAutofillFields =
() => setTimeout(() => FormAutofillContent.identifyAutofillFields(doc));
if (doc.readyState === "loading") {
doc.addEventListener("DOMContentLoaded", doIdentifyAutofillFields, {once: true});
} else {
doIdentifyAutofillFields();
}
break;
}
}

View File

@ -10,6 +10,14 @@ function setInput(selector, value) {
let input = document.querySelector("input" + selector);
input.value = value;
input.focus();
// "identifyAutofillFields" is invoked asynchronously in "focusin" event. We
// should make sure fields are ready for popup before doing tests.
//
// TODO: "setTimeout" is used here temporarily because there's no event to
// notify us of the state of "identifyAutofillFields" for now. We should
// figure out a better way after the heuristics land.
return new Promise(resolve => setTimeout(resolve));
}
function checkMenuEntries(expectedValues) {

View File

@ -43,7 +43,6 @@ var ParentUtils = {
},
};
ParentUtils.cleanUpAddress();
Services.obs.addObserver(ParentUtils, "formautofill-storage-changed");
addMessageListener("FormAutofillTest:AddAddress", (msg) => {

View File

@ -5,5 +5,5 @@ support-files =
formautofill_common.js
formautofill_parent_utils.js
[test_autofocus_form.html]
[test_basic_autocomplete_form.html]

View File

@ -0,0 +1,86 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test basic autofill</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<script type="text/javascript" src="formautofill_common.js"></script>
<script type="text/javascript" src="satchel_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
Form autofill test: autocomplete on an autofocus form
<script>
/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/SpawnTask.js */
/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
/* import-globals-from formautofill_common.js */
"use strict";
let expectingPopup = null;
let MOCK_STORAGE = [{
organization: "Sesame Street",
"street-address": "123 Sesame Street.",
tel: "1-345-345-3456",
}, {
organization: "Mozilla",
"street-address": "331 E. Evelyn Avenue",
tel: "1-650-903-0800",
}];
function expectPopup() {
info("expecting a popup");
return new Promise(resolve => {
expectingPopup = resolve;
});
}
function popupShownListener() {
info("popup shown for test ");
if (expectingPopup) {
expectingPopup();
expectingPopup = null;
}
}
async function setupAddressStorage() {
await addAddress(MOCK_STORAGE[0]);
await addAddress(MOCK_STORAGE[1]);
}
add_task(async function check_autocomplete_on_autofocus_field() {
await setupAddressStorage();
doKey("down");
await expectPopup();
checkMenuEntries(MOCK_STORAGE.map(address =>
JSON.stringify({primary: address.organization, secondary: address["street-address"]})
));
});
registerPopupShownListener(popupShownListener);
</script>
<p id="display"></p>
<div id="content">
<form id="form1">
<p>This is a basic form.</p>
<p><label>organization: <input id="organization" name="organization" autocomplete="organization" type="text"></label></p>
<script>
// Focuses the input before DOMContentLoaded
document.getElementById("organization").focus();
</script>
<p><label>streetAddress: <input id="street-address" name="street-address" autocomplete="street-address" type="text"></label></p>
<p><label>tel: <input id="tel" name="tel" autocomplete="tel" type="text"></label></p>
<p><label>country: <input id="country" name="country" autocomplete="country" type="text"></label></p>
</form>
</div>
<pre id="test"></pre>
</body>
</html>

View File

@ -95,7 +95,7 @@ async function setupFormHistory() {
add_task(async function history_only_menu_checking() {
await setupFormHistory();
setInput("#tel", "");
await setInput("#tel", "");
doKey("down");
await expectPopup();
checkMenuEntries(["1-234-567-890"]);
@ -105,21 +105,21 @@ add_task(async function history_only_menu_checking() {
add_task(async function check_menu_when_both_existed() {
await setupAddressStorage();
setInput("#organization", "");
await setInput("#organization", "");
doKey("down");
await expectPopup();
checkMenuEntries(MOCK_STORAGE.map(address =>
JSON.stringify({primary: address.organization, secondary: address["street-address"]})
));
setInput("#street-address", "");
await setInput("#street-address", "");
doKey("down");
await expectPopup();
checkMenuEntries(MOCK_STORAGE.map(address =>
JSON.stringify({primary: address["street-address"], secondary: address.organization})
));
setInput("#tel", "");
await setInput("#tel", "");
doKey("down");
await expectPopup();
checkMenuEntries(MOCK_STORAGE.map(address =>
@ -129,7 +129,7 @@ add_task(async function check_menu_when_both_existed() {
// Display history search result if no matched data in addresses.
add_task(async function check_fallback_for_mismatched_field() {
setInput("#email", "");
await setInput("#email", "");
doKey("down");
await expectPopup();
checkMenuEntries(["foo@mozilla.com"]);
@ -137,7 +137,7 @@ add_task(async function check_fallback_for_mismatched_field() {
// Autofill the address from dropdown menu.
add_task(async function check_fields_after_form_autofill() {
setInput("#organization", "Moz");
await setInput("#organization", "Moz");
doKey("down");
await expectPopup();
checkMenuEntries(MOCK_STORAGE.map(address =>
@ -149,7 +149,7 @@ add_task(async function check_fields_after_form_autofill() {
// Fallback to history search after autofill address.
add_task(async function check_fallback_after_form_autofill() {
setInput("#tel", "");
await setInput("#tel", "");
doKey("down");
await expectPopup();
checkMenuEntries(["1-234-567-890"]);

View File

@ -12,7 +12,7 @@
</Description>
</em:targetApplication>
<em:type>2</em:type>
<em:version>8.1.0</em:version>
<em:version>9.0.0</em:version>
<em:bootstrap>true</em:bootstrap>
<em:homepageURL>https://pageshot.net/</em:homepageURL>
<em:multiprocessCompatible>true</em:multiprocessCompatible>

View File

@ -28,6 +28,10 @@ FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales
'webextension/_locales/ach/messages.json'
]
FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["ar"] += [
'webextension/_locales/ar/messages.json'
]
FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"]["_locales"]["az"] += [
'webextension/_locales/az/messages.json'
]

View File

@ -0,0 +1,123 @@
{
"addonDescription": {
"message": "خذ مقاطع و لقطات من الوب و احفظهم مؤقتًا أو دائمًا."
},
"addonAuthorsList": {
"message": "موزيلا <screenshots-feedback@mozilla.com>"
},
"contextMenuLabel": {
"message": "خذ لقطة شاشة"
},
"myShotsLink": {
"message": "لقطاتي"
},
"screenshotInstructions": {
"message": "اسحب أو انقر في الصفحة لاختيار منطقة. اضغط ESC للإلغاء."
},
"saveScreenshotSelectedArea": {
"message": "احفظ"
},
"saveScreenshotVisibleArea": {
"message": "احفظ الجزء المرئي"
},
"saveScreenshotFullPage": {
"message": "احفظ كل الصفحة"
},
"cancelScreenshot": {
"message": "ألغِ"
},
"downloadScreenshot": {
"message": "نزّل"
},
"notificationLinkCopiedTitle": {
"message": "نُسخ الرابط"
},
"notificationLinkCopiedDetails": {
"message": "نُسِخَ رابط اللقطة إلى الحافظة. اضغط $META_KEY$-V للصقها.",
"placeholders": {
"meta_key": {
"content": "$1"
}
}
},
"requestErrorTitle": {
"message": "خارج الخدمة."
},
"requestErrorDetails": {
"message": "تعذّر حفظ لقطتك. رجاء أعد المحاولة فيما بعد."
},
"connectionErrorTitle": {
"message": "تعذّر الاتصال بلقطات شاشتك."
},
"connectionErrorDetails": {
"message": "رجاء فحص اتصال الإنترنت. إذا كان باستطاعتك الاتصال بالإنترنت، فربما هناك عطل مؤقت في خدمة «لقطات شاشة فَيَرفُكس»."
},
"loginErrorDetails": {
"message": "تعذّر حفظ لقطتك لعُطل في خدمة «لقطات شاشة فَيَرفُكس». رجاء إعادة المحاولة لاحقًا."
},
"unshootablePageErrorTitle": {
"message": "تعذّر أخذ لقطة شاشة لهذه الصفحة."
},
"unshootablePageErrorDetails": {
"message": "ليست هذه صفحة وِب قياسية، لذا لا يمكنك أخذ لقطة لها."
},
"selfScreenshotErrorTitle": {
"message": "لا يمكننا أخذ لقطة لصفحة من صفحات «لقطات شاشة فَيَرفُكس»!"
},
"genericErrorTitle": {
"message": "هناك عطل في «لقطات شاشة فَيَرفُكس»."
},
"genericErrorDetails": {
"message": "لسنا متأكدين ما المشكلة. أتمانع إعادة المحاولة أو أخذ لقطة لصفحة أخرى؟"
},
"tourBodyOne": {
"message": "خذ لقطات الشاشة و احفظها و شارطها دون مغادرة فَيَرفُكس."
},
"tourHeaderTwo": {
"message": "التقط ما تريده فقط"
},
"tourBodyTwo": {
"message": "انقر و اسحب لالتقاط جزء معين من الصفحة. يمكنك أيضًا التحويم لإبراز التحديد."
},
"tourHeaderThree": {
"message": "كما تريدها"
},
"tourBodyThree": {
"message": "احفظ اللقطات التي أخذتها على الوب لمشاركتها بسهولة، أو نزّلها على حاسوبك. يمكنك أيضًل النقر على زر ”لقطاتي“ للعثور على كل اللقطات التي أخذتها."
},
"tourHeaderFour": {
"message": "التقط النوافذ أو صفحات كاملة"
},
"tourBodyFour": {
"message": "اختر الأزرار في أعلى اليمين لالتقاط المنطقة المرئية في النافذة أو الصفحة كلها."
},
"tourSkip": {
"message": "تخطَّ"
},
"tourNext": {
"message": "الشريحة التالية"
},
"tourPrevious": {
"message": "الشريحة السابقة"
},
"tourDone": {
"message": "تمّ"
},
"termsAndPrivacyNoticeCloudServices": {
"message": "استخدامك لخدمات «لقطات شاشة فَيَرفُكس» يعني موافقتك على $TERMSANDPRIVACYNOTICETERMSLINK$ و $TERMSANDPRIVACYNOTICEPRIVACYLINK$.",
"placeholders": {
"termsandprivacynoticetermslink": {
"content": "$1"
},
"termsandprivacynoticeprivacylink": {
"content": "$2"
}
}
},
"termsAndPrivacyNoticeTermsLink": {
"message": "الشروط"
},
"termsAndPrivacyNoticyPrivacyLink": {
"message": "تنويه الخصوصية"
}
}

View File

@ -64,6 +64,9 @@
"selfScreenshotErrorTitle": {
"message": "Sie können kein Bildschirmfoto einer Firefox-Screenshots-Seite machen!"
},
"emptySelectionErrorTitle": {
"message": "Ihr Auswahlbereich ist zu klein"
},
"genericErrorTitle": {
"message": "Firefox Screenshots funktioniert nicht richtig."
},

View File

@ -64,6 +64,9 @@
"selfScreenshotErrorTitle": {
"message": "Njamóžośo wobrazowku boka Firefox Screenshots fotografěrowaś!"
},
"emptySelectionErrorTitle": {
"message": "Waš wuběrk jo pśemały"
},
"genericErrorTitle": {
"message": "Hopla! Firefox Screenshots njeźěła."
},

View File

@ -64,6 +64,9 @@
"selfScreenshotErrorTitle": {
"message": "You cant take a shot of a Firefox Screenshots page!"
},
"emptySelectionErrorTitle": {
"message": "Your selection is too small"
},
"genericErrorTitle": {
"message": "Whoa! Firefox Screenshots went haywire."
},

View File

@ -64,6 +64,9 @@
"selfScreenshotErrorTitle": {
"message": "¡No puedes tomar una captura de la página de capturas de pantalla de Firefox!"
},
"emptySelectionErrorTitle": {
"message": "Tu selección es demasiado pequeña"
},
"genericErrorTitle": {
"message": "¡Oye! Las capturas de pantalla de Firefox salieron mal."
},

View File

@ -64,6 +64,9 @@
"selfScreenshotErrorTitle": {
"message": "Vous ne pouvez pas effectuer une capture décran dune page Firefox Screenshots."
},
"emptySelectionErrorTitle": {
"message": "La zone sélectionnée est trop petite"
},
"genericErrorTitle": {
"message": "Firefox Screenshots semble avoir un problème."
},

View File

@ -64,6 +64,9 @@
"selfScreenshotErrorTitle": {
"message": "Njemóžeće wobrazowku strony Firefox Screenshots fotografować!"
},
"emptySelectionErrorTitle": {
"message": "Waš wuběr je přemały"
},
"genericErrorTitle": {
"message": "Hopla! Firefox Screenshots njefunguje."
},

View File

@ -64,6 +64,9 @@
"selfScreenshotErrorTitle": {
"message": "Nem készíthet képet a Firefox képernyőképek oldalról!"
},
"emptySelectionErrorTitle": {
"message": "A kijelölés túl kicsi"
},
"genericErrorTitle": {
"message": "Húha! A Firefox képernyőképek megkergült."
},

View File

@ -64,6 +64,9 @@
"selfScreenshotErrorTitle": {
"message": "Non è possibile salvare uno screenshot di una pagina di Firefox Screenshots"
},
"emptySelectionErrorTitle": {
"message": "Larea selezionata è troppo piccola"
},
"genericErrorTitle": {
"message": "Wow! Firefox Screenshots è andato in tilt"
},

View File

@ -64,6 +64,9 @@
"selfScreenshotErrorTitle": {
"message": "Firefox Screenshots ページのショットは撮れません。"
},
"emptySelectionErrorTitle": {
"message": "選択範囲が小さすぎます"
},
"genericErrorTitle": {
"message": "Firefox Screenshots に問題が発生しました。"
},

View File

@ -12,7 +12,7 @@
"message": "Minhas capturas"
},
"screenshotInstructions": {
"message": "Arraste ou clique na página para selecionar uma área. Pressione ESC para cancelar."
"message": "Arraste ou clique na página para selecionar uma região. Pressione ESC para cancelar."
},
"saveScreenshotSelectedArea": {
"message": "Salvar"
@ -41,7 +41,7 @@
}
},
"requestErrorTitle": {
"message": "Fora de ordem."
"message": "Com defeito."
},
"requestErrorDetails": {
"message": "Desculpa! Não pudemos salvar a sua captura de tela. Por favor, tente novamente mais tarde."

View File

@ -64,6 +64,9 @@
"selfScreenshotErrorTitle": {
"message": "Nemôžete vytvoriť snímku obrazovky stránky Firefox Screenshots!"
},
"emptySelectionErrorTitle": {
"message": "Váš výber je príliš malý"
},
"genericErrorTitle": {
"message": "Ups! Služba Firefox Screenshots prestala pracovať."
},

View File

@ -64,6 +64,9 @@
"selfScreenshotErrorTitle": {
"message": "Posnetka strani Firefox Screenshots ni mogoče zajeti!"
},
"emptySelectionErrorTitle": {
"message": "Vaš izbor je premajhen"
},
"genericErrorTitle": {
"message": "Uf! Firefox Screenshots se je pokvaril."
},

View File

@ -6,7 +6,7 @@
"message": "Mozilla <screenshots-feedback@mozilla.com>"
},
"contextMenuLabel": {
"message": "Усликајте екран"
"message": "Усликај екран"
},
"myShotsLink": {
"message": "Моји снимци"
@ -64,6 +64,9 @@
"selfScreenshotErrorTitle": {
"message": "Не можете усликати Firefox Screenshots страницу!"
},
"emptySelectionErrorTitle": {
"message": "Ваша селекција је премала"
},
"genericErrorTitle": {
"message": "Ајој! Firefox Screenshots је пошашавио."
},

View File

@ -64,6 +64,9 @@
"selfScreenshotErrorTitle": {
"message": "Du kan inte ta en skärmbild av sidan Firefox Screenshots!"
},
"emptySelectionErrorTitle": {
"message": "Ditt val är för litet"
},
"genericErrorTitle": {
"message": "Oj! Firefox Screenshots verkar inte fungera korrekt."
},

View File

@ -11,6 +11,9 @@
"saveScreenshotSelectedArea": {
"message": "భద్రపరచు"
},
"saveScreenshotVisibleArea": {
"message": "కనిపించే దానిని బద్రపరచండి"
},
"saveScreenshotFullPage": {
"message": "పూర్తి పేజీని భద్రపరచు"
},
@ -23,9 +26,15 @@
"notificationLinkCopiedTitle": {
"message": "లంకె కాపీ అయింది"
},
"requestErrorTitle": {
"message": "పని చెయుట లేదు."
},
"requestErrorDetails": {
"message": "క్షమిచండి! మీ తెరను భద్రపరచలేకపోయాం. దయచేసి కాసేపాగి మళ్ళీ ప్రయత్నించండి."
},
"tourHeaderTwo": {
"message": ""
},
"tourHeaderThree": {
"message": "మీకు నచ్చినట్టుగా"
},

View File

@ -64,6 +64,9 @@
"selfScreenshotErrorTitle": {
"message": "Ви не можете зробити знімок сторінки Firefox Screenshots!"
},
"emptySelectionErrorTitle": {
"message": "Обрана область є замалою"
},
"genericErrorTitle": {
"message": "Оу! З Firefox Screenshots щось негаразд."
},

View File

@ -12,7 +12,7 @@ this.auth = (function() {
let sentryPublicDSN = null;
let abTests = {};
catcher.watchPromise(browser.storage.local.get(["registrationInfo", "abTests"]).then((result) => {
let registrationInfoFetched = catcher.watchPromise(browser.storage.local.get(["registrationInfo", "abTests"]).then((result) => {
if (result.abTests) {
abTests = result.abTests;
}
@ -177,33 +177,37 @@ this.auth = (function() {
};
exports.setDeviceInfoFromOldAddon = function(newDeviceInfo) {
if (!(newDeviceInfo.deviceId && newDeviceInfo.secret)) {
throw new Error("Bad deviceInfo");
}
if (registrationInfo.deviceId === newDeviceInfo.deviceId &&
registrationInfo.secret === newDeviceInfo.secret) {
// Probably we already imported the information
return Promise.resolve(false);
}
registrationInfo = {
deviceId: newDeviceInfo.deviceId,
secret: newDeviceInfo.secret,
registered: true
};
initialized = false;
return browser.storage.local.set({registrationInfo}).then(() => {
return true;
return registrationInfoFetched.then(() => {
if (!(newDeviceInfo.deviceId && newDeviceInfo.secret)) {
throw new Error("Bad deviceInfo");
}
if (registrationInfo.deviceId === newDeviceInfo.deviceId &&
registrationInfo.secret === newDeviceInfo.secret) {
// Probably we already imported the information
return Promise.resolve(false);
}
registrationInfo = {
deviceId: newDeviceInfo.deviceId,
secret: newDeviceInfo.secret,
registered: true
};
initialized = false;
return browser.storage.local.set({registrationInfo}).then(() => {
return true;
});
});
};
communication.register("getAuthInfo", (sender, ownershipCheck) => {
let info = registrationInfo;
if (info.registered) {
return login({ownershipCheck}).then((result) => {
return {isOwner: result && result.isOwner, deviceId: registrationInfo.deviceId};
});
}
return Promise.resolve(info);
return registrationInfoFetched.then(() => {
let info = registrationInfo;
if (info.registered) {
return login({ownershipCheck}).then((result) => {
return {isOwner: result && result.isOwner, deviceId: registrationInfo.deviceId};
});
}
return info;
});
});
return exports;

View File

@ -37,6 +37,9 @@ this.senderror = (function() {
MY_SHOTS: {
title: browser.i18n.getMessage("selfScreenshotErrorTitle")
},
EMPTY_SELECTION: {
title: browser.i18n.getMessage("emptySelectionErrorTitle")
},
generic: {
title: browser.i18n.getMessage("genericErrorTitle"),
info: browser.i18n.getMessage("genericErrorDetails"),
@ -89,7 +92,7 @@ this.senderror = (function() {
return;
}
if (!Raven.isSetup()) {
Raven.config(dsn).install();
Raven.config(dsn, {allowSecretKey: true}).install();
}
let exception = new Error(e.message);
exception.stack = e.multilineStack || e.stack || undefined;
@ -112,7 +115,9 @@ this.senderror = (function() {
if (!errorObj.noPopup) {
exports.showError(errorObj);
}
exports.reportError(errorObj);
if (!errorObj.noReport) {
exports.reportError(errorObj);
}
});
return exports;

View File

@ -1,6 +1,7 @@
window.buildSettings = {
defaultSentryDsn: "https://97d8afa496f94764ae255e739b147f4b@sentry.prod.mozaws.net/139",
logLevel: "" || "warn"
defaultSentryDsn: "https://904ccdd4866247c092ae8fc1a4764a63:940d44bdc71d4daea133c19080ccd38d@sentry.prod.mozaws.net/224",
logLevel: "" || "warn",
captureText: ("" === "true")
};
null;

View File

@ -421,6 +421,9 @@ window.inlineSelectionCss = `
position: absolute;
right: 5px;
top: 5px; }
html[dir="rtl"] .myshots-all-buttons-container {
left: 5px;
right: inherit; }
.myshots-all-buttons-container .spacer {
background-color: #c9c9c9;
flex: 0 0 1px;

View File

@ -493,6 +493,9 @@ class AbstractShot {
}
delete this._clips[name];
}
delAllClips() {
this._clips = {};
}
biggestClipSortOrder() {
let biggest = 0;
for (let clipId in this._clips) {

View File

@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Firefox Screenshots",
"version": "8.1.0",
"version": "9.0.0",
"description": "__MSG_addonDescription__",
"author": "__MSG_addonAuthorsList__",
"homepage_url": "https://github.com/mozilla-services/screenshots",
@ -16,7 +16,7 @@
"16": "icons/icon-16.svg",
"32": "icons/icon-32.svg"
},
"default_title": "__MSG_contextMenuLabel__",
"default_title": "Firefox Screenshots",
"browser_style": false
},
"background": {

View File

@ -46,6 +46,8 @@ this.slides = (function() {
doc.documentElement
);
doc.addEventListener("keyup", onKeyUp);
doc.documentElement.dir = browser.i18n.getMessage("@@bidi_dir");
doc.documentElement.lang = browser.i18n.getMessage("@@ui_locale");
localizeText(doc);
activateSlide(doc);
resolve();

View File

@ -1,5 +1,5 @@
/* globals global, documentMetadata, util, uicontrol, ui, catcher */
/* globals domainFromUrl, randomString */
/* globals buildSettings, domainFromUrl, randomString */
"use strict";
@ -67,18 +67,31 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars
// isSaving indicates we're aleady in the middle of saving
// we use a timeout so in the case of a failure the button will
// still start working again
if (Math.floor(selectedPos.left) == Math.floor(selectedPos.right) ||
Math.floor(selectedPos.top) == Math.floor(selectedPos.bottom)) {
let exc = new Error("Empty selection");
exc.popupMessage = "EMPTY_SELECTION";
exc.noReport = true;
catcher.unhandled(exc);
return;
}
const uicontrol = global.uicontrol;
let deactivateAfterFinish = true;
if (isSaving) {
return;
}
isSaving = setTimeout(() => {
ui.Box.clearSaveDisabled();
isSaving = null;
}, 1000);
selectedPos = selectedPos.asJson();
let captureText = util.captureEnclosedText(selectedPos);
let captureText = "";
if (buildSettings.captureText) {
captureText = util.captureEnclosedText(selectedPos);
}
let dataUrl = screenshotPage(selectedPos);
if (dataUrl) {
shot.delAllClips();
shot.addClip({
createdDate: Date.now(),
image: {

View File

@ -104,6 +104,8 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
if (this.addClassName) {
this.document.body.className = this.addClassName;
}
this.document.documentElement.dir = browser.i18n.getMessage("@@bidi_dir");
this.document.documentElement.lang = browser.i18n.getMessage("@@ui_locale");
resolve();
});
document.body.appendChild(this.element);
@ -246,6 +248,8 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
if (this.addClassName) {
this.document.body.className = this.addClassName;
}
this.document.documentElement.dir = browser.i18n.getMessage("@@bidi_dir");
this.document.documentElement.lang = browser.i18n.getMessage("@@ui_locale");
const overlay = this.document.querySelector(".preview-overlay");
overlay.querySelector(".preview-instructions").textContent = browser.i18n.getMessage("screenshotInstructions");
overlay.querySelector(".myshots-link").textContent = browser.i18n.getMessage("myShotsLink");
@ -537,6 +541,10 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
return false;
},
clearSaveDisabled() {
this.save.removeAttribute("disabled");
},
el: null,
boxTopEl: null,
boxLeftEl: null,

View File

@ -376,7 +376,7 @@ this.uicontrol = (function() {
ui.Box.remove();
const handler = watchFunction(assertIsTrusted(keyupHandler));
document.addEventListener("keyup", handler);
registeredDocumentHandlers.push({name: "keyup", doc: document, handler});
registeredDocumentHandlers.push({name: "keyup", doc: document, handler, useCapture: false});
}));
},
@ -874,15 +874,21 @@ this.uicontrol = (function() {
window.addEventListener('beforeunload', beforeunloadHandler);
}
let mousedownSetOnDocument = false;
function installHandlersOnDocument(docObj) {
for (let [eventName, handler] of primedDocumentHandlers) {
let watchHandler = watchFunction(handler);
docObj.addEventListener(eventName, watchHandler, eventName !== "keyup");
registeredDocumentHandlers.push({name: eventName, doc: docObj, watchHandler});
let useCapture = eventName !== "keyup";
docObj.addEventListener(eventName, watchHandler, useCapture);
registeredDocumentHandlers.push({name: eventName, doc: docObj, handler: watchHandler, useCapture});
}
if (!mousedownSetOnDocument) {
let mousedownHandler = primedDocumentHandlers.get("mousedown");
document.addEventListener("mousedown", mousedownHandler, true);
registeredDocumentHandlers.push({name: "mousedown", doc: document, handler: mousedownHandler, useCapture: true});
mousedownSetOnDocument = true;
}
let mousedownHandler = primedDocumentHandlers.get("mousedown");
document.addEventListener("mousedown", mousedownHandler, true);
registeredDocumentHandlers.push({name: "mousedown", doc: document, watchHandler: mousedownHandler, useCapture: true});
}
function beforeunloadHandler() {

View File

@ -483,6 +483,14 @@ toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-p
border: none;
}
/* On Mac, native buttons keep their full opacity when they become disabled
* and only the glyph or text on top of them becomes less opaque. */
#back-button[disabled="true"] > .toolbarbutton-icon {
opacity: 1 !important;
-moz-context-properties: fill, fill-opacity;
fill-opacity: 0.4;
}
/* Inactive elements are faded out on OSX */
.toolbarbutton-1:not(:hover):-moz-window-inactive,
#main-window:not([customizing]) .toolbarbutton-1:-moz-window-inactive[disabled="true"] {

View File

@ -2,5 +2,5 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="context-fill" d="M13 6H8.2l2-2a.967.967 0 0 0 0-1.4l-.8-.9a.967.967 0 0 0-1.4 0L2.4 7.3a.967.967 0 0 0 0 1.4L8 14.3a.967.967 0 0 0 1.4 0l.8-.8a.965.965 0 0 0 0-1.4l-2-2.1H13c.6 0 1-.2 1-.8V7a.945.945 0 0 0-1-1z"/>
<path fill-opacity="context-fill-opacity" fill="context-fill" d="M13 6H8.2l2-2a.967.967 0 0 0 0-1.4l-.8-.9a.967.967 0 0 0-1.4 0L2.4 7.3a.967.967 0 0 0 0 1.4L8 14.3a.967.967 0 0 0 1.4 0l.8-.8a.965.965 0 0 0 0-1.4l-2-2.1H13c.6 0 1-.2 1-.8V7a.945.945 0 0 0-1-1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 531 B

After

Width:  |  Height:  |  Size: 567 B

View File

@ -291,12 +291,15 @@ description > html|a {
* Dialog
*/
#dialogOverlay {
background-color: rgba(0,0,0,0.5);
.dialogOverlay {
visibility: hidden;
}
#dialogBox {
.dialogOverlay[topmost="true"] {
background-color: rgba(0,0,0,0.5);
}
.dialogBox {
background-color: #fbfbfb;
background-clip: content-box;
color: #424e5a;
@ -311,20 +314,20 @@ description > html|a {
padding: 0;
}
#dialogBox[resizable="true"] {
.dialogBox[resizable="true"] {
resize: both;
overflow: hidden;
min-height: 20em;
min-width: 66ch;
}
#dialogBox > .groupbox-title {
.dialogBox > .groupbox-title {
padding: 3.5px 0;
background-color: #F1F1F1;
border-bottom: 1px solid #C1C1C1;
}
#dialogTitle {
.dialogTitle {
text-align: center;
-moz-user-select: none;
}
@ -339,12 +342,12 @@ description > html|a {
min-width: 0;
}
#dialogBox > .groupbox-body {
.dialogBox > .groupbox-body {
-moz-appearance: none;
padding: 20px;
}
#dialogFrame {
.dialogFrame {
-moz-box-flex: 1;
/* Default dialog dimensions */
width: 66ch;

View File

@ -294,12 +294,15 @@ description > html|a {
* Dialog
*/
#dialogOverlay {
background-color: rgba(0,0,0,0.5);
.dialogOverlay {
visibility: hidden;
}
#dialogBox {
.dialogOverlay[topmost="true"] {
background-color: rgba(0,0,0,0.5);
}
.dialogBox {
background-color: #fbfbfb;
background-clip: content-box;
color: #424e5a;
@ -314,20 +317,20 @@ description > html|a {
padding: 0;
}
#dialogBox[resizable="true"] {
.dialogBox[resizable="true"] {
resize: both;
overflow: hidden;
min-height: 20em;
min-width: 66ch;
}
#dialogBox > .groupbox-title {
.dialogBox > .groupbox-title {
padding: 3.5px 0;
background-color: #F1F1F1;
border-bottom: 1px solid #C1C1C1;
}
#dialogTitle {
.dialogTitle {
text-align: center;
-moz-user-select: none;
}
@ -342,12 +345,12 @@ description > html|a {
min-width: 0;
}
#dialogBox > .groupbox-body {
.dialogBox > .groupbox-body {
-moz-appearance: none;
padding: 20px;
}
#dialogFrame {
.dialogFrame {
-moz-box-flex: 1;
/* Default dialog dimensions */
width: 66ch;

View File

@ -294,6 +294,7 @@ toolbarbutton.bookmark-item:not(.subviewbutton):hover:not([disabled="true"]):not
border-color: var(--toolbarbutton-hover-bordercolor);
box-shadow: var(--toolbarbutton-hover-boxshadow);
%endif
color: inherit;
}
.findbar-button:not([disabled=true]):-moz-any([checked="true"],:hover:active) > .toolbarbutton-text,
@ -310,6 +311,7 @@ toolbarbutton.bookmark-item[open="true"],
box-shadow: var(--toolbarbutton-active-boxshadow);
%endif
transition-duration: 10ms;
color: inherit;
}
#nav-bar .toolbarbutton-1[checked]:not(:active):hover > .toolbarbutton-icon {

View File

@ -7,7 +7,6 @@
"use strict";
const { Ci } = require("chrome");
const { Class } = require("sdk/core/heritage");
const Services = require("Services");
loader.lazyRequireGetter(this, "HarCollector", "devtools/client/netmonitor/har/har-collector", true);
@ -36,7 +35,11 @@ const trace = {
* If the default log directory preference isn't set the following
* directory is used by default: <profile>/har/logs
*/
var HarAutomation = Class({
function HarAutomation(toolbox) {
this.initialize(toolbox);
}
HarAutomation.prototype = {
// Initialization
initialize: function (toolbox) {
@ -201,7 +204,7 @@ var HarAutomation = Class({
getString: function (stringGrip) {
return this.webConsoleClient.getString(stringGrip);
},
});
};
// Helpers

View File

@ -43,14 +43,14 @@
<splitter class="devtools-side-splitter"/>
<vbox flex="1">
<hbox id="storage-toolbar" class="devtools-toolbar">
<button id="add-button"
class="devtools-button add-button"></button>
<spacer flex="1"/>
<textbox id="storage-searchbox"
class="devtools-filterinput"
type="search"
timeout="200"
placeholder="&searchBox.placeholder;"/>
<button id="add-button"
class="devtools-button add-button"></button>
<spacer flex="1"/>
<button class="devtools-button sidebar-toggle" hidden="true"></button>
</hbox>
<vbox id="storage-table" class="theme-sidebar" flex="1"/>

View File

@ -24,7 +24,8 @@ support-files =
!/devtools/client/framework/test/shared-head.js
[browser_storage_basic.js]
[browser_storage_basic_usercontextid.js]
[browser_storage_basic_usercontextid_1.js]
[browser_storage_basic_usercontextid_2.js]
tags = usercontextid
[browser_storage_basic_with_fragment.js]
[browser_storage_cache_delete.js]

View File

@ -0,0 +1,118 @@
/* 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/. */
// A test to check that the storage inspector is working correctly without
// userContextId.
"use strict";
const testCases = [
[
["cookies", "http://test1.example.org"],
[
getCookieId("c1", "test1.example.org", "/browser"),
getCookieId("cs2", ".example.org", "/"),
getCookieId("c3", "test1.example.org", "/"),
getCookieId("uc1", ".example.org", "/")
]
],
[
["cookies", "https://sectest1.example.org"],
[
getCookieId("uc1", ".example.org", "/"),
getCookieId("cs2", ".example.org", "/"),
getCookieId("sc1", "sectest1.example.org", "/browser/devtools/client/storage/test/")
]
],
[["localStorage", "http://test1.example.org"],
["ls1", "ls2"]],
[["localStorage", "http://sectest1.example.org"],
["iframe-u-ls1"]],
[["localStorage", "https://sectest1.example.org"],
["iframe-s-ls1"]],
[["sessionStorage", "http://test1.example.org"],
["ss1"]],
[["sessionStorage", "http://sectest1.example.org"],
["iframe-u-ss1", "iframe-u-ss2"]],
[["sessionStorage", "https://sectest1.example.org"],
["iframe-s-ss1"]],
[["indexedDB", "http://test1.example.org"],
["idb1 (default)", "idb2 (default)"]],
[["indexedDB", "http://test1.example.org", "idb1 (default)"],
["obj1", "obj2"]],
[["indexedDB", "http://test1.example.org", "idb2 (default)"],
["obj3"]],
[["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
[1, 2, 3]],
[["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"],
[1]],
[["indexedDB", "http://test1.example.org", "idb2 (default)", "obj3"],
[]],
[["indexedDB", "http://sectest1.example.org"],
[]],
[["indexedDB", "https://sectest1.example.org"],
["idb-s1 (default)", "idb-s2 (default)"]],
[["indexedDB", "https://sectest1.example.org", "idb-s1 (default)"],
["obj-s1"]],
[["indexedDB", "https://sectest1.example.org", "idb-s2 (default)"],
["obj-s2"]],
[["indexedDB", "https://sectest1.example.org", "idb-s1 (default)", "obj-s1"],
[6, 7]],
[["indexedDB", "https://sectest1.example.org", "idb-s2 (default)", "obj-s2"],
[16]],
[["Cache", "http://test1.example.org", "plop"],
[MAIN_DOMAIN + "404_cached_file.js",
MAIN_DOMAIN + "browser_storage_basic.js"]],
];
/**
* Test that the desired number of tree items are present
*/
function testTree(tests) {
let doc = gPanelWindow.document;
for (let [item] of tests) {
ok(doc.querySelector("[data-id='" + JSON.stringify(item) + "']"),
"Tree item " + item[0] + " should be present in the storage tree");
}
}
/**
* Test that correct table entries are shown for each of the tree item
*/
function* testTables(tests) {
let doc = gPanelWindow.document;
// Expand all nodes so that the synthesized click event actually works
gUI.tree.expandAll();
// First tree item is already selected so no clicking and waiting for update
for (let id of tests[0][1]) {
ok(doc.querySelector(".table-widget-cell[data-id='" + id + "']"),
"Table item " + id + " should be present");
}
// Click rest of the tree items and wait for the table to be updated
for (let [treeItem, items] of tests.slice(1)) {
yield selectTreeItem(treeItem);
// Check whether correct number of items are present in the table
is(doc.querySelectorAll(
".table-widget-wrapper:first-of-type .table-widget-cell"
).length, items.length, "Number of items in table is correct");
// Check if all the desired items are present in the table
for (let id of items) {
ok(doc.querySelector(".table-widget-cell[data-id='" + id + "']"),
"Table item " + id + " should be present");
}
}
}
add_task(function* () {
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html");
testTree(testCases);
yield testTables(testCases);
yield finishTests();
});

View File

@ -7,65 +7,6 @@
"use strict";
const testCases = [
[
["cookies", "http://test1.example.org"],
[
getCookieId("c1", "test1.example.org", "/browser"),
getCookieId("cs2", ".example.org", "/"),
getCookieId("c3", "test1.example.org", "/"),
getCookieId("uc1", ".example.org", "/")
]
],
[
["cookies", "https://sectest1.example.org"],
[
getCookieId("uc1", ".example.org", "/"),
getCookieId("cs2", ".example.org", "/"),
getCookieId("sc1", "sectest1.example.org", "/browser/devtools/client/storage/test/")
]
],
[["localStorage", "http://test1.example.org"],
["ls1", "ls2"]],
[["localStorage", "http://sectest1.example.org"],
["iframe-u-ls1"]],
[["localStorage", "https://sectest1.example.org"],
["iframe-s-ls1"]],
[["sessionStorage", "http://test1.example.org"],
["ss1"]],
[["sessionStorage", "http://sectest1.example.org"],
["iframe-u-ss1", "iframe-u-ss2"]],
[["sessionStorage", "https://sectest1.example.org"],
["iframe-s-ss1"]],
[["indexedDB", "http://test1.example.org"],
["idb1 (default)", "idb2 (default)"]],
[["indexedDB", "http://test1.example.org", "idb1 (default)"],
["obj1", "obj2"]],
[["indexedDB", "http://test1.example.org", "idb2 (default)"],
["obj3"]],
[["indexedDB", "http://test1.example.org", "idb1 (default)", "obj1"],
[1, 2, 3]],
[["indexedDB", "http://test1.example.org", "idb1 (default)", "obj2"],
[1]],
[["indexedDB", "http://test1.example.org", "idb2 (default)", "obj3"],
[]],
[["indexedDB", "http://sectest1.example.org"],
[]],
[["indexedDB", "https://sectest1.example.org"],
["idb-s1 (default)", "idb-s2 (default)"]],
[["indexedDB", "https://sectest1.example.org", "idb-s1 (default)"],
["obj-s1"]],
[["indexedDB", "https://sectest1.example.org", "idb-s2 (default)"],
["obj-s2"]],
[["indexedDB", "https://sectest1.example.org", "idb-s1 (default)", "obj-s1"],
[6, 7]],
[["indexedDB", "https://sectest1.example.org", "idb-s2 (default)", "obj-s2"],
[16]],
[["Cache", "http://test1.example.org", "plop"],
[MAIN_DOMAIN + "404_cached_file.js",
MAIN_DOMAIN + "browser_storage_basic.js"]],
];
const testCasesUserContextId = [
[
["cookies", "http://test1.example.org"],
@ -169,11 +110,6 @@ function* testTables(tests) {
}
add_task(function* () {
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html");
testTree(testCases);
yield testTables(testCases);
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings-usercontextid.html",
{userContextId: 1});

View File

@ -201,9 +201,10 @@ var openStoragePanel = Task.async(function* (cb) {
*/
function waitForToolboxFrameFocus(toolbox) {
info("Making sure that the toolbox's frame is focused");
let def = promise.defer();
waitForFocus(def.resolve, toolbox.win);
return def.promise;
return new Promise(resolve => {
waitForFocus(resolve, toolbox.win);
});
}
/**
@ -274,18 +275,16 @@ function* finishTests() {
// Sends a click event on the passed DOM node in an async manner
function* click(node) {
let def = promise.defer();
node.scrollIntoView();
// We need setTimeout here to allow any scrolling to complete before clicking
// the node.
setTimeout(() => {
node.click();
def.resolve();
}, 200);
return def;
return new Promise(resolve => {
// We need setTimeout here to allow any scrolling to complete before clicking
// the node.
setTimeout(() => {
node.click();
resolve();
}, 200);
});
}
/**
@ -306,35 +305,34 @@ function* click(node) {
function variablesViewExpandTo(options) {
let root = options.rootVariable;
let expandTo = options.expandTo.split(".");
let lastDeferred = promise.defer();
function getNext(prop) {
let name = expandTo.shift();
let newProp = prop.get(name);
return new Promise((resolve, reject) => {
function getNext(prop) {
let name = expandTo.shift();
let newProp = prop.get(name);
if (expandTo.length > 0) {
ok(newProp, "found property " + name);
if (newProp && newProp.expand) {
newProp.expand();
getNext(newProp);
if (expandTo.length > 0) {
ok(newProp, "found property " + name);
if (newProp && newProp.expand) {
newProp.expand();
getNext(newProp);
} else {
reject(prop);
}
} else if (newProp) {
resolve(newProp);
} else {
lastDeferred.reject(prop);
reject(prop);
}
} else if (newProp) {
lastDeferred.resolve(newProp);
} else {
lastDeferred.reject(prop);
}
}
if (root && root.expand) {
root.expand();
getNext(root);
} else {
lastDeferred.resolve(root);
}
return lastDeferred.promise;
if (root && root.expand) {
root.expand();
getNext(root);
} else {
resolve(root);
}
});
}
/**
@ -412,33 +410,32 @@ function findVariableViewProperties(ruleArray, parsed) {
}
function processExpandRules(rules) {
let rule = rules.shift();
if (!rule) {
return promise.resolve(null);
}
return new Promise(resolve => {
let rule = rules.shift();
if (!rule) {
resolve(null);
}
let deferred = promise.defer();
let expandOptions = {
rootVariable: gUI.view.getScopeAtIndex(parsed ? 1 : 0),
expandTo: rule.name
};
let expandOptions = {
rootVariable: gUI.view.getScopeAtIndex(parsed ? 1 : 0),
expandTo: rule.name
};
variablesViewExpandTo(expandOptions).then(function onSuccess(prop) {
let name = rule.name;
let lastName = name.split(".").pop();
rule.name = lastName;
variablesViewExpandTo(expandOptions).then(function onSuccess(prop) {
let name = rule.name;
let lastName = name.split(".").pop();
rule.name = lastName;
let matched = matchVariablesViewProperty(prop, rule);
return matched.then(onMatch.bind(null, prop, rule)).then(function () {
rule.name = name;
let matched = matchVariablesViewProperty(prop, rule);
return matched.then(onMatch.bind(null, prop, rule)).then(function () {
rule.name = name;
});
}, function onFailure() {
resolve(null);
}).then(processExpandRules.bind(null, rules)).then(function () {
resolve(null);
});
}, function onFailure() {
return promise.resolve(null);
}).then(processExpandRules.bind(null, rules)).then(function () {
deferred.resolve(null);
});
return deferred.promise;
}
function onAllRulesMatched(rules) {
@ -545,8 +542,10 @@ function* selectTableItem(id) {
showAvailableIds();
}
let updated = gUI.once("sidebar-updated");
yield click(target);
yield gUI.once("sidebar-updated");
yield updated;
}
/**
@ -554,29 +553,28 @@ function* selectTableItem(id) {
* @param {Object} target An observable object that either supports on/off or
* addEventListener/removeEventListener
* @param {String} eventName
* @param {Boolean} [useCapture] for addEventListener/removeEventListener
* @param {Boolean} useCapture Optional, for addEventListener/removeEventListener
* @return A promise that resolves when the event has been handled
*/
function once(target, eventName, useCapture = false) {
info("Waiting for event: '" + eventName + "' on " + target + ".");
let deferred = promise.defer();
for (let [add, remove] of [
["addEventListener", "removeEventListener"],
["addListener", "removeListener"],
["on", "off"]
]) {
if ((add in target) && (remove in target)) {
target[add](eventName, function onEvent(...aArgs) {
target[remove](eventName, onEvent, useCapture);
deferred.resolve.apply(deferred, aArgs);
}, useCapture);
break;
return new Promise(resolve => {
for (let [add, remove] of [
["addEventListener", "removeEventListener"],
["addListener", "removeListener"],
["on", "off"]
]) {
if ((add in target) && (remove in target)) {
target[add](eventName, function onEvent(...aArgs) {
info("Got event: '" + eventName + "' on " + target + ".");
target[remove](eventName, onEvent, useCapture);
resolve(...aArgs);
}, useCapture);
break;
}
}
}
return deferred.promise;
});
}
/**

View File

@ -99,6 +99,77 @@ const CANVAS_SIZE = 4096;
// the viewport's edges, therefore the lines won't looks as "infinite").
const CANVAS_INFINITY = CANVAS_SIZE << 8;
/**
* Returns an array containing the four coordinates of a rectangle, given its diagonal
* as input; optionally applying a matrix, and a function to each of the coordinates'
* value.
*
* @param {Number} x1
* The x-axis coordinate of the rectangle's diagonal start point.
* @param {Number} y1
* The y-axis coordinate of the rectangle's diagonal start point.
* @param {Number} x2
* The x-axis coordinate of the rectangle's diagonal end point.
* @param {Number} y2
* The y-axis coordinate of the rectangle's diagonal end point.
* @param {Array} [matrix=identity()]
* A transformation matrix to apply.
* @return {Array}
* The rect four corners' points transformed by the matrix given.
*/
function getPointsFromDiagonal(x1, y1, x2, y2, matrix = identity()) {
return [
[x1, y1],
[x2, y1],
[x2, y2],
[x1, y2]
].map(point => {
let transformedPoint = apply(matrix, point);
return {x: transformedPoint[0], y: transformedPoint[1]};
});
}
/**
* Takes an array of four points and returns a DOMRect-like object, represent the
* boundaries defined by the points given.
*
* @param {Array} points
* The four points.
* @return {Object}
* A DOMRect-like object.
*/
function getBoundsFromPoints(points) {
let bounds = {};
bounds.left = Math.min(points[0].x, points[1].x, points[2].x, points[3].x);
bounds.right = Math.max(points[0].x, points[1].x, points[2].x, points[3].x);
bounds.top = Math.min(points[0].y, points[1].y, points[2].y, points[3].y);
bounds.bottom = Math.max(points[0].y, points[1].y, points[2].y, points[3].y);
bounds.x = bounds.left;
bounds.y = bounds.top;
bounds.width = bounds.right - bounds.left;
bounds.height = bounds.bottom - bounds.top;
return bounds;
}
/**
* Takes an array of four points and returns a string represent a path description.
*
* @param {Array} points
* The four points.
* @return {String}
* A Path Description that can be used in svg's <path> element.
*/
function getPathDescriptionFromPoints(points) {
return "M" + points[0].x + "," + points[0].y + " " +
"L" + points[1].x + "," + points[1].y + " " +
"L" + points[2].x + "," + points[2].y + " " +
"L" + points[3].x + "," + points[3].y;
}
/**
* Draws a line to the context given, applying a transformation matrix if passed.
*
@ -141,18 +212,13 @@ function drawLine(ctx, x1, y1, x2, y2, matrix = identity()) {
* The transformation matrix to apply.
*/
function drawRect(ctx, x1, y1, x2, y2, matrix = identity()) {
let p = [
[x1, y1],
[x2, y1],
[x2, y2],
[x1, y2]
].map(point => apply(matrix, point).map(Math.round));
let p = getPointsFromDiagonal(x1, y1, x2, y2, matrix);
ctx.beginPath();
ctx.moveTo(p[0][0], p[0][1]);
ctx.lineTo(p[1][0], p[1][1]);
ctx.lineTo(p[2][0], p[2][1]);
ctx.lineTo(p[3][0], p[3][1]);
ctx.moveTo(Math.round(p[0].x), Math.round(p[0].y));
ctx.lineTo(Math.round(p[1].x), Math.round(p[1].y));
ctx.lineTo(Math.round(p[2].x), Math.round(p[2].y));
ctx.lineTo(Math.round(p[3].x), Math.round(p[3].y));
ctx.closePath();
}
@ -808,18 +874,11 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
*
* @param {GridArea} area
* The grid area object.
* @param {Number} x1
* The first x-coordinate of the grid area rectangle.
* @param {Number} x2
* The second x-coordinate of the grid area rectangle.
* @param {Number} y1
* The first y-coordinate of the grid area rectangle.
* @param {Number} y2
* The second y-coordinate of the grid area rectangle.
* @param {Object} bounds
* A DOMRect-like object represent the grid area rectangle.
*/
_updateGridAreaInfobar(area, x1, x2, y1, y2) {
let width = x2 - x1;
let height = y2 - y1;
_updateGridAreaInfobar(area, bounds) {
let { width, height } = bounds;
let dim = parseFloat(width.toPrecision(6)) +
" \u00D7 " +
parseFloat(height.toPrecision(6));
@ -828,15 +887,24 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
this.getElement("area-infobar-dimensions").setTextContent(dim);
let container = this.getElement("area-infobar-container");
this._moveInfobar(container, x1, x2, y1, y2, {
moveInfobar(container, bounds, this.win, {
position: "bottom",
hideIfOffscreen: true
});
},
_updateGridCellInfobar(rowNumber, columnNumber, x1, x2, y1, y2) {
let width = x2 - x1;
let height = y2 - y1;
/**
* Update the grid information displayed in the grid cell info bar.
*
* @param {Number} rowNumber
* The grid cell's row number.
* @param {Number} columnNumber
* The grid cell's column number.
* @param {Object} bounds
* A DOMRect-like object represent the grid cell rectangle.
*/
_updateGridCellInfobar(rowNumber, columnNumber, bounds) {
let { width, height } = bounds;
let dim = parseFloat(width.toPrecision(6)) +
" \u00D7 " +
parseFloat(height.toPrecision(6));
@ -847,7 +915,7 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
this.getElement("cell-infobar-dimensions").setTextContent(dim);
let container = this.getElement("cell-infobar-container");
this._moveInfobar(container, x1, x2, y1, y2, {
moveInfobar(container, bounds, this.win, {
position: "top",
hideIfOffscreen: true
});
@ -870,34 +938,8 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
this.getElement("line-infobar-names").setTextContent(gridLineNames);
let container = this.getElement("line-infobar-container");
this._moveInfobar(container, x, x, y, y);
},
/**
* Move the given grid infobar to the right place in the highlighter.
*
* @param {Number} x1
* The first x-coordinate of the grid rectangle.
* @param {Number} x2
* The second x-coordinate of the grid rectangle.
* @param {Number} y1
* The first y-coordinate of the grid rectangle.
* @param {Number} y2
* The second y-coordinate of the grid rectangle.
*/
_moveInfobar(container, x1, x2, y1, y2, options) {
let bounds = {
bottom: y2,
height: y2 - y1,
left: x1,
right: x2,
top: y1,
width: x2 - x1,
x: x1,
y: y1,
};
moveInfobar(container, bounds, this.win, options);
moveInfobar(container,
getBoundsFromPoints([{x, y}, {x, y}, {x, y}, {x, y}]), this.win);
},
/**
@ -991,8 +1033,8 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
},
/**
* Updates the current matrix taking in account the following transformations, in this
* order:
* Updates the current matrices for both canvas drawing and SVG, taking in account the
* following transformations, in this order:
* 1. The scale given by the display pixel ratio.
* 2. The translation to the top left corner of the element.
* 3. The scale given by the current zoom.
@ -1502,11 +1544,11 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
*/
renderGridArea(areaName) {
let paths = [];
let currentZoom = getCurrentZoom(this.win);
let { devicePixelRatio } = this.win;
let displayPixelRatio = getDisplayPixelRatio(this.win);
for (let i = 0; i < this.gridData.length; i++) {
let fragment = this.gridData[i];
let {bounds} = this.currentQuads.content[i];
for (let area of fragment.areas) {
if (areaName && areaName != area.name) {
@ -1518,23 +1560,33 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
let columnStart = fragment.cols.lines[area.columnStart - 1];
let columnEnd = fragment.cols.lines[area.columnEnd - 1];
let x1 = columnStart.start + columnStart.breadth +
(bounds.left / currentZoom);
let x2 = columnEnd.start + (bounds.left / currentZoom);
let y1 = rowStart.start + rowStart.breadth +
(bounds.top / currentZoom);
let y2 = rowEnd.start + (bounds.top / currentZoom);
let x1 = columnStart.start + columnStart.breadth;
let y1 = rowStart.start + rowStart.breadth;
let x2 = columnEnd.start;
let y2 = rowEnd.start;
let path = "M" + x1 + "," + y1 + " " +
"L" + x2 + "," + y1 + " " +
"L" + x2 + "," + y2 + " " +
"L" + x1 + "," + y2;
paths.push(path);
let points = getPointsFromDiagonal(x1, y1, x2, y2, this.currentMatrix);
// Scale down by `devicePixelRatio` since SVG element already take them into
// account.
let svgPoints = points.map(point => ({
x: Math.round(point.x / devicePixelRatio),
y: Math.round(point.y / devicePixelRatio)
}));
// Scale down by `displayPixelRatio` since infobar's HTML elements already take it
// into account; and the zoom scaling is handled by `moveInfobar`.
let bounds = getBoundsFromPoints(points.map(point => ({
x: Math.round(point.x / displayPixelRatio),
y: Math.round(point.y / displayPixelRatio)
})));
paths.push(getPathDescriptionFromPoints(svgPoints));
// Update and show the info bar when only displaying a single grid area.
if (areaName) {
this._showGridAreaInfoBar();
this._updateGridAreaInfobar(area, x1, x2, y1, y2);
this._updateGridAreaInfobar(area, bounds);
}
}
}
@ -1568,23 +1620,34 @@ CssGridHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, {
return;
}
let currentZoom = getCurrentZoom(this.win);
let {bounds} = this.currentQuads.content[gridFragmentIndex];
let x1 = column.start;
let y1 = row.start;
let x2 = column.start + column.breadth;
let y2 = row.start + row.breadth;
let x1 = column.start + (bounds.left / currentZoom);
let x2 = column.start + column.breadth + (bounds.left / currentZoom);
let y1 = row.start + (bounds.top / currentZoom);
let y2 = row.start + row.breadth + (bounds.top / currentZoom);
let { devicePixelRatio } = this.win;
let displayPixelRatio = getDisplayPixelRatio(this.win);
let points = getPointsFromDiagonal(x1, y1, x2, y2, this.currentMatrix);
// Scale down by `devicePixelRatio` since SVG element already take them into account.
let svgPoints = points.map(point => ({
x: Math.round(point.x / devicePixelRatio),
y: Math.round(point.y / devicePixelRatio)
}));
// Scale down by `displayPixelRatio` since infobar's HTML elements already take it
// into account, and the zoom scaling is handled by `moveInfobar`.
let bounds = getBoundsFromPoints(points.map(point => ({
x: Math.round(point.x / displayPixelRatio),
y: Math.round(point.y / displayPixelRatio)
})));
let path = "M" + x1 + "," + y1 + " " +
"L" + x2 + "," + y1 + " " +
"L" + x2 + "," + y2 + " " +
"L" + x1 + "," + y2;
let cells = this.getElement("cells");
cells.setAttribute("d", path);
cells.setAttribute("d", getPathDescriptionFromPoints(svgPoints));
this._showGridCellInfoBar();
this._updateGridCellInfobar(rowNumber, columnNumber, x1, x2, y1, y2);
this._updateGridCellInfobar(rowNumber, columnNumber, bounds);
},
/**

View File

@ -1180,7 +1180,7 @@ function start() {
}).then(function() {
// viewport depends on test environment.
var expectedWarning = new RegExp(
"Animation cannot be run on the compositor because the area of the frame size " +
"Animation cannot be run on the compositor because the area of the frame " +
"\\(\\d+\\) is too large relative to the viewport " +
"\\(larger than \\d+\\)");
assert_animation_property_state_equals(

View File

@ -10089,17 +10089,12 @@ nsIDocument::CreateStaticClone(nsIDocShell* aCloneContainer)
RefPtr<StyleSheet> sheet = GetStyleSheetAt(i);
if (sheet) {
if (sheet->IsApplicable()) {
// XXXheycam Need to make ServoStyleSheet cloning work.
if (sheet->IsGecko()) {
RefPtr<StyleSheet> clonedSheet =
sheet->Clone(nullptr, nullptr, clonedDoc, nullptr);
NS_WARNING_ASSERTION(clonedSheet,
"Cloning a stylesheet didn't work!");
if (clonedSheet) {
clonedDoc->AddStyleSheet(clonedSheet);
}
} else {
NS_ERROR("stylo: ServoStyleSheet doesn't support cloning");
RefPtr<StyleSheet> clonedSheet =
sheet->Clone(nullptr, nullptr, clonedDoc, nullptr);
NS_WARNING_ASSERTION(clonedSheet,
"Cloning a stylesheet didn't work!");
if (clonedSheet) {
clonedDoc->AddStyleSheet(clonedSheet);
}
}
}
@ -10109,17 +10104,12 @@ nsIDocument::CreateStaticClone(nsIDocShell* aCloneContainer)
for (StyleSheet* sheet : Reversed(thisAsDoc->mOnDemandBuiltInUASheets)) {
if (sheet) {
if (sheet->IsApplicable()) {
// XXXheycam Need to make ServoStyleSheet cloning work.
if (sheet->IsGecko()) {
RefPtr<StyleSheet> clonedSheet =
sheet->Clone(nullptr, nullptr, clonedDoc, nullptr);
NS_WARNING_ASSERTION(clonedSheet,
"Cloning a stylesheet didn't work!");
if (clonedSheet) {
clonedDoc->AddOnDemandBuiltInUASheet(clonedSheet);
}
} else {
NS_ERROR("stylo: ServoStyleSheet doesn't support cloning");
RefPtr<StyleSheet> clonedSheet =
sheet->Clone(nullptr, nullptr, clonedDoc, nullptr);
NS_WARNING_ASSERTION(clonedSheet,
"Cloning a stylesheet didn't work!");
if (clonedSheet) {
clonedDoc->AddOnDemandBuiltInUASheet(clonedSheet);
}
}
}

View File

@ -133,6 +133,7 @@
#ifdef USE_SKIA
#include "SurfaceTypes.h"
#include "GLBlitHelper.h"
#include "ScopedGLHelpers.h"
#endif
using mozilla::gl::GLContext;
@ -5232,46 +5233,49 @@ CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage,
return;
}
gl->MakeCurrent();
GLuint videoTexture = 0;
gl->fGenTextures(1, &videoTexture);
// skiaGL expect upload on drawing, and uses texture 0 for texturing,
// so we must active texture 0 and bind the texture for it.
gl->fActiveTexture(LOCAL_GL_TEXTURE0);
gl->fBindTexture(LOCAL_GL_TEXTURE_2D, videoTexture);
{
gl->MakeCurrent();
GLuint videoTexture = 0;
gl->fGenTextures(1, &videoTexture);
// skiaGL expect upload on drawing, and uses texture 0 for texturing,
// so we must active texture 0 and bind the texture for it.
gl->fActiveTexture(LOCAL_GL_TEXTURE0);
const gl::ScopedBindTexture scopeBindTexture(gl, videoTexture);
gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGB, srcImage->GetSize().width, srcImage->GetSize().height, 0, LOCAL_GL_RGB, LOCAL_GL_UNSIGNED_SHORT_5_6_5, nullptr);
gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGB, srcImage->GetSize().width, srcImage->GetSize().height, 0, LOCAL_GL_RGB, LOCAL_GL_UNSIGNED_SHORT_5_6_5, nullptr);
gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
const gl::OriginPos destOrigin = gl::OriginPos::TopLeft;
bool ok = gl->BlitHelper()->BlitImageToTexture(srcImage, srcImage->GetSize(),
videoTexture, LOCAL_GL_TEXTURE_2D,
destOrigin);
if (ok) {
NativeSurface texSurf;
texSurf.mType = NativeSurfaceType::OPENGL_TEXTURE;
texSurf.mFormat = SurfaceFormat::R5G6B5_UINT16;
texSurf.mSize.width = srcImage->GetSize().width;
texSurf.mSize.height = srcImage->GetSize().height;
texSurf.mSurface = (void*)((uintptr_t)videoTexture);
const gl::OriginPos destOrigin = gl::OriginPos::TopLeft;
bool ok = gl->BlitHelper()->BlitImageToTexture(srcImage, srcImage->GetSize(),
videoTexture, LOCAL_GL_TEXTURE_2D,
destOrigin);
if (ok) {
NativeSurface texSurf;
texSurf.mType = NativeSurfaceType::OPENGL_TEXTURE;
texSurf.mFormat = SurfaceFormat::R5G6B5_UINT16;
texSurf.mSize.width = srcImage->GetSize().width;
texSurf.mSize.height = srcImage->GetSize().height;
texSurf.mSurface = (void*)((uintptr_t)videoTexture);
srcSurf = mTarget->CreateSourceSurfaceFromNativeSurface(texSurf);
if (!srcSurf) {
srcSurf = mTarget->CreateSourceSurfaceFromNativeSurface(texSurf);
if (!srcSurf) {
gl->fDeleteTextures(1, &videoTexture);
}
imgSize.width = srcImage->GetSize().width;
imgSize.height = srcImage->GetSize().height;
int32_t displayWidth = video->VideoWidth();
int32_t displayHeight = video->VideoHeight();
aSw *= (double)imgSize.width / (double)displayWidth;
aSh *= (double)imgSize.height / (double)displayHeight;
} else {
gl->fDeleteTextures(1, &videoTexture);
}
imgSize.width = srcImage->GetSize().width;
imgSize.height = srcImage->GetSize().height;
int32_t displayWidth = video->VideoWidth();
int32_t displayHeight = video->VideoHeight();
aSw *= (double)imgSize.width / (double)displayWidth;
aSh *= (double)imgSize.height / (double)displayHeight;
} else {
gl->fDeleteTextures(1, &videoTexture);
}
srcImage = nullptr;
if (mCanvasElement) {

View File

@ -12,9 +12,9 @@ TablePartRelPosWarning=Relative positioning of table rows and row groups is now
ScrollLinkedEffectFound2=This site appears to use a scroll-linked positioning effect. This may not work well with asynchronous panning; see https://developer.mozilla.org/docs/Mozilla/Performance/ScrollLinkedEffects for further details and to join the discussion on related tools and features!
## LOCALIZATION NOTE(CompositorAnimationWarningContentTooLargeArea):
## %1$S is an integer value of the area of the frame size
## %1$S is an integer value of the area of the frame
## %2$S is an integer value of the area of a limit based on the viewport size
CompositorAnimationWarningContentTooLargeArea=Animation cannot be run on the compositor because the area of the frame size (%1$S) is too large relative to the viewport (larger than %2$S)
CompositorAnimationWarningContentTooLargeArea=Animation cannot be run on the compositor because the area of the frame (%1$S) is too large relative to the viewport (larger than %2$S)
## LOCALIZATION NOTE(CompositorAnimationWarningContentTooLarge2):
## (%1$S, %2$S) is a pair of integer values of the frame size
## (%3$S, %4$S) is a pair of integer values of a limit based on the viewport size

View File

@ -22,10 +22,6 @@
#include "AndroidMediaReader.h"
#include "AndroidMediaPluginHost.h"
#endif
#ifdef MOZ_DIRECTSHOW
#include "DirectShowDecoder.h"
#include "DirectShowReader.h"
#endif
#ifdef MOZ_FMP4
#include "MP4Decoder.h"
#include "MP4Demuxer.h"
@ -146,9 +142,6 @@ CanHandleCodecsType(const MediaContainerType& aType,
}
MediaCodecs supportedCodecs;
#ifdef MOZ_DIRECTSHOW
DirectShowDecoder::GetSupportedCodecs(aType, &supportedCodecs);
#endif
#ifdef MOZ_ANDROID_OMX
if (MediaDecoder::IsAndroidMediaPluginEnabled()) {
EnsureAndroidMediaPluginHost()->FindDecoder(aType, &supportedCodecs);
@ -211,11 +204,6 @@ CanHandleMediaType(const MediaContainerType& aType,
if (FlacDecoder::IsSupportedType(mimeType)) {
return CANPLAY_MAYBE;
}
#ifdef MOZ_DIRECTSHOW
if (DirectShowDecoder::GetSupportedCodecs(mimeType, nullptr)) {
return CANPLAY_MAYBE;
}
#endif
#ifdef MOZ_ANDROID_OMX
if (MediaDecoder::IsAndroidMediaPluginEnabled() &&
EnsureAndroidMediaPluginHost()->FindDecoder(mimeType, nullptr)) {
@ -314,15 +302,6 @@ InstantiateDecoder(const MediaContainerType& aType,
return decoder.forget();
}
#ifdef MOZ_DIRECTSHOW
// Note: DirectShow should come before WMF, so that we prefer DirectShow's
// MP3 support over WMF's.
if (DirectShowDecoder::GetSupportedCodecs(aType, nullptr)) {
decoder = new DirectShowDecoder(aOwner);
return decoder.forget();
}
#endif
if (IsHttpLiveStreamingType(aType)) {
// We don't have an HLS decoder.
Telemetry::Accumulate(Telemetry::MEDIA_HLS_DECODER_SUCCESS, false);
@ -387,13 +366,7 @@ DecoderTraits::CreateReader(const MediaContainerType& aType,
if (WebMDecoder::IsSupportedType(aType)) {
decoderReader =
new MediaFormatReader(aDecoder, new WebMDemuxer(aDecoder->GetResource()));
} else
#ifdef MOZ_DIRECTSHOW
if (DirectShowDecoder::GetSupportedCodecs(aType, nullptr)) {
decoderReader = new DirectShowReader(aDecoder);
} else
#endif
if (false) {} // dummy if to take care of the dangling else
}
return decoderReader;
}
@ -426,9 +399,6 @@ bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType)
MP3Decoder::IsSupportedType(*type) ||
ADTSDecoder::IsSupportedType(*type) ||
FlacDecoder::IsSupportedType(*type) ||
#ifdef MOZ_DIRECTSHOW
DirectShowDecoder::GetSupportedCodecs(*type, nullptr) ||
#endif
false;
}

View File

@ -738,9 +738,7 @@ AudioCallbackDriver::Init()
return true;
}
}
bool aec;
Unused << mGraphImpl->AudioTrackPresent(aec);
SetMicrophoneActive(aec);
SetMicrophoneActive(mGraphImpl->mInputWanted);
cubeb_stream_register_device_changed_callback(mAudioStream,
AudioCallbackDriver::DeviceChangedCallback_s);

View File

@ -13,8 +13,8 @@
namespace mozilla {
// Thread pool listener which ensures that MSCOM is initialized and
// deinitialized on the thread pool thread. We may call into WMF or
// DirectShow on this thread, so we need MSCOM working.
// deinitialized on the thread pool thread. We may call into WMF on this thread,
// so we need MSCOM working.
class MSCOMInitThreadPoolListener final : public nsIThreadPoolListener {
~MSCOMInitThreadPoolListener() {}
public:

View File

@ -1,285 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "SampleSink.h"
#include "AudioSinkFilter.h"
#include "AudioSinkInputPin.h"
#include "VideoUtils.h"
#include "mozilla/Logging.h"
#include <initguid.h>
#include <wmsdkidl.h>
#define DELETE_RESET(p) { delete (p) ; (p) = nullptr ;}
DEFINE_GUID(CLSID_MozAudioSinkFilter, 0x1872d8c8, 0xea8d, 0x4c34, 0xae, 0x96, 0x69, 0xde,
0xf1, 0x33, 0x7b, 0x33);
using namespace mozilla::media;
namespace mozilla {
static LazyLogModule gDirectShowLog("DirectShowDecoder");
#define LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
AudioSinkFilter::AudioSinkFilter(const wchar_t* aObjectName, HRESULT* aOutResult)
: BaseFilter(aObjectName, CLSID_MozAudioSinkFilter),
mFilterCritSec("AudioSinkFilter::mFilterCritSec")
{
(*aOutResult) = S_OK;
mInputPin = new AudioSinkInputPin(L"AudioSinkInputPin",
this,
&mFilterCritSec,
aOutResult);
}
AudioSinkFilter::~AudioSinkFilter()
{
}
int
AudioSinkFilter::GetPinCount()
{
return 1;
}
BasePin*
AudioSinkFilter::GetPin(int aIndex)
{
CriticalSectionAutoEnter lockFilter(mFilterCritSec);
return (aIndex == 0) ? static_cast<BasePin*>(mInputPin) : nullptr;
}
HRESULT
AudioSinkFilter::Pause()
{
CriticalSectionAutoEnter lockFilter(mFilterCritSec);
if (mState == State_Stopped) {
// Change the state, THEN activate the input pin.
mState = State_Paused;
if (mInputPin && mInputPin->IsConnected()) {
mInputPin->Active();
}
} else if (mState == State_Running) {
mState = State_Paused;
}
return S_OK;
}
HRESULT
AudioSinkFilter::Stop()
{
CriticalSectionAutoEnter lockFilter(mFilterCritSec);
mState = State_Stopped;
if (mInputPin) {
mInputPin->Inactive();
}
GetSampleSink()->Flush();
return S_OK;
}
HRESULT
AudioSinkFilter::Run(REFERENCE_TIME tStart)
{
LOG("AudioSinkFilter::Run(%lld) [%4.2lf]",
RefTimeToUsecs(tStart),
double(RefTimeToUsecs(tStart)) / USECS_PER_S);
return media::BaseFilter::Run(tStart);
}
HRESULT
AudioSinkFilter::GetClassID( OUT CLSID * pCLSID )
{
(* pCLSID) = CLSID_MozAudioSinkFilter;
return S_OK;
}
HRESULT
AudioSinkFilter::QueryInterface(REFIID aIId, void **aInterface)
{
if (aIId == IID_IMediaSeeking) {
*aInterface = static_cast<IMediaSeeking*>(this);
AddRef();
return S_OK;
}
return mozilla::media::BaseFilter::QueryInterface(aIId, aInterface);
}
ULONG
AudioSinkFilter::AddRef()
{
return ::InterlockedIncrement(&mRefCnt);
}
ULONG
AudioSinkFilter::Release()
{
unsigned long newRefCnt = ::InterlockedDecrement(&mRefCnt);
if (!newRefCnt) {
delete this;
}
return newRefCnt;
}
SampleSink*
AudioSinkFilter::GetSampleSink()
{
return mInputPin->GetSampleSink();
}
// IMediaSeeking implementation.
//
// Calls to IMediaSeeking are forwarded to the output pin that the
// AudioSinkInputPin is connected to, i.e. upstream towards the parser and
// source filters, which actually implement seeking.
#define ENSURE_CONNECTED_PIN_SEEKING \
if (!mInputPin) { \
return E_NOTIMPL; \
} \
RefPtr<IMediaSeeking> pinSeeking = mInputPin->GetConnectedPinSeeking(); \
if (!pinSeeking) { \
return E_NOTIMPL; \
}
HRESULT
AudioSinkFilter::GetCapabilities(DWORD* aCapabilities)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->GetCapabilities(aCapabilities);
}
HRESULT
AudioSinkFilter::CheckCapabilities(DWORD* aCapabilities)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->CheckCapabilities(aCapabilities);
}
HRESULT
AudioSinkFilter::IsFormatSupported(const GUID* aFormat)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->IsFormatSupported(aFormat);
}
HRESULT
AudioSinkFilter::QueryPreferredFormat(GUID* aFormat)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->QueryPreferredFormat(aFormat);
}
HRESULT
AudioSinkFilter::GetTimeFormat(GUID* aFormat)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->GetTimeFormat(aFormat);
}
HRESULT
AudioSinkFilter::IsUsingTimeFormat(const GUID* aFormat)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->IsUsingTimeFormat(aFormat);
}
HRESULT
AudioSinkFilter::SetTimeFormat(const GUID* aFormat)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->SetTimeFormat(aFormat);
}
HRESULT
AudioSinkFilter::GetDuration(LONGLONG* aDuration)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->GetDuration(aDuration);
}
HRESULT
AudioSinkFilter::GetStopPosition(LONGLONG* aStop)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->GetStopPosition(aStop);
}
HRESULT
AudioSinkFilter::GetCurrentPosition(LONGLONG* aCurrent)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->GetCurrentPosition(aCurrent);
}
HRESULT
AudioSinkFilter::ConvertTimeFormat(LONGLONG* aTarget,
const GUID* aTargetFormat,
LONGLONG aSource,
const GUID* aSourceFormat)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->ConvertTimeFormat(aTarget,
aTargetFormat,
aSource,
aSourceFormat);
}
HRESULT
AudioSinkFilter::SetPositions(LONGLONG* aCurrent,
DWORD aCurrentFlags,
LONGLONG* aStop,
DWORD aStopFlags)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->SetPositions(aCurrent,
aCurrentFlags,
aStop,
aStopFlags);
}
HRESULT
AudioSinkFilter::GetPositions(LONGLONG* aCurrent,
LONGLONG* aStop)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->GetPositions(aCurrent, aStop);
}
HRESULT
AudioSinkFilter::GetAvailable(LONGLONG* aEarliest,
LONGLONG* aLatest)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->GetAvailable(aEarliest, aLatest);
}
HRESULT
AudioSinkFilter::SetRate(double aRate)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->SetRate(aRate);
}
HRESULT
AudioSinkFilter::GetRate(double* aRate)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->GetRate(aRate);
}
HRESULT
AudioSinkFilter::GetPreroll(LONGLONG* aPreroll)
{
ENSURE_CONNECTED_PIN_SEEKING
return pinSeeking->GetPreroll(aPreroll);
}
} // namespace mozilla

View File

@ -1,95 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(AudioSinkFilter_h_)
#define AudioSinkFilter_h_
#include "BaseFilter.h"
#include "DirectShowUtils.h"
#include "nsAutoPtr.h"
#include "mozilla/RefPtr.h"
namespace mozilla {
class AudioSinkInputPin;
class SampleSink;
// Filter that acts as the end of the graph. Audio samples input into
// this filter block the calling thread, and the calling thread is
// unblocked when the decode thread extracts the sample. The samples
// input into this filter are stored in the SampleSink, where the blocking
// is implemented. The input pin owns the SampleSink.
class AudioSinkFilter: public mozilla::media::BaseFilter,
public IMediaSeeking
{
public:
AudioSinkFilter(const wchar_t* aObjectName, HRESULT* aOutResult);
virtual ~AudioSinkFilter();
// Gets the input pin's sample sink.
SampleSink* GetSampleSink();
// IUnknown implementation.
STDMETHODIMP QueryInterface(REFIID aIId, void **aInterface);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
// --------------------------------------------------------------------
// CBaseFilter methods
int GetPinCount ();
mozilla::media::BasePin* GetPin ( IN int Index);
STDMETHODIMP Pause ();
STDMETHODIMP Stop ();
STDMETHODIMP GetClassID ( OUT CLSID * pCLSID);
STDMETHODIMP Run(REFERENCE_TIME tStart);
// IMediaSeeking Methods...
// We defer to SourceFilter, but we must expose the interface on
// the output pins. Seeking commands come upstream from the renderers,
// but they must be actioned at the source filters.
STDMETHODIMP GetCapabilities(DWORD* aCapabilities);
STDMETHODIMP CheckCapabilities(DWORD* aCapabilities);
STDMETHODIMP IsFormatSupported(const GUID* aFormat);
STDMETHODIMP QueryPreferredFormat(GUID* aFormat);
STDMETHODIMP GetTimeFormat(GUID* aFormat);
STDMETHODIMP IsUsingTimeFormat(const GUID* aFormat);
STDMETHODIMP SetTimeFormat(const GUID* aFormat);
STDMETHODIMP GetDuration(LONGLONG* pDuration);
STDMETHODIMP GetStopPosition(LONGLONG* pStop);
STDMETHODIMP GetCurrentPosition(LONGLONG* aCurrent);
STDMETHODIMP ConvertTimeFormat(LONGLONG* aTarget,
const GUID* aTargetFormat,
LONGLONG aSource,
const GUID* aSourceFormat);
STDMETHODIMP SetPositions(LONGLONG* aCurrent,
DWORD aCurrentFlags,
LONGLONG* aStop,
DWORD aStopFlags);
STDMETHODIMP GetPositions(LONGLONG* aCurrent,
LONGLONG* aStop);
STDMETHODIMP GetAvailable(LONGLONG* aEarliest,
LONGLONG* aLatest);
STDMETHODIMP SetRate(double aRate);
STDMETHODIMP GetRate(double* aRate);
STDMETHODIMP GetPreroll(LONGLONG* aPreroll);
// --------------------------------------------------------------------
// class factory calls this
static IUnknown * CreateInstance (IN LPUNKNOWN punk, OUT HRESULT * phr);
private:
CriticalSection mFilterCritSec;
// Note: The input pin defers its refcounting to the sink filter, so when
// the input pin is addrefed, what actually happens is the sink filter is
// addrefed.
nsAutoPtr<AudioSinkInputPin> mInputPin;
};
} // namespace mozilla
#endif // AudioSinkFilter_h_

View File

@ -1,195 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "AudioSinkInputPin.h"
#include "AudioSinkFilter.h"
#include "SampleSink.h"
#include "mozilla/Logging.h"
#include <wmsdkidl.h>
using namespace mozilla::media;
namespace mozilla {
static LazyLogModule gDirectShowLog("DirectShowDecoder");
#define LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
AudioSinkInputPin::AudioSinkInputPin(wchar_t* aObjectName,
AudioSinkFilter* aFilter,
mozilla::CriticalSection* aLock,
HRESULT* aOutResult)
: BaseInputPin(aObjectName, aFilter, aLock, aOutResult, aObjectName),
mSegmentStartTime(0)
{
MOZ_COUNT_CTOR(AudioSinkInputPin);
mSampleSink = new SampleSink();
}
AudioSinkInputPin::~AudioSinkInputPin()
{
MOZ_COUNT_DTOR(AudioSinkInputPin);
}
HRESULT
AudioSinkInputPin::GetMediaType(int aPosition, MediaType* aOutMediaType)
{
NS_ENSURE_TRUE(aPosition >= 0, E_INVALIDARG);
NS_ENSURE_TRUE(aOutMediaType, E_POINTER);
if (aPosition > 0) {
return S_FALSE;
}
// Note: We set output as PCM, as IEEE_FLOAT only works when using the
// MP3 decoder as an MFT, and we can't do that while using DirectShow.
aOutMediaType->SetType(&MEDIATYPE_Audio);
aOutMediaType->SetSubtype(&MEDIASUBTYPE_PCM);
aOutMediaType->SetType(&FORMAT_WaveFormatEx);
aOutMediaType->SetTemporalCompression(FALSE);
return S_OK;
}
HRESULT
AudioSinkInputPin::CheckMediaType(const MediaType* aMediaType)
{
if (!aMediaType) {
return E_INVALIDARG;
}
GUID majorType = *aMediaType->Type();
if (majorType != MEDIATYPE_Audio && majorType != WMMEDIATYPE_Audio) {
return E_INVALIDARG;
}
if (*aMediaType->Subtype() != MEDIASUBTYPE_PCM) {
return E_INVALIDARG;
}
if (*aMediaType->FormatType() != FORMAT_WaveFormatEx) {
return E_INVALIDARG;
}
// We accept the media type, stash its layout format!
WAVEFORMATEX* wfx = (WAVEFORMATEX*)(aMediaType->pbFormat);
GetSampleSink()->SetAudioFormat(wfx);
return S_OK;
}
AudioSinkFilter*
AudioSinkInputPin::GetAudioSinkFilter()
{
return reinterpret_cast<AudioSinkFilter*>(mFilter);
}
SampleSink*
AudioSinkInputPin::GetSampleSink()
{
return mSampleSink;
}
HRESULT
AudioSinkInputPin::SetAbsoluteMediaTime(IMediaSample* aSample)
{
HRESULT hr;
REFERENCE_TIME start = 0, end = 0;
hr = aSample->GetTime(&start, &end);
NS_ENSURE_TRUE(SUCCEEDED(hr), E_FAIL);
{
CriticalSectionAutoEnter lock(*mLock);
start += mSegmentStartTime;
end += mSegmentStartTime;
}
hr = aSample->SetMediaTime(&start, &end);
NS_ENSURE_TRUE(SUCCEEDED(hr), E_FAIL);
return S_OK;
}
HRESULT
AudioSinkInputPin::Receive(IMediaSample* aSample )
{
HRESULT hr;
NS_ENSURE_TRUE(aSample, E_POINTER);
hr = BaseInputPin::Receive(aSample);
if (SUCCEEDED(hr) && hr != S_FALSE) { // S_FALSE == flushing
// Set the timestamp of the sample after being adjusted for
// seeking/segments in the "media time" attribute. When we seek,
// DirectShow starts a new "segment", and starts labeling samples
// from time=0 again, so we need to correct for this to get the
// actual timestamps after seeking.
hr = SetAbsoluteMediaTime(aSample);
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
hr = GetSampleSink()->Receive(aSample);
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
}
return S_OK;
}
already_AddRefed<IMediaSeeking>
AudioSinkInputPin::GetConnectedPinSeeking()
{
RefPtr<IPin> peer = GetConnected();
if (!peer)
return nullptr;
RefPtr<IMediaSeeking> seeking;
peer->QueryInterface(static_cast<IMediaSeeking**>(getter_AddRefs(seeking)));
return seeking.forget();
}
HRESULT
AudioSinkInputPin::BeginFlush()
{
HRESULT hr = media::BaseInputPin::BeginFlush();
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
GetSampleSink()->Flush();
return S_OK;
}
HRESULT
AudioSinkInputPin::EndFlush()
{
HRESULT hr = media::BaseInputPin::EndFlush();
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
// Reset the EOS flag, so that if we're called after a seek we still work.
GetSampleSink()->Reset();
return S_OK;
}
HRESULT
AudioSinkInputPin::EndOfStream(void)
{
HRESULT hr = media::BaseInputPin::EndOfStream();
if (FAILED(hr) || hr == S_FALSE) {
// Pin is stil flushing.
return hr;
}
GetSampleSink()->SetEOS();
return S_OK;
}
HRESULT
AudioSinkInputPin::NewSegment(REFERENCE_TIME tStart,
REFERENCE_TIME tStop,
double dRate)
{
CriticalSectionAutoEnter lock(*mLock);
// Record the start time of the new segment, so that we can store the
// correct absolute timestamp in the "media time" each incoming sample.
mSegmentStartTime = tStart;
return S_OK;
}
} // namespace mozilla

View File

@ -1,76 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(AudioSinkInputPin_h_)
#define AudioSinkInputPin_h_
#include "BaseInputPin.h"
#include "DirectShowUtils.h"
#include "mozilla/RefPtr.h"
#include "nsAutoPtr.h"
namespace mozilla {
namespace media {
class MediaType;
}
class AudioSinkFilter;
class SampleSink;
// Input pin for capturing audio output of a DirectShow filter graph.
// This is the input pin for the AudioSinkFilter.
class AudioSinkInputPin: public mozilla::media::BaseInputPin
{
public:
AudioSinkInputPin(wchar_t* aObjectName,
AudioSinkFilter* aFilter,
mozilla::CriticalSection* aLock,
HRESULT* aOutResult);
virtual ~AudioSinkInputPin();
HRESULT GetMediaType (IN int iPos, OUT mozilla::media::MediaType * pmt);
HRESULT CheckMediaType (IN const mozilla::media::MediaType * pmt);
STDMETHODIMP Receive (IN IMediaSample *);
STDMETHODIMP BeginFlush() override;
STDMETHODIMP EndFlush() override;
// Called when we start decoding a new segment, that happens directly after
// a seek. This captures the segment's start time. Samples decoded by the
// MP3 decoder have their timestamps offset from the segment start time.
// Storing the segment start time enables us to set each sample's MediaTime
// as an offset in the stream relative to the start of the stream, rather
// than the start of the segment, i.e. its absolute time in the stream.
STDMETHODIMP NewSegment(REFERENCE_TIME tStart,
REFERENCE_TIME tStop,
double dRate) override;
STDMETHODIMP EndOfStream() override;
// Returns the IMediaSeeking interface of the connected output pin.
// We forward seeking requests upstream from the sink to the source
// filters.
already_AddRefed<IMediaSeeking> GetConnectedPinSeeking();
SampleSink* GetSampleSink();
private:
AudioSinkFilter* GetAudioSinkFilter();
// Sets the media time on the media sample, relative to the segment
// start time.
HRESULT SetAbsoluteMediaTime(IMediaSample* aSample);
nsAutoPtr<SampleSink> mSampleSink;
// Synchronized by the filter lock; BaseInputPin::mLock.
REFERENCE_TIME mSegmentStartTime;
};
} // namespace mozilla
#endif // AudioSinkInputPin_h_

View File

@ -1,58 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "DirectShowDecoder.h"
#include "DirectShowReader.h"
#include "DirectShowUtils.h"
#include "MediaContainerType.h"
#include "MediaDecoderStateMachine.h"
#include "mozilla/Preferences.h"
#include "mozilla/WindowsVersion.h"
namespace mozilla {
MediaDecoderStateMachine* DirectShowDecoder::CreateStateMachine()
{
return new MediaDecoderStateMachine(this, new DirectShowReader(this));
}
/* static */
bool
DirectShowDecoder::GetSupportedCodecs(const MediaContainerType& aType,
MediaCodecs* aOutCodecs)
{
if (!IsEnabled()) {
return false;
}
if (aType.Type() == MEDIAMIMETYPE("audio/mpeg")
|| aType.Type() == MEDIAMIMETYPE("audio/mp3")) {
if (aOutCodecs) {
*aOutCodecs = MediaCodecs("mp3");
}
return true;
}
return false;
}
/* static */
bool
DirectShowDecoder::IsEnabled()
{
return CanDecodeMP3UsingDirectShow() &&
Preferences::GetBool("media.directshow.enabled");
}
DirectShowDecoder::DirectShowDecoder(MediaDecoderOwner* aOwner)
: MediaDecoder(aOwner)
{
}
DirectShowDecoder::~DirectShowDecoder() = default;
} // namespace mozilla

View File

@ -1,48 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(DirectShowDecoder_h_)
#define DirectShowDecoder_h_
#include "MediaDecoder.h"
namespace mozilla {
class MediaCodecs;
class MediaContainerType;
// Decoder that uses DirectShow to playback MP3 files only.
class DirectShowDecoder : public MediaDecoder
{
public:
explicit DirectShowDecoder(MediaDecoderOwner* aOwner);
virtual ~DirectShowDecoder();
MediaDecoder* Clone(MediaDecoderOwner* aOwner) override {
if (!IsEnabled()) {
return nullptr;
}
return new DirectShowDecoder(aOwner);
}
MediaDecoderStateMachine* CreateStateMachine() override;
// Returns true if aType is a MIME type that we render with the
// DirectShow backend. If aCodecList is non null,
// it is filled with a (static const) null-terminated list of strings
// denoting the codecs we'll playback. Note that playback is strictly
// limited to MP3 only.
static bool GetSupportedCodecs(const MediaContainerType& aType,
MediaCodecs* aOutCodecs);
// Returns true if the DirectShow backend is preffed on.
static bool IsEnabled();
};
} // namespace mozilla
#endif

View File

@ -1,360 +0,0 @@
/* -*- 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 "DirectShowReader.h"
#include "MediaDecoderReader.h"
#include "mozilla/RefPtr.h"
#include "DirectShowUtils.h"
#include "AudioSinkFilter.h"
#include "SourceFilter.h"
#include "SampleSink.h"
#include "VideoUtils.h"
using namespace mozilla::media;
namespace mozilla {
// Windows XP's MP3 decoder filter. This is available on XP only, on Vista
// and later we can use the DMO Wrapper filter and MP3 decoder DMO.
const GUID DirectShowReader::CLSID_MPEG_LAYER_3_DECODER_FILTER =
{ 0x38BE3000, 0xDBF4, 0x11D0, {0x86, 0x0E, 0x00, 0xA0, 0x24, 0xCF, 0xEF, 0x6D} };
static LazyLogModule gDirectShowLog("DirectShowDecoder");
#define LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
DirectShowReader::DirectShowReader(AbstractMediaDecoder* aDecoder)
: MediaDecoderReader(aDecoder),
mMP3FrameParser(aDecoder->GetResource()->GetLength()),
#ifdef DIRECTSHOW_REGISTER_GRAPH
mRotRegister(0),
#endif
mNumChannels(0),
mAudioRate(0),
mBytesPerSample(0)
{
MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
MOZ_COUNT_CTOR(DirectShowReader);
}
DirectShowReader::~DirectShowReader()
{
MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
MOZ_COUNT_DTOR(DirectShowReader);
#ifdef DIRECTSHOW_REGISTER_GRAPH
if (mRotRegister) {
RemoveGraphFromRunningObjectTable(mRotRegister);
}
#endif
}
// Try to parse the MP3 stream to make sure this is indeed an MP3, get the
// estimated duration of the stream, and find the offset of the actual MP3
// frames in the stream, as DirectShow doesn't like large ID3 sections.
static nsresult
ParseMP3Headers(MP3FrameParser *aParser, MediaResource *aResource)
{
const uint32_t MAX_READ_SIZE = 4096;
uint64_t offset = 0;
while (aParser->NeedsData() && !aParser->ParsedHeaders()) {
uint32_t bytesRead;
char buffer[MAX_READ_SIZE];
nsresult rv = aResource->ReadAt(offset, buffer,
MAX_READ_SIZE, &bytesRead);
NS_ENSURE_SUCCESS(rv, rv);
if (!bytesRead) {
// End of stream.
return NS_ERROR_FAILURE;
}
aParser->Parse(reinterpret_cast<uint8_t*>(buffer), bytesRead, offset);
offset += bytesRead;
}
return aParser->IsMP3() ? NS_OK : NS_ERROR_FAILURE;
}
nsresult
DirectShowReader::ReadMetadata(MediaInfo* aInfo,
MetadataTags** aTags)
{
MOZ_ASSERT(OnTaskQueue());
HRESULT hr;
nsresult rv;
// Create the filter graph, reference it by the GraphBuilder interface,
// to make graph building more convenient.
hr = CoCreateInstance(CLSID_FilterGraph,
nullptr,
CLSCTX_INPROC_SERVER,
IID_IGraphBuilder,
reinterpret_cast<void**>(static_cast<IGraphBuilder**>(getter_AddRefs(mGraph))));
NS_ENSURE_TRUE(SUCCEEDED(hr) && mGraph, NS_ERROR_FAILURE);
rv = ParseMP3Headers(&mMP3FrameParser, mDecoder->GetResource());
NS_ENSURE_SUCCESS(rv, rv);
#ifdef DIRECTSHOW_REGISTER_GRAPH
hr = AddGraphToRunningObjectTable(mGraph, &mRotRegister);
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
#endif
// Extract the interface pointers we'll need from the filter graph.
hr = mGraph->QueryInterface(static_cast<IMediaControl**>(getter_AddRefs(mControl)));
NS_ENSURE_TRUE(SUCCEEDED(hr) && mControl, NS_ERROR_FAILURE);
hr = mGraph->QueryInterface(static_cast<IMediaSeeking**>(getter_AddRefs(mMediaSeeking)));
NS_ENSURE_TRUE(SUCCEEDED(hr) && mMediaSeeking, NS_ERROR_FAILURE);
// Build the graph. Create the filters we need, and connect them. We
// build the entire graph ourselves to prevent other decoders installed
// on the system being created and used.
// Our source filters, wraps the MediaResource.
mSourceFilter = new SourceFilter(MEDIATYPE_Stream, MEDIASUBTYPE_MPEG1Audio);
NS_ENSURE_TRUE(mSourceFilter, NS_ERROR_FAILURE);
rv = mSourceFilter->Init(mDecoder->GetResource(), mMP3FrameParser.GetMP3Offset());
NS_ENSURE_SUCCESS(rv, rv);
hr = mGraph->AddFilter(mSourceFilter, L"MozillaDirectShowSource");
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
// The MPEG demuxer.
RefPtr<IBaseFilter> demuxer;
hr = CreateAndAddFilter(mGraph,
CLSID_MPEG1Splitter,
L"MPEG1Splitter",
getter_AddRefs(demuxer));
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
// Platform MP3 decoder.
RefPtr<IBaseFilter> decoder;
// Firstly try to create the MP3 decoder filter that ships with WinXP
// directly. This filter doesn't normally exist on later versions of
// Windows.
hr = CreateAndAddFilter(mGraph,
CLSID_MPEG_LAYER_3_DECODER_FILTER,
L"MPEG Layer 3 Decoder",
getter_AddRefs(decoder));
if (FAILED(hr)) {
// Failed to create MP3 decoder filter. Try to instantiate
// the MP3 decoder DMO.
hr = AddMP3DMOWrapperFilter(mGraph, getter_AddRefs(decoder));
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
}
// Sink, captures audio samples and inserts them into our pipeline.
static const wchar_t* AudioSinkFilterName = L"MozAudioSinkFilter";
mAudioSinkFilter = new AudioSinkFilter(AudioSinkFilterName, &hr);
NS_ENSURE_TRUE(mAudioSinkFilter && SUCCEEDED(hr), NS_ERROR_FAILURE);
hr = mGraph->AddFilter(mAudioSinkFilter, AudioSinkFilterName);
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
// Join the filters.
hr = ConnectFilters(mGraph, mSourceFilter, demuxer);
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
hr = ConnectFilters(mGraph, demuxer, decoder);
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
hr = ConnectFilters(mGraph, decoder, mAudioSinkFilter);
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
WAVEFORMATEX format;
mAudioSinkFilter->GetSampleSink()->GetAudioFormat(&format);
NS_ENSURE_TRUE(format.wFormatTag == WAVE_FORMAT_PCM, NS_ERROR_FAILURE);
mInfo.mAudio.mChannels = mNumChannels = format.nChannels;
mInfo.mAudio.mRate = mAudioRate = format.nSamplesPerSec;
mInfo.mAudio.mBitDepth = format.wBitsPerSample;
mBytesPerSample = format.wBitsPerSample / 8;
// Begin decoding!
hr = mControl->Run();
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
DWORD seekCaps = 0;
hr = mMediaSeeking->GetCapabilities(&seekCaps);
mInfo.mMediaSeekable = SUCCEEDED(hr) && (AM_SEEKING_CanSeekAbsolute & seekCaps);
int64_t duration = mMP3FrameParser.GetDuration();
if (SUCCEEDED(hr)) {
mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(duration));
}
LOG("Successfully initialized DirectShow MP3 decoder.");
LOG("Channels=%u Hz=%u duration=%lld bytesPerSample=%d",
mInfo.mAudio.mChannels,
mInfo.mAudio.mRate,
RefTimeToUsecs(duration),
mBytesPerSample);
*aInfo = mInfo;
// Note: The SourceFilter strips ID3v2 tags out of the stream.
*aTags = nullptr;
return NS_OK;
}
inline float
UnsignedByteToAudioSample(uint8_t aValue)
{
return aValue * (2.0f / UINT8_MAX) - 1.0f;
}
bool
DirectShowReader::Finish(HRESULT aStatus)
{
MOZ_ASSERT(OnTaskQueue());
LOG("DirectShowReader::Finish(0x%x)", aStatus);
// Notify the filter graph of end of stream.
RefPtr<IMediaEventSink> eventSink;
HRESULT hr = mGraph->QueryInterface(static_cast<IMediaEventSink**>(getter_AddRefs(eventSink)));
if (SUCCEEDED(hr) && eventSink) {
eventSink->Notify(EC_COMPLETE, aStatus, 0);
}
return false;
}
class DirectShowCopy
{
public:
DirectShowCopy(uint8_t *aSource, uint32_t aBytesPerSample,
uint32_t aSamples, uint32_t aChannels)
: mSource(aSource)
, mBytesPerSample(aBytesPerSample)
, mSamples(aSamples)
, mChannels(aChannels)
, mNextSample(0)
{ }
uint32_t operator()(AudioDataValue *aBuffer, uint32_t aSamples)
{
uint32_t maxSamples = std::min(aSamples, mSamples - mNextSample);
uint32_t frames = maxSamples / mChannels;
size_t byteOffset = mNextSample * mBytesPerSample;
if (mBytesPerSample == 1) {
for (uint32_t i = 0; i < maxSamples; ++i) {
uint8_t *sample = mSource + byteOffset;
aBuffer[i] = UnsignedByteToAudioSample(*sample);
byteOffset += mBytesPerSample;
}
} else if (mBytesPerSample == 2) {
for (uint32_t i = 0; i < maxSamples; ++i) {
int16_t *sample = reinterpret_cast<int16_t *>(mSource + byteOffset);
aBuffer[i] = AudioSampleToFloat(*sample);
byteOffset += mBytesPerSample;
}
}
mNextSample += maxSamples;
return frames;
}
private:
uint8_t * const mSource;
const uint32_t mBytesPerSample;
const uint32_t mSamples;
const uint32_t mChannels;
uint32_t mNextSample;
};
bool
DirectShowReader::DecodeAudioData()
{
MOZ_ASSERT(OnTaskQueue());
HRESULT hr;
SampleSink* sink = mAudioSinkFilter->GetSampleSink();
if (sink->AtEOS()) {
// End of stream.
return Finish(S_OK);
}
// Get the next chunk of audio samples. This blocks until the sample
// arrives, or an error occurs (like the stream is shutdown).
RefPtr<IMediaSample> sample;
hr = sink->Extract(sample);
if (FAILED(hr) || hr == S_FALSE) {
return Finish(hr);
}
int64_t start = 0, end = 0;
sample->GetMediaTime(&start, &end);
LOG("DirectShowReader::DecodeAudioData [%4.2lf-%4.2lf]",
RefTimeToSeconds(start),
RefTimeToSeconds(end));
LONG length = sample->GetActualDataLength();
LONG numSamples = length / mBytesPerSample;
LONG numFrames = length / mBytesPerSample / mNumChannels;
BYTE* data = nullptr;
hr = sample->GetPointer(&data);
NS_ENSURE_TRUE(SUCCEEDED(hr), Finish(hr));
mAudioCompactor.Push(mDecoder->GetResource()->Tell(),
RefTimeToUsecs(start),
mInfo.mAudio.mRate,
numFrames,
mNumChannels,
DirectShowCopy(reinterpret_cast<uint8_t *>(data),
mBytesPerSample,
numSamples,
mNumChannels));
return true;
}
bool
DirectShowReader::DecodeVideoFrame(bool& aKeyframeSkip,
const media::TimeUnit& aTimeThreshold)
{
MOZ_ASSERT(OnTaskQueue());
return false;
}
RefPtr<MediaDecoderReader::SeekPromise>
DirectShowReader::Seek(const SeekTarget& aTarget)
{
nsresult res = SeekInternal(aTarget.GetTime().ToMicroseconds());
if (NS_FAILED(res)) {
return SeekPromise::CreateAndReject(res, __func__);
} else {
return SeekPromise::CreateAndResolve(aTarget.GetTime(), __func__);
}
}
nsresult
DirectShowReader::SeekInternal(int64_t aTargetUs)
{
HRESULT hr;
MOZ_ASSERT(OnTaskQueue());
LOG("DirectShowReader::Seek() target=%lld", aTargetUs);
hr = mControl->Pause();
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
nsresult rv = ResetDecode();
NS_ENSURE_SUCCESS(rv, rv);
LONGLONG seekPosition = UsecsToRefTime(aTargetUs);
hr = mMediaSeeking->SetPositions(&seekPosition,
AM_SEEKING_AbsolutePositioning,
nullptr,
AM_SEEKING_NoPositioning);
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
hr = mControl->Run();
NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
return NS_OK;
}
} // namespace mozilla

View File

@ -1,109 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(DirectShowReader_h_)
#define DirectShowReader_h_
#include "windows.h" // HRESULT, DWORD
#include "MediaDecoderReader.h"
#include "MediaResource.h"
#include "mozilla/RefPtr.h"
#include "MP3FrameParser.h"
// Add the graph to the Running Object Table so that we can connect
// to this graph with GraphEdit/GraphStudio. Note: on Vista and up you must
// also regsvr32 proppage.dll from the Windows SDK.
// See: http://msdn.microsoft.com/en-us/library/ms787252(VS.85).aspx
// #define DIRECTSHOW_REGISTER_GRAPH
struct IGraphBuilder;
struct IMediaControl;
struct IMediaSeeking;
namespace mozilla {
class AudioSinkFilter;
class SourceFilter;
// Decoder backend for decoding MP3 using DirectShow. DirectShow operates as
// a filter graph. The basic design of the DirectShowReader is that we have
// a SourceFilter that wraps the MediaResource that connects to the
// MP3 decoder filter. The MP3 decoder filter "pulls" data as it requires it
// downstream on its own thread. When the MP3 decoder has produced a block of
// decoded samples, its thread calls downstream into our AudioSinkFilter,
// passing the decoded buffer in. The AudioSinkFilter inserts the samples into
// a SampleSink object. The SampleSink blocks the MP3 decoder's thread until
// the decode thread calls DecodeAudioData(), whereupon the SampleSink
// releases the decoded samples to the decode thread, and unblocks the MP3
// decoder's thread. The MP3 decoder can then request more data from the
// SourceFilter, and decode more data. If the decode thread calls
// DecodeAudioData() and there's no decoded samples waiting to be extracted
// in the SampleSink, the SampleSink blocks the decode thread until the MP3
// decoder produces a decoded sample.
class DirectShowReader : public MediaDecoderReader
{
public:
explicit DirectShowReader(AbstractMediaDecoder* aDecoder);
virtual ~DirectShowReader();
bool DecodeAudioData() override;
bool DecodeVideoFrame(bool& aKeyframeSkip,
const media::TimeUnit& aTimeThreshold) override;
nsresult ReadMetadata(MediaInfo* aInfo,
MetadataTags** aTags) override;
RefPtr<SeekPromise> Seek(const SeekTarget& aTarget) override;
static const GUID CLSID_MPEG_LAYER_3_DECODER_FILTER;
private:
// Notifies the filter graph that playback is complete. aStatus is
// the code to send to the filter graph. Always returns false, so
// that we can just "return Finish()" from DecodeAudioData().
bool Finish(HRESULT aStatus);
nsresult SeekInternal(int64_t aTime);
// DirectShow filter graph, and associated playback and seeking
// control interfaces.
RefPtr<IGraphBuilder> mGraph;
RefPtr<IMediaControl> mControl;
RefPtr<IMediaSeeking> mMediaSeeking;
// Wraps the MediaResource, and feeds undecoded data into the filter graph.
RefPtr<SourceFilter> mSourceFilter;
// Sits at the end of the graph, removing decoded samples from the graph.
// The graph will block while this is blocked, i.e. it will pause decoding.
RefPtr<AudioSinkFilter> mAudioSinkFilter;
// Some MP3s are variable bitrate, so DirectShow's duration estimation
// can make its duration estimation based on the wrong bitrate. So we parse
// the MP3 frames to get a more accuate estimate of the duration.
MP3FrameParser mMP3FrameParser;
#ifdef DIRECTSHOW_REGISTER_GRAPH
// Used to add/remove the filter graph to the Running Object Table. You can
// connect GraphEdit/GraphStudio to the graph to observe and/or debug its
// topology and state.
DWORD mRotRegister;
#endif
// Number of channels in the audio stream.
uint32_t mNumChannels;
// Samples per second in the audio stream.
uint32_t mAudioRate;
// Number of bytes per sample. Can be either 1 or 2.
uint32_t mBytesPerSample;
};
} // namespace mozilla
#endif

View File

@ -1,369 +0,0 @@
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "DirectShowUtils.h"
#include "dmodshow.h"
#include "wmcodecdsp.h"
#include "dmoreg.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/RefPtr.h"
#include "nsPrintfCString.h"
#define WARN(...) NS_WARNING(nsPrintfCString(__VA_ARGS__).get())
namespace mozilla {
// Create a table which maps GUIDs to a string representation of the GUID.
// This is useful for debugging purposes, for logging the GUIDs of media types.
// This is only available when logging is enabled, i.e. not in release builds.
struct GuidToName {
const char* name;
const GUID guid;
};
#pragma push_macro("OUR_GUID_ENTRY")
#undef OUR_GUID_ENTRY
#define OUR_GUID_ENTRY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
{ #name, {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}} },
static const GuidToName GuidToNameTable[] = {
#include <uuids.h>
};
#pragma pop_macro("OUR_GUID_ENTRY")
const char*
GetDirectShowGuidName(const GUID& aGuid)
{
const size_t len = ArrayLength(GuidToNameTable);
for (unsigned i = 0; i < len; i++) {
if (IsEqualGUID(aGuid, GuidToNameTable[i].guid)) {
return GuidToNameTable[i].name;
}
}
return "Unknown";
}
void
RemoveGraphFromRunningObjectTable(DWORD aRotRegister)
{
RefPtr<IRunningObjectTable> runningObjectTable;
if (SUCCEEDED(GetRunningObjectTable(0, getter_AddRefs(runningObjectTable)))) {
runningObjectTable->Revoke(aRotRegister);
}
}
HRESULT
AddGraphToRunningObjectTable(IUnknown *aUnkGraph, DWORD *aOutRotRegister)
{
HRESULT hr;
RefPtr<IMoniker> moniker;
RefPtr<IRunningObjectTable> runningObjectTable;
hr = GetRunningObjectTable(0, getter_AddRefs(runningObjectTable));
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
const size_t STRING_LENGTH = 256;
WCHAR wsz[STRING_LENGTH];
StringCchPrintfW(wsz,
STRING_LENGTH,
L"FilterGraph %08x pid %08x",
(DWORD_PTR)aUnkGraph,
GetCurrentProcessId());
hr = CreateItemMoniker(L"!", wsz, getter_AddRefs(moniker));
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
hr = runningObjectTable->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE,
aUnkGraph,
moniker,
aOutRotRegister);
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
return S_OK;
}
const char*
GetGraphNotifyString(long evCode)
{
#define CASE(x) case x: return #x
switch(evCode) {
CASE(EC_ACTIVATE); // A video window is being activated or deactivated.
CASE(EC_BANDWIDTHCHANGE); // Not supported.
CASE(EC_BUFFERING_DATA); // The graph is buffering data, or has stopped buffering data.
CASE(EC_BUILT); // Send by the Video Control when a graph has been built. Not forwarded to applications.
CASE(EC_CLOCK_CHANGED); // The reference clock has changed.
CASE(EC_CLOCK_UNSET); // The clock provider was disconnected.
CASE(EC_CODECAPI_EVENT); // Sent by an encoder to signal an encoding event.
CASE(EC_COMPLETE); // All data from a particular stream has been rendered.
CASE(EC_CONTENTPROPERTY_CHANGED); // Not supported.
CASE(EC_DEVICE_LOST); // A Plug and Play device was removed or has become available again.
CASE(EC_DISPLAY_CHANGED); // The display mode has changed.
CASE(EC_END_OF_SEGMENT); // The end of a segment has been reached.
CASE(EC_EOS_SOON); // Not supported.
CASE(EC_ERROR_STILLPLAYING); // An asynchronous command to run the graph has failed.
CASE(EC_ERRORABORT); // An operation was aborted because of an error.
CASE(EC_ERRORABORTEX); // An operation was aborted because of an error.
CASE(EC_EXTDEVICE_MODE_CHANGE); // Not supported.
CASE(EC_FILE_CLOSED); // The source file was closed because of an unexpected event.
CASE(EC_FULLSCREEN_LOST); // The video renderer is switching out of full-screen mode.
CASE(EC_GRAPH_CHANGED); // The filter graph has changed.
CASE(EC_LENGTH_CHANGED); // The length of a source has changed.
CASE(EC_LOADSTATUS); // Notifies the application of progress when opening a network file.
CASE(EC_MARKER_HIT); // Not supported.
CASE(EC_NEED_RESTART); // A filter is requesting that the graph be restarted.
CASE(EC_NEW_PIN); // Not supported.
CASE(EC_NOTIFY_WINDOW); // Notifies a filter of the video renderer's window.
CASE(EC_OLE_EVENT); // A filter is passing a text string to the application.
CASE(EC_OPENING_FILE); // The graph is opening a file, or has finished opening a file.
CASE(EC_PALETTE_CHANGED); // The video palette has changed.
CASE(EC_PAUSED); // A pause request has completed.
CASE(EC_PLEASE_REOPEN); // The source file has changed.
CASE(EC_PREPROCESS_COMPLETE); // Sent by the WM ASF Writer filter when it completes the pre-processing for multipass encoding.
CASE(EC_PROCESSING_LATENCY); // Indicates the amount of time that a component is taking to process each sample.
CASE(EC_QUALITY_CHANGE); // The graph is dropping samples, for quality control.
//CASE(EC_RENDER_FINISHED); // Not supported.
CASE(EC_REPAINT); // A video renderer requires a repaint.
CASE(EC_SAMPLE_LATENCY); // Specifies how far behind schedule a component is for processing samples.
//CASE(EC_SAMPLE_NEEDED); // Requests a new input sample from the Enhanced Video Renderer (EVR) filter.
CASE(EC_SCRUB_TIME); // Specifies the time stamp for the most recent frame step.
CASE(EC_SEGMENT_STARTED); // A new segment has started.
CASE(EC_SHUTTING_DOWN); // The filter graph is shutting down, prior to being destroyed.
CASE(EC_SNDDEV_IN_ERROR); // A device error has occurred in an audio capture filter.
CASE(EC_SNDDEV_OUT_ERROR); // A device error has occurred in an audio renderer filter.
CASE(EC_STARVATION); // A filter is not receiving enough data.
CASE(EC_STATE_CHANGE); // The filter graph has changed state.
CASE(EC_STATUS); // Contains two arbitrary status strings.
CASE(EC_STEP_COMPLETE); // A filter performing frame stepping has stepped the specified number of frames.
CASE(EC_STREAM_CONTROL_STARTED); // A stream-control start command has taken effect.
CASE(EC_STREAM_CONTROL_STOPPED); // A stream-control stop command has taken effect.
CASE(EC_STREAM_ERROR_STILLPLAYING); // An error has occurred in a stream. The stream is still playing.
CASE(EC_STREAM_ERROR_STOPPED); // A stream has stopped because of an error.
CASE(EC_TIMECODE_AVAILABLE); // Not supported.
CASE(EC_UNBUILT); // Send by the Video Control when a graph has been torn down. Not forwarded to applications.
CASE(EC_USERABORT); // The user has terminated playback.
CASE(EC_VIDEO_SIZE_CHANGED); // The native video size has changed.
CASE(EC_VIDEOFRAMEREADY); // A video frame is ready for display.
CASE(EC_VMR_RECONNECTION_FAILED); // Sent by the VMR-7 and the VMR-9 when it was unable to accept a dynamic format change request from the upstream decoder.
CASE(EC_VMR_RENDERDEVICE_SET); // Sent when the VMR has selected its rendering mechanism.
CASE(EC_VMR_SURFACE_FLIPPED); // Sent when the VMR-7's allocator presenter has called the DirectDraw Flip method on the surface being presented.
CASE(EC_WINDOW_DESTROYED); // The video renderer was destroyed or removed from the graph.
CASE(EC_WMT_EVENT); // Sent by the WM ASF Reader filter when it reads ASF files protected by digital rights management (DRM).
CASE(EC_WMT_INDEX_EVENT); // Sent when an application uses the WM ASF Writer to index Windows Media Video files.
CASE(S_OK); // Success.
CASE(VFW_S_AUDIO_NOT_RENDERED); // Partial success; the audio was not rendered.
CASE(VFW_S_DUPLICATE_NAME); // Success; the Filter Graph Manager modified a filter name to avoid duplication.
CASE(VFW_S_PARTIAL_RENDER); // Partial success; some of the streams in this movie are in an unsupported format.
CASE(VFW_S_VIDEO_NOT_RENDERED); // Partial success; the video was not rendered.
CASE(E_ABORT); // Operation aborted.
CASE(E_OUTOFMEMORY); // Insufficient memory.
CASE(E_POINTER); // Null pointer argument.
CASE(VFW_E_CANNOT_CONNECT); // No combination of intermediate filters could be found to make the connection.
CASE(VFW_E_CANNOT_RENDER); // No combination of filters could be found to render the stream.
CASE(VFW_E_NO_ACCEPTABLE_TYPES); // There is no common media type between these pins.
CASE(VFW_E_NOT_IN_GRAPH);
default:
return "Unknown Code";
};
#undef CASE
}
HRESULT
CreateAndAddFilter(IGraphBuilder* aGraph,
REFGUID aFilterClsId,
LPCWSTR aFilterName,
IBaseFilter **aOutFilter)
{
NS_ENSURE_TRUE(aGraph, E_POINTER);
NS_ENSURE_TRUE(aOutFilter, E_POINTER);
HRESULT hr;
RefPtr<IBaseFilter> filter;
hr = CoCreateInstance(aFilterClsId,
nullptr,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
getter_AddRefs(filter));
if (FAILED(hr)) {
// Object probably not available on this system.
WARN("CoCreateInstance failed, hr=%x", hr);
return hr;
}
hr = aGraph->AddFilter(filter, aFilterName);
if (FAILED(hr)) {
WARN("AddFilter failed, hr=%x", hr);
return hr;
}
filter.forget(aOutFilter);
return S_OK;
}
HRESULT
CreateMP3DMOWrapperFilter(IBaseFilter **aOutFilter)
{
NS_ENSURE_TRUE(aOutFilter, E_POINTER);
HRESULT hr;
// Create the wrapper filter.
RefPtr<IBaseFilter> filter;
hr = CoCreateInstance(CLSID_DMOWrapperFilter,
nullptr,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
getter_AddRefs(filter));
if (FAILED(hr)) {
WARN("CoCreateInstance failed, hr=%x", hr);
return hr;
}
// Query for IDMOWrapperFilter.
RefPtr<IDMOWrapperFilter> dmoWrapper;
hr = filter->QueryInterface(IID_IDMOWrapperFilter,
getter_AddRefs(dmoWrapper));
if (FAILED(hr)) {
WARN("QueryInterface failed, hr=%x", hr);
return hr;
}
hr = dmoWrapper->Init(CLSID_CMP3DecMediaObject, DMOCATEGORY_AUDIO_DECODER);
if (FAILED(hr)) {
// Can't instantiate MP3 DMO. It doesn't exist on Windows XP, we're
// probably hitting that. Don't log warning to console, this is an
// expected error.
WARN("dmoWrapper Init failed, hr=%x", hr);
return hr;
}
filter.forget(aOutFilter);
return S_OK;
}
HRESULT
AddMP3DMOWrapperFilter(IGraphBuilder* aGraph,
IBaseFilter **aOutFilter)
{
NS_ENSURE_TRUE(aGraph, E_POINTER);
NS_ENSURE_TRUE(aOutFilter, E_POINTER);
HRESULT hr;
// Create the wrapper filter.
RefPtr<IBaseFilter> filter;
hr = CreateMP3DMOWrapperFilter(getter_AddRefs(filter));
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
// Add the wrapper filter to graph.
hr = aGraph->AddFilter(filter, L"MP3 Decoder DMO");
if (FAILED(hr)) {
WARN("AddFilter failed, hr=%x", hr);
return hr;
}
filter.forget(aOutFilter);
return S_OK;
}
bool
CanDecodeMP3UsingDirectShow()
{
RefPtr<IBaseFilter> filter;
// Can we create the MP3 demuxer filter?
if (FAILED(CoCreateInstance(CLSID_MPEG1Splitter,
nullptr,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
getter_AddRefs(filter)))) {
return false;
}
// Can we create either the WinXP MP3 decoder filter or the MP3 DMO decoder?
if (FAILED(CoCreateInstance(DirectShowReader::CLSID_MPEG_LAYER_3_DECODER_FILTER,
nullptr,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
getter_AddRefs(filter))) &&
FAILED(CreateMP3DMOWrapperFilter(getter_AddRefs(filter)))) {
return false;
}
// Else, we can create all of the components we need. Assume
// DirectShow is going to work...
return true;
}
// Match a pin by pin direction and connection state.
HRESULT
MatchUnconnectedPin(IPin* aPin,
PIN_DIRECTION aPinDir,
bool *aOutMatches)
{
NS_ENSURE_TRUE(aPin, E_POINTER);
NS_ENSURE_TRUE(aOutMatches, E_POINTER);
// Ensure the pin is unconnected.
RefPtr<IPin> peer;
HRESULT hr = aPin->ConnectedTo(getter_AddRefs(peer));
if (hr != VFW_E_NOT_CONNECTED) {
*aOutMatches = false;
return hr;
}
// Ensure the pin is of the specified direction.
PIN_DIRECTION pinDir;
hr = aPin->QueryDirection(&pinDir);
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
*aOutMatches = (pinDir == aPinDir);
return S_OK;
}
// Return the first unconnected input pin or output pin.
already_AddRefed<IPin>
GetUnconnectedPin(IBaseFilter* aFilter, PIN_DIRECTION aPinDir)
{
RefPtr<IEnumPins> enumPins;
HRESULT hr = aFilter->EnumPins(getter_AddRefs(enumPins));
NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
// Test each pin to see if it matches the direction we're looking for.
RefPtr<IPin> pin;
while (S_OK == enumPins->Next(1, getter_AddRefs(pin), nullptr)) {
bool matches = FALSE;
if (SUCCEEDED(MatchUnconnectedPin(pin, aPinDir, &matches)) &&
matches) {
return pin.forget();
}
}
return nullptr;
}
HRESULT
ConnectFilters(IGraphBuilder* aGraph,
IBaseFilter* aOutputFilter,
IBaseFilter* aInputFilter)
{
RefPtr<IPin> output = GetUnconnectedPin(aOutputFilter, PINDIR_OUTPUT);
NS_ENSURE_TRUE(output, E_FAIL);
RefPtr<IPin> input = GetUnconnectedPin(aInputFilter, PINDIR_INPUT);
NS_ENSURE_TRUE(output, E_FAIL);
return aGraph->Connect(output, input);
}
} // namespace mozilla
// avoid redefined macro in unified build
#undef WARN

View File

@ -1,125 +0,0 @@
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 _DirectShowUtils_h_
#define _DirectShowUtils_h_
#include <stdint.h>
#include "dshow.h"
// XXXbz windowsx.h defines GetFirstChild, GetNextSibling,
// GetPrevSibling are macros, apparently... Eeevil. We have functions
// called that on some classes, so undef them.
#undef GetFirstChild
#undef GetNextSibling
#undef GetPrevSibling
#include "DShowTools.h"
#include "mozilla/Logging.h"
namespace mozilla {
// Win32 "Event" wrapper. Must be paired with a CriticalSection to create a
// Java-style "monitor".
class Signal {
public:
explicit Signal(CriticalSection* aLock)
: mLock(aLock)
{
CriticalSectionAutoEnter lock(*mLock);
mEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
}
~Signal() {
CriticalSectionAutoEnter lock(*mLock);
CloseHandle(mEvent);
}
// Lock must be held.
void Notify() {
SetEvent(mEvent);
}
// Lock must be held. Check the wait condition before waiting!
HRESULT Wait() {
mLock->Leave();
DWORD result = WaitForSingleObject(mEvent, INFINITE);
mLock->Enter();
return result == WAIT_OBJECT_0 ? S_OK : E_FAIL;
}
private:
CriticalSection* mLock;
HANDLE mEvent;
};
HRESULT
AddGraphToRunningObjectTable(IUnknown *aUnkGraph, DWORD *aOutRotRegister);
void
RemoveGraphFromRunningObjectTable(DWORD aRotRegister);
const char*
GetGraphNotifyString(long evCode);
// Creates a filter and adds it to a graph.
HRESULT
CreateAndAddFilter(IGraphBuilder* aGraph,
REFGUID aFilterClsId,
LPCWSTR aFilterName,
IBaseFilter **aOutFilter);
HRESULT
AddMP3DMOWrapperFilter(IGraphBuilder* aGraph,
IBaseFilter **aOutFilter);
// Connects the output pin on aOutputFilter to an input pin on
// aInputFilter, in aGraph.
HRESULT
ConnectFilters(IGraphBuilder* aGraph,
IBaseFilter* aOutputFilter,
IBaseFilter* aInputFilter);
HRESULT
MatchUnconnectedPin(IPin* aPin,
PIN_DIRECTION aPinDir,
bool *aOutMatches);
// Converts from microseconds to DirectShow "Reference Time"
// (hundreds of nanoseconds).
inline int64_t
UsecsToRefTime(const int64_t aUsecs)
{
return aUsecs * 10;
}
// Converts from DirectShow "Reference Time" (hundreds of nanoseconds)
// to microseconds.
inline int64_t
RefTimeToUsecs(const int64_t hRefTime)
{
return hRefTime / 10;
}
// Converts from DirectShow "Reference Time" (hundreds of nanoseconds)
// to seconds.
inline double
RefTimeToSeconds(const REFERENCE_TIME aRefTime)
{
return double(aRefTime) / 10000000;
}
const char*
GetDirectShowGuidName(const GUID& aGuid);
// Returns true if we can instantiate an MP3 demuxer and decoder filters.
// Use this to detect whether MP3 support is installed.
bool
CanDecodeMP3UsingDirectShow();
} // namespace mozilla
#endif

View File

@ -1,159 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "SampleSink.h"
#include "AudioSinkFilter.h"
#include "AudioSinkInputPin.h"
#include "VideoUtils.h"
#include "mozilla/Logging.h"
using namespace mozilla::media;
namespace mozilla {
static LazyLogModule gDirectShowLog("DirectShowDecoder");
#define LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
SampleSink::SampleSink()
: mMonitor("SampleSink"),
mIsFlushing(false),
mAtEOS(false)
{
MOZ_COUNT_CTOR(SampleSink);
}
SampleSink::~SampleSink()
{
MOZ_COUNT_DTOR(SampleSink);
}
void
SampleSink::SetAudioFormat(const WAVEFORMATEX* aInFormat)
{
NS_ENSURE_TRUE(aInFormat, );
ReentrantMonitorAutoEnter mon(mMonitor);
memcpy(&mAudioFormat, aInFormat, sizeof(WAVEFORMATEX));
}
void
SampleSink::GetAudioFormat(WAVEFORMATEX* aOutFormat)
{
MOZ_ASSERT(aOutFormat);
ReentrantMonitorAutoEnter mon(mMonitor);
memcpy(aOutFormat, &mAudioFormat, sizeof(WAVEFORMATEX));
}
HRESULT
SampleSink::Receive(IMediaSample* aSample)
{
ReentrantMonitorAutoEnter mon(mMonitor);
while (true) {
if (mIsFlushing) {
return S_FALSE;
}
if (!mSample) {
break;
}
if (mAtEOS) {
return E_UNEXPECTED;
}
// Wait until the consumer thread consumes the sample.
mon.Wait();
}
if (MOZ_LOG_TEST(gDirectShowLog, LogLevel::Debug)) {
REFERENCE_TIME start = 0, end = 0;
HRESULT hr = aSample->GetMediaTime(&start, &end);
LOG("SampleSink::Receive() [%4.2lf-%4.2lf]",
(double)RefTimeToUsecs(start) / USECS_PER_S,
(double)RefTimeToUsecs(end) / USECS_PER_S);
}
mSample = aSample;
// Notify the signal, to awaken the consumer thread in WaitForSample()
// if necessary.
mon.NotifyAll();
return S_OK;
}
HRESULT
SampleSink::Extract(RefPtr<IMediaSample>& aOutSample)
{
ReentrantMonitorAutoEnter mon(mMonitor);
// Loop until we have a sample, or we should abort.
while (true) {
if (mIsFlushing) {
return S_FALSE;
}
if (mSample) {
break;
}
if (mAtEOS) {
// Order is important here, if we have a sample, we should return it
// before reporting EOS.
return E_UNEXPECTED;
}
// Wait until the producer thread gives us a sample.
mon.Wait();
}
aOutSample = mSample;
if (MOZ_LOG_TEST(gDirectShowLog, LogLevel::Debug)) {
int64_t start = 0, end = 0;
mSample->GetMediaTime(&start, &end);
LOG("SampleSink::Extract() [%4.2lf-%4.2lf]",
(double)RefTimeToUsecs(start) / USECS_PER_S,
(double)RefTimeToUsecs(end) / USECS_PER_S);
}
mSample = nullptr;
// Notify the signal, to awaken the producer thread in Receive()
// if necessary.
mon.NotifyAll();
return S_OK;
}
void
SampleSink::Flush()
{
LOG("SampleSink::Flush()");
ReentrantMonitorAutoEnter mon(mMonitor);
mIsFlushing = true;
mSample = nullptr;
mon.NotifyAll();
}
void
SampleSink::Reset()
{
LOG("SampleSink::Reset()");
ReentrantMonitorAutoEnter mon(mMonitor);
mIsFlushing = false;
mAtEOS = false;
}
void
SampleSink::SetEOS()
{
LOG("SampleSink::SetEOS()");
ReentrantMonitorAutoEnter mon(mMonitor);
mAtEOS = true;
// Notify to unblock any threads waiting for samples in
// Extract() or Receive(). Now that we're at EOS, no more samples
// will come!
mon.NotifyAll();
}
bool
SampleSink::AtEOS()
{
ReentrantMonitorAutoEnter mon(mMonitor);
return mAtEOS && !mSample;
}
} // namespace mozilla

View File

@ -1,67 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(SampleSink_h_)
#define SampleSink_h_
#include "BaseFilter.h"
#include "DirectShowUtils.h"
#include "mozilla/RefPtr.h"
#include "mozilla/ReentrantMonitor.h"
namespace mozilla {
class SampleSink {
public:
SampleSink();
virtual ~SampleSink();
// Sets the audio format of the incoming samples. The upstream filter
// calls this. This makes a copy.
void SetAudioFormat(const WAVEFORMATEX* aInFormat);
// Copies the format of incoming audio samples into into *aOutFormat.
void GetAudioFormat(WAVEFORMATEX* aOutFormat);
// Called when a sample is delivered by the DirectShow graph to the sink.
// The decode thread retrieves the sample by calling WaitForSample().
// Blocks if there's already a sample waiting to be consumed by the decode
// thread.
HRESULT Receive(IMediaSample* aSample);
// Retrieves a sample from the sample queue, blocking until one becomes
// available, or until an error occurs. Returns S_FALSE on EOS.
HRESULT Extract(RefPtr<IMediaSample>& aOutSample);
// Unblocks any threads waiting in GetSample().
// Clears mSample, which unblocks upstream stream.
void Flush();
// Opens up the sink to receive more samples in PutSample().
// Clears EOS flag.
void Reset();
// Marks that we've reacehd the end of stream.
void SetEOS();
// Returns whether we're at end of stream.
bool AtEOS();
private:
// All data in this class is syncronized by mMonitor.
ReentrantMonitor mMonitor;
RefPtr<IMediaSample> mSample;
// Format of the audio stream we're receiving.
WAVEFORMATEX mAudioFormat;
bool mIsFlushing;
bool mAtEOS;
};
} // namespace mozilla
#endif // SampleSink_h_

View File

@ -1,683 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "SourceFilter.h"
#include "MediaResource.h"
#include "mozilla/RefPtr.h"
#include "DirectShowUtils.h"
#include "MP3FrameParser.h"
#include "mozilla/Logging.h"
#include <algorithm>
using namespace mozilla::media;
namespace mozilla {
// Define to trace what's on...
//#define DEBUG_SOURCE_TRACE 1
#if defined (DEBUG_SOURCE_TRACE)
static LazyLogModule gDirectShowLog("DirectShowDecoder");
#define DIRECTSHOW_LOG(...) MOZ_LOG(gDirectShowLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
#else
#define DIRECTSHOW_LOG(...)
#endif
static HRESULT
DoGetInterface(IUnknown* aUnknown, void** aInterface)
{
if (!aInterface)
return E_POINTER;
*aInterface = aUnknown;
aUnknown->AddRef();
return S_OK;
}
// Stores details of IAsyncReader::Request().
class ReadRequest {
public:
ReadRequest(IMediaSample* aSample,
DWORD_PTR aDwUser,
uint32_t aOffset,
uint32_t aCount)
: mSample(aSample),
mDwUser(aDwUser),
mOffset(aOffset),
mCount(aCount)
{
MOZ_COUNT_CTOR(ReadRequest);
}
~ReadRequest() {
MOZ_COUNT_DTOR(ReadRequest);
}
RefPtr<IMediaSample> mSample;
DWORD_PTR mDwUser;
uint32_t mOffset;
uint32_t mCount;
};
// A wrapper around media resource that presents only a partition of the
// underlying resource to the caller to use. The partition returned is from
// an offset to the end of stream, and this object deals with ensuring
// the offsets and lengths etc are translated from the reduced partition
// exposed to the caller, to the absolute offsets of the underlying stream.
class MediaResourcePartition {
public:
MediaResourcePartition(MediaResource* aResource,
int64_t aDataStart)
: mResource(aResource),
mDataOffset(aDataStart)
{}
int64_t GetLength() {
int64_t len = mResource.GetLength();
if (len == -1) {
return len;
}
return std::max<int64_t>(0, len - mDataOffset);
}
nsresult ReadAt(int64_t aOffset, char* aBuffer,
uint32_t aCount, uint32_t* aBytes)
{
return mResource.ReadAt(aOffset + mDataOffset,
aBuffer,
aCount,
aBytes);
}
int64_t GetCachedDataEnd() {
int64_t tell = mResource.GetResource()->Tell();
int64_t dataEnd =
mResource.GetResource()->GetCachedDataEnd(tell) - mDataOffset;
return dataEnd;
}
private:
// MediaResource from which we read data.
MediaResourceIndex mResource;
int64_t mDataOffset;
};
// Output pin for SourceFilter, which implements IAsyncReader, to
// allow downstream filters to pull/read data from it. Downstream pins
// register to read data using Request(), and asynchronously wait for the
// reads to complete using WaitForNext(). They may also synchronously read
// using SyncRead(). This class is a delegate (tear off) of
// SourceFilter.
//
// We can expose only a segment of the MediaResource to the filter graph.
// This is used to strip off the ID3v2 tags from the stream, as DirectShow
// has trouble parsing some headers.
//
// Implements:
// * IAsyncReader
// * IPin
// * IQualityControl
// * IUnknown
//
class DECLSPEC_UUID("18e5cfb2-1015-440c-a65c-e63853235894")
OutputPin : public IAsyncReader,
public BasePin
{
public:
OutputPin(MediaResource* aMediaResource,
SourceFilter* aParent,
CriticalSection& aFilterLock,
int64_t aMP3DataStart);
virtual ~OutputPin();
// IUnknown
// Defer to ref counting to BasePin, which defers to owning nsBaseFilter.
STDMETHODIMP_(ULONG) AddRef() override { return BasePin::AddRef(); }
STDMETHODIMP_(ULONG) Release() override { return BasePin::Release(); }
STDMETHODIMP QueryInterface(REFIID iid, void** ppv) override;
// BasePin Overrides.
// Determines if the pin accepts a specific media type.
HRESULT CheckMediaType(const MediaType* aMediaType) override;
// Retrieves a preferred media type, by index value.
HRESULT GetMediaType(int aPosition, MediaType* aMediaType) override;
// Releases the pin from a connection.
HRESULT BreakConnect(void) override;
// Determines whether a pin connection is suitable.
HRESULT CheckConnect(IPin* aPin) override;
// IAsyncReader overrides
// The RequestAllocator method requests an allocator during the
// pin connection.
STDMETHODIMP RequestAllocator(IMemAllocator* aPreferred,
ALLOCATOR_PROPERTIES* aProps,
IMemAllocator** aActual) override;
// The Request method queues an asynchronous request for data. Downstream
// will call WaitForNext() when they want to retrieve the result.
STDMETHODIMP Request(IMediaSample* aSample, DWORD_PTR aUserData) override;
// The WaitForNext method waits for the next pending read request
// to complete. This method fails if the graph is flushing.
// Defers to SyncRead/5.
STDMETHODIMP WaitForNext(DWORD aTimeout,
IMediaSample** aSamples,
DWORD_PTR* aUserData) override;
// The SyncReadAligned method performs a synchronous read. The method
// blocks until the request is completed. Defers to SyncRead/5. This
// method does not fail if the graph is flushing.
STDMETHODIMP SyncReadAligned(IMediaSample* aSample) override;
// The SyncRead method performs a synchronous read. The method blocks
// until the request is completed. Defers to SyncRead/5. This
// method does not fail if the graph is flushing.
STDMETHODIMP SyncRead(LONGLONG aPosition, LONG aLength, BYTE* aBuffer) override;
// The Length method retrieves the total length of the stream.
STDMETHODIMP Length(LONGLONG* aTotal, LONGLONG* aAvailable) override;
// IPin Overrides
STDMETHODIMP BeginFlush(void) override;
STDMETHODIMP EndFlush(void) override;
uint32_t GetAndResetBytesConsumedCount();
private:
// Protects thread-shared data/structures (mFlushCount, mPendingReads).
// WaitForNext() also waits on this monitor
CriticalSection& mPinLock;
// Signal used with mPinLock to implement WaitForNext().
Signal mSignal;
// The filter that owns us. Weak reference, as we're a delegate (tear off).
SourceFilter* mParentSource;
MediaResourcePartition mResource;
// Counter, inc'd in BeginFlush(), dec'd in EndFlush(). Calls to this can
// come from multiple threads and can interleave, hence the counter.
int32_t mFlushCount;
// Number of bytes that have been read from the output pin since the last
// time GetAndResetBytesConsumedCount() was called.
uint32_t mBytesConsumed;
// Deque of ReadRequest* for reads that are yet to be serviced.
// nsReadRequest's are stored on the heap, popper must delete them.
nsDeque mPendingReads;
// Flags if the downstream pin has QI'd for IAsyncReader. We refuse
// connection if they don't query, as it means they're assuming that we're
// a push filter, and we're not.
bool mQueriedForAsyncReader;
};
// For mingw __uuidof support
#ifdef __CRT_UUID_DECL
}
__CRT_UUID_DECL(mozilla::OutputPin, 0x18e5cfb2,0x1015,0x440c,0xa6,0x5c,0xe6,0x38,0x53,0x23,0x58,0x94);
namespace mozilla {
#endif
OutputPin::OutputPin(MediaResource* aResource,
SourceFilter* aParent,
CriticalSection& aFilterLock,
int64_t aMP3DataStart)
: BasePin(static_cast<BaseFilter*>(aParent),
&aFilterLock,
L"MozillaOutputPin",
PINDIR_OUTPUT),
mPinLock(aFilterLock),
mSignal(&mPinLock),
mParentSource(aParent),
mResource(aResource, aMP3DataStart),
mFlushCount(0),
mBytesConsumed(0),
mQueriedForAsyncReader(false)
{
MOZ_COUNT_CTOR(OutputPin);
DIRECTSHOW_LOG("OutputPin::OutputPin()");
}
OutputPin::~OutputPin()
{
MOZ_COUNT_DTOR(OutputPin);
DIRECTSHOW_LOG("OutputPin::~OutputPin()");
}
HRESULT
OutputPin::BreakConnect()
{
mQueriedForAsyncReader = false;
return BasePin::BreakConnect();
}
STDMETHODIMP
OutputPin::QueryInterface(REFIID aIId, void** aInterface)
{
if (aIId == IID_IAsyncReader) {
mQueriedForAsyncReader = true;
return DoGetInterface(static_cast<IAsyncReader*>(this), aInterface);
}
if (aIId == __uuidof(OutputPin)) {
AddRef();
*aInterface = this;
return S_OK;
}
return BasePin::QueryInterface(aIId, aInterface);
}
HRESULT
OutputPin::CheckConnect(IPin* aPin)
{
// Our connection is only suitable if the downstream pin knows
// that we're asynchronous (i.e. it queried for IAsyncReader).
return mQueriedForAsyncReader ? S_OK : S_FALSE;
}
HRESULT
OutputPin::CheckMediaType(const MediaType* aMediaType)
{
const MediaType *myMediaType = mParentSource->GetMediaType();
if (IsEqualGUID(aMediaType->majortype, myMediaType->majortype) &&
IsEqualGUID(aMediaType->subtype, myMediaType->subtype) &&
IsEqualGUID(aMediaType->formattype, myMediaType->formattype))
{
DIRECTSHOW_LOG("OutputPin::CheckMediaType() Match: major=%s minor=%s TC=%d FSS=%d SS=%u",
GetDirectShowGuidName(aMediaType->majortype),
GetDirectShowGuidName(aMediaType->subtype),
aMediaType->TemporalCompression(),
aMediaType->bFixedSizeSamples,
aMediaType->SampleSize());
return S_OK;
}
DIRECTSHOW_LOG("OutputPin::CheckMediaType() Failed to match: major=%s minor=%s TC=%d FSS=%d SS=%u",
GetDirectShowGuidName(aMediaType->majortype),
GetDirectShowGuidName(aMediaType->subtype),
aMediaType->TemporalCompression(),
aMediaType->bFixedSizeSamples,
aMediaType->SampleSize());
return S_FALSE;
}
HRESULT
OutputPin::GetMediaType(int aPosition, MediaType* aMediaType)
{
if (!aMediaType)
return E_POINTER;
if (aPosition == 0) {
aMediaType->Assign(mParentSource->GetMediaType());
return S_OK;
}
return VFW_S_NO_MORE_ITEMS;
}
static inline bool
IsPowerOf2(int32_t x) {
return ((-x & x) != x);
}
STDMETHODIMP
OutputPin::RequestAllocator(IMemAllocator* aPreferred,
ALLOCATOR_PROPERTIES* aProps,
IMemAllocator** aActual)
{
// Require the downstream pin to suggest what they want...
if (!aPreferred) return E_POINTER;
if (!aProps) return E_POINTER;
if (!aActual) return E_POINTER;
// We only care about alignment - our allocator will reject anything
// which isn't power-of-2 aligned, so so try a 4-byte aligned allocator.
ALLOCATOR_PROPERTIES props;
memcpy(&props, aProps, sizeof(ALLOCATOR_PROPERTIES));
if (aProps->cbAlign == 0 || IsPowerOf2(aProps->cbAlign)) {
props.cbAlign = 4;
}
// Limit allocator's number of buffers. We know that the media will most
// likely be bound by network speed, not by decoding speed. We also
// store the incoming data in a Gecko stream, if we don't limit buffers
// here we'll end up duplicating a lot of storage. We must have enough
// space for audio key frames to fit in the first batch of buffers however,
// else pausing may fail for some downstream decoders.
if (props.cBuffers > BaseFilter::sMaxNumBuffers) {
props.cBuffers = BaseFilter::sMaxNumBuffers;
}
// The allocator properties that are actually used. We don't store
// this, we need it for SetProperties() below to succeed.
ALLOCATOR_PROPERTIES actualProps;
HRESULT hr;
if (aPreferred) {
// Play nice and prefer the downstream pin's preferred allocator.
hr = aPreferred->SetProperties(&props, &actualProps);
if (SUCCEEDED(hr)) {
aPreferred->AddRef();
*aActual = aPreferred;
return S_OK;
}
}
// Else downstream hasn't requested a specific allocator, so create one...
// Just create a default allocator. It's highly unlikely that we'll use
// this anyway, as most parsers insist on using their own allocators.
RefPtr<IMemAllocator> allocator;
hr = CoCreateInstance(CLSID_MemoryAllocator,
0,
CLSCTX_INPROC_SERVER,
IID_IMemAllocator,
getter_AddRefs(allocator));
if(FAILED(hr) || (allocator == nullptr)) {
NS_WARNING("Can't create our own DirectShow allocator.");
return hr;
}
// See if we can make it suitable
hr = allocator->SetProperties(&props, &actualProps);
if (SUCCEEDED(hr)) {
// We need to release our refcount on pAlloc, and addref
// it to pass a refcount to the caller - this is a net nothing.
allocator.forget(aActual);
return S_OK;
}
NS_WARNING("Failed to pick an allocator");
return hr;
}
STDMETHODIMP
OutputPin::Request(IMediaSample* aSample, DWORD_PTR aDwUser)
{
if (!aSample) return E_FAIL;
CriticalSectionAutoEnter lock(*mLock);
NS_ASSERTION(!mFlushCount, "Request() while flushing");
if (mFlushCount)
return VFW_E_WRONG_STATE;
REFERENCE_TIME refStart = 0, refEnd = 0;
if (FAILED(aSample->GetTime(&refStart, &refEnd))) {
NS_WARNING("Sample incorrectly timestamped");
return VFW_E_SAMPLE_TIME_NOT_SET;
}
// Convert reference time to bytes.
uint32_t start = (uint32_t)(refStart / 10000000);
uint32_t end = (uint32_t)(refEnd / 10000000);
uint32_t numBytes = end - start;
ReadRequest* request = new ReadRequest(aSample,
aDwUser,
start,
numBytes);
// Memory for |request| is free when it's popped from the completed
// reads list.
// Push this onto the queue of reads to be serviced.
mPendingReads.Push(request);
// Notify any threads blocked in WaitForNext() which are waiting for mPendingReads
// to become non-empty.
mSignal.Notify();
return S_OK;
}
STDMETHODIMP
OutputPin::WaitForNext(DWORD aTimeout,
IMediaSample** aOutSample,
DWORD_PTR* aOutDwUser)
{
NS_ASSERTION(aTimeout == 0 || aTimeout == INFINITE,
"Oops, we don't handle this!");
*aOutSample = nullptr;
*aOutDwUser = 0;
LONGLONG offset = 0;
LONG count = 0;
BYTE* buf = nullptr;
{
CriticalSectionAutoEnter lock(*mLock);
// Wait until there's a pending read to service.
while (aTimeout && mPendingReads.GetSize() == 0 && !mFlushCount) {
// Note: No need to guard against shutdown-during-wait here, as
// typically the thread doing the pull will have already called
// Request(), so we won't Wait() here anyway. SyncRead() will fail
// on shutdown.
mSignal.Wait();
}
nsAutoPtr<ReadRequest> request(reinterpret_cast<ReadRequest*>(mPendingReads.PopFront()));
if (!request)
return VFW_E_WRONG_STATE;
*aOutSample = request->mSample;
*aOutDwUser = request->mDwUser;
offset = request->mOffset;
count = request->mCount;
buf = nullptr;
request->mSample->GetPointer(&buf);
NS_ASSERTION(buf != nullptr, "Invalid buffer!");
if (mFlushCount) {
return VFW_E_TIMEOUT;
}
}
return SyncRead(offset, count, buf);
}
STDMETHODIMP
OutputPin::SyncReadAligned(IMediaSample* aSample)
{
{
// Ignore reads while flushing.
CriticalSectionAutoEnter lock(*mLock);
if (mFlushCount) {
return S_FALSE;
}
}
if (!aSample)
return E_FAIL;
REFERENCE_TIME lStart = 0, lEnd = 0;
if (FAILED(aSample->GetTime(&lStart, &lEnd))) {
NS_WARNING("Sample incorrectly timestamped");
return VFW_E_SAMPLE_TIME_NOT_SET;
}
// Convert reference time to bytes.
int32_t start = (int32_t)(lStart / 10000000);
int32_t end = (int32_t)(lEnd / 10000000);
int32_t numBytes = end - start;
// If the range extends off the end of stream, truncate to the end of stream
// as per IAsyncReader specificiation.
int64_t streamLength = mResource.GetLength();
if (streamLength != -1) {
// We know the exact length of the stream, fail if the requested offset
// is beyond it.
if (start > streamLength) {
return VFW_E_BADALIGN;
}
// If the end of the chunk to read is off the end of the stream,
// truncate it to the end of the stream.
if ((start + numBytes) > streamLength) {
numBytes = (uint32_t)(streamLength - start);
}
}
BYTE* buf=0;
aSample->GetPointer(&buf);
return SyncRead(start, numBytes, buf);
}
STDMETHODIMP
OutputPin::SyncRead(LONGLONG aPosition,
LONG aLength,
BYTE* aBuffer)
{
MOZ_ASSERT(!NS_IsMainThread());
NS_ENSURE_TRUE(aPosition >= 0, E_FAIL);
NS_ENSURE_TRUE(aLength > 0, E_FAIL);
NS_ENSURE_TRUE(aBuffer, E_POINTER);
DIRECTSHOW_LOG("OutputPin::SyncRead(%lld, %d)", aPosition, aLength);
{
// Ignore reads while flushing.
CriticalSectionAutoEnter lock(*mLock);
if (mFlushCount) {
return S_FALSE;
}
}
uint32_t totalBytesRead = 0;
nsresult rv = mResource.ReadAt(aPosition,
reinterpret_cast<char*>(aBuffer),
aLength,
&totalBytesRead);
if (NS_FAILED(rv)) {
return E_FAIL;
}
if (totalBytesRead > 0) {
CriticalSectionAutoEnter lock(*mLock);
mBytesConsumed += totalBytesRead;
}
return (totalBytesRead == aLength) ? S_OK : S_FALSE;
}
STDMETHODIMP
OutputPin::Length(LONGLONG* aTotal, LONGLONG* aAvailable)
{
HRESULT hr = S_OK;
int64_t length = mResource.GetLength();
if (length == -1) {
hr = VFW_S_ESTIMATED;
// Don't have a length. Just lie, it seems to work...
*aTotal = INT32_MAX;
} else {
*aTotal = length;
}
if (aAvailable) {
*aAvailable = mResource.GetCachedDataEnd();
}
DIRECTSHOW_LOG("OutputPin::Length() len=%lld avail=%lld", *aTotal, *aAvailable);
return hr;
}
STDMETHODIMP
OutputPin::BeginFlush()
{
CriticalSectionAutoEnter lock(*mLock);
mFlushCount++;
mSignal.Notify();
return S_OK;
}
STDMETHODIMP
OutputPin::EndFlush(void)
{
CriticalSectionAutoEnter lock(*mLock);
mFlushCount--;
return S_OK;
}
uint32_t
OutputPin::GetAndResetBytesConsumedCount()
{
CriticalSectionAutoEnter lock(*mLock);
uint32_t bytesConsumed = mBytesConsumed;
mBytesConsumed = 0;
return bytesConsumed;
}
SourceFilter::SourceFilter(const GUID& aMajorType,
const GUID& aSubType)
: BaseFilter(L"MozillaDirectShowSource", __uuidof(SourceFilter))
{
MOZ_COUNT_CTOR(SourceFilter);
mMediaType.majortype = aMajorType;
mMediaType.subtype = aSubType;
DIRECTSHOW_LOG("SourceFilter Constructor(%s, %s)",
GetDirectShowGuidName(aMajorType),
GetDirectShowGuidName(aSubType));
}
SourceFilter::~SourceFilter()
{
MOZ_COUNT_DTOR(SourceFilter);
DIRECTSHOW_LOG("SourceFilter Destructor()");
}
BasePin*
SourceFilter::GetPin(int n)
{
if (n == 0) {
NS_ASSERTION(mOutputPin != 0, "GetPin with no pin!");
return static_cast<BasePin*>(mOutputPin);
} else {
return nullptr;
}
}
// Get's the media type we're supplying.
const MediaType*
SourceFilter::GetMediaType() const
{
return &mMediaType;
}
nsresult
SourceFilter::Init(MediaResource* aResource, int64_t aMP3Offset)
{
DIRECTSHOW_LOG("SourceFilter::Init()");
mOutputPin = new OutputPin(aResource,
this,
mLock,
aMP3Offset);
NS_ENSURE_TRUE(mOutputPin != nullptr, NS_ERROR_FAILURE);
return NS_OK;
}
uint32_t
SourceFilter::GetAndResetBytesConsumedCount()
{
return mOutputPin->GetAndResetBytesConsumedCount();
}
} // namespace mozilla

View File

@ -1,75 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#if !defined(nsDirectShowSource_h___)
#define nsDirectShowSource_h___
#include "BaseFilter.h"
#include "BasePin.h"
#include "MediaType.h"
#include "nsDeque.h"
#include "nsAutoPtr.h"
#include "DirectShowUtils.h"
#include "mozilla/RefPtr.h"
namespace mozilla {
class MediaResource;
class OutputPin;
// SourceFilter is an asynchronous DirectShow source filter which
// reads from an MediaResource, and supplies data via a pull model downstream
// using OutputPin. It us used to supply a generic byte stream into
// DirectShow.
//
// Implements:
// * IBaseFilter
// * IMediaFilter
// * IPersist
// * IUnknown
//
class DECLSPEC_UUID("5c2a7ad0-ba82-4659-9178-c4719a2765d6")
SourceFilter : public media::BaseFilter
{
public:
// Constructs source filter to deliver given media type.
SourceFilter(const GUID& aMajorType, const GUID& aSubType);
~SourceFilter();
nsresult Init(MediaResource *aResource, int64_t aMP3Offset);
// BaseFilter overrides.
// Only one output - the byte stream.
int GetPinCount() override { return 1; }
media::BasePin* GetPin(int n) override;
// Get's the media type we're supplying.
const media::MediaType* GetMediaType() const;
uint32_t GetAndResetBytesConsumedCount();
protected:
// Our async pull output pin.
nsAutoPtr<OutputPin> mOutputPin;
// Type of byte stream we output.
media::MediaType mMediaType;
};
} // namespace mozilla
// For mingw __uuidof support
#ifdef __CRT_UUID_DECL
__CRT_UUID_DECL(mozilla::SourceFilter, 0x5c2a7ad0,0xba82,0x4659,0x91,0x78,0xc4,0x71,0x9a,0x27,0x65,0xd6);
#endif
#endif

View File

@ -1,41 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXPORTS += [
'AudioSinkFilter.h',
'AudioSinkInputPin.h',
'DirectShowDecoder.h',
'DirectShowReader.h',
'DirectShowUtils.h',
]
UNIFIED_SOURCES += [
'DirectShowDecoder.cpp',
'DirectShowUtils.cpp',
'SourceFilter.cpp',
]
SOURCES += [
'AudioSinkFilter.cpp',
'AudioSinkInputPin.cpp',
'DirectShowReader.cpp',
'SampleSink.cpp',
]
# If WebRTC isn't being built, we need to compile the DirectShow base classes so that
# they're available at link time.
if not CONFIG['MOZ_WEBRTC']:
SOURCES += [
'/media/webrtc/trunk/webrtc/modules/video_capture/windows/BaseFilter.cpp',
'/media/webrtc/trunk/webrtc/modules/video_capture/windows/BaseInputPin.cpp',
'/media/webrtc/trunk/webrtc/modules/video_capture/windows/BasePin.cpp',
'/media/webrtc/trunk/webrtc/modules/video_capture/windows/MediaType.cpp',
]
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/media/webrtc/trunk/webrtc/modules/video_capture/windows',
]

View File

@ -50,9 +50,6 @@ DIRS += [
'webvtt',
]
if CONFIG['MOZ_DIRECTSHOW']:
DIRS += ['directshow']
if CONFIG['MOZ_ANDROID_OMX']:
DIRS += ['android']
@ -297,11 +294,6 @@ LOCAL_INCLUDES += [
'/netwerk/base',
]
if CONFIG['MOZ_DIRECTSHOW']:
LOCAL_INCLUDES += [
'/media/webrtc/trunk/webrtc/modules/video_capture/windows',
]
if CONFIG['MOZ_WEBRTC']:
LOCAL_INCLUDES += [
'/media/webrtc/signaling/src/common',

View File

@ -132,14 +132,47 @@ RefPtr<MediaDataDecoder::FlushPromise>
H264Converter::Flush()
{
mDecodePromiseRequest.DisconnectIfExists();
mFlushRequest.DisconnectIfExists();
mShutdownRequest.DisconnectIfExists();
mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
mNeedKeyframe = true;
/*
When we detect a change of content in the H264 stream, we first flush the
current decoder (1), then shut it down (2).
It is possible possible for H264Converter::Flush to be called during any of
those time.
If during (1):
- mFlushRequest and mFlushPromise will not be empty.
- The old decoder can still be used, with the current extradata as stored
in mCurrentConfig.mExtraData.
If during (2):
- mShutdownRequest and mShutdownPromise won't be empty.
- mDecoder is empty.
- The old decoder is no longer referenced by the H264Converter.
*/
if (mFlushPromise) {
// Flush in progress, hijack that one.
mFlushRequest.Disconnect();
return mFlushPromise.forget();
}
if (mDecoder) {
return mDecoder->Flush();
}
return FlushPromise::CreateAndResolve(true, __func__);
if (!mShutdownPromise) {
return FlushPromise::CreateAndResolve(true, __func__);
}
mShutdownRequest.Disconnect();
// Let's continue when the the current shutdown completes.
RefPtr<ShutdownPromise> shutdownPromise = mShutdownPromise.forget();
return shutdownPromise->Then(
AbstractThread::GetCurrent()->AsTaskQueue(),
__func__,
[](bool) { return FlushPromise::CreateAndResolve(true, __func__); },
[](bool) {
MOZ_ASSERT_UNREACHABLE("Shutdown promises are always resolved");
return FlushPromise::CreateAndResolve(true, __func__);
});
}
RefPtr<MediaDataDecoder::DecodePromise>
@ -158,8 +191,11 @@ H264Converter::Shutdown()
mInitPromiseRequest.DisconnectIfExists();
mDecodePromiseRequest.DisconnectIfExists();
mFlushRequest.DisconnectIfExists();
mFlushPromise = nullptr;
mShutdownRequest.DisconnectIfExists();
mPendingSample = nullptr;
mNeedAVCC.reset();
if (mShutdownPromise) {
// We have a shutdown in progress, return that promise instead as we can't
// shutdown a decoder twice.
@ -347,6 +383,8 @@ H264Converter::CheckForSPSChange(MediaRawData* aSample)
}
// This sample doesn't contain inband SPS/PPS
// We now check if the out of band one has changed.
// This scenario can only occur on Android with devices that can recycle a
// decoder.
if (mp4_demuxer::AnnexB::HasSPS(aSample->mExtraData) &&
!mp4_demuxer::AnnexB::CompareExtraData(aSample->mExtraData,
mOriginalExtraData)) {
@ -373,7 +411,8 @@ H264Converter::CheckForSPSChange(MediaRawData* aSample)
// The SPS has changed, signal to flush the current decoder and create a
// new one.
RefPtr<H264Converter> self = this;
mDecoder->Flush()
mFlushPromise = mDecoder->Flush();
mFlushPromise
->Then(AbstractThread::GetCurrent()->AsTaskQueue(),
__func__,
[self, sample, this]() {
@ -385,7 +424,6 @@ H264Converter::CheckForSPSChange(MediaRawData* aSample)
[self, sample, this]() {
mShutdownRequest.Complete();
mShutdownPromise = nullptr;
mNeedAVCC.reset();
nsresult rv = CreateDecoderAndInit(sample);
if (rv == NS_ERROR_DOM_MEDIA_INITIALIZING_DECODER) {
// All good so far, will continue later.
@ -400,6 +438,7 @@ H264Converter::CheckForSPSChange(MediaRawData* aSample)
},
[self, this](const MediaResult& aError) {
mFlushRequest.Complete();
mFlushPromise = nullptr;
mDecodePromise.Reject(aError, __func__);
})
->Track(mFlushRequest);

View File

@ -92,6 +92,7 @@ private:
MozPromiseRequestHolder<DecodePromise> mDecodePromiseRequest;
MozPromiseHolder<DecodePromise> mDecodePromise;
MozPromiseRequestHolder<FlushPromise> mFlushRequest;
RefPtr<FlushPromise> mFlushPromise;
MozPromiseRequestHolder<ShutdownPromise> mShutdownRequest;
RefPtr<ShutdownPromise> mShutdownPromise;

View File

@ -263,10 +263,10 @@ var gPlayTests = [
{ name:"small-shot.mp3", type:"audio/mpeg", duration:0.27 },
{ name:"owl.mp3", type:"audio/mpeg", duration:3.343 },
// owl.mp3 as above, but with something funny going on in the ID3v2 tag
// that causes DirectShow to fail.
// that caused DirectShow to fail.
{ name:"owl-funny-id3.mp3", type:"audio/mpeg", duration:3.343 },
// owl.mp3 as above, but with something even funnier going on in the ID3v2 tag
// that causes DirectShow to fail.
// that caused DirectShow to fail.
{ name:"owl-funnier-id3.mp3", type:"audio/mpeg", duration:3.343 },
// One second of silence with ~140KB of ID3 tags. Usually when the first MP3
// frame is at such a high offset into the file, MP3FrameParser will give up

View File

@ -162,8 +162,7 @@ var haveMp4 = getPref("media.wmf.enabled") ||
check_mp4(document.getElementById('v'), haveMp4);
var haveMp3 = getPref("media.directshow.enabled") ||
getPref("media.wmf.enabled") ||
var haveMp3 = getPref("media.wmf.enabled") ||
(IsLinux() && getPref("media.ffmpeg.enabled")) ||
(IsSupportedAndroid() &&
((IsJellyBeanOrLater() && getPref("media.android-media-codec.enabled")) ||

View File

@ -1939,7 +1939,7 @@ WorkerLoadInfo::SetPrincipalFromChannel(nsIChannel* aChannel)
return SetPrincipalOnMainThread(principal, loadGroup);
}
#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
bool
WorkerLoadInfo::FinalChannelPrincipalIsValid(nsIChannel* aChannel)
{
@ -2027,7 +2027,7 @@ WorkerLoadInfo::PrincipalURIMatchesScriptURL()
return equal;
}
#endif // defined(DEBUG) || !defined(RELEASE_OR_BETA)
#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
bool
WorkerLoadInfo::ProxyReleaseMainThreadObjects(WorkerPrivate* aWorkerPrivate)
@ -3922,7 +3922,7 @@ WorkerPrivateParent<Derived>::SetPrincipalFromChannel(nsIChannel* aChannel)
return mLoadInfo.SetPrincipalFromChannel(aChannel);
}
#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
template <class Derived>
bool
WorkerPrivateParent<Derived>::FinalChannelPrincipalIsValid(nsIChannel* aChannel)
@ -4071,7 +4071,7 @@ WorkerPrivateParent<Derived>::AssertInnerWindowIsCorrect() const
#endif
#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
template <class Derived>
bool
WorkerPrivateParent<Derived>::PrincipalIsValid() const

View File

@ -2967,12 +2967,7 @@ HTMLEditor::EnableStyleSheet(const nsAString& aURL,
nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
sheet->SetAssociatedDocument(doc, StyleSheet::NotOwnedByDocument);
if (sheet->IsServo()) {
// XXXheycam ServoStyleSheets don't support being enabled/disabled yet.
NS_ERROR("stylo: ServoStyleSheets can't be disabled yet");
return NS_ERROR_FAILURE;
}
return sheet->AsGecko()->SetDisabled(!aEnable);
return sheet->SetDisabled(!aEnable);
}
bool

View File

@ -207,7 +207,7 @@ struct writeBuf
this->offset = 0;
}
~writeBuf() {
delete this->data;
delete[] this->data;
}
template <class T>

View File

@ -235,6 +235,26 @@ if CONFIG['CLANG_CXX'] or CONFIG['CLANG_CL']:
'-Wno-error=uninitialized',
]
if CONFIG['_MSC_VER'] and not CONFIG['CLANG_CL']:
CFLAGS += [
'-wd4005', # 'WIN32_LEAN_AND_MEAN' : macro redefinition
'-wd4018', # '>' : signed/unsigned mismatch
'-wd4047', # different levels of indirection
'-wd4101', # unreferenced local variable
'-wd4133', # 'function' : incompatible types
'-wd4146', # unary minus operator applied to unsigned type
'-wd4311', # 'variable' : pointer truncation from 'type' to 'type'
'-wd4477', # format string '%s' requires an argument of type 'type'
'-wd4996', # The compiler encountered a deprecated declaration.
]
CXXFLAGS += [
'-wd4005', # 'WIN32_LEAN_AND_MEAN' : macro redefinition
'-wd4018', # '>' : signed/unsigned mismatch
'-wd4146', # unary minus operator applied to unsigned type
'-wd4828', # illegal in the current source character set
'-wd4838', # requires a narrowing conversion
]
# See bug 386897.
if CONFIG['GNU_CC'] and CONFIG['OS_TARGET'] == 'Android' and CONFIG['MOZ_OPTIMIZE']:
CFLAGS += ['-O2']

View File

@ -154,6 +154,14 @@ if CONFIG['CLANG_CL']:
CFLAGS += [
'-Wno-unused-variable',
]
if CONFIG['_MSC_VER'] and not CONFIG['CLANG_CL']:
CFLAGS += [
'-wd4047', # different levels of indirection
'-wd4101', # unreferenced local variable
'-wd4133', # 'function' : incompatible types
'-wd4146', # unary minus operator applied to unsigned type
'-wd4311', # 'variable' : pointer truncation from 'type' to 'type'
]
# See bug 386897.
if CONFIG['OS_TARGET'] == 'Android' and CONFIG['MOZ_OPTIMIZE']:

View File

@ -751,6 +751,17 @@ ServoRestyleManager::ContentStateChanged(nsIContent* aContent,
PostRestyleEvent(aElement, restyleHint, changeHint);
}
static inline bool
AttributeInfluencesOtherPseudoClassState(Element* aElement, nsIAtom* aAttribute)
{
// We must record some state for :-moz-browser-frame and
// :-moz-table-border-nonzero.
return (aAttribute == nsGkAtoms::mozbrowser &&
aElement->IsAnyOfHTMLElements(nsGkAtoms::iframe, nsGkAtoms::frame)) ||
(aAttribute == nsGkAtoms::border &&
aElement->IsHTMLElement(nsGkAtoms::table));
}
void
ServoRestyleManager::AttributeWillChange(Element* aElement,
int32_t aNameSpaceID,
@ -766,6 +777,10 @@ ServoRestyleManager::AttributeWillChange(Element* aElement,
ServoElementSnapshot& snapshot = SnapshotFor(aElement);
snapshot.AddAttrs(aElement);
if (AttributeInfluencesOtherPseudoClassState(aElement, aAttribute)) {
snapshot.AddOtherPseudoClassState(aElement);
}
if (Element* parent = aElement->GetFlattenedTreeParentElementForStyle()) {
parent->NoteDirtyDescendantsForServo();
}

View File

@ -153,7 +153,6 @@ using namespace mozilla::gfx;
#define GRID_ENABLED_PREF_NAME "layout.css.grid.enabled"
#define GRID_TEMPLATE_SUBGRID_ENABLED_PREF_NAME "layout.css.grid-template-subgrid-value.enabled"
#define WEBKIT_PREFIXES_ENABLED_PREF_NAME "layout.css.prefixes.webkit"
#define DISPLAY_FLOW_ROOT_ENABLED_PREF_NAME "layout.css.display-flow-root.enabled"
#define TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME "layout.css.text-align-unsafe-value.enabled"
#define FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME "layout.css.float-logical-values.enabled"
@ -320,36 +319,6 @@ WebkitPrefixEnabledPrefChangeCallback(const char* aPrefName, void* aClosure)
}
}
// When the pref "layout.css.display-flow-root.enabled" changes, this function is
// invoked to let us update kDisplayKTable, to selectively disable or restore
// the entries for "flow-root" in that table.
static void
DisplayFlowRootEnabledPrefChangeCallback(const char* aPrefName, void* aClosure)
{
NS_ASSERTION(strcmp(aPrefName, DISPLAY_FLOW_ROOT_ENABLED_PREF_NAME) == 0,
"Did you misspell " DISPLAY_FLOW_ROOT_ENABLED_PREF_NAME " ?");
static bool sIsDisplayFlowRootKeywordIndexInitialized;
static int32_t sIndexOfFlowRootInDisplayTable;
bool isDisplayFlowRootEnabled =
Preferences::GetBool(DISPLAY_FLOW_ROOT_ENABLED_PREF_NAME, false);
if (!sIsDisplayFlowRootKeywordIndexInitialized) {
// First run: find the position of "flow-root" in kDisplayKTable.
sIndexOfFlowRootInDisplayTable =
nsCSSProps::FindIndexOfKeyword(eCSSKeyword_flow_root,
nsCSSProps::kDisplayKTable);
sIsDisplayFlowRootKeywordIndexInitialized = true;
}
// OK -- now, stomp on or restore the "flow-root" entry in kDisplayKTable,
// depending on whether the pref is enabled vs. disabled.
if (sIndexOfFlowRootInDisplayTable >= 0) {
nsCSSProps::kDisplayKTable[sIndexOfFlowRootInDisplayTable].mKeyword =
isDisplayFlowRootEnabled ? eCSSKeyword_flow_root : eCSSKeyword_UNKNOWN;
}
}
// When the pref "layout.css.text-align-unsafe-value.enabled" changes, this
// function is called to let us update kTextAlignKTable & kTextAlignLastKTable,
// to selectively disable or restore the entries for "unsafe" in those tables.
@ -7780,8 +7749,6 @@ static const PrefCallbacks kPrefCallbacks[] = {
WebkitPrefixEnabledPrefChangeCallback },
{ TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME,
TextAlignUnsafeEnabledPrefChangeCallback },
{ DISPLAY_FLOW_ROOT_ENABLED_PREF_NAME,
DisplayFlowRootEnabledPrefChangeCallback },
{ FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME,
FloatLogicalValuesEnabledPrefChangeCallback },
};

Some files were not shown because too many files have changed in this diff Show More