Merge mozilla-central to b2g-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-01-18 14:25:19 +01:00
commit 6d9a12357f
131 changed files with 2908 additions and 2705 deletions

View File

@ -1342,9 +1342,7 @@ var CustomizableUIInternal = {
}
let tooltip = this.getLocalizedProperty(aWidget, "tooltiptext", additionalTooltipArguments);
if (tooltip) {
node.setAttribute("tooltiptext", tooltip);
}
node.setAttribute("tooltiptext", tooltip);
node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional");
let commandHandler = this.handleWidgetCommand.bind(this, aWidget, node);
@ -1387,8 +1385,6 @@ var CustomizableUIInternal = {
},
getLocalizedProperty: function(aWidget, aProp, aFormatArgs, aDef) {
const kReqStringProps = ["label"];
if (typeof aWidget == "string") {
aWidget = gPalette.get(aWidget);
}
@ -1399,7 +1395,7 @@ var CustomizableUIInternal = {
// Let widgets pass their own string identifiers or strings, so that
// we can use strings which aren't the default (in case string ids change)
// and so that non-builtin-widgets can also provide labels, tooltips, etc.
if (aWidget[aProp] != null) {
if (aWidget[aProp]) {
name = aWidget[aProp];
// By using this as the default, if a widget provides a full string rather
// than a string ID for localization, we will fall back to that string
@ -1416,9 +1412,7 @@ var CustomizableUIInternal = {
}
return gWidgetsBundle.GetStringFromName(name) || def;
} catch(ex) {
// If an empty string was explicitly passed, treat it as an actual
// value rather than a missing property.
if (!def && (name != "" || kReqStringProps.includes(aProp))) {
if (!def) {
ERROR("Could not localize property '" + name + "'.");
}
}
@ -2342,7 +2336,6 @@ var CustomizableUIInternal = {
this.wrapWidgetEventHandler("onBeforeCreated", widget);
this.wrapWidgetEventHandler("onClick", widget);
this.wrapWidgetEventHandler("onCreated", widget);
this.wrapWidgetEventHandler("onDestroyed", widget);
if (widget.type == "button") {
widget.onCommand = typeof aData.onCommand == "function" ?
@ -2446,9 +2439,6 @@ var CustomizableUIInternal = {
}
}
}
if (widgetNode && widget.onDestroyed) {
widget.onDestroyed(window.document);
}
}
gPalette.delete(aWidgetId);
@ -3182,11 +3172,6 @@ this.CustomizableUI = {
* - onCreated(aNode): Attached to all widgets; a function that will be invoked
* whenever the widget has a DOM node constructed, passing the
* constructed node as an argument.
* - onDestroyed(aDoc): Attached to all non-custom widgets; a function that
* will be invoked after the widget has a DOM node destroyed,
* passing the document from which it was removed. This is
* useful especially for 'view' type widgets that need to
* cleanup after views that were constructed on the fly.
* - onCommand(aEvt): Only useful for button widgets; a function that will be
* invoked when the user activates the button.
* - onClick(aEvt): Attached to all widgets; a function that will be invoked

View File

@ -329,7 +329,6 @@ const PanelUI = {
evt.initCustomEvent("ViewShowing", true, true, viewNode);
viewNode.dispatchEvent(evt);
if (evt.defaultPrevented) {
aAnchor.open = false;
return;
}

View File

@ -326,12 +326,6 @@
]]></body>
</method>
<method name="_shouldSetPosition">
<body><![CDATA[
return this.getAttribute("nosubviews") == "true";
]]></body>
</method>
<method name="_shouldSetHeight">
<body><![CDATA[
return this.getAttribute("nosubviews") != "true";
@ -351,19 +345,6 @@
this.ignoreMutations = false;
]]></body>
</method>
<method name="_adjustContainerHeight">
<body><![CDATA[
if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
let height;
if (this.showingSubViewAsMainView) {
height = this._heightOfSubview(this._mainView);
} else {
height = this._mainView.scrollHeight;
}
this._viewContainer.style.height = height + "px";
}
]]></body>
</method>
<method name="_syncContainerWithSubView">
<body><![CDATA[
// Check that this panel is still alive:
@ -380,16 +361,18 @@
<method name="_syncContainerWithMainView">
<body><![CDATA[
// Check that this panel is still alive:
if (!this._panel || !this._panel.parentNode) {
if (!this._panel || !this._panel.parentNode || !this._shouldSetHeight()) {
return;
}
if (this._shouldSetPosition()) {
this._panel.adjustArrowPosition();
}
if (this._shouldSetHeight()) {
this._adjustContainerHeight();
if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
let height;
if (this.showingSubViewAsMainView) {
height = this._heightOfSubview(this._mainView);
} else {
height = this._mainView.scrollHeight;
}
this._viewContainer.style.height = height + "px";
}
]]></body>
</method>

View File

@ -2,14 +2,13 @@
"extends": "../../../toolkit/components/extensions/.eslintrc",
"globals": {
"AllWindowEvents": true,
"currentWindow": true,
"EventEmitter": true,
"IconDetails": true,
"openPanel": true,
"makeWidgetId": true,
"PanelPopup": true,
"TabContext": true,
"ViewPopup": true,
"AllWindowEvents": true,
"WindowEventManager": true,
"WindowListManager": true,
"WindowManager": true,

View File

@ -13,8 +13,6 @@ var {
runSafe,
} = ExtensionUtils;
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// WeakMap[Extension -> BrowserAction]
var browserActionMap = new WeakMap();
@ -26,10 +24,7 @@ function browserActionOf(extension) {
// as the associated popup.
function BrowserAction(options, extension) {
this.extension = extension;
let widgetId = makeWidgetId(extension.id);
this.id = `${widgetId}-browser-action`;
this.viewId = `PanelUI-webext-${widgetId}-browser-action-view`;
this.id = makeWidgetId(extension.id) + "-browser-action";
this.widget = null;
this.tabManager = TabManager.for(extension);
@ -42,7 +37,7 @@ function BrowserAction(options, extension) {
this.defaults = {
enabled: true,
title: title || extension.name,
title: title,
badgeText: "",
badgeBackgroundColor: null,
icon: IconDetails.normalize({ path: options.default_icon }, extension,
@ -60,60 +55,31 @@ BrowserAction.prototype = {
build() {
let widget = CustomizableUI.createWidget({
id: this.id,
viewId: this.viewId,
type: "view",
type: "custom",
removable: true,
label: this.defaults.title || this.extension.name,
tooltiptext: this.defaults.title || "",
defaultArea: CustomizableUI.AREA_NAVBAR,
onBeforeCreated: document => {
let view = document.createElementNS(XUL_NS, "panelview");
view.id = this.viewId;
view.setAttribute("flex", "1");
document.getElementById("PanelUI-multiView").appendChild(view);
},
onDestroyed: document => {
let view = document.getElementById(this.viewId);
if (view) {
view.remove();
}
},
onCreated: node => {
node.classList.add("badged-button");
onBuild: document => {
let node = document.createElement("toolbarbutton");
node.id = this.id;
node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional badged-button");
node.setAttribute("constrain-size", "true");
this.updateButton(node, this.defaults);
},
onViewShowing: event => {
let document = event.target.ownerDocument;
let tabbrowser = document.defaultView.gBrowser;
let tab = tabbrowser.selectedTab;
let popupURL = this.getProperty(tab, "popup");
this.tabManager.addActiveTabPermission(tab);
// If the widget has a popup URL defined, we open a popup, but do not
// dispatch a click event to the extension.
// If it has no popup URL defined, we dispatch a click event, but do not
// open a popup.
if (popupURL) {
try {
new ViewPopup(this.extension, event.target, popupURL);
} catch (e) {
Cu.reportError(e);
event.preventDefault();
node.addEventListener("command", event => { // eslint-disable-line mozilla/balanced-listeners
let tab = tabbrowser.selectedTab;
let popup = this.getProperty(tab, "popup");
this.tabManager.addActiveTabPermission(tab);
if (popup) {
this.togglePopup(node, popup);
} else {
this.emit("click");
}
} else {
// This isn't not a hack, but it seems to provide the correct behavior
// with the fewest complications.
event.preventDefault();
this.emit("click");
}
});
return node;
},
});
@ -123,12 +89,22 @@ BrowserAction.prototype = {
this.widget = widget;
},
togglePopup(node, popupResource) {
openPanel(node, popupResource, this.extension);
},
// Update the toolbar button |node| with the tab context data
// in |tabData|.
updateButton(node, tabData) {
let title = tabData.title || this.extension.name;
node.setAttribute("tooltiptext", title);
node.setAttribute("label", title);
if (tabData.title) {
node.setAttribute("tooltiptext", tabData.title);
node.setAttribute("label", tabData.title);
node.setAttribute("aria-label", tabData.title);
} else {
node.removeAttribute("tooltiptext");
node.removeAttribute("label");
node.removeAttribute("aria-label");
}
if (tabData.badgeText) {
node.setAttribute("badge", tabData.badgeText);
@ -186,10 +162,8 @@ BrowserAction.prototype = {
setProperty(tab, prop, value) {
if (tab == null) {
this.defaults[prop] = value;
} else if (value != null) {
this.tabContext.get(tab)[prop] = value;
} else {
delete this.tabContext.get(tab)[prop];
this.tabContext.get(tab)[prop] = value;
}
this.updateOnChange(tab);
@ -252,13 +226,7 @@ extensions.registerSchemaAPI("browserAction", null, (extension, context) => {
setTitle: function(details) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let title = details.title;
// Clear the tab-specific title when given a null string.
if (tab && title == "") {
title = null;
}
browserActionOf(extension).setProperty(tab, "title", title);
browserActionOf(extension).setProperty(tab, "title", details.title);
},
getTitle: function(details, callback) {

View File

@ -28,7 +28,7 @@ function PageAction(options, extension) {
this.defaults = {
show: false,
title: title || extension.name,
title: title,
icon: IconDetails.normalize({ path: options.default_icon }, extension,
null, true),
popup: popup && extension.baseURI.resolve(popup),
@ -58,12 +58,7 @@ PageAction.prototype = {
// If |tab| is currently selected, updates the page action button to
// reflect the new value.
setProperty(tab, prop, value) {
if (value != null) {
this.tabContext.get(tab)[prop] = value;
} else {
delete this.tabContext.get(tab)[prop];
}
this.tabContext.get(tab)[prop] = value;
if (tab.selected) {
this.updateButton(tab.ownerDocument.defaultView);
}
@ -89,9 +84,13 @@ PageAction.prototype = {
if (tabData.show) {
// Update the title and icon only if the button is visible.
let title = tabData.title || this.extension.name;
button.setAttribute("tooltiptext", title);
button.setAttribute("aria-label", title);
if (tabData.title) {
button.setAttribute("tooltiptext", tabData.title);
button.setAttribute("aria-label", tabData.title);
} else {
button.removeAttribute("tooltiptext");
button.removeAttribute("aria-label");
}
let icon = IconDetails.getURL(tabData.icon, window, this.extension);
button.setAttribute("src", icon);
@ -138,16 +137,12 @@ PageAction.prototype = {
// the any click listeners in the add-on.
handleClick(window) {
let tab = window.gBrowser.selectedTab;
let popupURL = this.tabContext.get(tab).popup;
let popup = this.tabContext.get(tab).popup;
this.tabManager.addActiveTabPermission(tab);
// If the widget has a popup URL defined, we open a popup, but do not
// dispatch a click event to the extension.
// If it has no popup URL defined, we dispatch a click event, but do not
// open a popup.
if (popupURL) {
new PanelPopup(this.extension, this.getButton(window), popupURL);
if (popup) {
openPanel(this.getButton(window), popup, this.extension);
} else {
this.emit("click", tab);
}
@ -218,9 +213,7 @@ extensions.registerSchemaAPI("pageAction", null, (extension, context) => {
setTitle(details) {
let tab = TabManager.getTab(details.tabId);
// Clear the tab-specific title when given a null string.
PageAction.for(extension).setProperty(tab, "title", details.title || null);
PageAction.for(extension).setProperty(tab, "title", details.title);
},
getTitle(details, callback) {

View File

@ -2,16 +2,12 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const INTEGER = /^[1-9]\d*$/;
var {
@ -130,203 +126,103 @@ global.makeWidgetId = id => {
return id.replace(/[^a-z0-9_-]/g, "_");
};
class BasePopup {
constructor(extension, viewNode, popupURL) {
let popupURI = Services.io.newURI(popupURL, null, extension.baseURI);
// Open a panel anchored to the given node, containing a browser opened
// to the given URL, owned by the given extension. If |popupURL| is not
// an absolute URL, it is resolved relative to the given extension's
// base URL.
global.openPanel = (node, popupURL, extension) => {
let document = node.ownerDocument;
Services.scriptSecurityManager.checkLoadURIWithPrincipal(
extension.principal, popupURI,
Services.scriptSecurityManager.DISALLOW_SCRIPT);
let popupURI = Services.io.newURI(popupURL, null, extension.baseURI);
this.extension = extension;
this.popupURI = popupURI;
this.viewNode = viewNode;
this.window = viewNode.ownerDocument.defaultView;
Services.scriptSecurityManager.checkLoadURIWithPrincipal(
extension.principal, popupURI,
Services.scriptSecurityManager.DISALLOW_SCRIPT);
this.contentReady = new Promise(resolve => {
this._resolveContentReady = resolve;
});
this.viewNode.addEventListener(this.DESTROY_EVENT, this);
this.browser = null;
this.browserReady = this.createBrowser(viewNode, popupURI);
}
destroy() {
this.browserReady.then(() => {
this.browser.removeEventListener("load", this, true);
this.browser.removeEventListener("DOMTitleChanged", this, true);
this.browser.removeEventListener("DOMWindowClose", this, true);
this.viewNode.removeEventListener(this.DESTROY_EVENT, this);
this.context.unload();
this.browser.remove();
this.browser = null;
this.viewNode = null;
this.context = null;
});
}
// Returns the name of the event fired on `viewNode` when the popup is being
// destroyed. This must be implemented by every subclass.
get DESTROY_EVENT() {
throw new Error("Not implemented");
}
handleEvent(event) {
switch (event.type) {
case this.DESTROY_EVENT:
this.destroy();
break;
case "DOMWindowClose":
if (event.target === this.browser.contentWindow) {
event.preventDefault();
this.closePopup();
}
break;
case "DOMTitleChanged":
this.viewNode.setAttribute("aria-label", this.browser.contentTitle);
break;
case "load":
// We use a capturing listener, so we get this event earlier than any
// load listeners in the content page. Resizing after a timeout ensures
// that we calculate the size after the entire event cycle has completed
// (unless someone spins the event loop, anyway), and hopefully after
// the content has made any modifications.
//
// In the future, to match Chrome's behavior, we'll need to update this
// dynamically, probably in response to MozScrolledAreaChanged events.
this.window.setTimeout(() => this.resizeBrowser(), 0);
break;
}
}
createBrowser(viewNode, popupURI) {
let document = viewNode.ownerDocument;
this.browser = document.createElementNS(XUL_NS, "browser");
this.browser.setAttribute("type", "content");
this.browser.setAttribute("disableglobalhistory", "true");
// Note: When using noautohide panels, the popup manager will add width and
// height attributes to the panel, breaking our resize code, if the browser
// starts out smaller than 30px by 10px. This isn't an issue now, but it
// will be if and when we popup debugging.
// This overrides the content's preferred size when displayed in a
// fixed-size, slide-in panel.
this.browser.setAttribute("flex", "1");
viewNode.appendChild(this.browser);
return new Promise(resolve => {
// The first load event is for about:blank.
// We can't finish setting up the browser until the binding has fully
// initialized. Waiting for the first load event guarantees that it has.
let loadListener = event => {
this.browser.removeEventListener("load", loadListener, true);
resolve();
};
this.browser.addEventListener("load", loadListener, true);
}).then(() => {
let { contentWindow } = this.browser;
contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.allowScriptsToClose();
this.context = new ExtensionPage(this.extension, {
type: "popup",
contentWindow,
uri: popupURI,
docShell: this.browser.docShell,
});
GlobalManager.injectInDocShell(this.browser.docShell, this.extension, this.context);
this.browser.setAttribute("src", this.context.uri.spec);
this.browser.addEventListener("load", this, true);
this.browser.addEventListener("DOMTitleChanged", this, true);
this.browser.addEventListener("DOMWindowClose", this, true);
});
}
// Resizes the browser to match the preferred size of the content.
resizeBrowser() {
let width, height;
try {
let w = {}, h = {};
this.browser.docShell.contentViewer.getContentSize(w, h);
width = w.value / this.window.devicePixelRatio;
height = h.value / this.window.devicePixelRatio;
// The width calculation is imperfect, and is often a fraction of a pixel
// too narrow, even after taking the ceiling, which causes lines of text
// to wrap.
width += 1;
} catch (e) {
// getContentSize can throw
[width, height] = [400, 400];
}
width = Math.ceil(Math.min(width, 800));
height = Math.ceil(Math.min(height, 600));
this.browser.style.width = `${width}px`;
this.browser.style.height = `${height}px`;
this._resolveContentReady();
}
}
global.PanelPopup = class PanelPopup extends BasePopup {
constructor(extension, imageNode, popupURL) {
let document = imageNode.ownerDocument;
let panel = document.createElement("panel");
panel.setAttribute("id", makeWidgetId(extension.id) + "-panel");
panel.setAttribute("class", "browser-extension-panel");
panel.setAttribute("type", "arrow");
panel.setAttribute("role", "group");
let panel = document.createElement("panel");
panel.setAttribute("id", makeWidgetId(extension.id) + "-panel");
panel.setAttribute("class", "browser-extension-panel");
panel.setAttribute("type", "arrow");
panel.setAttribute("role", "group");
let anchor;
if (node.localName == "toolbarbutton") {
// Toolbar buttons are a special case. The panel becomes a child of
// the button, and is anchored to the button's icon.
node.appendChild(panel);
anchor = document.getAnonymousElementByAttribute(node, "class", "toolbarbutton-icon");
} else {
// In all other cases, the panel is anchored to the target node
// itself, and is a child of a popupset node.
document.getElementById("mainPopupSet").appendChild(panel);
anchor = node;
}
super(extension, panel, popupURL);
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
let browser = document.createElementNS(XUL_NS, "browser");
browser.setAttribute("type", "content");
browser.setAttribute("disableglobalhistory", "true");
panel.appendChild(browser);
this.contentReady.then(() => {
panel.openPopup(imageNode, "bottomcenter topright", 0, 0, false, false);
let titleChangedListener = () => {
panel.setAttribute("aria-label", browser.contentTitle);
};
let context;
let popuphidden = () => {
panel.removeEventListener("popuphidden", popuphidden);
browser.removeEventListener("DOMTitleChanged", titleChangedListener, true);
context.unload();
panel.remove();
};
panel.addEventListener("popuphidden", popuphidden);
let loadListener = () => {
panel.removeEventListener("load", loadListener);
context = new ExtensionPage(extension, {
type: "popup",
contentWindow: browser.contentWindow,
uri: popupURI,
docShell: browser.docShell,
});
}
GlobalManager.injectInDocShell(browser.docShell, extension, context);
browser.setAttribute("src", context.uri.spec);
get DESTROY_EVENT() {
return "popuphidden";
}
let contentLoadListener = event => {
if (event.target != browser.contentDocument) {
return;
}
browser.removeEventListener("load", contentLoadListener, true);
destroy() {
super.destroy();
this.viewNode.remove();
}
let contentViewer = browser.docShell.contentViewer;
let width = {}, height = {};
try {
contentViewer.getContentSize(width, height);
[width, height] = [width.value, height.value];
} catch (e) {
// getContentSize can throw
[width, height] = [400, 400];
}
closePopup() {
this.viewNode.hidePopup();
}
};
let window = document.defaultView;
width /= window.devicePixelRatio;
height /= window.devicePixelRatio;
width = Math.min(width, 800);
height = Math.min(height, 800);
global.ViewPopup = class ViewPopup extends BasePopup {
get DESTROY_EVENT() {
return "ViewHiding";
}
browser.setAttribute("width", width);
browser.setAttribute("height", height);
closePopup() {
CustomizableUI.hidePanelForNode(this.viewNode);
}
panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
};
browser.addEventListener("load", contentLoadListener, true);
browser.addEventListener("DOMTitleChanged", titleChangedListener, true);
};
panel.addEventListener("load", loadListener);
return panel;
};
// Manages tab-specific context data, and dispatching tab select events

View File

@ -10,9 +10,6 @@
"XPCOMUtils": true,
"Task": true,
// Browser window globals.
"PanelUI": false,
// Test harness globals
"ExtensionTestUtils": false,

View File

@ -2,141 +2,8 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
function* runTests(options) {
function background(getTests) {
// Gets the current details of the browser action, and returns a
// promise that resolves to an object containing them.
function getDetails(tabId) {
return Promise.all([
new Promise(resolve => browser.browserAction.getTitle({tabId}, resolve)),
new Promise(resolve => browser.browserAction.getPopup({tabId}, resolve)),
new Promise(resolve => browser.browserAction.getBadgeText({tabId}, resolve)),
new Promise(resolve => browser.browserAction.getBadgeBackgroundColor({tabId}, resolve))]
).then(details => {
return Promise.resolve({ title: details[0],
popup: details[1],
badge: details[2],
badgeBackgroundColor: details[3] });
});
}
function checkDetails(expecting, tabId) {
return getDetails(tabId).then(details => {
browser.test.assertEq(expecting.title, details.title,
"expected value from getTitle");
browser.test.assertEq(expecting.popup, details.popup,
"expected value from getPopup");
browser.test.assertEq(expecting.badge, details.badge,
"expected value from getBadge");
browser.test.assertEq(String(expecting.badgeBackgroundColor),
String(details.badgeBackgroundColor),
"expected value from getBadgeBackgroundColor");
});
}
let expectDefaults = expecting => {
return checkDetails(expecting);
};
let tabs = [];
let tests = getTests(tabs, expectDefaults);
// Runs the next test in the `tests` array, checks the results,
// and passes control back to the outer test scope.
function nextTest() {
let test = tests.shift();
test(expecting => {
// Check that the API returns the expected values, and then
// run the next test.
new Promise(resolve => {
return browser.tabs.query({ active: true, currentWindow: true }, resolve);
}).then(tabs => {
return checkDetails(expecting, tabs[0].id);
}).then(() => {
// Check that the actual icon has the expected values, then
// run the next test.
browser.test.sendMessage("nextTest", expecting, tests.length);
});
});
}
browser.test.onMessage.addListener((msg) => {
if (msg != "runNextTest") {
browser.test.fail("Expecting 'runNextTest' message");
}
nextTest();
});
browser.tabs.query({ active: true, currentWindow: true }, resultTabs => {
tabs[0] = resultTabs[0].id;
nextTest();
});
}
let extension = ExtensionTestUtils.loadExtension({
manifest: options.manifest,
background: `(${background})(${options.getTests})`,
});
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
function checkDetails(details) {
let button = document.getElementById(browserActionId);
ok(button, "button exists");
let title = details.title || options.manifest.name;
is(button.getAttribute("image"), details.icon, "icon URL is correct");
is(button.getAttribute("tooltiptext"), title, "image title is correct");
is(button.getAttribute("label"), title, "image label is correct");
is(button.getAttribute("badge"), details.badge, "badge text is correct");
is(button.getAttribute("disabled") == "true", Boolean(details.disabled), "disabled state is correct");
if (details.badge && details.badgeBackgroundColor) {
let badge = button.ownerDocument.getAnonymousElementByAttribute(
button, "class", "toolbarbutton-badge");
let badgeColor = window.getComputedStyle(badge).backgroundColor;
let color = details.badgeBackgroundColor;
let expectedColor = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
is(badgeColor, expectedColor, "badge color is correct");
}
// TODO: Popup URL.
}
let awaitFinish = new Promise(resolve => {
extension.onMessage("nextTest", (expecting, testsRemaining) => {
checkDetails(expecting);
if (testsRemaining) {
extension.sendMessage("runNextTest");
} else {
resolve();
}
});
});
yield extension.startup();
yield awaitFinish;
yield extension.unload();
}
add_task(function* testTabSwitchContext() {
yield runTests({
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"browser_action": {
"default_icon": "default.png",
@ -146,7 +13,7 @@ add_task(function* testTabSwitchContext() {
"permissions": ["tabs"],
},
getTests(tabs, expectDefaults) {
background: function() {
let details = [
{ "icon": browser.runtime.getURL("default.png"),
"popup": browser.runtime.getURL("default.html"),
@ -183,7 +50,10 @@ add_task(function* testTabSwitchContext() {
"badgeBackgroundColor": [0, 0xff, 0, 0xff] },
];
return [
let tabs = [];
let expectDefaults;
let tests = [
expect => {
browser.test.log("Initial state, expect default properties.");
expectDefaults(details[0]).then(() => {
@ -287,82 +157,124 @@ add_task(function* testTabSwitchContext() {
});
},
];
// Gets the current details of the browser action, and returns a
// promise that resolves to an object containing them.
function getDetails(tabId) {
return Promise.all([
new Promise(resolve => browser.browserAction.getTitle({tabId}, resolve)),
new Promise(resolve => browser.browserAction.getPopup({tabId}, resolve)),
new Promise(resolve => browser.browserAction.getBadgeText({tabId}, resolve)),
new Promise(resolve => browser.browserAction.getBadgeBackgroundColor({tabId}, resolve))]
).then(details => {
return Promise.resolve({ title: details[0],
popup: details[1],
badge: details[2],
badgeBackgroundColor: details[3] });
});
}
function checkDetails(expecting, tabId) {
return getDetails(tabId).then(details => {
browser.test.assertEq(expecting.title, details.title,
"expected value from getTitle");
browser.test.assertEq(expecting.popup, details.popup,
"expected value from getPopup");
browser.test.assertEq(expecting.badge, details.badge,
"expected value from getBadge");
browser.test.assertEq(String(expecting.badgeBackgroundColor),
String(details.badgeBackgroundColor),
"expected value from getBadgeBackgroundColor");
});
}
expectDefaults = expecting => {
return checkDetails(expecting);
};
// Runs the next test in the `tests` array, checks the results,
// and passes control back to the outer test scope.
function nextTest() {
let test = tests.shift();
test(expecting => {
// Check that the API returns the expected values, and then
// run the next test.
new Promise(resolve => {
return browser.tabs.query({ active: true, currentWindow: true }, resolve);
}).then(tabs => {
return checkDetails(expecting, tabs[0].id);
}).then(() => {
// Check that the actual icon has the expected values, then
// run the next test.
browser.test.sendMessage("nextTest", expecting, tests.length);
});
});
}
browser.test.onMessage.addListener((msg) => {
if (msg != "runNextTest") {
browser.test.fail("Expecting 'runNextTest' message");
}
nextTest();
});
browser.tabs.query({ active: true, currentWindow: true }, resultTabs => {
tabs[0] = resultTabs[0].id;
nextTest();
});
},
});
});
add_task(function* testDefaultTitle() {
yield runTests({
manifest: {
"name": "Foo Extension",
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
"browser_action": {
"default_icon": "icon.png",
},
function checkDetails(details) {
let button = document.getElementById(browserActionId);
"permissions": ["tabs"],
},
ok(button, "button exists");
getTests(tabs, expectDefaults) {
let details = [
{ "title": "Foo Extension",
"popup": "",
"badge": "",
"badgeBackgroundColor": null,
"icon": browser.runtime.getURL("icon.png") },
{ "title": "Foo Title",
"popup": "",
"badge": "",
"badgeBackgroundColor": null,
"icon": browser.runtime.getURL("icon.png") },
{ "title": "Bar Title",
"popup": "",
"badge": "",
"badgeBackgroundColor": null,
"icon": browser.runtime.getURL("icon.png") },
{ "title": "",
"popup": "",
"badge": "",
"badgeBackgroundColor": null,
"icon": browser.runtime.getURL("icon.png") },
];
is(button.getAttribute("image"), details.icon, "icon URL is correct");
is(button.getAttribute("tooltiptext"), details.title, "image title is correct");
is(button.getAttribute("label"), details.title, "image label is correct");
is(button.getAttribute("aria-label"), details.title, "image aria-label is correct");
is(button.getAttribute("badge"), details.badge, "badge text is correct");
is(button.getAttribute("disabled") == "true", Boolean(details.disabled), "disabled state is correct");
return [
expect => {
browser.test.log("Initial state. Expect extension title as default title.");
expectDefaults(details[0]).then(() => {
expect(details[0]);
});
},
expect => {
browser.test.log("Change the title. Expect new title.");
browser.browserAction.setTitle({ tabId: tabs[0], title: "Foo Title" });
expectDefaults(details[0]).then(() => {
expect(details[1]);
});
},
expect => {
browser.test.log("Change the default. Expect same properties.");
browser.browserAction.setTitle({ title: "Bar Title" });
expectDefaults(details[2]).then(() => {
expect(details[1]);
});
},
expect => {
browser.test.log("Clear the title. Expect new default title.");
browser.browserAction.setTitle({ tabId: tabs[0], title: "" });
expectDefaults(details[2]).then(() => {
expect(details[2]);
});
},
expect => {
browser.test.log("Set default title to null string. Expect null string from API, extension title in UI.");
browser.browserAction.setTitle({ title: "" });
expectDefaults(details[3]).then(() => {
expect(details[3]);
});
},
];
},
if (details.badge && details.badgeBackgroundColor) {
let badge = button.ownerDocument.getAnonymousElementByAttribute(
button, "class", "toolbarbutton-badge");
let badgeColor = window.getComputedStyle(badge).backgroundColor;
let color = details.badgeBackgroundColor;
let expectedColor = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
is(badgeColor, expectedColor, "badge color is correct");
}
// TODO: Popup URL.
}
let awaitFinish = new Promise(resolve => {
extension.onMessage("nextTest", (expecting, testsRemaining) => {
checkDetails(expecting);
if (testsRemaining) {
extension.sendMessage("runNextTest");
} else {
resolve();
}
});
});
yield extension.startup();
yield awaitFinish;
yield extension.unload();
});

View File

@ -2,7 +2,21 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
function* testInArea(area) {
function promisePopupShown(popup) {
return new Promise(resolve => {
if (popup.popupOpen) {
resolve();
} else {
let onPopupShown = event => {
popup.removeEventListener("popupshown", onPopupShown);
resolve();
};
popup.addEventListener("popupshown", onPopupShown);
}
});
}
add_task(function* testPageActionPopup() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"background": {
@ -102,34 +116,30 @@ function* testInArea(area) {
},
});
let panelId = makeWidgetId(extension.id) + "-panel";
extension.onMessage("send-click", () => {
clickBrowserAction(extension);
});
let widget;
extension.onMessage("next-test", Task.async(function* () {
if (!widget) {
widget = getBrowserActionWidget(extension);
CustomizableUI.addWidgetToArea(widget.id, area);
}
let panel = document.getElementById(panelId);
if (panel) {
yield promisePopupShown(panel);
panel.hidePopup();
yield closeBrowserAction(extension);
panel = document.getElementById(panelId);
is(panel, null, "panel successfully removed from document after hiding");
}
extension.sendMessage("next-test");
}));
yield Promise.all([extension.startup(), extension.awaitFinish("browseraction-tests-done")]);
yield extension.unload();
let view = document.getElementById(widget.viewId);
is(view, null, "browserAction view removed from document");
}
add_task(function* testBrowserActionInToolbar() {
yield testInArea(CustomizableUI.AREA_NAVBAR);
});
add_task(function* testBrowserActionInPanel() {
yield testInArea(CustomizableUI.AREA_PANEL);
let panel = document.getElementById(panelId);
is(panel, null, "browserAction panel removed from document");
});

View File

@ -33,13 +33,23 @@ add_task(function* () {
yield extension.startup();
let widgetId = makeWidgetId(extension.id) + "-browser-action";
let node = CustomizableUI.getWidget(widgetId).forWindow(window).node;
// Do this a few times to make sure the pop-up is reloaded each time.
for (let i = 0; i < 3; i++) {
clickBrowserAction(extension);
let evt = new CustomEvent("command", {
bubbles: true,
cancelable: true,
});
node.dispatchEvent(evt);
yield extension.awaitMessage("popup");
closeBrowserAction(extension);
let panel = node.querySelector("panel");
if (panel) {
panel.hidePopup();
}
}
yield extension.unload();

View File

@ -110,13 +110,23 @@ add_task(function* () {
yield checkWindow("background", winId2, "win2");
function* triggerPopup(win, callback) {
yield clickBrowserAction(extension, win);
let widgetId = makeWidgetId(extension.id) + "-browser-action";
let node = CustomizableUI.getWidget(widgetId).forWindow(win).node;
let evt = new CustomEvent("command", {
bubbles: true,
cancelable: true,
});
node.dispatchEvent(evt);
yield extension.awaitMessage("popup-ready");
yield callback();
closeBrowserAction(extension, win);
let panel = node.querySelector("panel");
if (panel) {
panel.hidePopup();
}
}
// Set focus to some other window.

View File

@ -116,21 +116,25 @@ add_task(function* () {
yield checkViews("background", 2, 0);
function* triggerPopup(win, callback) {
yield clickBrowserAction(extension, win);
let widgetId = makeWidgetId(extension.id) + "-browser-action";
let node = CustomizableUI.getWidget(widgetId).forWindow(win).node;
let evt = new CustomEvent("command", {
bubbles: true,
cancelable: true,
});
node.dispatchEvent(evt);
yield extension.awaitMessage("popup-ready");
yield callback();
closeBrowserAction(extension, win);
let panel = node.querySelector("panel");
if (panel) {
panel.hidePopup();
}
}
// The popup occasionally closes prematurely if we open it immediately here.
// I'm not sure what causes it to close (it's something internal, and seems to
// be focus-related, but it's not caused by JS calling hidePopup), but even a
// short timeout seems to consistently fix it.
yield new Promise(resolve => win1.setTimeout(resolve, 10));
yield triggerPopup(win1, function*() {
yield checkViews("background", 2, 1);
yield checkViews("popup", 2, 1);

View File

@ -2,165 +2,18 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
function* runTests(options) {
function background(getTests) {
let tabs;
let tests;
// Gets the current details of the page action, and returns a
// promise that resolves to an object containing them.
function getDetails() {
return new Promise(resolve => {
return browser.tabs.query({ active: true, currentWindow: true }, resolve);
}).then(tabs => {
let tabId = tabs[0].id;
return Promise.all([
new Promise(resolve => browser.pageAction.getTitle({tabId}, resolve)),
new Promise(resolve => browser.pageAction.getPopup({tabId}, resolve))]);
}).then(details => {
return Promise.resolve({ title: details[0],
popup: details[1] });
});
}
// Runs the next test in the `tests` array, checks the results,
// and passes control back to the outer test scope.
function nextTest() {
let test = tests.shift();
test(expecting => {
function finish() {
// Check that the actual icon has the expected values, then
// run the next test.
browser.test.sendMessage("nextTest", expecting, tests.length);
}
if (expecting) {
// Check that the API returns the expected values, and then
// run the next test.
getDetails().then(details => {
browser.test.assertEq(expecting.title, details.title,
"expected value from getTitle");
browser.test.assertEq(expecting.popup, details.popup,
"expected value from getPopup");
finish();
});
} else {
finish();
}
});
}
function runTests() {
tabs = [];
tests = getTests(tabs);
browser.tabs.query({ active: true, currentWindow: true }, resultTabs => {
tabs[0] = resultTabs[0].id;
nextTest();
});
}
browser.test.onMessage.addListener((msg) => {
if (msg == "runTests") {
runTests();
} else if (msg == "runNextTest") {
nextTest();
} else {
browser.test.fail(`Unexpected message: ${msg}`);
}
});
runTests();
}
let extension = ExtensionTestUtils.loadExtension({
manifest: options.manifest,
background: `(${background})(${options.getTests})`,
});
let pageActionId = makeWidgetId(extension.id) + "-page-action";
let currentWindow = window;
let windows = [];
function checkDetails(details) {
let image = currentWindow.document.getElementById(pageActionId);
if (details == null) {
ok(image == null || image.hidden, "image is hidden");
} else {
ok(image, "image exists");
is(image.src, details.icon, "icon URL is correct");
let title = details.title || options.manifest.name;
is(image.getAttribute("tooltiptext"), title, "image title is correct");
is(image.getAttribute("aria-label"), title, "image aria-label is correct");
// TODO: Popup URL.
}
}
let testNewWindows = 1;
let awaitFinish = new Promise(resolve => {
extension.onMessage("nextTest", (expecting, testsRemaining) => {
checkDetails(expecting);
if (testsRemaining) {
extension.sendMessage("runNextTest");
} else if (testNewWindows) {
testNewWindows--;
BrowserTestUtils.openNewBrowserWindow().then(window => {
windows.push(window);
currentWindow = window;
return focusWindow(window);
}).then(() => {
extension.sendMessage("runTests");
});
} else {
resolve();
}
});
});
yield extension.startup();
yield awaitFinish;
yield extension.unload();
let node = document.getElementById(pageActionId);
is(node, null, "pageAction image removed from document");
currentWindow = null;
for (let win of windows.splice(0)) {
node = win.document.getElementById(pageActionId);
is(node, null, "pageAction image removed from second document");
yield BrowserTestUtils.closeWindow(win);
}
}
add_task(function* testTabSwitchContext() {
yield runTests({
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"name": "Foo Extension",
"page_action": {
"default_icon": "default.png",
"default_popup": "default.html",
"default_title": "Default Title \u263a",
},
"permissions": ["tabs"],
},
getTests(tabs) {
background: function() {
let details = [
{ "icon": browser.runtime.getURL("default.png"),
"popup": browser.runtime.getURL("default.html"),
@ -171,12 +24,11 @@ add_task(function* testTabSwitchContext() {
{ "icon": browser.runtime.getURL("2.png"),
"popup": browser.runtime.getURL("2.html"),
"title": "Title 2" },
{ "icon": browser.runtime.getURL("2.png"),
"popup": browser.runtime.getURL("2.html"),
"title": "Default Title \u263a" },
];
return [
let tabs;
let tests;
let allTests = [
expect => {
browser.test.log("Initial state. No icon visible.");
expect(null);
@ -208,12 +60,6 @@ add_task(function* testTabSwitchContext() {
expect(details[2]);
},
expect => {
browser.test.log("Clear the title. Expect default title.");
browser.pageAction.setTitle({ tabId: tabs[1], title: "" });
expect(details[3]);
},
expect => {
browser.test.log("Navigate to a new page. Expect icon hidden.");
@ -258,53 +104,135 @@ add_task(function* testTabSwitchContext() {
expect(null);
},
];
// Gets the current details of the page action, and returns a
// promise that resolves to an object containing them.
function getDetails() {
return new Promise(resolve => {
return browser.tabs.query({ active: true, currentWindow: true }, resolve);
}).then(tabs => {
let tabId = tabs[0].id;
return Promise.all([
new Promise(resolve => browser.pageAction.getTitle({tabId}, resolve)),
new Promise(resolve => browser.pageAction.getPopup({tabId}, resolve))]);
}).then(details => {
return Promise.resolve({ title: details[0],
popup: details[1] });
});
}
// Runs the next test in the `tests` array, checks the results,
// and passes control back to the outer test scope.
function nextTest() {
let test = tests.shift();
test(expecting => {
function finish() {
// Check that the actual icon has the expected values, then
// run the next test.
browser.test.sendMessage("nextTest", expecting, tests.length);
}
if (expecting) {
// Check that the API returns the expected values, and then
// run the next test.
getDetails().then(details => {
browser.test.assertEq(expecting.title, details.title,
"expected value from getTitle");
browser.test.assertEq(expecting.popup, details.popup,
"expected value from getPopup");
finish();
});
} else {
finish();
}
});
}
function runTests() {
tabs = [];
tests = allTests.slice();
browser.tabs.query({ active: true, currentWindow: true }, resultTabs => {
tabs[0] = resultTabs[0].id;
nextTest();
});
}
browser.test.onMessage.addListener((msg) => {
if (msg == "runTests") {
runTests();
} else if (msg == "runNextTest") {
nextTest();
} else {
browser.test.fail(`Unexpected message: ${msg}`);
}
});
runTests();
},
});
});
add_task(function* testDefaultTitle() {
yield runTests({
manifest: {
"name": "Foo Extension",
let pageActionId = makeWidgetId(extension.id) + "-page-action";
let currentWindow = window;
let windows = [];
"page_action": {
"default_icon": "icon.png",
},
function checkDetails(details) {
let image = currentWindow.document.getElementById(pageActionId);
if (details == null) {
ok(image == null || image.hidden, "image is hidden");
} else {
ok(image, "image exists");
"permissions": ["tabs"],
},
is(image.src, details.icon, "icon URL is correct");
is(image.getAttribute("tooltiptext"), details.title, "image title is correct");
is(image.getAttribute("aria-label"), details.title, "image aria-label is correct");
// TODO: Popup URL.
}
}
getTests(tabs) {
let details = [
{ "title": "Foo Extension",
"popup": "",
"icon": browser.runtime.getURL("icon.png") },
{ "title": "Foo Title",
"popup": "",
"icon": browser.runtime.getURL("icon.png") },
];
let testNewWindows = 1;
return [
expect => {
browser.test.log("Initial state. No icon visible.");
expect(null);
},
expect => {
browser.test.log("Show the icon on the first tab, expect extension title as default title.");
browser.pageAction.show(tabs[0]);
expect(details[0]);
},
expect => {
browser.test.log("Change the title. Expect new title.");
browser.pageAction.setTitle({ tabId: tabs[0], title: "Foo Title" });
expect(details[1]);
},
expect => {
browser.test.log("Clear the title. Expect extension title.");
browser.pageAction.setTitle({ tabId: tabs[0], title: "" });
expect(details[0]);
},
];
},
let awaitFinish = new Promise(resolve => {
extension.onMessage("nextTest", (expecting, testsRemaining) => {
checkDetails(expecting);
if (testsRemaining) {
extension.sendMessage("runNextTest");
} else if (testNewWindows) {
testNewWindows--;
BrowserTestUtils.openNewBrowserWindow().then(window => {
windows.push(window);
currentWindow = window;
return focusWindow(window);
}).then(() => {
extension.sendMessage("runTests");
});
} else {
resolve();
}
});
});
yield extension.startup();
yield awaitFinish;
yield extension.unload();
let node = document.getElementById(pageActionId);
is(node, null, "pageAction image removed from document");
currentWindow = null;
for (let win of windows.splice(0)) {
node = win.document.getElementById(pageActionId);
is(node, null, "pageAction image removed from second document");
yield BrowserTestUtils.closeWindow(win);
}
});

View File

@ -2,9 +2,21 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* testPageActionPopup() {
let scriptPage = url => `<html><head><meta charset="utf-8"><script src="${url}"></script></head></html>`;
function promisePopupShown(popup) {
return new Promise(resolve => {
if (popup.popupOpen) {
resolve();
} else {
let onPopupShown = event => {
popup.removeEventListener("popupshown", onPopupShown);
resolve();
};
popup.addEventListener("popupshown", onPopupShown);
}
});
}
add_task(function* testPageActionPopup() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"background": {
@ -16,17 +28,17 @@ add_task(function* testPageActionPopup() {
},
files: {
"popup-a.html": scriptPage("popup-a.js"),
"popup-a.html": `<script src="popup-a.js"></script>`,
"popup-a.js": function() {
browser.runtime.sendMessage("from-popup-a");
},
"data/popup-b.html": scriptPage("popup-b.js"),
"data/popup-b.html": `<script src="popup-b.js"></script>`,
"data/popup-b.js": function() {
browser.runtime.sendMessage("from-popup-b");
},
"data/background.html": scriptPage("background.js"),
"data/background.html": `<script src="background.js"></script>`,
"data/background.js": function() {
let tabId;

View File

@ -42,6 +42,19 @@ add_task(function* testPageActionPopup() {
},
});
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
let pageActionId = makeWidgetId(extension.id) + "-page-action";
function openPopup(buttonId) {
let button = document.getElementById(buttonId);
if (buttonId == pageActionId) {
// TODO: I don't know why a proper synthesized event doesn't work here.
button.dispatchEvent(new MouseEvent("click", {}));
} else {
EventUtils.synthesizeMouseAtCenter(button, {}, window);
}
}
let promiseConsoleMessage = pattern => new Promise(resolve => {
Services.console.registerListener(function listener(msg) {
if (pattern.test(msg.message)) {
@ -59,25 +72,21 @@ add_task(function* testPageActionPopup() {
// BrowserAction:
let awaitMessage = promiseConsoleMessage(/WebExt Privilege Escalation: BrowserAction/);
SimpleTest.expectUncaughtException();
yield clickBrowserAction(extension);
openPopup(browserActionId);
let message = yield awaitMessage;
ok(message.includes("WebExt Privilege Escalation: BrowserAction: typeof(browser) = undefined"),
`No BrowserAction API injection`);
yield closeBrowserAction(extension);
// PageAction
awaitMessage = promiseConsoleMessage(/WebExt Privilege Escalation: PageAction/);
SimpleTest.expectUncaughtException();
yield clickPageAction(extension);
openPopup(pageActionId);
message = yield awaitMessage;
ok(message.includes("WebExt Privilege Escalation: PageAction: typeof(browser) = undefined"),
`No PageAction API injection: ${message}`);
yield closePageAction(extension);
SimpleTest.expectUncaughtException(false);
@ -86,13 +95,12 @@ add_task(function* testPageActionPopup() {
yield extension.awaitMessage("ok");
yield clickBrowserAction(extension);
// Check that unprivileged documents don't get the API.
openPopup(browserActionId);
yield extension.awaitMessage("from-popup-a");
yield closeBrowserAction(extension);
yield clickPageAction(extension);
openPopup(pageActionId);
yield extension.awaitMessage("from-popup-b");
yield closePageAction(extension);
yield extension.unload();
});

View File

@ -48,11 +48,6 @@ function* testHasPermission(params) {
extension.sendMessage("execute-script");
yield extension.awaitFinish("executeScript");
if (params.tearDown) {
yield params.tearDown(extension);
}
yield extension.unload();
}
@ -87,7 +82,6 @@ add_task(function* testGoodPermissions() {
return Promise.resolve();
},
setup: clickBrowserAction,
tearDown: closeBrowserAction,
});
info("Test activeTab permission with a page action click");
@ -105,7 +99,6 @@ add_task(function* testGoodPermissions() {
});
},
setup: clickPageAction,
tearDown: closePageAction,
});
info("Test activeTab permission with a browser action w/popup click");
@ -115,7 +108,6 @@ add_task(function* testGoodPermissions() {
"browser_action": { "default_popup": "_blank.html" },
},
setup: clickBrowserAction,
tearDown: closeBrowserAction,
});
info("Test activeTab permission with a page action w/popup click");
@ -133,7 +125,6 @@ add_task(function* testGoodPermissions() {
});
},
setup: clickPageAction,
tearDown: closePageAction,
});
info("Test activeTab permission with a context menu click");

View File

@ -63,7 +63,6 @@ add_task(function* () {
clickBrowserAction(extension);
yield extension.awaitMessage("popup-finished");
yield closeBrowserAction(extension);
yield extension.unload();
});

View File

@ -2,13 +2,7 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
/* exported CustomizableUI makeWidgetId focusWindow forceGC
* getBrowserActionWidget
* clickBrowserAction clickPageAction
* getBrowserActionPopup getPageActionPopup
* closeBrowserAction closePageAction
* promisePopupShown
*/
/* exported AppConstants CustomizableUI forceGC makeWidgetId focusWindow clickBrowserAction clickPageAction */
var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm");
var {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm");
@ -45,58 +39,12 @@ var focusWindow = Task.async(function* focusWindow(win) {
yield promise;
});
function promisePopupShown(popup) {
return new Promise(resolve => {
if (popup.state == "open") {
resolve();
} else {
let onPopupShown = event => {
popup.removeEventListener("popupshown", onPopupShown);
resolve();
};
popup.addEventListener("popupshown", onPopupShown);
}
});
}
function clickBrowserAction(extension, win = window) {
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
let elem = win.document.getElementById(browserActionId);
function getBrowserActionWidget(extension) {
return CustomizableUI.getWidget(makeWidgetId(extension.id) + "-browser-action");
}
function getBrowserActionPopup(extension, win = window) {
let group = getBrowserActionWidget(extension);
if (group.areaType == CustomizableUI.TYPE_TOOLBAR) {
return win.document.getElementById("customizationui-widget-panel");
}
return null;
}
var clickBrowserAction = Task.async(function* (extension, win = window) {
let group = getBrowserActionWidget(extension);
let widget = group.forWindow(win);
if (group.areaType == CustomizableUI.TYPE_TOOLBAR) {
ok(!widget.overflowed, "Expect widget not to be overflowed");
} else if (group.areaType == CustomizableUI.TYPE_MENU_PANEL) {
yield win.PanelUI.show();
}
EventUtils.synthesizeMouseAtCenter(widget.node, {}, win);
});
function closeBrowserAction(extension, win = window) {
let group = getBrowserActionWidget(extension);
let node = win.document.getElementById(group.viewId);
CustomizableUI.hidePanelForNode(node);
return Promise.resolve();
}
function getPageActionPopup(extension, win = window) {
let panelId = makeWidgetId(extension.id) + "-panel";
return win.document.getElementById(panelId);
EventUtils.synthesizeMouseAtCenter(elem, {}, win);
return new Promise(SimpleTest.executeSoon);
}
function clickPageAction(extension, win = window) {
@ -115,13 +63,3 @@ function clickPageAction(extension, win = window) {
EventUtils.synthesizeMouseAtCenter(elem, {}, win);
return new Promise(SimpleTest.executeSoon);
}
function closePageAction(extension, win = window) {
let node = getPageActionPopup(extension, win);
if (node) {
node.hidePopup();
}
return Promise.resolve();
}

View File

@ -250,11 +250,6 @@ panelmultiview[nosubviews=true] > .panel-viewcontainer > .panel-viewstack > .pan
max-width: @standaloneSubviewWidth@;
}
/* Give WebExtension stand-alone panels extra width for Chrome compatibility */
.cui-widget-panel[viewId^=PanelUI-webext-] .panel-mainview {
max-width: 800px;
}
panelview:not([mainview]) .toolbarbutton-text,
.cui-widget-panel toolbarbutton > .toolbarbutton-text {
text-align: start;

View File

@ -209,9 +209,6 @@ const DominatorTree = module.exports = createClass({
getKey: node =>
node instanceof DominatorTreeLazyChildren ? node.key() : node.nodeId,
itemHeight: TREE_ROW_HEIGHT,
// We can't cache traversals because incremental fetching of children
// means the traversal might not be valid.
reuseCachedTraversal: _ => false,
});
}
});

View File

@ -166,10 +166,6 @@ const Tree = module.exports = createClass({
onFocus: PropTypes.func,
// The depth to which we should automatically expand new items.
autoExpandDepth: PropTypes.number,
// A predicate that returns true if the last DFS traversal that was cached
// can be reused, false otherwise. The predicate function is passed the
// cached traversal as an array of nodes.
reuseCachedTraversal: PropTypes.func,
// Optional event handlers for when items are expanded or collapsed.
onExpand: PropTypes.func,
onCollapse: PropTypes.func,
@ -178,7 +174,6 @@ const Tree = module.exports = createClass({
getDefaultProps() {
return {
autoExpandDepth: AUTO_EXPAND_DEPTH,
reuseCachedTraversal: null,
};
},
@ -187,7 +182,6 @@ const Tree = module.exports = createClass({
scroll: 0,
height: window.innerHeight,
seen: new Set(),
cachedTraversal: undefined,
};
},
@ -329,23 +323,12 @@ const Tree = module.exports = createClass({
* Perform a pre-order depth-first search over the whole forest.
*/
_dfsFromRoots(maxDepth = Infinity) {
const cached = this.state.cachedTraversal;
if (cached
&& maxDepth === Infinity
&& this.props.reuseCachedTraversal
&& this.props.reuseCachedTraversal(cached)) {
return cached;
}
const traversal = [];
for (let root of this.props.getRoots()) {
this._dfs(root, maxDepth, traversal);
}
if (this.props.reuseCachedTraversal) {
this.state.cachedTraversal = traversal;
}
return traversal;
},
@ -365,10 +348,6 @@ const Tree = module.exports = createClass({
}
}
}
this.setState({
cachedTraversal: null,
});
}),
/**
@ -380,10 +359,6 @@ const Tree = module.exports = createClass({
if (this.props.onCollapse) {
this.props.onCollapse(item);
}
this.setState({
cachedTraversal: null,
});
}),
/**

View File

@ -221,6 +221,12 @@ html, body, #app, #memory-tool {
*/
flex: 1;
background-color: var(--theme-toolbar-background);
/**
* By default, flex items have min-width: auto;
* (https://drafts.csswg.org/css-flexbox/#min-size-auto)
*/
min-width: 0;
}
#heap-view > .heap-view-panel {
@ -236,6 +242,12 @@ html, body, #app, #memory-tool {
* Flexing to fill out remaining horizontal space. @see #heap-view.
*/
flex: 1;
/**
* By default, flex items have min-width: auto;
* (https://drafts.csswg.org/css-flexbox/#min-size-auto)
*/
min-width: 0;
}
#heap-view > .heap-view-panel > .snapshot-status,

View File

@ -83,6 +83,32 @@ namespace {
// Animation interface:
//
// ---------------------------------------------------------------------------
/* static */ already_AddRefed<Animation>
Animation::Constructor(const GlobalObject& aGlobal,
KeyframeEffectReadOnly* aEffect,
AnimationTimeline* aTimeline,
ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
RefPtr<Animation> animation = new Animation(global);
if (!aTimeline) {
// Bug 1096776: We do not support null timeline yet.
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
if (!aEffect) {
// Bug 1049975: We do not support null effect yet.
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
animation->SetTimeline(aTimeline);
animation->SetEffect(aEffect);
return animation.forget();
}
void
Animation::SetId(const nsAString& aId)
{

View File

@ -90,6 +90,11 @@ public:
};
// Animation interface methods
static already_AddRefed<Animation>
Constructor(const GlobalObject& aGlobal,
KeyframeEffectReadOnly* aEffect,
AnimationTimeline* aTimeline,
ErrorResult& aRv);
void GetId(nsAString& aResult) const { aResult = mId; }
void SetId(const nsAString& aId);
KeyframeEffectReadOnly* GetEffect() const { return mEffect; }

View File

@ -70,12 +70,13 @@ EffectSet::GetEffectSet(const nsIFrame* aFrame)
return nullptr;
}
} else {
if (!content->MayHaveAnimations()) {
return nullptr;
}
propName = nsGkAtoms::animationEffectsProperty;
}
if (!content->MayHaveAnimations()) {
return nullptr;
}
return static_cast<EffectSet*>(content->GetProperty(propName));
}
@ -101,9 +102,7 @@ EffectSet::GetOrCreateEffectSet(dom::Element* aElement,
return nullptr;
}
if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
aElement->SetMayHaveAnimations();
}
aElement->SetMayHaveAnimations();
return effectSet;
}

View File

@ -63,6 +63,7 @@ PendingAnimationTracker::TriggerPendingAnimationsOnNextTick(const TimeStamp&
// itself on the next tick where it has a timeline.
if (!timeline) {
iter.Remove();
continue;
}
// When the timeline's refresh driver is under test control, its values

View File

@ -1352,10 +1352,11 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement)
for (uint32_t i = 0; props[i]; ++i) {
tmp->DeleteProperty(*props[i]);
}
// Bug 1226091: Call MayHaveAnimations() first
nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
for (uint32_t i = 0; effectProps[i]; ++i) {
tmp->DeleteProperty(effectProps[i]);
if (tmp->MayHaveAnimations()) {
nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
for (uint32_t i = 0; effectProps[i]; ++i) {
tmp->DeleteProperty(effectProps[i]);
}
}
}
}
@ -1899,6 +1900,14 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(FragmentOrElement)
tmp->OwnerDoc()->BindingManager()->Traverse(tmp, cb);
// Check that whenever we have effect properties, MayHaveAnimations is set.
#ifdef DEBUG
nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
for (uint32_t i = 0; effectProps[i]; ++i) {
MOZ_ASSERT_IF(tmp->GetProperty(effectProps[i]), tmp->MayHaveAnimations());
}
#endif
if (tmp->HasProperties()) {
if (tmp->IsHTMLElement() || tmp->IsSVGElement()) {
nsIAtom*** props = Element::HTMLSVGPropertiesToTraverseAndUnlink();
@ -1907,13 +1916,14 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(FragmentOrElement)
static_cast<nsISupports*>(tmp->GetProperty(*props[i]));
cb.NoteXPCOMChild(property);
}
// Bug 1226091: Check MayHaveAnimations() first
nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
for (uint32_t i = 0; effectProps[i]; ++i) {
EffectSet* effectSet =
static_cast<EffectSet*>(tmp->GetProperty(effectProps[i]));
if (effectSet) {
effectSet->Traverse(cb);
if (tmp->MayHaveAnimations()) {
nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
for (uint32_t i = 0; effectProps[i]; ++i) {
EffectSet* effectSet =
static_cast<EffectSet*>(tmp->GetProperty(effectProps[i]));
if (effectSet) {
effectSet->Traverse(cb);
}
}
}
}

View File

@ -28,6 +28,7 @@ CaptureStreamTestHelper.prototype = {
blackTransparent: { data: [0, 0, 0, 0], name: "blackTransparent" },
green: { data: [0, 255, 0, 255], name: "green" },
red: { data: [255, 0, 0, 255], name: "red" },
blue: { data: [0, 0, 255, 255], name: "blue"},
grey: { data: [128, 128, 128, 255], name: "grey" },
/* Default element size for createAndAppendElement() */

View File

@ -231,11 +231,11 @@ Request::Constructor(const GlobalObject& aGlobal,
RefPtr<Request> inputReq = &aInput.GetAsRequest();
nsCOMPtr<nsIInputStream> body;
inputReq->GetBody(getter_AddRefs(body));
if (inputReq->BodyUsed()) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
return nullptr;
}
if (body) {
if (inputReq->BodyUsed()) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
return nullptr;
}
temporaryBody = body;
}

View File

@ -1925,8 +1925,7 @@ MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
nsPIDOMWindow *outer = aWindow->GetOuterWindow();
vc.mBrowserWindow.Construct(outer->WindowID());
}
// | Fall through
// V
MOZ_FALLTHROUGH;
case dom::MediaSourceEnum::Screen:
case dom::MediaSourceEnum::Application:
case dom::MediaSourceEnum::Window:

View File

@ -252,7 +252,7 @@ already_AddRefed<SharedThreadPool> GetMediaThreadPool(MediaThreadType aType)
name = "MediaPDecoder";
break;
default:
MOZ_ASSERT(false);
MOZ_FALLTHROUGH_ASSERT("Unexpected MediaThreadType");
case MediaThreadType::PLAYBACK:
name = "MediaPlayback";
break;

View File

@ -90,10 +90,10 @@ MediaCodecProxy::MediaCodecProxy(sp<ALooper> aLooper,
: mCodecLooper(aLooper)
, mCodecMime(aMime)
, mCodecEncoder(aEncoder)
, mMediaCodecLock("MediaCodecProxy::mMediaCodecLock")
, mPendingRequestMediaResource(false)
, mPromiseMonitor("MediaCodecProxy::mPromiseMonitor")
{
MOZ_ASSERT(mCodecLooper != nullptr, "ALooper should not be nullptr.");
mCodecPromise.SetMonitor(&mPromiseMonitor);
}
MediaCodecProxy::~MediaCodecProxy()
@ -134,6 +134,7 @@ MediaCodecProxy::AsyncAllocateVideoMediaCodec()
mResourceClient->SetListener(this);
mResourceClient->Acquire();
mozilla::MonitorAutoLock lock(mPromiseMonitor);
RefPtr<CodecPromise> p = mCodecPromise.Ensure(__func__);
return p.forget();
}
@ -141,23 +142,16 @@ MediaCodecProxy::AsyncAllocateVideoMediaCodec()
void
MediaCodecProxy::ReleaseMediaCodec()
{
mCodecPromise.RejectIfExists(true, __func__);
releaseCodec();
if (!mResourceClient) {
return;
}
mozilla::MonitorAutoLock mon(mMediaCodecLock);
if (mPendingRequestMediaResource) {
mPendingRequestMediaResource = false;
mon.NotifyAll();
}
// At first, release mResourceClient's resource to prevent a conflict with
// mResourceClient's callback.
if (mResourceClient) {
mResourceClient->ReleaseResource();
mResourceClient = nullptr;
}
mozilla::MonitorAutoLock lock(mPromiseMonitor);
mCodecPromise.RejectIfExists(true, __func__);
releaseCodec();
}
bool
@ -473,18 +467,12 @@ void
MediaCodecProxy::ResourceReserved()
{
MCP_LOG("resourceReserved");
mozilla::MonitorAutoLock lock(mPromiseMonitor);
// Create MediaCodec
if (!allocateCodec()) {
ReleaseMediaCodec();
mCodecPromise.RejectIfExists(true, __func__);
return;
}
// Notify initialization waiting.
mozilla::MonitorAutoLock mon(mMediaCodecLock);
mPendingRequestMediaResource = false;
mon.NotifyAll();
mCodecPromise.ResolveIfExists(true, __func__);
}
@ -492,7 +480,7 @@ MediaCodecProxy::ResourceReserved()
void
MediaCodecProxy::ResourceReserveFailed()
{
ReleaseMediaCodec();
mozilla::MonitorAutoLock lock(mPromiseMonitor);
mCodecPromise.RejectIfExists(true, __func__);
}

View File

@ -163,6 +163,8 @@ private:
bool mCodecEncoder;
mozilla::MozPromiseHolder<CodecPromise> mCodecPromise;
// When mPromiseMonitor is held, mResourceClient's functions should not be called.
mozilla::Monitor mPromiseMonitor;
// Media Resource Management
RefPtr<mozilla::MediaSystemResourceClient> mResourceClient;
@ -174,9 +176,6 @@ private:
//MediaCodec buffers to hold input/output data.
Vector<sp<ABuffer> > mInputBuffers;
Vector<sp<ABuffer> > mOutputBuffers;
mozilla::Monitor mMediaCodecLock;
bool mPendingRequestMediaResource;
};
} // namespace android

View File

@ -64,6 +64,13 @@ OMXCodecProxy::OMXCodecProxy(
OMXCodecProxy::~OMXCodecProxy()
{
// At first, release mResourceClient's resource to prevent a conflict with
// mResourceClient's callback.
if (mResourceClient) {
mResourceClient->ReleaseResource();
mResourceClient = nullptr;
}
mState = ResourceState::END;
mCodecPromise.RejectIfExists(true, __func__);
@ -78,11 +85,6 @@ OMXCodecProxy::~OMXCodecProxy()
// Complete all pending Binder ipc transactions
IPCThreadState::self()->flushCommands();
if (mResourceClient) {
mResourceClient->ReleaseResource();
mResourceClient = nullptr;
}
mSource.clear();
free(mComponentName);
mComponentName = nullptr;

View File

@ -112,6 +112,8 @@ tags=capturestream
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || android_version == '18' # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
[test_peerConnection_captureStream_canvas_2d.html]
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
[test_peerConnection_multiple_captureStream_canvas_2d.html]
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || (android_version == '18' && debug) # b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
[test_peerConnection_captureStream_canvas_webgl.html]
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
# [test_peerConnection_certificates.html] # bug 1180968
@ -206,10 +208,13 @@ skip-if = toolkit == 'gonk' || android_version == '18'
[test_peerConnection_addDataChannel.html]
skip-if = toolkit == 'gonk' # B2G emulator seems to be so slow that DTLS cannot establish properly
[test_peerConnection_addDataChannelNoBundle.html]
# b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # b2g(emulator seems to be so slow that DTLS cannot establish properly), android(bug 1240256, intermittent ICE failures starting w/bug 1232082, possibly from timeout)
# b2g(Bug 960442, video support for WebRTC is disabled on b2g), android(Bug 1189784, timeouts on 4.3 emulator)
[test_peerConnection_verifyAudioAfterRenegotiation.html]
skip-if = toolkit == 'gonk' || (android_version == '18' && debug) # B2G emulator is too slow to finish a renegotiation test in under 5 minutes, android(Bug 1189784, timeouts on 4.3 emulator)
[test_peerConnection_verifyVideoAfterRenegotiation.html]
# B2G emulator is too slow to finish a renegotiation test in under 5 minutes, Bug 1180000 for Linux debug e10s, android(Bug 1189784, timeouts on 4.3 emulator)
skip-if = toolkit == 'gonk' || android_version == '18'
[test_peerConnection_webAudio.html]
tags = webaudio webrtc
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)

View File

@ -0,0 +1,100 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1166832",
title: "Canvas(2D)::Multiple CaptureStream as video-only input to peerconnection",
visible: true
});
/**
* Test to verify using multiple capture streams concurrently.
*/
runNetworkTest(() => {
var test = new PeerConnectionTest();
var h = new CaptureStreamTestHelper2D(50, 50);
var vremote1;
var stream1;
var canvas1 = h.createAndAppendElement('canvas', 'source_canvas1');
var vremote2;
var stream2;
var canvas2 = h.createAndAppendElement('canvas', 'source_canvas2');
test.setMediaConstraints([{video: true}, {video: true}], []);
test.chain.replace("PC_LOCAL_GUM", [
function DRAW_INITIAL_LOCAL1_GREEN(test) {
h.drawColor(canvas1, h.green);
},
function DRAW_INITIAL_LOCAL2_BLUE(test) {
h.drawColor(canvas2, h.blue);
},
function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
stream1 = canvas1.captureStream(0); // fps = 0 to capture single frame
test.pcLocal.attachMedia(stream1, 'video', 'local');
stream2 = canvas2.captureStream(0); // fps = 0 to capture single frame
test.pcLocal.attachMedia(stream2, 'video', 'local');
}
]);
test.chain.append([
function CHECK_REMOTE_VIDEO() {
var testremote = document.getElementById('pcRemote_remote3_video');
ok(!testremote, "Should not have remote3 video element for pcRemote");
vremote1 = document.getElementById('pcRemote_remote1_video');
vremote2 = document.getElementById('pcRemote_remote2_video');
// since we don't know which remote video is created first, we don't know
// which should be blue or green, but this will make sure that one is
// green and one is blue
return Promise.race([
Promise.all([
h.waitForPixelColor(vremote1, h.green, 128,
"pcRemote's remote1 should become green"),
h.waitForPixelColor(vremote2, h.blue, 128,
"pcRemote's remote2 should become blue")
]),
Promise.all([
h.waitForPixelColor(vremote2, h.green, 128,
"pcRemote's remote2 should become green"),
h.waitForPixelColor(vremote1, h.blue, 128,
"pcRemote's remote1 should become blue")
])
]);
},
function DRAW_LOCAL1_RED() {
// After requesting a frame it will be captured at the time of next render.
// Next render will happen at next stable state, at the earliest,
// i.e., this order of `requestFrame(); draw();` should work.
stream1.requestFrame();
h.drawColor(canvas1, h.red);
},
function DRAW_LOCAL2_RED() {
// After requesting a frame it will be captured at the time of next render.
// Next render will happen at next stable state, at the earliest,
// i.e., this order of `requestFrame(); draw();` should work.
stream2.requestFrame();
h.drawColor(canvas2, h.red);
},
function WAIT_FOR_REMOTE1_RED() {
return h.waitForPixelColor(vremote1, h.red, 128,
"pcRemote's remote1 should become red");
},
function WAIT_FOR_REMOTE2_RED() {
return h.waitForPixelColor(vremote2, h.red, 128,
"pcRemote's remote2 should become red");
}
]);
test.run();
});
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,123 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1166832",
title: "Renegotiation: verify audio after renegotiation"
});
var test;
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
var checkAudio = (analyser, fun) => {
analyser.enableDebugCanvas();
return analyser.waitForAnalysisSuccess(fun)
.then(() => analyser.disableDebugCanvas());
};
var checkAudioEnabled = (analyser, freq) =>
checkAudio(analyser, array => array[freq] > 200);
var checkAudioDisabled = (analyser, freq) =>
checkAudio(analyser, array => array[freq] < 50);
var ac = new AudioContext();
var local1Analyser;
var remote1Analyser;
test.chain.append([
function CHECK_ASSUMPTIONS() {
is(test.pcLocal.mediaElements.length, 1,
"pcLocal should only have one media element");
is(test.pcRemote.mediaElements.length, 1,
"pcRemote should only have one media element");
is(test.pcLocal.streams.length, 1,
"pcLocal should only have one stream (the local one)");
is(test.pcRemote.streams.length, 1,
"pcRemote should only have one stream (the remote one)");
},
function CHECK_AUDIO() {
local1Analyser = new AudioStreamAnalyser(ac, test.pcLocal.streams[0]);
remote1Analyser = new AudioStreamAnalyser(ac, test.pcRemote.streams[0]);
freq1k = local1Analyser.binIndexForFrequency(1000);
return Promise.resolve()
.then(() => info("Checking local audio enabled"))
.then(() => checkAudioEnabled(local1Analyser, freq1k))
.then(() => info("Checking remote audio enabled"))
.then(() => checkAudioEnabled(remote1Analyser, freq1k))
.then(() => test.pcLocal.streams[0].getAudioTracks()[0].enabled = false)
.then(() => info("Checking local audio disabled"))
.then(() => checkAudioDisabled(local1Analyser, freq1k))
.then(() => info("Checking remote audio disabled"))
.then(() => checkAudioDisabled(remote1Analyser, freq1k))
}
]);
addRenegotiation(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
test.setMediaConstraints([{audio: true}],
[]);
return test.pcLocal.getAllUserMedia([{audio: true}]);
},
]
);
test.chain.append([
function CHECK_ASSUMPTIONS2() {
is(test.pcLocal.mediaElements.length, 2,
"pcLocal should have two media elements");
is(test.pcRemote.mediaElements.length, 2,
"pcRemote should have two media elements");
is(test.pcLocal.streams.length, 2,
"pcLocal should have two streams");
is(test.pcRemote.streams.length, 2,
"pcRemote should have two streams");
},
function RE_CHECK_AUDIO() {
local2Analyser = new AudioStreamAnalyser(ac, test.pcLocal.streams[1]);
remote2Analyser = new AudioStreamAnalyser(ac, test.pcRemote.streams[1]);
freq1k = local2Analyser.binIndexForFrequency(1000);
return Promise.resolve()
.then(() => info("Checking local audio disabled"))
.then(() => checkAudioDisabled(local1Analyser, freq1k))
.then(() => info("Checking remote audio disabled"))
.then(() => checkAudioDisabled(remote1Analyser, freq1k))
.then(() => info("Checking local2 audio enabled"))
.then(() => checkAudioEnabled(local2Analyser, freq1k))
.then(() => info("Checking remote2 audio enabled"))
.then(() => checkAudioEnabled(remote2Analyser, freq1k))
.then(() => test.pcLocal.streams[1].getAudioTracks()[0].enabled = false)
.then(() => test.pcLocal.streams[0].getAudioTracks()[0].enabled = true)
.then(() => info("Checking local2 audio disabled"))
.then(() => checkAudioDisabled(local2Analyser, freq1k))
.then(() => info("Checking remote2 audio disabled"))
.then(() => checkAudioDisabled(remote2Analyser, freq1k))
.then(() => info("Checking local audio enabled"))
.then(() => checkAudioEnabled(local1Analyser, freq1k))
.then(() => info("Checking remote audio enabled"))
.then(() => checkAudioEnabled(remote1Analyser, freq1k))
}
]);
test.setMediaConstraints([{audio: true}], []);
test.run();
});
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,100 @@
<!DOCTYPE HTML>
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
createHTML({
bug: "1166832",
title: "Renegotiation: verify video after renegotiation"
});
runNetworkTest(() => {
var test = new PeerConnectionTest();
var h1 = new CaptureStreamTestHelper2D(50, 50);
var canvas1 = h1.createAndAppendElement('canvas', 'source_canvas1');
var stream1;
var vremote1;
var h2 = new CaptureStreamTestHelper2D(50, 50);
var canvas2;
var stream2;
var vremote2;
test.setMediaConstraints([{video: true}], []);
test.chain.replace("PC_LOCAL_GUM", [
function DRAW_INITIAL_LOCAL_GREEN(test) {
h1.drawColor(canvas1, h1.green);
},
function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
stream1 = canvas1.captureStream(0);
test.pcLocal.attachMedia(stream1, 'video', 'local');
}
]);
test.chain.append([
function FIND_REMOTE_VIDEO() {
vremote1 = document.getElementById('pcRemote_remote1_video');
ok(!!vremote1, "Should have remote video element for pcRemote");
},
function WAIT_FOR_REMOTE_GREEN() {
return h1.waitForPixelColor(vremote1, h1.green, 128,
"pcRemote's remote should become green");
},
function DRAW_LOCAL_RED() {
// After requesting a frame it will be captured at the time of next render.
// Next render will happen at next stable state, at the earliest,
// i.e., this order of `requestFrame(); draw();` should work.
stream1.requestFrame();
h1.drawColor(canvas1, h1.red);
},
function WAIT_FOR_REMOTE_RED() {
return h1.waitForPixelColor(vremote1, h1.red, 128,
"pcRemote's remote should become red");
}
]);
addRenegotiation(test.chain,
[
function PC_LOCAL_ADD_SECOND_STREAM(test) {
canvas2 = h2.createAndAppendElement('canvas', 'source_canvas2');
h2.drawColor(canvas2, h2.blue);
stream2 = canvas2.captureStream(0);
// can't use test.pcLocal.getAllUserMedia([{video: true}]);
// because it doesn't let us substitute the capture stream
return test.pcLocal.attachMedia(stream2, 'video', 'local');
}
]
);
test.chain.append([
function FIND_REMOTE2_VIDEO() {
vremote2 = document.getElementById('pcRemote_remote2_video');
ok(!!vremote2, "Should have remote2 video element for pcRemote");
},
function WAIT_FOR_REMOTE2_BLUE() {
return h2.waitForPixelColor(vremote2, h2.blue, 128,
"pcRemote's remote2 should become blue");
},
function DRAW_NEW_LOCAL_GREEN(test) {
stream1.requestFrame();
h1.drawColor(canvas1, h1.green);
},
function WAIT_FOR_REMOTE1_GREEN() {
return h1.waitForPixelColor(vremote1, h1.green, 128,
"pcRemote's remote1 should become green");
}
]);
test.run();
});
</script>
</pre>
</body>
</html>

View File

@ -569,7 +569,7 @@ public:
aPrevious->mCurve, aPrevious->mCurveLength,
aPrevious->mDuration, aTime);
case AudioTimelineEvent::SetTarget:
MOZ_ASSERT(false, "unreached");
MOZ_FALLTHROUGH_ASSERT("AudioTimelineEvent::SetTarget");
case AudioTimelineEvent::SetValue:
case AudioTimelineEvent::Cancel:
case AudioTimelineEvent::Stream:
@ -617,7 +617,7 @@ public:
aPrevious->mCurve, aPrevious->mCurveLength,
aPrevious->mDuration, aTime);
case AudioTimelineEvent::SetTarget:
MOZ_ASSERT(false, "unreached");
MOZ_FALLTHROUGH_ASSERT("AudioTimelineEvent::SetTarget");
case AudioTimelineEvent::SetValue:
case AudioTimelineEvent::Cancel:
case AudioTimelineEvent::Stream:

View File

@ -570,7 +570,7 @@ WebAudioDecodeJob::OnFailure(ErrorCode aErrorCode)
const char* errorMessage;
switch (aErrorCode) {
case NoError:
MOZ_ASSERT(false, "Who passed NoError to OnFailure?");
MOZ_FALLTHROUGH_ASSERT("Who passed NoError to OnFailure?");
// Fall through to get some sort of a sane error message if this actually
// happens at runtime.
case UnknownError:

View File

@ -133,7 +133,7 @@ void WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
case EBML_ID:
mLastInitStartOffset = mCurrentOffset + (p - aBuffer) -
(mElement.mID.mLength + mElement.mSize.mLength);
/* FALLTHROUGH */
MOZ_FALLTHROUGH;
default:
mSkipBytes = mElement.mSize.mValue;
mState = SKIP_DATA;

View File

@ -187,7 +187,7 @@ ParseClockValue(RangedPtr<const char16_t>& aIter,
!ParseColon(iter, aEnd)) {
return false;
}
// intentional fall through
MOZ_FALLTHROUGH;
case PARTIAL_CLOCK_VALUE:
if (!ParseSecondsOrMinutes(iter, aEnd, minutes) ||
!ParseColon(iter, aEnd) ||

View File

@ -352,7 +352,7 @@ ConvertPathSegmentData(SVGPathDataAndInfo::const_iterator& aStart,
aResult[5] = aStart[5];
aResult[6] = aStart[6];
AdjustSegmentForRelativeness(adjustmentType, aResult + 5, aState);
// fall through
MOZ_FALLTHROUGH;
case PATHSEG_CURVETO_QUADRATIC_ABS:
case PATHSEG_CURVETO_QUADRATIC_REL:
case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
@ -360,7 +360,7 @@ ConvertPathSegmentData(SVGPathDataAndInfo::const_iterator& aStart,
aResult[3] = aStart[3];
aResult[4] = aStart[4];
AdjustSegmentForRelativeness(adjustmentType, aResult + 3, aState);
// fall through
MOZ_FALLTHROUGH;
case PATHSEG_MOVETO_ABS:
case PATHSEG_MOVETO_REL:
case PATHSEG_LINETO_ABS:

View File

@ -143,7 +143,7 @@ SVGTransformListParser::ParseTranslate()
switch (count) {
case 1:
t[1] = 0.f;
// fall-through
MOZ_FALLTHROUGH;
case 2:
{
nsSVGTransform* transform = mTransforms.AppendElement(fallible);
@ -171,7 +171,7 @@ SVGTransformListParser::ParseScale()
switch (count) {
case 1:
s[1] = s[0];
// fall-through
MOZ_FALLTHROUGH;
case 2:
{
nsSVGTransform* transform = mTransforms.AppendElement(fallible);
@ -200,7 +200,7 @@ SVGTransformListParser::ParseRotate()
switch (count) {
case 1:
r[1] = r[2] = 0.f;
// fall-through
MOZ_FALLTHROUGH;
case 3:
{
nsSVGTransform* transform = mTransforms.AppendElement(fallible);

View File

@ -124,6 +124,19 @@ function testBug1109574() {
var r3 = new Request(r1);
}
// Bug 1184550 - Request constructor should always throw if used flag is set,
// even if body is null
function testBug1184550() {
var req = new Request("", { method: 'post', body: "Test" });
fetch(req);
ok(req.bodyUsed, "Request body should be used immediately after fetch()");
return fetch(req).then(function(resp) {
ok(false, "Second fetch with same request should fail.");
}).catch(function(err) {
is(err.name, 'TypeError', "Second fetch with same request should fail.");
});
}
function testHeaderGuard() {
var headers = {
"Cookie": "Custom cookie",
@ -500,6 +513,7 @@ function runTest() {
testUrlMalformed();
testMethod();
testBug1109574();
testBug1184550();
testHeaderGuard();
testModeCorsPreflightEnumValue();
testBug1154268();

View File

@ -12,7 +12,9 @@
enum AnimationPlayState { "idle", "pending", "running", "paused", "finished" };
[Func="nsDocument::IsWebAnimationsEnabled"]
[Func="nsDocument::IsWebAnimationsEnabled",
Constructor (optional KeyframeEffectReadOnly? effect = null,
optional AnimationTimeline? timeline = null)]
interface Animation : EventTarget {
attribute DOMString id;
// Bug 1049975: Make 'effect' writeable

View File

@ -76,8 +76,7 @@ LayerManager::GetRootScrollableLayerId()
return FrameMetrics::NULL_SCROLL_ID;
}
nsTArray<LayerMetricsWrapper> queue;
queue.AppendElement(LayerMetricsWrapper(mRoot));
nsTArray<LayerMetricsWrapper> queue = { LayerMetricsWrapper(mRoot) };
while (queue.Length()) {
LayerMetricsWrapper layer = queue[0];
queue.RemoveElementAt(0);
@ -110,8 +109,7 @@ LayerManager::GetRootScrollableLayers(nsTArray<Layer*>& aArray)
return;
}
nsTArray<Layer*> queue;
queue.AppendElement(mRoot);
nsTArray<Layer*> queue = { mRoot };
while (queue.Length()) {
Layer* layer = queue[0];
queue.RemoveElementAt(0);
@ -134,8 +132,7 @@ LayerManager::GetScrollableLayers(nsTArray<Layer*>& aArray)
return;
}
nsTArray<Layer*> queue;
queue.AppendElement(mRoot);
nsTArray<Layer*> queue = { mRoot };
while (!queue.IsEmpty()) {
Layer* layer = queue.LastElement();
queue.RemoveElementAt(queue.Length() - 1);

View File

@ -890,9 +890,8 @@ TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_NoTouchAction) {
TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNone) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
nsTArray<uint32_t> behaviors;
behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::NONE);
behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::NONE);
nsTArray<uint32_t> behaviors = { mozilla::layers::AllowedTouchBehavior::NONE,
mozilla::layers::AllowedTouchBehavior::NONE };
DoPinchTest(false, &behaviors);
}

View File

@ -12,6 +12,7 @@
#include "gfx2DGlue.h"
#include "gfxUtils.h"
#include "mozilla/gfx/2D.h"
#include "GeckoProfiler.h"
using namespace mozilla::gfx;
@ -227,6 +228,7 @@ TextureSourceD3D9::DataToTexture(DeviceManagerD3D9* aDeviceManager,
_D3DFORMAT aFormat,
uint32_t aBPP)
{
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
RefPtr<IDirect3DSurface9> surface;
D3DLOCKED_RECT lockedRect;
RefPtr<IDirect3DTexture9> texture = InitTextures(aDeviceManager, aSize, aFormat,
@ -322,6 +324,7 @@ DataTextureSourceD3D9::Update(gfx::DataSourceSurface* aSurface,
nsIntRegion* aDestRegion,
gfx::IntPoint* aSrcOffset)
{
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
// Right now we only support full surface update. If aDestRegion is provided,
// It will be ignored. Incremental update with a source offset is only used
// on Mac so it is not clear that we ever will need to support it for D3D.
@ -636,6 +639,7 @@ DXGID3D9TextureData::Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
TextureFlags aFlags,
IDirect3DDevice9* aDevice)
{
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
MOZ_ASSERT(aFormat == gfx::SurfaceFormat::B8G8R8X8);
if (aFormat != gfx::SurfaceFormat::B8G8R8X8) {
return nullptr;
@ -707,6 +711,7 @@ bool
DataTextureSourceD3D9::UpdateFromTexture(IDirect3DTexture9* aTexture,
const nsIntRegion* aRegion)
{
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
MOZ_ASSERT(aTexture);
D3DSURFACE_DESC desc;

View File

@ -105,9 +105,8 @@ CommonAnimationManager::GetAnimationCollection(dom::Element *aElement,
AnimationCollection::PropertyDtor(aElement, propName, collection, nullptr);
return nullptr;
}
if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
aElement->SetMayHaveAnimations();
}
aElement->SetMayHaveAnimations();
AddElementCollection(collection);
}
@ -124,9 +123,7 @@ CommonAnimationManager::GetAnimationCollection(const nsIFrame* aFrame)
return nullptr;
}
if (pseudoElement->second() ==
nsCSSPseudoElements::ePseudo_NotPseudoElement &&
!pseudoElement->first()->MayHaveAnimations()) {
if (!pseudoElement->first()->MayHaveAnimations()) {
return nullptr;
}

View File

@ -112,7 +112,7 @@ MP4Metadata::~MP4Metadata()
#ifdef MOZ_RUST_MP4PARSE
// Helper to test the rust parser on a data source.
static bool try_rust(const UniquePtr<mp4parse_state, FreeMP4ParseState>& aRustState, RefPtr<Stream> aSource, int32_t* aCount)
static bool try_rust(const UniquePtr<mp4parse_state, FreeMP4ParseState>& aRustState, RefPtr<Stream> aSource)
{
static LazyLogModule sLog("MP4Metadata");
int64_t length;
@ -129,9 +129,7 @@ static bool try_rust(const UniquePtr<mp4parse_state, FreeMP4ParseState>& aRustSt
MOZ_LOG(sLog, LogLevel::Warning, ("Error copying mp4 data"));
return false;
}
*aCount = mp4parse_read(aRustState.get(), buffer.data(), bytes_read);
MOZ_LOG(sLog, LogLevel::Info, ("rust parser found %d tracks", int(*aCount)));
return true;
return mp4parse_read(aRustState.get(), buffer.data(), bytes_read);
}
#endif
@ -139,12 +137,18 @@ uint32_t
MP4Metadata::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const
{
#ifdef MOZ_RUST_MP4PARSE
static LazyLogModule sLog("MP4Metadata");
// Try in rust first.
mRustState.reset(mp4parse_new());
int32_t rust_tracks = 0;
bool rust_mp4parse_success = try_rust(mRustState, mSource, &rust_tracks);
int32_t rust_mp4parse_success = try_rust(mRustState, mSource);
Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_SUCCESS,
rust_mp4parse_success);
rust_mp4parse_success == 0);
if (rust_mp4parse_success < 0) {
Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_ERROR_CODE,
-rust_mp4parse_success);
}
uint32_t rust_tracks = mp4parse_get_track_count(mRustState.get());
MOZ_LOG(sLog, LogLevel::Info, ("rust parser found %u tracks", rust_tracks));
#endif
size_t tracks = mPrivate->mMetadataExtractor->countTracks();
uint32_t total = 0;
@ -176,7 +180,7 @@ MP4Metadata::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const
uint32_t rust_total = 0;
const char* rust_track_type = nullptr;
if (rust_mp4parse_success && rust_tracks > 0) {
for (int32_t i = 0; i < rust_tracks; ++i) {
for (uint32_t i = 0; i < rust_tracks; ++i) {
mp4parse_track_info track_info;
int32_t r = mp4parse_get_track_info(mRustState.get(), i, &track_info);
switch (aType) {
@ -197,7 +201,6 @@ MP4Metadata::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const
}
}
}
static LazyLogModule sLog("MP4Metadata");
MOZ_LOG(sLog, LogLevel::Info, ("%s tracks found: stagefright=%u rust=%u",
rust_track_type, total, rust_total));
switch (aType) {

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
diff --git a/media/libstagefright/binding/byteorder/mod.rs b/media/libstagefright/binding/byteorder/mod.rs
index 59ba692..9d2d1d5 100644
index 7eea1e3..8a108cf 100644
--- a/media/libstagefright/binding/byteorder/mod.rs
+++ b/media/libstagefright/binding/byteorder/mod.rs
@@ -36,16 +36,16 @@ assert_eq!(wtr, vec![5, 2, 0, 3]);
@@ -36,7 +36,6 @@ assert_eq!(wtr, vec![5, 2, 0, 3]);
```
*/
@ -10,12 +10,14 @@ index 59ba692..9d2d1d5 100644
#![doc(html_root_url = "http://burntsushi.net/rustdoc/byteorder")]
#![deny(missing_docs)]
@@ -45,10 +44,11 @@ use std::mem::transmute;
use std::ptr::copy_nonoverlapping;
use std::mem::transmute;
#[cfg(not(feature = "no-std"))]
-pub use new::{ReadBytesExt, WriteBytesExt, Error, Result};
+pub use byteorder::new::{ReadBytesExt, WriteBytesExt, Error, Result};
#[cfg(not(feature = "no-std"))]
-mod new;
+// Re-export new so gecko can build us as a mod intead of a crate.
+pub mod new;
@ -23,7 +25,7 @@ index 59ba692..9d2d1d5 100644
#[inline]
fn extend_sign(val: u64, nbytes: usize) -> i64 {
diff --git a/media/libstagefright/binding/byteorder/new.rs b/media/libstagefright/binding/byteorder/new.rs
index bbef0cd..a2e5393 100644
index 54ee6a7..4efcbc3 100644
--- a/media/libstagefright/binding/byteorder/new.rs
+++ b/media/libstagefright/binding/byteorder/new.rs
@@ -3,7 +3,7 @@ use std::fmt;

View File

@ -41,18 +41,48 @@ assert_eq!(wtr, vec![5, 2, 0, 3]);
#![deny(missing_docs)]
use std::mem::transmute;
use std::ptr::copy_nonoverlapping;
#[cfg(not(feature = "no-std"))]
pub use byteorder::new::{ReadBytesExt, WriteBytesExt, Error, Result};
#[cfg(not(feature = "no-std"))]
// Re-export new so gecko can build us as a mod intead of a crate.
pub mod new;
#[inline]
fn extend_sign(val: u64, nbytes: usize) -> i64 {
let shift = (8 - nbytes) * 8;
let shift = (8 - nbytes) * 8;
(val << shift) as i64 >> shift
}
#[inline]
fn unextend_sign(val: i64, nbytes: usize) -> u64 {
let shift = (8 - nbytes) * 8;
(val << shift) as u64 >> shift
}
#[inline]
fn pack_size(n: u64) -> usize {
if n < 1 << 8 {
1
} else if n < 1 << 16 {
2
} else if n < 1 << 24 {
3
} else if n < 1 << 32 {
4
} else if n < 1 << 40 {
5
} else if n < 1 << 48 {
6
} else if n < 1 << 56 {
7
} else {
8
}
}
/// ByteOrder describes types that can serialize integers as bytes.
///
/// Note that `Self` does not appear anywhere in this trait's definition!
@ -120,6 +150,12 @@ pub trait ByteOrder {
/// Panics when `buf.len() < 8`.
fn write_u64(buf: &mut [u8], n: u64);
/// Writes an unsigned integer `n` to `buf` using only `nbytes`.
///
/// If `n` is not representable in `nbytes`, or if `nbytes` is `> 8`, then
/// this method panics.
fn write_uint(buf: &mut [u8], n: u64, nbytes: usize);
/// Reads a signed 16 bit integer from `buf`.
///
/// Panics when `buf.len() < 2`.
@ -193,6 +229,15 @@ pub trait ByteOrder {
Self::write_u64(buf, n as u64)
}
/// Writes a signed integer `n` to `buf` using only `nbytes`.
///
/// If `n` is not representable in `nbytes`, or if `nbytes` is `> 8`, then
/// this method panics.
#[inline]
fn write_int(buf: &mut [u8], n: i64, nbytes: usize) {
Self::write_uint(buf, unextend_sign(n, nbytes), nbytes)
}
/// Writes a IEEE754 single-precision (4 bytes) floating point number.
///
/// Panics when `buf.len() < 4`.
@ -238,41 +283,16 @@ pub type NativeEndian = BigEndian;
macro_rules! read_num_bytes {
($ty:ty, $size:expr, $src:expr, $which:ident) => ({
assert!($src.len() >= $size); // critical for memory safety!
assert!($size <= $src.len());
unsafe {
(*($src.as_ptr() as *const $ty)).$which()
}
});
($ty:ty, $size:expr, le $bytes:expr, $src:expr, $which:ident) => ({
use std::ptr::copy_nonoverlapping;
assert!($bytes > 0 && $bytes < 9 && $bytes <= $src.len());
let mut out = [0u8; $size];
let ptr_out = out.as_mut_ptr();
unsafe {
copy_nonoverlapping($src.as_ptr(), ptr_out, $bytes);
(*(ptr_out as *const $ty)).$which()
}
});
($ty:ty, $size:expr, be $bytes:expr, $src:expr, $which:ident) => ({
use std::ptr::copy_nonoverlapping;
assert!($bytes > 0 && $bytes < 9 && $bytes <= $src.len());
let mut out = [0u8; $size];
let ptr_out = out.as_mut_ptr();
unsafe {
copy_nonoverlapping($src.as_ptr(),
ptr_out.offset((8 - $bytes) as isize), $bytes);
(*(ptr_out as *const $ty)).$which()
}
});
}
macro_rules! write_num_bytes {
($ty:ty, $size:expr, $n:expr, $dst:expr, $which:ident) => ({
use std::ptr::copy_nonoverlapping;
assert!($dst.len() >= $size); // critical for memory safety!
assert!($size <= $dst.len());
unsafe {
// N.B. https://github.com/rust-lang/rust/issues/22776
let bytes = transmute::<_, [u8; $size]>($n.$which());
@ -299,7 +319,14 @@ impl ByteOrder for BigEndian {
#[inline]
fn read_uint(buf: &[u8], nbytes: usize) -> u64 {
read_num_bytes!(u64, 8, be nbytes, buf, to_be)
assert!(1 <= nbytes && nbytes <= 8 && nbytes <= buf.len());
let mut out = [0u8; 8];
let ptr_out = out.as_mut_ptr();
unsafe {
copy_nonoverlapping(
buf.as_ptr(), ptr_out.offset((8 - nbytes) as isize), nbytes);
(*(ptr_out as *const u64)).to_be()
}
}
#[inline]
@ -316,6 +343,19 @@ impl ByteOrder for BigEndian {
fn write_u64(buf: &mut [u8], n: u64) {
write_num_bytes!(u64, 8, n, buf, to_be);
}
#[inline]
fn write_uint(buf: &mut [u8], n: u64, nbytes: usize) {
assert!(pack_size(n) <= nbytes && nbytes <= 8);
assert!(nbytes <= buf.len());
unsafe {
let bytes: [u8; 8] = transmute(n.to_be());
copy_nonoverlapping(
bytes.as_ptr().offset((8 - nbytes) as isize),
buf.as_mut_ptr(),
nbytes);
}
}
}
impl ByteOrder for LittleEndian {
@ -336,7 +376,13 @@ impl ByteOrder for LittleEndian {
#[inline]
fn read_uint(buf: &[u8], nbytes: usize) -> u64 {
read_num_bytes!(u64, 8, le nbytes, buf, to_le)
assert!(1 <= nbytes && nbytes <= 8 && nbytes <= buf.len());
let mut out = [0u8; 8];
let ptr_out = out.as_mut_ptr();
unsafe {
copy_nonoverlapping(buf.as_ptr(), ptr_out, nbytes);
(*(ptr_out as *const u64)).to_le()
}
}
#[inline]
@ -353,6 +399,16 @@ impl ByteOrder for LittleEndian {
fn write_u64(buf: &mut [u8], n: u64) {
write_num_bytes!(u64, 8, n, buf, to_le);
}
#[inline]
fn write_uint(buf: &mut [u8], n: u64, nbytes: usize) {
assert!(pack_size(n as u64) <= nbytes && nbytes <= 8);
assert!(nbytes <= buf.len());
unsafe {
let bytes: [u8; 8] = transmute(n.to_le());
copy_nonoverlapping(bytes.as_ptr(), buf.as_mut_ptr(), nbytes);
}
}
}
#[cfg(test)]
@ -386,8 +442,8 @@ mod test {
let max = ($max - 1) >> (8 * (8 - $bytes));
fn prop(n: $ty_int) -> bool {
let mut buf = [0; 8];
BigEndian::$write(&mut buf, n);
n == BigEndian::$read(&mut buf[8 - $bytes..], $bytes)
BigEndian::$write(&mut buf, n, $bytes);
n == BigEndian::$read(&mut buf[..$bytes], $bytes)
}
qc_sized(prop as fn($ty_int) -> bool, max);
}
@ -397,7 +453,7 @@ mod test {
let max = ($max - 1) >> (8 * (8 - $bytes));
fn prop(n: $ty_int) -> bool {
let mut buf = [0; 8];
LittleEndian::$write(&mut buf, n);
LittleEndian::$write(&mut buf, n, $bytes);
n == LittleEndian::$read(&mut buf[..$bytes], $bytes)
}
qc_sized(prop as fn($ty_int) -> bool, max);
@ -408,7 +464,7 @@ mod test {
let max = ($max - 1) >> (8 * (8 - $bytes));
fn prop(n: $ty_int) -> bool {
let mut buf = [0; 8];
NativeEndian::$write(&mut buf, n);
NativeEndian::$write(&mut buf, n, $bytes);
n == NativeEndian::$read(&mut buf[..$bytes], $bytes)
}
qc_sized(prop as fn($ty_int) -> bool, max);
@ -467,23 +523,23 @@ mod test {
qc_byte_order!(prop_f32, f32, ::std::u64::MAX as u64, read_f32, write_f32);
qc_byte_order!(prop_f64, f64, ::std::i64::MAX as u64, read_f64, write_f64);
qc_byte_order!(prop_uint_1, u64, super::U64_MAX, 1, read_uint, write_u64);
qc_byte_order!(prop_uint_2, u64, super::U64_MAX, 2, read_uint, write_u64);
qc_byte_order!(prop_uint_3, u64, super::U64_MAX, 3, read_uint, write_u64);
qc_byte_order!(prop_uint_4, u64, super::U64_MAX, 4, read_uint, write_u64);
qc_byte_order!(prop_uint_5, u64, super::U64_MAX, 5, read_uint, write_u64);
qc_byte_order!(prop_uint_6, u64, super::U64_MAX, 6, read_uint, write_u64);
qc_byte_order!(prop_uint_7, u64, super::U64_MAX, 7, read_uint, write_u64);
qc_byte_order!(prop_uint_8, u64, super::U64_MAX, 8, read_uint, write_u64);
qc_byte_order!(prop_uint_1, u64, super::U64_MAX, 1, read_uint, write_uint);
qc_byte_order!(prop_uint_2, u64, super::U64_MAX, 2, read_uint, write_uint);
qc_byte_order!(prop_uint_3, u64, super::U64_MAX, 3, read_uint, write_uint);
qc_byte_order!(prop_uint_4, u64, super::U64_MAX, 4, read_uint, write_uint);
qc_byte_order!(prop_uint_5, u64, super::U64_MAX, 5, read_uint, write_uint);
qc_byte_order!(prop_uint_6, u64, super::U64_MAX, 6, read_uint, write_uint);
qc_byte_order!(prop_uint_7, u64, super::U64_MAX, 7, read_uint, write_uint);
qc_byte_order!(prop_uint_8, u64, super::U64_MAX, 8, read_uint, write_uint);
qc_byte_order!(prop_int_1, i64, super::I64_MAX, 1, read_int, write_i64);
qc_byte_order!(prop_int_2, i64, super::I64_MAX, 2, read_int, write_i64);
qc_byte_order!(prop_int_3, i64, super::I64_MAX, 3, read_int, write_i64);
qc_byte_order!(prop_int_4, i64, super::I64_MAX, 4, read_int, write_i64);
qc_byte_order!(prop_int_5, i64, super::I64_MAX, 5, read_int, write_i64);
qc_byte_order!(prop_int_6, i64, super::I64_MAX, 6, read_int, write_i64);
qc_byte_order!(prop_int_7, i64, super::I64_MAX, 7, read_int, write_i64);
qc_byte_order!(prop_int_8, i64, super::I64_MAX, 8, read_int, write_i64);
qc_byte_order!(prop_int_1, i64, super::I64_MAX, 1, read_int, write_int);
qc_byte_order!(prop_int_2, i64, super::I64_MAX, 2, read_int, write_int);
qc_byte_order!(prop_int_3, i64, super::I64_MAX, 3, read_int, write_int);
qc_byte_order!(prop_int_4, i64, super::I64_MAX, 4, read_int, write_int);
qc_byte_order!(prop_int_5, i64, super::I64_MAX, 5, read_int, write_int);
qc_byte_order!(prop_int_6, i64, super::I64_MAX, 6, read_int, write_int);
qc_byte_order!(prop_int_7, i64, super::I64_MAX, 7, read_int, write_int);
qc_byte_order!(prop_int_8, i64, super::I64_MAX, 8, read_int, write_int);
macro_rules! qc_bytes_ext {
($name:ident, $ty_int:ident, $max:expr,

View File

@ -295,6 +295,36 @@ pub trait WriteBytesExt: io::Write {
write_all(self, &buf)
}
/// Writes an unsigned n-bytes integer to the underlying writer.
///
/// If the given integer is not representable in the given number of bytes,
/// this method panics. If `nbytes > 8`, this method panics.
#[inline]
fn write_uint<T: ByteOrder>(
&mut self,
n: u64,
nbytes: usize,
) -> Result<()> {
let mut buf = [0; 8];
T::write_uint(&mut buf, n, nbytes);
write_all(self, &buf[0..nbytes])
}
/// Writes a signed n-bytes integer to the underlying writer.
///
/// If the given integer is not representable in the given number of bytes,
/// this method panics. If `nbytes > 8`, this method panics.
#[inline]
fn write_int<T: ByteOrder>(
&mut self,
n: i64,
nbytes: usize,
) -> Result<()> {
let mut buf = [0; 8];
T::write_int(&mut buf, n, nbytes);
write_all(self, &buf[0..nbytes])
}
/// Writes a IEEE754 single-precision (4 bytes) floating point number to
/// the underlying writer.
#[inline]

View File

@ -34,16 +34,30 @@ use media_time_to_ms;
use track_time_to_ms;
use SampleEntry;
// These constants *must* match those in include/mp4parse.h.
/// Map Error to int32 return codes.
const MP4PARSE_OK: i32 = 0;
const MP4PARSE_ERROR_BADARG: i32 = -1;
const MP4PARSE_ERROR_INVALID: i32 = -2;
const MP4PARSE_ERROR_UNSUPPORTED: i32 = -3;
const MP4PARSE_ERROR_EOF: i32 = -4;
const MP4PARSE_ASSERT: i32 = -5;
const MP4PARSE_ERROR_IO: i32 = -6;
/// Map TrackType to uint32 constants.
const TRACK_TYPE_H264: u32 = 0;
const TRACK_TYPE_AAC: u32 = 1;
const TRACK_TYPE_AAC: u32 = 1;
// These structs *must* match those declared in include/mp4parse.h.
#[repr(C)]
pub struct TrackInfo {
track_type: u32,
track_id: u32,
duration: u64,
media_time: i64, // wants to be u64? understand how elst adjustment works
// TODO(kinetik): include crypto guff
}
#[repr(C)]
@ -51,8 +65,11 @@ pub struct TrackAudioInfo {
channels: u16,
bit_depth: u16,
sample_rate: u32,
// profile: i32,
// extended_profile: i32, // check types
// TODO(kinetik):
// int32_t profile;
// int32_t extended_profile; // check types
// extra_data
// codec_specific_config
}
#[repr(C)]
@ -61,8 +78,13 @@ pub struct TrackVideoInfo {
display_height: u32,
image_width: u16,
image_height: u16,
// TODO(kinetik):
// extra_data
// codec_specific_config
}
// C API wrapper functions.
/// Allocate an opaque rust-side parser context.
#[no_mangle]
pub extern "C" fn mp4parse_new() -> *mut MediaContext {
@ -78,7 +100,7 @@ pub unsafe extern "C" fn mp4parse_free(context: *mut MediaContext) {
}
/// Feed a buffer through `read_mp4()` with the given rust-side
/// parser context, returning the number of detected tracks.
/// parser context, returning success or an error code.
///
/// This is safe to call with NULL arguments but will crash
/// if given invalid pointers, as is usual for C.
@ -86,7 +108,7 @@ pub unsafe extern "C" fn mp4parse_free(context: *mut MediaContext) {
pub unsafe extern "C" fn mp4parse_read(context: *mut MediaContext, buffer: *const u8, size: usize) -> i32 {
// Validate arguments from C.
if context.is_null() || buffer.is_null() || size < 8 {
return -1;
return MP4PARSE_ERROR_BADARG;
}
let mut context: &mut MediaContext = &mut *context;
@ -96,24 +118,38 @@ pub unsafe extern "C" fn mp4parse_read(context: *mut MediaContext, buffer: *cons
let mut c = Cursor::new(b);
// Parse in a subthread to catch any panics.
let task = std::thread::spawn(move || {
match read_mp4(&mut c, &mut context) {
Ok(_) => {},
Err(Error::UnexpectedEOF) => {},
Err(e) => { panic!(e); },
}
// Make sure the track count fits in an i32 so we can use
// negative values for failure.
assert!(context.tracks.len() < i32::max_value() as usize);
context.tracks.len() as i32
});
task.join().unwrap_or(-1)
let task = std::thread::spawn(move || read_mp4(&mut c, &mut context));
// The task's JoinHandle will return an error result if the
// thread panicked, and will wrap the closure's return'd
// result in an Ok(..) otherwise, meaning we could see
// Ok(Err(Error::..)) here. So map thread failures back
// to an mp4parse::Error before converting to a C return value.
match task.join().or(Err(Error::AssertCaught)) {
Ok(_) => MP4PARSE_OK,
Err(Error::InvalidData) => MP4PARSE_ERROR_INVALID,
Err(Error::Unsupported) => MP4PARSE_ERROR_UNSUPPORTED,
Err(Error::UnexpectedEOF) => MP4PARSE_ERROR_EOF,
Err(Error::AssertCaught) => MP4PARSE_ASSERT,
Err(Error::Io(_)) => MP4PARSE_ERROR_IO,
}
}
/// Return the number of tracks parsed by previous `read_mp4()` calls.
#[no_mangle]
pub unsafe extern "C" fn mp4parse_get_track_count(context: *const MediaContext) -> u32 {
// Validate argument from C.
assert!(!context.is_null());
let context = &*context;
// Make sure the track count fits in a u32.
assert!(context.tracks.len() < u32::max_value() as usize);
context.tracks.len() as u32
}
#[no_mangle]
pub unsafe extern "C" fn mp4parse_get_track_info(context: *mut MediaContext, track: i32, info: *mut TrackInfo) -> i32 {
if context.is_null() || track < 0 || info.is_null() {
return -1;
pub unsafe extern "C" fn mp4parse_get_track_info(context: *mut MediaContext, track: u32, info: *mut TrackInfo) -> i32 {
if context.is_null() || info.is_null() {
return MP4PARSE_ERROR_BADARG;
}
let context: &mut MediaContext = &mut *context;
@ -121,13 +157,13 @@ pub unsafe extern "C" fn mp4parse_get_track_info(context: *mut MediaContext, tra
let info: &mut TrackInfo = &mut *info;
if track_index >= context.tracks.len() {
return -1;
return MP4PARSE_ERROR_BADARG;
}
info.track_type = match context.tracks[track_index].track_type {
TrackType::Video => TRACK_TYPE_H264,
TrackType::Audio => TRACK_TYPE_AAC,
TrackType::Unknown => return -1,
TrackType::Unknown => return MP4PARSE_ERROR_UNSUPPORTED,
};
// Maybe context & track should just have a single simple is_valid() instead?
@ -135,8 +171,8 @@ pub unsafe extern "C" fn mp4parse_get_track_info(context: *mut MediaContext, tra
context.tracks[track_index].timescale.is_none() ||
context.tracks[track_index].duration.is_none() ||
context.tracks[track_index].track_id.is_none() {
return -1;
}
return MP4PARSE_ERROR_INVALID;
}
std::thread::spawn(move || {
let track = &context.tracks[track_index];
@ -152,98 +188,102 @@ pub unsafe extern "C" fn mp4parse_get_track_info(context: *mut MediaContext, tra
};
info.duration = track_time_to_ms(track.duration.unwrap(), track.timescale.unwrap());
info.track_id = track.track_id.unwrap();
0
}).join().unwrap_or(-1)
MP4PARSE_OK
}).join().unwrap_or(MP4PARSE_ERROR_INVALID)
}
#[no_mangle]
pub unsafe extern "C" fn mp4parse_get_track_audio_info(context: *mut MediaContext, track: i32, info: *mut TrackAudioInfo) -> i32 {
if context.is_null() || track < 0 || info.is_null() {
return -1;
pub unsafe extern "C" fn mp4parse_get_track_audio_info(context: *mut MediaContext, track: u32, info: *mut TrackAudioInfo) -> i32 {
if context.is_null() || info.is_null() {
return MP4PARSE_ERROR_BADARG;
}
let context: &mut MediaContext = &mut *context;
if track as usize >= context.tracks.len() {
return -1;
return MP4PARSE_ERROR_BADARG;
}
let track = &context.tracks[track as usize];
match track.track_type {
TrackType::Audio => {},
_ => return -1,
TrackType::Audio => {}
_ => return MP4PARSE_ERROR_INVALID,
};
let audio = match track.data {
Some(ref data) => data,
None => return -1,
None => return MP4PARSE_ERROR_INVALID,
};
let audio = match audio {
&SampleEntry::Audio(ref x) => x,
_ => return -1,
let audio = match *audio {
SampleEntry::Audio(ref x) => x,
_ => return MP4PARSE_ERROR_INVALID,
};
(*info).channels = audio.channelcount;
(*info).bit_depth = audio.samplesize;
(*info).sample_rate = audio.samplerate >> 16; // 16.16 fixed point
0
MP4PARSE_OK
}
#[no_mangle]
pub unsafe extern "C" fn mp4parse_get_track_video_info(context: *mut MediaContext, track: i32, info: *mut TrackVideoInfo) -> i32 {
if context.is_null() || track < 0 || info.is_null() {
return -1;
pub unsafe extern "C" fn mp4parse_get_track_video_info(context: *mut MediaContext, track: u32, info: *mut TrackVideoInfo) -> i32 {
if context.is_null() || info.is_null() {
return MP4PARSE_ERROR_BADARG;
}
let context: &mut MediaContext = &mut *context;
if track as usize >= context.tracks.len() {
return -1;
return MP4PARSE_ERROR_BADARG;
}
let track = &context.tracks[track as usize];
match track.track_type {
TrackType::Video => {},
_ => return -1,
TrackType::Video => {}
_ => return MP4PARSE_ERROR_INVALID,
};
let video = match track.data {
Some(ref data) => data,
None => return -1,
None => return MP4PARSE_ERROR_INVALID,
};
let video = match video {
&SampleEntry::Video(ref x) => x,
_ => return -1,
let video = match *video {
SampleEntry::Video(ref x) => x,
_ => return MP4PARSE_ERROR_INVALID,
};
if let Some(ref tkhd) = track.tkhd {
(*info).display_width = tkhd.width >> 16; // 16.16 fixed point
(*info).display_height = tkhd.height >> 16; // 16.16 fixed point
} else {
return -1
return MP4PARSE_ERROR_INVALID;
}
(*info).image_width = video.width;
(*info).image_width = video.height;
0
MP4PARSE_OK
}
#[test]
fn new_context() {
let context = mp4parse_new();
assert!(!context.is_null());
unsafe { mp4parse_free(context); }
unsafe {
mp4parse_free(context);
}
}
#[test]
#[should_panic(expected = "assertion failed")]
fn free_null_context() {
unsafe { mp4parse_free(std::ptr::null_mut()); }
unsafe {
mp4parse_free(std::ptr::null_mut());
}
}
#[test]
@ -257,16 +297,21 @@ fn arg_validation() {
let buffer = vec![0u8; 8];
unsafe {
assert_eq!(-1, mp4parse_read(null_context, null_buffer, 0));
assert_eq!(-1, mp4parse_read(context, null_buffer, 0));
assert_eq!(MP4PARSE_ERROR_BADARG,
mp4parse_read(null_context, null_buffer, 0));
assert_eq!(MP4PARSE_ERROR_BADARG,
mp4parse_read(context, null_buffer, 0));
}
for size in 0..buffer.len() {
println!("testing buffer length {}", size);
unsafe {
assert_eq!(-1, mp4parse_read(context, buffer.as_ptr(), size));
assert_eq!(MP4PARSE_ERROR_BADARG,
mp4parse_read(context, buffer.as_ptr(), size));
}
}
unsafe { mp4parse_free(context); }
unsafe {
mp4parse_free(context);
}
}

View File

@ -11,6 +11,14 @@ extern "C" {
struct mp4parse_state;
#define MP4PARSE_OK 0
#define MP4PARSE_ERROR_BADARG -1 // Argument validation failure
#define MP4PARSE_ERROR_INVALID -2 // Error::InvalidData
#define MP4PARSE_ERROR_UNSUPPORTED -3 // Error::Unsupported
#define MP4PARSE_ERROR_EOF -4 // Error::UnexpectedEOF
#define MP4PARSE_ASSERT -5 // Error::AssertCaught
#define MP4PARSE_ERROR_IO -6 // Error::Io(_)
#define MP4PARSE_TRACK_TYPE_H264 0 // "video/avc"
#define MP4PARSE_TRACK_TYPE_AAC 1 // "audio/mp4a-latm"
@ -18,12 +26,6 @@ struct mp4parse_track_audio_info {
uint16_t channels;
uint16_t bit_depth;
uint32_t sample_rate;
//int32_t profile;
//int32_t extended_profile; // check types
// TODO(kinetik):
// extra_data
// codec_specific_config
};
struct mp4parse_track_video_info {
@ -31,10 +33,6 @@ struct mp4parse_track_video_info {
uint32_t display_height;
uint16_t image_width;
uint16_t image_height;
// TODO(kinetik):
// extra_data
// codec_specific_config
};
struct mp4parse_track_info {
@ -42,7 +40,6 @@ struct mp4parse_track_info {
uint32_t track_id;
uint64_t duration;
int64_t media_time;
// TODO(kinetik): crypto guff
};
struct mp4parse_state* mp4parse_new(void);
@ -50,11 +47,13 @@ void mp4parse_free(struct mp4parse_state* state);
int32_t mp4parse_read(struct mp4parse_state* state, uint8_t *buffer, size_t size);
int32_t mp4parse_get_track_info(struct mp4parse_state* state, int32_t track, struct mp4parse_track_info* track_info);
uint32_t mp4parse_get_track_count(struct mp4parse_state* state);
int32_t mp4parse_get_track_audio_info(struct mp4parse_state* state, int32_t track, struct mp4parse_track_audio_info* track_info);
int32_t mp4parse_get_track_info(struct mp4parse_state* state, uint32_t track, struct mp4parse_track_info* track_info);
int32_t mp4parse_get_track_video_info(struct mp4parse_state* state, int32_t track, struct mp4parse_track_video_info* track_info);
int32_t mp4parse_get_track_audio_info(struct mp4parse_state* state, uint32_t track, struct mp4parse_track_audio_info* track_info);
int32_t mp4parse_get_track_video_info(struct mp4parse_state* state, uint32_t track, struct mp4parse_track_video_info* track_info);
#ifdef __cplusplus
}

View File

@ -2,7 +2,7 @@
# Script to update mp4parse-rust sources to latest upstream
# Default version.
VER=v0.1.4
VER=v0.1.6
# Accept version or commit from the command line.
if test -n "$1"; then
@ -23,7 +23,7 @@ cp _upstream/mp4parse/include/mp4parse.h include/
git clone https://github.com/BurntSushi/byteorder _upstream/byteorder
pushd _upstream/byteorder
git checkout 0.3.13
git checkout 0.4.2
popd
cp _upstream/byteorder/src/lib.rs byteorder/mod.rs
cp _upstream/byteorder/src/new.rs byteorder/new.rs

View File

@ -22,27 +22,27 @@ TEST(rust, MP4MetadataEmpty)
ASSERT_NE(context, nullptr);
rv = mp4parse_read(nullptr, nullptr, 0);
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
rv = mp4parse_read(context, nullptr, 0);
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
size_t len = 4097;
rv = mp4parse_read(nullptr, nullptr, len);
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
rv = mp4parse_read(context, nullptr, len);
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
std::vector<uint8_t> buf;
rv = mp4parse_read(nullptr, buf.data(), buf.size());
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
rv = mp4parse_read(context, buf.data(), buf.size());
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
buf.reserve(len);
rv = mp4parse_read(nullptr, buf.data(), buf.size());
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
rv = mp4parse_read(context, buf.data(), buf.size());
EXPECT_EQ(rv, -1);
EXPECT_EQ(rv, MP4PARSE_ERROR_BADARG);
mp4parse_free(context);
}
@ -62,7 +62,10 @@ TEST(rust, MP4Metadata)
ASSERT_NE(context, nullptr);
int32_t rv = mp4parse_read(context, buf.data(), buf.size());
EXPECT_EQ(rv, 2);
EXPECT_EQ(rv, MP4PARSE_OK);
uint32_t tracks = mp4parse_get_track_count(context);
EXPECT_EQ(tracks, 2U);
mp4parse_free(context);
}

View File

@ -1144,6 +1144,8 @@ WebrtcVideoConduit::SelectSendResolution(unsigned short width,
bool changed = false;
if (mSendingWidth != width || mSendingHeight != height)
{
CSFLogDebug(logTag, "%s: resolution changing to %ux%u (from %ux%u)",
__FUNCTION__, width, height, mSendingWidth, mSendingHeight);
// This will avoid us continually retrying this operation if it fails.
// If the resolution changes, we'll try again. In the meantime, we'll
// keep using the old size in the encoder.
@ -1155,6 +1157,8 @@ WebrtcVideoConduit::SelectSendResolution(unsigned short width,
// uses mSendingWidth/Height
unsigned int framerate = SelectSendFrameRate(mSendingFramerate);
if (mSendingFramerate != framerate) {
CSFLogDebug(logTag, "%s: framerate changing to %u (from %u)",
__FUNCTION__, framerate, mSendingFramerate);
mSendingFramerate = framerate;
changed = true;
}
@ -1221,6 +1225,10 @@ WebrtcVideoConduit::ReconfigureSendCodec(unsigned short width,
CSFLogError(logTag, "%s: GetSendCodec failed, err %d", __FUNCTION__, err);
return NS_ERROR_FAILURE;
}
CSFLogDebug(logTag,
"%s: Requesting resolution change to %ux%u (from %ux%u)",
__FUNCTION__, width, height, vie_codec.width, vie_codec.height);
// Likely spurious unless there was some error, but rarely checked
if (vie_codec.width != width || vie_codec.height != height ||
vie_codec.maxFramerate != mSendingFramerate)
@ -1399,6 +1407,8 @@ WebrtcVideoConduit::SendVideoFrame(webrtc::I420VideoFrame& frame)
return kMediaConduitNoError;
}
if (frame.width() != mLastWidth || frame.height() != mLastHeight) {
CSFLogDebug(logTag, "%s: call SelectSendResolution with %ux%u",
__FUNCTION__, frame.width(), frame.height());
if (SelectSendResolution(frame.width(), frame.height(), &frame)) {
// SelectSendResolution took ownership of the data in i420_frame.
// Submit the frame after reconfig is done

View File

@ -20,6 +20,9 @@ if [ ! -f /etc/redhat-release ] || [ "$(< /etc/redhat-release)" != "CentOS relea
# see testing/taskcluster/scripts/misc/repackage-jdk.sh
export JAVA_HOME="$topsrcdir/java_home"
export PATH="$PATH:$topsrcdir/java_home/bin"
mk_add_options "export JAVA_HOME=$topsrcdir/java_home"
mk_add_options "export PATH=$PATH:$topsrcdir/java_home/bin"
fi
# Set the most aggressive settings for szip. Not the default because it's
@ -62,10 +65,6 @@ HOST_CXX="$topsrcdir/gcc/bin/g++"
# Avoid dependency on libstdc++ 4.7
ac_add_options --enable-stdcxx-compat
mk_add_options "export ANT_HOME=$topsrcdir/apache-ant"
mk_add_options "export PATH=$topsrcdir/apache-ant/bin:$PATH"
JS_BINARY="$topsrcdir/mobile/android/config/js_wrapper.sh"
# Configure gaia

View File

@ -52,7 +52,7 @@ public class SearchEngineBar extends RecyclerView
super(context, attrs);
mDividerPaint = new Paint();
mDividerPaint.setColor(ColorUtils.getColor(context, R.color.divider_light));
mDividerPaint.setColor(ColorUtils.getColor(context, R.color.toolbar_divider_grey));
mDividerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();

View File

@ -162,10 +162,7 @@ public final class GeckoLoader {
f = context.getCacheDir();
putenv("CACHE_DIRECTORY=" + f.getPath());
/* We really want to use this code, but it requires bumping up the SDK to 17 so for now
we will use reflection. See https://bugzilla.mozilla.org/show_bug.cgi?id=811763#c11
if (Build.VERSION.SDK_INT >= 17) {
if (AppConstants.Versions.feature17Plus) {
android.os.UserManager um = (android.os.UserManager)context.getSystemService(Context.USER_SERVICE);
if (um != null) {
putenv("MOZ_ANDROID_USER_SERIAL_NUMBER=" + um.getSerialNumberForUser(android.os.Process.myUserHandle()));
@ -173,21 +170,6 @@ public final class GeckoLoader {
Log.d(LOGTAG, "Unable to obtain user manager service on a device with SDK version " + Build.VERSION.SDK_INT);
}
}
*/
try {
Object userManager = context.getSystemService("user");
if (userManager != null) {
// if userManager is non-null that means we're running on 4.2+ and so the rest of this
// should just work
Object userHandle = android.os.Process.class.getMethod("myUserHandle", (Class[])null).invoke(null);
Object userSerial = userManager.getClass().getMethod("getSerialNumberForUser", userHandle.getClass()).invoke(userManager, userHandle);
putenv("MOZ_ANDROID_USER_SERIAL_NUMBER=" + userSerial.toString());
}
} catch (Exception e) {
// Guard against any unexpected failures
Log.d(LOGTAG, "Unable to set the user serial number", e);
}
setupLocaleEnvironment();
// We don't need this any more.

View File

@ -92,7 +92,7 @@ public abstract class DoorHanger extends LinearLayout {
mPositiveButton = (Button) findViewById(R.id.doorhanger_button_positive);
mOnButtonClickListener = config.getButtonClickListener();
mDividerColor = ColorUtils.getColor(context, R.color.divider_light);
mDividerColor = ColorUtils.getColor(context, R.color.toolbar_divider_grey);
final ViewStub contentStub = (ViewStub) findViewById(R.id.content);
contentStub.setLayoutResource(getContentResource());

View File

@ -6,7 +6,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/divider_light"/>
<solid android:color="@color/toolbar_divider_grey"/>
<size android:height="1dp" />
</shape>

View File

@ -6,7 +6,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/divider_light"/>
<solid android:color="@color/toolbar_divider_grey"/>
<size android:width="1dp" />
</shape>

View File

@ -9,7 +9,7 @@
<item>
<shape android:shape="rectangle" >
<stroke android:width="1dp"
android:color="@color/divider_light" />
android:color="@color/toolbar_divider_grey" />
<padding android:top="1dp" />
</shape>
</item>

View File

@ -24,7 +24,7 @@
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@color/divider_light"/>
android:background="@color/toolbar_divider_grey"/>
<ViewStub android:id="@id/home_empty_view_stub"
android:layout="@layout/home_empty_panel"

View File

@ -70,7 +70,7 @@
<View android:id="@+id/divider_doorhanger"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/divider_light"
android:background="@color/toolbar_divider_grey"
android:visibility="gone"/>
</merge>

View File

@ -24,7 +24,7 @@
gecko:strip="@drawable/home_tab_menu_strip"
gecko:titlebarFill="true"
gecko:activeTextColor="@android:color/white"
gecko:inactiveTextColor="@color/divider_light" />
gecko:inactiveTextColor="@color/toolbar_divider_grey" />
</org.mozilla.gecko.firstrun.FirstrunPager>
</org.mozilla.gecko.firstrun.FirstrunAnimationContainer>

View File

@ -95,7 +95,7 @@
<View android:id="@+id/divider_doorhanger"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/divider_light"
android:background="@color/toolbar_divider_grey"
android:visibility="gone"/>
</LinearLayout>

View File

@ -60,7 +60,7 @@
<!-- DropDown List View -->
<style name="DropDownListView" parent="@android:style/Widget.Holo.ListView.DropDown">
<item name="android:listSelector">@drawable/action_bar_button</item>
<item name="android:divider">@color/divider_light</item>
<item name="android:divider">@color/toolbar_divider_grey</item>
<item name="android:dividerHeight">1dp</item>
</style>

View File

@ -98,7 +98,7 @@
<color name="text_color_link">#22629E</color>
<!-- Divider colors -->
<color name="divider_light">#FFD7D9DB</color>
<color name="toolbar_divider_grey">#D7D9DB</color>
<color name="doorhanger_link">#FF2AA1FE</color>

View File

@ -45,7 +45,7 @@
</style>
<style name="Widget.ListView" parent="Widget.BaseListView">
<item name="android:divider">@color/divider_light</item>
<item name="android:divider">@color/toolbar_divider_grey</item>
<item name="android:dividerHeight">1dp</item>
<item name="android:cacheColorHint">@android:color/transparent</item>
<item name="android:listSelector">@drawable/action_bar_button</item>
@ -82,7 +82,7 @@
<style name="Widget.GeckoMenuListView" parent="Widget.ListView">
<item name="android:listSelector">@drawable/menu_item_action_bar_bg</item>
<item name="android:divider">@color/divider_light</item>
<item name="android:divider">@color/toolbar_divider_grey</item>
</style>
<style name="Widget.MenuItemActionBar">
@ -236,7 +236,7 @@
</style>
<style name="Widget.HomeListView" parent="Widget.ListView">
<item name="android:divider">@color/divider_light</item>
<item name="android:divider">@color/toolbar_divider_grey</item>
</style>
<style name="Widget.TopSitesListView" parent="Widget.BookmarksListView"/>
@ -556,12 +556,12 @@
</style>
<style name="Widget.RemoteTabsListView" parent="Widget.HomeListView">
<item name="android:childDivider">@color/divider_light</item>
<item name="android:childDivider">@color/toolbar_divider_grey</item>
<item name="android:drawSelectorOnTop">true</item>
</style>
<style name="Widget.HistoryListView" parent="Widget.HomeListView">
<item name="android:childDivider">@color/divider_light</item>
<item name="android:childDivider">@color/toolbar_divider_grey</item>
<item name="android:drawSelectorOnTop">true</item>
</style>
@ -662,7 +662,7 @@
</style>
<style name="ToastDividerBase">
<item name="android:background">@color/divider_light</item>
<item name="android:background">@color/toolbar_divider_grey</item>
<item name="android:layout_width">1dp</item>
<item name="android:layout_height">match_parent</item>
</style>

View File

@ -20,6 +20,9 @@ if [ ! -f /etc/redhat-release ] || [ "$(< /etc/redhat-release)" != "CentOS relea
# see testing/taskcluster/scripts/misc/repackage-jdk.sh
export JAVA_HOME="$topsrcdir/java_home"
export PATH="$PATH:$topsrcdir/java_home/bin"
mk_add_options "export JAVA_HOME=$topsrcdir/java_home"
mk_add_options "export PATH=$PATH:$topsrcdir/java_home/bin"
fi
# Set the most aggressive settings for szip. Not the default because it's
@ -61,8 +64,4 @@ HOST_CXX="$topsrcdir/gcc/bin/g++"
# Avoid dependency on libstdc++ 4.7
ac_add_options --enable-stdcxx-compat
mk_add_options "export ANT_HOME=$topsrcdir/apache-ant"
mk_add_options "export PATH=$topsrcdir/apache-ant/bin:$PATH"
JS_BINARY="$topsrcdir/mobile/android/config/js_wrapper.sh"

View File

@ -22,13 +22,6 @@
"unpack": true
},
{
"size": 7920445,
"digest": "e28b7a12fbbef02ad742958df8dd356ea2adb8ef79e95cd8eb8dbc953eb4cc11888969dac7d636187fd3ace9c63d9a6bc3d7795021c1d811a843e413fe5e52c9",
"algorithm": "sha512",
"filename": "apache-ant.tar.bz2",
"unpack": true
},
{
"size": 4906080,
"digest": "d735544e039da89382c53b2302b7408d4610247b4f8b5cdc5a4d5a8ec5470947b19e8ea7f7a37e78222e661347e394e0030d81f41534138b527b14e9c4e55634",
"algorithm": "sha512",

View File

@ -23,13 +23,6 @@
"unpack": true
},
{
"size": 7920445,
"digest": "e28b7a12fbbef02ad742958df8dd356ea2adb8ef79e95cd8eb8dbc953eb4cc11888969dac7d636187fd3ace9c63d9a6bc3d7795021c1d811a843e413fe5e52c9",
"algorithm": "sha512",
"filename": "apache-ant.tar.bz2",
"unpack": true
},
{
"size": 4906080,
"digest": "d735544e039da89382c53b2302b7408d4610247b4f8b5cdc5a4d5a8ec5470947b19e8ea7f7a37e78222e661347e394e0030d81f41534138b527b14e9c4e55634",
"algorithm": "sha512",

View File

@ -24,14 +24,6 @@
"unpack": true
},
{
"size": 7920445,
"visibility": "public",
"digest": "e28b7a12fbbef02ad742958df8dd356ea2adb8ef79e95cd8eb8dbc953eb4cc11888969dac7d636187fd3ace9c63d9a6bc3d7795021c1d811a843e413fe5e52c9",
"algorithm": "sha512",
"filename": "apache-ant.tar.bz2",
"unpack": true
},
{
"size": 4906080,
"visibility": "public",
"unpack": true,

View File

@ -24,14 +24,6 @@
"unpack": true
},
{
"size": 7920445,
"visibility": "public",
"digest": "e28b7a12fbbef02ad742958df8dd356ea2adb8ef79e95cd8eb8dbc953eb4cc11888969dac7d636187fd3ace9c63d9a6bc3d7795021c1d811a843e413fe5e52c9",
"algorithm": "sha512",
"filename": "apache-ant.tar.bz2",
"unpack": true
},
{
"size": 4906080,
"visibility": "public",
"unpack": true,

View File

@ -1,308 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ClosingService.h"
#include "nsIOService.h"
#ifdef MOZ_NUWA_PROCESS
#include "ipc/Nuwa.h"
#endif
class ClosingLayerSecret
{
public:
explicit ClosingLayerSecret(mozilla::net::ClosingService *aClosingService)
: mClosingService(aClosingService)
{
}
~ClosingLayerSecret()
{
mClosingService = nullptr;
}
RefPtr<mozilla::net::ClosingService> mClosingService;
};
namespace mozilla {
namespace net {
static PRIOMethods sTcpUdpPRCloseLayerMethods;
static PRIOMethods *sTcpUdpPRCloseLayerMethodsPtr = nullptr;
static PRDescIdentity sTcpUdpPRCloseLayerId;
static PRStatus
TcpUdpPRCloseLayerClose(PRFileDesc *aFd)
{
if (!aFd) {
return PR_FAILURE;
}
PRFileDesc* layer = PR_PopIOLayer(aFd, PR_TOP_IO_LAYER);
MOZ_RELEASE_ASSERT(layer &&
layer->identity == sTcpUdpPRCloseLayerId,
"Closing Layer not on top of stack");
ClosingLayerSecret *closingLayerSecret =
reinterpret_cast<ClosingLayerSecret *>(layer->secret);
PRStatus status = PR_SUCCESS;
if (aFd) {
// If this is called during shutdown do not call ..method->close(fd) and
// let it leak.
if (gIOService->IsNetTearingDown()) {
// If the ClosingService layer is the first layer above PR_NSPR_IO_LAYER
// we are not going to leak anything, but the PR_Close will not be called.
PR_Free(aFd);
} else if (closingLayerSecret->mClosingService) {
closingLayerSecret->mClosingService->PostRequest(aFd);
} else {
// Socket is created before closing service has been started or there was
// a problem with starting it.
PR_Close(aFd);
}
}
layer->secret = nullptr;
layer->dtor(layer);
delete closingLayerSecret;
return status;
}
ClosingService* ClosingService::sInstance = nullptr;
ClosingService::ClosingService()
: mShutdown(false)
, mMonitor("ClosingService.mMonitor")
{
MOZ_ASSERT(!sInstance,
"multiple ClosingService instances!");
}
// static
void
ClosingService::Start()
{
if (!sTcpUdpPRCloseLayerMethodsPtr) {
sTcpUdpPRCloseLayerId = PR_GetUniqueIdentity("TCP and UDP PRClose layer");
PR_ASSERT(PR_INVALID_IO_LAYER != sTcpUdpPRCloseLayerId);
sTcpUdpPRCloseLayerMethods = *PR_GetDefaultIOMethods();
sTcpUdpPRCloseLayerMethods.close = TcpUdpPRCloseLayerClose;
sTcpUdpPRCloseLayerMethodsPtr = &sTcpUdpPRCloseLayerMethods;
}
if (!sInstance) {
ClosingService* service = new ClosingService();
if (NS_SUCCEEDED(service->StartInternal())) {
NS_ADDREF(service);
sInstance = service;
} else {
delete service;
}
}
}
nsresult
ClosingService::StartInternal()
{
mThread = PR_CreateThread(PR_USER_THREAD, ThreadFunc, this,
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
PR_JOINABLE_THREAD, 32 * 1024);
if (!mThread) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
// static
nsresult
ClosingService::AttachIOLayer(PRFileDesc *aFd)
{
// We are going to remove ClosingService soon.
// This change is going to turn it off, so ClosingService is not used.
// Bug 1238010.
return NS_OK;
if (!sTcpUdpPRCloseLayerMethodsPtr) {
return NS_OK;
}
PRFileDesc * layer;
PRStatus status;
layer = PR_CreateIOLayerStub(sTcpUdpPRCloseLayerId,
sTcpUdpPRCloseLayerMethodsPtr);
if (!layer) {
return NS_OK;
}
ClosingLayerSecret *secret = new ClosingLayerSecret(sInstance);
layer->secret = reinterpret_cast<PRFilePrivate *>(secret);
status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer);
if (status == PR_FAILURE) {
delete secret;
PR_DELETE(layer);
}
return NS_OK;
}
void
ClosingService::PostRequest(PRFileDesc *aFd)
{
mozilla::MonitorAutoLock mon(mMonitor);
// Check if shutdown is called.
if (mShutdown) {
// Let the socket leak. We are in shutdown and some PRClose can take a long
// time. To prevent shutdown crash (bug 1152046) do not accept sockets any
// more.
// If the ClosingService layer is the first layer above PR_NSPR_IO_LAYER
// we are not going to leak anything, but PR_Close will not be called.
PR_Free(aFd);
return;
}
mQueue.AppendElement(aFd);
if (mQueue.Length() == 1) {
mon.Notify();
}
}
// static
void
ClosingService::Shutdown()
{
MOZ_ASSERT(NS_IsMainThread());
if (sInstance) {
sInstance->ShutdownInternal();
NS_RELEASE(sInstance);
}
}
void
ClosingService::ShutdownInternal()
{
{
mozilla::MonitorAutoLock mon(mMonitor);
if (mShutdown) {
// This should not happen.
return;
}
mShutdown = true;
// If it is waiting on the empty queue, wake it up.
if (mQueue.Length() == 0) {
mon.Notify();
}
}
if (mThread) {
PR_JoinThread(mThread);
mThread = nullptr;
}
}
void
ClosingService::ThreadFunc()
{
PR_SetCurrentThreadName("Closing Service");
#ifdef MOZ_NUWA_PROCESS
if (IsNuwaProcess()) {
NuwaMarkCurrentThread(nullptr, nullptr);
}
#endif
for (;;) {
PRFileDesc *fd;
{
mozilla::MonitorAutoLock mon(mMonitor);
while (!mShutdown && (mQueue.Length() == 0)) {
mon.Wait();
}
if (mShutdown) {
// If we are in shutdown leak the rest of the sockets.
for (uint32_t i = 0; i < mQueue.Length(); i++) {
fd = mQueue[i];
// If the ClosingService layer is the first layer above
// PR_NSPR_IO_LAYER we are not going to leak anything, but PR_Close
// will not be called.
PR_Free(fd);
}
mQueue.Clear();
return;
}
fd = mQueue[0];
mQueue.RemoveElementAt(0);
}
// Leave lock before closing socket. It can block for a long time and in
// case we accidentally attach this layer twice this would cause deadlock.
bool tcp = (PR_GetDescType(PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER)) ==
PR_DESC_SOCKET_TCP);
PRIntervalTime closeStarted = PR_IntervalNow();
fd->methods->close(fd);
// Post telemetry.
if (tcp) {
SendPRCloseTelemetry(closeStarted,
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_NORMAL,
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_SHUTDOWN,
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_CONNECTIVITY_CHANGE,
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_LINK_CHANGE,
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_OFFLINE);
} else {
SendPRCloseTelemetry(closeStarted,
Telemetry::PRCLOSE_UDP_BLOCKING_TIME_NORMAL,
Telemetry::PRCLOSE_UDP_BLOCKING_TIME_SHUTDOWN,
Telemetry::PRCLOSE_UDP_BLOCKING_TIME_CONNECTIVITY_CHANGE,
Telemetry::PRCLOSE_UDP_BLOCKING_TIME_LINK_CHANGE,
Telemetry::PRCLOSE_UDP_BLOCKING_TIME_OFFLINE);
}
}
}
void
ClosingService::SendPRCloseTelemetry(PRIntervalTime aStart,
mozilla::Telemetry::ID aIDNormal,
mozilla::Telemetry::ID aIDShutdown,
mozilla::Telemetry::ID aIDConnectivityChange,
mozilla::Telemetry::ID aIDLinkChange,
mozilla::Telemetry::ID aIDOffline)
{
PRIntervalTime now = PR_IntervalNow();
if (gIOService->IsNetTearingDown()) {
Telemetry::Accumulate(aIDShutdown,
PR_IntervalToMilliseconds(now - aStart));
} else if (PR_IntervalToSeconds(now - gIOService->LastConnectivityChange())
< 60) {
Telemetry::Accumulate(aIDConnectivityChange,
PR_IntervalToMilliseconds(now - aStart));
} else if (PR_IntervalToSeconds(now - gIOService->LastNetworkLinkChange())
< 60) {
Telemetry::Accumulate(aIDLinkChange,
PR_IntervalToMilliseconds(now - aStart));
} else if (PR_IntervalToSeconds(now - gIOService->LastOfflineStateChange())
< 60) {
Telemetry::Accumulate(aIDOffline,
PR_IntervalToMilliseconds(now - aStart));
} else {
Telemetry::Accumulate(aIDNormal,
PR_IntervalToMilliseconds(now - aStart));
}
}
} //namwspacw mozilla
} //namespace net

View File

@ -1,71 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef ClosingService_h__
#define ClosingService_h__
#include "nsTArray.h"
#include "nspr.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Monitor.h"
//-----------------------------------------------------------------------------
// ClosingService
//-----------------------------------------------------------------------------
// A helper class carrying call to PR_Close on FD to a separate thread -
// closingThread. This may be a workaround for shutdown blocks that are caused
// by serial calls to close on UDP and TCP sockets.
// This service is started by nsIOService and also the class adds itself as an
// observer to "xpcom-shutdown-threads" notification where we join the thread
// and remove reference.
// During worktime of the thread the class is also self-referenced,
// since observer service might throw the reference away sooner than the thread
// is actually done.
namespace mozilla {
namespace net {
class ClosingService final
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ClosingService)
ClosingService();
// Attaching this layer on tcp or udp sockets PRClose will be send to the
// closingThread.
static nsresult AttachIOLayer(PRFileDesc *aFd);
static void Start();
static void Shutdown();
void PostRequest(PRFileDesc *aFd);
private:
~ClosingService() {}
nsresult StartInternal();
void ShutdownInternal();
void ThreadFunc();
static void ThreadFunc(void *aClosure)
{ static_cast<ClosingService*>(aClosure)->ThreadFunc(); }
void SendPRCloseTelemetry(PRIntervalTime aStart,
mozilla::Telemetry::ID aIDNormal,
mozilla::Telemetry::ID aIDShutdown,
mozilla::Telemetry::ID aIDConnectivityChange,
mozilla::Telemetry::ID aIDLinkChange,
mozilla::Telemetry::ID aIDOffline);
static ClosingService* sInstance;
Atomic<bool> mShutdown;
nsTArray<PRFileDesc *> mQueue;
mozilla::Monitor mMonitor;
PRThread *mThread;
};
} // namespace net
} // namespace mozilla
#endif // ClosingService_h_

View File

@ -193,7 +193,6 @@ UNIFIED_SOURCES += [
'CaptivePortalService.cpp',
'ChannelDiverterChild.cpp',
'ChannelDiverterParent.cpp',
'ClosingService.cpp',
'Dashboard.cpp',
'EventTokenBucket.cpp',
'LoadContextInfo.cpp',

View File

@ -49,7 +49,6 @@
#include "mozilla/Telemetry.h"
#include "mozilla/net/DNS.h"
#include "CaptivePortalService.h"
#include "ClosingService.h"
#include "ReferrerPolicy.h"
#include "nsContentSecurityManager.h"
#include "nsHttpHandler.h"
@ -65,7 +64,6 @@
using namespace mozilla;
using mozilla::net::IsNeckoChild;
using mozilla::net::ClosingService;
using mozilla::net::CaptivePortalService;
using mozilla::net::gHttpHandler;
@ -260,10 +258,6 @@ nsIOService::Init()
InitializeNetworkLinkService();
// Start the closing service. Actual PR_Close() will be carried out on
// a separate "closing" thread. Start the closing servicee here since this
// point is executed only once per session.
ClosingService::Start();
SetOffline(false);
return NS_OK;
@ -1077,9 +1071,6 @@ nsIOService::SetOffline(bool offline)
DebugOnly<nsresult> rv = mSocketTransportService->Shutdown();
NS_ASSERTION(NS_SUCCEEDED(rv), "socket transport service shutdown failed");
}
if (mShutdown) {
ClosingService::Shutdown();
}
}
mSettingOffline = false;

View File

@ -138,7 +138,7 @@ private:
private:
bool mOffline;
bool mOfflineForProfileChange;
mozilla::Atomic<bool, mozilla::Relaxed> mOfflineForProfileChange;
bool mManageLinkStatus;
bool mConnectivity;
// If true, the connectivity state will be mirrored by IOService.offline
@ -150,7 +150,7 @@ private:
bool mSettingOffline;
bool mSetOfflineValue;
bool mShutdown;
mozilla::Atomic<bool, mozilla::Relaxed> mShutdown;
nsCOMPtr<nsPISocketTransportService> mSocketTransportService;
nsCOMPtr<nsPIDNSService> mDNSService;

View File

@ -6,6 +6,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/UniquePtr.h"
#include "nsIIncrementalDownload.h"
#include "nsIRequestObserver.h"
@ -150,7 +151,7 @@ private:
nsCOMPtr<nsIFile> mDest;
nsCOMPtr<nsIChannel> mChannel;
nsCOMPtr<nsITimer> mTimer;
UniquePtr<char[]> mChunk;
mozilla::UniquePtr<char[]> mChunk;
int32_t mChunkLen;
int32_t mChunkSize;
int32_t mInterval;
@ -712,7 +713,7 @@ nsIncrementalDownload::OnStartRequest(nsIRequest *request,
if (diff < int64_t(mChunkSize))
mChunkSize = uint32_t(diff);
mChunk = MakeUniqueFallible<char[]>(mChunkSize);
mChunk = mozilla::MakeUniqueFallible<char[]>(mChunkSize);
if (!mChunk)
rv = NS_ERROR_OUT_OF_MEMORY;

View File

@ -16,7 +16,6 @@
#include "nsProxyInfo.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "ClosingService.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "plstr.h"
@ -1325,9 +1324,6 @@ nsSocketTransport::InitiateSocket()
// Attach network activity monitor
mozilla::net::NetworkActivityMonitor::AttachIOLayer(fd);
// Attach closing service.
ClosingService::AttachIOLayer(fd);
PRStatus status;
// Make the socket non-blocking...

View File

@ -16,6 +16,7 @@
#include "nsError.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsIOService.h"
#include "prnetdb.h"
#include "prio.h"
#include "nsNetAddr.h"
@ -29,7 +30,6 @@
#include "nsIDNSRecord.h"
#include "nsIDNSService.h"
#include "nsICancelable.h"
#include "ClosingService.h"
#ifdef MOZ_WIDGET_GONK
#include "NetStatistics.h"
@ -332,6 +332,10 @@ nsUDPSocket::TryAttach()
if (!gSocketTransportService)
return NS_ERROR_FAILURE;
if (gIOService->IsNetTearingDown()) {
return NS_ERROR_FAILURE;
}
//
// find out if it is going to be ok to attach another socket to the STS.
// if not then we have to wait for the STS to tell us that it is ok.
@ -581,6 +585,10 @@ nsUDPSocket::InitWithAddress(const NetAddr *aAddr, nsIPrincipal *aPrincipal,
{
NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
if (gIOService->IsNetTearingDown()) {
return NS_ERROR_FAILURE;
}
bool addressReuse = (aOptionalArgc == 1) ? aAddressReuse : true;
//
@ -657,7 +665,6 @@ nsUDPSocket::InitWithAddress(const NetAddr *aAddr, nsIPrincipal *aPrincipal,
// create proxy via NetworkActivityMonitor
NetworkActivityMonitor::AttachIOLayer(mFD);
ClosingService::AttachIOLayer(mFD);
// wait until AsyncListen is called before polling the socket for
// client connections.

View File

@ -36,7 +36,6 @@
#include "prerr.h"
#include "prerror.h"
#include "NetworkActivityMonitor.h"
#include "ClosingService.h"
using namespace mozilla::net;
@ -145,9 +144,6 @@ void ARTPConnection::MakePortPair(
NetworkActivityMonitor::AttachIOLayer(*rtpSocket);
NetworkActivityMonitor::AttachIOLayer(*rtcpSocket);
ClosingService::AttachIOLayer(*rtpSocket);
ClosingService::AttachIOLayer(*rtcpSocket);
// Reduce the chance of using duplicate port numbers.
srand(time(NULL));
// rand() * 1000 may overflow int type, use long long.

View File

@ -34,7 +34,6 @@
#include "prnetdb.h"
#include "prerr.h"
#include "NetworkActivityMonitor.h"
#include "ClosingService.h"
using namespace mozilla::net;
@ -110,7 +109,6 @@ void ARTPSession::MakeUDPSocket(PRFileDesc **s, unsigned port) {
}
NetworkActivityMonitor::AttachIOLayer(*s);
ClosingService::AttachIOLayer(*s);
PRNetAddr addr;
addr.inet.family = PR_AF_INET;

View File

@ -31,7 +31,6 @@
#include <media/stagefright/MetaData.h>
#include <utils/ByteOrder.h>
#include "ClosingService.h"
#include "NetworkActivityMonitor.h"
using namespace mozilla::net;
@ -65,7 +64,6 @@ ARTPWriter::ARTPWriter(int fd)
}
NetworkActivityMonitor::AttachIOLayer(mSocket);
ClosingService::AttachIOLayer(mSocket);
mRTPAddr.inet.family = PR_AF_INET;

View File

@ -34,7 +34,6 @@
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsNetCID.h"
#include "ClosingService.h"
#include "nsIServiceManager.h"
#include "nsICryptoHash.h"
@ -284,7 +283,6 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
}
NetworkActivityMonitor::AttachIOLayer(mSocket);
ClosingService::AttachIOLayer(mSocket);
MakeSocketBlocking(mSocket, false);

View File

@ -27,7 +27,6 @@
#include "prnetdb.h"
#include "prerr.h"
#include "NetworkActivityMonitor.h"
#include "ClosingService.h"
using namespace mozilla::net;
@ -45,7 +44,6 @@ UDPPusher::UDPPusher(const char *filename, unsigned port)
}
NetworkActivityMonitor::AttachIOLayer(mSocket);
ClosingService::AttachIOLayer(mSocket);
PRNetAddr addr;
addr.inet.family = PR_AF_INET;

View File

@ -149,7 +149,6 @@ void nsNotifyAddrListener::checkLink(void)
void nsNotifyAddrListener::OnNetlinkMessage(int aNetlinkSocket)
{
struct nlmsghdr *nlh;
struct rtmsg *route_entry;
// The buffer size below, (4095) was chosen partly based on testing and
// partly on existing sample source code using this size. It needs to be
@ -158,7 +157,6 @@ void nsNotifyAddrListener::OnNetlinkMessage(int aNetlinkSocket)
struct rtattr *attr;
int attr_len;
const struct ifaddrmsg* newifam;
bool link_local;
// inspired by check_pf.c.
nsAutoPtr<char> addr;
@ -181,131 +179,79 @@ void nsNotifyAddrListener::OnNetlinkMessage(int aNetlinkSocket)
break;
}
switch(nlh->nlmsg_type) {
case RTM_DELROUTE:
LOG(("nsNotifyAddrListener::OnNetlinkMessage deleted route"));
case RTM_NEWROUTE:
LOG(("nsNotifyAddrListener::OnNetlinkMessage new/deleted route"));
// Get the route data
route_entry = static_cast<struct rtmsg *>(NLMSG_DATA(nlh));
LOG(("nsNotifyAddrListener::OnNetlinkMessage: new/deleted address\n"));
newifam = reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(nlh));
// We are just intrested in main routing table
if (route_entry->rtm_table != RT_TABLE_MAIN)
continue;
if ((route_entry->rtm_family != AF_INET) &&
(route_entry->rtm_family != AF_INET6)) {
continue;
}
attr = (struct rtattr *) RTM_RTA(route_entry);
attr_len = RTM_PAYLOAD(nlh);
link_local = false;
/* Loop through all attributes */
for ( ; RTA_OK(attr, attr_len); attr = RTA_NEXT(attr, attr_len)) {
if (attr->rta_type == RTA_GATEWAY) {
if (route_entry->rtm_family == AF_INET6) {
unsigned char *g = (unsigned char *)
RTA_DATA(attr);
if ((g[0] == 0xFE) && ((g[1] & 0xc0) == 0x80)) {
link_local = true;
break;
}
}
}
}
if (!link_local) {
LOG(("OnNetlinkMessage: route update\n"));
networkChange = true;
} else {
LOG(("OnNetlinkMessage: ignored link-local route update\n"));
}
break;
case RTM_DELADDR:
LOG(("nsNotifyAddrListener::OnNetlinkMessage deleted address"));
case RTM_NEWADDR:
LOG(("nsNotifyAddrListener::OnNetlinkMessage: new/deleted address"
"\n"));
newifam = reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(nlh));
if ((newifam->ifa_family != AF_INET) &&
(newifam->ifa_family != AF_INET6)) {
continue;
}
attr = IFA_RTA (newifam);
attr_len = IFA_PAYLOAD (nlh);
for (;attr_len && RTA_OK (attr, attr_len);
attr = RTA_NEXT (attr, attr_len)) {
if (attr->rta_type == IFA_ADDRESS) {
if (newifam->ifa_family == AF_INET) {
struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
addr = (char*)malloc(INET_ADDRSTRLEN);
inet_ntop(AF_INET, in, addr.get(), INET_ADDRSTRLEN);
} else {
struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
addr = (char*)malloc(INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, in, addr.get(), INET6_ADDRSTRLEN);
}
} else if (attr->rta_type == IFA_LOCAL) {
if (newifam->ifa_family == AF_INET) {
struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
localaddr = (char*)malloc(INET_ADDRSTRLEN);
inet_ntop(AF_INET, in, localaddr.get(), INET_ADDRSTRLEN);
} else {
struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
localaddr = (char*)malloc(INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, in, localaddr.get(), INET6_ADDRSTRLEN);
}
}
}
if (localaddr) {
addr = localaddr;
}
if (!addr) {
continue;
}
if (nlh->nlmsg_type == RTM_NEWADDR) {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: a new address "
"- %s.", addr.get()));
struct ifaddrmsg* ifam;
nsCString addrStr;
addrStr.Assign(addr);
if (mAddressInfo.Get(addrStr, &ifam)) {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: the address "
"already known."));
if (memcmp(ifam, newifam, sizeof(struct ifaddrmsg))) {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: but "
"the address info has been changed."));
networkChange = true;
memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
}
} else {
networkChange = true;
ifam = (struct ifaddrmsg*)malloc(sizeof(struct ifaddrmsg));
memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
mAddressInfo.Put(addrStr,ifam);
}
} else {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: an address "
"has been deleted - %s.", addr.get()));
networkChange = true;
nsCString addrStr;
addrStr.Assign(addr);
mAddressInfo.Remove(addrStr);
}
// clean it up.
localaddr = nullptr;
addr = nullptr;
break;
default:
if ((newifam->ifa_family != AF_INET) &&
(newifam->ifa_family != AF_INET6)) {
continue;
}
attr = IFA_RTA (newifam);
attr_len = IFA_PAYLOAD (nlh);
for (;attr_len && RTA_OK (attr, attr_len);
attr = RTA_NEXT (attr, attr_len)) {
if (attr->rta_type == IFA_ADDRESS) {
if (newifam->ifa_family == AF_INET) {
struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
addr = (char*)malloc(INET_ADDRSTRLEN);
inet_ntop(AF_INET, in, addr.get(), INET_ADDRSTRLEN);
} else {
struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
addr = (char*)malloc(INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, in, addr.get(), INET6_ADDRSTRLEN);
}
} else if (attr->rta_type == IFA_LOCAL) {
if (newifam->ifa_family == AF_INET) {
struct in_addr* in = (struct in_addr*)RTA_DATA(attr);
localaddr = (char*)malloc(INET_ADDRSTRLEN);
inet_ntop(AF_INET, in, localaddr.get(), INET_ADDRSTRLEN);
} else {
struct in6_addr* in = (struct in6_addr*)RTA_DATA(attr);
localaddr = (char*)malloc(INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, in, localaddr.get(), INET6_ADDRSTRLEN);
}
}
}
if (localaddr) {
addr = localaddr;
}
if (!addr) {
continue;
}
if (nlh->nlmsg_type == RTM_NEWADDR) {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: a new address "
"- %s.", addr.get()));
struct ifaddrmsg* ifam;
nsCString addrStr;
addrStr.Assign(addr);
if (mAddressInfo.Get(addrStr, &ifam)) {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: the address "
"already known."));
if (memcmp(ifam, newifam, sizeof(struct ifaddrmsg))) {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: but "
"the address info has been changed."));
networkChange = true;
memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
}
} else {
networkChange = true;
ifam = (struct ifaddrmsg*)malloc(sizeof(struct ifaddrmsg));
memcpy(ifam, newifam, sizeof(struct ifaddrmsg));
mAddressInfo.Put(addrStr,ifam);
}
} else {
LOG(("nsNotifyAddrListener::OnNetlinkMessage: an address "
"has been deleted - %s.", addr.get()));
networkChange = true;
nsCString addrStr;
addrStr.Assign(addr);
mAddressInfo.Remove(addrStr);
}
// clean it up.
localaddr = nullptr;
addr = nullptr;
}
if (networkChange && mAllowChangedEvent) {
@ -329,8 +275,7 @@ nsNotifyAddrListener::Run()
memset(&addr, 0, sizeof(addr)); // clear addr
addr.nl_family = AF_NETLINK;
addr.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR |
RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE;
addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
if (bind(netlinkSocket, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
// failure!

View File

@ -74,7 +74,6 @@ class DebianBootstrapper(BaseBootstrapper):
MOBILE_ANDROID_COMMON_PACKAGES = [
'zlib1g-dev', # mobile/android requires system zlib.
'openjdk-7-jdk',
'ant',
'wget', # For downloading the Android SDK and NDK.
'libncurses5:i386', # See comments about i386 below.
'libstdc++6:i386',

View File

@ -43,7 +43,6 @@ class FedoraBootstrapper(BaseBootstrapper):
]
self.mobile_android_packages = [
'ant',
'ncurses-devel.i686',
'libstdc++.i686',
'zlib-devel.i686',

View File

@ -334,7 +334,6 @@ class OSXBootstrapper(BaseBootstrapper):
# 1. System packages.
packages = [
('ant', 'ant'),
('brew-cask', 'caskroom/cask/brew-cask'), # For installing Java later.
('wget', 'wget'),
]

View File

@ -0,0 +1,335 @@
/* 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/. */
/* global Accessibility, Components, Log, ElementNotAccessibleError,
XPCOMUtils */
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Log.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'setInterval',
'resource://gre/modules/Timer.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'clearInterval',
'resource://gre/modules/Timer.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'ElementNotAccessibleError',
'chrome://marionette/content/error.js');
this.EXPORTED_SYMBOLS = ['Accessibility'];
/**
* Accessible states used to check element's state from the accessiblity API
* perspective.
*/
const states = {
unavailable: Ci.nsIAccessibleStates.STATE_UNAVAILABLE,
focusable: Ci.nsIAccessibleStates.STATE_FOCUSABLE,
selectable: Ci.nsIAccessibleStates.STATE_SELECTABLE,
selected: Ci.nsIAccessibleStates.STATE_SELECTED
};
var logger = Log.repository.getLogger('Marionette');
/**
* Component responsible for interacting with platform accessibility API. Its
* methods serve as wrappers for testing content and chrome accessibility as
* well as accessibility of user interactions.
*
* @param {Function} getCapabilies
* Session capabilities getter.
*/
this.Accessibility = function Accessibility(getCapabilies = () => {}) {
// A flag indicating whether the accessibility issue should be logged or cause
// an exception. Default: log to stdout.
Object.defineProperty(this, 'strict', {
configurable: true,
get: function() {
let capabilies = getCapabilies();
return !!capabilies.raisesAccessibilityExceptions;
}
});
// An interface for in-process accessibility clients
// Note: we access it lazily to not enable accessibility when it is not needed
Object.defineProperty(this, 'retrieval', {
configurable: true,
get: function() {
delete this.retrieval;
this.retrieval = Cc[
'@mozilla.org/accessibleRetrieval;1'].getService(
Ci.nsIAccessibleRetrieval);
return this.retrieval;
}
});
};
Accessibility.prototype = {
/**
* Number of attempts to get an accessible object for an element. We attempt
* more than once because accessible tree can be out of sync with the DOM tree
* for a short period of time.
* @type {Number}
*/
GET_ACCESSIBLE_ATTEMPTS: 100,
/**
* An interval between attempts to retrieve an accessible object for an
* element.
* @type {Number} ms
*/
GET_ACCESSIBLE_ATTEMPT_INTERVAL: 10,
/**
* Accessible object roles that support some action
* @type Object
*/
ACTIONABLE_ROLES: new Set([
'pushbutton',
'checkbutton',
'combobox',
'key',
'link',
'menuitem',
'check menu item',
'radio menu item',
'option',
'listbox option',
'listbox rich option',
'check rich option',
'combobox option',
'radiobutton',
'rowheader',
'switch',
'slider',
'spinbutton',
'pagetab',
'entry',
'outlineitem'
]),
/**
* Get an accessible object for a DOM element
* @param nsIDOMElement element
* @param Boolean mustHaveAccessible a flag indicating that the element must
* have an accessible object
* @return nsIAccessible object for the element
*/
getAccessibleObject(element, mustHaveAccessible = false) {
return new Promise((resolve, reject) => {
let acc = this.retrieval.getAccessibleFor(element);
if (acc || !mustHaveAccessible) {
// If accessible object is found, return it. If it is not required,
// also resolve.
resolve(acc);
} else {
// If we require an accessible object, we need to poll for it because
// accessible tree might be out of sync with DOM tree for a short time.
let attempts = this.GET_ACCESSIBLE_ATTEMPTS;
let intervalId = setInterval(() => {
let acc = this.retrieval.getAccessibleFor(element);
if (acc || --attempts <= 0) {
clearInterval(intervalId);
if (acc) { resolve(acc); }
else { reject(); }
}
}, this.GET_ACCESSIBLE_ATTEMPT_INTERVAL);
}
}).catch(() => this.error(
'Element does not have an accessible object', element));
},
/**
* Check if the accessible has a role that supports some action
* @param nsIAccessible object
* @return Boolean an indicator of role being actionable
*/
isActionableRole(accessible) {
return this.ACTIONABLE_ROLES.has(
this.retrieval.getStringRole(accessible.role));
},
/**
* Determine if an accessible has at least one action that it supports
* @param nsIAccessible object
* @return Boolean an indicator of supporting at least one accessible action
*/
hasActionCount(accessible) {
return accessible.actionCount > 0;
},
/**
* Determine if an accessible has a valid name
* @param nsIAccessible object
* @return Boolean an indicator that the element has a non empty valid name
*/
hasValidName(accessible) {
return accessible.name && accessible.name.trim();
},
/**
* Check if an accessible has a set hidden attribute
* @param nsIAccessible object
* @return Boolean an indicator that the element has a hidden accessible
* attribute set to true
*/
hasHiddenAttribute(accessible) {
let hidden = false;
try {
hidden = accessible.attributes.getStringProperty('hidden');
} finally {
// If the property is missing, exception will be thrown.
return hidden && hidden === 'true';
}
},
/**
* Verify if an accessible has a given state
* @param nsIAccessible object
* @param Number stateToMatch the state to match
* @return Boolean accessible has a state
*/
matchState(accessible, stateToMatch) {
let state = {};
accessible.getState(state, {});
return !!(state.value & stateToMatch);
},
/**
* Check if an accessible is hidden from the user of the accessibility API
* @param nsIAccessible object
* @return Boolean an indicator that the element is hidden from the user
*/
isHidden(accessible) {
while (accessible) {
if (this.hasHiddenAttribute(accessible)) {
return true;
}
accessible = accessible.parent;
}
return false;
},
/**
* Send an error message or log the error message in the log
* @param String message
* @param DOMElement element that caused an error
*/
error(message, element) {
if (!message) {
return;
}
if (element) {
let {id, tagName, className} = element;
message += `: id: ${id}, tagName: ${tagName}, className: ${className}`;
}
if (this.strict) {
throw new ElementNotAccessibleError(message);
}
logger.error(message);
},
/**
* Check if the element's visible state corresponds to its accessibility API
* visibility
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
* @param Boolean visible element's visibility state
*/
checkVisible(accessible, element, visible) {
if (!accessible) {
return;
}
let hiddenAccessibility = this.isHidden(accessible);
let message;
if (visible && hiddenAccessibility) {
message = 'Element is not currently visible via the accessibility API ' +
'and may not be manipulated by it';
} else if (!visible && !hiddenAccessibility) {
message = 'Element is currently only visible via the accessibility API ' +
'and can be manipulated by it';
}
this.error(message, element);
},
/**
* Check if the element's unavailable accessibility state matches the enabled
* state
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
* @param Boolean enabled element's enabled state
* @param Object container frame and optional ShadowDOM
*/
checkEnabled(accessible, element, enabled, container) {
if (!accessible) {
return;
}
let disabledAccessibility = this.matchState(accessible, states.unavailable);
let explorable = container.frame.document.defaultView.getComputedStyle(
element).getPropertyValue('pointer-events') !== 'none';
let message;
if (!explorable && !disabledAccessibility) {
message = 'Element is enabled but is not explorable via the ' +
'accessibility API';
} else if (enabled && disabledAccessibility) {
message = 'Element is enabled but disabled via the accessibility API';
} else if (!enabled && !disabledAccessibility) {
message = 'Element is disabled but enabled via the accessibility API';
}
this.error(message, element);
},
/**
* Check if it is possible to activate an element with the accessibility API
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
*/
checkActionable(accessible, element) {
if (!accessible) {
return;
}
let message;
if (!this.hasActionCount(accessible)) {
message = 'Element does not support any accessible actions';
} else if (!this.isActionableRole(accessible)) {
message = 'Element does not have a correct accessibility role ' +
'and may not be manipulated via the accessibility API';
} else if (!this.hasValidName(accessible)) {
message = 'Element is missing an accessible name';
} else if (!this.matchState(accessible, states.focusable)) {
message = 'Element is not focusable via the accessibility API';
}
this.error(message, element);
},
/**
* Check if element's selected state corresponds to its accessibility API
* selected state.
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
* @param Boolean selected element's selected state
*/
checkSelected(accessible, element, selected) {
if (!accessible) {
return;
}
if (!this.matchState(accessible, states.selectable)) {
// Element is not selectable via the accessibility API
return;
}
let selectedAccessibility = this.matchState(accessible, states.selected);
let message;
if (selected && !selectedAccessibility) {
message =
'Element is selected but not selected via the accessibility API';
} else if (!selected && selectedAccessibility) {
message =
'Element is not selected but selected via the accessibility API';
}
this.error(message, element);
}
};

View File

@ -23,6 +23,7 @@ XPCOMUtils.defineLazyServiceGetter(
this, "cookieManager", "@mozilla.org/cookiemanager;1", "nsICookieManager2");
Cu.import("chrome://marionette/content/actions.js");
Cu.import("chrome://marionette/content/interactions.js");
Cu.import("chrome://marionette/content/elements.js");
Cu.import("chrome://marionette/content/error.js");
Cu.import("chrome://marionette/content/modal.js");
@ -164,6 +165,8 @@ this.GeckoDriver = function(appName, device, stopSignal, emulator) {
"version": Services.appinfo.version,
};
this.interactions = new Interactions(utils, () => this.sessionCapabilities);
this.mm = globalMessageManager;
this.listener = proxy.toListener(() => this.mm, this.sendAsync.bind(this));
@ -1981,10 +1984,9 @@ GeckoDriver.prototype.clickElement = function(cmd, resp) {
switch (this.context) {
case Context.CHROME:
// click atom fails, fall back to click() action
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
el.click();
yield this.interactions.clickElement({ frame: win },
this.curBrowser.elementManager, id)
break;
case Context.CONTENT:
@ -2082,8 +2084,8 @@ GeckoDriver.prototype.isElementDisplayed = function(cmd, resp) {
switch (this.context) {
case Context.CHROME:
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(id, {frame: win});
resp.body.value = utils.isElementDisplayed(el);
resp.body.value = yield this.interactions.isElementDisplayed(
{frame: win}, this.curBrowser.elementManager, id);
break;
case Context.CONTENT:
@ -2130,8 +2132,8 @@ GeckoDriver.prototype.isElementEnabled = function(cmd, resp) {
case Context.CHROME:
// Selenium atom doesn't quite work here
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(id, {frame: win});
resp.body.value = !(!!el.disabled);
resp.body.value = yield this.interactions.isElementEnabled(
{frame: win}, this.curBrowser.elementManager, id);
break;
case Context.CONTENT:
@ -2153,14 +2155,8 @@ GeckoDriver.prototype.isElementSelected = function(cmd, resp) {
case Context.CHROME:
// Selenium atom doesn't quite work here
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
if (typeof el.checked != "undefined") {
resp.body.value = !!el.checked;
} else if (typeof el.selected != "undefined") {
resp.body.value = !!el.selected;
} else {
resp.body.value = true;
}
resp.body.value = yield this.interactions.isElementSelected(
{ frame: win }, this.curBrowser.elementManager, id);
break;
case Context.CONTENT:
@ -2209,15 +2205,8 @@ GeckoDriver.prototype.sendKeysToElement = function(cmd, resp) {
switch (this.context) {
case Context.CHROME:
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
utils.sendKeysToElement(
win,
el,
value,
() => {},
e => { throw e; },
cmd.id,
true /* ignore visibility check */);
yield this.interactions.sendKeysToElement(
{ frame: win }, this.curBrowser.elementManager, id, value, true);
break;
case Context.CONTENT:
@ -2806,9 +2795,6 @@ GeckoDriver.prototype.sendKeysToDialog = function(cmd, resp) {
win,
loginTextbox,
cmd.parameters.value,
() => {},
e => { throw e; },
this.command_id,
true /* ignore visibility check */);
};

View File

@ -5,12 +5,6 @@
let {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("chrome://marionette/content/error.js");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, 'setInterval',
'resource://gre/modules/Timer.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'clearInterval',
'resource://gre/modules/Timer.jsm');
/**
* The ElementManager manages DOM element references and
@ -30,7 +24,6 @@ XPCOMUtils.defineLazyModuleGetter(this, 'clearInterval',
*/
this.EXPORTED_SYMBOLS = [
"Accessibility",
"elements",
"ElementManager",
"CLASS_NAME",
@ -47,8 +40,8 @@ this.EXPORTED_SYMBOLS = [
const DOCUMENT_POSITION_DISCONNECTED = 1;
const uuidGen = Components.classes["@mozilla.org/uuid-generator;1"]
.getService(Components.interfaces.nsIUUIDGenerator);
const uuidGen = Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator);
this.CLASS_NAME = "class name";
this.SELECTOR = "css selector";
@ -61,192 +54,6 @@ this.XPATH = "xpath";
this.ANON= "anon";
this.ANON_ATTRIBUTE = "anon attribute";
this.Accessibility = function Accessibility() {
// A flag indicating whether the accessibility issue should be logged or cause
// an exception. Default: log to stdout.
this.strict = false;
// An interface for in-process accessibility clients
// Note: we access it lazily to not enable accessibility when it is not needed
Object.defineProperty(this, 'accessibleRetrieval', {
configurable: true,
get: function() {
delete this.accessibleRetrieval;
this.accessibleRetrieval = Components.classes[
'@mozilla.org/accessibleRetrieval;1'].getService(
Components.interfaces.nsIAccessibleRetrieval);
return this.accessibleRetrieval;
}
});
};
Accessibility.prototype = {
/**
* Number of attempts to get an accessible object for an element. We attempt
* more than once because accessible tree can be out of sync with the DOM tree
* for a short period of time.
* @type {Number}
*/
GET_ACCESSIBLE_ATTEMPTS: 100,
/**
* An interval between attempts to retrieve an accessible object for an
* element.
* @type {Number} ms
*/
GET_ACCESSIBLE_ATTEMPT_INTERVAL: 10,
/**
* Accessible object roles that support some action
* @type Object
*/
actionableRoles: new Set([
'pushbutton',
'checkbutton',
'combobox',
'key',
'link',
'menuitem',
'check menu item',
'radio menu item',
'option',
'listbox option',
'listbox rich option',
'check rich option',
'combobox option',
'radiobutton',
'rowheader',
'switch',
'slider',
'spinbutton',
'pagetab',
'entry',
'outlineitem'
]),
/**
* Get an accessible object for a DOM element
* @param nsIDOMElement element
* @param Boolean mustHaveAccessible a flag indicating that the element must
* have an accessible object
* @return nsIAccessible object for the element
*/
getAccessibleObject(element, mustHaveAccessible = false) {
return new Promise((resolve, reject) => {
let acc = this.accessibleRetrieval.getAccessibleFor(element);
if (acc || !mustHaveAccessible) {
// If accessible object is found, return it. If it is not required,
// also resolve.
resolve(acc);
} else {
// If we require an accessible object, we need to poll for it because
// accessible tree might be out of sync with DOM tree for a short time.
let attempts = this.GET_ACCESSIBLE_ATTEMPTS;
let intervalId = setInterval(() => {
let acc = this.accessibleRetrieval.getAccessibleFor(element);
if (acc || --attempts <= 0) {
clearInterval(intervalId);
if (acc) { resolve(acc); }
else { reject(); }
}
}, this.GET_ACCESSIBLE_ATTEMPT_INTERVAL);
}
}).catch(() => this.handleErrorMessage(
'Element does not have an accessible object', element));
},
/**
* Check if the accessible has a role that supports some action
* @param nsIAccessible object
* @return Boolean an indicator of role being actionable
*/
isActionableRole(accessible) {
return this.actionableRoles.has(
this.accessibleRetrieval.getStringRole(accessible.role));
},
/**
* Determine if an accessible has at least one action that it supports
* @param nsIAccessible object
* @return Boolean an indicator of supporting at least one accessible action
*/
hasActionCount(accessible) {
return accessible.actionCount > 0;
},
/**
* Determine if an accessible has a valid name
* @param nsIAccessible object
* @return Boolean an indicator that the element has a non empty valid name
*/
hasValidName(accessible) {
return accessible.name && accessible.name.trim();
},
/**
* Check if an accessible has a set hidden attribute
* @param nsIAccessible object
* @return Boolean an indicator that the element has a hidden accessible
* attribute set to true
*/
hasHiddenAttribute(accessible) {
let hidden;
try {
hidden = accessible.attributes.getStringProperty('hidden');
} finally {
// If the property is missing, exception will be thrown.
return hidden && hidden === 'true';
}
},
/**
* Verify if an accessible has a given state
* @param nsIAccessible object
* @param String stateName name of the state to match
* @return Boolean accessible has a state
*/
matchState(accessible, stateName) {
let stateToMatch = Components.interfaces.nsIAccessibleStates[stateName];
let state = {};
accessible.getState(state, {});
return !!(state.value & stateToMatch);
},
/**
* Check if an accessible is hidden from the user of the accessibility API
* @param nsIAccessible object
* @return Boolean an indicator that the element is hidden from the user
*/
isHidden(accessible) {
while (accessible) {
if (this.hasHiddenAttribute(accessible)) {
return true;
}
accessible = accessible.parent;
}
return false;
},
/**
* Send an error message or log the error message in the log
* @param String message
* @param DOMElement element that caused an error
*/
handleErrorMessage(message, element) {
if (!message) {
return;
}
if (element) {
message += ` -> id: ${element.id}, tagName: ${element.tagName}, className: ${element.className}\n`;
}
if (this.strict) {
throw new ElementNotAccessibleError(message);
}
dump(Date.now() + " Marionette: " + message);
}
};
this.ElementManager = function ElementManager(notSupported) {
this.seenItems = {};
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
@ -291,7 +98,7 @@ ElementManager.prototype = {
}
}
let id = elements.generateUUID();
this.seenItems[id] = Components.utils.getWeakReference(element);
this.seenItems[id] = Cu.getWeakReference(element);
return id;
},
@ -562,7 +369,7 @@ ElementManager.prototype = {
on_success, on_error,
command_id),
100,
Components.interfaces.nsITimer.TYPE_ONE_SHOT);
Ci.nsITimer.TYPE_ONE_SHOT);
}
} else {
if (isArrayLike) {
@ -598,7 +405,7 @@ ElementManager.prototype = {
*/
findByXPath: function EM_findByXPath(root, value, node) {
return root.evaluate(value, node, null,
Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
},
/**
@ -616,7 +423,7 @@ ElementManager.prototype = {
*/
findByXPathAll: function EM_findByXPathAll(root, value, node) {
let values = root.evaluate(value, node, null,
Components.interfaces.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
let elements = [];
let element = values.iterateNext();
while (element) {

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