mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 04:15:43 +00:00
Merge autoland to central, a=merge CLOSED TREE
MozReview-Commit-ID: HLPLchv8Lt7
This commit is contained in:
commit
b7e2a83ab4
@ -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$
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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";
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -43,7 +43,6 @@ var ParentUtils = {
|
||||
},
|
||||
};
|
||||
|
||||
ParentUtils.cleanUpAddress();
|
||||
Services.obs.addObserver(ParentUtils, "formautofill-storage-changed");
|
||||
|
||||
addMessageListener("FormAutofillTest:AddAddress", (msg) => {
|
||||
|
@ -5,5 +5,5 @@ support-files =
|
||||
formautofill_common.js
|
||||
formautofill_parent_utils.js
|
||||
|
||||
[test_autofocus_form.html]
|
||||
[test_basic_autocomplete_form.html]
|
||||
|
||||
|
@ -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>
|
@ -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"]);
|
||||
|
@ -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>
|
||||
|
@ -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'
|
||||
]
|
||||
|
@ -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": "تنويه الخصوصية"
|
||||
}
|
||||
}
|
@ -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."
|
||||
},
|
||||
|
@ -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."
|
||||
},
|
||||
|
@ -64,6 +64,9 @@
|
||||
"selfScreenshotErrorTitle": {
|
||||
"message": "You can’t take a shot of a Firefox Screenshots page!"
|
||||
},
|
||||
"emptySelectionErrorTitle": {
|
||||
"message": "Your selection is too small"
|
||||
},
|
||||
"genericErrorTitle": {
|
||||
"message": "Whoa! Firefox Screenshots went haywire."
|
||||
},
|
||||
|
@ -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."
|
||||
},
|
||||
|
@ -64,6 +64,9 @@
|
||||
"selfScreenshotErrorTitle": {
|
||||
"message": "Vous ne pouvez pas effectuer une capture d’écran d’une page Firefox Screenshots."
|
||||
},
|
||||
"emptySelectionErrorTitle": {
|
||||
"message": "La zone sélectionnée est trop petite"
|
||||
},
|
||||
"genericErrorTitle": {
|
||||
"message": "Firefox Screenshots semble avoir un problème."
|
||||
},
|
||||
|
@ -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."
|
||||
},
|
||||
|
@ -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."
|
||||
},
|
||||
|
@ -64,6 +64,9 @@
|
||||
"selfScreenshotErrorTitle": {
|
||||
"message": "Non è possibile salvare uno screenshot di una pagina di Firefox Screenshots"
|
||||
},
|
||||
"emptySelectionErrorTitle": {
|
||||
"message": "L’area selezionata è troppo piccola"
|
||||
},
|
||||
"genericErrorTitle": {
|
||||
"message": "Wow! Firefox Screenshots è andato in tilt"
|
||||
},
|
||||
|
@ -64,6 +64,9 @@
|
||||
"selfScreenshotErrorTitle": {
|
||||
"message": "Firefox Screenshots ページのショットは撮れません。"
|
||||
},
|
||||
"emptySelectionErrorTitle": {
|
||||
"message": "選択範囲が小さすぎます"
|
||||
},
|
||||
"genericErrorTitle": {
|
||||
"message": "Firefox Screenshots に問題が発生しました。"
|
||||
},
|
||||
|
@ -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."
|
||||
|
@ -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ť."
|
||||
},
|
||||
|
@ -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."
|
||||
},
|
||||
|
@ -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 је пошашавио."
|
||||
},
|
||||
|
@ -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."
|
||||
},
|
||||
|
@ -11,6 +11,9 @@
|
||||
"saveScreenshotSelectedArea": {
|
||||
"message": "భద్రపరచు"
|
||||
},
|
||||
"saveScreenshotVisibleArea": {
|
||||
"message": "కనిపించే దానిని బద్రపరచండి"
|
||||
},
|
||||
"saveScreenshotFullPage": {
|
||||
"message": "పూర్తి పేజీని భద్రపరచు"
|
||||
},
|
||||
@ -23,9 +26,15 @@
|
||||
"notificationLinkCopiedTitle": {
|
||||
"message": "లంకె కాపీ అయింది"
|
||||
},
|
||||
"requestErrorTitle": {
|
||||
"message": "పని చెయుట లేదు."
|
||||
},
|
||||
"requestErrorDetails": {
|
||||
"message": "క్షమిచండి! మీ తెరను భద్రపరచలేకపోయాం. దయచేసి కాసేపాగి మళ్ళీ ప్రయత్నించండి."
|
||||
},
|
||||
"tourHeaderTwo": {
|
||||
"message": ""
|
||||
},
|
||||
"tourHeaderThree": {
|
||||
"message": "మీకు నచ్చినట్టుగా"
|
||||
},
|
||||
|
@ -64,6 +64,9 @@
|
||||
"selfScreenshotErrorTitle": {
|
||||
"message": "Ви не можете зробити знімок сторінки Firefox Screenshots!"
|
||||
},
|
||||
"emptySelectionErrorTitle": {
|
||||
"message": "Обрана область є замалою"
|
||||
},
|
||||
"genericErrorTitle": {
|
||||
"message": "Оу! З Firefox Screenshots щось негаразд."
|
||||
},
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -493,6 +493,9 @@ class AbstractShot {
|
||||
}
|
||||
delete this._clips[name];
|
||||
}
|
||||
delAllClips() {
|
||||
this._clips = {};
|
||||
}
|
||||
biggestClipSortOrder() {
|
||||
let biggest = 0;
|
||||
for (let clipId in this._clips) {
|
||||
|
@ -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": {
|
||||
|
@ -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();
|
||||
|
@ -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: {
|
||||
|
@ -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,
|
||||
|
@ -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() {
|
||||
|
@ -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"] {
|
||||
|
@ -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 |
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"/>
|
||||
|
@ -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]
|
||||
|
@ -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();
|
||||
});
|
@ -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});
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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_
|
@ -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
|
||||
|
@ -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_
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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_
|
@ -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
|
@ -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
|
@ -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',
|
||||
]
|
@ -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',
|
||||
|
@ -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);
|
||||
|
@ -92,6 +92,7 @@ private:
|
||||
MozPromiseRequestHolder<DecodePromise> mDecodePromiseRequest;
|
||||
MozPromiseHolder<DecodePromise> mDecodePromise;
|
||||
MozPromiseRequestHolder<FlushPromise> mFlushRequest;
|
||||
RefPtr<FlushPromise> mFlushPromise;
|
||||
MozPromiseRequestHolder<ShutdownPromise> mShutdownRequest;
|
||||
RefPtr<ShutdownPromise> mShutdownPromise;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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")) ||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -207,7 +207,7 @@ struct writeBuf
|
||||
this->offset = 0;
|
||||
}
|
||||
~writeBuf() {
|
||||
delete this->data;
|
||||
delete[] this->data;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
|
@ -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']
|
||||
|
@ -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']:
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user