mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-25 03:05:34 +00:00
Merge mozilla-central to b2g-inbound
This commit is contained in:
commit
963f5afc07
@ -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
|
||||
|
@ -329,7 +329,6 @@ const PanelUI = {
|
||||
evt.initCustomEvent("ViewShowing", true, true, viewNode);
|
||||
viewNode.dispatchEvent(evt);
|
||||
if (evt.defaultPrevented) {
|
||||
aAnchor.open = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -10,9 +10,6 @@
|
||||
"XPCOMUtils": true,
|
||||
"Task": true,
|
||||
|
||||
// Browser window globals.
|
||||
"PanelUI": false,
|
||||
|
||||
// Test harness globals
|
||||
"ExtensionTestUtils": false,
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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");
|
||||
});
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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");
|
||||
|
@ -63,7 +63,6 @@ add_task(function* () {
|
||||
|
||||
clickBrowserAction(extension);
|
||||
yield extension.awaitMessage("popup-finished");
|
||||
yield closeBrowserAction(extension);
|
||||
|
||||
yield extension.unload();
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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; }
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() */
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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__);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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) ||
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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());
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
@ -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_
|
@ -193,7 +193,6 @@ UNIFIED_SOURCES += [
|
||||
'CaptivePortalService.cpp',
|
||||
'ChannelDiverterChild.cpp',
|
||||
'ChannelDiverterParent.cpp',
|
||||
'ClosingService.cpp',
|
||||
'Dashboard.cpp',
|
||||
'EventTokenBucket.cpp',
|
||||
'LoadContextInfo.cpp',
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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...
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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!
|
||||
|
@ -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',
|
||||
|
@ -43,7 +43,6 @@ class FedoraBootstrapper(BaseBootstrapper):
|
||||
]
|
||||
|
||||
self.mobile_android_packages = [
|
||||
'ant',
|
||||
'ncurses-devel.i686',
|
||||
'libstdc++.i686',
|
||||
'zlib-devel.i686',
|
||||
|
@ -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'),
|
||||
]
|
||||
|
335
testing/marionette/accessibility.js
Normal file
335
testing/marionette/accessibility.js
Normal 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);
|
||||
}
|
||||
};
|
@ -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 */);
|
||||
};
|
||||
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user