зеркало из https://github.com/mozilla/gecko-dev.git
merge mozilla-central to mozilla-inbound. r=merge a=merge on a CLOSED TREE
This commit is contained in:
Коммит
4bf9d6e663
|
@ -102,3 +102,47 @@ RootAccessibleWrap::DocumentActivated(DocAccessible* aDocument)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
RootAccessibleWrap::accNavigate(
|
||||
/* [in] */ long navDir,
|
||||
/* [optional][in] */ VARIANT varStart,
|
||||
/* [retval][out] */ VARIANT __RPC_FAR *pvarEndUpAt)
|
||||
{
|
||||
// Special handling for NAVRELATION_EMBEDS.
|
||||
// When we only have a single process, this can be handled the same way as
|
||||
// any other relation.
|
||||
// However, for multi process, the normal relation mechanism doesn't work
|
||||
// because it can't handle remote objects.
|
||||
if (navDir != NAVRELATION_EMBEDS ||
|
||||
varStart.vt != VT_I4 || varStart.lVal != CHILDID_SELF) {
|
||||
// We only handle EMBEDS on the root here.
|
||||
// Forward to the base implementation.
|
||||
return DocAccessibleWrap::accNavigate(navDir, varStart, pvarEndUpAt);
|
||||
}
|
||||
|
||||
if (!pvarEndUpAt) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
Accessible* target = nullptr;
|
||||
// Get the document in the active tab.
|
||||
ProxyAccessible* docProxy = GetPrimaryRemoteTopLevelContentDoc();
|
||||
if (docProxy) {
|
||||
target = WrapperFor(docProxy);
|
||||
} else {
|
||||
// The base implementation could handle this, but we may as well
|
||||
// just handle it here.
|
||||
Relation rel = RelationByType(RelationType::EMBEDS);
|
||||
target = rel.Next();
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
VariantInit(pvarEndUpAt);
|
||||
pvarEndUpAt->pdispVal = NativeAccessible(target);
|
||||
pvarEndUpAt->vt = VT_DISPATCH;
|
||||
return S_OK;
|
||||
}
|
||||
|
|
|
@ -38,6 +38,11 @@ public:
|
|||
*/
|
||||
already_AddRefed<IUnknown> GetInternalUnknown();
|
||||
|
||||
virtual /* [id] */ HRESULT STDMETHODCALLTYPE accNavigate(
|
||||
/* [in] */ long navDir,
|
||||
/* [optional][in] */ VARIANT varStart,
|
||||
/* [retval][out] */ VARIANT __RPC_FAR *pvarEndUpAt) override;
|
||||
|
||||
private:
|
||||
// DECLARE_AGGREGATABLE declares the internal IUnknown methods as well as
|
||||
// mInternalUnknown.
|
||||
|
|
|
@ -1464,6 +1464,7 @@ var gBrowserInit = {
|
|||
|
||||
BrowserOffline.init();
|
||||
IndexedDBPromptHelper.init();
|
||||
CanvasPermissionPromptHelper.init();
|
||||
|
||||
if (AppConstants.E10S_TESTING_ONLY)
|
||||
gRemoteTabsUI.init();
|
||||
|
@ -1897,6 +1898,7 @@ var gBrowserInit = {
|
|||
}
|
||||
BrowserOffline.uninit();
|
||||
IndexedDBPromptHelper.uninit();
|
||||
CanvasPermissionPromptHelper.uninit();
|
||||
PanelUI.uninit();
|
||||
AutoShowBookmarksToolbar.uninit();
|
||||
}
|
||||
|
@ -5717,17 +5719,29 @@ var gUIDensity = {
|
|||
mode = this.getCurrentDensity().mode;
|
||||
}
|
||||
|
||||
let doc = document.documentElement;
|
||||
switch (mode) {
|
||||
case this.MODE_COMPACT:
|
||||
doc.setAttribute("uidensity", "compact");
|
||||
break;
|
||||
case this.MODE_TOUCH:
|
||||
doc.setAttribute("uidensity", "touch");
|
||||
break;
|
||||
default:
|
||||
doc.removeAttribute("uidensity");
|
||||
break;
|
||||
let docs = [
|
||||
document.documentElement,
|
||||
SidebarUI.browser.contentDocument.documentElement,
|
||||
];
|
||||
for (let doc of docs) {
|
||||
switch (mode) {
|
||||
case this.MODE_COMPACT:
|
||||
doc.setAttribute("uidensity", "compact");
|
||||
break;
|
||||
case this.MODE_TOUCH:
|
||||
doc.setAttribute("uidensity", "touch");
|
||||
break;
|
||||
default:
|
||||
doc.removeAttribute("uidensity");
|
||||
break;
|
||||
}
|
||||
}
|
||||
let tree = SidebarUI.browser.contentDocument.querySelector(".sidebar-placesTree");
|
||||
if (tree) {
|
||||
// Tree items don't update their styles without changing some property on the
|
||||
// parent tree element, like background-color or border. See bug 1407399.
|
||||
tree.style.border = "1px";
|
||||
tree.style.border = "";
|
||||
}
|
||||
|
||||
TabsInTitlebar.updateAppearance(true);
|
||||
|
@ -6669,6 +6683,82 @@ var IndexedDBPromptHelper = {
|
|||
}
|
||||
};
|
||||
|
||||
var CanvasPermissionPromptHelper = {
|
||||
_permissionsPrompt: "canvas-permissions-prompt",
|
||||
_notificationIcon: "canvas-notification-icon",
|
||||
|
||||
init() {
|
||||
Services.obs.addObserver(this, this._permissionsPrompt);
|
||||
},
|
||||
|
||||
uninit() {
|
||||
Services.obs.removeObserver(this, this._permissionsPrompt);
|
||||
},
|
||||
|
||||
// aSubject is an nsIBrowser (e10s) or an nsIDOMWindow (non-e10s).
|
||||
// aData is an URL string.
|
||||
observe(aSubject, aTopic, aData) {
|
||||
if (aTopic != this._permissionsPrompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
let browser;
|
||||
if (aSubject instanceof Ci.nsIDOMWindow) {
|
||||
let contentWindow = aSubject.QueryInterface(Ci.nsIDOMWindow);
|
||||
browser = gBrowser.getBrowserForContentWindow(contentWindow);
|
||||
} else {
|
||||
browser = aSubject.QueryInterface(Ci.nsIBrowser);
|
||||
}
|
||||
|
||||
let uri = Services.io.newURI(aData);
|
||||
if (gBrowser.selectedBrowser !== browser) {
|
||||
// Must belong to some other window.
|
||||
return;
|
||||
}
|
||||
|
||||
let message = gNavigatorBundle.getFormattedString("canvas.siteprompt", [ uri.asciiHost ]);
|
||||
|
||||
function setCanvasPermission(aURI, aPerm, aPersistent) {
|
||||
Services.perms.add(aURI, "canvas/extractData", aPerm,
|
||||
aPersistent ? Ci.nsIPermissionManager.EXPIRE_NEVER
|
||||
: Ci.nsIPermissionManager.EXPIRE_SESSION);
|
||||
}
|
||||
|
||||
let mainAction = {
|
||||
label: gNavigatorBundle.getString("canvas.allow"),
|
||||
accessKey: gNavigatorBundle.getString("canvas.allow.accesskey"),
|
||||
callback(state) {
|
||||
setCanvasPermission(uri, Ci.nsIPermissionManager.ALLOW_ACTION,
|
||||
state && state.checkboxChecked);
|
||||
}
|
||||
};
|
||||
|
||||
let secondaryActions = [{
|
||||
label: gNavigatorBundle.getString("canvas.notAllow"),
|
||||
accessKey: gNavigatorBundle.getString("canvas.notAllow.accesskey"),
|
||||
callback(state) {
|
||||
setCanvasPermission(uri, Ci.nsIPermissionManager.DENY_ACTION,
|
||||
state && state.checkboxChecked);
|
||||
}
|
||||
}];
|
||||
|
||||
let checkbox = {
|
||||
// In PB mode, we don't want the "always remember" checkbox
|
||||
show: !PrivateBrowsingUtils.isWindowPrivate(window)
|
||||
};
|
||||
if (checkbox.show) {
|
||||
checkbox.checked = true;
|
||||
checkbox.label = gBrowserBundle.GetStringFromName("canvas.remember");
|
||||
}
|
||||
|
||||
let options = {
|
||||
checkbox
|
||||
};
|
||||
PopupNotifications.show(browser, aTopic, message, this._notificationIcon,
|
||||
mainAction, secondaryActions, options);
|
||||
}
|
||||
};
|
||||
|
||||
function CanCloseWindow() {
|
||||
// Avoid redundant calls to canClose from showing multiple
|
||||
// PermitUnload dialogs.
|
||||
|
|
|
@ -823,6 +823,8 @@
|
|||
tooltiptext="&urlbar.geolocationNotificationAnchor.tooltip;"/>
|
||||
<image id="addons-notification-icon" class="notification-anchor-icon install-icon" role="button"
|
||||
tooltiptext="&urlbar.addonsNotificationAnchor.tooltip;"/>
|
||||
<image id="canvas-notification-icon" class="notification-anchor-icon" role="button"
|
||||
tooltiptext="&urlbar.canvasNotificationAnchor.tooltip;"/>
|
||||
<image id="indexedDB-notification-icon" class="notification-anchor-icon indexedDB-icon" role="button"
|
||||
tooltiptext="&urlbar.indexedDBNotificationAnchor.tooltip;"/>
|
||||
<image id="password-notification-icon" class="notification-anchor-icon login-icon" role="button"
|
||||
|
|
|
@ -3,6 +3,7 @@ support-files=
|
|||
head.js
|
||||
permissions.html
|
||||
|
||||
[browser_canvas_fingerprinting_resistance.js]
|
||||
[browser_permissions.js]
|
||||
[browser_temporary_permissions.js]
|
||||
support-files =
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/**
|
||||
* When "privacy.resistFingerprinting" is set to true, user permission is
|
||||
* required for canvas data extraction.
|
||||
* This tests whether the site permission prompt for canvas data extraction
|
||||
* works properly.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const kUrl = "https://example.com/";
|
||||
const kPrincipal = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
|
||||
.getService(Ci.nsIScriptSecurityManager)
|
||||
.createCodebasePrincipal(Services.io.newURI(kUrl), {});
|
||||
const kPermission = "canvas/extractData";
|
||||
|
||||
function initTab() {
|
||||
let contentWindow = content.wrappedJSObject;
|
||||
|
||||
let drawCanvas = (fillStyle, id) => {
|
||||
let contentDocument = contentWindow.document;
|
||||
let width = 64, height = 64;
|
||||
let canvas = contentDocument.createElement("canvas");
|
||||
if (id) {
|
||||
canvas.setAttribute("id", id);
|
||||
}
|
||||
canvas.setAttribute("width", width);
|
||||
canvas.setAttribute("height", height);
|
||||
contentDocument.body.appendChild(canvas);
|
||||
|
||||
let context = canvas.getContext("2d");
|
||||
context.fillStyle = fillStyle;
|
||||
context.fillRect(0, 0, width, height);
|
||||
|
||||
return canvas;
|
||||
};
|
||||
|
||||
let placeholder = drawCanvas("white");
|
||||
contentWindow.kPlaceholderData = placeholder.toDataURL();
|
||||
let canvas = drawCanvas("cyan", "canvas-id-canvas");
|
||||
isnot(canvas.toDataURL(), contentWindow.kPlaceholderData,
|
||||
"privacy.resistFingerprinting = false, canvas data != placeholder data");
|
||||
}
|
||||
|
||||
function enableResistFingerprinting() {
|
||||
return SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["privacy.resistFingerprinting", true]
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function promisePopupShown() {
|
||||
return BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown")
|
||||
}
|
||||
|
||||
function promisePopupHidden() {
|
||||
return BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden")
|
||||
}
|
||||
|
||||
function extractCanvasData(grantPermission) {
|
||||
let contentWindow = content.wrappedJSObject;
|
||||
let canvas = contentWindow.document.getElementById("canvas-id-canvas");
|
||||
let canvasData = canvas.toDataURL();
|
||||
if (grantPermission) {
|
||||
isnot(canvasData, contentWindow.kPlaceholderData,
|
||||
"privacy.resistFingerprinting = true, permission granted, canvas data != placeholderdata");
|
||||
} else if (grantPermission === false) {
|
||||
is(canvasData, contentWindow.kPlaceholderData,
|
||||
"privacy.resistFingerprinting = true, permission denied, canvas data == placeholderdata");
|
||||
} else {
|
||||
is(canvasData, contentWindow.kPlaceholderData,
|
||||
"privacy.resistFingerprinting = true, requesting permission, canvas data == placeholderdata");
|
||||
}
|
||||
}
|
||||
|
||||
function triggerCommand(button) {
|
||||
let notifications = PopupNotifications.panel.childNodes;
|
||||
let notification = notifications[0];
|
||||
EventUtils.synthesizeMouseAtCenter(notification[button], {});
|
||||
}
|
||||
|
||||
function triggerMainCommand() {
|
||||
triggerCommand("button");
|
||||
}
|
||||
|
||||
function triggerSecondaryCommand() {
|
||||
triggerCommand("secondaryButton");
|
||||
}
|
||||
|
||||
function testPermission() {
|
||||
return Services.perms.testPermissionFromPrincipal(kPrincipal, kPermission);
|
||||
}
|
||||
|
||||
async function withNewTab(grantPermission, browser) {
|
||||
await ContentTask.spawn(browser, null, initTab);
|
||||
await enableResistFingerprinting();
|
||||
let popupShown = promisePopupShown();
|
||||
await ContentTask.spawn(browser, null, extractCanvasData);
|
||||
await popupShown;
|
||||
let popupHidden = promisePopupHidden();
|
||||
if (grantPermission) {
|
||||
triggerMainCommand();
|
||||
await popupHidden;
|
||||
is(testPermission(), Services.perms.ALLOW_ACTION, "permission granted");
|
||||
} else {
|
||||
triggerSecondaryCommand();
|
||||
await popupHidden;
|
||||
is(testPermission(), Services.perms.DENY_ACTION, "permission denied");
|
||||
}
|
||||
await ContentTask.spawn(browser, grantPermission, extractCanvasData);
|
||||
await SpecialPowers.popPrefEnv();
|
||||
}
|
||||
|
||||
async function doTest(grantPermission) {
|
||||
Services.perms.removeFromPrincipal(kPrincipal, kPermission);
|
||||
await BrowserTestUtils.withNewTab(kUrl, withNewTab.bind(null, grantPermission));
|
||||
}
|
||||
|
||||
// Tests clicking "Don't Allow" button of the permission prompt.
|
||||
add_task(doTest.bind(null, false));
|
||||
|
||||
// Tests clicking "Allow" button of the permission prompt.
|
||||
add_task(doTest.bind(null, true));
|
|
@ -3,6 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource://gre/modules/AppConstants.jsm");
|
||||
window.top.gUIDensity.update();
|
||||
|
||||
var SidebarUtils = {
|
||||
handleTreeClick: function SU_handleTreeClick(aTree, aEvent, aGutterSelect) {
|
||||
|
|
|
@ -4,59 +4,8 @@
|
|||
* maximum values.
|
||||
*/
|
||||
|
||||
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
const TEST_DOMAIN = "http://example.net/";
|
||||
const TEST_PATH = TEST_DOMAIN + "browser/browser/components/resistfingerprinting/test/browser/";
|
||||
|
||||
let gMaxAvailWidth;
|
||||
let gMaxAvailHeight;
|
||||
|
||||
// We need the chrome UI size of popup windows for testing outerWidth/Height.
|
||||
let gPopupChromeUIWidth;
|
||||
let gPopupChromeUIHeight;
|
||||
|
||||
const TESTCASES = [
|
||||
{ settingWidth: 1025, settingHeight: 1050, targetWidth: 1000, targetHeight: 1000 },
|
||||
{ settingWidth: 9999, settingHeight: 9999, targetWidth: 1000, targetHeight: 1000 },
|
||||
{ settingWidth: 999, settingHeight: 999, targetWidth: 1000, targetHeight: 1000 },
|
||||
];
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({"set":
|
||||
[["privacy.resistFingerprinting", true]]
|
||||
});
|
||||
|
||||
// Calculate the popup window's chrome UI size for tests of outerWidth/Height.
|
||||
let popUpChromeUISize = await calcPopUpWindowChromeUISize();
|
||||
|
||||
gPopupChromeUIWidth = popUpChromeUISize.chromeWidth;
|
||||
gPopupChromeUIHeight = popUpChromeUISize.chromeHeight;
|
||||
|
||||
// Calculate the maximum available size.
|
||||
let maxAvailSize = await calcMaximumAvailSize(gPopupChromeUIWidth,
|
||||
gPopupChromeUIHeight);
|
||||
|
||||
gMaxAvailWidth = maxAvailSize.maxAvailWidth;
|
||||
gMaxAvailHeight = maxAvailSize.maxAvailHeight;
|
||||
});
|
||||
|
||||
add_task(async function test_window_open() {
|
||||
// Open a tab to test window.open().
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser, TEST_PATH + "file_dummy.html");
|
||||
|
||||
for (let test of TESTCASES) {
|
||||
// Test 'width' and 'height' of window features.
|
||||
await testWindowOpen(tab.linkedBrowser, test.settingWidth, test.settingHeight,
|
||||
test.targetWidth, test.targetHeight, false, gMaxAvailWidth,
|
||||
gMaxAvailHeight, gPopupChromeUIWidth, gPopupChromeUIHeight);
|
||||
|
||||
// test 'outerWidth' and 'outerHeight' of window features.
|
||||
await testWindowOpen(tab.linkedBrowser, test.settingWidth, test.settingHeight,
|
||||
test.targetWidth, test.targetHeight, true, gMaxAvailWidth,
|
||||
gMaxAvailHeight, gPopupChromeUIWidth, gPopupChromeUIHeight);
|
||||
}
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
OpenTest.run([
|
||||
{settingWidth: 1025, settingHeight: 1050, targetWidth: 1000, targetHeight: 1000},
|
||||
{settingWidth: 9999, settingHeight: 9999, targetWidth: 1000, targetHeight: 1000},
|
||||
{settingWidth: 999, settingHeight: 999, targetWidth: 1000, targetHeight: 1000}
|
||||
]);
|
||||
|
|
|
@ -4,59 +4,8 @@
|
|||
* middle values.
|
||||
*/
|
||||
|
||||
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
const TEST_DOMAIN = "http://example.net/";
|
||||
const TEST_PATH = TEST_DOMAIN + "browser/browser/components/resistfingerprinting/test/browser/";
|
||||
|
||||
let gMaxAvailWidth;
|
||||
let gMaxAvailHeight;
|
||||
|
||||
// We need the chrome UI size of popup windows for testing outerWidth/Height.
|
||||
let gPopupChromeUIWidth;
|
||||
let gPopupChromeUIHeight;
|
||||
|
||||
const TESTCASES = [
|
||||
{ settingWidth: 600, settingHeight: 600, targetWidth: 600, targetHeight: 600 },
|
||||
{ settingWidth: 599, settingHeight: 599, targetWidth: 600, targetHeight: 600 },
|
||||
{ settingWidth: 401, settingHeight: 501, targetWidth: 600, targetHeight: 600 },
|
||||
];
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({"set":
|
||||
[["privacy.resistFingerprinting", true]]
|
||||
});
|
||||
|
||||
// Calculate the popup window's chrome UI size for tests of outerWidth/Height.
|
||||
let popUpChromeUISize = await calcPopUpWindowChromeUISize();
|
||||
|
||||
gPopupChromeUIWidth = popUpChromeUISize.chromeWidth;
|
||||
gPopupChromeUIHeight = popUpChromeUISize.chromeHeight;
|
||||
|
||||
// Calculate the maximum available size.
|
||||
let maxAvailSize = await calcMaximumAvailSize(gPopupChromeUIWidth,
|
||||
gPopupChromeUIHeight);
|
||||
|
||||
gMaxAvailWidth = maxAvailSize.maxAvailWidth;
|
||||
gMaxAvailHeight = maxAvailSize.maxAvailHeight;
|
||||
});
|
||||
|
||||
add_task(async function test_window_open() {
|
||||
// Open a tab to test window.open().
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser, TEST_PATH + "file_dummy.html");
|
||||
|
||||
for (let test of TESTCASES) {
|
||||
// Test 'width' and 'height' of window features.
|
||||
await testWindowOpen(tab.linkedBrowser, test.settingWidth, test.settingHeight,
|
||||
test.targetWidth, test.targetHeight, false, gMaxAvailWidth,
|
||||
gMaxAvailHeight, gPopupChromeUIWidth, gPopupChromeUIHeight);
|
||||
|
||||
// test 'outerWidth' and 'outerHeight' of window features.
|
||||
await testWindowOpen(tab.linkedBrowser, test.settingWidth, test.settingHeight,
|
||||
test.targetWidth, test.targetHeight, true, gMaxAvailWidth,
|
||||
gMaxAvailHeight, gPopupChromeUIWidth, gPopupChromeUIHeight);
|
||||
}
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
OpenTest.run([
|
||||
{settingWidth: 600, settingHeight: 600, targetWidth: 600, targetHeight: 600},
|
||||
{settingWidth: 599, settingHeight: 599, targetWidth: 600, targetHeight: 600},
|
||||
{settingWidth: 401, settingHeight: 501, targetWidth: 600, targetHeight: 600}
|
||||
]);
|
||||
|
|
|
@ -4,58 +4,7 @@
|
|||
* minimum values.
|
||||
*/
|
||||
|
||||
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
const TEST_DOMAIN = "http://example.net/";
|
||||
const TEST_PATH = TEST_DOMAIN + "browser/browser/components/resistfingerprinting/test/browser/";
|
||||
|
||||
let gMaxAvailWidth;
|
||||
let gMaxAvailHeight;
|
||||
|
||||
// We need the chrome UI size of popup windows for testing outerWidth/Height.
|
||||
let gPopupChromeUIWidth;
|
||||
let gPopupChromeUIHeight;
|
||||
|
||||
const TESTCASES = [
|
||||
{ settingWidth: 199, settingHeight: 99, targetWidth: 200, targetHeight: 100 },
|
||||
{ settingWidth: 10, settingHeight: 10, targetWidth: 200, targetHeight: 100 },
|
||||
];
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({"set":
|
||||
[["privacy.resistFingerprinting", true]]
|
||||
});
|
||||
|
||||
// Calculate the popup window's chrome UI size for tests of outerWidth/Height.
|
||||
let popUpChromeUISize = await calcPopUpWindowChromeUISize();
|
||||
|
||||
gPopupChromeUIWidth = popUpChromeUISize.chromeWidth;
|
||||
gPopupChromeUIHeight = popUpChromeUISize.chromeHeight;
|
||||
|
||||
// Calculate the maximum available size.
|
||||
let maxAvailSize = await calcMaximumAvailSize(gPopupChromeUIWidth,
|
||||
gPopupChromeUIHeight);
|
||||
|
||||
gMaxAvailWidth = maxAvailSize.maxAvailWidth;
|
||||
gMaxAvailHeight = maxAvailSize.maxAvailHeight;
|
||||
});
|
||||
|
||||
add_task(async function test_window_open() {
|
||||
// Open a tab to test window.open().
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser, TEST_PATH + "file_dummy.html");
|
||||
|
||||
for (let test of TESTCASES) {
|
||||
// Test 'width' and 'height' of window features.
|
||||
await testWindowOpen(tab.linkedBrowser, test.settingWidth, test.settingHeight,
|
||||
test.targetWidth, test.targetHeight, false, gMaxAvailWidth,
|
||||
gMaxAvailHeight, gPopupChromeUIWidth, gPopupChromeUIHeight);
|
||||
|
||||
// test 'outerWidth' and 'outerHeight' of window features.
|
||||
await testWindowOpen(tab.linkedBrowser, test.settingWidth, test.settingHeight,
|
||||
test.targetWidth, test.targetHeight, true, gMaxAvailWidth,
|
||||
gMaxAvailHeight, gPopupChromeUIWidth, gPopupChromeUIHeight);
|
||||
}
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
OpenTest.run([
|
||||
{settingWidth: 199, settingHeight: 99, targetWidth: 200, targetHeight: 100},
|
||||
{settingWidth: 10, settingHeight: 10, targetWidth: 200, targetHeight: 100}
|
||||
]);
|
||||
|
|
|
@ -4,64 +4,8 @@
|
|||
* test is for maximum values.
|
||||
*/
|
||||
|
||||
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
const TEST_DOMAIN = "http://example.net/";
|
||||
const TEST_PATH = TEST_DOMAIN + "browser/browser/components/resistfingerprinting/test/browser/";
|
||||
|
||||
let gMaxAvailWidth;
|
||||
let gMaxAvailHeight;
|
||||
|
||||
// We need the chrome UI size of popup windows for testing outerWidth/Height.
|
||||
let gPopupChromeUIWidth;
|
||||
let gPopupChromeUIHeight;
|
||||
|
||||
const TESTCASES = [
|
||||
{ settingWidth: 1025, settingHeight: 1050, targetWidth: 1000, targetHeight: 1000,
|
||||
initWidth: 200, initHeight: 100 },
|
||||
{ settingWidth: 9999, settingHeight: 9999, targetWidth: 1000, targetHeight: 1000,
|
||||
initWidth: 200, initHeight: 100 },
|
||||
{ settingWidth: 999, settingHeight: 999, targetWidth: 1000, targetHeight: 1000,
|
||||
initWidth: 200, initHeight: 100 },
|
||||
];
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({"set":
|
||||
[["privacy.resistFingerprinting", true]]
|
||||
});
|
||||
|
||||
// Calculate the popup window's chrome UI size for tests of outerWidth/Height.
|
||||
let popUpChromeUISize = await calcPopUpWindowChromeUISize();
|
||||
|
||||
gPopupChromeUIWidth = popUpChromeUISize.chromeWidth;
|
||||
gPopupChromeUIHeight = popUpChromeUISize.chromeHeight;
|
||||
|
||||
// Calculate the maximum available size.
|
||||
let maxAvailSize = await calcMaximumAvailSize(gPopupChromeUIWidth,
|
||||
gPopupChromeUIHeight);
|
||||
|
||||
gMaxAvailWidth = maxAvailSize.maxAvailWidth;
|
||||
gMaxAvailHeight = maxAvailSize.maxAvailHeight;
|
||||
});
|
||||
|
||||
add_task(async function test_window_size_setting() {
|
||||
// Open a tab to test.
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser, TEST_PATH + "file_dummy.html");
|
||||
|
||||
for (let test of TESTCASES) {
|
||||
// Test window.innerWidth and window.innerHeight.
|
||||
await testWindowSizeSetting(tab.linkedBrowser, test.settingWidth, test.settingHeight,
|
||||
test.targetWidth, test.targetHeight, test.initWidth,
|
||||
test.initHeight, false, gMaxAvailWidth, gMaxAvailHeight,
|
||||
gPopupChromeUIWidth, gPopupChromeUIHeight);
|
||||
|
||||
// test window.outerWidth and window.outerHeight.
|
||||
await testWindowSizeSetting(tab.linkedBrowser, test.settingWidth, test.settingHeight,
|
||||
test.targetWidth, test.targetHeight, test.initWidth,
|
||||
test.initHeight, true, gMaxAvailWidth, gMaxAvailHeight,
|
||||
gPopupChromeUIWidth, gPopupChromeUIHeight);
|
||||
}
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
WindowSettingTest.run([
|
||||
{settingWidth: 1025, settingHeight: 1050, targetWidth: 1000, targetHeight: 1000, initWidth: 200, initHeight: 100},
|
||||
{settingWidth: 9999, settingHeight: 9999, targetWidth: 1000, targetHeight: 1000, initWidth: 200, initHeight: 100},
|
||||
{settingWidth: 999, settingHeight: 999, targetWidth: 1000, targetHeight: 1000, initWidth: 200, initHeight: 100}
|
||||
]);
|
||||
|
|
|
@ -4,64 +4,8 @@
|
|||
* test is for middle values.
|
||||
*/
|
||||
|
||||
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
const TEST_DOMAIN = "http://example.net/";
|
||||
const TEST_PATH = TEST_DOMAIN + "browser/browser/components/resistfingerprinting/test/browser/";
|
||||
|
||||
let gMaxAvailWidth;
|
||||
let gMaxAvailHeight;
|
||||
|
||||
// We need the chrome UI size of popup windows for testing outerWidth/Height.
|
||||
let gPopupChromeUIWidth;
|
||||
let gPopupChromeUIHeight;
|
||||
|
||||
const TESTCASES = [
|
||||
{ settingWidth: 600, settingHeight: 600, targetWidth: 600, targetHeight: 600,
|
||||
initWidth: 200, initHeight: 100 },
|
||||
{ settingWidth: 599, settingHeight: 599, targetWidth: 600, targetHeight: 600,
|
||||
initWidth: 200, initHeight: 100 },
|
||||
{ settingWidth: 401, settingHeight: 501, targetWidth: 600, targetHeight: 600,
|
||||
initWidth: 200, initHeight: 100 },
|
||||
];
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({"set":
|
||||
[["privacy.resistFingerprinting", true]]
|
||||
});
|
||||
|
||||
// Calculate the popup window's chrome UI size for tests of outerWidth/Height.
|
||||
let popUpChromeUISize = await calcPopUpWindowChromeUISize();
|
||||
|
||||
gPopupChromeUIWidth = popUpChromeUISize.chromeWidth;
|
||||
gPopupChromeUIHeight = popUpChromeUISize.chromeHeight;
|
||||
|
||||
// Calculate the maximum available size.
|
||||
let maxAvailSize = await calcMaximumAvailSize(gPopupChromeUIWidth,
|
||||
gPopupChromeUIHeight);
|
||||
|
||||
gMaxAvailWidth = maxAvailSize.maxAvailWidth;
|
||||
gMaxAvailHeight = maxAvailSize.maxAvailHeight;
|
||||
});
|
||||
|
||||
add_task(async function test_window_size_setting() {
|
||||
// Open a tab to test.
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser, TEST_PATH + "file_dummy.html");
|
||||
|
||||
for (let test of TESTCASES) {
|
||||
// Test window.innerWidth and window.innerHeight.
|
||||
await testWindowSizeSetting(tab.linkedBrowser, test.settingWidth, test.settingHeight,
|
||||
test.targetWidth, test.targetHeight, test.initWidth,
|
||||
test.initHeight, false, gMaxAvailWidth, gMaxAvailHeight,
|
||||
gPopupChromeUIWidth, gPopupChromeUIHeight);
|
||||
|
||||
// test window.outerWidth and window.outerHeight.
|
||||
await testWindowSizeSetting(tab.linkedBrowser, test.settingWidth, test.settingHeight,
|
||||
test.targetWidth, test.targetHeight, test.initWidth,
|
||||
test.initHeight, true, gMaxAvailWidth, gMaxAvailHeight,
|
||||
gPopupChromeUIWidth, gPopupChromeUIHeight);
|
||||
}
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
WindowSettingTest.run([
|
||||
{settingWidth: 600, settingHeight: 600, targetWidth: 600, targetHeight: 600, initWidth: 200, initHeight: 100},
|
||||
{settingWidth: 599, settingHeight: 599, targetWidth: 600, targetHeight: 600, initWidth: 200, initHeight: 100},
|
||||
{settingWidth: 401, settingHeight: 501, targetWidth: 600, targetHeight: 600, initWidth: 200, initHeight: 100}
|
||||
]);
|
||||
|
|
|
@ -4,62 +4,7 @@
|
|||
* test is for minimum values.
|
||||
*/
|
||||
|
||||
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
const TEST_DOMAIN = "http://example.net/";
|
||||
const TEST_PATH = TEST_DOMAIN + "browser/browser/components/resistfingerprinting/test/browser/";
|
||||
|
||||
let gMaxAvailWidth;
|
||||
let gMaxAvailHeight;
|
||||
|
||||
// We need the chrome UI size of popup windows for testing outerWidth/Height.
|
||||
let gPopupChromeUIWidth;
|
||||
let gPopupChromeUIHeight;
|
||||
|
||||
const TESTCASES = [
|
||||
{ settingWidth: 199, settingHeight: 99, targetWidth: 200, targetHeight: 100,
|
||||
initWidth: 1000, initHeight: 1000 },
|
||||
{ settingWidth: 10, settingHeight: 10, targetWidth: 200, targetHeight: 100,
|
||||
initWidth: 1000, initHeight: 1000 },
|
||||
];
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({"set":
|
||||
[["privacy.resistFingerprinting", true]]
|
||||
});
|
||||
|
||||
// Calculate the popup window's chrome UI size for tests of outerWidth/Height.
|
||||
let popUpChromeUISize = await calcPopUpWindowChromeUISize();
|
||||
|
||||
gPopupChromeUIWidth = popUpChromeUISize.chromeWidth;
|
||||
gPopupChromeUIHeight = popUpChromeUISize.chromeHeight;
|
||||
|
||||
// Calculate the maximum available size.
|
||||
let maxAvailSize = await calcMaximumAvailSize(gPopupChromeUIWidth,
|
||||
gPopupChromeUIHeight);
|
||||
|
||||
gMaxAvailWidth = maxAvailSize.maxAvailWidth;
|
||||
gMaxAvailHeight = maxAvailSize.maxAvailHeight;
|
||||
});
|
||||
|
||||
add_task(async function test_window_size_setting() {
|
||||
// Open a tab to test.
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser, TEST_PATH + "file_dummy.html");
|
||||
|
||||
for (let test of TESTCASES) {
|
||||
// Test window.innerWidth and window.innerHeight.
|
||||
await testWindowSizeSetting(tab.linkedBrowser, test.settingWidth, test.settingHeight,
|
||||
test.targetWidth, test.targetHeight, test.initWidth,
|
||||
test.initHeight, false, gMaxAvailWidth, gMaxAvailHeight,
|
||||
gPopupChromeUIWidth, gPopupChromeUIHeight);
|
||||
|
||||
// test window.outerWidth and window.outerHeight.
|
||||
await testWindowSizeSetting(tab.linkedBrowser, test.settingWidth, test.settingHeight,
|
||||
test.targetWidth, test.targetHeight, test.initWidth,
|
||||
test.initHeight, true, gMaxAvailWidth, gMaxAvailHeight,
|
||||
gPopupChromeUIWidth, gPopupChromeUIHeight);
|
||||
}
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
WindowSettingTest.run([
|
||||
{settingWidth: 199, settingHeight: 99, targetWidth: 200, targetHeight: 100, initWidth: 1000, initHeight: 1000},
|
||||
{settingWidth: 10, settingHeight: 10, targetWidth: 200, targetHeight: 100, initWidth: 1000, initHeight: 1000}
|
||||
]);
|
||||
|
|
|
@ -228,3 +228,88 @@ async function testWindowSizeSetting(aBrowser, aSettingWidth, aSettingHeight,
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
class RoundedWindowTest {
|
||||
// testOuter is optional. run() can be invoked with only 1 parameter.
|
||||
static run(testCases, testOuter) {
|
||||
// "this" is the calling class itself.
|
||||
// e.g. when invoked by RoundedWindowTest.run(), "this" is "class RoundedWindowTest".
|
||||
let test = new this(testCases);
|
||||
add_task(async () => test.setup());
|
||||
add_task(async () => {
|
||||
if (testOuter == undefined) {
|
||||
// If testOuter is not given, do tests for both inner and outer.
|
||||
await test.doTests(false);
|
||||
await test.doTests(true);
|
||||
} else {
|
||||
await test.doTests(testOuter);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get TEST_PATH() {
|
||||
return "http://example.net/browser/browser/components/resistfingerprinting/test/browser/";
|
||||
}
|
||||
|
||||
constructor(testCases) {
|
||||
this.testCases = testCases;
|
||||
}
|
||||
|
||||
async setup() {
|
||||
await SpecialPowers.pushPrefEnv({"set":
|
||||
[["privacy.resistFingerprinting", true]]
|
||||
});
|
||||
|
||||
// Calculate the popup window's chrome UI size for tests of outerWidth/Height.
|
||||
let popUpChromeUISize = await calcPopUpWindowChromeUISize();
|
||||
|
||||
this.popupChromeUIWidth = popUpChromeUISize.chromeWidth;
|
||||
this.popupChromeUIHeight = popUpChromeUISize.chromeHeight;
|
||||
|
||||
// Calculate the maximum available size.
|
||||
let maxAvailSize = await calcMaximumAvailSize(this.popupChromeUIWidth,
|
||||
this.popupChromeUIHeight);
|
||||
|
||||
this.maxAvailWidth = maxAvailSize.maxAvailWidth;
|
||||
this.maxAvailHeight = maxAvailSize.maxAvailHeight;
|
||||
}
|
||||
|
||||
async doTests(testOuter) {
|
||||
// Open a tab to test.
|
||||
this.tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser, this.TEST_PATH + "file_dummy.html");
|
||||
|
||||
for (let test of this.testCases) {
|
||||
await this.doTest(test, testOuter);
|
||||
}
|
||||
|
||||
await BrowserTestUtils.removeTab(this.tab);
|
||||
}
|
||||
|
||||
async doTest() {
|
||||
throw new Error("RoundedWindowTest.doTest must be overridden.");
|
||||
}
|
||||
}
|
||||
|
||||
class WindowSettingTest extends RoundedWindowTest {
|
||||
async doTest(test, testOuter) {
|
||||
await testWindowSizeSetting(this.tab.linkedBrowser,
|
||||
test.settingWidth, test.settingHeight,
|
||||
test.targetWidth, test.targetHeight,
|
||||
test.initWidth, test.initHeight,
|
||||
testOuter,
|
||||
this.maxAvailWidth, this.maxAvailHeight,
|
||||
this.popupChromeUIWidth, this.popupChromeUIHeight);
|
||||
}
|
||||
}
|
||||
|
||||
class OpenTest extends RoundedWindowTest {
|
||||
async doTest(test, testOuter) {
|
||||
await testWindowOpen(this.tab.linkedBrowser,
|
||||
test.settingWidth, test.settingHeight,
|
||||
test.targetWidth, test.targetHeight,
|
||||
testOuter,
|
||||
this.maxAvailWidth, this.maxAvailHeight,
|
||||
this.popupChromeUIWidth, this.popupChromeUIHeight);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -205,6 +205,7 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
|||
<!ENTITY urlbar.defaultNotificationAnchor.tooltip "Open message panel">
|
||||
<!ENTITY urlbar.geolocationNotificationAnchor.tooltip "Open location request panel">
|
||||
<!ENTITY urlbar.addonsNotificationAnchor.tooltip "Open add-on installation message panel">
|
||||
<!ENTITY urlbar.canvasNotificationAnchor.tooltip "Manage canvas extraction permission">
|
||||
<!ENTITY urlbar.indexedDBNotificationAnchor.tooltip "Open offline storage message panel">
|
||||
<!ENTITY urlbar.passwordNotificationAnchor.tooltip "Open save password message panel">
|
||||
<!ENTITY urlbar.pluginsNotificationAnchor.tooltip "Manage plug-in use">
|
||||
|
|
|
@ -467,6 +467,15 @@ offlineApps.usage=This website (%S) is now storing more than %SMB of data on you
|
|||
offlineApps.manageUsage=Show settings
|
||||
offlineApps.manageUsageAccessKey=S
|
||||
|
||||
# Canvas permission prompt
|
||||
# LOCALIZATION NOTE (canvas.siteprompt): %S is hostname
|
||||
canvas.siteprompt=Will you allow %S to use your HTML5 canvas image data? This may be used to uniquely identify your computer.
|
||||
canvas.notAllow=Don’t Allow
|
||||
canvas.notAllow.accesskey=n
|
||||
canvas.allow=Allow Data Access
|
||||
canvas.allow.accesskey=A
|
||||
canvas.remember=Always remember my decision
|
||||
|
||||
identity.identified.verifier=Verified by: %S
|
||||
identity.identified.verified_by_you=You have added a security exception for this site.
|
||||
identity.identified.state_and_country=%S, %S
|
||||
|
|
|
@ -51,6 +51,11 @@
|
|||
min-height: 24px;
|
||||
}
|
||||
|
||||
:root[uidensity=touch] #search-box,
|
||||
:root[uidensity=touch] .sidebar-placesTreechildren::-moz-tree-row {
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.sidebar-placesTreechildren::-moz-tree-cell(leaf) ,
|
||||
.sidebar-placesTreechildren::-moz-tree-image(leaf) {
|
||||
cursor: pointer;
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
font-size: 1.0909rem;
|
||||
}
|
||||
|
||||
:root[uidensity=touch] .sidebar-placesTreechildren::-moz-tree-row {
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.sidebar-placesTree {
|
||||
-moz-appearance: -moz-mac-source-list;
|
||||
-moz-font-smoothing-background-color: -moz-mac-source-list;
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
|
||||
skin/classic/browser/notification-icons/camera-blocked.svg (../shared/notification-icons/camera-blocked.svg)
|
||||
skin/classic/browser/notification-icons/camera.svg (../shared/notification-icons/camera.svg)
|
||||
skin/classic/browser/notification-icons/canvas.svg (../shared/notification-icons/canvas.svg)
|
||||
skin/classic/browser/notification-icons/default-info.svg (../shared/notification-icons/default-info.svg)
|
||||
skin/classic/browser/notification-icons/desktop-notification-blocked.svg (../shared/notification-icons/desktop-notification-blocked.svg)
|
||||
skin/classic/browser/notification-icons/desktop-notification.svg (../shared/notification-icons/desktop-notification.svg)
|
||||
|
|
|
@ -117,6 +117,11 @@
|
|||
list-style-image: url(chrome://browser/skin/notification-icons/screen-blocked.svg);
|
||||
}
|
||||
|
||||
#canvas-notification-icon,
|
||||
.popup-notification-icon[popupid="canvas-permissions-prompt"] {
|
||||
list-style-image: url(chrome://browser/skin/notification-icons/canvas.svg);
|
||||
}
|
||||
|
||||
#webRTC-preview:not([hidden]) {
|
||||
display: -moz-stack;
|
||||
border-radius: 4px;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 481.156 508.687" fill="context-fill" fill-opacity="context-fill-opacity">
|
||||
<path d="M477.656 289.656c-36.133 224.615-220.16 222.188-283 218C22.22 496.163-68.584 254.586 61.98 128.066l105.857 105.872c-7.183 24.6 2.7 54.1 42.418 70.59 42.865 17.8 87.4 9.747 87.4 9.747s-14.08-22.03-15.565-68.266c-1.1-34.2-40.15-55.996-72.513-50.483L108.757 94.69c30.6-12.34 59.033-1.8 69.9 6.966 26.87 21.688 16.616 68.436 53 54 70.87-28.12-40.744-132.32 53-154 79.026-18.278 220.516 116.945 193 288zm-371-14a41 41 0 1 0 41 41 41 41 0 0 0-41-41zm149.5 92a43.5 43.5 0 1 0 43.5 43.5 43.5 43.5 0 0 0-43.5-43.5zm97.5-273a40 40 0 1 0 40 40 40 40 0 0 0-40-40zm24.5 141a45.5 45.5 0 1 0 45.5 45.5 45.5 45.5 0 0 0-45.5-45.5z"/>
|
||||
<path d="M213.656 296.656c-36.083-15.022-42.678-42.92-33.52-64.423L35.847 87.925a17.732 17.732 0 0 1 25.076-25.078L205.45 207.393c28.076-10.037 67.206 8.853 68.206 40.263 1.24 38.716 13 58 13 58s-37.2 5.905-73-9z"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.2 KiB |
|
@ -21,6 +21,11 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
:root[uidensity=touch] #search-box,
|
||||
:root[uidensity=touch] .sidebar-placesTreechildren::-moz-tree-row {
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.sidebar-placesTreechildren::-moz-tree-cell,
|
||||
.sidebar-placesTreechildren::-moz-tree-twisty {
|
||||
padding: 0 4px;
|
||||
|
|
|
@ -76,7 +76,7 @@ function createToolMenuElements(toolDefinition, doc) {
|
|||
|
||||
let oncommand = function (id, event) {
|
||||
let window = event.target.ownerDocument.defaultView;
|
||||
gDevToolsBrowser.selectToolCommand(window.gBrowser, id);
|
||||
gDevToolsBrowser.selectToolCommand(window.gBrowser, id, window.performance.now());
|
||||
}.bind(null, id);
|
||||
|
||||
let menuitem = createMenuItem({
|
||||
|
|
|
@ -73,7 +73,7 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
* of there
|
||||
*/
|
||||
// used by browser-sets.inc, command
|
||||
toggleToolboxCommand(gBrowser) {
|
||||
toggleToolboxCommand(gBrowser, startTime) {
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let toolbox = gDevTools.getToolbox(target);
|
||||
|
||||
|
@ -81,7 +81,11 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
// - should close a docked toolbox
|
||||
// - should focus a windowed toolbox
|
||||
let isDocked = toolbox && toolbox.hostType != Toolbox.HostType.WINDOW;
|
||||
isDocked ? gDevTools.closeToolbox(target) : gDevTools.showToolbox(target);
|
||||
if (isDocked) {
|
||||
gDevTools.closeToolbox(target);
|
||||
} else {
|
||||
gDevTools.showToolbox(target, null, null, null, startTime);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -216,7 +220,7 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
*/
|
||||
// Used when: - registering a new tool
|
||||
// - new xul window, to add menu items
|
||||
selectToolCommand(gBrowser, toolId) {
|
||||
selectToolCommand(gBrowser, toolId, startTime) {
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let toolbox = gDevTools.getToolbox(target);
|
||||
let toolDefinition = gDevTools.getToolDefinition(toolId);
|
||||
|
@ -234,7 +238,7 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
}
|
||||
gDevTools.emit("select-tool-command", toolId);
|
||||
} else {
|
||||
gDevTools.showToolbox(target, toolId).then(newToolbox => {
|
||||
gDevTools.showToolbox(target, toolId, null, null, startTime).then(newToolbox => {
|
||||
newToolbox.fireCustomKey(toolId);
|
||||
gDevTools.emit("select-tool-command", toolId);
|
||||
});
|
||||
|
@ -253,18 +257,21 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
* - `toolId` used to identify a toolbox's panel like inspector or webconsole,
|
||||
* - `id` used to identify any other key shortcuts like scratchpad or
|
||||
* about:debugging
|
||||
* @param {Number} startTime
|
||||
* Optional, indicates the time at which the key event fired. This is a
|
||||
* `performance.now()` timing.
|
||||
*/
|
||||
onKeyShortcut(window, key) {
|
||||
onKeyShortcut(window, key, startTime) {
|
||||
// If this is a toolbox's panel key shortcut, delegate to selectToolCommand
|
||||
if (key.toolId) {
|
||||
gDevToolsBrowser.selectToolCommand(window.gBrowser, key.toolId);
|
||||
gDevToolsBrowser.selectToolCommand(window.gBrowser, key.toolId, startTime);
|
||||
return;
|
||||
}
|
||||
// Otherwise implement all other key shortcuts individually here
|
||||
switch (key.id) {
|
||||
case "toggleToolbox":
|
||||
case "toggleToolboxF12":
|
||||
gDevToolsBrowser.toggleToolboxCommand(window.gBrowser);
|
||||
gDevToolsBrowser.toggleToolboxCommand(window.gBrowser, startTime);
|
||||
break;
|
||||
case "toggleToolbar":
|
||||
gDevToolsBrowser.getDeveloperToolbar(window).focusToggle();
|
||||
|
|
|
@ -421,6 +421,12 @@ DevTools.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Boolean, true, if we never opened a toolbox.
|
||||
* Used to implement the telemetry tracking toolbox opening.
|
||||
*/
|
||||
_firstShowToolbox: true,
|
||||
|
||||
/**
|
||||
* Show a Toolbox for a target (either by creating a new one, or if a toolbox
|
||||
* already exists for the target, by bring to the front the existing one)
|
||||
|
@ -437,11 +443,14 @@ DevTools.prototype = {
|
|||
* The type of host (bottom, window, side)
|
||||
* @param {object} hostOptions
|
||||
* Options for host specifically
|
||||
* @param {Number} startTime
|
||||
* Optional, indicates the time at which the user event related to this toolbox
|
||||
* opening started. This is a `performance.now()` timing.
|
||||
*
|
||||
* @return {Toolbox} toolbox
|
||||
* The toolbox that was opened
|
||||
*/
|
||||
showToolbox: Task.async(function* (target, toolId, hostType, hostOptions) {
|
||||
showToolbox: Task.async(function* (target, toolId, hostType, hostOptions, startTime) {
|
||||
let toolbox = this._toolboxes.get(target);
|
||||
if (toolbox) {
|
||||
if (hostType != null && toolbox.hostType != hostType) {
|
||||
|
@ -465,10 +474,37 @@ DevTools.prototype = {
|
|||
this._creatingToolboxes.set(target, toolboxPromise);
|
||||
toolbox = yield toolboxPromise;
|
||||
this._creatingToolboxes.delete(target);
|
||||
|
||||
if (startTime) {
|
||||
this.logToolboxOpenTime(toolbox.currentToolId, startTime);
|
||||
}
|
||||
this._firstShowToolbox = false;
|
||||
}
|
||||
return toolbox;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Log telemetry related to toolbox opening.
|
||||
* Two distinct probes are logged. One for cold startup, when we open the very first
|
||||
* toolbox. This one includes devtools framework loading. And a second one for all
|
||||
* subsequent toolbox opening, which should all be faster.
|
||||
* These two probes are indexed by Tool ID.
|
||||
*
|
||||
* @param {String} toolId
|
||||
* The id of the opened tool.
|
||||
* @param {Number} startTime
|
||||
* Indicates the time at which the user event related to the toolbox
|
||||
* opening started. This is a `performance.now()` timing.
|
||||
*/
|
||||
logToolboxOpenTime(toolId, startTime) {
|
||||
let { performance } = Services.appShell.hiddenDOMWindow;
|
||||
let delay = performance.now() - startTime;
|
||||
let telemetryKey = this._firstShowToolbox ?
|
||||
"DEVTOOLS_COLD_TOOLBOX_OPEN_DELAY_MS" : "DEVTOOLS_WARM_TOOLBOX_OPEN_DELAY_MS";
|
||||
let histogram = Services.telemetry.getKeyedHistogramById(telemetryKey);
|
||||
histogram.add(toolId, delay);
|
||||
},
|
||||
|
||||
createToolbox: Task.async(function* (target, toolId, hostType, hostOptions) {
|
||||
let manager = new ToolboxHostManager(target, hostType, hostOptions);
|
||||
|
||||
|
@ -575,13 +611,16 @@ DevTools.prototype = {
|
|||
* An array of CSS selectors to find the target node. Several selectors can be
|
||||
* needed if the element is nested in frames and not directly in the root
|
||||
* document.
|
||||
* @param {Number} startTime
|
||||
* Optional, indicates the time at which the user event related to this node
|
||||
* inspection started. This is a `performance.now()` timing.
|
||||
* @return {Promise} a promise that resolves when the node is selected in the inspector
|
||||
* markup view.
|
||||
*/
|
||||
async inspectNode(tab, nodeSelectors) {
|
||||
async inspectNode(tab, nodeSelectors, startTime) {
|
||||
let target = TargetFactory.forTab(tab);
|
||||
|
||||
let toolbox = await gDevTools.showToolbox(target, "inspector");
|
||||
let toolbox = await gDevTools.showToolbox(target, "inspector", null, null, startTime);
|
||||
let inspector = toolbox.getCurrentPanel();
|
||||
|
||||
// new-node-front tells us when the node has been selected, whether the
|
||||
|
|
|
@ -41,7 +41,7 @@ exports.menuitems = [
|
|||
l10nKey: "devToolboxMenuItem",
|
||||
oncommand(event) {
|
||||
let window = event.target.ownerDocument.defaultView;
|
||||
gDevToolsBrowser.toggleToolboxCommand(window.gBrowser);
|
||||
gDevToolsBrowser.toggleToolboxCommand(window.gBrowser, window.performance.now());
|
||||
},
|
||||
keyId: "toggleToolbox",
|
||||
checkbox: true
|
||||
|
|
|
@ -995,6 +995,7 @@ JSTerm.prototype = {
|
|||
this.focus();
|
||||
this.emit("messages-cleared");
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all of the private messages from the Web Console output.
|
||||
*
|
||||
|
@ -1773,7 +1774,13 @@ JSTerm.prototype = {
|
|||
this._sidebarDestroy();
|
||||
|
||||
this.clearCompletion();
|
||||
this.clearOutput();
|
||||
|
||||
if (this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
|
||||
this.webConsoleClient.clearNetworkRequests();
|
||||
this.hud.outputNode.innerHTML = "";
|
||||
} else {
|
||||
this.clearOutput();
|
||||
}
|
||||
|
||||
this.autocompletePopup.destroy();
|
||||
this.autocompletePopup = null;
|
||||
|
|
|
@ -206,11 +206,10 @@ skip-if = true # Bug 1401881
|
|||
[browser_jsterm_inspect.js]
|
||||
[browser_jsterm_no_autocompletion_on_defined_variables.js]
|
||||
skip-if = true # Bug 1401881
|
||||
[browser_jsterm_no_input_and_tab_key_pressed.js]
|
||||
[browser_netmonitor_shows_reqs_in_webconsole.js]
|
||||
[browser_webconsole.js]
|
||||
skip-if = true # Bug 1404829
|
||||
[browser_webconsole_No_input_and_Tab_key_pressed.js]
|
||||
skip-if = true # Bug 1403910
|
||||
[browser_webconsole_No_input_change_and_Tab_key_pressed.js]
|
||||
skip-if = true # Bug 1404882
|
||||
[browser_webconsole_add_edited_input_to_history.js]
|
||||
|
|
|
@ -7,13 +7,10 @@
|
|||
|
||||
// See Bug 583816.
|
||||
|
||||
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
|
||||
"test/browser/test-console.html";
|
||||
const TEST_URI = "data:text/html,Testing jsterm with no input";
|
||||
|
||||
add_task(function* () {
|
||||
yield loadTab(TEST_URI);
|
||||
|
||||
let hud = yield openConsole();
|
||||
let hud = yield openNewTabAndConsole(TEST_URI);
|
||||
testCompletion(hud);
|
||||
});
|
||||
|
||||
|
@ -25,7 +22,7 @@ function testCompletion(hud) {
|
|||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
is(jsterm.completeNode.value, "<- no result", "<- no result - matched");
|
||||
is(input.value, "", "inputnode is empty - matched");
|
||||
is(input.getAttribute("focused"), "true", "input is still focused");
|
||||
ok(hasFocus(input), "input is still focused");
|
||||
|
||||
// Any thing which is not in property autocompleter
|
||||
jsterm.setInputValue("window.Bug583816");
|
||||
|
@ -33,5 +30,5 @@ function testCompletion(hud) {
|
|||
is(jsterm.completeNode.value, " <- no result",
|
||||
"completenode content - matched");
|
||||
is(input.value, "window.Bug583816", "inputnode content - matched");
|
||||
is(input.getAttribute("focused"), "true", "input is still focused");
|
||||
ok(hasFocus(input), "input is still focused");
|
||||
}
|
|
@ -160,12 +160,18 @@ this.DevToolsShim = {
|
|||
* markup view or that resolves immediately if DevTools are not installed.
|
||||
*/
|
||||
inspectNode: function (tab, selectors) {
|
||||
// Record the timing at which this event started in order to compute later in
|
||||
// gDevTools.showToolbox, the complete time it takes to open the toolbox.
|
||||
// i.e. especially take `DevtoolsStartup.initDevTools` into account.
|
||||
let { performance } = Services.appShell.hiddenDOMWindow;
|
||||
let startTime = performance.now();
|
||||
|
||||
let devtoolsReady = this._maybeInitializeDevTools("ContextMenu");
|
||||
if (!devtoolsReady) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this._gDevTools.inspectNode(tab, selectors);
|
||||
return this._gDevTools.inspectNode(tab, selectors, startTime);
|
||||
},
|
||||
|
||||
_onDevToolsRegistered: function () {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> %htmlDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD;
|
||||
<!ENTITY % aboutdevtoolsDTD SYSTEM "chrome://devtools-shim/locale/aboutdevtools.dtd"> %aboutdevtoolsDTD;
|
||||
<!ENTITY % aboutdevtoolsDTD SYSTEM "chrome://devtools-shim/content/aboutdevtools/tmp-locale/aboutdevtools.dtd"> %aboutdevtoolsDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" dir="&locale.dir;">
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<!ENTITY aboutDevtools.headTitle "About Developer Tools">
|
||||
<!ENTITY aboutDevtools.enable.title "Enable Firefox Developer Tools">
|
||||
<!ENTITY aboutDevtools.enable.inspectElementTitle "Enable Firefox Developer Tools to use Inspect Element">
|
|
@ -449,11 +449,15 @@ DevToolsStartup.prototype = {
|
|||
},
|
||||
|
||||
onKey(window, key) {
|
||||
// Record the timing at which this event started in order to compute later in
|
||||
// gDevTools.showToolbox, the complete time it takes to open the toolbox.
|
||||
// i.e. especially take `initDevTools` into account.
|
||||
let startTime = window.performance.now();
|
||||
let require = this.initDevTools("KeyShortcut");
|
||||
if (require) {
|
||||
// require might be null if initDevTools was called while DevTools are disabled.
|
||||
let { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser");
|
||||
gDevToolsBrowser.onKeyShortcut(window, key);
|
||||
gDevToolsBrowser.onKeyShortcut(window, key, startTime);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -10,6 +10,10 @@ devtools-shim.jar:
|
|||
|
||||
content/aboutdevtools/images/otter.png (aboutdevtools/images/otter.png)
|
||||
|
||||
# Temporary localisation file, move back to devtools/shim/locales/en-US when ready for localization
|
||||
# See https://bugzilla.mozilla.org/show_bug.cgi?id=1408369
|
||||
content/aboutdevtools/tmp-locale/aboutdevtools.dtd (aboutdevtools/tmp-locale/aboutdevtools.dtd)
|
||||
|
||||
content/aboutdevtools/images/feature-inspector.svg (aboutdevtools/images/feature-inspector.svg)
|
||||
content/aboutdevtools/images/feature-console.svg (aboutdevtools/images/feature-console.svg)
|
||||
content/aboutdevtools/images/feature-debugger.svg (aboutdevtools/images/feature-debugger.svg)
|
||||
|
|
|
@ -200,20 +200,18 @@ AnimationEffectReadOnly::GetComputedTimingAt(
|
|||
? fmod(overallProgress, 1.0)
|
||||
: fmod(result.mIterationStart, 1.0);
|
||||
|
||||
// When we finish exactly at the end of an iteration we need to report
|
||||
// the end of the final iteration and not the start of the next iteration.
|
||||
// We *don't* want to do this when we have a zero-iteration animation or
|
||||
// when the animation has been effectively made into a zero-duration animation
|
||||
// using a negative end-delay, however.
|
||||
if (result.mPhase == ComputedTiming::AnimationPhase::After &&
|
||||
progress == 0.0 &&
|
||||
result.mIterations != 0.0 &&
|
||||
(result.mActiveTime != zeroDuration ||
|
||||
result.mDuration == zeroDuration)) {
|
||||
// The only way we can be in the after phase with a progress of zero and
|
||||
// a current iteration of zero, is if we have a zero iteration count or
|
||||
// were clipped using a negative end delay--both of which we should have
|
||||
// detected above.
|
||||
// When we are at the end of the active interval and the end of an iteration
|
||||
// we need to report the end of the final iteration and not the start of the
|
||||
// next iteration. We *don't* want to do this, however, when we have
|
||||
// a zero-iteration animation.
|
||||
if (progress == 0.0 &&
|
||||
(result.mPhase == ComputedTiming::AnimationPhase::After ||
|
||||
result.mPhase == ComputedTiming::AnimationPhase::Active) &&
|
||||
result.mActiveTime == result.mActiveDuration &&
|
||||
result.mIterations != 0.0) {
|
||||
// The only way we can reach the end of the active interval and have
|
||||
// a progress of zero and a current iteration of zero, is if we have a zero
|
||||
// iteration count -- something we should have detected above.
|
||||
MOZ_ASSERT(result.mCurrentIteration != 0,
|
||||
"Should not have zero current iteration");
|
||||
progress = 1.0;
|
||||
|
|
|
@ -28,8 +28,14 @@ public:
|
|||
|
||||
void AddPlayPending(dom::Animation& aAnimation)
|
||||
{
|
||||
MOZ_ASSERT(!IsWaitingToPause(aAnimation),
|
||||
"Animation is already waiting to pause");
|
||||
// We'd like to assert here that IsWaitingToPause(aAnimation) is false but
|
||||
// if |aAnimation| was tracked here as a pause-pending animation when it was
|
||||
// removed from |mDocument|, then re-attached to |mDocument|, and then
|
||||
// played again, we could end up here with IsWaitingToPause returning true.
|
||||
//
|
||||
// However, that should be harmless since all it means is that we'll call
|
||||
// Animation::TriggerOnNextTick or Animation::TriggerNow twice, both of
|
||||
// which will handle the redundant call gracefully.
|
||||
AddPending(aAnimation, mPlayPendingSet);
|
||||
mHasPlayPendingGeometricAnimations = CheckState::Indeterminate;
|
||||
}
|
||||
|
@ -45,8 +51,10 @@ public:
|
|||
|
||||
void AddPausePending(dom::Animation& aAnimation)
|
||||
{
|
||||
MOZ_ASSERT(!IsWaitingToPlay(aAnimation),
|
||||
"Animation is already waiting to play");
|
||||
// As with AddPausePending, we'd like to assert that
|
||||
// IsWaitingToPlay(aAnimation) is false but there are some circumstances
|
||||
// where this can be true. Fortunately adding the animation to both pending
|
||||
// sets should be harmless.
|
||||
AddPending(aAnimation, mPausePendingSet);
|
||||
}
|
||||
void RemovePausePending(dom::Animation& aAnimation)
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html class=reftest-wait>
|
||||
<meta charset=utf-8>
|
||||
<script>
|
||||
|
||||
function boom() {
|
||||
const div = document.createElement('div');
|
||||
const anim = div.animate([{}], {});
|
||||
document.documentElement.appendChild(div);
|
||||
anim.pause();
|
||||
document.documentElement.removeChild(div);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
document.documentElement.appendChild(div);
|
||||
anim.play();
|
||||
document.documentElement.className = '';
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="boom();"></body>
|
||||
</html>
|
|
@ -10,6 +10,7 @@ pref(dom.animations-api.core.enabled,true) load 1272475-1.html
|
|||
pref(dom.animations-api.core.enabled,true) load 1272475-2.html
|
||||
pref(dom.animations-api.core.enabled,true) load 1278485-1.html
|
||||
pref(dom.animations-api.core.enabled,true) load 1277272-1.html
|
||||
pref(dom.animations-api.core.enabled,true) load 1282691-1.html
|
||||
pref(dom.animations-api.core.enabled,true) load 1291413-1.html
|
||||
pref(dom.animations-api.core.enabled,true) load 1291413-2.html
|
||||
pref(dom.animations-api.core.enabled,true) load 1304886-1.html
|
||||
|
|
|
@ -3467,27 +3467,38 @@ Element::GetTokenList(nsAtom* aAtom,
|
|||
Element*
|
||||
Element::Closest(const nsAString& aSelector, ErrorResult& aResult)
|
||||
{
|
||||
nsCSSSelectorList* selectorList = ParseSelectorList(aSelector, aResult);
|
||||
if (!selectorList) {
|
||||
// Either we failed (and aResult already has the exception), or this
|
||||
// is a pseudo-element-only selector that matches nothing.
|
||||
return nullptr;
|
||||
}
|
||||
TreeMatchContext matchingContext(false,
|
||||
nsRuleWalker::eRelevantLinkUnvisited,
|
||||
OwnerDoc(),
|
||||
TreeMatchContext::eNeverMatchVisited);
|
||||
matchingContext.SetHasSpecifiedScope();
|
||||
matchingContext.AddScopeElement(this);
|
||||
for (nsINode* node = this; node; node = node->GetParentNode()) {
|
||||
if (node->IsElement() &&
|
||||
nsCSSRuleProcessor::SelectorListMatches(node->AsElement(),
|
||||
matchingContext,
|
||||
selectorList)) {
|
||||
return node->AsElement();
|
||||
return WithSelectorList<Element*>(
|
||||
aSelector,
|
||||
aResult,
|
||||
[&](const RawServoSelectorList* aList) -> Element* {
|
||||
if (!aList) {
|
||||
return nullptr;
|
||||
}
|
||||
return const_cast<Element*>(Servo_SelectorList_Closest(this, aList));
|
||||
},
|
||||
[&](nsCSSSelectorList* aList) -> Element* {
|
||||
if (!aList) {
|
||||
// Either we failed (and aError already has the exception), or this
|
||||
// is a pseudo-element-only selector that matches nothing.
|
||||
return nullptr;
|
||||
}
|
||||
TreeMatchContext matchingContext(false,
|
||||
nsRuleWalker::eRelevantLinkUnvisited,
|
||||
OwnerDoc(),
|
||||
TreeMatchContext::eNeverMatchVisited);
|
||||
matchingContext.SetHasSpecifiedScope();
|
||||
matchingContext.AddScopeElement(this);
|
||||
for (nsINode* node = this; node; node = node->GetParentNode()) {
|
||||
if (node->IsElement() &&
|
||||
nsCSSRuleProcessor::SelectorListMatches(node->AsElement(),
|
||||
matchingContext,
|
||||
aList)) {
|
||||
return node->AsElement();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
);
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
@ -155,6 +155,7 @@ public:
|
|||
EncodingCompleteEvent* aEncodingCompleteEvent,
|
||||
int32_t aFormat,
|
||||
const nsIntSize aSize,
|
||||
bool aUsePlaceholder,
|
||||
bool aUsingCustomOptions)
|
||||
: Runnable("EncodingRunnable")
|
||||
, mType(aType)
|
||||
|
@ -165,6 +166,7 @@ public:
|
|||
, mEncodingCompleteEvent(aEncodingCompleteEvent)
|
||||
, mFormat(aFormat)
|
||||
, mSize(aSize)
|
||||
, mUsePlaceholder(aUsePlaceholder)
|
||||
, mUsingCustomOptions(aUsingCustomOptions)
|
||||
{}
|
||||
|
||||
|
@ -176,6 +178,7 @@ public:
|
|||
mImageBuffer.get(),
|
||||
mFormat,
|
||||
mSize,
|
||||
mUsePlaceholder,
|
||||
mImage,
|
||||
nullptr,
|
||||
nullptr,
|
||||
|
@ -190,6 +193,7 @@ public:
|
|||
mImageBuffer.get(),
|
||||
mFormat,
|
||||
mSize,
|
||||
mUsePlaceholder,
|
||||
mImage,
|
||||
nullptr,
|
||||
nullptr,
|
||||
|
@ -239,6 +243,7 @@ private:
|
|||
RefPtr<EncodingCompleteEvent> mEncodingCompleteEvent;
|
||||
int32_t mFormat;
|
||||
const nsIntSize mSize;
|
||||
bool mUsePlaceholder;
|
||||
bool mUsingCustomOptions;
|
||||
};
|
||||
|
||||
|
@ -251,6 +256,7 @@ nsresult
|
|||
ImageEncoder::ExtractData(nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
const nsIntSize aSize,
|
||||
bool aUsePlaceholder,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
layers::AsyncCanvasRenderer* aRenderer,
|
||||
nsIInputStream** aStream)
|
||||
|
@ -260,7 +266,8 @@ ImageEncoder::ExtractData(nsAString& aType,
|
|||
return NS_IMAGELIB_ERROR_NO_ENCODER;
|
||||
}
|
||||
|
||||
return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, nullptr,
|
||||
return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize,
|
||||
aUsePlaceholder, nullptr,
|
||||
aContext, aRenderer, aStream, encoder);
|
||||
}
|
||||
|
||||
|
@ -270,6 +277,7 @@ ImageEncoder::ExtractDataFromLayersImageAsync(nsAString& aType,
|
|||
const nsAString& aOptions,
|
||||
bool aUsingCustomOptions,
|
||||
layers::Image* aImage,
|
||||
bool aUsePlaceholder,
|
||||
EncodeCompleteCallback* aEncodeCallback)
|
||||
{
|
||||
nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
|
||||
|
@ -294,6 +302,7 @@ ImageEncoder::ExtractDataFromLayersImageAsync(nsAString& aType,
|
|||
completeEvent,
|
||||
imgIEncoder::INPUT_FORMAT_HOSTARGB,
|
||||
size,
|
||||
aUsePlaceholder,
|
||||
aUsingCustomOptions);
|
||||
return sThreadPool->Dispatch(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
@ -306,6 +315,7 @@ ImageEncoder::ExtractDataAsync(nsAString& aType,
|
|||
UniquePtr<uint8_t[]> aImageBuffer,
|
||||
int32_t aFormat,
|
||||
const nsIntSize aSize,
|
||||
bool aUsePlaceholder,
|
||||
EncodeCompleteCallback* aEncodeCallback)
|
||||
{
|
||||
nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
|
||||
|
@ -329,6 +339,7 @@ ImageEncoder::ExtractDataAsync(nsAString& aType,
|
|||
completeEvent,
|
||||
aFormat,
|
||||
aSize,
|
||||
aUsePlaceholder,
|
||||
aUsingCustomOptions);
|
||||
return sThreadPool->Dispatch(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
@ -359,6 +370,7 @@ ImageEncoder::ExtractDataInternal(const nsAString& aType,
|
|||
uint8_t* aImageBuffer,
|
||||
int32_t aFormat,
|
||||
const nsIntSize aSize,
|
||||
bool aUsePlaceholder,
|
||||
layers::Image* aImage,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
layers::AsyncCanvasRenderer* aRenderer,
|
||||
|
@ -373,7 +385,7 @@ ImageEncoder::ExtractDataInternal(const nsAString& aType,
|
|||
|
||||
// get image bytes
|
||||
nsresult rv;
|
||||
if (aImageBuffer) {
|
||||
if (aImageBuffer && !aUsePlaceholder) {
|
||||
if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
@ -386,17 +398,17 @@ ImageEncoder::ExtractDataInternal(const nsAString& aType,
|
|||
aEncoder,
|
||||
nsPromiseFlatString(aOptions).get(),
|
||||
getter_AddRefs(imgStream));
|
||||
} else if (aContext) {
|
||||
} else if (aContext && !aUsePlaceholder) {
|
||||
NS_ConvertUTF16toUTF8 encoderType(aType);
|
||||
rv = aContext->GetInputStream(encoderType.get(),
|
||||
nsPromiseFlatString(aOptions).get(),
|
||||
getter_AddRefs(imgStream));
|
||||
} else if (aRenderer) {
|
||||
} else if (aRenderer && !aUsePlaceholder) {
|
||||
NS_ConvertUTF16toUTF8 encoderType(aType);
|
||||
rv = aRenderer->GetInputStream(encoderType.get(),
|
||||
nsPromiseFlatString(aOptions).get(),
|
||||
getter_AddRefs(imgStream));
|
||||
} else if (aImage) {
|
||||
} else if (aImage && !aUsePlaceholder) {
|
||||
// It is safe to convert PlanarYCbCr format from YUV to RGB off-main-thread.
|
||||
// Other image formats could have problem to convert format off-main-thread.
|
||||
// So here it uses a help function GetBRGADataSourceSurfaceSync() to convert
|
||||
|
@ -472,6 +484,10 @@ ImageEncoder::ExtractDataInternal(const nsAString& aType,
|
|||
if (!emptyCanvas->Map(DataSourceSurface::MapType::WRITE, &map)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (aUsePlaceholder) {
|
||||
// If placeholder data was requested, return all-white, opaque image data.
|
||||
memset(map.mData, 0xFF, 4 * aSize.width * aSize.height);
|
||||
}
|
||||
rv = aEncoder->InitFromData(map.mData,
|
||||
aSize.width * aSize.height * 4,
|
||||
aSize.width,
|
||||
|
|
|
@ -42,6 +42,7 @@ public:
|
|||
static nsresult ExtractData(nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
const nsIntSize aSize,
|
||||
bool aUsePlaceholder,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
layers::AsyncCanvasRenderer* aRenderer,
|
||||
nsIInputStream** aStream);
|
||||
|
@ -63,6 +64,7 @@ public:
|
|||
UniquePtr<uint8_t[]> aImageBuffer,
|
||||
int32_t aFormat,
|
||||
const nsIntSize aSize,
|
||||
bool aUsePlaceholder,
|
||||
EncodeCompleteCallback* aEncodeCallback);
|
||||
|
||||
// Extract an Image asynchronously. Its function is same as ExtractDataAsync
|
||||
|
@ -74,6 +76,7 @@ public:
|
|||
const nsAString& aOptions,
|
||||
bool aUsingCustomOptions,
|
||||
layers::Image* aImage,
|
||||
bool aUsePlaceholder,
|
||||
EncodeCompleteCallback* aEncodeCallback);
|
||||
|
||||
// Gives you a stream containing the image represented by aImageBuffer.
|
||||
|
@ -95,6 +98,7 @@ private:
|
|||
uint8_t* aImageBuffer,
|
||||
int32_t aFormat,
|
||||
const nsIntSize aSize,
|
||||
bool aUsePlaceholder,
|
||||
layers::Image* aImage,
|
||||
nsICanvasRenderingContextInternal* aContext,
|
||||
layers::AsyncCanvasRenderer* aRenderer,
|
||||
|
|
|
@ -124,7 +124,7 @@ DOMInterfaces = {
|
|||
|
||||
'CanvasRenderingContext2D': {
|
||||
'implicitJSContext': [
|
||||
'createImageData', 'getImageData'
|
||||
'createImageData', 'getImageData', 'isPointInPath', 'isPointInStroke'
|
||||
],
|
||||
'binaryNames': {
|
||||
'mozImageSmoothingEnabled': 'imageSmoothingEnabled'
|
||||
|
|
|
@ -4905,12 +4905,19 @@ CanvasRenderingContext2D::LineDashOffset() const {
|
|||
}
|
||||
|
||||
bool
|
||||
CanvasRenderingContext2D::IsPointInPath(double aX, double aY, const CanvasWindingRule& aWinding)
|
||||
CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, double aX, double aY, const CanvasWindingRule& aWinding)
|
||||
{
|
||||
if (!FloatValidate(aX, aY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for site-specific permission and return false if no permission.
|
||||
if (mCanvasElement) {
|
||||
nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc();
|
||||
if (!ownerDoc || !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx))
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureUserSpacePath(aWinding);
|
||||
if (!mPath) {
|
||||
return false;
|
||||
|
@ -4923,7 +4930,7 @@ CanvasRenderingContext2D::IsPointInPath(double aX, double aY, const CanvasWindin
|
|||
return mPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform());
|
||||
}
|
||||
|
||||
bool CanvasRenderingContext2D::IsPointInPath(const CanvasPath& aPath, double aX, double aY, const CanvasWindingRule& aWinding)
|
||||
bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, const CanvasPath& aPath, double aX, double aY, const CanvasWindingRule& aWinding)
|
||||
{
|
||||
if (!FloatValidate(aX, aY)) {
|
||||
return false;
|
||||
|
@ -4940,12 +4947,19 @@ bool CanvasRenderingContext2D::IsPointInPath(const CanvasPath& aPath, double aX,
|
|||
}
|
||||
|
||||
bool
|
||||
CanvasRenderingContext2D::IsPointInStroke(double aX, double aY)
|
||||
CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, double aX, double aY)
|
||||
{
|
||||
if (!FloatValidate(aX, aY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for site-specific permission and return false if no permission.
|
||||
if (mCanvasElement) {
|
||||
nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc();
|
||||
if (!ownerDoc || !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx))
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureUserSpacePath();
|
||||
if (!mPath) {
|
||||
return false;
|
||||
|
@ -4967,7 +4981,7 @@ CanvasRenderingContext2D::IsPointInStroke(double aX, double aY)
|
|||
return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mTarget->GetTransform());
|
||||
}
|
||||
|
||||
bool CanvasRenderingContext2D::IsPointInStroke(const CanvasPath& aPath, double aX, double aY)
|
||||
bool CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, const CanvasPath& aPath, double aX, double aY)
|
||||
{
|
||||
if (!FloatValidate(aX, aY)) {
|
||||
return false;
|
||||
|
@ -5808,7 +5822,17 @@ CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx,
|
|||
IntRect dstWriteRect = srcReadRect;
|
||||
dstWriteRect.MoveBy(-aX, -aY);
|
||||
|
||||
{
|
||||
// Check for site-specific permission. This check is not needed if the
|
||||
// canvas was created with a docshell (that is only done for special
|
||||
// internal uses).
|
||||
bool usePlaceholder = false;
|
||||
if (mCanvasElement) {
|
||||
nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc();
|
||||
usePlaceholder = !ownerDoc ||
|
||||
!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx);
|
||||
}
|
||||
|
||||
do {
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
bool isShared;
|
||||
uint8_t* data = JS_GetUint8ClampedArrayData(darray, &isShared, nogc);
|
||||
|
@ -5816,6 +5840,13 @@ CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx,
|
|||
|
||||
uint32_t srcStride = rawData.mStride;
|
||||
uint8_t* src = rawData.mData + srcReadRect.y * srcStride + srcReadRect.x * 4;
|
||||
|
||||
// Return all-white, opaque pixel data if no permission.
|
||||
if (usePlaceholder) {
|
||||
memset(data, 0xFF, len.value());
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4;
|
||||
|
||||
if (mOpaque) {
|
||||
|
@ -5827,7 +5858,7 @@ CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx,
|
|||
dst, aWidth * 4, SurfaceFormat::R8G8B8A8,
|
||||
dstWriteRect.Size());
|
||||
}
|
||||
}
|
||||
} while (false);
|
||||
|
||||
readback->Unmap();
|
||||
*aRetval = darray;
|
||||
|
|
|
@ -204,10 +204,10 @@ public:
|
|||
bool DrawCustomFocusRing(mozilla::dom::Element& aElement);
|
||||
void Clip(const CanvasWindingRule& aWinding);
|
||||
void Clip(const CanvasPath& aPath, const CanvasWindingRule& aWinding);
|
||||
bool IsPointInPath(double aX, double aY, const CanvasWindingRule& aWinding);
|
||||
bool IsPointInPath(const CanvasPath& aPath, double aX, double aY, const CanvasWindingRule& aWinding);
|
||||
bool IsPointInStroke(double aX, double aY);
|
||||
bool IsPointInStroke(const CanvasPath& aPath, double aX, double aY);
|
||||
bool IsPointInPath(JSContext* aCx, double aX, double aY, const CanvasWindingRule& aWinding);
|
||||
bool IsPointInPath(JSContext* aCx, const CanvasPath& aPath, double aX, double aY, const CanvasWindingRule& aWinding);
|
||||
bool IsPointInStroke(JSContext* aCx, double aX, double aY);
|
||||
bool IsPointInStroke(JSContext* aCx, const CanvasPath& aPath, double aX, double aY);
|
||||
void FillText(const nsAString& aText, double aX, double aY,
|
||||
const Optional<double>& aMaxWidth,
|
||||
mozilla::ErrorResult& aError);
|
||||
|
|
|
@ -25,6 +25,7 @@ CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
|
|||
BlobCallback& aCallback,
|
||||
const nsAString& aType,
|
||||
JS::Handle<JS::Value> aParams,
|
||||
bool aUsePlaceholder,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
// Encoder callback when encoding is complete.
|
||||
|
@ -58,7 +59,7 @@ CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
|
|||
RefPtr<EncodeCompleteCallback> callback =
|
||||
new EncodeCallback(aGlobal, &aCallback);
|
||||
|
||||
ToBlob(aCx, aGlobal, callback, aType, aParams, aRv);
|
||||
ToBlob(aCx, aGlobal, callback, aType, aParams, aUsePlaceholder, aRv);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -67,6 +68,7 @@ CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
|
|||
EncodeCompleteCallback* aCallback,
|
||||
const nsAString& aType,
|
||||
JS::Handle<JS::Value> aParams,
|
||||
bool aUsePlaceholder,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsAutoString type;
|
||||
|
@ -107,6 +109,7 @@ CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
|
|||
Move(imageBuffer),
|
||||
format,
|
||||
GetWidthHeight(),
|
||||
aUsePlaceholder,
|
||||
callback);
|
||||
}
|
||||
|
||||
|
|
|
@ -58,11 +58,11 @@ protected:
|
|||
|
||||
void ToBlob(JSContext* aCx, nsIGlobalObject* global, BlobCallback& aCallback,
|
||||
const nsAString& aType, JS::Handle<JS::Value> aParams,
|
||||
ErrorResult& aRv);
|
||||
bool aUsePlaceholder, ErrorResult& aRv);
|
||||
|
||||
void ToBlob(JSContext* aCx, nsIGlobalObject* aGlobal, EncodeCompleteCallback* aCallback,
|
||||
const nsAString& aType, JS::Handle<JS::Value> aParams,
|
||||
ErrorResult& aRv);
|
||||
bool aUsePlaceholder, ErrorResult& aRv);
|
||||
|
||||
virtual already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
CreateContext(CanvasContextType aContextType);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "nsICanvasRenderingContextInternal.h"
|
||||
#include "nsIHTMLCollection.h"
|
||||
#include "mozilla/dom/HTMLCanvasElement.h"
|
||||
#include "mozilla/dom/TabChild.h"
|
||||
#include "nsIPrincipal.h"
|
||||
|
||||
#include "nsGfxCIID.h"
|
||||
|
@ -23,11 +24,150 @@
|
|||
#include "mozilla/gfx/Matrix.h"
|
||||
#include "WebGL2Context.h"
|
||||
|
||||
#include "nsIScriptObjectPrincipal.h"
|
||||
#include "nsIPermissionManager.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozIThirdPartyUtil.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsUnicharUtils.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nsIConsoleService.h"
|
||||
#include "jsapi.h"
|
||||
|
||||
#define TOPIC_CANVAS_PERMISSIONS_PROMPT "canvas-permissions-prompt"
|
||||
#define PERMISSION_CANVAS_EXTRACT_DATA "canvas/extractData"
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
namespace mozilla {
|
||||
namespace CanvasUtils {
|
||||
|
||||
bool IsImageExtractionAllowed(nsIDocument *aDocument, JSContext *aCx)
|
||||
{
|
||||
// Do the rest of the checks only if privacy.resistFingerprinting is on.
|
||||
if (!nsContentUtils::ShouldResistFingerprinting()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't proceed if we don't have a document or JavaScript context.
|
||||
if (!aDocument || !aCx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Documents with system principal can always extract canvas data.
|
||||
nsPIDOMWindowOuter *win = aDocument->GetWindow();
|
||||
nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(win));
|
||||
if (sop && nsContentUtils::IsSystemPrincipal(sop->GetPrincipal())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Always give permission to chrome scripts (e.g. Page Inspector).
|
||||
if (nsContentUtils::ThreadsafeIsCallerChrome()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the document URI and its spec.
|
||||
nsIURI *docURI = aDocument->GetDocumentURI();
|
||||
nsCString docURISpec;
|
||||
docURI->GetSpec(docURISpec);
|
||||
|
||||
// Allow local files to extract canvas data.
|
||||
bool isFileURL;
|
||||
(void) docURI->SchemeIs("file", &isFileURL);
|
||||
if (isFileURL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get calling script file and line for logging.
|
||||
JS::AutoFilename scriptFile;
|
||||
unsigned scriptLine = 0;
|
||||
bool isScriptKnown = false;
|
||||
if (JS::DescribeScriptedCaller(aCx, &scriptFile, &scriptLine)) {
|
||||
isScriptKnown = true;
|
||||
// Don't show canvas prompt for PDF.js
|
||||
if (scriptFile.get() &&
|
||||
strcmp(scriptFile.get(), "resource://pdf.js/build/pdf.js") == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
nsIDocument* topLevelDocument = aDocument->GetTopLevelContentDocument();
|
||||
nsIURI *topLevelDocURI = topLevelDocument ? topLevelDocument->GetDocumentURI() : nullptr;
|
||||
nsCString topLevelDocURISpec;
|
||||
if (topLevelDocURI) {
|
||||
topLevelDocURI->GetSpec(topLevelDocURISpec);
|
||||
}
|
||||
|
||||
// Load Third Party Util service.
|
||||
nsresult rv;
|
||||
nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
|
||||
do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
// Block all third-party attempts to extract canvas.
|
||||
bool isThirdParty = true;
|
||||
rv = thirdPartyUtil->IsThirdPartyURI(topLevelDocURI, docURI, &isThirdParty);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
if (isThirdParty) {
|
||||
nsAutoCString message;
|
||||
message.AppendPrintf("Blocked third party %s in page %s from extracting canvas data.",
|
||||
docURISpec.get(), topLevelDocURISpec.get());
|
||||
if (isScriptKnown) {
|
||||
message.AppendPrintf(" %s:%u.", scriptFile.get(), scriptLine);
|
||||
}
|
||||
nsContentUtils::LogMessageToConsole(message.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load Permission Manager service.
|
||||
nsCOMPtr<nsIPermissionManager> permissionManager =
|
||||
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
// Check if the site has permission to extract canvas data.
|
||||
// Either permit or block extraction if a stored permission setting exists.
|
||||
uint32_t permission;
|
||||
rv = permissionManager->TestPermission(topLevelDocURI,
|
||||
PERMISSION_CANVAS_EXTRACT_DATA,
|
||||
&permission);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
switch (permission) {
|
||||
case nsIPermissionManager::ALLOW_ACTION:
|
||||
return true;
|
||||
case nsIPermissionManager::DENY_ACTION:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// At this point, permission is unknown (nsIPermissionManager::UNKNOWN_ACTION).
|
||||
nsAutoCString message;
|
||||
message.AppendPrintf("Blocked %s in page %s from extracting canvas data.",
|
||||
docURISpec.get(), topLevelDocURISpec.get());
|
||||
if (isScriptKnown) {
|
||||
message.AppendPrintf(" %s:%u.", scriptFile.get(), scriptLine);
|
||||
}
|
||||
nsContentUtils::LogMessageToConsole(message.get());
|
||||
|
||||
// Prompt the user (asynchronous).
|
||||
if (XRE_IsContentProcess()) {
|
||||
TabChild* tabChild = TabChild::GetFrom(win);
|
||||
if (tabChild) {
|
||||
tabChild->SendShowCanvasPermissionPrompt(topLevelDocURISpec);
|
||||
}
|
||||
} else {
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
if (obs) {
|
||||
obs->NotifyObservers(win, TOPIC_CANVAS_PERMISSIONS_PROMPT,
|
||||
NS_ConvertUTF8toUTF16(topLevelDocURISpec).get());
|
||||
}
|
||||
}
|
||||
|
||||
// We don't extract the image for now -- user may override at prompt.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
GetCanvasContextType(const nsAString& str, dom::CanvasContextType* const out_type)
|
||||
{
|
||||
|
|
|
@ -49,6 +49,9 @@ void DoDrawImageSecurityCheck(dom::HTMLCanvasElement *aCanvasElement,
|
|||
// Check if the context is chrome or has the permission to drawWindow
|
||||
bool HasDrawWindowPrivilege(JSContext* aCx, JSObject* aObj);
|
||||
|
||||
// Check site-specific permission and display prompt if appropriate.
|
||||
bool IsImageExtractionAllowed(nsIDocument *aDocument, JSContext *aCx);
|
||||
|
||||
// Make a double out of |v|, treating undefined values as 0.0 (for
|
||||
// the sake of sparse arrays). Return true iff coercion
|
||||
// succeeded.
|
||||
|
|
|
@ -286,8 +286,13 @@ OffscreenCanvas::ToBlob(JSContext* aCx,
|
|||
RefPtr<EncodeCompleteCallback> callback =
|
||||
new EncodeCallback(global, promise);
|
||||
|
||||
CanvasRenderingContextHelper::ToBlob(aCx, global,
|
||||
callback, aType, aParams, aRv);
|
||||
// TODO: Can we obtain the context and document here somehow
|
||||
// so that we can decide when usePlaceholder should be true/false?
|
||||
// See https://trac.torproject.org/18599
|
||||
// For now, we always return a placeholder if fingerprinting resistance is on.
|
||||
bool usePlaceholder = nsContentUtils::ShouldResistFingerprinting();
|
||||
CanvasRenderingContextHelper::ToBlob(aCx, global, callback, aType, aParams,
|
||||
usePlaceholder, aRv);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#include "nsRefreshDriver.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "ActiveLayerTracker.h"
|
||||
#include "CanvasUtils.h"
|
||||
#include "VRManagerChild.h"
|
||||
#include "WebGL1Context.h"
|
||||
#include "WebGL2Context.h"
|
||||
|
@ -62,8 +63,10 @@ class RequestedFrameRefreshObserver : public nsARefreshObserver
|
|||
|
||||
public:
|
||||
RequestedFrameRefreshObserver(HTMLCanvasElement* const aOwningElement,
|
||||
nsRefreshDriver* aRefreshDriver)
|
||||
nsRefreshDriver* aRefreshDriver,
|
||||
bool aReturnPlaceholderData)
|
||||
: mRegistered(false),
|
||||
mReturnPlaceholderData(aReturnPlaceholderData),
|
||||
mOwningElement(aOwningElement),
|
||||
mRefreshDriver(aRefreshDriver)
|
||||
{
|
||||
|
@ -71,7 +74,8 @@ public:
|
|||
}
|
||||
|
||||
static already_AddRefed<DataSourceSurface>
|
||||
CopySurface(const RefPtr<SourceSurface>& aSurface)
|
||||
CopySurface(const RefPtr<SourceSurface>& aSurface,
|
||||
bool aReturnPlaceholderData)
|
||||
{
|
||||
RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
|
||||
if (!data) {
|
||||
|
@ -100,12 +104,23 @@ public:
|
|||
MOZ_ASSERT(data->GetSize() == copy->GetSize());
|
||||
MOZ_ASSERT(data->GetFormat() == copy->GetFormat());
|
||||
|
||||
memcpy(write.GetData(), read.GetData(),
|
||||
write.GetStride() * copy->GetSize().height);
|
||||
if (aReturnPlaceholderData) {
|
||||
// If returning placeholder data, fill the frame copy with white pixels.
|
||||
memset(write.GetData(), 0xFF,
|
||||
write.GetStride() * copy->GetSize().height);
|
||||
} else {
|
||||
memcpy(write.GetData(), read.GetData(),
|
||||
write.GetStride() * copy->GetSize().height);
|
||||
}
|
||||
|
||||
return copy.forget();
|
||||
}
|
||||
|
||||
void SetReturnPlaceholderData(bool aReturnPlaceholderData)
|
||||
{
|
||||
mReturnPlaceholderData = aReturnPlaceholderData;
|
||||
}
|
||||
|
||||
void WillRefresh(TimeStamp aTime) override
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
@ -144,7 +159,7 @@ public:
|
|||
{
|
||||
AUTO_PROFILER_LABEL(
|
||||
"RequestedFrameRefreshObserver::WillRefresh:CopySurface", OTHER);
|
||||
copy = CopySurface(snapshot);
|
||||
copy = CopySurface(snapshot, mReturnPlaceholderData);
|
||||
if (!copy) {
|
||||
return;
|
||||
}
|
||||
|
@ -201,6 +216,7 @@ private:
|
|||
}
|
||||
|
||||
bool mRegistered;
|
||||
bool mReturnPlaceholderData;
|
||||
HTMLCanvasElement* const mOwningElement;
|
||||
RefPtr<nsRefreshDriver> mRefreshDriver;
|
||||
};
|
||||
|
@ -754,7 +770,14 @@ HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate,
|
|||
new CanvasCaptureTrackSource(principal, stream));
|
||||
stream->AddTrackInternal(track);
|
||||
|
||||
rv = RegisterFrameCaptureListener(stream->FrameCaptureListener());
|
||||
// Check site-specific permission and display prompt if appropriate.
|
||||
// If no permission, arrange for the frame capture listener to return
|
||||
// all-white, opaque image data.
|
||||
bool usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(
|
||||
OwnerDoc(),
|
||||
nsContentUtils::GetCurrentJSContext());
|
||||
|
||||
rv = RegisterFrameCaptureListener(stream->FrameCaptureListener(), usePlaceholder);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return nullptr;
|
||||
|
@ -764,13 +787,18 @@ HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate,
|
|||
}
|
||||
|
||||
nsresult
|
||||
HTMLCanvasElement::ExtractData(nsAString& aType,
|
||||
HTMLCanvasElement::ExtractData(JSContext* aCx,
|
||||
nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
nsIInputStream** aStream)
|
||||
{
|
||||
// Check site-specific permission and display prompt if appropriate.
|
||||
// If no permission, return all-white, opaque image data.
|
||||
bool usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(OwnerDoc(), aCx);
|
||||
return ImageEncoder::ExtractData(aType,
|
||||
aOptions,
|
||||
GetSize(),
|
||||
usePlaceholder,
|
||||
mCurrentContext,
|
||||
mAsyncCanvasRenderer,
|
||||
aStream);
|
||||
|
@ -800,12 +828,12 @@ HTMLCanvasElement::ToDataURLImpl(JSContext* aCx,
|
|||
}
|
||||
|
||||
nsCOMPtr<nsIInputStream> stream;
|
||||
rv = ExtractData(type, params, getter_AddRefs(stream));
|
||||
rv = ExtractData(aCx, type, params, getter_AddRefs(stream));
|
||||
|
||||
// If there are unrecognized custom parse options, we should fall back to
|
||||
// the default values for the encoder without any options at all.
|
||||
if (rv == NS_ERROR_INVALID_ARG && usingCustomParseOptions) {
|
||||
rv = ExtractData(type, EmptyString(), getter_AddRefs(stream));
|
||||
rv = ExtractData(aCx, type, EmptyString(), getter_AddRefs(stream));
|
||||
}
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
@ -855,8 +883,11 @@ HTMLCanvasElement::ToBlob(JSContext* aCx,
|
|||
return;
|
||||
}
|
||||
|
||||
// Check site-specific permission and display prompt if appropriate.
|
||||
// If no permission, return all-white, opaque image data.
|
||||
bool usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(OwnerDoc(), aCx);
|
||||
CanvasRenderingContextHelper::ToBlob(aCx, global, aCallback, aType,
|
||||
aParams, aRv);
|
||||
aParams, usePlaceholder, aRv);
|
||||
|
||||
}
|
||||
|
||||
|
@ -925,7 +956,8 @@ HTMLCanvasElement::MozGetAsFileImpl(const nsAString& aName,
|
|||
{
|
||||
nsCOMPtr<nsIInputStream> stream;
|
||||
nsAutoString type(aType);
|
||||
nsresult rv = ExtractData(type, EmptyString(), getter_AddRefs(stream));
|
||||
nsresult rv = ExtractData(nsContentUtils::GetCurrentJSContext(),
|
||||
type, EmptyString(), getter_AddRefs(stream));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
uint64_t imgSize;
|
||||
|
@ -1246,7 +1278,8 @@ HTMLCanvasElement::IsContextCleanForFrameCapture()
|
|||
}
|
||||
|
||||
nsresult
|
||||
HTMLCanvasElement::RegisterFrameCaptureListener(FrameCaptureListener* aListener)
|
||||
HTMLCanvasElement::RegisterFrameCaptureListener(FrameCaptureListener* aListener,
|
||||
bool aReturnPlaceholderData)
|
||||
{
|
||||
WeakPtr<FrameCaptureListener> listener = aListener;
|
||||
|
||||
|
@ -1285,7 +1318,9 @@ HTMLCanvasElement::RegisterFrameCaptureListener(FrameCaptureListener* aListener)
|
|||
}
|
||||
|
||||
mRequestedFrameRefreshObserver =
|
||||
new RequestedFrameRefreshObserver(this, driver);
|
||||
new RequestedFrameRefreshObserver(this, driver, aReturnPlaceholderData);
|
||||
} else {
|
||||
mRequestedFrameRefreshObserver->SetReturnPlaceholderData(aReturnPlaceholderData);
|
||||
}
|
||||
|
||||
mRequestedFrameListeners.AppendElement(listener);
|
||||
|
|
|
@ -262,7 +262,8 @@ public:
|
|||
* caller's responsibility to keep them alive. Once a registered
|
||||
* FrameCaptureListener is destroyed it will be automatically deregistered.
|
||||
*/
|
||||
nsresult RegisterFrameCaptureListener(FrameCaptureListener* aListener);
|
||||
nsresult RegisterFrameCaptureListener(FrameCaptureListener* aListener,
|
||||
bool aReturnPlaceholderData);
|
||||
|
||||
/*
|
||||
* Returns true when there is at least one registered FrameCaptureListener
|
||||
|
@ -348,7 +349,8 @@ protected:
|
|||
virtual already_AddRefed<nsICanvasRenderingContextInternal>
|
||||
CreateContext(CanvasContextType aContextType) override;
|
||||
|
||||
nsresult ExtractData(nsAString& aType,
|
||||
nsresult ExtractData(JSContext* aCx,
|
||||
nsAString& aType,
|
||||
const nsAString& aOptions,
|
||||
nsIInputStream** aStream);
|
||||
nsresult ToDataURLImpl(JSContext* aCx,
|
||||
|
|
|
@ -1860,6 +1860,8 @@ void HTMLMediaElement::AbortExistingLoads()
|
|||
|
||||
mEventDeliveryPaused = false;
|
||||
mPendingEvents.Clear();
|
||||
|
||||
AssertReadyStateIsNothing();
|
||||
}
|
||||
|
||||
void HTMLMediaElement::NoSupportedMediaSourceError(const nsACString& aErrorDetails)
|
||||
|
@ -1921,6 +1923,8 @@ void HTMLMediaElement::QueueLoadFromSourceTask()
|
|||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING);
|
||||
}
|
||||
|
||||
AssertReadyStateIsNothing();
|
||||
|
||||
ChangeDelayLoadStatus(true);
|
||||
ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_LOADING);
|
||||
RefPtr<Runnable> r = NewRunnableMethod("HTMLMediaElement::LoadFromSourceChildren",
|
||||
|
@ -2579,6 +2583,8 @@ nsresult HTMLMediaElement::LoadResource()
|
|||
return FinishDecoderSetup(decoder);
|
||||
}
|
||||
|
||||
AssertReadyStateIsNothing();
|
||||
|
||||
RefPtr<ChannelLoader> loader = new ChannelLoader;
|
||||
nsresult rv = loader->Load(this);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
|
@ -4868,12 +4874,36 @@ HTMLMediaElement::CanPlayType(const nsAString& aType, nsAString& aResult)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
HTMLMediaElement::AssertReadyStateIsNothing()
|
||||
{
|
||||
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
||||
if (mReadyState != nsIDOMHTMLMediaElement::HAVE_NOTHING) {
|
||||
char buf[1024];
|
||||
SprintfLiteral(buf,
|
||||
"readyState=%d networkState=%d mLoadWaitStatus=%d "
|
||||
"mSourceLoadCandidate=%d "
|
||||
"mIsLoadingFromSourceChildren=%d mPreloadAction=%d "
|
||||
"mSuspendedForPreloadNone=%d error=%d",
|
||||
int(mReadyState.Ref()),
|
||||
int(mNetworkState),
|
||||
int(mLoadWaitStatus),
|
||||
!!mSourceLoadCandidate,
|
||||
mIsLoadingFromSourceChildren,
|
||||
int(mPreloadAction),
|
||||
mSuspendedForPreloadNone,
|
||||
GetError() ? GetError()->Code() : 0);
|
||||
MOZ_CRASH_UNSAFE_PRINTF("ReadyState should be HAVE_NOTHING! %s", buf);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
nsresult
|
||||
HTMLMediaElement::InitializeDecoderAsClone(ChannelMediaDecoder* aOriginal)
|
||||
{
|
||||
NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
|
||||
NS_ASSERTION(mDecoder == nullptr, "Shouldn't have a decoder");
|
||||
MOZ_DIAGNOSTIC_ASSERT(mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING);
|
||||
AssertReadyStateIsNothing();
|
||||
|
||||
MediaDecoderInit decoderInit(this,
|
||||
mMuted ? 0.0 : mVolume,
|
||||
|
@ -4927,7 +4957,7 @@ nsresult HTMLMediaElement::InitializeDecoderForChannel(nsIChannel* aChannel,
|
|||
nsIStreamListener** aListener)
|
||||
{
|
||||
NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
|
||||
MOZ_DIAGNOSTIC_ASSERT(mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING);
|
||||
AssertReadyStateIsNothing();
|
||||
|
||||
DecoderDoctorDiagnostics diagnostics;
|
||||
|
||||
|
@ -5041,6 +5071,10 @@ HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder)
|
|||
// This will also do an AddRemoveSelfReference.
|
||||
NotifyOwnerDocumentActivityChanged();
|
||||
|
||||
if (mPausedForInactiveDocumentOrChannel) {
|
||||
mDecoder->Suspend();
|
||||
}
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
if (!mPaused) {
|
||||
SetPlayedOrSeeked(true);
|
||||
|
@ -6586,6 +6620,7 @@ void HTMLMediaElement::NotifyAddedSource()
|
|||
if (!HasAttr(kNameSpaceID_None, nsGkAtoms::src) &&
|
||||
mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY)
|
||||
{
|
||||
AssertReadyStateIsNothing();
|
||||
QueueSelectResourceTask();
|
||||
}
|
||||
|
||||
|
|
|
@ -1820,6 +1820,9 @@ private:
|
|||
// resolved/rejected at AsyncResolveSeekDOMPromiseIfExists()/
|
||||
// AsyncRejectSeekDOMPromiseIfExists() methods.
|
||||
RefPtr<dom::Promise> mSeekDOMPromise;
|
||||
|
||||
// For debugging bug 1407148.
|
||||
void AssertReadyStateIsNothing();
|
||||
};
|
||||
|
||||
// Check if the context is chrome or has the debugger or tabs permission
|
||||
|
|
|
@ -85,6 +85,7 @@ function* testSteps()
|
|||
ok(!("exception" in info), "shouldn't throw" + test);
|
||||
is(JSON.stringify(objectStore.keyPath), JSON.stringify(info.keyPath),
|
||||
"correct keyPath property" + test);
|
||||
// eslint-disable-next-line no-self-compare
|
||||
ok(objectStore.keyPath === objectStore.keyPath,
|
||||
"object identity should be preserved");
|
||||
stores[indexName] = objectStore;
|
||||
|
@ -174,6 +175,7 @@ function* testSteps()
|
|||
ok(!("exception" in info), "shouldn't throw" + test);
|
||||
is(JSON.stringify(index.keyPath), JSON.stringify(info.keyPath),
|
||||
"index has correct keyPath property" + test);
|
||||
// eslint-disable-next-line no-self-compare
|
||||
ok(index.keyPath === index.keyPath,
|
||||
"object identity should be preserved");
|
||||
indexes[indexName] = index;
|
||||
|
|
|
@ -594,6 +594,14 @@ parent:
|
|||
*/
|
||||
async RequestCrossBrowserNavigation(uint32_t aGlobalIndex);
|
||||
|
||||
/**
|
||||
* This function is used to notify the parent that it should display a
|
||||
* canvas permission prompt.
|
||||
*
|
||||
* @param aFirstPartyURI first party of the tab that is requesting access.
|
||||
*/
|
||||
async ShowCanvasPermissionPrompt(nsCString aFirstPartyURI);
|
||||
|
||||
child:
|
||||
/**
|
||||
* Notify the remote browser that it has been Show()n on this
|
||||
|
|
|
@ -3604,6 +3604,27 @@ TabParent::RecvRequestCrossBrowserNavigation(const uint32_t& aGlobalIndex)
|
|||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
TabParent::RecvShowCanvasPermissionPrompt(const nsCString& aFirstPartyURI)
|
||||
{
|
||||
nsCOMPtr<nsIBrowser> browser = do_QueryInterface(mFrameElement);
|
||||
if (!browser) {
|
||||
// If the tab is being closed, the browser may not be available.
|
||||
// In this case we can ignore the request.
|
||||
return IPC_OK();
|
||||
}
|
||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||
if (!os) {
|
||||
return IPC_FAIL_NO_REASON(this);
|
||||
}
|
||||
nsresult rv = os->NotifyObservers(browser, "canvas-permissions-prompt",
|
||||
NS_ConvertUTF8toUTF16(aFirstPartyURI).get());
|
||||
if (NS_FAILED(rv)) {
|
||||
return IPC_FAIL_NO_REASON(this);
|
||||
}
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
void
|
||||
TabParent::LiveResizeStarted()
|
||||
{
|
||||
|
|
|
@ -648,6 +648,7 @@ protected:
|
|||
const bool& aTruncate) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvRequestCrossBrowserNavigation(const uint32_t& aGlobalIndex) override;
|
||||
virtual mozilla::ipc::IPCResult RecvShowCanvasPermissionPrompt(const nsCString& aFirstPartyURI) override;
|
||||
|
||||
ContentCacheInParent mContentCache;
|
||||
|
||||
|
|
|
@ -289,11 +289,18 @@ public:
|
|||
!mImage.height) {
|
||||
return ImageRect();
|
||||
}
|
||||
|
||||
gfx::IntRect imageRect = ImageRect();
|
||||
int64_t w = (aWidth * imageRect.Width()) / mImage.width;
|
||||
int64_t h = (aHeight * imageRect.Height()) / mImage.height;
|
||||
if (!w || !h) {
|
||||
return imageRect;
|
||||
}
|
||||
|
||||
imageRect.x = (imageRect.x * aWidth) / mImage.width;
|
||||
imageRect.y = (imageRect.y * aHeight) / mImage.height;
|
||||
imageRect.SetWidth((aWidth * imageRect.Width()) / mImage.width);
|
||||
imageRect.SetHeight((aHeight * imageRect.Height()) / mImage.height);
|
||||
imageRect.SetWidth(w);
|
||||
imageRect.SetHeight(h);
|
||||
return imageRect;
|
||||
}
|
||||
|
||||
|
|
|
@ -157,6 +157,7 @@ CaptureTask::SetCurrentFrames(const VideoSegment& aSegment)
|
|||
options,
|
||||
false,
|
||||
image,
|
||||
false,
|
||||
new EncodeComplete(this));
|
||||
if (NS_FAILED(rv)) {
|
||||
PostTrackEndEvent();
|
||||
|
|
|
@ -96,18 +96,21 @@ TrackBuffersManager::TrackBuffersManager(MediaSourceDecoder* aParentDecoder,
|
|||
, mType(aType)
|
||||
, mParser(ContainerParser::CreateForMIMEType(aType))
|
||||
, mProcessedInput(0)
|
||||
, mTaskQueue(aParentDecoder->GetDemuxer()->GetTaskQueue())
|
||||
, mParentDecoder(
|
||||
new nsMainThreadPtrHolder<MediaSourceDecoder>(
|
||||
"TrackBuffersManager::mParentDecoder", aParentDecoder, false /* strict */))
|
||||
, mParentDecoder(new nsMainThreadPtrHolder<MediaSourceDecoder>(
|
||||
"TrackBuffersManager::mParentDecoder",
|
||||
aParentDecoder,
|
||||
false /* strict */))
|
||||
, mAbstractMainThread(aParentDecoder->AbstractMainThread())
|
||||
, mEnded(false)
|
||||
, mVideoEvictionThreshold(Preferences::GetUint("media.mediasource.eviction_threshold.video",
|
||||
100 * 1024 * 1024))
|
||||
, mAudioEvictionThreshold(Preferences::GetUint("media.mediasource.eviction_threshold.audio",
|
||||
20 * 1024 * 1024))
|
||||
, mVideoEvictionThreshold(
|
||||
Preferences::GetUint("media.mediasource.eviction_threshold.video",
|
||||
100 * 1024 * 1024))
|
||||
, mAudioEvictionThreshold(
|
||||
Preferences::GetUint("media.mediasource.eviction_threshold.audio",
|
||||
20 * 1024 * 1024))
|
||||
, mEvictionState(EvictionState::NO_EVICTION_NEEDED)
|
||||
, mMonitor("TrackBuffersManager")
|
||||
, mMutex("TrackBuffersManager")
|
||||
, mTaskQueue(aParentDecoder->GetDemuxer()->GetTaskQueue())
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Must be instanciated on the main thread");
|
||||
}
|
||||
|
@ -127,8 +130,12 @@ TrackBuffersManager::AppendData(already_AddRefed<MediaByteBuffer> aData,
|
|||
|
||||
mEnded = false;
|
||||
|
||||
return InvokeAsync(GetTaskQueue(), this, __func__,
|
||||
&TrackBuffersManager::DoAppendData, data.forget(), aAttributes);
|
||||
return InvokeAsync(static_cast<AbstractThread*>(GetTaskQueueSafe().get()),
|
||||
this,
|
||||
__func__,
|
||||
&TrackBuffersManager::DoAppendData,
|
||||
data.forget(),
|
||||
aAttributes);
|
||||
}
|
||||
|
||||
RefPtr<TrackBuffersManager::AppendPromise>
|
||||
|
@ -148,7 +155,8 @@ TrackBuffersManager::QueueTask(SourceBufferTask* aTask)
|
|||
// The source buffer is a wrapped native, it would be unlinked twice and so
|
||||
// the TrackBuffersManager::Detach() would also be called twice. Since the
|
||||
// detach task has been done before, we could ignore this task.
|
||||
if (!GetTaskQueue()) {
|
||||
RefPtr<AutoTaskQueue> taskQueue = GetTaskQueueSafe();
|
||||
if (!taskQueue) {
|
||||
MOZ_ASSERT(aTask->GetType() == SourceBufferTask::Type::Detach,
|
||||
"only detach task could happen here!");
|
||||
MSE_DEBUG("Could not queue the task '%s' without task queue",
|
||||
|
@ -156,15 +164,14 @@ TrackBuffersManager::QueueTask(SourceBufferTask* aTask)
|
|||
return;
|
||||
}
|
||||
|
||||
if (!OnTaskQueue()) {
|
||||
GetTaskQueue()->Dispatch(NewRunnableMethod<RefPtr<SourceBufferTask>>(
|
||||
if (!taskQueue->IsCurrentThreadIn()) {
|
||||
taskQueue->Dispatch(NewRunnableMethod<RefPtr<SourceBufferTask>>(
|
||||
"TrackBuffersManager::QueueTask",
|
||||
this,
|
||||
&TrackBuffersManager::QueueTask,
|
||||
aTask));
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
mQueue.Push(aTask);
|
||||
ProcessTasks();
|
||||
}
|
||||
|
@ -172,8 +179,16 @@ TrackBuffersManager::QueueTask(SourceBufferTask* aTask)
|
|||
void
|
||||
TrackBuffersManager::ProcessTasks()
|
||||
{
|
||||
// ProcessTask is always called OnTaskQueue, however it is possible that it is
|
||||
// called once again after a first Detach task has run, in which case
|
||||
// mTaskQueue would be null.
|
||||
// This can happen under two conditions:
|
||||
// 1- Two Detach tasks were queued in a row due to a double cycle collection.
|
||||
// 2- An call to ProcessTasks() had queued another run of ProcessTasks while
|
||||
// a Detach task is pending.
|
||||
// We handle these two cases by aborting early.
|
||||
// A second Detach task was queued, prior the first one running, ignore it.
|
||||
if (!GetTaskQueue()) {
|
||||
if (!mTaskQueue) {
|
||||
RefPtr<SourceBufferTask> task = mQueue.Pop();
|
||||
if (!task) {
|
||||
return;
|
||||
|
@ -234,17 +249,17 @@ TrackBuffersManager::ProcessTasks()
|
|||
break;
|
||||
case Type::Detach:
|
||||
mCurrentInputBuffer = nullptr;
|
||||
mTaskQueue = nullptr;
|
||||
MOZ_DIAGNOSTIC_ASSERT(mQueue.Length() == 0,
|
||||
"Detach task must be the last");
|
||||
mVideoTracks.Reset();
|
||||
mAudioTracks.Reset();
|
||||
ShutdownDemuxers();
|
||||
ResetTaskQueue();
|
||||
return;
|
||||
default:
|
||||
NS_WARNING("Invalid Task");
|
||||
}
|
||||
GetTaskQueue()->Dispatch(
|
||||
TaskQueueFromTaskQueue()->Dispatch(
|
||||
NewRunnableMethod("TrackBuffersManager::ProcessTasks",
|
||||
this,
|
||||
&TrackBuffersManager::ProcessTasks));
|
||||
|
@ -295,7 +310,9 @@ TrackBuffersManager::RangeRemoval(TimeUnit aStart, TimeUnit aEnd)
|
|||
|
||||
mEnded = false;
|
||||
|
||||
return InvokeAsync(GetTaskQueue(), this, __func__,
|
||||
return InvokeAsync(static_cast<AbstractThread*>(GetTaskQueueSafe().get()),
|
||||
this,
|
||||
__func__,
|
||||
&TrackBuffersManager::CodedFrameRemovalWithPromise,
|
||||
TimeInterval(aStart, aEnd));
|
||||
}
|
||||
|
@ -351,7 +368,7 @@ TrackBuffersManager::Buffered() const
|
|||
|
||||
// http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered
|
||||
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
nsTArray<const TimeIntervals*> tracks;
|
||||
if (HasVideo()) {
|
||||
tracks.AppendElement(&mVideoBufferedRanges);
|
||||
|
@ -635,7 +652,7 @@ TrackBuffersManager::CodedFrameRemoval(TimeInterval aInterval)
|
|||
void
|
||||
TrackBuffersManager::UpdateBufferedRanges()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
|
||||
mVideoBufferedRanges = mVideoTracks.mSanitizedBufferedRanges;
|
||||
mAudioBufferedRanges = mAudioTracks.mSanitizedBufferedRanges;
|
||||
|
@ -775,8 +792,9 @@ TrackBuffersManager::SegmentParserLoop()
|
|||
// 3. If the input buffer contains one or more complete coded frames, then run the coded frame processing algorithm.
|
||||
RefPtr<TrackBuffersManager> self = this;
|
||||
CodedFrameProcessing()
|
||||
->Then(GetTaskQueue(), __func__,
|
||||
[self] (bool aNeedMoreData) {
|
||||
->Then(TaskQueueFromTaskQueue(),
|
||||
__func__,
|
||||
[self](bool aNeedMoreData) {
|
||||
self->mProcessingRequest.Complete();
|
||||
if (aNeedMoreData) {
|
||||
self->NeedMoreData();
|
||||
|
@ -784,7 +802,7 @@ TrackBuffersManager::SegmentParserLoop()
|
|||
self->ScheduleSegmentParserLoop();
|
||||
}
|
||||
},
|
||||
[self] (const MediaResult& aRejectValue) {
|
||||
[self](const MediaResult& aRejectValue) {
|
||||
self->mProcessingRequest.Complete();
|
||||
self->RejectAppend(aRejectValue, __func__);
|
||||
})
|
||||
|
@ -825,7 +843,8 @@ TrackBuffersManager::RejectAppend(const MediaResult& aRejectValue, const char* a
|
|||
void
|
||||
TrackBuffersManager::ScheduleSegmentParserLoop()
|
||||
{
|
||||
GetTaskQueue()->Dispatch(
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
TaskQueueFromTaskQueue()->Dispatch(
|
||||
NewRunnableMethod("TrackBuffersManager::SegmentParserLoop",
|
||||
this,
|
||||
&TrackBuffersManager::SegmentParserLoop));
|
||||
|
@ -874,6 +893,7 @@ TrackBuffersManager::CreateDemuxerforMIMEType()
|
|||
void
|
||||
TrackBuffersManager::ResetDemuxingState()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
MOZ_ASSERT(mParser && mParser->HasInitData());
|
||||
RecreateParser(true);
|
||||
mCurrentInputBuffer = new SourceBufferResource();
|
||||
|
@ -887,7 +907,8 @@ TrackBuffersManager::ResetDemuxingState()
|
|||
return;
|
||||
}
|
||||
mInputDemuxer->Init()
|
||||
->Then(GetTaskQueue(), __func__,
|
||||
->Then(TaskQueueFromTaskQueue(),
|
||||
__func__,
|
||||
this,
|
||||
&TrackBuffersManager::OnDemuxerResetDone,
|
||||
&TrackBuffersManager::OnDemuxerInitFailed)
|
||||
|
@ -958,6 +979,7 @@ TrackBuffersManager::AppendDataToCurrentInputBuffer(MediaByteBuffer* aData)
|
|||
void
|
||||
TrackBuffersManager::InitializationSegmentReceived()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
MOZ_ASSERT(mParser->HasCompleteInitData());
|
||||
|
||||
int64_t endInit = mParser->InitSegmentRange().mEnd;
|
||||
|
@ -989,7 +1011,8 @@ TrackBuffersManager::InitializationSegmentReceived()
|
|||
return;
|
||||
}
|
||||
mInputDemuxer->Init()
|
||||
->Then(GetTaskQueue(), __func__,
|
||||
->Then(TaskQueueFromTaskQueue(),
|
||||
__func__,
|
||||
this,
|
||||
&TrackBuffersManager::OnDemuxerInitDone,
|
||||
&TrackBuffersManager::OnDemuxerInitFailed)
|
||||
|
@ -1177,7 +1200,7 @@ TrackBuffersManager::OnDemuxerInitDone(const MediaResult& aResult)
|
|||
}
|
||||
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
mInfo = info;
|
||||
}
|
||||
|
||||
|
@ -1292,7 +1315,9 @@ TrackBuffersManager::DoDemuxVideo()
|
|||
return;
|
||||
}
|
||||
mVideoTracks.mDemuxer->GetSamples(-1)
|
||||
->Then(GetTaskQueue(), __func__, this,
|
||||
->Then(TaskQueueFromTaskQueue(),
|
||||
__func__,
|
||||
this,
|
||||
&TrackBuffersManager::OnVideoDemuxCompleted,
|
||||
&TrackBuffersManager::OnVideoDemuxFailed)
|
||||
->Track(mVideoTracks.mDemuxRequest);
|
||||
|
@ -1334,7 +1359,9 @@ TrackBuffersManager::DoDemuxAudio()
|
|||
return;
|
||||
}
|
||||
mAudioTracks.mDemuxer->GetSamples(-1)
|
||||
->Then(GetTaskQueue(), __func__, this,
|
||||
->Then(TaskQueueFromTaskQueue(),
|
||||
__func__,
|
||||
this,
|
||||
&TrackBuffersManager::OnAudioDemuxCompleted,
|
||||
&TrackBuffersManager::OnAudioDemuxFailed)
|
||||
->Track(mAudioTracks.mDemuxRequest);
|
||||
|
@ -1871,7 +1898,7 @@ TrackBuffersManager::UpdateHighestTimestamp(TrackData& aTrackData,
|
|||
const media::TimeUnit& aHighestTime)
|
||||
{
|
||||
if (aHighestTime > aTrackData.mHighestStartTimestamp) {
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
aTrackData.mHighestStartTimestamp = aHighestTime;
|
||||
}
|
||||
}
|
||||
|
@ -1965,7 +1992,7 @@ TrackBuffersManager::RemoveFrames(const TimeIntervals& aIntervals,
|
|||
aTrackData.mEvictionIndex.mLastIndex >= samplesRemoved &&
|
||||
aTrackData.mEvictionIndex.mEvictable >= sizeRemoved,
|
||||
"Invalid eviction index");
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
aTrackData.mEvictionIndex.mLastIndex -= samplesRemoved;
|
||||
aTrackData.mEvictionIndex.mEvictable -= sizeRemoved;
|
||||
} else {
|
||||
|
@ -2004,7 +2031,7 @@ TrackBuffersManager::RemoveFrames(const TimeIntervals& aIntervals,
|
|||
highestStartTime = sample->mTime;
|
||||
}
|
||||
}
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
aTrackData.mHighestStartTimestamp = highestStartTime;
|
||||
}
|
||||
|
||||
|
@ -2066,7 +2093,7 @@ TrackBuffersManager::SetAppendState(AppendState aAppendState)
|
|||
MediaInfo
|
||||
TrackBuffersManager::GetMetadata() const
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
return mInfo;
|
||||
}
|
||||
|
||||
|
@ -2087,7 +2114,7 @@ TrackBuffersManager::HighestStartTime(TrackInfo::TrackType aTrack) const
|
|||
TimeIntervals
|
||||
TrackBuffersManager::SafeBuffered(TrackInfo::TrackType aTrack) const
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
return aTrack == TrackInfo::kVideoTrack
|
||||
? mVideoBufferedRanges
|
||||
: mAudioBufferedRanges;
|
||||
|
@ -2096,7 +2123,7 @@ TrackBuffersManager::SafeBuffered(TrackInfo::TrackType aTrack) const
|
|||
TimeUnit
|
||||
TrackBuffersManager::HighestStartTime() const
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
TimeUnit highestStartTime;
|
||||
for (auto& track : GetTracksList()) {
|
||||
highestStartTime =
|
||||
|
@ -2108,7 +2135,7 @@ TrackBuffersManager::HighestStartTime() const
|
|||
TimeUnit
|
||||
TrackBuffersManager::HighestEndTime() const
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
|
||||
nsTArray<const TimeIntervals*> tracks;
|
||||
if (HasVideo()) {
|
||||
|
@ -2124,7 +2151,7 @@ TimeUnit
|
|||
TrackBuffersManager::HighestEndTime(
|
||||
nsTArray<const TimeIntervals*>& aTracks) const
|
||||
{
|
||||
mMonitor.AssertCurrentThreadOwns();
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
||||
TimeUnit highestEndTime;
|
||||
|
||||
|
@ -2137,7 +2164,7 @@ TrackBuffersManager::HighestEndTime(
|
|||
void
|
||||
TrackBuffersManager::ResetEvictionIndex(TrackData& aTrackData)
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
aTrackData.mEvictionIndex.Reset();
|
||||
}
|
||||
|
||||
|
@ -2157,7 +2184,7 @@ TrackBuffersManager::UpdateEvictionIndex(TrackData& aTrackData,
|
|||
evictable += data[i]->ComputedSizeOfIncludingThis();
|
||||
}
|
||||
aTrackData.mEvictionIndex.mLastIndex = currentIndex;
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
aTrackData.mEvictionIndex.mEvictable += evictable;
|
||||
}
|
||||
|
||||
|
@ -2535,7 +2562,7 @@ TrackBuffersManager::FindCurrentPosition(TrackInfo::TrackType aTrack,
|
|||
uint32_t
|
||||
TrackBuffersManager::Evictable(TrackInfo::TrackType aTrack) const
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
return GetTracksData(aTrack).mEvictionIndex.mEvictable;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "AutoTaskQueue.h"
|
||||
|
||||
#include "MediaContainerType.h"
|
||||
|
@ -35,7 +36,7 @@ class SourceBufferTaskQueue
|
|||
{
|
||||
public:
|
||||
SourceBufferTaskQueue()
|
||||
: mMonitor("SourceBufferTaskQueue")
|
||||
: mMutex("SourceBufferTaskQueue")
|
||||
{}
|
||||
~SourceBufferTaskQueue()
|
||||
{
|
||||
|
@ -44,13 +45,13 @@ public:
|
|||
|
||||
void Push(SourceBufferTask* aTask)
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
mQueue.AppendElement(aTask);
|
||||
}
|
||||
|
||||
already_AddRefed<SourceBufferTask> Pop()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
if (!mQueue.Length()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -61,12 +62,12 @@ public:
|
|||
|
||||
nsTArray<SourceBufferTask>::size_type Length() const
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MutexAutoLock mut(mMutex);
|
||||
return mQueue.Length();
|
||||
}
|
||||
|
||||
private:
|
||||
mutable Monitor mMonitor;
|
||||
mutable Mutex mMutex;
|
||||
nsTArray<RefPtr<SourceBufferTask>> mQueue;
|
||||
};
|
||||
|
||||
|
@ -455,16 +456,29 @@ private:
|
|||
TrackData mAudioTracks;
|
||||
|
||||
// TaskQueue methods and objects.
|
||||
AbstractThread* GetTaskQueue() const
|
||||
RefPtr<AutoTaskQueue> GetTaskQueueSafe() const
|
||||
{
|
||||
MutexAutoLock mut(mMutex);
|
||||
return mTaskQueue;
|
||||
}
|
||||
NotNull<AbstractThread*> TaskQueueFromTaskQueue() const
|
||||
{
|
||||
#ifdef DEBUG
|
||||
RefPtr<AutoTaskQueue> taskQueue = GetTaskQueueSafe();
|
||||
MOZ_ASSERT(taskQueue && taskQueue->IsCurrentThreadIn());
|
||||
#endif
|
||||
return WrapNotNull(mTaskQueue.get());
|
||||
}
|
||||
bool OnTaskQueue() const
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(GetTaskQueue());
|
||||
return GetTaskQueue()->IsCurrentThreadIn();
|
||||
auto taskQueue = TaskQueueFromTaskQueue();
|
||||
return taskQueue->IsCurrentThreadIn();
|
||||
}
|
||||
void ResetTaskQueue()
|
||||
{
|
||||
MutexAutoLock mut(mMutex);
|
||||
mTaskQueue = nullptr;
|
||||
}
|
||||
RefPtr<AutoTaskQueue> mTaskQueue;
|
||||
|
||||
// SourceBuffer Queues and running context.
|
||||
SourceBufferTaskQueue mQueue;
|
||||
|
@ -506,7 +520,11 @@ private:
|
|||
Atomic<EvictionState> mEvictionState;
|
||||
|
||||
// Monitor to protect following objects accessed across multiple threads.
|
||||
mutable Monitor mMonitor;
|
||||
mutable Mutex mMutex;
|
||||
// mTaskQueue is only ever written after construction on the task queue.
|
||||
// As such, it can be accessed while on task queue without the need for the
|
||||
// mutex.
|
||||
RefPtr<AutoTaskQueue> mTaskQueue;
|
||||
// Stable audio and video track time ranges.
|
||||
media::TimeIntervals mVideoBufferedRanges;
|
||||
media::TimeIntervals mAudioBufferedRanges;
|
||||
|
|
|
@ -326,13 +326,16 @@ CalculateDistanceToEllipticArc(const Point& P, const Point& normal,
|
|||
|
||||
Float S = sqrt(B * B - A * C);
|
||||
|
||||
Float n1 = (- B + S) / A;
|
||||
Float n2 = (- B - S) / A;
|
||||
Float n1 = - B + S;
|
||||
Float n2 = - B - S;
|
||||
|
||||
MOZ_ASSERT(n1 >= 0);
|
||||
MOZ_ASSERT(n2 >= 0);
|
||||
#ifdef DEBUG
|
||||
Float epsilon = (Float) 0.001;
|
||||
MOZ_ASSERT(n1 >= -epsilon);
|
||||
MOZ_ASSERT(n2 >= -epsilon);
|
||||
#endif
|
||||
|
||||
return n1 < n2 ? n1 : n2;
|
||||
return std::max((n1 < n2 ? n1 : n2) / A, (Float) 0.0);
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
|
|
|
@ -15,6 +15,13 @@
|
|||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/gfx/HelpersCairo.h"
|
||||
#include "mozilla/gfx/Logging.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "nsUTF8Utils.h"
|
||||
|
||||
// IPP spec disallow the job-name which is over 255 characters.
|
||||
// RFC: https://tools.ietf.org/html/rfc2911#section-4.1.2
|
||||
#define IPP_JOB_NAME_LIMIT_LENGTH 255
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
@ -160,6 +167,33 @@ PrintTarget::GetReferenceDrawTarget(DrawEventRecorder* aRecorder)
|
|||
return do_AddRef(mRefDT);
|
||||
}
|
||||
|
||||
/* static */
|
||||
void
|
||||
PrintTarget::AdjustPrintJobNameForIPP(const nsAString& aJobName,
|
||||
nsCString& aAdjustedJobName)
|
||||
{
|
||||
CopyUTF16toUTF8(aJobName, aAdjustedJobName);
|
||||
|
||||
if (aAdjustedJobName.Length() > IPP_JOB_NAME_LIMIT_LENGTH) {
|
||||
uint32_t length =
|
||||
RewindToPriorUTF8Codepoint(aAdjustedJobName.get(),
|
||||
(IPP_JOB_NAME_LIMIT_LENGTH - 3U));
|
||||
aAdjustedJobName.SetLength(length);
|
||||
aAdjustedJobName.AppendLiteral("...");
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
void
|
||||
PrintTarget::AdjustPrintJobNameForIPP(const nsAString& aJobName,
|
||||
nsString& aAdjustedJobName)
|
||||
{
|
||||
nsAutoCString jobName;
|
||||
AdjustPrintJobNameForIPP(aJobName, jobName);
|
||||
|
||||
CopyUTF8toUTF16(jobName, aAdjustedJobName);
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<DrawTarget>
|
||||
PrintTarget::CreateWrapAndRecordDrawTarget(DrawEventRecorder* aRecorder,
|
||||
DrawTarget* aDrawTarget)
|
||||
|
|
|
@ -136,6 +136,11 @@ public:
|
|||
*/
|
||||
virtual already_AddRefed<DrawTarget> GetReferenceDrawTarget(DrawEventRecorder* aRecorder);
|
||||
|
||||
static void AdjustPrintJobNameForIPP(const nsAString& aJobName,
|
||||
nsCString& aAdjustedJobName);
|
||||
static void AdjustPrintJobNameForIPP(const nsAString& aJobName,
|
||||
nsString& aAdjustedJobName);
|
||||
|
||||
protected:
|
||||
|
||||
// Only created via subclass's constructors
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "mozilla/ServoStyleRule.h"
|
||||
#include "mozilla/ServoStyleSet.h"
|
||||
#include "mozilla/ServoImportRule.h"
|
||||
#include "mozilla/StyleSheetInlines.h"
|
||||
|
||||
#include "nsDocument.h"
|
||||
#include "nsStyleSheetService.h"
|
||||
|
@ -175,7 +176,10 @@ void
|
|||
ServoStyleRuleMap::FillTableFromStyleSheet(ServoStyleSheet* aSheet)
|
||||
{
|
||||
if (aSheet->IsComplete()) {
|
||||
FillTableFromRuleList(aSheet->GetCssRulesInternal());
|
||||
// XBL stylesheets are not expected to ever change, so it's a waste
|
||||
// to make its inner unique.
|
||||
FillTableFromRuleList(aSheet->GetCssRulesInternal(
|
||||
/* aRequireUniqueInner = */ !mStyleSet->IsForXBL()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -284,7 +284,8 @@ inDOMUtils::GetCSSStyleRules(nsIDOMElement *aElement,
|
|||
// Collect style rule maps for bindings.
|
||||
for (nsIContent* bindingContent = element; bindingContent;
|
||||
bindingContent = bindingContent->GetBindingParent()) {
|
||||
if (nsXBLBinding* binding = bindingContent->GetXBLBinding()) {
|
||||
for (nsXBLBinding* binding = bindingContent->GetXBLBinding();
|
||||
binding; binding = binding->GetBaseBinding()) {
|
||||
if (ServoStyleSet* styleSet = binding->GetServoStyleSet()) {
|
||||
ServoStyleRuleMap* map = styleSet->StyleRuleMap();
|
||||
map->EnsureTable();
|
||||
|
|
|
@ -136,6 +136,8 @@ SERVO_BINDING_FUNC(Servo_SelectorList_Parse,
|
|||
const nsACString* selector_list)
|
||||
SERVO_BINDING_FUNC(Servo_SelectorList_Matches, bool,
|
||||
RawGeckoElementBorrowed, RawServoSelectorListBorrowed)
|
||||
SERVO_BINDING_FUNC(Servo_SelectorList_Closest, RawGeckoElementBorrowedOrNull,
|
||||
RawGeckoElementBorrowed, RawServoSelectorListBorrowed)
|
||||
SERVO_BINDING_FUNC(Servo_StyleSet_AddSizeOfExcludingThis, void,
|
||||
mozilla::MallocSizeOf malloc_size_of,
|
||||
mozilla::MallocSizeOf malloc_enclosing_size_of,
|
||||
|
|
|
@ -478,6 +478,9 @@ public:
|
|||
ServoStyleContext* aNewLayoutParent,
|
||||
Element* aElement);
|
||||
|
||||
bool IsMaster() const { return mKind == Kind::Master; }
|
||||
bool IsForXBL() const { return mKind == Kind::ForXBL; }
|
||||
|
||||
private:
|
||||
friend class AutoSetInServoTraversal;
|
||||
friend class AutoPrepareTraversal;
|
||||
|
@ -489,9 +492,6 @@ private:
|
|||
*/
|
||||
const SnapshotTable& Snapshots();
|
||||
|
||||
bool IsMaster() const { return mKind == Kind::Master; }
|
||||
bool IsForXBL() const { return mKind == Kind::ForXBL; }
|
||||
|
||||
/**
|
||||
* Resolve all ServoDeclarationBlocks attached to mapped
|
||||
* presentation attributes cached on the document.
|
||||
|
|
|
@ -393,10 +393,15 @@ ServoStyleSheet::Clone(StyleSheet* aCloneParent,
|
|||
}
|
||||
|
||||
ServoCSSRuleList*
|
||||
ServoStyleSheet::GetCssRulesInternal()
|
||||
ServoStyleSheet::GetCssRulesInternal(bool aRequireUniqueInner)
|
||||
{
|
||||
if (!mRuleList) {
|
||||
EnsureUniqueInner();
|
||||
MOZ_ASSERT(aRequireUniqueInner || !mDocument,
|
||||
"Not requiring unique inner for stylesheet associated "
|
||||
"with document may have undesired behavior");
|
||||
if (aRequireUniqueInner) {
|
||||
EnsureUniqueInner();
|
||||
}
|
||||
|
||||
RefPtr<ServoCssRules> rawRules =
|
||||
Servo_StyleSheet_GetRules(Inner()->mContents).Consume();
|
||||
|
|
|
@ -120,7 +120,7 @@ public:
|
|||
|
||||
// Internal GetCssRules method which do not have security check and
|
||||
// completelness check.
|
||||
ServoCSSRuleList* GetCssRulesInternal();
|
||||
ServoCSSRuleList* GetCssRulesInternal(bool aRequireUniqueInner = true);
|
||||
|
||||
// Returns the stylesheet's Servo origin as an OriginFlags value.
|
||||
OriginFlags GetOrigin();
|
||||
|
|
|
@ -450,7 +450,7 @@ StyleSheet::EnsureUniqueInner()
|
|||
"unexpected number of outers");
|
||||
mDirty = true;
|
||||
|
||||
if (mInner->mSheets.Length() == 1) {
|
||||
if (HasUniqueInner()) {
|
||||
// already unique
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -138,6 +138,7 @@ public:
|
|||
|
||||
bool IsModified() const { return mDirty; }
|
||||
|
||||
inline bool HasUniqueInner() const;
|
||||
void EnsureUniqueInner();
|
||||
|
||||
// Append all of this sheet's child sheets to aArray.
|
||||
|
|
|
@ -129,6 +129,12 @@ StyleSheet::GetIntegrity(dom::SRIMetadata& aResult) const
|
|||
aResult = SheetInfo().mIntegrity;
|
||||
}
|
||||
|
||||
bool
|
||||
StyleSheet::HasUniqueInner() const
|
||||
{
|
||||
return mInner->mSheets.Length() == 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // mozilla_StyleSheetInlines_h
|
||||
|
|
|
@ -341,6 +341,7 @@ skip-if = toolkit == 'android' && debug # bug 1397615
|
|||
[test_variable_serialization_specified.html]
|
||||
[test_variables.html]
|
||||
support-files = support/external-variable-url.css
|
||||
[test_variables_loop.html]
|
||||
[test_variables_order.html]
|
||||
support-files = support/external-variable-url.css
|
||||
[test_video_object_fit.html]
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<title>CSS variables loop resolving</title>
|
||||
<script src="/MochiKit/MochiKit.js"></script>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css" type="text/css">
|
||||
<style id="test">
|
||||
#outer {
|
||||
--a: a;
|
||||
--b: b;
|
||||
--c: c;
|
||||
--d: d;
|
||||
--e: e;
|
||||
}
|
||||
#inner {
|
||||
--a: var(--d, ad);
|
||||
--b: var(--d, ad);
|
||||
--c: var(--d, ad);
|
||||
--d: var(--e, de);
|
||||
--e: var(--a, ea) var(--b, eb) var(--c, ec);
|
||||
}
|
||||
</style>
|
||||
<div id="outer">
|
||||
<div id="inner"></div>
|
||||
</div>
|
||||
<script>
|
||||
let inner_cs = getComputedStyle(document.getElementById("inner"));
|
||||
for (let v of ['a', 'b', 'c', 'd', 'e']) {
|
||||
is(inner_cs.getPropertyValue(`--${v}`), "",
|
||||
`Variable --${v} should be eliminated`);
|
||||
}
|
||||
</script>
|
|
@ -4,6 +4,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ParseFTPList.h"
|
||||
#include <algorithm>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
@ -25,8 +26,22 @@ static inline int ParsingFailed(struct list_state *state)
|
|||
return '"'; /* its part of a comment or error message */
|
||||
}
|
||||
|
||||
void
|
||||
FixupYear(PRExplodedTime* aTime)
|
||||
{
|
||||
/* if year has only two digits then assume that
|
||||
00-79 is 2000-2079
|
||||
80-99 is 1980-1999 */
|
||||
if (aTime->tm_year < 80) {
|
||||
aTime->tm_year += 2000;
|
||||
} else if (aTime->tm_year < 100) {
|
||||
aTime->tm_year += 1900;
|
||||
}
|
||||
}
|
||||
|
||||
int ParseFTPList(const char *line, struct list_state *state,
|
||||
struct list_result *result )
|
||||
struct list_result *result, PRTimeParamFn timeParam,
|
||||
NowTimeFn nowTimeFn)
|
||||
{
|
||||
unsigned int carry_buf_len; /* copy of state->carry_buf_len */
|
||||
unsigned int pos;
|
||||
|
@ -136,7 +151,7 @@ int ParseFTPList(const char *line, struct list_state *state,
|
|||
PRTime seconds;
|
||||
PR_sscanf(p+1, "%llu", &seconds);
|
||||
t = seconds * PR_USEC_PER_SEC;
|
||||
PR_ExplodeTime(t, PR_LocalTimeParameters, &(result->fe_time) );
|
||||
PR_ExplodeTime(t, timeParam, &(result->fe_time) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -453,46 +468,17 @@ int ParseFTPList(const char *line, struct list_state *state,
|
|||
while (pos < toklen[1] && (tokens[1][pos]) != '/')
|
||||
pos++;
|
||||
|
||||
/*
|
||||
* I've never seen size come back in bytes, its always in blocks, and
|
||||
* the following test fails. So, always perform the "size in blocks".
|
||||
* I'm leaving the "size in bytes" code if'd out in case we ever need
|
||||
* to re-instate it.
|
||||
*/
|
||||
#if 0
|
||||
if (pos < toklen[1] && ( (pos<<1) > (toklen[1]-1) ||
|
||||
(strtoul(tokens[1], (char **)0, 10) >
|
||||
strtoul(tokens[1]+pos+1, (char **)0, 10)) ))
|
||||
{ /* size is in bytes */
|
||||
if (pos > (sizeof(result->fe_size)-1))
|
||||
pos = sizeof(result->fe_size)-1;
|
||||
memcpy( result->fe_size, tokens[1], pos );
|
||||
result->fe_size[pos] = '\0';
|
||||
}
|
||||
else /* size is in blocks */
|
||||
#endif
|
||||
{
|
||||
/* size requires multiplication by blocksize.
|
||||
*
|
||||
* We could assume blocksize is 512 (like Lynx does) and
|
||||
* shift by 9, but that might not be right. Even if it
|
||||
* were, doing that wouldn't reflect what the file's
|
||||
* real size was. The sanest thing to do is not use the
|
||||
* LISTing's filesize, so we won't (like ftpmirror).
|
||||
*
|
||||
* ulltoa(((unsigned long long)fsz)<<9, result->fe_size, 10);
|
||||
*
|
||||
* A block is always 512 bytes on OpenVMS, compute size.
|
||||
* So its rounded up to the next block, so what, its better
|
||||
* than not showing the size at all.
|
||||
* A block is always 512 bytes on OpenVMS, compute size.
|
||||
* So its rounded up to the next block, so what, its better
|
||||
* than not showing the size at all.
|
||||
*/
|
||||
uint64_t fsz = uint64_t(strtoul(tokens[1], (char **)0, 10) * 512);
|
||||
SprintfLiteral(result->fe_size, "%" PRId64, fsz);
|
||||
}
|
||||
|
||||
/*
|
||||
* On OpenVMS, the size is given in blocks. A block is 512
|
||||
* bytes. This can only approximate the size of the file,
|
||||
* but that's better than not showing a size at all.
|
||||
* numBlocks is clamped to UINT32_MAX to make 32-bit and
|
||||
* 64-bit builds return consistent results.
|
||||
*/
|
||||
uint64_t numBlocks = strtoul(tokens[1], nullptr, 10);
|
||||
numBlocks = std::min(numBlocks, (uint64_t)UINT32_MAX);
|
||||
uint64_t fileSize = numBlocks * 512;
|
||||
SprintfLiteral(result->fe_size, "%" PRIu64, fileSize);
|
||||
} /* if (result->fe_type != 'd') */
|
||||
|
||||
p = tokens[2] + 2;
|
||||
|
@ -655,7 +641,7 @@ int ParseFTPList(const char *line, struct list_state *state,
|
|||
p = tokens[tokmarker+4];
|
||||
if (toklen[tokmarker+4] == 10) /* newstyle: YYYY-MM-DD format */
|
||||
{
|
||||
result->fe_time.tm_year = atoi(p+0) - 1900;
|
||||
result->fe_time.tm_year = atoi(p+0);
|
||||
result->fe_time.tm_month = atoi(p+5) - 1;
|
||||
result->fe_time.tm_mday = atoi(p+8);
|
||||
}
|
||||
|
@ -665,8 +651,7 @@ int ParseFTPList(const char *line, struct list_state *state,
|
|||
result->fe_time.tm_month = atoi(p) - 1;
|
||||
result->fe_time.tm_mday = atoi((p+pos)-5);
|
||||
result->fe_time.tm_year = atoi((p+pos)-2);
|
||||
if (result->fe_time.tm_year < 70)
|
||||
result->fe_time.tm_year += 100;
|
||||
FixupYear(&result->fe_time);
|
||||
}
|
||||
|
||||
p = tokens[tokmarker+5];
|
||||
|
@ -829,13 +814,7 @@ int ParseFTPList(const char *line, struct list_state *state,
|
|||
result->fe_time.tm_month--;
|
||||
result->fe_time.tm_mday = atoi(tokens[0]+3);
|
||||
result->fe_time.tm_year = atoi(tokens[0]+6);
|
||||
/* if year has only two digits then assume that
|
||||
00-79 is 2000-2079
|
||||
80-99 is 1980-1999 */
|
||||
if (result->fe_time.tm_year < 80)
|
||||
result->fe_time.tm_year += 2000;
|
||||
else if (result->fe_time.tm_year < 100)
|
||||
result->fe_time.tm_year += 1900;
|
||||
FixupYear(&result->fe_time);
|
||||
}
|
||||
|
||||
result->fe_time.tm_hour = atoi(tokens[1]+0);
|
||||
|
@ -947,8 +926,7 @@ int ParseFTPList(const char *line, struct list_state *state,
|
|||
result->fe_time.tm_month = atoi(&p[35-18]) - 1;
|
||||
result->fe_time.tm_mday = atoi(&p[38-18]);
|
||||
result->fe_time.tm_year = atoi(&p[41-18]);
|
||||
if (result->fe_time.tm_year < 80)
|
||||
result->fe_time.tm_year += 100;
|
||||
FixupYear(&result->fe_time);
|
||||
result->fe_time.tm_hour = atoi(&p[46-18]);
|
||||
result->fe_time.tm_min = atoi(&p[49-18]);
|
||||
|
||||
|
@ -1161,8 +1139,8 @@ int ParseFTPList(const char *line, struct list_state *state,
|
|||
|
||||
if (!state->now_time)
|
||||
{
|
||||
state->now_time = PR_Now();
|
||||
PR_ExplodeTime((state->now_time), PR_LocalTimeParameters, &(state->now_tm) );
|
||||
state->now_time = nowTimeFn();
|
||||
PR_ExplodeTime((state->now_time), timeParam, &(state->now_tm) );
|
||||
}
|
||||
|
||||
result->fe_time.tm_year = state->now_tm.tm_year;
|
||||
|
@ -1374,7 +1352,7 @@ int ParseFTPList(const char *line, struct list_state *state,
|
|||
{
|
||||
result->fe_time.tm_month = pos/3;
|
||||
result->fe_time.tm_mday = atoi(tokens[3]);
|
||||
result->fe_time.tm_year = atoi(tokens[4]) - 1900;
|
||||
result->fe_time.tm_year = atoi(tokens[4]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1385,8 +1363,7 @@ int ParseFTPList(const char *line, struct list_state *state,
|
|||
result->fe_time.tm_month = atoi(p+0)-1;
|
||||
result->fe_time.tm_mday = atoi(p+3);
|
||||
result->fe_time.tm_year = atoi(p+6);
|
||||
if (result->fe_time.tm_year < 80) /* SuperTCP */
|
||||
result->fe_time.tm_year += 100;
|
||||
FixupYear(&result->fe_time); /* SuperTCP */
|
||||
|
||||
pos = 3; /* SuperTCP toknum of date field */
|
||||
}
|
||||
|
@ -1631,7 +1608,7 @@ int ParseFTPList(const char *line, struct list_state *state,
|
|||
|
||||
pos = atoi(p);
|
||||
if (pos > 24)
|
||||
result->fe_time.tm_year = pos-1900;
|
||||
result->fe_time.tm_year = pos;
|
||||
else
|
||||
{
|
||||
if (p[1] == ':')
|
||||
|
@ -1640,8 +1617,8 @@ int ParseFTPList(const char *line, struct list_state *state,
|
|||
result->fe_time.tm_min = atoi(p+3);
|
||||
if (!state->now_time)
|
||||
{
|
||||
state->now_time = PR_Now();
|
||||
PR_ExplodeTime((state->now_time), PR_LocalTimeParameters, &(state->now_tm) );
|
||||
state->now_time = nowTimeFn();
|
||||
PR_ExplodeTime((state->now_time), timeParam, &(state->now_tm) );
|
||||
}
|
||||
result->fe_time.tm_year = state->now_tm.tm_year;
|
||||
if ( (( state->now_tm.tm_month << 4) + state->now_tm.tm_mday) <
|
||||
|
@ -1688,207 +1665,3 @@ int ParseFTPList(const char *line, struct list_state *state,
|
|||
return ParsingFailed(state);
|
||||
}
|
||||
|
||||
/* ==================================================================== */
|
||||
/* standalone testing */
|
||||
/* ==================================================================== */
|
||||
#if 0
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static int do_it(FILE *outfile,
|
||||
char *line, size_t linelen, struct list_state *state,
|
||||
char **cmnt_buf, unsigned int *cmnt_buf_sz,
|
||||
char **list_buf, unsigned int *list_buf_sz )
|
||||
{
|
||||
struct list_result result;
|
||||
char *p;
|
||||
int rc;
|
||||
|
||||
rc = ParseFTPList( line, state, &result );
|
||||
|
||||
if (!outfile)
|
||||
{
|
||||
outfile = stdout;
|
||||
if (rc == '?')
|
||||
fprintf(outfile, "junk: %.*s\n", (int)linelen, line );
|
||||
else if (rc == '"')
|
||||
fprintf(outfile, "cmnt: %.*s\n", (int)linelen, line );
|
||||
else
|
||||
fprintf(outfile,
|
||||
"list: %02u-%02u-%02u %02u:%02u%cM %20s %.*s%s%.*s\n",
|
||||
(result.fe_time.tm_mday ? (result.fe_time.tm_month + 1) : 0),
|
||||
result.fe_time.tm_mday,
|
||||
(result.fe_time.tm_mday ? (result.fe_time.tm_year % 100) : 0),
|
||||
result.fe_time.tm_hour -
|
||||
((result.fe_time.tm_hour > 12)?(12):(0)),
|
||||
result.fe_time.tm_min,
|
||||
((result.fe_time.tm_hour >= 12) ? 'P' : 'A'),
|
||||
(rc == 'd' ? "<DIR> " :
|
||||
(rc == 'l' ? "<JUNCTION> " : result.fe_size)),
|
||||
(int)result.fe_fnlen, result.fe_fname,
|
||||
((rc == 'l' && result.fe_lnlen) ? " -> " : ""),
|
||||
(int)((rc == 'l' && result.fe_lnlen) ? result.fe_lnlen : 0),
|
||||
((rc == 'l' && result.fe_lnlen) ? result.fe_lname : "") );
|
||||
}
|
||||
else if (rc != '?') /* NOT junk */
|
||||
{
|
||||
char **bufp = list_buf;
|
||||
unsigned int *bufz = list_buf_sz;
|
||||
|
||||
if (rc == '"') /* comment - make it a 'result' */
|
||||
{
|
||||
memset( &result, 0, sizeof(result));
|
||||
result.fe_fname = line;
|
||||
result.fe_fnlen = linelen;
|
||||
result.fe_type = 'f';
|
||||
if (line[linelen-1] == '/')
|
||||
{
|
||||
result.fe_type = 'd';
|
||||
result.fe_fnlen--;
|
||||
}
|
||||
bufp = cmnt_buf;
|
||||
bufz = cmnt_buf_sz;
|
||||
rc = result.fe_type;
|
||||
}
|
||||
|
||||
linelen = 80 + result.fe_fnlen + result.fe_lnlen;
|
||||
p = (char *)realloc( *bufp, *bufz + linelen );
|
||||
if (!p)
|
||||
return -1;
|
||||
sprintf( &p[*bufz],
|
||||
"%02u-%02u-%04u %02u:%02u:%02u %20s %.*s%s%.*s\n",
|
||||
(result.fe_time.tm_mday ? (result.fe_time.tm_month + 1) : 0),
|
||||
result.fe_time.tm_mday,
|
||||
(result.fe_time.tm_mday ? (result.fe_time.tm_year + 1900) : 0),
|
||||
result.fe_time.tm_hour,
|
||||
result.fe_time.tm_min,
|
||||
result.fe_time.tm_sec,
|
||||
(rc == 'd' ? "<DIR> " :
|
||||
(rc == 'l' ? "<JUNCTION> " : result.fe_size)),
|
||||
(int)result.fe_fnlen, result.fe_fname,
|
||||
((rc == 'l' && result.fe_lnlen) ? " -> " : ""),
|
||||
(int)((rc == 'l' && result.fe_lnlen) ? result.fe_lnlen : 0),
|
||||
((rc == 'l' && result.fe_lnlen) ? result.fe_lname : "") );
|
||||
linelen = strlen(&p[*bufz]);
|
||||
*bufp = p;
|
||||
*bufz = *bufz + linelen;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
FILE *infile = (FILE *)0;
|
||||
FILE *outfile = (FILE *)0;
|
||||
int need_close_in = 0;
|
||||
int need_close_out = 0;
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
infile = stdin;
|
||||
if (strcmp(argv[1], "-") == 0)
|
||||
need_close_in = 0;
|
||||
else if ((infile = fopen(argv[1], "r")) != ((FILE *)0))
|
||||
need_close_in = 1;
|
||||
else
|
||||
fprintf(stderr, "Unable to open input file '%s'\n", argv[1]);
|
||||
}
|
||||
if (infile && argc > 2)
|
||||
{
|
||||
outfile = stdout;
|
||||
if (strcmp(argv[2], "-") == 0)
|
||||
need_close_out = 0;
|
||||
else if ((outfile = fopen(argv[2], "w")) != ((FILE *)0))
|
||||
need_close_out = 1;
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Unable to open output file '%s'\n", argv[2]);
|
||||
fclose(infile);
|
||||
infile = (FILE *)0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!infile)
|
||||
{
|
||||
char *appname = &(argv[0][strlen(argv[0])]);
|
||||
while (appname > argv[0])
|
||||
{
|
||||
appname--;
|
||||
if (*appname == '/' || *appname == '\\' || *appname == ':')
|
||||
{
|
||||
appname++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fprintf(stderr,
|
||||
"Usage: %s <inputfilename> [<outputfilename>]\n"
|
||||
"\nIf an outout file is specified the results will be"
|
||||
"\nbe post-processed, and only the file entries will appear"
|
||||
"\n(or all comments if there are no file entries)."
|
||||
"\nNot specifying an output file causes %s to run in \"debug\""
|
||||
"\nmode, ie results are printed as lines are parsed."
|
||||
"\nIf a filename is a single dash ('-'), stdin/stdout is used."
|
||||
"\n", appname, appname );
|
||||
}
|
||||
else
|
||||
{
|
||||
char *cmnt_buf = (char *)0;
|
||||
unsigned int cmnt_buf_sz = 0;
|
||||
char *list_buf = (char *)0;
|
||||
unsigned int list_buf_sz = 0;
|
||||
|
||||
struct list_state state;
|
||||
char line[512];
|
||||
|
||||
memset( &state, 0, sizeof(state) );
|
||||
while (fgets(line, sizeof(line), infile))
|
||||
{
|
||||
size_t linelen = strlen(line);
|
||||
if (linelen < (sizeof(line)-1))
|
||||
{
|
||||
if (linelen > 0 && line[linelen-1] == '\n')
|
||||
linelen--;
|
||||
if (do_it( outfile, line, linelen, &state,
|
||||
&cmnt_buf, &cmnt_buf_sz, &list_buf, &list_buf_sz) != 0)
|
||||
{
|
||||
fprintf(stderr, "Insufficient memory. Listing may be incomplete.\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* no '\n' found. drop this and everything up to the next '\n' */
|
||||
fprintf(stderr, "drop: %.*s", (int)linelen, line );
|
||||
while (linelen == sizeof(line))
|
||||
{
|
||||
if (!fgets(line, sizeof(line), infile))
|
||||
break;
|
||||
linelen = 0;
|
||||
while (linelen < sizeof(line) && line[linelen] != '\n')
|
||||
linelen++;
|
||||
fprintf(stderr, "%.*s", (int)linelen, line );
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
if (outfile)
|
||||
{
|
||||
if (list_buf)
|
||||
fwrite( list_buf, 1, list_buf_sz, outfile );
|
||||
else if (cmnt_buf)
|
||||
fwrite( cmnt_buf, 1, cmnt_buf_sz, outfile );
|
||||
}
|
||||
if (list_buf)
|
||||
free(list_buf);
|
||||
if (cmnt_buf)
|
||||
free(cmnt_buf);
|
||||
|
||||
if (need_close_in)
|
||||
fclose(infile);
|
||||
if (outfile && need_close_out)
|
||||
fclose(outfile);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -97,8 +97,12 @@ struct list_result
|
|||
/* (converting all-upcase names may be desirable) */
|
||||
};
|
||||
|
||||
typedef PRTime (*NowTimeFn)();
|
||||
|
||||
int ParseFTPList(const char *line,
|
||||
struct list_state *state,
|
||||
struct list_result *result );
|
||||
struct list_result *result,
|
||||
PRTimeParamFn timeParam = PR_LocalTimeParameters,
|
||||
NowTimeFn nowTimeFn = PR_Now);
|
||||
|
||||
#endif /* !ParseRTPList_h___ */
|
||||
|
|
|
@ -1,174 +0,0 @@
|
|||
01-11-2001 10:38:25 <DIR> tmp
|
||||
02-29-2000 03:01:45 <DIR> 1998-100
|
||||
12-24-2000 21:44:06 1172 mirrors.html
|
||||
02-29-2000 03:01:47 <DIR> 1999-275
|
||||
05-08-2000 07:25:16 933 1999-275.html
|
||||
05-08-2000 07:27:06 322 1998-401.html
|
||||
02-29-2000 03:01:47 <DIR> 1998-401
|
||||
02-29-2000 03:01:45 <DIR> 1997-275
|
||||
05-08-2000 07:28:25 277 1998-515.html
|
||||
05-08-2000 07:36:43 325 2000-436.html
|
||||
12-23-2000 07:47:11 <DIR> checkpwd
|
||||
07-04-2002 19:47:46 <DIR> bib
|
||||
02-29-2000 03:01:47 <DIR> 1999-541
|
||||
04-28-2001 05:02:35 <DIR> 2001-260
|
||||
04-17-2002 20:27:55 <DIR> 2002-501
|
||||
06-01-2001 22:56:17 <DIR> 1995-514
|
||||
02-29-2000 03:01:45 <DIR> 1999-180
|
||||
06-01-2001 04:44:23 475 2000-515.html
|
||||
02-29-2000 03:01:47 <DIR> 1998-515
|
||||
02-29-2000 03:01:47 <DIR> 2000-515
|
||||
05-12-2000 09:46:13 <DIR> zpfft
|
||||
10-27-2001 15:00:14 <DIR> psibound
|
||||
02-29-2000 03:01:51 398 anonftpd.html
|
||||
02-26-2002 03:34:22 <DIR> nistp224
|
||||
08-10-2001 09:27:20 <DIR> cdb
|
||||
05-01-2002 01:58:27 <DIR> talks
|
||||
12-23-2000 22:34:35 <DIR> ftpparse
|
||||
01-11-2000 06:50:10 <DIR> clockspeed
|
||||
02-29-2000 03:01:52 1350 clockspeed.html
|
||||
02-06-2002 21:52:34 <DIR> slashdoc
|
||||
06-07-2002 18:00:40 <DIR> export
|
||||
07-17-2001 03:48:36 <DIR> daemontools
|
||||
07-12-2001 23:17:08 2187 daemontools.html
|
||||
08-30-2001 22:51:16 175 data.html
|
||||
12-28-2001 02:32:37 1901 crypto.html
|
||||
11-11-2001 00:45:23 739 arith.html
|
||||
11-30-2001 04:48:09 2733 cdb.html
|
||||
11-11-2001 00:51:02 344 floatasm.html
|
||||
12-21-2000 22:25:56 <DIR> djbfft
|
||||
02-29-2000 03:01:52 1319 djbfft.html
|
||||
07-09-2000 19:16:47 <DIR> dnscache
|
||||
07-04-2000 00:57:44 4016 dnscache.html
|
||||
07-10-2001 05:16:22 <DIR> docs
|
||||
02-29-2000 03:01:52 293 docs.html
|
||||
02-29-2000 03:01:52 952 dot-forward.html
|
||||
01-11-2000 06:50:11 <DIR> etc-mta
|
||||
02-29-2000 03:01:52 3174 etc-mta.html
|
||||
02-29-2000 03:01:52 2990 ezmlm.html
|
||||
12-21-2000 22:25:56 <DIR> ftp
|
||||
02-29-2000 03:01:49 <DIR> im
|
||||
02-29-2000 03:01:52 1823 fastforward.html
|
||||
02-29-2000 03:01:52 1707 ftp.html
|
||||
02-12-2001 03:17:10 <DIR> freebugtraq
|
||||
06-02-2002 03:39:25 <DIR> hardware
|
||||
06-01-2001 04:57:35 610 1995-514.html
|
||||
12-21-2000 22:25:57 <DIR> hash127
|
||||
05-19-2000 04:29:02 4835 hash127.html
|
||||
02-29-2000 03:01:52 1515 im.html
|
||||
02-12-2002 21:40:06 <DIR> immhf
|
||||
04-25-2001 23:48:49 2902 immhf.html
|
||||
01-25-2001 06:58:36 <DIR> lib
|
||||
06-06-2001 01:45:50 <DIR> libtai
|
||||
07-10-2000 20:54:16 2120 libtai.html
|
||||
11-27-2001 18:52:40 14460 qmail.html
|
||||
08-30-2001 22:51:23 2153 mail.html
|
||||
02-29-2000 03:01:52 108 maildir.html
|
||||
02-29-2000 03:01:50 <DIR> maildisasters
|
||||
02-29-2000 03:01:52 873 maildisasters.html
|
||||
02-29-2000 03:01:52 1688 mess822.html
|
||||
02-25-2002 03:47:27 <DIR> mirror
|
||||
12-26-2001 22:02:32 581 ntheory.html
|
||||
06-03-2002 17:36:27 <DIR> papers
|
||||
05-21-2002 18:50:15 <DIR> postpropter
|
||||
01-11-2000 06:50:12 <DIR> postings
|
||||
01-11-2000 06:50:12 <DIR> primegen
|
||||
08-30-2001 22:51:33 415 precompiled.html
|
||||
02-29-2000 03:01:52 1121 primegen.html
|
||||
05-05-2002 03:37:55 <DIR> proto
|
||||
02-29-2000 03:01:52 452 proto.html
|
||||
07-10-2001 00:17:53 <DIR> publicfile
|
||||
11-30-2001 04:59:17 5651 publicfile.html
|
||||
08-30-2001 22:51:37 409 qlist.html
|
||||
04-02-2002 06:25:45 <DIR> qmail
|
||||
02-02-2002 06:52:28 7549 lists.html
|
||||
02-29-2000 03:01:52 1166 qmailanalog.html
|
||||
08-30-2001 22:51:39 1434 qmsmac.html
|
||||
03-13-2000 06:38:06 465 rblsmtpd.html
|
||||
02-29-2000 03:01:52 1225 rights.html
|
||||
01-11-2000 06:50:13 <DIR> sarcasm
|
||||
09-14-2001 04:34:14 1058 unix.html
|
||||
02-29-2000 03:01:53 1568 serialmail.html
|
||||
10-02-2000 21:13:44 840 sigs.html
|
||||
05-21-2002 18:50:08 <DIR> smtp
|
||||
04-26-2001 17:58:47 1723 smtp.html
|
||||
12-21-2000 22:26:03 <DIR> software
|
||||
02-29-2000 03:01:53 3671 softwarelaw.html
|
||||
01-11-2000 06:50:13 <DIR> sortedsums
|
||||
02-29-2000 03:01:53 2735 sortedsums.html
|
||||
02-26-2002 03:06:38 <DIR> speed
|
||||
07-04-2000 00:40:17 572 surveydns.html
|
||||
10-04-2001 02:59:31 <DIR> surveys
|
||||
10-04-2001 02:46:36 3503 surveys.html
|
||||
11-10-2000 06:58:08 <DIR> syncookies
|
||||
02-01-2002 03:03:37 7918 syncookies.html
|
||||
08-30-2001 22:51:43 1115 tcpcontrol.html
|
||||
03-18-2002 13:08:27 589 tcpip.html
|
||||
02-29-2000 03:01:53 906 thoughts.html
|
||||
07-30-2001 16:18:09 <DIR> threecubes
|
||||
09-14-2001 03:25:58 525 time.html
|
||||
05-22-2002 06:21:05 <DIR> ucspi-tcp
|
||||
01-25-2002 00:41:31 4086 ucspi-tcp.html
|
||||
08-30-2001 22:51:50 732 web.html
|
||||
07-04-2002 19:47:48 <DIR> www
|
||||
09-14-2001 03:26:08 2717 y2k.html
|
||||
02-08-2001 10:28:09 <DIR> dnsroot
|
||||
04-23-2001 08:01:37 2745 2001-260.html
|
||||
02-29-2000 03:01:53 291 zeroseek.html
|
||||
02-29-2000 03:01:53 <DIR> zmodexp
|
||||
05-19-2000 04:29:07 1513 zmodexp.html
|
||||
08-01-2000 03:08:54 <DIR> zmult
|
||||
08-01-2000 03:24:53 669 zmult.html
|
||||
12-23-2000 07:19:47 1097 checkpwd.html
|
||||
12-31-2001 19:47:16 2077 focus.html
|
||||
07-20-2000 01:11:31 3842 im2000.html
|
||||
07-04-2002 19:47:43 <DIR> slashcommand
|
||||
07-04-2002 19:47:44 <DIR> slashpackage
|
||||
07-16-2001 00:24:39 602 slashpackage.html
|
||||
05-03-2002 02:15:33 23874 talks.html
|
||||
05-08-2000 07:24:59 566 1997-275.html
|
||||
05-08-2000 07:31:04 313 1998-100.html
|
||||
02-29-2000 03:01:47 <DIR> 2000-436
|
||||
05-08-2000 07:37:26 660 1997-494.html
|
||||
05-08-2000 07:40:41 730 1999-541.html
|
||||
09-13-2000 09:08:58 2367 psibound.html
|
||||
06-22-2000 04:09:00 1866 smallfactors.html
|
||||
06-09-2000 03:35:19 <DIR> smallfactors
|
||||
07-02-2000 00:55:19 <DIR> conferences
|
||||
05-22-2002 07:52:29 <DIR> djbdns
|
||||
07-17-2001 06:00:58 2712 djbdns.html
|
||||
07-17-2000 03:15:39 216 zpfft.html
|
||||
08-20-2000 20:56:37 <DIR> fastnewton
|
||||
08-20-2000 20:57:31 333 fastnewton.html
|
||||
09-01-2000 20:33:51 <DIR> patents
|
||||
12-23-2000 22:40:17 1748 ftpparse.html
|
||||
11-11-2001 00:57:50 3261 slash.html
|
||||
01-17-2001 18:50:10 2663 slashdoc.html
|
||||
03-18-2002 10:04:35 2344 dnsroot.html
|
||||
12-26-2001 22:03:00 3238 software.html
|
||||
09-14-2001 04:34:33 3481 compatibility.html
|
||||
05-01-2002 05:30:34 5525 distributors.html
|
||||
02-25-2001 04:49:03 212 freebugtraq.html
|
||||
03-31-2002 04:59:50 806 hardware.html
|
||||
03-31-2001 08:15:18 121 securesoftware.html
|
||||
05-22-2002 04:39:56 10950 conferences.html
|
||||
11-30-2001 18:51:50 <DIR> 2001-275
|
||||
05-29-2001 02:15:07 459 rwb100.html
|
||||
10-03-2001 07:40:42 863 nistp224.html
|
||||
07-30-2001 16:15:46 778 threecubes.html
|
||||
06-03-2002 17:32:16 27125 papers.html
|
||||
11-21-2001 03:51:09 379 bib.html
|
||||
11-30-2001 18:41:33 3791 2001-275.html
|
||||
10-02-2001 20:08:59 169 dh224.html
|
||||
03-16-2002 07:05:36 1126 courses.html
|
||||
09-03-2001 00:22:28 355 slashcommand.html
|
||||
03-27-2002 22:20:22 1199 patents.html
|
||||
11-08-2001 20:27:15 640 mailcopyright.html
|
||||
06-02-2002 01:27:50 1257 djb.html
|
||||
01-07-2002 07:12:34 301 2002-501.html
|
||||
05-01-2002 04:41:45 1655 export.html
|
||||
03-24-2002 11:40:59 6446 index.html
|
||||
03-16-2002 07:09:08 570 2005-590.html
|
||||
03-16-2002 07:05:49 800 2004-494.html
|
||||
06-02-2002 01:28:59 4459 positions.html
|
||||
06-02-2002 00:55:31 466 contact.html
|
|
@ -1,72 +0,0 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
cmnt: ftp> open ftp.microsoft.com
|
||||
cmnt: Microsoft FTP Service
|
||||
cmnt: Name (ftp.microsoft.com:cyp):
|
||||
cmnt: 331 Anonymous access allowed, send identity (e-mail name) as password.
|
||||
cmnt: 230-This is FTP.Microsoft.Com
|
||||
cmnt: Anonymous user logged in.
|
||||
cmnt: Remote system type is Windows_NT.
|
||||
cmnt: ftp> quote dirstyle
|
||||
cmnt: MSDOS-like directory output is on
|
||||
cmnt: ftp> quote dirstyle
|
||||
cmnt: MSDOS-like directory output is off
|
||||
cmnt: ftp> ls
|
||||
cmnt: PORT command successful.
|
||||
cmnt: Opening ASCII mode data connection for /bin/ls.
|
||||
list: 02-25-00 00:00AM <DIR> channel
|
||||
list: 02-25-00 00:00AM <DIR> Education
|
||||
list: 02-25-00 00:00AM <DIR> enterprise
|
||||
list: 06-20-01 00:00AM <DIR> ISN
|
||||
list: 05-31-01 00:00AM <DIR> Museum
|
||||
list: 02-14-01 00:00AM <DIR> TechNet
|
||||
list: 10-24-01 00:00AM <DIR> whql
|
||||
list: 02-05-01 00:00AM <DIR> 20Year
|
||||
list: 09-26-00 00:00AM <DIR> AccessFoxPro
|
||||
list: 12-21-00 00:00AM <DIR> AlbGrp
|
||||
list: 05-31-01 00:00AM <DIR> Alexandra
|
||||
list: 01-19-01 00:00AM <DIR> anna
|
||||
list: 04-06-00 00:00AM <DIR> anz
|
||||
list: 05-10-00 00:00AM <DIR> Chase Bobko2
|
||||
list: 03-29-00 00:00AM <DIR> cnn
|
||||
list: 11-21-00 00:00AM <DIR> Darin
|
||||
list: 03-09-00 00:00AM <DIR> digitalchicago
|
||||
list: 09-06-00 00:00AM <DIR> Dublin
|
||||
list: 01-30-01 00:00AM <DIR> eleanorf
|
||||
list: 04-26-01 00:00AM <DIR> girvin
|
||||
list: 04-26-00 00:00AM <DIR> Hires
|
||||
list: 08-15-00 00:00AM <DIR> HR
|
||||
list: 10-24-99 00:00AM 4368384 IMG00022.PCD
|
||||
list: 05-18-01 00:00AM <DIR> jacubowsky
|
||||
list: 10-12-00 00:00AM <DIR> JFalvey
|
||||
list: 03-28-01 00:00AM <DIR> johnci
|
||||
list: 07-14-00 00:00AM <DIR> Karin
|
||||
list: 09-07-00 00:00AM <DIR> Kjung
|
||||
list: 09-28-00 00:00AM <DIR> LarryE
|
||||
list: 08-17-00 00:00AM <DIR> Larson
|
||||
list: 09-12-00 00:00AM <DIR> marion
|
||||
list: 08-09-00 00:00AM <DIR> ms25
|
||||
list: 11-16-00 00:00AM <DIR> MS25Brochure
|
||||
list: 03-29-00 00:00AM <DIR> MShistory
|
||||
list: 09-05-00 00:00AM <DIR> Neils
|
||||
list: 08-02-00 00:00AM <DIR> NLM
|
||||
list: 09-06-00 00:00AM <DIR> PageOne
|
||||
list: 06-27-00 00:00AM <DIR> pccomputing
|
||||
list: 05-09-01 00:00AM <DIR> pictures
|
||||
list: 07-21-00 00:00AM <DIR> pranks
|
||||
list: 08-22-00 00:00AM <DIR> Sean
|
||||
list: 08-10-00 00:00AM <DIR> SLeong
|
||||
list: 09-07-00 00:00AM <DIR> svr
|
||||
list: 07-21-00 00:00AM <DIR> Transcontinental
|
||||
list: 10-23-00 00:00AM <DIR> veronist
|
||||
list: 06-15-00 00:00AM <DIR> zoe
|
||||
list: 07-14-00 00:00AM 2094926 canprankdesk.tif
|
||||
list: 07-21-00 00:00AM 95077 Jon Kauffman Enjoys the Good Life.jpg
|
||||
list: 07-21-00 00:00AM 52275 Name Plate.jpg
|
||||
list: 07-14-00 00:00AM 2250540 Valentineoffprank-HiRes.jpg
|
||||
junk: Transfer complete.
|
||||
junk: ftp> close
|
||||
junk: Thank-You For Using Microsoft Products!
|
||||
junk: ftp>
|
|
@ -13,6 +13,10 @@ UNIFIED_SOURCES += [
|
|||
'TestStandardURL.cpp',
|
||||
]
|
||||
|
||||
TEST_DIRS += [
|
||||
'parse-ftp',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul-gtest'
|
||||
|
|
|
@ -46,13 +46,15 @@ ftp> ls
|
|||
08-22-00 10:36AM <DIR> Sean
|
||||
08-10-00 01:54PM <DIR> SLeong
|
||||
09-07-00 05:46PM <DIR> svr
|
||||
07-21-00 07:22AM <JUNCTION> a_junction_sample
|
||||
10-23-00 01:27PM <JUNCTION> b_junction_sample => foo
|
||||
06-15-00 07:37AM <JUNCTION> c_junction_sample -> bar too
|
||||
07-14-00 01:35PM 2094926 canprankdesk.tif
|
||||
07-21-00 01:19PM 95077 Jon Kauffman Enjoys the Good Life.jpg
|
||||
07-21-00 01:19PM 52275 Name Plate.jpg
|
||||
07-14-00 01:38PM 2250540 Valentineoffprank-HiRes.jpg
|
||||
01-11-14 12:25AM 18864566 Bug961346
|
||||
10-10-2014 10:10AM <DIR> Bug1061898
|
||||
01-18-12 19:00 30724571 Bug1125816
|
||||
226 Transfer complete.
|
||||
ftp> close
|
||||
221 Thank-You For Using Microsoft Products!
|
|
@ -38,10 +38,12 @@
|
|||
08-22-2000 10:36:00 <DIR> Sean
|
||||
08-10-2000 13:54:00 <DIR> SLeong
|
||||
09-07-2000 17:46:00 <DIR> svr
|
||||
07-21-2000 07:22:00 <JUNCTION> a_junction_sample
|
||||
10-23-2000 13:27:00 <JUNCTION> b_junction_sample -> foo
|
||||
06-15-2000 07:37:00 <JUNCTION> c_junction_sample -> bar too
|
||||
07-14-2000 13:35:00 2094926 canprankdesk.tif
|
||||
07-21-2000 13:19:00 95077 Jon Kauffman Enjoys the Good Life.jpg
|
||||
07-21-2000 13:19:00 52275 Name Plate.jpg
|
||||
07-14-2000 13:38:00 2250540 Valentineoffprank-HiRes.jpg
|
||||
01-11-2014 00:25:00 18864566 Bug961346
|
||||
10-10-2014 10:10:00 <DIR> Bug1061898
|
||||
01-18-2012 19:00:00 30724571 Bug1125816
|
|
@ -0,0 +1,178 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
01-11-2001 09:38:25 <DIR> tmp
|
||||
02-29-2000 02:01:45 <DIR> 1998-100
|
||||
12-24-2000 20:44:06 1172 mirrors.html
|
||||
02-29-2000 02:01:47 <DIR> 1999-275
|
||||
05-08-2000 05:25:16 933 1999-275.html
|
||||
05-08-2000 05:27:06 322 1998-401.html
|
||||
02-29-2000 02:01:47 <DIR> 1998-401
|
||||
02-29-2000 02:01:45 <DIR> 1997-275
|
||||
05-08-2000 05:28:25 277 1998-515.html
|
||||
05-08-2000 05:36:43 325 2000-436.html
|
||||
12-23-2000 06:47:11 <DIR> checkpwd
|
||||
07-04-2002 17:47:46 <DIR> bib
|
||||
02-29-2000 02:01:47 <DIR> 1999-541
|
||||
04-28-2001 03:02:35 <DIR> 2001-260
|
||||
04-17-2002 18:27:55 <DIR> 2002-501
|
||||
06-01-2001 20:56:17 <DIR> 1995-514
|
||||
02-29-2000 02:01:45 <DIR> 1999-180
|
||||
06-01-2001 02:44:23 475 2000-515.html
|
||||
02-29-2000 02:01:47 <DIR> 1998-515
|
||||
02-29-2000 02:01:47 <DIR> 2000-515
|
||||
05-12-2000 07:46:13 <DIR> zpfft
|
||||
10-27-2001 13:00:14 <DIR> psibound
|
||||
02-29-2000 02:01:51 398 anonftpd.html
|
||||
02-26-2002 02:34:22 <DIR> nistp224
|
||||
08-10-2001 07:27:20 <DIR> cdb
|
||||
04-30-2002 23:58:27 <DIR> talks
|
||||
12-23-2000 21:34:35 <DIR> ftpparse
|
||||
01-11-2000 05:50:10 <DIR> clockspeed
|
||||
02-29-2000 02:01:52 1350 clockspeed.html
|
||||
02-06-2002 20:52:34 <DIR> slashdoc
|
||||
06-07-2002 16:00:40 <DIR> export
|
||||
07-17-2001 01:48:36 <DIR> daemontools
|
||||
07-12-2001 21:17:08 2187 daemontools.html
|
||||
08-30-2001 20:51:16 175 data.html
|
||||
12-28-2001 01:32:37 1901 crypto.html
|
||||
11-10-2001 23:45:23 739 arith.html
|
||||
11-30-2001 03:48:09 2733 cdb.html
|
||||
11-10-2001 23:51:02 344 floatasm.html
|
||||
12-21-2000 21:25:56 <DIR> djbfft
|
||||
02-29-2000 02:01:52 1319 djbfft.html
|
||||
07-09-2000 17:16:47 <DIR> dnscache
|
||||
07-03-2000 22:57:44 4016 dnscache.html
|
||||
07-10-2001 03:16:22 <DIR> docs
|
||||
02-29-2000 02:01:52 293 docs.html
|
||||
02-29-2000 02:01:52 952 dot-forward.html
|
||||
01-11-2000 05:50:11 <DIR> etc-mta
|
||||
02-29-2000 02:01:52 3174 etc-mta.html
|
||||
02-29-2000 02:01:52 2990 ezmlm.html
|
||||
12-21-2000 21:25:56 <DIR> ftp
|
||||
02-29-2000 02:01:49 <DIR> im
|
||||
02-29-2000 02:01:52 1823 fastforward.html
|
||||
02-29-2000 02:01:52 1707 ftp.html
|
||||
02-12-2001 02:17:10 <DIR> freebugtraq
|
||||
06-02-2002 01:39:25 <DIR> hardware
|
||||
06-01-2001 02:57:35 610 1995-514.html
|
||||
12-21-2000 21:25:57 <DIR> hash127
|
||||
05-19-2000 02:29:02 4835 hash127.html
|
||||
02-29-2000 02:01:52 1515 im.html
|
||||
02-12-2002 20:40:06 <DIR> immhf
|
||||
04-25-2001 21:48:49 2902 immhf.html
|
||||
01-25-2001 05:58:36 <DIR> lib
|
||||
06-05-2001 23:45:50 <DIR> libtai
|
||||
07-10-2000 18:54:16 2120 libtai.html
|
||||
11-27-2001 17:52:40 14460 qmail.html
|
||||
08-30-2001 20:51:23 2153 mail.html
|
||||
02-29-2000 02:01:52 108 maildir.html
|
||||
02-29-2000 02:01:50 <DIR> maildisasters
|
||||
02-29-2000 02:01:52 873 maildisasters.html
|
||||
02-29-2000 02:01:52 1688 mess822.html
|
||||
02-25-2002 02:47:27 <DIR> mirror
|
||||
12-26-2001 21:02:32 581 ntheory.html
|
||||
06-03-2002 15:36:27 <DIR> papers
|
||||
05-21-2002 16:50:15 <DIR> postpropter
|
||||
01-11-2000 05:50:12 <DIR> postings
|
||||
01-11-2000 05:50:12 <DIR> primegen
|
||||
08-30-2001 20:51:33 415 precompiled.html
|
||||
02-29-2000 02:01:52 1121 primegen.html
|
||||
05-05-2002 01:37:55 <DIR> proto
|
||||
02-29-2000 02:01:52 452 proto.html
|
||||
07-09-2001 22:17:53 <DIR> publicfile
|
||||
11-30-2001 03:59:17 5651 publicfile.html
|
||||
08-30-2001 20:51:37 409 qlist.html
|
||||
04-02-2002 05:25:45 <DIR> qmail
|
||||
02-02-2002 05:52:28 7549 lists.html
|
||||
02-29-2000 02:01:52 1166 qmailanalog.html
|
||||
08-30-2001 20:51:39 1434 qmsmac.html
|
||||
03-13-2000 05:38:06 465 rblsmtpd.html
|
||||
02-29-2000 02:01:52 1225 rights.html
|
||||
01-11-2000 05:50:13 <DIR> sarcasm
|
||||
09-14-2001 02:34:14 1058 unix.html
|
||||
02-29-2000 02:01:53 1568 serialmail.html
|
||||
10-02-2000 19:13:44 840 sigs.html
|
||||
05-21-2002 16:50:08 <DIR> smtp
|
||||
04-26-2001 15:58:47 1723 smtp.html
|
||||
12-21-2000 21:26:03 <DIR> software
|
||||
02-29-2000 02:01:53 3671 softwarelaw.html
|
||||
01-11-2000 05:50:13 <DIR> sortedsums
|
||||
02-29-2000 02:01:53 2735 sortedsums.html
|
||||
02-26-2002 02:06:38 <DIR> speed
|
||||
07-03-2000 22:40:17 572 surveydns.html
|
||||
10-04-2001 00:59:31 <DIR> surveys
|
||||
10-04-2001 00:46:36 3503 surveys.html
|
||||
11-10-2000 05:58:08 <DIR> syncookies
|
||||
02-01-2002 02:03:37 7918 syncookies.html
|
||||
08-30-2001 20:51:43 1115 tcpcontrol.html
|
||||
03-18-2002 12:08:27 589 tcpip.html
|
||||
02-29-2000 02:01:53 906 thoughts.html
|
||||
07-30-2001 14:18:09 <DIR> threecubes
|
||||
09-14-2001 01:25:58 525 time.html
|
||||
05-22-2002 04:21:05 <DIR> ucspi-tcp
|
||||
01-24-2002 23:41:31 4086 ucspi-tcp.html
|
||||
08-30-2001 20:51:50 732 web.html
|
||||
07-04-2002 17:47:48 <DIR> www
|
||||
09-14-2001 01:26:08 2717 y2k.html
|
||||
02-08-2001 09:28:09 <DIR> dnsroot
|
||||
04-23-2001 06:01:37 2745 2001-260.html
|
||||
02-29-2000 02:01:53 291 zeroseek.html
|
||||
02-29-2000 02:01:53 <DIR> zmodexp
|
||||
05-19-2000 02:29:07 1513 zmodexp.html
|
||||
08-01-2000 01:08:54 <DIR> zmult
|
||||
08-01-2000 01:24:53 669 zmult.html
|
||||
12-23-2000 06:19:47 1097 checkpwd.html
|
||||
12-31-2001 18:47:16 2077 focus.html
|
||||
07-19-2000 23:11:31 3842 im2000.html
|
||||
07-04-2002 17:47:43 <DIR> slashcommand
|
||||
07-04-2002 17:47:44 <DIR> slashpackage
|
||||
07-15-2001 22:24:39 602 slashpackage.html
|
||||
05-03-2002 00:15:33 23874 talks.html
|
||||
05-08-2000 05:24:59 566 1997-275.html
|
||||
05-08-2000 05:31:04 313 1998-100.html
|
||||
02-29-2000 02:01:47 <DIR> 2000-436
|
||||
05-08-2000 05:37:26 660 1997-494.html
|
||||
05-08-2000 05:40:41 730 1999-541.html
|
||||
09-13-2000 07:08:58 2367 psibound.html
|
||||
06-22-2000 02:09:00 1866 smallfactors.html
|
||||
06-09-2000 01:35:19 <DIR> smallfactors
|
||||
07-01-2000 22:55:19 <DIR> conferences
|
||||
05-22-2002 05:52:29 <DIR> djbdns
|
||||
07-17-2001 04:00:58 2712 djbdns.html
|
||||
07-17-2000 01:15:39 216 zpfft.html
|
||||
08-20-2000 18:56:37 <DIR> fastnewton
|
||||
08-20-2000 18:57:31 333 fastnewton.html
|
||||
09-01-2000 18:33:51 <DIR> patents
|
||||
12-23-2000 21:40:17 1748 ftpparse.html
|
||||
11-10-2001 23:57:50 3261 slash.html
|
||||
01-17-2001 17:50:10 2663 slashdoc.html
|
||||
03-18-2002 09:04:35 2344 dnsroot.html
|
||||
12-26-2001 21:03:00 3238 software.html
|
||||
09-14-2001 02:34:33 3481 compatibility.html
|
||||
05-01-2002 03:30:34 5525 distributors.html
|
||||
02-25-2001 03:49:03 212 freebugtraq.html
|
||||
03-31-2002 03:59:50 806 hardware.html
|
||||
03-31-2001 07:15:18 121 securesoftware.html
|
||||
05-22-2002 02:39:56 10950 conferences.html
|
||||
11-30-2001 17:51:50 <DIR> 2001-275
|
||||
05-29-2001 00:15:07 459 rwb100.html
|
||||
10-03-2001 05:40:42 863 nistp224.html
|
||||
07-30-2001 14:15:46 778 threecubes.html
|
||||
06-03-2002 15:32:16 27125 papers.html
|
||||
11-21-2001 02:51:09 379 bib.html
|
||||
11-30-2001 17:41:33 3791 2001-275.html
|
||||
10-02-2001 18:08:59 169 dh224.html
|
||||
03-16-2002 06:05:36 1126 courses.html
|
||||
09-02-2001 22:22:28 355 slashcommand.html
|
||||
03-27-2002 21:20:22 1199 patents.html
|
||||
11-08-2001 19:27:15 640 mailcopyright.html
|
||||
06-01-2002 23:27:50 1257 djb.html
|
||||
01-07-2002 06:12:34 301 2002-501.html
|
||||
05-01-2002 02:41:45 1655 export.html
|
||||
03-24-2002 10:40:59 6446 index.html
|
||||
03-16-2002 06:09:08 570 2005-590.html
|
||||
03-16-2002 06:05:49 800 2004-494.html
|
||||
06-01-2002 23:28:59 4459 positions.html
|
||||
06-01-2002 22:55:31 466 contact.html
|
|
@ -0,0 +1,169 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 "gtest/gtest.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#include "ParseFTPList.h"
|
||||
|
||||
|
||||
PRTime gTestTime = 0;
|
||||
|
||||
// Pretend this is the current time for the purpose of running the
|
||||
// test. The day and year matter because they are used to figure out a
|
||||
// default value when no year is specified. Tests will fail if this is
|
||||
// changed too much.
|
||||
const char* kDefaultTestTime = "01-Aug-2002 00:00:00 GMT";
|
||||
|
||||
static PRTime
|
||||
TestTime()
|
||||
{
|
||||
return gTestTime;
|
||||
}
|
||||
|
||||
static void
|
||||
ParseFTPLine(char* inputLine,
|
||||
FILE* resultFile,
|
||||
list_state* state)
|
||||
{
|
||||
struct list_result result;
|
||||
int rc = ParseFTPList(inputLine, state, &result, PR_GMTParameters, TestTime);
|
||||
|
||||
if (rc == '?' || rc == '"') {
|
||||
// Ignore junk and comments.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!resultFile) {
|
||||
// No result file was passed in, so there's nothing to check.
|
||||
return;
|
||||
}
|
||||
|
||||
char resultLine[512];
|
||||
ASSERT_NE(fgets(resultLine, sizeof(resultLine), resultFile), nullptr);
|
||||
|
||||
nsPrintfCString parsed("%02u-%02u-%04u %02u:%02u:%02u %20s %.*s%s%.*s\n",
|
||||
(result.fe_time.tm_mday ? (result.fe_time.tm_month + 1) : 0),
|
||||
result.fe_time.tm_mday,
|
||||
(result.fe_time.tm_mday ? result.fe_time.tm_year : 0),
|
||||
result.fe_time.tm_hour,
|
||||
result.fe_time.tm_min,
|
||||
result.fe_time.tm_sec,
|
||||
(rc == 'd' ? "<DIR> " :
|
||||
(rc == 'l' ? "<JUNCTION> " : result.fe_size)),
|
||||
(int)result.fe_fnlen, result.fe_fname,
|
||||
((rc == 'l' && result.fe_lnlen) ? " -> " : ""),
|
||||
(int)((rc == 'l' && result.fe_lnlen) ? result.fe_lnlen : 0),
|
||||
((rc == 'l' && result.fe_lnlen) ? result.fe_lname : ""));
|
||||
|
||||
ASSERT_STREQ(parsed.get(), resultLine);
|
||||
}
|
||||
|
||||
FILE*
|
||||
OpenResultFile(const char* resultFileName)
|
||||
{
|
||||
if (!resultFileName) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FILE* resultFile = fopen(resultFileName, "r");
|
||||
EXPECT_NE(resultFile, nullptr);
|
||||
|
||||
// Ignore anything in the expected result file before and including the first blank line.
|
||||
char resultLine[512];
|
||||
while (fgets(resultLine, sizeof(resultLine), resultFile)) {
|
||||
size_t lineLen = strlen(resultLine);
|
||||
EXPECT_LT(lineLen, sizeof(resultLine) - 1);
|
||||
if (lineLen > 0 && resultLine[lineLen - 1] == '\n') {
|
||||
lineLen--;
|
||||
}
|
||||
if (lineLen == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// There must be a blank line somewhere in the result file.
|
||||
EXPECT_EQ(strcmp(resultLine, "\n"), 0);
|
||||
|
||||
return resultFile;
|
||||
}
|
||||
|
||||
void
|
||||
ParseFTPFile(const char* inputFileName,
|
||||
const char* resultFileName)
|
||||
{
|
||||
printf("Checking %s\n", inputFileName);
|
||||
FILE* inFile = fopen(inputFileName, "r");
|
||||
ASSERT_NE(inFile, nullptr);
|
||||
|
||||
FILE* resultFile = OpenResultFile(resultFileName);
|
||||
|
||||
char inputLine[512];
|
||||
struct list_state state;
|
||||
memset(&state, 0, sizeof(state));
|
||||
while (fgets(inputLine, sizeof(inputLine), inFile)) {
|
||||
size_t lineLen = strlen(inputLine);
|
||||
EXPECT_LT(lineLen, sizeof(inputLine) - 1);
|
||||
if (lineLen > 0 && inputLine[lineLen - 1] == '\n') {
|
||||
lineLen--;
|
||||
}
|
||||
|
||||
ParseFTPLine(inputLine, resultFile, &state);
|
||||
}
|
||||
|
||||
// Make sure there are no extra lines in the result file.
|
||||
if (resultFile) {
|
||||
char resultLine[512];
|
||||
EXPECT_EQ(fgets(resultLine, sizeof(resultLine), resultFile), nullptr) <<
|
||||
"There should not be more lines in the expected results file than in the parser output.";
|
||||
fclose(resultFile);
|
||||
}
|
||||
|
||||
fclose(inFile);
|
||||
}
|
||||
|
||||
static const char* testFiles[] = {
|
||||
"3-guess",
|
||||
"C-VMold",
|
||||
"C-zVM",
|
||||
"D-WinNT",
|
||||
"E-EPLF",
|
||||
"O-guess",
|
||||
"R-dls",
|
||||
"U-HellSoft",
|
||||
"U-hethmon",
|
||||
"U-murksw",
|
||||
"U-ncFTPd",
|
||||
"U-NetPresenz",
|
||||
"U-NetWare",
|
||||
"U-nogid",
|
||||
"U-no_ug",
|
||||
"U-Novonyx",
|
||||
"U-proftpd",
|
||||
"U-Surge",
|
||||
"U-WarFTPd",
|
||||
"U-WebStar",
|
||||
"U-WinNT",
|
||||
"U-wu",
|
||||
"V-MultiNet",
|
||||
"V-VMS-mix",
|
||||
};
|
||||
|
||||
TEST(ParseFTPTest, Check)
|
||||
{
|
||||
PRStatus result = PR_ParseTimeString(kDefaultTestTime, true, &gTestTime);
|
||||
ASSERT_EQ(PR_SUCCESS, result);
|
||||
|
||||
char inputFileName[200];
|
||||
char resultFileName[200];
|
||||
for (size_t test = 0; test < mozilla::ArrayLength(testFiles); ++test) {
|
||||
snprintf(inputFileName, mozilla::ArrayLength(inputFileName), "%s.in", testFiles[test]);
|
||||
snprintf(resultFileName, mozilla::ArrayLength(inputFileName), "%s.out", testFiles[test]);
|
||||
ParseFTPFile(inputFileName, resultFileName);
|
||||
}
|
||||
}
|
||||
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче