Merge m-c to b2g-inbound

This commit is contained in:
Carsten "Tomcat" Book 2015-10-20 12:36:00 +02:00
commit 07bd9f4a87
449 changed files with 9818 additions and 3923 deletions

View File

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 1210154 - Update the clang toolchain
Bug 1215696 - Update mp4parse to v0.1.1

View File

@ -257,6 +257,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
"resource:///modules/ReaderParent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
"resource://gre/modules/LoginManagerParent.jsm");
var gInitialPages = [
"about:blank",
"about:newtab",
@ -1194,6 +1197,10 @@ var gBrowserInit = {
}
}, false, true);
gBrowser.addEventListener("InsecureLoginFormsStateChange", function() {
gIdentityHandler.refreshForInsecureLoginForms();
});
let uriToLoad = this._getUriToLoad();
if (uriToLoad && uriToLoad != "about:blank") {
if (uriToLoad instanceof Ci.nsISupportsArray) {
@ -7056,9 +7063,7 @@ var gIdentityHandler = {
}
// Then, update the user interface with the available data.
if (this._identityBox) {
this.refreshIdentityBlock();
}
this.refreshIdentityBlock();
// Handle a location change while the Control Center is focused
// by closing the popup (bug 1207542)
if (shouldHidePopup) {
@ -7071,6 +7076,20 @@ var gIdentityHandler = {
// information we don't want to suddenly change the panel contents.
},
/**
* This is called asynchronously when requested by the Logins module, after
* the insecure login forms state for the page has been updated.
*/
refreshForInsecureLoginForms() {
// Check this._uri because we don't want to refresh the user interface if
// this is called before the first page load in the window for any reason.
if (!this._uri) {
Cu.reportError("Unexpected early call to refreshForInsecureLoginForms.");
return;
}
this.refreshIdentityBlock();
},
/**
* Attempt to provide proper IDN treatment for host names
*/
@ -7107,6 +7126,10 @@ var gIdentityHandler = {
* Updates the identity block user interface with the data from this object.
*/
refreshIdentityBlock() {
if (!this._identityBox) {
return;
}
let icon_label = "";
let tooltip = "";
let icon_country_label = "";
@ -7175,6 +7198,11 @@ var gIdentityHandler = {
this._identityBox.classList.add("weakCipher");
}
}
if (LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser)) {
// Insecure login forms can only be present on "unknown identity"
// pages, either already insecure or with mixed active content loaded.
this._identityBox.classList.add("insecureLoginForms");
}
tooltip = gNavigatorBundle.getString("identity.unknown.tooltip");
}
@ -7212,6 +7240,12 @@ var gIdentityHandler = {
connection = "secure";
}
// Determine if there are insecure login forms.
let loginforms = "secure";
if (LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser)) {
loginforms = "insecure";
}
// Determine the mixed content state.
let mixedcontent = [];
if (this._isMixedPassiveContentLoaded) {
@ -7249,6 +7283,7 @@ var gIdentityHandler = {
for (let id of elementIDs) {
let element = document.getElementById(id);
updateAttribute(element, "connection", connection);
updateAttribute(element, "loginforms", loginforms);
updateAttribute(element, "ciphers", ciphers);
updateAttribute(element, "mixedcontent", mixedcontent);
updateAttribute(element, "isbroken", this._isBroken);

View File

@ -268,7 +268,7 @@ tags = mcb
tags = mcb
[browser_bug906190.js]
tags = mcb
skip-if = buildapp == "mulet" || e10s # Bug 1093642 - test manipulates content and relies on content focus
skip-if = buildapp == "mulet" || e10s || os == "linux" # Bug 1093642 - test manipulates content and relies on content focus, Bug 1212520 - Re-enable on Linux
[browser_mixedContentFromOnunload.js]
tags = mcb
[browser_mixedContentFramesOnHttp.js]
@ -322,6 +322,7 @@ skip-if = e10s # Bug 863514 - no gesture support.
[browser_homeDrop.js]
skip-if = buildapp == 'mulet'
[browser_identity_UI.js]
[browser_insecureLoginForms.js]
[browser_keywordBookmarklets.js]
skip-if = e10s # Bug 1102025 - different principals for the bookmarklet only in e10s mode (unclear if test or 'real' issue)
[browser_keywordSearch.js]

View File

@ -0,0 +1,92 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Load directly from the browser-chrome support files of login tests.
const testUrlPath =
"://example.com/browser/toolkit/components/passwordmgr/test/browser/";
/**
* Waits for the given number of occurrences of InsecureLoginFormsStateChange
* on the given browser element.
*/
function waitForInsecureLoginFormsStateChange(browser, count) {
return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange",
false, () => --count == 0);
}
/**
* Checks the insecure login forms logic for the identity block.
*/
add_task(function* test_simple() {
for (let scheme of ["http", "https"]) {
let tab = gBrowser.addTab(scheme + testUrlPath + "form_basic.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
let { gIdentityHandler } = gBrowser.ownerGlobal;
gIdentityHandler._identityBox.click();
document.getElementById("identity-popup-security-expander").click();
if (scheme == "http") {
let identityBoxImage = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("page-proxy-favicon"), "")
.getPropertyValue("list-style-image");
let securityViewBG = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("identity-popup-securityView"), "")
.getPropertyValue("background-image");
let securityContentBG = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("identity-popup-security-content"), "")
.getPropertyValue("background-image");
is(identityBoxImage,
"url(\"chrome://browser/skin/identity-mixed-active-loaded.svg\")",
"Using expected icon image in the identity block");
is(securityViewBG,
"url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
"Using expected icon image in the Control Center main view");
is(securityContentBG,
"url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
"Using expected icon image in the Control Center subview");
}
// Messages should be visible when the scheme is HTTP, and invisible when
// the scheme is HTTPS.
is(Array.every(document.querySelectorAll("[when-loginforms=insecure]"),
element => !is_hidden(element)),
scheme == "http",
"The relevant messages should visible or hidden.");
gIdentityHandler._identityPopup.hidden = true;
gBrowser.removeTab(tab);
}
});
/**
* Checks that the insecure login forms logic does not regress mixed content
* blocking messages when mixed active content is loaded.
*/
add_task(function* test_mixedcontent() {
yield new Promise(resolve => SpecialPowers.pushPrefEnv({
"set": [["security.mixed_content.block_active_content", false]],
}, resolve));
// Load the page with the subframe in a new tab.
let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// Two events are triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 3),
]);
assertMixedContentBlockingState(browser, { activeLoaded: true,
activeBlocked: false,
passiveLoaded: false });
gBrowser.removeTab(tab);
});

View File

@ -890,6 +890,13 @@ function assertMixedContentBlockingState(tabbrowser, states = {}) {
}
}
if (activeLoaded || activeBlocked || passiveLoaded) {
doc.getElementById("identity-popup-security-expander").click();
is(Array.filter(doc.querySelectorAll("[observes=identity-popup-mcb-learn-more]"),
element => !is_hidden(element)).length, 1,
"The 'Learn more' link should be visible once.");
}
gIdentityHandler._identityPopup.hidden = true;
}

View File

@ -41,6 +41,7 @@
<description when-mixedcontent="active-loaded">&identity.activeLoaded;</description>
<description class="identity-popup-warning-yellow"
when-ciphers="weak">&identity.weakEncryption;</description>
<description when-loginforms="insecure">&identity.insecureLoginForms;</description>
</vbox>
</vbox>
<button id="identity-popup-security-expander"
@ -116,7 +117,11 @@
when-connection="secure secure-ev"/>
<!-- Connection is Not Secure -->
<description when-connection="not-secure">&identity.description.insecure;</description>
<description when-connection="not-secure"
and-when-loginforms="secure">&identity.description.insecure;</description>
<!-- Insecure login forms -->
<description when-loginforms="insecure">&identity.description.insecureLoginForms;</description>
<!-- Weak Cipher -->
<description when-ciphers="weak">&identity.description.weakCipher;</description>
@ -138,8 +143,14 @@
class="identity-popup-warning-yellow">&identity.description.passiveLoaded3; <label observes="identity-popup-mcb-learn-more"/></description>
<!-- Active Mixed Content Blocking Disabled -->
<description when-mixedcontent="active-loaded">&identity.description.activeLoaded;</description>
<description when-mixedcontent="active-loaded">&identity.description.activeLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
<description when-mixedcontent="active-loaded"
and-when-loginforms="secure">&identity.description.activeLoaded;</description>
<description when-mixedcontent="active-loaded"
and-when-loginforms="secure">&identity.description.activeLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
<!-- Show only the first message when there are insecure login forms,
and make sure the Learn More link is included. -->
<description when-mixedcontent="active-loaded"
and-when-loginforms="insecure">&identity.description.activeLoaded; <label observes="identity-popup-mcb-learn-more"/></description>
<!-- Buttons to enable/disable mixed content blocking. -->
<button when-mixedcontent="active-blocked"

View File

@ -1,3 +1,7 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
@ -14,22 +18,11 @@ var {
// WeakMap[Extension -> BrowserAction]
var browserActionMap = new WeakMap();
// WeakMap[Extension -> docshell]
// This map is a cache of the windowless browser that's used to render ImageData
// for the browser_action icon.
var imageRendererMap = new WeakMap();
function browserActionOf(extension)
{
return browserActionMap.get(extension);
}
function makeWidgetId(id)
{
id = id.toLowerCase();
return id.replace(/[^a-z0-9_-]/g, "_");
}
var nextActionId = 0;
// Responsible for the browser_action section of the manifest as well
@ -40,13 +33,25 @@ function BrowserAction(options, extension)
this.id = makeWidgetId(extension.id) + "-browser-action";
this.widget = null;
this.title = new DefaultWeakMap(extension.localize(options.default_title));
this.badgeText = new DefaultWeakMap();
this.badgeBackgroundColor = new DefaultWeakMap();
this.icon = new DefaultWeakMap(options.default_icon);
this.popup = new DefaultWeakMap(options.default_popup);
let title = extension.localize(options.default_title || "");
let popup = extension.localize(options.default_popup || "");
if (popup) {
popup = extension.baseURI.resolve(popup);
}
this.context = null;
this.defaults = {
title: title,
badgeText: "",
badgeBackgroundColor: null,
icon: IconDetails.normalize({ path: options.default_icon }, extension,
null, true),
popup: popup,
};
this.tabContext = new TabContext(tab => Object.create(this.defaults),
extension);
EventEmitter.decorate(this);
}
BrowserAction.prototype = {
@ -62,10 +67,9 @@ BrowserAction.prototype = {
node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional badged-button");
node.setAttribute("constrain-size", "true");
this.updateTab(null, node);
this.updateButton(node, this.defaults);
let tabbrowser = document.defaultView.gBrowser;
tabbrowser.tabContainer.addEventListener("TabSelect", this);
node.addEventListener("command", event => {
let tab = tabbrowser.selectedTab;
@ -80,155 +84,58 @@ BrowserAction.prototype = {
return node;
},
});
this.tabContext.on("tab-select",
(evt, tab) => { this.updateWindow(tab.ownerDocument.defaultView); })
this.widget = widget;
},
handleEvent(event) {
if (event.type == "TabSelect") {
let window = event.target.ownerDocument.defaultView;
let tabbrowser = window.gBrowser;
let instance = CustomizableUI.getWidget(this.id).forWindow(window);
if (instance) {
this.updateTab(tabbrowser.selectedTab, instance.node);
}
}
},
togglePopup(node, popupResource) {
let popupURL = this.extension.baseURI.resolve(popupResource);
let document = node.ownerDocument;
let panel = document.createElement("panel");
panel.setAttribute("class", "browser-action-panel");
panel.setAttribute("type", "arrow");
panel.setAttribute("flip", "slide");
node.appendChild(panel);
panel.addEventListener("popuphidden", () => {
this.context.unload();
this.context = null;
panel.remove();
});
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);
let loadListener = () => {
panel.removeEventListener("load", loadListener);
this.context = new ExtensionPage(this.extension, {
type: "popup",
contentWindow: browser.contentWindow,
uri: Services.io.newURI(popupURL, null, null),
docShell: browser.docShell,
});
GlobalManager.injectInDocShell(browser.docShell, this.extension, this.context);
browser.setAttribute("src", popupURL);
let contentLoadListener = () => {
browser.removeEventListener("load", contentLoadListener);
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];
}
let window = document.defaultView;
width /= window.devicePixelRatio;
height /= window.devicePixelRatio;
width = Math.min(width, 800);
height = Math.min(height, 800);
browser.setAttribute("width", width);
browser.setAttribute("height", height);
let anchor = document.getAnonymousElementByAttribute(node, "class", "toolbarbutton-icon");
panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
};
browser.addEventListener("load", contentLoadListener, true);
};
panel.addEventListener("load", loadListener);
openPanel(node, popupResource, this.extension);
},
// Initialize the toolbar icon and popup given that |tab| is the
// current tab and |node| is the CustomizableUI node. Note: |tab|
// will be null if we don't know the current tab yet (during
// initialization).
updateTab(tab, node) {
let window = node.ownerDocument.defaultView;
let title = this.getProperty(tab, "title");
if (title) {
node.setAttribute("tooltiptext", title);
node.setAttribute("label", title);
// Update the toolbar button |node| with the tab context data
// in |tabData|.
updateButton(node, tabData) {
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");
}
let badgeText = this.badgeText.get(tab);
if (badgeText) {
node.setAttribute("badge", badgeText);
if (tabData.badgeText) {
node.setAttribute("badge", tabData.badgeText);
} else {
node.removeAttribute("badge");
}
function toHex(n) {
return Math.floor(n / 16).toString(16) + (n % 16).toString(16);
}
let badgeNode = node.ownerDocument.getAnonymousElementByAttribute(node,
'class', 'toolbarbutton-badge');
if (badgeNode) {
let color = this.badgeBackgroundColor.get(tab);
let color = tabData.badgeBackgroundColor;
if (Array.isArray(color)) {
color = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
}
badgeNode.style.backgroundColor = color;
badgeNode.style.backgroundColor = color || "";
}
let iconURL = this.getIcon(tab, node);
let iconURL = IconDetails.getURL(
tabData.icon, node.ownerDocument.defaultView, this.extension);
node.setAttribute("image", iconURL);
},
// Note: tab is allowed to be null here.
getIcon(tab, node) {
let icon = this.icon.get(tab);
let url;
if (typeof(icon) != "object") {
url = icon;
} else {
let window = node.ownerDocument.defaultView;
let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
let res = {value: 1}
utils.getResolution(res);
let size = res.value == 1 ? 19 : 38;
url = icon[size];
}
if (url) {
return this.extension.baseURI.resolve(url);
} else {
return "chrome://browser/content/extension.svg";
}
},
// Update the toolbar button for a given window.
updateWindow(window) {
let tab = window.gBrowser ? window.gBrowser.selectedTab : null;
let node = CustomizableUI.getWidget(this.id).forWindow(window).node;
this.updateTab(tab, node);
let widget = this.widget.forWindow(window);
if (widget) {
let tab = window.gBrowser.selectedTab;
this.updateButton(widget.node, this.tabContext.get(tab));
}
},
// Update the toolbar button when the extension changes the icon,
@ -240,12 +147,8 @@ BrowserAction.prototype = {
this.updateWindow(tab.ownerDocument.defaultView);
}
} else {
let e = Services.wm.getEnumerator("navigator:browser");
while (e.hasMoreElements()) {
let window = e.getNext();
if (window.gBrowser) {
this.updateWindow(window);
}
for (let window of WindowListManager.browserWindows()) {
this.updateWindow(window);
}
}
},
@ -253,30 +156,31 @@ BrowserAction.prototype = {
// tab is allowed to be null.
// prop should be one of "icon", "title", "badgeText", "popup", or "badgeBackgroundColor".
setProperty(tab, prop, value) {
this[prop].set(tab, value);
if (tab == null) {
this.defaults[prop] = value;
} else {
this.tabContext.get(tab)[prop] = value;
}
this.updateOnChange(tab);
},
// tab is allowed to be null.
// prop should be one of "title", "badgeText", "popup", or "badgeBackgroundColor".
getProperty(tab, prop) {
return this[prop].get(tab);
if (tab == null) {
return this.defaults[prop];
} else {
return this.tabContext.get(tab)[prop];
}
},
shutdown() {
let widget = CustomizableUI.getWidget(this.id);
for (let instance of widget.instances) {
let window = instance.node.ownerDocument.defaultView;
let tabbrowser = window.gBrowser;
tabbrowser.tabContainer.removeEventListener("TabSelect", this);
}
this.tabContext.shutdown();
CustomizableUI.destroyWidget(this.id);
},
};
EventEmitter.decorate(BrowserAction.prototype);
extensions.on("manifest_browser_action", (type, directive, extension, manifest) => {
let browserAction = new BrowserAction(manifest.browser_action, extension);
browserAction.build();
@ -288,37 +192,8 @@ extensions.on("shutdown", (type, extension) => {
browserActionMap.get(extension).shutdown();
browserActionMap.delete(extension);
}
imageRendererMap.delete(extension);
});
function convertImageDataToPNG(extension, imageData)
{
let webNav = imageRendererMap.get(extension);
if (!webNav) {
webNav = Services.appShell.createWindowlessBrowser(false);
let principal = Services.scriptSecurityManager.createCodebasePrincipal(extension.baseURI,
{addonId: extension.id});
let interfaceRequestor = webNav.QueryInterface(Ci.nsIInterfaceRequestor);
let docShell = interfaceRequestor.getInterface(Ci.nsIDocShell);
GlobalManager.injectInDocShell(docShell, extension, null);
docShell.createAboutBlankContentViewer(principal);
}
let document = webNav.document;
let canvas = document.createElement("canvas");
canvas.width = imageData.width;
canvas.height = imageData.height;
canvas.getContext("2d").putImageData(imageData, 0, 0);
let url = canvas.toDataURL("image/png");
canvas.remove();
return url;
}
extensions.registerAPI((extension, context) => {
return {
browserAction: {
@ -346,12 +221,8 @@ extensions.registerAPI((extension, context) => {
setIcon: function(details, callback) {
let tab = details.tabId ? TabManager.getTab(details.tabId) : null;
if (details.imageData) {
let url = convertImageDataToPNG(extension, details.imageData);
browserActionOf(extension).setProperty(tab, "icon", url);
} else {
browserActionOf(extension).setProperty(tab, "icon", details.path);
}
let icon = IconDetails.normalize(details, extension, context);
browserActionOf(extension).setProperty(tab, "icon", icon);
},
setBadgeText: function(details) {
@ -367,7 +238,13 @@ extensions.registerAPI((extension, context) => {
setPopup: function(details) {
let tab = details.tabId ? TabManager.getTab(details.tabId) : null;
browserActionOf(extension).setProperty(tab, "popup", details.popup);
// Note: Chrome resolves arguments to setIcon relative to the calling
// context, but resolves arguments to setPopup relative to the extension
// root.
// For internal consistency, we currently resolve both relative to the
// calling context.
let url = details.popup && context.uri.resolve(details.popup);
browserActionOf(extension).setProperty(tab, "popup", url);
},
getPopup: function(details, callback) {

View File

@ -0,0 +1,246 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
EventManager,
DefaultWeakMap,
ignoreEvent,
runSafe,
} = ExtensionUtils;
// WeakMap[Extension -> PageAction]
var pageActionMap = new WeakMap();
// Handles URL bar icons, including the |page_action| manifest entry
// and associated API.
function PageAction(options, extension)
{
this.extension = extension;
this.id = makeWidgetId(extension.id) + "-page-action";
let title = extension.localize(options.default_title || "");
let popup = extension.localize(options.default_popup || "");
if (popup) {
popup = extension.baseURI.resolve(popup);
}
this.defaults = {
show: false,
title: title,
icon: IconDetails.normalize({ path: options.default_icon }, extension,
null, true),
popup: popup && extension.baseURI.resolve(popup),
};
this.tabContext = new TabContext(tab => Object.create(this.defaults),
extension);
this.tabContext.on("location-change", this.handleLocationChange.bind(this));
// WeakMap[ChromeWindow -> <xul:image>]
this.buttons = new WeakMap();
EventEmitter.decorate(this);
}
PageAction.prototype = {
// Returns the value of the property |prop| for the given tab, where
// |prop| is one of "show", "title", "icon", "popup".
getProperty(tab, prop) {
return this.tabContext.get(tab)[prop];
},
// Sets the value of the property |prop| for the given tab to the
// given value, symmetrically to |getProperty|.
//
// If |tab| is currently selected, updates the page action button to
// reflect the new value.
setProperty(tab, prop, value) {
this.tabContext.get(tab)[prop] = value;
if (tab.selected) {
this.updateButton(tab.ownerDocument.defaultView);
}
},
// Updates the page action button in the given window to reflect the
// properties of the currently selected tab:
//
// Updates "tooltiptext" and "aria-label" to match "title" property.
// Updates "image" to match the "icon" property.
// Shows or hides the icon, based on the "show" property.
updateButton(window) {
let tabData = this.tabContext.get(window.gBrowser.selectedTab);
if (!(tabData.show || this.buttons.has(window))) {
// Don't bother creating a button for a window until it actually
// needs to be shown.
return;
}
let button = this.getButton(window);
if (tabData.show) {
// Update the title and icon only if the button is visible.
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);
}
button.hidden = !tabData.show;
},
// Create an |image| node and add it to the |urlbar-icons|
// container in the given window.
addButton(window) {
let document = window.document;
let button = document.createElement("image");
button.id = this.id;
button.setAttribute("class", "urlbar-icon");
button.addEventListener("click", event => {
if (event.button == 0) {
this.handleClick(window);
}
});
document.getElementById("urlbar-icons").appendChild(button);
return button;
},
// Returns the page action button for the given window, creating it if
// it doesn't already exist.
getButton(window) {
if (!this.buttons.has(window)) {
let button = this.addButton(window);
this.buttons.set(window, button);
}
return this.buttons.get(window);
},
// Handles a click event on the page action button for the given
// window.
// If the page action has a |popup| property, a panel is opened to
// that URL. Otherwise, a "click" event is emitted, and dispatched to
// the any click listeners in the add-on.
handleClick(window) {
let tab = window.gBrowser.selectedTab;
let popup = this.tabContext.get(tab).popup;
if (popup) {
openPanel(this.getButton(window), popup, this.extension);
} else {
this.emit("click", tab);
}
},
handleLocationChange(eventType, tab, fromBrowse) {
if (fromBrowse) {
this.tabContext.clear(tab);
}
this.updateButton(tab.ownerDocument.defaultView);
},
shutdown() {
this.tabContext.shutdown();
for (let window of WindowListManager.browserWindows()) {
if (this.buttons.has(window)) {
this.buttons.get(window).remove();
}
}
},
};
PageAction.for = extension => {
return pageActionMap.get(extension);
};
extensions.on("manifest_page_action", (type, directive, extension, manifest) => {
let pageAction = new PageAction(manifest.page_action, extension);
pageActionMap.set(extension, pageAction);
});
extensions.on("shutdown", (type, extension) => {
if (pageActionMap.has(extension)) {
pageActionMap.get(extension).shutdown();
pageActionMap.delete(extension);
}
});
extensions.registerAPI((extension, context) => {
return {
pageAction: {
onClicked: new EventManager(context, "pageAction.onClicked", fire => {
let listener = (evt, tab) => {
fire(TabManager.convert(extension, tab));
};
let pageAction = PageAction.for(extension);
pageAction.on("click", listener);
return () => {
pageAction.off("click", listener);
};
}).api(),
show(tabId) {
let tab = TabManager.getTab(tabId);
PageAction.for(extension).setProperty(tab, "show", true);
},
hide(tabId) {
let tab = TabManager.getTab(tabId);
PageAction.for(extension).setProperty(tab, "show", false);
},
setTitle(details) {
let tab = TabManager.getTab(details.tabId);
PageAction.for(extension).setProperty(tab, "title", details.title);
},
getTitle(details, callback) {
let tab = TabManager.getTab(details.tabId);
let title = PageAction.for(extension).getProperty(tab, "title");
runSafe(context, callback, title);
},
setIcon(details, callback) {
let tab = TabManager.getTab(details.tabId);
let icon = IconDetails.normalize(details, extension, context);
PageAction.for(extension).setProperty(tab, "icon", icon);
},
setPopup(details) {
let tab = TabManager.getTab(details.tabId);
// Note: Chrome resolves arguments to setIcon relative to the calling
// context, but resolves arguments to setPopup relative to the extension
// root.
// For internal consistency, we currently resolve both relative to the
// calling context.
let url = details.popup && context.uri.resolve(details.popup);
PageAction.for(extension).setProperty(tab, "popup", url);
},
getPopup(details, callback) {
let tab = TabManager.getTab(details.tabId);
let popup = PageAction.for(extension).getProperty(tab, "popup");
runSafe(context, callback, popup);
},
}
};
});

View File

@ -1,3 +1,7 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
@ -10,6 +14,239 @@ var {
// modules. All of the code is installed on |global|, which is a scope
// shared among the different ext-*.js scripts.
// Manages icon details for toolbar buttons in the |pageAction| and
// |browserAction| APIs.
global.IconDetails = {
// Accepted icon sizes.
SIZES: ["19", "38"],
// Normalizes the various acceptable input formats into an object
// with two properties, "19" and "38", containing icon URLs.
normalize(details, extension, context=null, localize=false) {
let result = {};
if (details.imageData) {
let imageData = details.imageData;
if (imageData instanceof Cu.getGlobalForObject(imageData).ImageData) {
imageData = {"19": imageData};
}
for (let size of this.SIZES) {
if (size in imageData) {
result[size] = this.convertImageDataToPNG(imageData[size], context);
}
}
}
if (details.path) {
let path = details.path;
if (typeof path != "object") {
path = {"19": path};
}
let baseURI = context ? context.uri : extension.baseURI;
for (let size of this.SIZES) {
if (size in path) {
let url = path[size];
if (localize) {
url = extension.localize(url);
}
url = baseURI.resolve(path[size]);
// The Chrome documentation specifies these parameters as
// relative paths. We currently accept absolute URLs as well,
// which means we need to check that the extension is allowed
// to load them.
try {
Services.scriptSecurityManager.checkLoadURIStrWithPrincipal(
extension.principal, url,
Services.scriptSecurityManager.DISALLOW_SCRIPT);
} catch (e if !context) {
// If there's no context, it's because we're handling this
// as a manifest directive. Log a warning rather than
// raising an error, but don't accept the URL in any case.
extension.manifestError(`Access to URL '${url}' denied`);
continue;
}
result[size] = url;
}
}
}
return result;
},
// Returns the appropriate icon URL for the given icons object and the
// screen resolution of the given window.
getURL(icons, window, extension) {
const DEFAULT = "chrome://browser/content/extension.svg";
// Use the higher resolution image if we're doing any up-scaling
// for high resolution monitors.
let res = window.devicePixelRatio;
let size = res > 1 ? "38" : "19";
return icons[size] || icons["19"] || icons["38"] || DEFAULT;
},
convertImageDataToPNG(imageData, context) {
let document = context.contentWindow.document;
let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
canvas.width = imageData.width;
canvas.height = imageData.height;
canvas.getContext("2d").putImageData(imageData, 0, 0);
return canvas.toDataURL("image/png");
}
};
global.makeWidgetId = id => {
id = id.toLowerCase();
// FIXME: This allows for collisions.
return id.replace(/[^a-z0-9_-]/g, "_");
}
// 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;
let popupURI = Services.io.newURI(popupURL, null, extension.baseURI);
Services.scriptSecurityManager.checkLoadURIWithPrincipal(
extension.principal, popupURI,
Services.scriptSecurityManager.DISALLOW_SCRIPT);
let panel = document.createElement("panel");
panel.setAttribute("id", makeWidgetId(extension.id) + "-panel");
panel.setAttribute("class", "browser-extension-panel");
panel.setAttribute("type", "arrow");
panel.setAttribute("flip", "slide");
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;
}
let context;
panel.addEventListener("popuphidden", () => {
context.unload();
panel.remove();
});
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);
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);
let contentLoadListener = () => {
browser.removeEventListener("load", contentLoadListener, true);
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];
}
let window = document.defaultView;
width /= window.devicePixelRatio;
height /= window.devicePixelRatio;
width = Math.min(width, 800);
height = Math.min(height, 800);
browser.setAttribute("width", width);
browser.setAttribute("height", height);
panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
};
browser.addEventListener("load", contentLoadListener, true);
};
panel.addEventListener("load", loadListener);
return panel;
}
// Manages tab-specific context data, and dispatching tab select events
// across all windows.
global.TabContext = function TabContext(getDefaults, extension) {
this.extension = extension;
this.getDefaults = getDefaults;
this.tabData = new WeakMap();
AllWindowEvents.addListener("progress", this);
AllWindowEvents.addListener("TabSelect", this);
EventEmitter.decorate(this);
}
TabContext.prototype = {
get(tab) {
if (!this.tabData.has(tab)) {
this.tabData.set(tab, this.getDefaults(tab));
}
return this.tabData.get(tab);
},
clear(tab) {
this.tabData.delete(tab);
},
handleEvent(event) {
if (event.type == "TabSelect") {
let tab = event.target;
this.emit("tab-select", tab);
this.emit("location-change", tab);
}
},
onLocationChange(browser, webProgress, request, locationURI, flags) {
let gBrowser = browser.ownerDocument.defaultView.gBrowser;
if (browser === gBrowser.selectedBrowser) {
let tab = gBrowser.getTabForBrowser(browser);
this.emit("location-change", tab, true);
}
},
shutdown() {
AllWindowEvents.removeListener("progress", this);
AllWindowEvents.removeListener("TabSelect", this);
},
};
// Manages mapping between XUL tabs and extension tab IDs.
global.TabManager = {
_tabs: new WeakMap(),
@ -39,9 +276,7 @@ global.TabManager = {
getTab(tabId) {
// FIXME: Speed this up without leaking memory somehow.
let e = Services.wm.getEnumerator("navigator:browser");
while (e.hasMoreElements()) {
let window = e.getNext();
for (let window of WindowListManager.browserWindows()) {
if (!window.gBrowser) {
continue;
}
@ -132,9 +367,7 @@ global.WindowManager = {
},
getWindow(id) {
let e = Services.wm.getEnumerator("navigator:browser");
while (e.hasMoreElements()) {
let window = e.getNext();
for (let window of WindowListManager.browserWindows(true)) {
if (this.getId(window) == id) {
return window;
}
@ -172,15 +405,25 @@ global.WindowListManager = {
_openListeners: new Set(),
_closeListeners: new Set(),
// Returns an iterator for all browser windows. Unless |includeIncomplete| is
// true, only fully-loaded windows are returned.
*browserWindows(includeIncomplete = false) {
let e = Services.wm.getEnumerator("navigator:browser");
while (e.hasMoreElements()) {
let window = e.getNext();
if (includeIncomplete || window.document.readyState == "complete") {
yield window;
}
}
},
addOpenListener(listener, fireOnExisting = true) {
if (this._openListeners.length == 0 && this._closeListeners.length == 0) {
Services.ww.registerNotification(this);
}
this._openListeners.add(listener);
let e = Services.wm.getEnumerator("navigator:browser");
while (e.hasMoreElements()) {
let window = e.getNext();
for (let window of this.browserWindows(true)) {
if (window.document.readyState != "complete") {
window.addEventListener("load", this);
} else if (fireOnExisting) {
@ -263,7 +506,11 @@ global.AllWindowEvents = {
list.add(listener);
if (needOpenListener) {
WindowListManager.addOpenListener(this.openListener);
WindowListManager.addOpenListener(this.openListener, false);
}
for (let window of WindowListManager.browserWindows()) {
this.addWindowListener(window, type, listener);
}
},
@ -283,9 +530,7 @@ global.AllWindowEvents = {
}
}
let e = Services.wm.getEnumerator("navigator:browser");
while (e.hasMoreElements()) {
let window = e.getNext();
for (let window of WindowListManager.browserWindows()) {
if (type == "progress") {
window.gBrowser.removeTabsProgressListener(listener);
} else {
@ -294,15 +539,19 @@ global.AllWindowEvents = {
}
},
addWindowListener(window, eventType, listener) {
if (eventType == "progress") {
window.gBrowser.addTabsProgressListener(listener);
} else {
window.addEventListener(eventType, listener);
}
},
// Runs whenever the "load" event fires for a new window.
openListener(window) {
for (let [eventType, listeners] of AllWindowEvents._listeners) {
for (let listener of listeners) {
if (eventType == "progress") {
window.gBrowser.addTabsProgressListener(listener);
} else {
window.addEventListener(eventType, listener);
}
this.addWindowListener(window, eventType, listener);
}
}
},

View File

@ -7,6 +7,7 @@ browser.jar:
content/browser/ext-utils.js
content/browser/ext-contextMenus.js
content/browser/ext-browserAction.js
content/browser/ext-pageAction.js
content/browser/ext-tabs.js
content/browser/ext-windows.js
content/browser/ext-bookmarks.js

View File

@ -7,7 +7,10 @@ support-files =
[browser_ext_simple.js]
[browser_ext_currentWindow.js]
[browser_ext_browserAction_simple.js]
[browser_ext_browserAction_icon.js]
[browser_ext_browserAction_pageAction_icon.js]
[browser_ext_browserAction_context.js]
[browser_ext_pageAction_context.js]
[browser_ext_pageAction_popup.js]
[browser_ext_contextMenus.js]
[browser_ext_getViews.js]
[browser_ext_tabs_executeScript.js]

View File

@ -0,0 +1,244 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* testTabSwitchContext() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"browser_action": {
"default_icon": "default.png",
"default_popup": "default.html",
"default_title": "Default Title",
},
"permissions": ["tabs"],
},
background: function () {
var details = [
{ "icon": browser.runtime.getURL("default.png"),
"popup": browser.runtime.getURL("default.html"),
"title": "Default Title",
"badge": "",
"badgeBackgroundColor": null },
{ "icon": browser.runtime.getURL("1.png"),
"popup": browser.runtime.getURL("default.html"),
"title": "Default Title",
"badge": "",
"badgeBackgroundColor": null },
{ "icon": browser.runtime.getURL("2.png"),
"popup": browser.runtime.getURL("2.html"),
"title": "Title 2",
"badge": "2",
"badgeBackgroundColor": [0xff, 0, 0] },
{ "icon": browser.runtime.getURL("1.png"),
"popup": browser.runtime.getURL("default-2.html"),
"title": "Default Title 2",
"badge": "d2",
"badgeBackgroundColor": [0, 0xff, 0] },
{ "icon": browser.runtime.getURL("default-2.png"),
"popup": browser.runtime.getURL("default-2.html"),
"title": "Default Title 2",
"badge": "d2",
"badgeBackgroundColor": [0, 0xff, 0] },
];
var tabs = [];
var tests = [
expect => {
browser.test.log("Initial state, expect default properties.");
expect(details[0]);
},
expect => {
browser.test.log("Change the icon in the current tab. Expect default properties excluding the icon.");
browser.browserAction.setIcon({ tabId: tabs[0], path: "1.png" });
expect(details[1]);
},
expect => {
browser.test.log("Create a new tab. Expect default properties.");
browser.tabs.create({ active: true, url: "about:blank?0" }, tab => {
tabs.push(tab.id);
expect(details[0]);
});
},
expect => {
browser.test.log("Change properties. Expect new properties.");
var tabId = tabs[1];
browser.browserAction.setIcon({ tabId, path: "2.png" });
browser.browserAction.setPopup({ tabId, popup: "2.html" });
browser.browserAction.setTitle({ tabId, title: "Title 2" });
browser.browserAction.setBadgeText({ tabId, text: "2" });
browser.browserAction.setBadgeBackgroundColor({ tabId, color: [0xff, 0, 0] });
expect(details[2]);
},
expect => {
browser.test.log("Navigate to a new page. Expect no changes.");
// TODO: This listener should not be necessary, but the |tabs.update|
// callback currently fires too early in e10s windows.
browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
if (tabId == tabs[1] && changed.url) {
browser.tabs.onUpdated.removeListener(listener);
expect(details[2]);
}
});
browser.tabs.update(tabs[1], { url: "about:blank?1" });
},
expect => {
browser.test.log("Switch back to the first tab. Expect previously set properties.");
browser.tabs.update(tabs[0], { active: true }, () => {
expect(details[1]);
});
},
expect => {
browser.test.log("Change default values, expect those changes reflected.");
browser.browserAction.setIcon({ path: "default-2.png" });
browser.browserAction.setPopup({ popup: "default-2.html" });
browser.browserAction.setTitle({ title: "Default Title 2" });
browser.browserAction.setBadgeText({ text: "d2" });
browser.browserAction.setBadgeBackgroundColor({ color: [0, 0xff, 0] });
expect(details[3]);
},
expect => {
browser.test.log("Switch back to tab 2. Expect former value, unaffected by changes to defaults in previous step.");
browser.tabs.update(tabs[1], { active: true }, () => {
expect(details[2]);
});
},
expect => {
browser.test.log("Delete tab, switch back to tab 1. Expect previous results again.");
browser.tabs.remove(tabs[1], () => {
expect(details[3]);
});
},
expect => {
browser.test.log("Create a new tab. Expect new default properties.");
browser.tabs.create({ active: true, url: "about:blank?2" }, tab => {
tabs.push(tab.id);
expect(details[4]);
});
},
expect => {
browser.test.log("Delete tab.");
browser.tabs.remove(tabs[2], () => {
expect(details[3]);
});
},
];
// Gets the current details of the browser 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 => {
var tabId = tabs[0].id;
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] });
});
}
// Runs the next test in the `tests` array, checks the results,
// and passes control back to the outer test scope.
function nextTest() {
var test = tests.shift();
test(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");
browser.test.assertEq(expecting.badge, details.badge,
"expected value from getBadge");
browser.test.assertEq(String(expecting.badgeBackgroundColor),
String(details.badgeBackgroundColor),
"expected value from getBadgeBackgroundColor");
// 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 browserActionId = makeWidgetId(extension.id) + "-browser-action";
function checkDetails(details) {
let button = document.getElementById(browserActionId);
ok(button, "button exists");
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");
if (details.badge && details.badgeBackgroundColor) {
let badge = button.ownerDocument.getAnonymousElementByAttribute(
button, 'class', 'toolbarbutton-badge');
let badgeColor = window.getComputedStyle(badge).backgroundColor;
let color = details.badgeBackgroundColor;
let expectedColor = `rgb(${color[0]}, ${color[1]}, ${color[2]})`
is(badgeColor, expectedColor, "badge color is correct");
}
// TODO: Popup URL.
}
let awaitFinish = new Promise(resolve => {
extension.onMessage("nextTest", (expecting, testsRemaining) => {
checkDetails(expecting);
if (testsRemaining) {
extension.sendMessage("runNextTest")
} else {
resolve();
}
});
});
yield extension.startup();
yield awaitFinish;
yield extension.unload();
});

View File

@ -1,40 +0,0 @@
add_task(function* () {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"browser_action": {},
"background": {
"page": "background.html",
}
},
files: {
"background.html": `<canvas id="canvas" width="2" height="2">
<script src="background.js"></script>`,
"background.js": function() {
var canvas = document.getElementById("canvas");
var canvasContext = canvas.getContext("2d");
canvasContext.clearRect(0, 0, canvas.width, canvas.height);
canvasContext.fillStyle = "green";
canvasContext.fillRect(0, 0, 1, 1);
var url = canvas.toDataURL("image/png");
var imageData = canvasContext.getImageData(0, 0, canvas.width, canvas.height);
browser.browserAction.setIcon({imageData});
browser.test.sendMessage("imageURL", url);
}
},
});
let [_, url] = yield Promise.all([extension.startup(), extension.awaitMessage("imageURL")]);
let widgetId = makeWidgetId(extension.id) + "-browser-action";
let node = CustomizableUI.getWidget(widgetId).forWindow(window).node;
let image = node.getAttribute("image");
is(image, url, "image is correct");
yield extension.unload();
});

View File

@ -0,0 +1,345 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
// Test that various combinations of icon details specs, for both paths
// and ImageData objects, result in the correct image being displayed in
// all display resolutions.
add_task(function* testDetailsObjects() {
function background() {
function getImageData(color) {
var canvas = document.createElement("canvas");
canvas.width = 2;
canvas.height = 2;
var canvasContext = canvas.getContext("2d");
canvasContext.clearRect(0, 0, canvas.width, canvas.height);
canvasContext.fillStyle = color;
canvasContext.fillRect(0, 0, 1, 1);
return {
url: canvas.toDataURL("image/png"),
imageData: canvasContext.getImageData(0, 0, canvas.width, canvas.height),
};
}
var imageData = {
red: getImageData("red"),
green: getImageData("green"),
};
var iconDetails = [
// Only paths.
{ details: { "path": "a.png" },
resolutions: {
"1": browser.runtime.getURL("data/a.png"),
"2": browser.runtime.getURL("data/a.png"), } },
{ details: { "path": "/a.png" },
resolutions: {
"1": browser.runtime.getURL("a.png"),
"2": browser.runtime.getURL("a.png"), } },
{ details: { "path": { "19": "a.png" } },
resolutions: {
"1": browser.runtime.getURL("data/a.png"),
"2": browser.runtime.getURL("data/a.png"), } },
{ details: { "path": { "38": "a.png" } },
resolutions: {
"1": browser.runtime.getURL("data/a.png"),
"2": browser.runtime.getURL("data/a.png"), } },
{ details: { "path": { "19": "a.png", "38": "a-x2.png" } },
resolutions: {
"1": browser.runtime.getURL("data/a.png"),
"2": browser.runtime.getURL("data/a-x2.png"), } },
// Only ImageData objects.
{ details: { "imageData": imageData.red.imageData },
resolutions: {
"1": imageData.red.url,
"2": imageData.red.url, } },
{ details: { "imageData": { "19": imageData.red.imageData } },
resolutions: {
"1": imageData.red.url,
"2": imageData.red.url, } },
{ details: { "imageData": { "38": imageData.red.imageData } },
resolutions: {
"1": imageData.red.url,
"2": imageData.red.url, } },
{ details: { "imageData": {
"19": imageData.red.imageData,
"38": imageData.green.imageData } },
resolutions: {
"1": imageData.red.url,
"2": imageData.green.url, } },
// Mixed path and imageData objects.
//
// The behavior is currently undefined if both |path| and
// |imageData| specify icons of the same size.
{ details: {
"path": { "19": "a.png" },
"imageData": { "38": imageData.red.imageData } },
resolutions: {
"1": browser.runtime.getURL("data/a.png"),
"2": imageData.red.url, } },
{ details: {
"path": { "38": "a.png" },
"imageData": { "19": imageData.red.imageData } },
resolutions: {
"1": imageData.red.url,
"2": browser.runtime.getURL("data/a.png"), } },
// A path or ImageData object by itself is treated as a 19px icon.
{ details: {
"path": "a.png",
"imageData": { "38": imageData.red.imageData } },
resolutions: {
"1": browser.runtime.getURL("data/a.png"),
"2": imageData.red.url, } },
{ details: {
"path": { "38": "a.png" },
"imageData": imageData.red.imageData, },
resolutions: {
"1": imageData.red.url,
"2": browser.runtime.getURL("data/a.png"), } },
];
// Allow serializing ImageData objects for logging.
ImageData.prototype.toJSON = () => "<ImageData>";
var tabId;
browser.test.onMessage.addListener((msg, test) => {
if (msg != "setIcon") {
browser.test.fail("expecting 'setIcon' message");
}
var details = iconDetails[test.index];
var expectedURL = details.resolutions[test.resolution];
var detailString = JSON.stringify(details);
browser.test.log(`Setting browerAction/pageAction to ${detailString} expecting URL ${expectedURL}`)
browser.browserAction.setIcon(Object.assign({tabId}, details.details));
browser.pageAction.setIcon(Object.assign({tabId}, details.details));
browser.test.sendMessage("imageURL", expectedURL);
});
// Generate a list of tests and resolutions to send back to the test
// context.
//
// This process is a bit convoluted, because the outer test context needs
// to handle checking the button nodes and changing the screen resolution,
// but it can't pass us icon definitions with ImageData objects. This
// shouldn't be a problem, since structured clones should handle ImageData
// objects without issue. Unfortunately, |cloneInto| implements a slightly
// different algorithm than we use in web APIs, and does not handle them
// correctly.
var tests = [];
for (var [idx, icon] of iconDetails.entries()) {
for (var res of Object.keys(icon.resolutions)) {
tests.push({ index: idx, resolution: Number(res) });
}
}
// Sort by resolution, so we don't needlessly switch back and forth
// between each test.
tests.sort(test => test.resolution);
browser.tabs.query({ active: true, currentWindow: true }, tabs => {
tabId = tabs[0].id;
browser.pageAction.show(tabId);
browser.test.sendMessage("ready", tests);
});
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"browser_action": {},
"page_action": {},
"background": {
"page": "data/background.html",
}
},
files: {
"data/background.html": `<script src="background.js"></script>`,
"data/background.js": background,
},
});
const RESOLUTION_PREF = "layout.css.devPixelsPerPx";
registerCleanupFunction(() => {
SpecialPowers.clearUserPref(RESOLUTION_PREF);
});
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
let pageActionId = makeWidgetId(extension.id) + "-page-action";
let [, tests] = yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
for (let test of tests) {
SpecialPowers.setCharPref(RESOLUTION_PREF, String(test.resolution));
is(window.devicePixelRatio, test.resolution, "window has the required resolution");
extension.sendMessage("setIcon", test);
let imageURL = yield extension.awaitMessage("imageURL");
let browserActionButton = document.getElementById(browserActionId);
is(browserActionButton.getAttribute("image"), imageURL, "browser action has the correct image");
let pageActionImage = document.getElementById(pageActionId);
is(pageActionImage.src, imageURL, "page action has the correct image");
}
yield extension.unload();
});
// Test that default icon details in the manifest.json file are handled
// correctly.
add_task(function *testDefaultDetails() {
// TODO: Test localized variants.
let icons = [
"foo/bar.png",
"/foo/bar.png",
{ "19": "foo/bar.png" },
{ "38": "foo/bar.png" },
{ "19": "foo/bar.png", "38": "baz/quux.png" },
];
let expectedURL = new RegExp(String.raw`^moz-extension://[^/]+/foo/bar\.png$`);
for (let icon of icons) {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"browser_action": { "default_icon": icon },
"page_action": { "default_icon": icon },
},
background: function () {
browser.tabs.query({ active: true, currentWindow: true }, tabs => {
var tabId = tabs[0].id;
browser.pageAction.show(tabId);
browser.test.sendMessage("ready");
});
}
});
yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
let pageActionId = makeWidgetId(extension.id) + "-page-action";
let browserActionButton = document.getElementById(browserActionId);
let image = browserActionButton.getAttribute("image");
ok(expectedURL.test(image), `browser action image ${image} matches ${expectedURL}`);
let pageActionImage = document.getElementById(pageActionId);
image = pageActionImage.src;
ok(expectedURL.test(image), `page action image ${image} matches ${expectedURL}`);
yield extension.unload();
let node = document.getElementById(pageActionId);
is(node, undefined, "pageAction image removed from document");
}
});
// Check that attempts to load a privileged URL as an icon image fail.
add_task(function* testSecureURLsDenied() {
// Test URLs passed to setIcon.
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"browser_action": {},
"page_action": {},
},
background: function () {
browser.tabs.query({ active: true, currentWindow: true }, tabs => {
var tabId = tabs[0].id;
var urls = ["chrome://browser/content/browser.xul",
"javascript:true"];
for (var url of urls) {
for (var api of ["pageAction", "browserAction"]) {
try {
browser[api].setIcon({tabId, path: url});
browser.test.fail(`Load of '${url}' succeeded. Expected failure.`);
browser.test.notifyFail("setIcon security tests");
return;
} catch (e) {
// We can't actually inspect the error here, since the
// error object belongs to the privileged scope of the API,
// rather than to the extension scope that calls into it.
// Just assume it's the expected security error, for now.
browser.test.succeed(`Load of '${url}' failed. Expected failure.`);
}
}
}
browser.test.notifyPass("setIcon security tests");
});
},
});
yield extension.startup();
yield extension.awaitFinish();
yield extension.unload();
// Test URLs included in the manifest.
let urls = ["chrome://browser/content/browser.xul",
"javascript:true"];
let matchURLForbidden = url => ({
message: new RegExp(`Loading extension.*Access to.*'${url}' denied`),
});
let messages = [matchURLForbidden(urls[0]),
matchURLForbidden(urls[1]),
matchURLForbidden(urls[0]),
matchURLForbidden(urls[1])];
let waitForConsole = new Promise(resolve => {
// Not necessary in browser-chrome tests, but monitorConsole gripes
// if we don't call it.
SimpleTest.waitForExplicitFinish();
SimpleTest.monitorConsole(resolve, messages);
});
extension = ExtensionTestUtils.loadExtension({
manifest: {
"browser_action": {
"default_icon": {
"19": urls[0],
"38": urls[1],
},
},
"page_action": {
"default_icon": {
"19": urls[0],
"38": urls[1],
},
},
},
});
yield extension.startup();
yield extension.unload();
SimpleTest.endMonitorConsole();
yield waitForConsole;
});

View File

@ -0,0 +1,209 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* testTabSwitchContext() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"page_action": {
"default_icon": "default.png",
"default_popup": "default.html",
"default_title": "Default Title",
},
"permissions": ["tabs"],
},
background: function () {
var details = [
{ "icon": browser.runtime.getURL("default.png"),
"popup": browser.runtime.getURL("default.html"),
"title": "Default Title" },
{ "icon": browser.runtime.getURL("1.png"),
"popup": browser.runtime.getURL("default.html"),
"title": "Default Title" },
{ "icon": browser.runtime.getURL("2.png"),
"popup": browser.runtime.getURL("2.html"),
"title": "Title 2" },
];
var tabs = [];
var tests = [
expect => {
browser.test.log("Initial state. No icon visible.");
expect(null);
},
expect => {
browser.test.log("Show the icon on the first tab, expect default properties.");
browser.pageAction.show(tabs[0]);
expect(details[0]);
},
expect => {
browser.test.log("Change the icon. Expect default properties excluding the icon.");
browser.pageAction.setIcon({ tabId: tabs[0], path: "1.png" });
expect(details[1]);
},
expect => {
browser.test.log("Create a new tab. No icon visible.");
browser.tabs.create({ active: true, url: "about:blank?0" }, tab => {
tabs.push(tab.id);
expect(null);
});
},
expect => {
browser.test.log("Change properties. Expect new properties.");
var tabId = tabs[1];
browser.pageAction.show(tabId);
browser.pageAction.setIcon({ tabId, path: "2.png" });
browser.pageAction.setPopup({ tabId, popup: "2.html" });
browser.pageAction.setTitle({ tabId, title: "Title 2" });
expect(details[2]);
},
expect => {
browser.test.log("Navigate to a new page. Expect icon hidden.");
// TODO: This listener should not be necessary, but the |tabs.update|
// callback currently fires too early in e10s windows.
browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
if (tabId == tabs[1] && changed.url) {
browser.tabs.onUpdated.removeListener(listener);
expect(null);
}
});
browser.tabs.update(tabs[1], { url: "about:blank?1" });
},
expect => {
browser.test.log("Show the icon. Expect default properties again.");
browser.pageAction.show(tabs[1]);
expect(details[0]);
},
expect => {
browser.test.log("Switch back to the first tab. Expect previously set properties.");
browser.tabs.update(tabs[0], { active: true }, () => {
expect(details[1]);
});
},
expect => {
browser.test.log("Hide the icon on tab 2. Switch back, expect hidden.");
browser.pageAction.hide(tabs[1]);
browser.tabs.update(tabs[1], { active: true }, () => {
expect(null);
});
},
expect => {
browser.test.log("Switch back to tab 1. Expect previous results again.");
browser.tabs.remove(tabs[1], () => {
expect(details[1]);
});
},
expect => {
browser.test.log("Hide the icon. Expect hidden.");
browser.pageAction.hide(tabs[0]);
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 => {
var 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() {
var 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();
}
});
}
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 pageActionId = makeWidgetId(extension.id) + "-page-action";
function checkDetails(details) {
let image = 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");
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.
}
}
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();
let node = document.getElementById(pageActionId);
is(node, undefined, "pageAction image removed from document");
});

View File

@ -0,0 +1,215 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
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": {
"page": "data/background.html"
},
"page_action": {
"default_popup": "popup-a.html"
}
},
files: {
"popup-a.html": `<script src="popup-a.js"></script>`,
"popup-a.js": function() {
browser.runtime.sendMessage("from-popup-a");
},
"data/popup-b.html": `<script src="popup-b.js"></script>`,
"data/popup-b.js": function() {
browser.runtime.sendMessage("from-popup-b");
},
"data/background.html": `<script src="background.js"></script>`,
"data/background.js": function() {
var tabId;
var tests = [
() => {
sendClick({ expectEvent: false, expectPopup: "a" });
},
() => {
sendClick({ expectEvent: false, expectPopup: "a" });
},
() => {
browser.pageAction.setPopup({ tabId, popup: "popup-b.html" });
sendClick({ expectEvent: false, expectPopup: "b" });
},
() => {
sendClick({ expectEvent: false, expectPopup: "b" });
},
() => {
browser.pageAction.setPopup({ tabId, popup: "" });
sendClick({ expectEvent: true, expectPopup: null });
},
() => {
sendClick({ expectEvent: true, expectPopup: null });
},
() => {
browser.pageAction.setPopup({ tabId, popup: "/popup-a.html" });
sendClick({ expectEvent: false, expectPopup: "a" });
},
];
var expect = {};
function sendClick({ expectEvent, expectPopup }) {
expect = { event: expectEvent, popup: expectPopup };
browser.test.sendMessage("send-click");
}
browser.runtime.onMessage.addListener(msg => {
if (expect.popup) {
browser.test.assertEq(msg, `from-popup-${expect.popup}`,
"expected popup opened");
} else {
browser.test.fail("unexpected popup");
}
expect.popup = null;
browser.test.sendMessage("next-test");
});
browser.pageAction.onClicked.addListener(() => {
if (expect.event) {
browser.test.succeed("expected click event received");
} else {
browser.test.fail("unexpected click event");
}
expect.event = false;
browser.test.sendMessage("next-test");
});
browser.test.onMessage.addListener((msg) => {
if (msg != "next-test") {
browser.test.fail("Expecting 'next-test' message");
}
if (tests.length) {
var test = tests.shift();
test();
} else {
browser.test.notifyPass("pageaction-tests-done");
}
});
browser.tabs.query({ active: true, currentWindow: true }, tabs => {
tabId = tabs[0].id;
browser.pageAction.show(tabId);
browser.test.sendMessage("next-test");
});
},
},
});
let pageActionId = makeWidgetId(extension.id) + "-page-action";
let panelId = makeWidgetId(extension.id) + "-panel";
extension.onMessage("send-click", () => {
let image = document.getElementById(pageActionId);
let evt = new MouseEvent("click", {});
image.dispatchEvent(evt);
});
extension.onMessage("next-test", Task.async(function* () {
let panel = document.getElementById(panelId);
if (panel) {
yield promisePopupShown(panel);
panel.hidePopup();
panel = document.getElementById(panelId);
is(panel, undefined, "panel successfully removed from document after hiding");
}
extension.sendMessage("next-test");
}));
yield Promise.all([extension.startup(), extension.awaitFinish("pageaction-tests-done")]);
yield extension.unload();
let node = document.getElementById(pageActionId);
is(node, undefined, "pageAction image removed from document");
let panel = document.getElementById(panelId);
is(panel, undefined, "pageAction panel removed from document");
});
add_task(function* testPageActionSecurity() {
const URL = "chrome://browser/content/browser.xul";
let matchURLForbidden = url => ({
message: new RegExp(`Loading extension.*Access to.*'${URL}' denied`),
});
let messages = [/Access to restricted URI denied/,
/Access to restricted URI denied/];
let waitForConsole = new Promise(resolve => {
// Not necessary in browser-chrome tests, but monitorConsole gripes
// if we don't call it.
SimpleTest.waitForExplicitFinish();
SimpleTest.monitorConsole(resolve, messages);
});
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"browser_action": { "default_popup": URL },
"page_action": { "default_popup": URL },
},
background: function () {
browser.tabs.query({ active: true, currentWindow: true }, tabs => {
var tabId = tabs[0].id;
browser.pageAction.show(tabId);
browser.test.sendMessage("ready");
});
},
});
yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
let pageActionId = makeWidgetId(extension.id) + "-page-action";
let browserAction = document.getElementById(browserActionId);
let evt = new CustomEvent("command", {});
browserAction.dispatchEvent(evt);
let pageAction = document.getElementById(pageActionId);
evt = new MouseEvent("click", {});
pageAction.dispatchEvent(evt);
yield extension.unload();
let node = document.getElementById(pageActionId);
is(node, undefined, "pageAction image removed from document");
SimpleTest.endMonitorConsole();
yield waitForConsole;
});

View File

@ -642,6 +642,7 @@ BrowserGlue.prototype = {
ExtensionManagement.registerScript("chrome://browser/content/ext-utils.js");
ExtensionManagement.registerScript("chrome://browser/content/ext-browserAction.js");
ExtensionManagement.registerScript("chrome://browser/content/ext-pageAction.js");
ExtensionManagement.registerScript("chrome://browser/content/ext-contextMenus.js");
ExtensionManagement.registerScript("chrome://browser/content/ext-tabs.js");
ExtensionManagement.registerScript("chrome://browser/content/ext-windows.js");

View File

@ -95,6 +95,18 @@ function handlePCRequest(aSubject, aTopic, aData) {
let { windowID, innerWindowID, callID, isSecure } = aSubject;
let contentWindow = Services.wm.getOuterWindowWithId(windowID);
let mm = getMessageManagerForWindow(contentWindow);
if (!mm) {
// Workaround for Bug 1207784. To use WebRTC, add-ons right now use
// hiddenWindow.mozRTCPeerConnection which is only privileged on OSX. Other
// platforms end up here without a message manager.
// TODO: Remove once there's a better way (1215591).
// Skip permission check in the absence of a message manager.
Services.obs.notifyObservers(null, "PeerConnection:response:allow", callID);
return;
}
if (!contentWindow.pendingPeerConnectionRequests) {
setupPendingListsInitially(contentWindow);
}
@ -107,8 +119,6 @@ function handlePCRequest(aSubject, aTopic, aData) {
documentURI: contentWindow.document.documentURI,
secure: isSecure,
};
let mm = getMessageManagerForWindow(contentWindow);
mm.sendAsyncMessage("rtcpeer:Request", request);
}

View File

@ -937,6 +937,9 @@ toolbar .toolbarbutton-1:-moz-any(@primaryToolbarButtons@) > :-moz-any(.toolbarb
.urlbar-icon {
padding: 0 3px;
/* 16x16 icon with border-box sizing */
width: 22px;
height: 16px;
}
#urlbar-search-footer {
@ -1942,7 +1945,7 @@ toolbarbutton.chevron > .toolbarbutton-icon {
-moz-margin-end: 0 !important;
}
.browser-action-panel > .panel-arrowcontainer > .panel-arrowcontent {
.browser-extension-panel > .panel-arrowcontainer > .panel-arrowcontent {
padding: 0;
}

View File

@ -166,7 +166,7 @@ browser.jar:
skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
#endif
../extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
% override chrome://browser/skin/feeds/audioFeedIcon.png chrome://browser/skin/feeds/feedIcon.png
% override chrome://browser/skin/feeds/audioFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png
% override chrome://browser/skin/feeds/videoFeedIcon.png chrome://browser/skin/feeds/feedIcon.png

View File

@ -1728,6 +1728,9 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button {
.urlbar-icon {
padding: 0 3px;
/* 16x16 icon with border-box sizing */
width: 22px;
height: 16px;
}
#urlbar-search-footer {
@ -2015,7 +2018,6 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
#page-report-button {
list-style-image: url("chrome://browser/skin/urlbar-popup-blocked@2x.png");
-moz-image-region: rect(0, 32px, 32px, 0);
width: 22px;
}
#page-report-button:hover:active,
@ -3617,7 +3619,7 @@ notification[value="loop-sharing-notification"] .messageImage {
padding-right: 0;
}
.browser-action-panel > .panel-arrowcontainer > .panel-arrowcontent {
.browser-extension-panel > .panel-arrowcontainer > .panel-arrowcontent {
padding: 0;
}

View File

@ -275,7 +275,7 @@ browser.jar:
skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
#endif
../extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
% override chrome://browser/skin/feeds/audioFeedIcon.png chrome://browser/skin/feeds/feedIcon.png
% override chrome://browser/skin/feeds/audioFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png
% override chrome://browser/skin/feeds/videoFeedIcon.png chrome://browser/skin/feeds/feedIcon.png

View File

@ -5,7 +5,7 @@
%endif
/* Hide all conditional elements by default. */
:-moz-any([when-connection],[when-mixedcontent],[when-ciphers]) {
:-moz-any([when-connection],[when-mixedcontent],[when-ciphers],[when-loginforms]) {
display: none;
}
@ -15,6 +15,8 @@
#identity-popup[connection=secure] [when-connection~=secure],
#identity-popup[connection=chrome] [when-connection~=chrome],
#identity-popup[connection=file] [when-connection~=file],
/* Show insecure login forms messages when needed. */
#identity-popup[loginforms=insecure] [when-loginforms=insecure],
/* Show weak cipher messages when needed. */
#identity-popup[ciphers=weak] [when-ciphers~=weak],
/* Show mixed content warnings when needed */
@ -28,6 +30,14 @@
display: inherit;
}
/* Hide redundant messages based on insecure login forms presence. */
#identity-popup[loginforms=secure] [and-when-loginforms=insecure] {
display: none;
}
#identity-popup[loginforms=insecure] [and-when-loginforms=secure] {
display: none;
}
/* Hide 'not secure' message in subview when weak cipher or mixed content messages are shown. */
#identity-popup-securityView-body:-moz-any([mixedcontent],[ciphers]) > description[when-connection=not-secure],
/* Hide 'passive-loaded (only)' message when there is mixed passive content loaded and active blocked. */
@ -224,6 +234,8 @@
background-image: url(chrome://browser/skin/controlcenter/conn-degraded.svg);
}
#identity-popup[loginforms=insecure] #identity-popup-securityView,
#identity-popup[loginforms=insecure] #identity-popup-security-content,
#identity-popup[mixedcontent~=active-loaded][isbroken] #identity-popup-securityView,
#identity-popup[mixedcontent~=active-loaded][isbroken] #identity-popup-security-content {
background-image: url(chrome://browser/skin/controlcenter/mcb-disabled.svg);

View File

@ -123,6 +123,7 @@
list-style-image: url(chrome://browser/skin/identity-secure.svg);
}
.insecureLoginForms > #identity-icons > #page-proxy-favicon[pageproxystate="valid"],
.mixedActiveContent > #identity-icons > #page-proxy-favicon[pageproxystate="valid"] {
list-style-image: url(chrome://browser/skin/identity-mixed-active-loaded.svg);
}

View File

@ -1325,6 +1325,9 @@ html|*.urlbar-input:-moz-lwtheme::-moz-placeholder,
.urlbar-icon {
padding: 0 3px;
/* 16x16 icon with border-box sizing */
width: 22px;
height: 16px;
}
.search-go-container {
@ -2802,7 +2805,7 @@ notification[value="loop-sharing-notification"] .messageImage {
%include browser-aero.css
}
.browser-action-panel > .panel-arrowcontainer > .panel-arrowcontent {
.browser-extension-panel > .panel-arrowcontainer > .panel-arrowcontent {
padding: 0;
}

View File

@ -285,7 +285,7 @@ browser.jar:
skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
#endif
../extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
% override chrome://browser/skin/page-livemarks.png chrome://browser/skin/feeds/feedIcon16.png
% override chrome://browser/skin/feeds/audioFeedIcon.png chrome://browser/skin/feeds/feedIcon.png
% override chrome://browser/skin/feeds/audioFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png

View File

@ -31,6 +31,13 @@ To ship chrome files in a JAR, an indented line indicates a file to be packaged:
<jarfile>.jar:
path/in/jar/file_name.xul (source/tree/location/file_name.xul)
The JAR location may be preceded with a base path between square brackets::
[base/path] <jarfile>.jar:
path/in/jar/file_name.xul (source/tree/location/file_name.xul)
In this case, the jar will be directly located under the given ``base/bath``,
while without a base path, it will be under a ``chrome`` directory.
If the JAR manifest and packaged file live in the same directory, the path and
parenthesis can be omitted. In other words, the following two lines are
equivalent::

View File

@ -70,4 +70,5 @@ if CONFIG['GNU_CXX']:
# installing it in dist/lib.
NO_EXPAND_LIBS = True
# We allow warnings for third-party code that can be updated from upstream.
ALLOW_COMPILER_WARNINGS = True

View File

@ -1256,7 +1256,7 @@ endif
libs realchrome:: $(FINAL_TARGET)/chrome
$(call py_action,jar_maker,\
$(QUIET) -j $(FINAL_TARGET)/chrome \
$(QUIET) -d $(FINAL_TARGET) \
$(MAKE_JARS_FLAGS) $(DEFINES) $(ACDEFINES) $(MOZ_DEBUG_DEFINES) \
$(JAR_MANIFEST))

View File

@ -9,6 +9,7 @@ EXPORTS += [
'sqlite3.h',
]
# We allow warnings for third-party code that can be updated from upstream.
ALLOW_COMPILER_WARNINGS = True
if CONFIG['MOZ_FOLD_LIBS']:

View File

@ -480,10 +480,10 @@ Workers.prototype = {
this._updateWorkerList();
},
_onWorkerSelect: function (type, workerActor) {
_onWorkerSelect: function (workerActor) {
DebuggerController.client.attachWorker(workerActor, (response, workerClient) => {
gDevTools.showToolbox(devtools.TargetFactory.forWorker(workerClient),
"jsdebugger", devtools.Toolbox.HostType.WINDOW);
gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
"jsdebugger", Toolbox.HostType.WINDOW);
});
}
};

View File

@ -0,0 +1,43 @@
/* 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/. */
"use strict";
// @TODO 1215606
// Use this assert instead of utils when fixed.
// const { assert } = require("devtools/shared/DevToolsUtils");
const { breakdownEquals, createSnapshot, assert } = require("../utils");
const { actions, snapshotState: states } = require("../constants");
const { takeCensus } = require("./snapshot");
const setBreakdownAndRefresh = exports.setBreakdownAndRefresh = function (heapWorker, breakdown) {
return function *(dispatch, getState) {
// Clears out all stored census data and sets
// the breakdown
dispatch(setBreakdown(breakdown));
let snapshot = getState().snapshots.find(s => s.selected);
// If selected snapshot does not have updated census if the breakdown
// changed, retake the census with new breakdown
if (snapshot && !breakdownEquals(snapshot.breakdown, breakdown)) {
yield dispatch(takeCensus(heapWorker, snapshot));
}
};
};
/**
* Clears out all census data in the snapshots and sets
* a new breakdown.
*
* @param {Breakdown} breakdown
*/
const setBreakdown = exports.setBreakdown = function (breakdown) {
// @TODO 1215606
assert(typeof breakdown === "object" && breakdown.by,
`Breakdowns must be an object with a \`by\` property, attempted to set: ${uneval(breakdown)}`);
return {
type: actions.SET_BREAKDOWN,
breakdown,
}
};

View File

@ -4,5 +4,6 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'breakdown.js',
'snapshot.js',
)

View File

@ -6,7 +6,7 @@
// @TODO 1215606
// Use this assert instead of utils when fixed.
// const { assert } = require("devtools/shared/DevToolsUtils");
const { createSnapshot, assert } = require("../utils");
const { getSnapshot, breakdownEquals, createSnapshot, assert } = require("../utils");
const { actions, snapshotState: states } = require("../constants");
/**
@ -17,19 +17,36 @@ const { actions, snapshotState: states } = require("../constants");
* @param {HeapAnalysesClient}
* @param {Object}
*/
const takeSnapshotAndCensus = exports.takeSnapshotAndCensus = function takeSnapshotAndCensus (front, heapWorker) {
return function *(dispatch, getStore) {
const takeSnapshotAndCensus = exports.takeSnapshotAndCensus = function (front, heapWorker) {
return function *(dispatch, getState) {
let snapshot = yield dispatch(takeSnapshot(front));
yield dispatch(readSnapshot(heapWorker, snapshot));
yield dispatch(takeCensus(heapWorker, snapshot));
};
};
/**
* Selects a snapshot and if the snapshot's census is using a different
* breakdown, take a new census.
*
* @param {HeapAnalysesClient}
* @param {Snapshot}
*/
const selectSnapshotAndRefresh = exports.selectSnapshotAndRefresh = function (heapWorker, snapshot) {
return function *(dispatch, getState) {
dispatch(selectSnapshot(snapshot));
// Attempt to take another census; if the snapshot already is using
// the correct breakdown, this will noop.
yield dispatch(takeCensus(heapWorker, snapshot));
};
};
/**
* @param {MemoryFront}
*/
const takeSnapshot = exports.takeSnapshot = function takeSnapshot (front) {
return function *(dispatch, getStore) {
const takeSnapshot = exports.takeSnapshot = function (front) {
return function *(dispatch, getState) {
let snapshot = createSnapshot();
dispatch({ type: actions.TAKE_SNAPSHOT_START, snapshot });
dispatch(selectSnapshot(snapshot));
@ -49,7 +66,7 @@ const takeSnapshot = exports.takeSnapshot = function takeSnapshot (front) {
* @param {Snapshot} snapshot,
*/
const readSnapshot = exports.readSnapshot = function readSnapshot (heapWorker, snapshot) {
return function *(dispatch, getStore) {
return function *(dispatch, getState) {
// @TODO 1215606
assert(snapshot.state === states.SAVED,
"Should only read a snapshot once");
@ -64,29 +81,42 @@ const readSnapshot = exports.readSnapshot = function readSnapshot (heapWorker, s
* @param {HeapAnalysesClient} heapWorker
* @param {Snapshot} snapshot,
*
* @see {Snapshot} model defined in devtools/client/memory/app.js
* @see {Snapshot} model defined in devtools/client/memory/models.js
* @see `devtools/shared/heapsnapshot/HeapAnalysesClient.js`
* @see `js/src/doc/Debugger/Debugger.Memory.md` for breakdown details
*/
const takeCensus = exports.takeCensus = function takeCensus (heapWorker, snapshot) {
return function *(dispatch, getStore) {
const takeCensus = exports.takeCensus = function (heapWorker, snapshot) {
return function *(dispatch, getState) {
// @TODO 1215606
assert([states.READ, states.SAVED_CENSUS].includes(snapshot.state),
"Can only take census of snapshots in READ or SAVED_CENSUS state");
let breakdown = getStore().breakdown;
dispatch({ type: actions.TAKE_CENSUS_START, snapshot, breakdown });
let census;
let breakdown = getState().breakdown;
let census = yield heapWorker.takeCensus(snapshot.path, { breakdown }, { asTreeNode: true });
dispatch({ type: actions.TAKE_CENSUS_END, snapshot, census });
// If breakdown hasn't changed, don't do anything
if (breakdownEquals(breakdown, snapshot.breakdown)) {
return;
}
// Keep taking a census if the breakdown changes during. Recheck
// that the breakdown used for the census is the same as
// the state's breakdown.
do {
breakdown = getState().breakdown;
dispatch({ type: actions.TAKE_CENSUS_START, snapshot, breakdown });
census = yield heapWorker.takeCensus(snapshot.path, { breakdown }, { asTreeNode: true });
} while (!breakdownEquals(breakdown, getState().breakdown));
dispatch({ type: actions.TAKE_CENSUS_END, snapshot, breakdown, census });
};
};
/**
* @param {Snapshot}
* @see {Snapshot} model defined in devtools/client/memory/app.js
* @see {Snapshot} model defined in devtools/client/memory/models.js
*/
const selectSnapshot = exports.selectSnapshot = function takeSnapshot (snapshot) {
const selectSnapshot = exports.selectSnapshot = function (snapshot) {
return {
type: actions.SELECT_SNAPSHOT,
snapshot

View File

@ -1,59 +1,18 @@
const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const { selectSnapshot, takeSnapshotAndCensus } = require("./actions/snapshot");
const { snapshotState } = require("./constants");
const { selectSnapshotAndRefresh, takeSnapshotAndCensus } = require("./actions/snapshot");
const { setBreakdownAndRefresh } = require("./actions/breakdown");
const { breakdownNameToSpec, getBreakdownDisplayData } = require("./utils");
const Toolbar = createFactory(require("./components/toolbar"));
const List = createFactory(require("./components/list"));
const SnapshotListItem = createFactory(require("./components/snapshot-list-item"));
const HeapView = createFactory(require("./components/heap"));
const stateModel = {
/**
* {MemoryFront}
* Used to communicate with the platform.
*/
front: PropTypes.any,
/**
* {HeapAnalysesClient}
* Used to communicate with the worker that performs analyses on heaps.
*/
heapWorker: PropTypes.any,
/**
* The breakdown object DSL describing how we want
* the census data to be.
* @see `js/src/doc/Debugger/Debugger.Memory.md`
*/
breakdown: PropTypes.object.isRequired,
/**
* {Array<Snapshot>}
* List of references to all snapshots taken
*/
snapshots: PropTypes.arrayOf(PropTypes.shape({
// Unique ID for a snapshot
id: PropTypes.number.isRequired,
// fs path to where the snapshot is stored; used to
// identify the snapshot for HeapAnalysesClient.
path: PropTypes.string,
// Whether or not this snapshot is currently selected.
selected: PropTypes.bool.isRequired,
// Whther or not the snapshot has been read into memory.
// Only needed to do once.
snapshotRead: PropTypes.bool.isRequired,
// State the snapshot is in
// @see ./constants.js
state: PropTypes.oneOf(Object.keys(snapshotState)).isRequired,
// Data of a census breakdown
census: PropTypes.any,
}))
};
const { app: appModel } = require("./models");
const App = createClass({
displayName: "memory-tool",
propTypes: stateModel,
propTypes: appModel,
childContextTypes: {
front: PropTypes.any,
@ -75,17 +34,17 @@ const App = createClass({
dom.div({ id: "memory-tool" }, [
Toolbar({
buttons: [{
className: "take-snapshot",
onClick: () => dispatch(takeSnapshotAndCensus(front, heapWorker))
}]
breakdowns: getBreakdownDisplayData(),
onTakeSnapshotClick: () => dispatch(takeSnapshotAndCensus(front, heapWorker)),
onBreakdownChange: breakdown =>
dispatch(setBreakdownAndRefresh(heapWorker, breakdownNameToSpec(breakdown))),
}),
dom.div({ id: "memory-tool-container" }, [
List({
itemComponent: SnapshotListItem,
items: snapshots,
onClick: snapshot => dispatch(selectSnapshot(snapshot))
onClick: snapshot => dispatch(selectSnapshotAndRefresh(heapWorker, snapshot))
}),
HeapView({

View File

@ -1,6 +1,7 @@
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { getSnapshotStatusText } = require("../utils");
const { snapshotState: states } = require("../constants");
const { snapshot: snapshotModel } = require("../models");
const TAKE_SNAPSHOT_TEXT = "Take snapshot";
/**
@ -14,7 +15,7 @@ const Heap = module.exports = createClass({
propTypes: {
onSnapshotClick: PropTypes.func.isRequired,
snapshot: PropTypes.any,
snapshot: snapshotModel,
},
render() {
@ -22,7 +23,6 @@ const Heap = module.exports = createClass({
let pane;
let census = snapshot ? snapshot.census : null;
let state = snapshot ? snapshot.state : "initial";
let statusText = getSnapshotStatusText(snapshot);
switch (state) {
case "initial":
@ -35,7 +35,8 @@ const Heap = module.exports = createClass({
case states.READING:
case states.READ:
case states.SAVING_CENSUS:
pane = dom.div({ className: "heap-view-panel", "data-state": state }, statusText);
pane = dom.div({ className: "heap-view-panel", "data-state": state },
getSnapshotStatusText(snapshot));
break;
case states.SAVED_CENSUS:
pane = dom.div({ className: "heap-view-panel", "data-state": "loaded" }, JSON.stringify(census || {}));

View File

@ -1,12 +1,13 @@
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { getSnapshotStatusText } = require("../utils");
const { snapshot: snapshotModel } = require("../models");
const SnapshotListItem = module.exports = createClass({
displayName: "snapshot-list-item",
propTypes: {
onClick: PropTypes.func,
item: PropTypes.any.isRequired,
item: snapshotModel.isRequired,
index: PropTypes.number.isRequired,
},

View File

@ -1,16 +1,26 @@
const { DOM, createClass } = require("devtools/client/shared/vendor/react");
const { DOM, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const Toolbar = module.exports = createClass({
displayName: "toolbar",
propTypes: {
breakdowns: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired,
})).isRequired,
onTakeSnapshotClick: PropTypes.func.isRequired,
onBreakdownChange: PropTypes.func.isRequired,
},
render() {
let buttons = this.props.buttons;
let { onTakeSnapshotClick, onBreakdownChange, breakdowns } = this.props;
return (
DOM.div({ className: "devtools-toolbar" }, ...buttons.map(spec => {
return DOM.button(Object.assign({}, spec, {
className: `${spec.className || "" } devtools-button`
}));
}))
DOM.div({ className: "devtools-toolbar" }, [
DOM.button({ className: `take-snapshot devtools-button`, onClick: onTakeSnapshotClick }),
DOM.select({
className: `select-breakdown`,
onChange: e => onBreakdownChange(e.target.value),
}, breakdowns.map(({ name, displayName }) => DOM.option({ value: name }, displayName)))
])
);
}
});

View File

@ -21,6 +21,39 @@ actions.TAKE_CENSUS_END = "take-census-end";
// Fired by UI to select a snapshot to view.
actions.SELECT_SNAPSHOT = "select-snapshot";
const COUNT = { by: "count", count: true, bytes: true };
const INTERNAL_TYPE = { by: "internalType", then: COUNT };
const ALLOCATION_STACK = { by: "allocationStack", then: COUNT, noStack: COUNT };
const OBJECT_CLASS = { by: "objectClass", then: COUNT, other: COUNT };
const breakdowns = exports.breakdowns = {
coarseType: {
displayName: "Coarse Type",
breakdown: {
by: "coarseType",
objects: ALLOCATION_STACK,
strings: ALLOCATION_STACK,
scripts: INTERNAL_TYPE,
other: INTERNAL_TYPE,
}
},
allocationStack: {
displayName: "Allocation Site",
breakdown: ALLOCATION_STACK,
},
objectClass: {
displayName: "Object Class",
breakdown: OBJECT_CLASS,
},
internalType: {
displayName: "Internal Type",
breakdown: INTERNAL_TYPE,
},
};
const snapshotState = exports.snapshotState = {};
/**

View File

@ -0,0 +1,61 @@
const { MemoryFront } = require("devtools/server/actors/memory");
const HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient");
const { PropTypes } = require("devtools/client/shared/vendor/react");
const { snapshotState: states } = require("./constants");
/**
* The breakdown object DSL describing how we want
* the census data to be.
* @see `js/src/doc/Debugger/Debugger.Memory.md`
*/
let breakdownModel = exports.breakdown = PropTypes.shape({
by: PropTypes.oneOf(["coarseType", "allocationStack", "objectClass", "internalType"]).isRequired,
});
/**
* Snapshot model.
*/
let snapshotModel = exports.snapshot = PropTypes.shape({
// Unique ID for a snapshot
id: PropTypes.number.isRequired,
// Whether or not this snapshot is currently selected.
selected: PropTypes.bool.isRequired,
// fs path to where the snapshot is stored; used to
// identify the snapshot for HeapAnalysesClient.
path: PropTypes.string,
// Data of a census breakdown
census: PropTypes.object,
// The breakdown used to generate the current census
breakdown: breakdownModel,
// State the snapshot is in
// @see ./constants.js
state: function (props, propName) {
let stateNames = Object.keys(states);
let current = props.state;
let shouldHavePath = [states.SAVED, states.READ, states.SAVING_CENSUS, states.SAVED_CENSUS];
let shouldHaveCensus = [states.SAVED_CENSUS];
if (!stateNames.contains(current)) {
throw new Error(`Snapshot state must be one of ${stateNames}.`);
}
if (shouldHavePath.contains(current) && !path) {
throw new Error(`Snapshots in state ${current} must have a snapshot path.`);
}
if (shouldHaveCensus.contains(current) && (!props.census || !props.breakdown)) {
throw new Error(`Snapshots in state ${current} must have a census and breakdown.`);
}
},
});
let appModel = exports.app = {
// {MemoryFront} Used to communicate with platform
front: PropTypes.instanceOf(MemoryFront),
// {HeapAnalysesClient} Used to interface with snapshots
heapWorker: PropTypes.instanceOf(HeapAnalysesClient),
// The breakdown object DSL describing how we want
// the census data to be.
// @see `js/src/doc/Debugger/Debugger.Memory.md`
breakdown: breakdownModel.isRequired,
// List of reference to all snapshots taken
snapshots: PropTypes.arrayOf(snapshotModel).isRequired,
};

View File

@ -14,6 +14,7 @@ DevToolsModules(
'app.js',
'constants.js',
'initializer.js',
'models.js',
'panel.js',
'reducers.js',
'store.js',

View File

@ -1,15 +1,16 @@
const { actions } = require("../constants");
const { actions, breakdowns } = require("../constants");
const DEFAULT_BREAKDOWN = breakdowns.coarseType.breakdown;
// Hardcoded breakdown for now
const DEFAULT_BREAKDOWN = {
by: "internalType",
then: { by: "count", count: true, bytes: true }
let handlers = Object.create(null);
handlers[actions.SET_BREAKDOWN] = function (_, action) {
return Object.assign({}, action.breakdown);
};
/**
* Not much to do here yet until we can change breakdowns,
* but this gets it in our store.
*/
module.exports = function (state=DEFAULT_BREAKDOWN, action) {
return Object.assign({}, DEFAULT_BREAKDOWN);
let handle = handlers[action.type];
if (handle) {
return handle(state, action);
}
return state;
};

View File

@ -33,6 +33,7 @@ handlers[actions.TAKE_CENSUS_START] = function (snapshots, action) {
let snapshot = getSnapshot(snapshots, action.snapshot);
snapshot.state = states.SAVING_CENSUS;
snapshot.census = null;
snapshot.breakdown = action.breakdown;
return [...snapshots];
};
@ -40,6 +41,7 @@ handlers[actions.TAKE_CENSUS_END] = function (snapshots, action) {
let snapshot = getSnapshot(snapshots, action.snapshot);
snapshot.state = states.SAVED_CENSUS;
snapshot.census = action.census;
snapshot.breakdown = action.breakdown;
return [...snapshots];
};

View File

@ -59,3 +59,32 @@ function waitUntilState (store, predicate) {
return deferred.promise;
}
function waitUntilSnapshotState (store, expected) {
let predicate = () => {
let snapshots = store.getState().snapshots;
do_print(snapshots.map(x => x.state));
return snapshots.length === expected.length &&
expected.every((state, i) => state === "*" || snapshots[i].state === state);
};
do_print(`Waiting for snapshots to be of state: ${expected}`);
return waitUntilState(store, predicate);
}
function isBreakdownType (census, type) {
// Little sanity check, all censuses should have atleast a children array
if (!census || !Array.isArray(census.children)) {
return false;
}
switch (type) {
case "coarseType":
return census.children.find(c => c.name === "objects");
case "objectClass":
return census.children.find(c => c.name === "Function");
case "internalType":
return census.children.find(c => c.name === "js::BaseShape") &&
!census.children.find(c => c.name === "objects");
default:
throw new Error(`isBreakdownType does not yet support ${type}`);
}
}

View File

@ -0,0 +1,92 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the task creator `setBreakdownAndRefreshAndRefresh()` for breakdown changing.
* We test this rather than `setBreakdownAndRefresh` directly, as we use the refresh action
* in the app itself composed from `setBreakdownAndRefresh`
*/
let { breakdowns, snapshotState: states } = require("devtools/client/memory/constants");
let { breakdownEquals } = require("devtools/client/memory/utils");
let { setBreakdownAndRefresh } = require("devtools/client/memory/actions/breakdown");
let { takeSnapshotAndCensus, selectSnapshotAndRefresh } = require("devtools/client/memory/actions/snapshot");
function run_test() {
run_next_test();
}
add_task(function *() {
let front = new StubbedMemoryFront();
let heapWorker = new HeapAnalysesClient();
yield front.attach();
let store = Store();
let { getState, dispatch } = store;
// Test default breakdown with no snapshots
equal(getState().breakdown.by, "coarseType", "default coarseType breakdown selected at start.");
dispatch(setBreakdownAndRefresh(heapWorker, breakdowns.objectClass.breakdown));
equal(getState().breakdown.by, "objectClass", "breakdown changed with no snapshots");
// Test invalid breakdowns
ok(getState().errors.length === 0, "No error actions in the queue.");
dispatch(setBreakdownAndRefresh(heapWorker, {}));
yield waitUntilState(store, () => getState().errors.length === 1);
ok(true, "Emits an error action when passing in an invalid breakdown object");
equal(getState().breakdown.by, "objectClass",
"current breakdown unchanged when passing invalid breakdown");
// Test new snapshots
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
ok(isBreakdownType(getState().snapshots[0].census, "objectClass"),
"New snapshots use the current, non-default breakdown");
// Updates when changing breakdown during `SAVING`
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVING]);
dispatch(setBreakdownAndRefresh(heapWorker, breakdowns.coarseType.breakdown));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVED_CENSUS]);
ok(isBreakdownType(getState().snapshots[1].census, "coarseType"),
"Breakdown can be changed while saving snapshots, uses updated breakdown in census");
// Updates when changing breakdown during `SAVING_CENSUS`
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVED_CENSUS, states.SAVING_CENSUS]);
dispatch(setBreakdownAndRefresh(heapWorker, breakdowns.objectClass.breakdown));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVED_CENSUS, states.SAVED_CENSUS]);
ok(breakdownEquals(getState().snapshots[2].breakdown, breakdowns.objectClass.breakdown),
"Breakdown can be changed while saving census, stores updated breakdown in snapshot");
ok(isBreakdownType(getState().snapshots[2].census, "objectClass"),
"Breakdown can be changed while saving census, uses updated breakdown in census");
// Updates census on currently selected snapshot when changing breakdown
ok(getState().snapshots[2].selected, "Third snapshot currently selected");
dispatch(setBreakdownAndRefresh(heapWorker, breakdowns.internalType.breakdown));
yield waitUntilState(store, () => isBreakdownType(getState().snapshots[2].census, "internalType"));
ok(isBreakdownType(getState().snapshots[2].census, "internalType"),
"Snapshot census updated when changing breakdowns after already generating one census");
// Does not update unselected censuses
ok(!getState().snapshots[1].selected, "Second snapshot unselected currently");
ok(breakdownEquals(getState().snapshots[1].breakdown, breakdowns.coarseType.breakdown),
"Second snapshot using `coarseType` breakdown still and not yet updated to correct breakdown");
ok(isBreakdownType(getState().snapshots[1].census, "coarseType"),
"Second snapshot using `coarseType` still for census and not yet updated to correct breakdown");
// Updates to current breakdown when switching to stale snapshot
dispatch(selectSnapshotAndRefresh(heapWorker, getState().snapshots[1]));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVING_CENSUS, states.SAVED_CENSUS]);
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS, states.SAVED_CENSUS, states.SAVED_CENSUS]);
ok(getState().snapshots[1].selected, "Second snapshot selected currently");
ok(breakdownEquals(getState().snapshots[1].breakdown, breakdowns.internalType.breakdown),
"Second snapshot using `internalType` breakdown and updated to correct breakdown");
ok(isBreakdownType(getState().snapshots[1].census, "internalType"),
"Second snapshot using `internalType` for census and updated to correct breakdown");
});

View File

@ -0,0 +1,38 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the task creator `setBreakdownAndRefreshAndRefresh()` for custom
* breakdowns.
*/
let { snapshotState: states } = require("devtools/client/memory/constants");
let { breakdownEquals } = require("devtools/client/memory/utils");
let { setBreakdownAndRefresh } = require("devtools/client/memory/actions/breakdown");
let { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
let custom = { by: "internalType", then: { by: "count", bytes: true }};
function run_test() {
run_next_test();
}
add_task(function *() {
let front = new StubbedMemoryFront();
let heapWorker = new HeapAnalysesClient();
yield front.attach();
let store = Store();
let { getState, dispatch } = store;
dispatch(setBreakdownAndRefresh(heapWorker, custom));
ok(breakdownEquals(getState().breakdown, custom),
"Custom breakdown stored in breakdown state.");
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
ok(breakdownEquals(getState().snapshots[0].breakdown, custom),
"New snapshot stored custom breakdown when done taking census");
ok(getState().snapshots[0].census.children.length, "Census has some children");
// Ensure we don't have `count` in any results
ok(getState().snapshots[0].census.children.every(c => !c.count), "Census used custom breakdown");
});

View File

@ -0,0 +1,45 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the action creator `setBreakdown()` for breakdown changing.
* Does not test refreshing the census information, check `setBreakdownAndRefresh` action
* for that.
*/
let { breakdowns, snapshotState: states } = require("devtools/client/memory/constants");
let { setBreakdown } = require("devtools/client/memory/actions/breakdown");
let { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
function run_test() {
run_next_test();
}
add_task(function *() {
let front = new StubbedMemoryFront();
let heapWorker = new HeapAnalysesClient();
yield front.attach();
let store = Store();
let { getState, dispatch } = store;
// Test default breakdown with no snapshots
equal(getState().breakdown.by, "coarseType", "default coarseType breakdown selected at start.");
dispatch(setBreakdown(breakdowns.objectClass.breakdown));
equal(getState().breakdown.by, "objectClass", "breakdown changed with no snapshots");
// Test invalid breakdowns
try {
dispatch(setBreakdown({}));
ok(false, "Throws when passing in an invalid breakdown object");
} catch (e) {
ok(true, "Throws when passing in an invalid breakdown object");
}
equal(getState().breakdown.by, "objectClass",
"current breakdown unchanged when passing invalid breakdown");
// Test new snapshots
dispatch(takeSnapshotAndCensus(front, heapWorker));
yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]);
ok(isBreakdownType(getState().snapshots[0].census, "objectClass"),
"New snapshots use the current, non-default breakdown");
});

View File

@ -5,7 +5,8 @@
* Tests the async reducer responding to the action `takeCensus(heapWorker, snapshot)`
*/
var { snapshotState: states } = require("devtools/client/memory/constants");
var { snapshotState: states, breakdowns } = require("devtools/client/memory/constants");
var { breakdownEquals } = require("devtools/client/memory/utils");
var { ERROR_TYPE } = require("devtools/client/shared/redux/middleware/task");
var actions = require("devtools/client/memory/actions/snapshot");
@ -43,7 +44,9 @@ add_task(function *() {
snapshot = store.getState().snapshots[0];
ok(snapshot.census, "Snapshot has census after saved census");
ok(snapshot.census.children.length, "Census is in tree node form with the default breakdown");
ok(snapshot.census.children.find(t => t.name === "JSObject"),
ok(snapshot.census.children.length, "Census is in tree node form");
ok(isBreakdownType(snapshot.census, "coarseType"),
"Census is in tree node form with the default breakdown");
ok(breakdownEquals(snapshot.breakdown, breakdowns.coarseType.breakdown),
"Snapshot stored correct breakdown used for the census");
});

View File

@ -0,0 +1,51 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the task creator `takeSnapshotAndCensus()` for the whole flow of
* taking a snapshot, and its sub-actions.
*/
let utils = require("devtools/client/memory/utils");
let { snapshotState: states, breakdowns } = require("devtools/client/memory/constants");
let { Preferences } = require("resource://gre/modules/Preferences.jsm");
function run_test() {
run_next_test();
}
add_task(function *() {
ok(utils.breakdownEquals(breakdowns.allocationStack.breakdown, {
by: "allocationStack",
then: { by: "count", count: true, bytes: true },
noStack: { by: "count", count: true, bytes: true },
}), "utils.breakdownEquals() passes with preset"),
ok(!utils.breakdownEquals(breakdowns.allocationStack.breakdown, {
by: "allocationStack",
then: { by: "count", count: false, bytes: true },
noStack: { by: "count", count: true, bytes: true },
}), "utils.breakdownEquals() fails when deep properties do not match");
ok(!utils.breakdownEquals(breakdowns.allocationStack.breakdown, {
by: "allocationStack",
then: { by: "count", bytes: true },
noStack: { by: "count", count: true, bytes: true },
}), "utils.breakdownEquals() fails when deep properties are missing.");
let s1 = utils.createSnapshot();
let s2 = utils.createSnapshot();
ok(s1.state, states.SAVING, "utils.createSnapshot() creates snapshot in saving state");
ok(s1.id !== s2.id, "utils.createSnapshot() creates snapshot with unique ids");
ok(utils.breakdownEquals(utils.breakdownNameToSpec("coarseType"), breakdowns.coarseType.breakdown),
"utils.breakdownNameToSpec() works for presets");
ok(utils.breakdownEquals(utils.breakdownNameToSpec("coarseType"), breakdowns.coarseType.breakdown),
"utils.breakdownNameToSpec() works for presets");
let custom = { by: "internalType", then: { by: "count", bytes: true }};
Preferences.set("devtools.memory.custom-breakdowns", JSON.stringify({ "My Breakdown": custom }));
ok(utils.breakdownEquals(utils.getCustomBreakdowns()["My Breakdown"], custom),
"utils.getCustomBreakdowns() returns custom breakdowns");
});

View File

@ -6,6 +6,10 @@ firefox-appdir = browser
skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_action-select-snapshot.js]
[test_action-set-breakdown.js]
[test_action-set-breakdown-and-refresh-01.js]
[test_action-set-breakdown-and-refresh-02.js]
[test_action-take-census.js]
[test_action-take-snapshot.js]
[test_action-take-snapshot-and-census.js]
[test_utils.js]

View File

@ -1,5 +1,7 @@
const { Preferences } = require("resource://gre/modules/Preferences.jsm");
const CUSTOM_BREAKDOWN_PREF = "devtools.memory.custom-breakdowns";
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { snapshotState: states } = require("./constants");
const { snapshotState: states, breakdowns } = require("./constants");
const SAVING_SNAPSHOT_TEXT = "Saving snapshot...";
const READING_SNAPSHOT_TEXT = "Reading snapshot...";
const SAVING_CENSUS_TEXT = "Taking heap census...";
@ -14,6 +16,78 @@ exports.assert = function (condition, message) {
}
};
/**
* Returns an array of objects with the unique key `name`
* and `displayName` for each breakdown.
*
* @return {Object{name, displayName}}
*/
exports.getBreakdownDisplayData = function () {
return exports.getBreakdownNames().map(name => {
// If it's a preset use the display name value
let preset = breakdowns[name];
let displayName = name;
if (preset && preset.displayName) {
displayName = preset.displayName;
}
return { name, displayName };
});
};
/**
* Returns an array of the unique names for each breakdown in
* presets and custom pref.
*
* @return {Array<Breakdown>}
*/
exports.getBreakdownNames = function () {
let custom = exports.getCustomBreakdowns();
return Object.keys(Object.assign({}, breakdowns, custom));
};
/**
* Returns custom breakdowns defined in `devtools.memory.custom-breakdowns` pref.
*
* @return {Object}
*/
exports.getCustomBreakdowns = function () {
let customBreakdowns = Object.create(null);
try {
customBreakdowns = JSON.parse(Preferences.get(CUSTOM_BREAKDOWN_PREF)) || Object.create(null);
} catch (e) {
DevToolsUtils.reportException(
`String stored in "${CUSTOM_BREAKDOWN_PREF}" pref cannot be parsed by \`JSON.parse()\`.`);
}
return customBreakdowns;
}
/**
* Converts a breakdown preset name, like "allocationStack", and returns the
* spec for the breakdown. Also checks properties of keys in the `devtools.memory.custom-breakdowns`
* pref. If not found, returns an empty object.
*
* @param {String} name
* @return {Object}
*/
exports.breakdownNameToSpec = function (name) {
let customBreakdowns = exports.getCustomBreakdowns();
// If breakdown is already a breakdown, use it
if (typeof name === "object") {
return name;
}
// If it's in our custom breakdowns, use it
else if (name in customBreakdowns) {
return customBreakdowns[name];
}
// If breakdown name is in our presets, use that
else if (name in breakdowns) {
return breakdowns[name].breakdown;
}
return Object.create(null);
};
/**
* Returns a string representing a readable form of the snapshot's state.
*
@ -21,16 +95,26 @@ exports.assert = function (condition, message) {
* @return {String}
*/
exports.getSnapshotStatusText = function (snapshot) {
switch (snapshot && snapshot.state) {
exports.assert((snapshot || {}).state,
`Snapshot must have expected state, found ${(snapshot || {}).state}.`);
switch (snapshot.state) {
case states.SAVING:
return SAVING_SNAPSHOT_TEXT;
case states.SAVED:
case states.READING:
return READING_SNAPSHOT_TEXT;
case states.READ:
case states.SAVING_CENSUS:
return SAVING_CENSUS_TEXT;
// If it's read, it shouldn't have any label, as we could've cleared the
// census cache by changing the breakdown, and we should lazily
// go to SAVING_CENSUS. If it's SAVED_CENSUS, we have no status to display.
case states.READ:
case states.SAVED_CENSUS:
return "";
}
DevToolsUtils.reportException(`Snapshot in unexpected state: ${snapshot.state}`);
return "";
}
@ -66,3 +150,43 @@ exports.createSnapshot = function createSnapshot () {
path: null,
};
};
/**
* Takes two objects and compares them deeply, returning
* a boolean indicating if they're equal or not. Used for breakdown
* comparison.
*
* @param {Any} obj1
* @param {Any} obj2
* @return {Boolean}
*/
exports.breakdownEquals = function (obj1, obj2) {
let type1 = typeof obj1;
let type2 = typeof obj2;
// Quick checks
if (type1 !== type2 || (Array.isArray(obj1) !== Array.isArray(obj2))) {
return false;
}
if (obj1 === obj2) {
return true;
}
if (Array.isArray(obj1)) {
if (obj1.length !== obj2.length) { return false; }
return obj1.every((_, i) => exports.breakdownEquals(obj[1], obj2[i]));
}
else if (type1 === "object") {
let k1 = Object.keys(obj1);
let k2 = Object.keys(obj2);
if (k1.length !== k2.length) {
return false;
}
return k1.every(k => exports.breakdownEquals(obj1[k], obj2[k]));
}
return false;
};

View File

@ -103,6 +103,8 @@ pref("devtools.debugger.ui.variables-searchbox-visible", false);
// Enable the Memory tools
pref("devtools.memory.enabled", false);
pref("devtools.memory.custom-breakdowns", "{}");
// Enable the Performance tools
pref("devtools.performance.enabled", true);

View File

@ -63,7 +63,7 @@ CensusTreeNodeBreakdowns.internalType = function (node, breakdown, report) {
for (let key of Object.keys(report)) {
node.children.push(new CensusTreeNode(breakdown.then, report[key], key));
}
}
};
CensusTreeNodeBreakdowns.objectClass = function (node, breakdown, report) {
node.children = [];
@ -71,14 +71,18 @@ CensusTreeNodeBreakdowns.objectClass = function (node, breakdown, report) {
let bd = key === "other" ? breakdown.other : breakdown.then;
node.children.push(new CensusTreeNode(bd, report[key], key));
}
}
};
CensusTreeNodeBreakdowns.coarseType = function (node, breakdown, report) {
node.children = [];
for (let type of Object.keys(breakdown).filter(type => COARSE_TYPES.has(type))) {
node.children.push(new CensusTreeNode(breakdown[type], report[type], type));
}
}
};
CensusTreeNodeBreakdowns.allocationStack = function (node, breakdown, report) {
node.children = [];
};
function sortByBytes (a, b) {
return (b.bytes || 0) - (a.bytes || 0);

View File

@ -787,7 +787,7 @@ NetworkMonitor.prototype = {
// associated with a load group. Bug 1160837 will hopefully introduce a
// platform fix that will render the following code entirely useless.
if (aChannel.loadInfo &&
aChannel.loadInfo.contentPolicyType == Ci.nsIContentPolicy.TYPE_BEACON) {
aChannel.loadInfo.externalContentPolicyType == Ci.nsIContentPolicy.TYPE_BEACON) {
let nonE10sMatch = this.window &&
aChannel.loadInfo.loadingDocument === this.window.document;
let e10sMatch = this.topFrame &&
@ -838,7 +838,7 @@ NetworkMonitor.prototype = {
// Determine if this is an XHR request.
httpActivity.isXHR = event.isXHR =
(aChannel.loadInfo.contentPolicyType === Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST);
(aChannel.loadInfo.externalContentPolicyType === Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST);
// Determine the HTTP version.
let httpVersionMaj = {};

View File

@ -14,13 +14,11 @@ function run_test() {
}
// Make sure the queue has items in it...
var queue = prefetch.enumerateQueue();
do_check_true(queue.hasMoreElements());
do_check_true(prefetch.hasMoreElements());
// Now disable the pref to force the queue to empty...
prefs.setBoolPref("network.prefetch-next", false);
queue = prefetch.enumerateQueue();
do_check_false(queue.hasMoreElements());
do_check_false(prefetch.hasMoreElements());
// Now reenable the pref, and add more items to the queue.
prefs.setBoolPref("network.prefetch-next", true);
@ -28,7 +26,5 @@ function run_test() {
var uri = ios.newURI("http://localhost/" + i, null, null);
prefetch.prefetchURI(uri, uri, null, true);
}
queue = prefetch.enumerateQueue();
do_check_true(queue.hasMoreElements());
do_check_true(prefetch.hasMoreElements());
}

View File

@ -1720,6 +1720,9 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
nsIDocument* document =
HasFlag(NODE_FORCE_XBL_BINDINGS) ? OwnerDoc() : GetComposedDoc();
if (HasPointerLock()) {
nsIDocument::UnlockPointer();
}
if (aNullParent) {
if (IsFullScreenAncestor()) {
// The element being removed is an ancestor of the full-screen element,
@ -1731,9 +1734,6 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
// Fully exit full-screen.
nsIDocument::ExitFullscreenInDocTree(OwnerDoc());
}
if (HasPointerLock()) {
nsIDocument::UnlockPointer();
}
if (GetParent() && GetParent()->IsInUncomposedDoc()) {
// Update the editable descendant count in the ancestors before we

View File

@ -668,11 +668,6 @@ public:
ErrorResult& aError);
already_AddRefed<nsIHTMLCollection>
GetElementsByClassName(const nsAString& aClassNames);
bool MozMatchesSelector(const nsAString& aSelector,
ErrorResult& aError)
{
return Matches(aSelector, aError);
}
void SetPointerCapture(int32_t aPointerId, ErrorResult& aError)
{
bool activeState = false;
@ -1769,8 +1764,8 @@ NS_IMETHOD MozMatchesSelector(const nsAString& selector, \
bool* _retval) final override \
{ \
mozilla::ErrorResult rv; \
*_retval = Element::MozMatchesSelector(selector, rv); \
return rv.StealNSResult(); \
*_retval = Element::Matches(selector, rv); \
return rv.StealNSResult(); \
} \
NS_IMETHOD SetCapture(bool retargetToElement) final override \
{ \

View File

@ -418,5 +418,23 @@ URLSearchParams::NotifyObserver()
}
}
uint32_t
URLSearchParams::GetIterableLength() const
{
return mParams->Length();
}
const nsAString&
URLSearchParams::GetKeyAtIndex(uint32_t aIndex) const
{
return mParams->GetKeyAtIndex(aIndex);
}
const nsAString&
URLSearchParams::GetValueAtIndex(uint32_t aIndex) const
{
return mParams->GetValueAtIndex(aIndex);
}
} // namespace dom
} // namespace mozilla

View File

@ -91,6 +91,23 @@ public:
mParams.Clear();
}
uint32_t Length() const
{
return mParams.Length();
}
const nsAString& GetKeyAtIndex(uint32_t aIndex) const
{
MOZ_ASSERT(aIndex < mParams.Length());
return mParams[aIndex].mKey;
}
const nsAString& GetValueAtIndex(uint32_t aIndex) const
{
MOZ_ASSERT(aIndex < mParams.Length());
return mParams[aIndex].mValue;
}
private:
void DecodeString(const nsACString& aInput, nsAString& aOutput);
void ConvertString(const nsACString& aInput, nsAString& aOutput);
@ -153,6 +170,10 @@ public:
void Delete(const nsAString& aName);
uint32_t GetIterableLength() const;
const nsAString& GetKeyAtIndex(uint32_t aIndex) const;
const nsAString& GetValueAtIndex(uint32_t aIndex) const;
void Stringify(nsString& aRetval) const
{
Serialize(aRetval);

View File

@ -3196,7 +3196,7 @@ convertSheetType(uint32_t aSheetType)
default:
NS_ASSERTION(false, "wrong type");
// we must return something although this should never happen
return nsIDocument::SheetTypeCount;
return nsIDocument::AdditionalSheetTypeCount;
}
}

View File

@ -2413,7 +2413,7 @@ nsDocument::RemoveDocStyleSheetsFromStyleSets()
}
void
nsDocument::RemoveStyleSheetsFromStyleSets(nsCOMArray<nsIStyleSheet>& aSheets, nsStyleSet::sheetType aType)
nsDocument::RemoveStyleSheetsFromStyleSets(nsCOMArray<nsIStyleSheet>& aSheets, SheetType aType)
{
// The stylesheets should forget us
int32_t indx = aSheets.Count();
@ -2440,16 +2440,17 @@ nsDocument::ResetStylesheetsToURI(nsIURI* aURI)
mozAutoDocUpdate upd(this, UPDATE_STYLE, true);
RemoveDocStyleSheetsFromStyleSets();
RemoveStyleSheetsFromStyleSets(mOnDemandBuiltInUASheets, nsStyleSet::eAgentSheet);
RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAgentSheet], nsStyleSet::eAgentSheet);
RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eUserSheet], nsStyleSet::eUserSheet);
RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAuthorSheet], nsStyleSet::eDocSheet);
RemoveStyleSheetsFromStyleSets(mOnDemandBuiltInUASheets, SheetType::Agent);
RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAgentSheet], SheetType::Agent);
RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eUserSheet], SheetType::User);
RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAuthorSheet], SheetType::Doc);
// Release all the sheets
mStyleSheets.Clear();
mOnDemandBuiltInUASheets.Clear();
for (uint32_t i = 0; i < SheetTypeCount; ++i)
mAdditionalSheets[i].Clear();
for (auto& sheets : mAdditionalSheets) {
sheets.Clear();
}
// NOTE: We don't release the catalog sheets. It doesn't really matter
// now, but it could in the future -- in which case not releasing them
@ -2483,14 +2484,14 @@ static bool
AppendAuthorSheet(nsIStyleSheet *aSheet, void *aData)
{
nsStyleSet *styleSet = static_cast<nsStyleSet*>(aData);
styleSet->AppendStyleSheet(nsStyleSet::eDocSheet, aSheet);
styleSet->AppendStyleSheet(SheetType::Doc, aSheet);
return true;
}
static void
AppendSheetsToStyleSet(nsStyleSet* aStyleSet,
const nsCOMArray<nsIStyleSheet>& aSheets,
nsStyleSet::sheetType aType)
SheetType aType)
{
for (int32_t i = aSheets.Count() - 1; i >= 0; --i) {
aStyleSet->AppendStyleSheet(aType, aSheets[i]);
@ -2502,13 +2503,13 @@ void
nsDocument::FillStyleSet(nsStyleSet* aStyleSet)
{
NS_PRECONDITION(aStyleSet, "Must have a style set");
NS_PRECONDITION(aStyleSet->SheetCount(nsStyleSet::eDocSheet) == 0,
NS_PRECONDITION(aStyleSet->SheetCount(SheetType::Doc) == 0,
"Style set already has document sheets?");
// We could consider moving this to nsStyleSet::Init, to match its
// handling of the eAnimationSheet and eTransitionSheet levels.
aStyleSet->DirtyRuleProcessors(nsStyleSet::ePresHintSheet);
aStyleSet->DirtyRuleProcessors(nsStyleSet::eStyleAttrSheet);
aStyleSet->DirtyRuleProcessors(SheetType::PresHint);
aStyleSet->DirtyRuleProcessors(SheetType::StyleAttr);
int32_t i;
for (i = mStyleSheets.Count() - 1; i >= 0; --i) {
@ -2528,16 +2529,16 @@ nsDocument::FillStyleSet(nsStyleSet* aStyleSet)
for (i = mOnDemandBuiltInUASheets.Count() - 1; i >= 0; --i) {
nsIStyleSheet* sheet = mOnDemandBuiltInUASheets[i];
if (sheet->IsApplicable()) {
aStyleSet->PrependStyleSheet(nsStyleSet::eAgentSheet, sheet);
aStyleSet->PrependStyleSheet(SheetType::Agent, sheet);
}
}
AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAgentSheet],
nsStyleSet::eAgentSheet);
SheetType::Agent);
AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eUserSheet],
nsStyleSet::eUserSheet);
SheetType::User);
AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAuthorSheet],
nsStyleSet::eDocSheet);
SheetType::Doc);
}
static void
@ -4141,7 +4142,7 @@ nsDocument::AddOnDemandBuiltInUASheet(CSSStyleSheet* aSheet)
// do not override Firefox OS/Mobile's content.css sheet. Maybe we should
// have an insertion point to match the order of
// nsDocumentViewer::CreateStyleSet though?
shell->StyleSet()->PrependStyleSheet(nsStyleSet::eAgentSheet, aSheet);
shell->StyleSet()->PrependStyleSheet(SheetType::Agent, aSheet);
}
}
@ -4373,20 +4374,20 @@ nsDocument::NotifyStyleSheetApplicableStateChanged()
}
}
static nsStyleSet::sheetType
static SheetType
ConvertAdditionalSheetType(nsIDocument::additionalSheetType aType)
{
switch(aType) {
case nsIDocument::eAgentSheet:
return nsStyleSet::eAgentSheet;
return SheetType::Agent;
case nsIDocument::eUserSheet:
return nsStyleSet::eUserSheet;
return SheetType::User;
case nsIDocument::eAuthorSheet:
return nsStyleSet::eDocSheet;
return SheetType::Doc;
default:
NS_ASSERTION(false, "wrong type");
MOZ_ASSERT(false, "wrong type");
// we must return something although this should never happen
return nsStyleSet::eSheetTypeCount;
return SheetType::Count;
}
}
@ -4460,7 +4461,7 @@ nsDocument::AddAdditionalStyleSheet(additionalSheetType aType, nsIStyleSheet* aS
BeginUpdate(UPDATE_STYLE);
nsCOMPtr<nsIPresShell> shell = GetShell();
if (shell) {
nsStyleSet::sheetType type = ConvertAdditionalSheetType(aType);
SheetType type = ConvertAdditionalSheetType(aType);
shell->StyleSet()->AppendStyleSheet(type, aSheet);
}
@ -4488,7 +4489,7 @@ nsDocument::RemoveAdditionalStyleSheet(additionalSheetType aType, nsIURI* aSheet
MOZ_ASSERT(sheetRef->IsApplicable());
nsCOMPtr<nsIPresShell> shell = GetShell();
if (shell) {
nsStyleSet::sheetType type = ConvertAdditionalSheetType(aType);
SheetType type = ConvertAdditionalSheetType(aType);
shell->StyleSet()->RemoveStyleSheet(type, sheetRef);
}
}

View File

@ -1516,7 +1516,7 @@ protected:
void RemoveDocStyleSheetsFromStyleSets();
void RemoveStyleSheetsFromStyleSets(nsCOMArray<nsIStyleSheet>& aSheets,
nsStyleSet::sheetType aType);
mozilla::SheetType aType);
void ResetStylesheetsToURI(nsIURI* aURI);
void FillStyleSet(nsStyleSet* aStyleSet);
@ -1571,7 +1571,7 @@ protected:
nsCOMArray<nsIStyleSheet> mStyleSheets;
nsCOMArray<nsIStyleSheet> mOnDemandBuiltInUASheets;
nsCOMArray<nsIStyleSheet> mAdditionalSheets[SheetTypeCount];
nsCOMArray<nsIStyleSheet> mAdditionalSheets[AdditionalSheetTypeCount];
// Array of observers
nsTObserverArray<nsIDocumentObserver*> mObservers;

View File

@ -4961,7 +4961,7 @@ nsGlobalWindow::GetInnerWidth(int32_t* aInnerWidth)
}
void
nsGlobalWindow::SetInnerWidthOuter(int32_t aInnerWidth, ErrorResult& aError)
nsGlobalWindow::SetInnerWidthOuter(int32_t aInnerWidth, ErrorResult& aError, bool aCallerIsChrome)
{
MOZ_RELEASE_ASSERT(IsOuterWindow());
@ -4970,7 +4970,7 @@ nsGlobalWindow::SetInnerWidthOuter(int32_t aInnerWidth, ErrorResult& aError)
return;
}
CheckSecurityWidthAndHeight(&aInnerWidth, nullptr);
CheckSecurityWidthAndHeight(&aInnerWidth, nullptr, aCallerIsChrome);
RefPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
@ -4999,7 +4999,7 @@ nsGlobalWindow::SetInnerWidthOuter(int32_t aInnerWidth, ErrorResult& aError)
void
nsGlobalWindow::SetInnerWidth(int32_t aInnerWidth, ErrorResult& aError)
{
FORWARD_TO_OUTER_OR_THROW(SetInnerWidthOuter, (aInnerWidth, aError), aError, );
FORWARD_TO_OUTER_OR_THROW(SetInnerWidthOuter, (aInnerWidth, aError, nsContentUtils::IsCallerChrome()), aError, );
}
void
@ -5013,14 +5013,14 @@ nsGlobalWindow::SetInnerWidth(JSContext* aCx, JS::Handle<JS::Value> aValue,
NS_IMETHODIMP
nsGlobalWindow::SetInnerWidth(int32_t aInnerWidth)
{
FORWARD_TO_INNER(SetInnerWidth, (aInnerWidth), NS_ERROR_UNEXPECTED);
FORWARD_TO_OUTER(SetInnerWidth, (aInnerWidth), NS_ERROR_UNEXPECTED);
if (IsFrame()) {
return NS_OK;
}
ErrorResult rv;
SetInnerWidth(aInnerWidth, rv);
SetInnerWidthOuter(aInnerWidth, rv, /* aCallerIsChrome = */ true);
return rv.StealNSResult();
}
@ -5062,7 +5062,7 @@ nsGlobalWindow::GetInnerHeight(int32_t* aInnerHeight)
}
void
nsGlobalWindow::SetInnerHeightOuter(int32_t aInnerHeight, ErrorResult& aError)
nsGlobalWindow::SetInnerHeightOuter(int32_t aInnerHeight, ErrorResult& aError, bool aCallerIsChrome)
{
MOZ_RELEASE_ASSERT(IsOuterWindow());
@ -5081,7 +5081,7 @@ nsGlobalWindow::SetInnerHeightOuter(int32_t aInnerHeight, ErrorResult& aError)
nsRect shellArea = presContext->GetVisibleArea();
nscoord height = aInnerHeight;
nscoord width = shellArea.width;
CheckSecurityWidthAndHeight(nullptr, &height);
CheckSecurityWidthAndHeight(nullptr, &height, aCallerIsChrome);
SetCSSViewportWidthAndHeight(width,
nsPresContext::CSSPixelsToAppUnits(height));
return;
@ -5092,14 +5092,14 @@ nsGlobalWindow::SetInnerHeightOuter(int32_t aInnerHeight, ErrorResult& aError)
nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell));
docShellAsWin->GetSize(&width, &height);
CheckSecurityWidthAndHeight(nullptr, &aInnerHeight);
CheckSecurityWidthAndHeight(nullptr, &aInnerHeight, aCallerIsChrome);
aError = SetDocShellWidthAndHeight(width, CSSToDevIntPixels(aInnerHeight));
}
void
nsGlobalWindow::SetInnerHeight(int32_t aInnerHeight, ErrorResult& aError)
{
FORWARD_TO_OUTER_OR_THROW(SetInnerHeightOuter, (aInnerHeight, aError), aError, );
FORWARD_TO_OUTER_OR_THROW(SetInnerHeightOuter, (aInnerHeight, aError, nsContentUtils::IsCallerChrome()), aError, );
}
void
@ -5113,14 +5113,14 @@ nsGlobalWindow::SetInnerHeight(JSContext* aCx, JS::Handle<JS::Value> aValue,
NS_IMETHODIMP
nsGlobalWindow::SetInnerHeight(int32_t aInnerHeight)
{
FORWARD_TO_INNER(SetInnerHeight, (aInnerHeight), NS_ERROR_UNEXPECTED);
FORWARD_TO_OUTER(SetInnerHeight, (aInnerHeight), NS_ERROR_UNEXPECTED);
if (IsFrame()) {
return NS_OK;
}
ErrorResult rv;
SetInnerHeight(aInnerHeight, rv);
SetInnerHeightOuter(aInnerHeight, rv, /* aCallerIsChrome = */ true);
return rv.StealNSResult();
}
@ -5225,7 +5225,7 @@ nsGlobalWindow::GetOuterHeight(int32_t* aOuterHeight)
void
nsGlobalWindow::SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,
ErrorResult& aError)
ErrorResult& aError, bool aCallerIsChrome)
{
MOZ_ASSERT(IsOuterWindow());
@ -5236,7 +5236,8 @@ nsGlobalWindow::SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,
}
CheckSecurityWidthAndHeight(aIsWidth ? &aLengthCSSPixels : nullptr,
aIsWidth ? nullptr : &aLengthCSSPixels);
aIsWidth ? nullptr : &aLengthCSSPixels,
aCallerIsChrome);
int32_t width, height;
aError = treeOwnerAsWin->GetSize(&width, &height);
@ -5254,17 +5255,17 @@ nsGlobalWindow::SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,
}
void
nsGlobalWindow::SetOuterWidthOuter(int32_t aOuterWidth, ErrorResult& aError)
nsGlobalWindow::SetOuterWidthOuter(int32_t aOuterWidth, ErrorResult& aError, bool aCallerIsChrome)
{
MOZ_RELEASE_ASSERT(IsOuterWindow());
SetOuterSize(aOuterWidth, true, aError);
SetOuterSize(aOuterWidth, true, aError, aCallerIsChrome);
}
void
nsGlobalWindow::SetOuterWidth(int32_t aOuterWidth, ErrorResult& aError)
{
FORWARD_TO_OUTER_OR_THROW(SetOuterWidthOuter, (aOuterWidth, aError), aError, );
FORWARD_TO_OUTER_OR_THROW(SetOuterWidthOuter, (aOuterWidth, aError, nsContentUtils::IsCallerChrome()), aError, );
}
void
@ -5278,30 +5279,30 @@ nsGlobalWindow::SetOuterWidth(JSContext* aCx, JS::Handle<JS::Value> aValue,
NS_IMETHODIMP
nsGlobalWindow::SetOuterWidth(int32_t aOuterWidth)
{
FORWARD_TO_INNER(SetOuterWidth, (aOuterWidth), NS_ERROR_UNEXPECTED);
FORWARD_TO_OUTER(SetOuterWidth, (aOuterWidth), NS_ERROR_UNEXPECTED);
if (IsFrame()) {
return NS_OK;
}
ErrorResult rv;
SetOuterWidth(aOuterWidth, rv);
SetOuterWidthOuter(aOuterWidth, rv, /* aCallerIsChrome = */ true);
return rv.StealNSResult();
}
void
nsGlobalWindow::SetOuterHeightOuter(int32_t aOuterHeight, ErrorResult& aError)
nsGlobalWindow::SetOuterHeightOuter(int32_t aOuterHeight, ErrorResult& aError, bool aCallerIsChrome)
{
MOZ_RELEASE_ASSERT(IsOuterWindow());
SetOuterSize(aOuterHeight, false, aError);
SetOuterSize(aOuterHeight, false, aError, aCallerIsChrome);
}
void
nsGlobalWindow::SetOuterHeight(int32_t aOuterHeight, ErrorResult& aError)
{
FORWARD_TO_OUTER_OR_THROW(SetOuterHeightOuter, (aOuterHeight, aError), aError, );
FORWARD_TO_OUTER_OR_THROW(SetOuterHeightOuter, (aOuterHeight, aError, nsContentUtils::IsCallerChrome()), aError, );
}
void
@ -5315,14 +5316,14 @@ nsGlobalWindow::SetOuterHeight(JSContext* aCx, JS::Handle<JS::Value> aValue,
NS_IMETHODIMP
nsGlobalWindow::SetOuterHeight(int32_t aOuterHeight)
{
FORWARD_TO_INNER(SetOuterHeight, (aOuterHeight), NS_ERROR_UNEXPECTED);
FORWARD_TO_OUTER(SetOuterHeight, (aOuterHeight), NS_ERROR_UNEXPECTED);
if (IsFrame()) {
return NS_OK;
}
ErrorResult rv;
SetOuterHeight(aOuterHeight, rv);
SetOuterHeightOuter(aOuterHeight, rv, /* aCallerIsChrome = */ true);
return rv.StealNSResult();
}
@ -5644,7 +5645,7 @@ nsGlobalWindow::MatchMedia(const nsAString& aMediaQueryList,
}
void
nsGlobalWindow::SetScreenXOuter(int32_t aScreenX, ErrorResult& aError)
nsGlobalWindow::SetScreenXOuter(int32_t aScreenX, ErrorResult& aError, bool aCallerIsChrome)
{
MOZ_RELEASE_ASSERT(IsOuterWindow());
@ -5660,7 +5661,7 @@ nsGlobalWindow::SetScreenXOuter(int32_t aScreenX, ErrorResult& aError)
return;
}
CheckSecurityLeftAndTop(&aScreenX, nullptr);
CheckSecurityLeftAndTop(&aScreenX, nullptr, aCallerIsChrome);
x = CSSToDevIntPixels(aScreenX);
aError = treeOwnerAsWin->SetPosition(x, y);
@ -5669,7 +5670,7 @@ nsGlobalWindow::SetScreenXOuter(int32_t aScreenX, ErrorResult& aError)
void
nsGlobalWindow::SetScreenX(int32_t aScreenX, ErrorResult& aError)
{
FORWARD_TO_OUTER_OR_THROW(SetScreenXOuter, (aScreenX, aError), aError, );
FORWARD_TO_OUTER_OR_THROW(SetScreenXOuter, (aScreenX, aError, nsContentUtils::IsCallerChrome()), aError, );
}
void
@ -5683,14 +5684,14 @@ nsGlobalWindow::SetScreenX(JSContext* aCx, JS::Handle<JS::Value> aValue,
NS_IMETHODIMP
nsGlobalWindow::SetScreenX(int32_t aScreenX)
{
FORWARD_TO_INNER(SetScreenX, (aScreenX), NS_ERROR_UNEXPECTED);
FORWARD_TO_OUTER(SetScreenX, (aScreenX), NS_ERROR_UNEXPECTED);
if (IsFrame()) {
return NS_OK;
}
ErrorResult rv;
SetScreenX(aScreenX, rv);
SetScreenXOuter(aScreenX, rv, /* aCallerIsChrome = */ true);
return rv.StealNSResult();
}
@ -5730,7 +5731,7 @@ nsGlobalWindow::GetScreenY(JSContext* aCx,
}
void
nsGlobalWindow::SetScreenYOuter(int32_t aScreenY, ErrorResult& aError)
nsGlobalWindow::SetScreenYOuter(int32_t aScreenY, ErrorResult& aError, bool aCallerIsChrome)
{
MOZ_RELEASE_ASSERT(IsOuterWindow());
@ -5746,7 +5747,7 @@ nsGlobalWindow::SetScreenYOuter(int32_t aScreenY, ErrorResult& aError)
return;
}
CheckSecurityLeftAndTop(nullptr, &aScreenY);
CheckSecurityLeftAndTop(nullptr, &aScreenY, aCallerIsChrome);
y = CSSToDevIntPixels(aScreenY);
aError = treeOwnerAsWin->SetPosition(x, y);
@ -5755,7 +5756,7 @@ nsGlobalWindow::SetScreenYOuter(int32_t aScreenY, ErrorResult& aError)
void
nsGlobalWindow::SetScreenY(int32_t aScreenY, ErrorResult& aError)
{
FORWARD_TO_OUTER_OR_THROW(SetScreenYOuter, (aScreenY, aError), aError, );
FORWARD_TO_OUTER_OR_THROW(SetScreenYOuter, (aScreenY, aError, nsContentUtils::IsCallerChrome()), aError, );
}
void
@ -5769,14 +5770,14 @@ nsGlobalWindow::SetScreenY(JSContext* aCx, JS::Handle<JS::Value> aValue,
NS_IMETHODIMP
nsGlobalWindow::SetScreenY(int32_t aScreenY)
{
FORWARD_TO_INNER(SetScreenY, (aScreenY), NS_ERROR_UNEXPECTED);
FORWARD_TO_OUTER(SetScreenY, (aScreenY), NS_ERROR_UNEXPECTED);
if (IsFrame()) {
return NS_OK;
}
ErrorResult rv;
SetScreenY(aScreenY, rv);
SetScreenYOuter(aScreenY, rv, /* aCallerIsChrome = */ true);
return rv.StealNSResult();
}
@ -5784,12 +5785,12 @@ nsGlobalWindow::SetScreenY(int32_t aScreenY)
// NOTE: Arguments to this function should have values scaled to
// CSS pixels, not device pixels.
void
nsGlobalWindow::CheckSecurityWidthAndHeight(int32_t* aWidth, int32_t* aHeight)
nsGlobalWindow::CheckSecurityWidthAndHeight(int32_t* aWidth, int32_t* aHeight, bool aCallerIsChrome)
{
MOZ_ASSERT(IsOuterWindow());
#ifdef MOZ_XUL
if (!nsContentUtils::IsCallerChrome()) {
if (!aCallerIsChrome) {
// if attempting to resize the window, hide any open popups
nsContentUtils::HidePopupsInDocument(mDoc);
}
@ -5848,7 +5849,7 @@ nsGlobalWindow::SetCSSViewportWidthAndHeight(nscoord aInnerWidth, nscoord aInner
// NOTE: Arguments to this function should have values scaled to
// CSS pixels, not device pixels.
void
nsGlobalWindow::CheckSecurityLeftAndTop(int32_t* aLeft, int32_t* aTop)
nsGlobalWindow::CheckSecurityLeftAndTop(int32_t* aLeft, int32_t* aTop, bool aCallerIsChrome)
{
MOZ_ASSERT(IsOuterWindow());
@ -5856,7 +5857,7 @@ nsGlobalWindow::CheckSecurityLeftAndTop(int32_t* aLeft, int32_t* aTop)
// Check security state for use in determing window dimensions
if (!nsContentUtils::IsCallerChrome()) {
if (!aCallerIsChrome) {
#ifdef MOZ_XUL
// if attempting to move the window, hide any open popups
nsContentUtils::HidePopupsInDocument(mDoc);
@ -7623,7 +7624,7 @@ nsGlobalWindow::MoveToOuter(int32_t aXPos, int32_t aYPos, ErrorResult& aError, b
// Mild abuse of a "size" object so we don't need more helper functions.
nsIntSize cssPos(aXPos, aYPos);
CheckSecurityLeftAndTop(&cssPos.width, &cssPos.height);
CheckSecurityLeftAndTop(&cssPos.width, &cssPos.height, aCallerIsChrome);
nsIntSize devPos = CSSToDevIntPixels(cssPos);
@ -7683,7 +7684,7 @@ nsGlobalWindow::MoveByOuter(int32_t aXDif, int32_t aYDif, ErrorResult& aError, b
cssPos.width += aXDif;
cssPos.height += aYDif;
CheckSecurityLeftAndTop(&cssPos.width, &cssPos.height);
CheckSecurityLeftAndTop(&cssPos.width, &cssPos.height, aCallerIsChrome);
nsIntSize newDevPos(CSSToDevIntPixels(cssPos));
@ -7741,7 +7742,7 @@ nsGlobalWindow::ResizeToOuter(int32_t aWidth, int32_t aHeight, ErrorResult& aErr
}
nsIntSize cssSize(aWidth, aHeight);
CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height);
CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerIsChrome);
nsIntSize devSz(CSSToDevIntPixels(cssSize));
@ -7821,7 +7822,7 @@ nsGlobalWindow::ResizeByOuter(int32_t aWidthDif, int32_t aHeightDif,
cssSize.width += aWidthDif;
cssSize.height += aHeightDif;
CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height);
CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerIsChrome);
nsIntSize newDevSize(CSSToDevIntPixels(cssSize));
@ -7888,7 +7889,7 @@ nsGlobalWindow::SizeToContentOuter(ErrorResult& aError, bool aCallerIsChrome)
}
nsIntSize cssSize(DevToCSSIntPixels(nsIntSize(width, height)));
CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height);
CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerIsChrome);
nsIntSize newDevSize(CSSToDevIntPixels(cssSize));

View File

@ -1207,27 +1207,27 @@ protected:
// And the implementations of WindowCoordGetter/WindowCoordSetter.
int32_t GetInnerWidthOuter(mozilla::ErrorResult& aError);
int32_t GetInnerWidth(mozilla::ErrorResult& aError);
void SetInnerWidthOuter(int32_t aInnerWidth, mozilla::ErrorResult& aError);
void SetInnerWidthOuter(int32_t aInnerWidth, mozilla::ErrorResult& aError, bool aCallerIsChrome);
void SetInnerWidth(int32_t aInnerWidth, mozilla::ErrorResult& aError);
int32_t GetInnerHeightOuter(mozilla::ErrorResult& aError);
int32_t GetInnerHeight(mozilla::ErrorResult& aError);
void SetInnerHeightOuter(int32_t aInnerHeight, mozilla::ErrorResult& aError);
void SetInnerHeightOuter(int32_t aInnerHeight, mozilla::ErrorResult& aError, bool aCallerIsChrome);
void SetInnerHeight(int32_t aInnerHeight, mozilla::ErrorResult& aError);
int32_t GetScreenXOuter(mozilla::ErrorResult& aError);
int32_t GetScreenX(mozilla::ErrorResult& aError);
void SetScreenXOuter(int32_t aScreenX, mozilla::ErrorResult& aError);
void SetScreenXOuter(int32_t aScreenX, mozilla::ErrorResult& aError, bool aCallerIsChrome);
void SetScreenX(int32_t aScreenX, mozilla::ErrorResult& aError);
int32_t GetScreenYOuter(mozilla::ErrorResult& aError);
int32_t GetScreenY(mozilla::ErrorResult& aError);
void SetScreenYOuter(int32_t aScreenY, mozilla::ErrorResult& aError);
void SetScreenYOuter(int32_t aScreenY, mozilla::ErrorResult& aError, bool aCallerIsChrome);
void SetScreenY(int32_t aScreenY, mozilla::ErrorResult& aError);
int32_t GetOuterWidthOuter(mozilla::ErrorResult& aError);
int32_t GetOuterWidth(mozilla::ErrorResult& aError);
void SetOuterWidthOuter(int32_t aOuterWidth, mozilla::ErrorResult& aError);
void SetOuterWidthOuter(int32_t aOuterWidth, mozilla::ErrorResult& aError, bool aCallerIsChrome);
void SetOuterWidth(int32_t aOuterWidth, mozilla::ErrorResult& aError);
int32_t GetOuterHeightOuter(mozilla::ErrorResult& aError);
int32_t GetOuterHeight(mozilla::ErrorResult& aError);
void SetOuterHeightOuter(int32_t aOuterHeight, mozilla::ErrorResult& aError);
void SetOuterHeightOuter(int32_t aOuterHeight, mozilla::ErrorResult& aError, bool aCallerIsChrome);
void SetOuterHeight(int32_t aOuterHeight, mozilla::ErrorResult& aError);
// Array of idle observers that are notified of idle events.
@ -1457,8 +1457,8 @@ public:
// Outer windows only.
void EnsureReflowFlushAndPaint();
void CheckSecurityWidthAndHeight(int32_t* width, int32_t* height);
void CheckSecurityLeftAndTop(int32_t* left, int32_t* top);
void CheckSecurityWidthAndHeight(int32_t* width, int32_t* height, bool aCallerIsChrome);
void CheckSecurityLeftAndTop(int32_t* left, int32_t* top, bool aCallerIsChrome);
// Outer windows only.
// Arguments to this function should have values in app units
@ -1486,7 +1486,7 @@ public:
nsresult GetInnerSize(mozilla::CSSIntSize& aSize);
nsIntSize GetOuterSize(mozilla::ErrorResult& aError);
void SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,
mozilla::ErrorResult& aError);
mozilla::ErrorResult& aError, bool aCallerIsChrome);
nsRect GetInnerScreenRect();
void ScrollTo(const mozilla::CSSIntPoint& aScroll,

View File

@ -571,11 +571,14 @@ nsHostObjectProtocolHandler::NewChannel2(nsIURI* uri,
return rv.StealNSResult();
}
nsAutoString contentType;
blob->GetType(contentType);
nsCOMPtr<nsIChannel> channel;
rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel),
uri,
stream,
EmptyCString(), // aContentType
NS_ConvertUTF16toUTF8(contentType),
EmptyCString(), // aContentCharset
aLoadInfo);
if (NS_WARN_IF(rv.Failed())) {

View File

@ -944,7 +944,7 @@ public:
eAgentSheet,
eUserSheet,
eAuthorSheet,
SheetTypeCount
AdditionalSheetTypeCount
};
virtual nsresult LoadAdditionalStyleSheet(additionalSheetType aType, nsIURI* aSheetURI) = 0;

View File

@ -275,30 +275,41 @@ nsScriptLoader::ShouldLoadScript(nsIDocument* aDocument,
return NS_OK;
}
class ContextMediator : public nsIStreamLoaderObserver
{
public:
explicit ContextMediator(nsScriptLoader *aScriptLoader, nsISupports *aContext)
: mScriptLoader(aScriptLoader)
, mContext(aContext) {}
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLOADEROBSERVER
private:
virtual ~ContextMediator() {}
RefPtr<nsScriptLoader> mScriptLoader;
nsCOMPtr<nsISupports> mContext;
};
NS_IMPL_ISUPPORTS(ContextMediator, nsIStreamLoaderObserver)
NS_IMETHODIMP
ContextMediator::OnStreamComplete(nsIStreamLoader* aLoader,
nsISupports* aContext,
nsresult aStatus,
uint32_t aStringLen,
const uint8_t* aString)
{
// pass arguments through except for the aContext,
// we have to mediate and use mContext instead.
return mScriptLoader->OnStreamComplete(aLoader, mContext, aStatus,
aStringLen, aString);
}
nsresult
nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType,
bool aScriptFromHead)
{
nsISupports *context = aRequest->mElement.get()
? static_cast<nsISupports *>(aRequest->mElement.get())
: static_cast<nsISupports *>(mDocument);
nsresult rv = ShouldLoadScript(mDocument, context, aRequest->mURI, aType, aRequest->IsPreload());
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup();
nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(mDocument->MasterDocument()->GetWindow()));
if (!window) {
return NS_ERROR_NULL_POINTER;
}
nsIDocShell *docshell = window->GetDocShell();
nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
// If this document is sandboxed without 'allow-scripts', abort.
if (mDocument->GetSandboxFlags() & SANDBOXED_SCRIPTS) {
return NS_OK;
@ -307,17 +318,39 @@ nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType,
nsContentPolicyType contentPolicyType = aRequest->IsPreload()
? nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD
: nsIContentPolicy::TYPE_INTERNAL_SCRIPT;
nsCOMPtr<nsINode> context;
if (aRequest->mElement) {
context = do_QueryInterface(aRequest->mElement);
}
else {
context = mDocument;
}
nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup();
nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(mDocument->MasterDocument()->GetWindow()));
NS_ENSURE_TRUE(window, NS_ERROR_NULL_POINTER);
nsIDocShell *docshell = window->GetDocShell();
nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
nsSecurityFlags securityFlags =
aRequest->mCORSMode == CORS_NONE
? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL
: nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
if (aRequest->mCORSMode == CORS_USE_CREDENTIALS) {
securityFlags |= nsILoadInfo::SEC_REQUIRE_CORS_WITH_CREDENTIALS;
}
securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
nsCOMPtr<nsIChannel> channel;
rv = NS_NewChannel(getter_AddRefs(channel),
aRequest->mURI,
mDocument,
nsILoadInfo::SEC_NORMAL,
contentPolicyType,
loadGroup,
prompter,
nsIRequest::LOAD_NORMAL |
nsIChannel::LOAD_CLASSIFY_URI);
nsresult rv = NS_NewChannel(getter_AddRefs(channel),
aRequest->mURI,
context,
securityFlags,
contentPolicyType,
loadGroup,
prompter,
nsIRequest::LOAD_NORMAL |
nsIChannel::LOAD_CLASSIFY_URI);
NS_ENSURE_SUCCESS(rv, rv);
@ -356,26 +389,13 @@ nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType,
timedChannel->SetInitiatorType(NS_LITERAL_STRING("script"));
}
RefPtr<ContextMediator> mediator = new ContextMediator(this, aRequest);
nsCOMPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
rv = NS_NewStreamLoader(getter_AddRefs(loader), mediator);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIStreamListener> listener = loader.get();
if (aRequest->mCORSMode != CORS_NONE) {
bool withCredentials = (aRequest->mCORSMode == CORS_USE_CREDENTIALS);
RefPtr<nsCORSListenerProxy> corsListener =
new nsCORSListenerProxy(listener, mDocument->NodePrincipal(),
withCredentials);
rv = corsListener->Init(channel, DataURIHandling::Allow);
NS_ENSURE_SUCCESS(rv, rv);
listener = corsListener;
}
rv = channel->AsyncOpen(listener, aRequest);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
return channel->AsyncOpen2(loader);
}
bool

View File

@ -51,10 +51,7 @@
#include "nsICachingChannel.h"
#include "nsContentUtils.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIContentPolicy.h"
#include "nsContentPolicyUtils.h"
#include "nsError.h"
#include "nsCORSListenerProxy.h"
#include "nsIHTMLDocument.h"
#include "nsIStorageStream.h"
#include "nsIPromptFactory.h"
@ -126,7 +123,7 @@ using namespace mozilla::dom;
#define XML_HTTP_REQUEST_SYNCLOOPING (1 << 10) // Internal
#define XML_HTTP_REQUEST_BACKGROUND (1 << 13) // Internal
#define XML_HTTP_REQUEST_USE_XSITE_AC (1 << 14) // Internal
#define XML_HTTP_REQUEST_NEED_AC_PREFLIGHT (1 << 15) // Internal
#define XML_HTTP_REQUEST_NEED_AC_PREFLIGHT_IF_XSITE (1 << 15) // Internal
#define XML_HTTP_REQUEST_AC_WITH_CREDENTIALS (1 << 16) // Internal
#define XML_HTTP_REQUEST_TIMED_OUT (1 << 17) // Internal
#define XML_HTTP_REQUEST_DELETED (1 << 18) // Internal
@ -1533,47 +1530,15 @@ nsXMLHttpRequest::IsSystemXHR()
return mIsSystem || nsContentUtils::IsSystemPrincipal(mPrincipal);
}
nsresult
void
nsXMLHttpRequest::CheckChannelForCrossSiteRequest(nsIChannel* aChannel)
{
// A system XHR (chrome code or a web app with the right permission) can
// always perform cross-site requests. In the web app case, however, we
// must still check for protected URIs like file:///.
if (IsSystemXHR()) {
if (!nsContentUtils::IsSystemPrincipal(mPrincipal)) {
nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager();
nsCOMPtr<nsIURI> uri;
aChannel->GetOriginalURI(getter_AddRefs(uri));
return secMan->CheckLoadURIWithPrincipal(
mPrincipal, uri, nsIScriptSecurityManager::STANDARD);
}
return NS_OK;
// load anything, and same-origin loads are always allowed.
if (!IsSystemXHR() &&
!nsContentUtils::CheckMayLoad(mPrincipal, aChannel, true)) {
mState |= XML_HTTP_REQUEST_USE_XSITE_AC;
}
// If this is a same-origin request or the channel's URI inherits
// its principal, it's allowed.
if (nsContentUtils::CheckMayLoad(mPrincipal, aChannel, true)) {
return NS_OK;
}
// This is a cross-site request
mState |= XML_HTTP_REQUEST_USE_XSITE_AC;
// Check if we need to do a preflight request.
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
NS_ENSURE_TRUE(httpChannel, NS_ERROR_DOM_BAD_URI);
nsAutoCString method;
httpChannel->GetRequestMethod(method);
if (!mCORSUnsafeHeaders.IsEmpty() ||
(mUpload && mUpload->HasListeners()) ||
(!method.LowerCaseEqualsLiteral("get") &&
!method.LowerCaseEqualsLiteral("post") &&
!method.LowerCaseEqualsLiteral("head"))) {
mState |= XML_HTTP_REQUEST_NEED_AC_PREFLIGHT;
}
return NS_OK;
}
NS_IMETHODIMP
@ -1680,21 +1645,6 @@ nsXMLHttpRequest::Open(const nsACString& inMethod, const nsACString& url,
rv = CheckInnerWindowCorrectness();
NS_ENSURE_SUCCESS(rv, rv);
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
uri,
mPrincipal,
doc,
EmptyCString(), //mime guess
nullptr, //extra
&shouldLoad,
nsContentUtils::GetContentPolicy(),
nsContentUtils::GetSecurityManager());
if (NS_FAILED(rv)) return rv;
if (NS_CP_REJECTED(shouldLoad)) {
// Disallowed by content policy
return NS_ERROR_CONTENT_BLOCKED;
}
// XXXbz this is wrong: we should only be looking at whether
// user/password were passed, not at the values! See bug 759624.
@ -1717,20 +1667,27 @@ nsXMLHttpRequest::Open(const nsACString& inMethod, const nsACString& url,
// will be automatically aborted if the user leaves the page.
nsCOMPtr<nsILoadGroup> loadGroup = GetLoadGroup();
nsSecurityFlags secFlags = nsILoadInfo::SEC_NORMAL;
nsSecurityFlags secFlags;
nsLoadFlags loadFlags = nsIRequest::LOAD_BACKGROUND;
if (IsSystemXHR()) {
// Don't give this document the system principal. We need to keep track of
// mPrincipal being system because we use it for various security checks
// that should be passing, but the document data shouldn't get a system
// principal. Hence we set the sandbox flag in loadinfo, so that
// GetChannelResultPrincipal will give us the nullprincipal.
secFlags |= nsILoadInfo::SEC_SANDBOXED;
//For a XHR, disable interception
if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
// When chrome is loading we want to make sure to sandbox any potential
// result document. We also want to allow cross-origin loads.
secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL |
nsILoadInfo::SEC_SANDBOXED;
}
else if (IsSystemXHR()) {
// For pages that have appropriate permissions, we want to still allow
// cross-origin loads, but make sure that the any potential result
// documents get the same principal as the loader.
secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
} else {
secFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
}
else {
// Otherwise use CORS. Again, make sure that potential result documents
// use the same principal as the loader.
secFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS |
nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
}
// If we have the document, use it
@ -1755,10 +1712,10 @@ nsXMLHttpRequest::Open(const nsACString& inMethod, const nsACString& url,
loadFlags);
}
if (NS_FAILED(rv)) return rv;
NS_ENSURE_SUCCESS(rv, rv);
mState &= ~(XML_HTTP_REQUEST_USE_XSITE_AC |
XML_HTTP_REQUEST_NEED_AC_PREFLIGHT);
XML_HTTP_REQUEST_NEED_AC_PREFLIGHT_IF_XSITE);
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
if (httpChannel) {
@ -1774,7 +1731,7 @@ nsXMLHttpRequest::Open(const nsACString& inMethod, const nsACString& url,
ChangeState(XML_HTTP_REQUEST_OPENED);
return rv;
return NS_OK;
}
void
@ -2818,14 +2775,21 @@ nsXMLHttpRequest::Send(nsIVariant* aVariant, const Nullable<RequestBody>& aBody)
ResetResponse();
rv = CheckChannelForCrossSiteRequest(mChannel);
NS_ENSURE_SUCCESS(rv, rv);
CheckChannelForCrossSiteRequest(mChannel);
bool withCredentials = !!(mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS);
// Hook us up to listen to redirects and the like
mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
mChannel->SetNotificationCallbacks(this);
if (!IsSystemXHR() && withCredentials) {
// This is quite sad. We have to create the channel in .open(), since the
// chrome-only xhr.channel API depends on that. However .withCredentials
// can be modified after, so we don't know what to set the
// SEC_REQUIRE_CORS_WITH_CREDENTIALS flag to when the channel is
// created. So set it here using a hacky internal API.
// Not doing this for system XHR uses since those don't use CORS.
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
static_cast<LoadInfo*>(loadInfo.get())->SetWithCredentialsSecFlag();
}
// Blocking gets are common enough out of XHR that we should mark
// the channel slow by default for pipeline purposes
@ -2846,24 +2810,6 @@ nsXMLHttpRequest::Send(nsIVariant* aVariant, const Nullable<RequestBody>& aBody)
internalHttpChannel->SetResponseTimeoutEnabled(false);
}
nsCOMPtr<nsIStreamListener> listener = this;
if (!IsSystemXHR()) {
// Always create a nsCORSListenerProxy here even if it's
// a same-origin request right now, since it could be redirected.
RefPtr<nsCORSListenerProxy> corsListener =
new nsCORSListenerProxy(listener, mPrincipal, withCredentials);
rv = corsListener->Init(mChannel, DataURIHandling::Allow);
NS_ENSURE_SUCCESS(rv, rv);
listener = corsListener;
}
else {
// Because of bug 682305, we can't let listener be the XHR object itself
// because JS wouldn't be able to use it. So if we haven't otherwise
// created a listener around 'this', do so now.
listener = new nsStreamListenerWrapper(listener);
}
if (mIsAnon) {
AddLoadFlags(mChannel, nsIRequest::LOAD_ANONYMOUS);
}
@ -2871,9 +2817,6 @@ nsXMLHttpRequest::Send(nsIVariant* aVariant, const Nullable<RequestBody>& aBody)
AddLoadFlags(mChannel, nsIChannel::LOAD_EXPLICIT_CREDENTIALS);
}
NS_ASSERTION(listener != this,
"Using an object as a listener that can't be exposed to JS");
// When we are sync loading, we need to bypass the local cache when it would
// otherwise block us waiting for exclusive access to the cache. If we don't
// do this, then we could dead lock in some cases (see bug 309424).
@ -2906,10 +2849,19 @@ nsXMLHttpRequest::Send(nsIVariant* aVariant, const Nullable<RequestBody>& aBody)
mRequestSentTime = PR_Now();
StartTimeoutTimer();
// Check if we need to do a preflight request.
if (!mCORSUnsafeHeaders.IsEmpty() ||
(mUpload && mUpload->HasListeners()) ||
(!method.LowerCaseEqualsLiteral("get") &&
!method.LowerCaseEqualsLiteral("post") &&
!method.LowerCaseEqualsLiteral("head"))) {
mState |= XML_HTTP_REQUEST_NEED_AC_PREFLIGHT_IF_XSITE;
}
// Set up the preflight if needed
if (mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT) {
// Check to see if this initial OPTIONS request has already been cached
// in our special Access Control Cache.
if ((mState & XML_HTTP_REQUEST_USE_XSITE_AC) &&
(mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT_IF_XSITE)) {
NS_ENSURE_TRUE(internalHttpChannel, NS_ERROR_DOM_BAD_URI);
rv = internalHttpChannel->SetCorsPreflightParameters(mCORSUnsafeHeaders,
withCredentials, mPrincipal);
@ -2932,16 +2884,30 @@ nsXMLHttpRequest::Send(nsIVariant* aVariant, const Nullable<RequestBody>& aBody)
}
}
// Hook us up to listen to redirects and the like
// Only do this very late since this creates a cycle between
// the channel and us. This cycle has to be manually broken if anything
// below fails.
mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
mChannel->SetNotificationCallbacks(this);
// Start reading from the channel
rv = mChannel->AsyncOpen(listener, nullptr);
// Because of bug 682305, we can't let listener be the XHR object itself
// because JS wouldn't be able to use it. So create a listener around 'this'.
// Make sure to hold a strong reference so that we don't leak the wrapper.
nsCOMPtr<nsIStreamListener> listener = new nsStreamListenerWrapper(this);
rv = mChannel->AsyncOpen2(listener);
listener = nullptr;
if (NS_WARN_IF(NS_FAILED(rv))) {
// Drop our ref to the channel to avoid cycles
// Drop our ref to the channel to avoid cycles. Also drop channel's
// ref to us to be extra safe.
mChannel->SetNotificationCallbacks(mNotificationCallbacks);
mChannel = nullptr;
return rv;
}
// Either AsyncOpen was called, or CORS will open the channel later.
mWaitingForOnStopRequest = true;
// If we're synchronous, spin an event loop here and wait
@ -3384,17 +3350,12 @@ nsXMLHttpRequest::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
nsresult rv;
if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) {
rv = CheckChannelForCrossSiteRequest(aNewChannel);
if (NS_FAILED(rv)) {
NS_WARNING("nsXMLHttpRequest::OnChannelRedirect: "
"CheckChannelForCrossSiteRequest returned failure");
return rv;
}
CheckChannelForCrossSiteRequest(aNewChannel);
// Disable redirects for preflighted cross-site requests entirely for now
// Note, do this after the call to CheckChannelForCrossSiteRequest
// to make sure that XML_HTTP_REQUEST_USE_XSITE_AC is up-to-date
if ((mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT)) {
if ((mState & XML_HTTP_REQUEST_USE_XSITE_AC) &&
(mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT_IF_XSITE)) {
aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
return NS_ERROR_DOM_BAD_URI;
}
}
@ -3555,7 +3516,7 @@ bool
nsXMLHttpRequest::AllowUploadProgress()
{
return !(mState & XML_HTTP_REQUEST_USE_XSITE_AC) ||
(mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT);
(mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT_IF_XSITE);
}
/////////////////////////////////////////////////////

View File

@ -626,7 +626,7 @@ protected:
*
* Also updates the XML_HTTP_REQUEST_USE_XSITE_AC bit.
*/
nsresult CheckChannelForCrossSiteRequest(nsIChannel* aChannel);
void CheckChannelForCrossSiteRequest(nsIChannel* aChannel);
void StartProgressEventTimer();

View File

@ -50,28 +50,6 @@ function info(aMessage) {
message(obj);
}
function todo(aBool, aMessage) {
var obj = {
type: "todo",
bool: aBool,
message: aMessage
};
++message.ping;
message(obj);
}
function todo_is(aActual, aExpected, aMessage) {
var obj = {
type: "todo_is",
actual: aActual,
expected: aExpected,
message: aMessage
};
++message.ping;
message(obj);
}
function request(aURL) {
return new Promise(function (aResolve, aReject) {
var xhr = new XMLHttpRequest();
@ -125,15 +103,11 @@ function testSuccessResponse() {
message: "request to same-origin redirect to cross-origin URL",
requestURL: "http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://example.com/tests/dom/base/test/file_XHRResponseURL.text",
responseURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.text",
skip: isInWorker(),
reason: "cross-origin redirect request not works on Workers, see bug 882458"
},
{
message: "request to same-origin redirects several times and finally go to cross-origin URL",
requestURL: "http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://example.com/tests/dom/base/test/file_XHRResponseURL.text"),
responseURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.text",
skip: isInWorker(),
reason: "cross-origin redirect request not works on Workers, see bug 882458"
},
// tests that start with cross-origin request
@ -151,43 +125,31 @@ function testSuccessResponse() {
message: "request to cross-origin redirect to the same cross-origin URL",
requestURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://example.com/tests/dom/base/test/file_XHRResponseURL.text",
responseURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.text",
skip: isInWorker(),
reason: "cross-origin redirect request not works on Workers, see bug 882458"
},
{
message: "request to cross-origin redirect to another cross-origin URL",
requestURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://example.org/tests/dom/base/test/file_XHRResponseURL.text",
responseURL: "http://example.org/tests/dom/base/test/file_XHRResponseURL.text",
skip: isInWorker(),
reason: "cross-origin redirect request not works on Workers, see bug 882458"
},
{
message: "request to cross-origin redirects several times and finally go to same-origin URL",
requestURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.text"),
responseURL: "http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.text",
skip: isInWorker(),
reason: "cross-origin redirect request not works on Workers, see bug 882458"
},
{
message: "request to cross-origin redirects several times and finally go to the same cross-origin URL",
requestURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://example.com/tests/dom/base/test/file_XHRResponseURL.text"),
responseURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.text",
skip: isInWorker(),
reason: "cross-origin redirect request not works on Workers, see bug 882458"
},
{
message: "request to cross-origin redirects several times and finally go to another cross-origin URL",
requestURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://example.org/tests/dom/base/test/file_XHRResponseURL.text"),
responseURL: "http://example.org/tests/dom/base/test/file_XHRResponseURL.text",
skip: isInWorker(),
reason: "cross-origin redirect request not works on Workers, see bug 882458"
},
{
message: "request to cross-origin redirects to another cross-origin and finally go to the other cross-origin URL",
requestURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://example.org/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://test1.example.com/tests/dom/base/test/file_XHRResponseURL.text"),
responseURL: "http://test1.example.com/tests/dom/base/test/file_XHRResponseURL.text",
skip: isInWorker(),
reason: "cross-origin redirect request not works on Workers, see bug 882458"
},
{
message: "request URL has fragment",
@ -209,13 +171,8 @@ function testSuccessResponse() {
];
var sequence = createSequentialRequest(parameters, function (aXHR, aParam) {
if (aParam.skip) {
todo(aXHR.succeeded, aParam.reason);
todo_is(aXHR.responseURL, aParam.responseURL, aParam.reason);
} else {
ok(aXHR.succeeded, "assert request succeeded");
is(aXHR.responseURL, aParam.responseURL, aParam.message);
}
ok(aXHR.succeeded, "assert request succeeded");
is(aXHR.responseURL, aParam.responseURL, aParam.message);
});
sequence.then(function () {

View File

@ -510,7 +510,6 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop spec
[test_bug433533.html]
[test_bug433662.html]
[test_bug435425.html]
[test_bug438519.html]
[test_bug444030.xhtml]
[test_bug444322.html]
[test_bug444722.html]

View File

@ -1,44 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=438519
-->
<head>
<title>Test for Bug 438519</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body onload="doTest()">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=438519">Mozilla Bug 438519</a>
<p id="display"></p>
<div id="content" style="display:none">
<iframe id="empty" src="data:text/xml,<!DOCTYPE HTML []><html></html>"></iframe>
<iframe id="missing" src="data:text/xml,<!DOCTYPE HTML><html></html>"></iframe>
<iframe id="entity" src="data:text/xml,<!DOCTYPE HTML [ <!ENTITY foo 'foo'> ]><html></html>"></iframe>
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
/** Test for Bug 218236 **/
SimpleTest.waitForExplicitFinish();
function doTest() {
function checkInternalSubset(id, expected) {
var e = document.getElementById(id);
is(e.contentDocument.doctype.internalSubset, expected, "checking '" + id + "'");
}
checkInternalSubset("empty", "");
checkInternalSubset("missing", null);
checkInternalSubset("entity", " <!ENTITY foo 'foo'> ");
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

View File

@ -24,7 +24,6 @@ function checkDoc(title, expectedtitle, normalizedtitle) {
is(doc.doctype.name, "html");
is(doc.doctype.publicId, "");
is(doc.doctype.systemId, "");
is(doc.doctype.internalSubset, null, "internalSubset should be null!");
isElement(doc.documentElement, "html");
isElement(doc.documentElement.firstChild, "head");
if (title !== undefined) {

View File

@ -237,6 +237,58 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=887836
runTest();
}
function testIterable() {
var u = new URLSearchParams();
u.set('1','2');
u.set('2','4');
u.set('3','6');
u.set('4','8');
u.set('5','10');
var key_iter = u.keys();
var value_iter = u.values();
var entries_iter = u.entries();
for (var i = 0; i < 5; ++i) {
var v = i + 1;
var key = key_iter.next();
var value = value_iter.next();
var entry = entries_iter.next();
is(key.value, v.toString(), "Correct Key iterator: " + v.toString());
ok(!key.done, "Key.done is false");
is(value.value, (v * 2).toString(), "Correct Value iterator: " + (v * 2).toString());
ok(!value.done, "Value.done is false");
is(entry.value[0], v.toString(), "Correct Entry 0 iterator: " + v.toString());
is(entry.value[1], (v * 2).toString(), "Correct Entry 1 iterator: " + (v * 2).toString());
ok(!entry.done, "Entry.done is false");
}
var last = key_iter.next();
ok(last.done, "Nothing more to read.");
is(last.value, undefined, "Undefined is the last key");
last = value_iter.next();
ok(last.done, "Nothing more to read.");
is(last.value, undefined, "Undefined is the last value");
last = entries_iter.next();
ok(last.done, "Nothing more to read.");
key_iter = u.keys();
key_iter.next();
key_iter.next();
u.delete('1');
u.delete('2');
u.delete('3');
u.delete('4');
u.delete('5');
last = key_iter.next();
ok(last.done, "Nothing more to read.");
is(last.value, undefined, "Undefined is the last key");
runTest();
}
var tests = [
testSimpleURLSearchParams,
testCopyURLSearchParams,
@ -248,7 +300,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=887836
testOrdering,
testDelete,
testGetNULL,
testSet
testSet,
testIterable
];
function runTest() {

View File

@ -700,10 +700,6 @@ DOMInterfaces = {
'notflattened': True
},
'IterableIterator': {
'skipGen': True
},
'KeyEvent': {
'concrete': False
},

View File

@ -740,7 +740,7 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
"interface that already has %s "
"declaration" %
(member.maplikeOrSetlikeOrIterableType,
self.maplikeOrSetlike.maplikeOrSetlikeOrIterableType),
self.maplikeOrSetlikeOrIterable.maplikeOrSetlikeOrIterableType),
[self.maplikeOrSetlikeOrIterable.location,
member.location])
self.maplikeOrSetlikeOrIterable = member
@ -6567,6 +6567,7 @@ class Parser(Tokenizer):
interfaceStatements = [p for p in self._productions if
isinstance(p, IDLInterface)]
iterableIteratorIface = None
for iface in interfaceStatements:
iterable = None
# We haven't run finish() on the interface yet, so we don't know
@ -6578,33 +6579,30 @@ class Parser(Tokenizer):
iterable = m
break
if iterable:
def simpleExtendedAttr(str):
return IDLExtendedAttribute(iface.location, (str, ))
nextMethod = IDLMethod(
iface.location,
IDLUnresolvedIdentifier(iface.location, "next"),
BuiltinTypes[IDLBuiltinType.Types.object], [])
nextMethod.addExtendedAttributes([simpleExtendedAttr("Throws")])
itr_ident = IDLUnresolvedIdentifier(iface.location,
iface.identifier.name + "Iterator")
itr_iface = IDLInterface(iface.location, self.globalScope(),
itr_ident, None, [],
itr_ident, None, [nextMethod],
isKnownNonPartial=True)
itr_iface.addExtendedAttributes([IDLExtendedAttribute(iface.location,
("NoInterfaceObject", ))])
itr_iface.addExtendedAttributes([simpleExtendedAttr("NoInterfaceObject")])
# Make sure the exposure set for the iterator interface is the
# same as the exposure set for the iterable interface, because
# we're going to generate methods on the iterable that return
# instances of the iterator.
itr_iface._exposureGlobalNames = set(iface._exposureGlobalNames)
# Always append generated iterable interfaces and their
# matching implements statements after the interface they're a
# member of, otherwise nativeType generation won't work
# correctly.
# Always append generated iterable interfaces after the
# interface they're a member of, otherwise nativeType generation
# won't work correctly.
itr_iface.iterableInterface = iface
self._productions.append(itr_iface)
iterable.iteratorType = IDLWrapperType(iface.location, itr_iface)
itrPlaceholder = IDLIdentifierPlaceholder(iface.location,
IDLUnresolvedIdentifier(iface.location,
"IterableIterator"))
implements = IDLImplementsStatement(iface.location,
IDLIdentifierPlaceholder(iface.location,
itr_ident),
itrPlaceholder)
self._productions.append(implements)
# Then, finish all the IDLImplementsStatements. In particular, we
# have to make sure we do those before we do the IDLInterfaces.

View File

@ -10,20 +10,24 @@ def WebIDLTest(parser, harness):
"%s - Should have production count %d" % (prefix, numProductions))
harness.ok(isinstance(results[0], WebIDL.IDLInterface),
"%s - Should be an IDLInterface" % (prefix))
harness.check(len(results[0].members), len(expectedMembers),
"%s - Should be %d members" % (prefix,
len(expectedMembers)))
# Make a copy, since we plan to modify it
expectedMembers = list(expectedMembers)
for m in results[0].members:
name = m.identifier.name
if (name, type(m)) in expectedMembers:
harness.ok(True, "%s - %s - Should be a %s" % (prefix, name,
type(m)))
elif isinstance(m, WebIDL.IDLMaplikeOrSetlike):
harness.ok(True, "%s - %s - Should be a MaplikeOrSetlike" %
(prefix, name))
expectedMembers.remove((name, type(m)))
else:
harness.ok(False, "%s - %s - Unknown symbol of type %s" %
(prefix, name, type(m)))
# A bit of a hoop because we can't generate the error string if we pass
if len(expectedMembers) == 0:
harness.ok(True, "Found all the members")
else:
harness.ok(False,
"Expected member not found: %s of type %s" %
(expectedMembers[0][0], expectedMembers[0][1]))
return results
def shouldFail(prefix, iface):
@ -38,11 +42,11 @@ def WebIDLTest(parser, harness):
prefix + " - Interface failed as expected")
except Exception, e:
harness.ok(False,
prefix + " - Interface failed but not as a WebIDLError exception")
prefix + " - Interface failed but not as a WebIDLError exception: %s" % e)
iterableMembers = [(x, WebIDL.IDLMethod) for x in ["entries", "keys",
"values"]]
setROMembers = ([(x, WebIDL.IDLMethod) for x in ["has", "foreach"]] +
setROMembers = ([(x, WebIDL.IDLMethod) for x in ["has", "forEach"]] +
[("__setlike", WebIDL.IDLMaplikeOrSetlike)] +
iterableMembers)
setROMembers.extend([("size", WebIDL.IDLAttribute)])
@ -58,7 +62,7 @@ def WebIDLTest(parser, harness):
"__clear",
"__delete"]] +
setRWMembers)
mapROMembers = ([(x, WebIDL.IDLMethod) for x in ["get", "has", "foreach"]] +
mapROMembers = ([(x, WebIDL.IDLMethod) for x in ["get", "has", "forEach"]] +
[("__maplike", WebIDL.IDLMaplikeOrSetlike)] +
iterableMembers)
mapROMembers.extend([("size", WebIDL.IDLAttribute)])
@ -70,6 +74,10 @@ def WebIDLTest(parser, harness):
"__delete"]] +
mapRWMembers)
# OK, now that we've used iterableMembers to set up the above, append
# __iterable to it for the iterable<> case.
iterableMembers.append(("__iterable", WebIDL.IDLIterable))
disallowedIterableNames = ["keys", "entries", "values"]
disallowedMemberNames = ["forEach", "has", "size"] + disallowedIterableNames
mapDisallowedMemberNames = ["get"] + disallowedMemberNames
@ -86,14 +94,18 @@ def WebIDLTest(parser, harness):
interface Foo1 {
iterable<long>;
};
""", iterableMembers)
""", iterableMembers,
# numProductions == 2 because of the generated iterator iface,
numProductions=2)
shouldPass("Iterable (key and value)",
"""
interface Foo1 {
iterable<long, long>;
};
""", iterableMembers)
""", iterableMembers,
# numProductions == 2 because of the generated iterator iface,
numProductions=2)
shouldPass("Maplike (readwrite)",
"""
@ -574,5 +586,5 @@ def WebIDLTest(parser, harness):
if m.identifier.name in ["clear", "set", "delete"]:
harness.ok(m.isMethod(), "%s should be a method" % m.identifier.name)
harness.check(m.maxArgCount, 4, "%s should have 4 arguments" % m.identifier.name)
harness.ok(not m.isMaplikeOrSetlikeMethod(),
harness.ok(not m.isMaplikeOrSetlikeOrIterableMethod(),
"%s should not be a maplike/setlike function" % m.identifier.name)

View File

@ -192,8 +192,7 @@ static_assert(int(HeadersGuardEnum::None) == 0 &&
static_assert(int(RequestMode::Same_origin) == 0 &&
int(RequestMode::No_cors) == 1 &&
int(RequestMode::Cors) == 2 &&
int(RequestMode::Cors_with_forced_preflight) == 3 &&
int(RequestMode::EndGuard_) == 4,
int(RequestMode::EndGuard_) == 3,
"RequestMode values are as expected");
static_assert(int(RequestCredentials::Omit) == 0 &&
int(RequestCredentials::Same_origin) == 1 &&

View File

@ -71,6 +71,11 @@ public:
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter);
void FramebufferTextureLayer(GLenum target, GLenum attachment, WebGLTexture* texture, GLint level, GLint layer);
virtual JS::Value GetFramebufferAttachmentParameter(JSContext* cx, GLenum target,
GLenum attachment, GLenum pname,
ErrorResult& rv) override;
void InvalidateFramebuffer(GLenum target, const dom::Sequence<GLenum>& attachments,
ErrorResult& rv);
void InvalidateSubFramebuffer (GLenum target, const dom::Sequence<GLenum>& attachments, GLint x, GLint y,

View File

@ -8,10 +8,17 @@
#include "GLContext.h"
#include "GLScreenBuffer.h"
#include "WebGLContextUtils.h"
#include "WebGLFormats.h"
#include "WebGLFramebuffer.h"
namespace mozilla {
using gl::GLContext;
using gl::GLFormats;
using webgl::EffectiveFormat;
using webgl::FormatInfo;
using webgl::ComponentType;
// Returns one of FLOAT, INT, UNSIGNED_INT.
// Fixed-points (normalized ints) are considered FLOAT.
static GLenum
@ -418,6 +425,103 @@ WebGL2Context::FramebufferTextureLayer(GLenum target, GLenum attachment,
fb->FramebufferTextureLayer(attachment, texture, level, layer);
}
JS::Value
WebGL2Context::GetFramebufferAttachmentParameter(JSContext* cx,
GLenum target,
GLenum attachment,
GLenum pname,
ErrorResult& rv)
{
if (IsContextLost())
return JS::NullValue();
// OpenGL ES 3.0.4 (August 27, 2014) 6.1. QUERYING GL STATE 240
// "getFramebufferAttachmentParamter returns information about attachments of a bound
// framebuffer object. target must be DRAW_FRAMEBUFFER, READ_FRAMEBUFFER, or
// FRAMEBUFFER."
if (!ValidateFramebufferTarget(target, "getFramebufferAttachmentParameter"))
return JS::NullValue();
// FRAMEBUFFER is equivalent to DRAW_FRAMEBUFFER.
if (target == LOCAL_GL_FRAMEBUFFER)
target = LOCAL_GL_DRAW_FRAMEBUFFER;
WebGLFramebuffer* boundFB = nullptr;
switch (target) {
case LOCAL_GL_DRAW_FRAMEBUFFER: boundFB = mBoundDrawFramebuffer; break;
case LOCAL_GL_READ_FRAMEBUFFER: boundFB = mBoundReadFramebuffer; break;
}
if (boundFB) {
return boundFB->GetAttachmentParameter(cx, attachment, pname, rv);
}
// Handle default FB
const gl::GLFormats& formats = gl->GetGLFormats();
GLenum internalFormat = LOCAL_GL_NONE;
/* If the default framebuffer is bound to target, then attachment must be BACK,
identifying the color buffer; DEPTH, identifying the depth buffer; or STENCIL,
identifying the stencil buffer. */
switch (attachment) {
case LOCAL_GL_BACK:
internalFormat = formats.color_texInternalFormat;
break;
case LOCAL_GL_DEPTH:
internalFormat = formats.depth;
break;
case LOCAL_GL_STENCIL:
internalFormat = formats.stencil;
break;
default:
ErrorInvalidEnum("getFramebufferAttachmentParameter: Can only query "
"attachment BACK, DEPTH, or STENCIL from default "
"framebuffer");
return JS::NullValue();
}
const FormatInfo* info = webgl::GetInfoBySizedFormat(internalFormat);
MOZ_RELEASE_ASSERT(info);
EffectiveFormat effectiveFormat = info->effectiveFormat;
switch (pname) {
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
return JS::Int32Value(LOCAL_GL_FRAMEBUFFER_DEFAULT);
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE:
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE:
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE:
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE:
return JS::Int32Value(webgl::GetComponentSize(effectiveFormat, pname));
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT &&
pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE)
{
ErrorInvalidOperation("getFramebufferAttachmentParameter: Querying "
"FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE against "
"DEPTH_STENCIL_ATTACHMENT is an error.");
return JS::NullValue();
}
return JS::Int32Value(webgl::GetComponentType(effectiveFormat));
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
return JS::Int32Value(webgl::GetColorEncoding(effectiveFormat));
}
/* Any combinations of framebuffer type and pname not described above will generate an
INVALID_ENUM error. */
ErrorInvalidEnum("getFramebufferAttachmentParameter: Invalid combination of ");
return JS::NullValue();
}
// Map attachments intended for the default buffer, to attachments for a non-
// default buffer.
static bool

View File

@ -458,9 +458,9 @@ public:
}
GLenum GetError();
JS::Value GetFramebufferAttachmentParameter(JSContext* cx, GLenum target,
GLenum attachment, GLenum pname,
ErrorResult& rv);
virtual JS::Value GetFramebufferAttachmentParameter(JSContext* cx, GLenum target,
GLenum attachment, GLenum pname,
ErrorResult& rv);
void GetFramebufferAttachmentParameter(JSContext* cx, GLenum target,
GLenum attachment, GLenum pname,

View File

@ -809,5 +809,319 @@ FormatUsageAuthority::AddUnpackOption(GLenum unpackFormat, GLenum unpackType,
MOZ_ALWAYS_TRUE(didInsert);
}
////////////////////////////////////////////////////////////////////////////////
struct ComponentSizes
{
GLubyte redSize;
GLubyte greenSize;
GLubyte blueSize;
GLubyte alphaSize;
GLubyte depthSize;
GLubyte stencilSize;
};
static ComponentSizes kComponentSizes[] = {
// GLES 3.0.4, p128-129, "Required Texture Formats"
// "Texture and renderbuffer color formats"
{ 32, 32, 32, 32, 0, 0 }, // RGBA32I,
{ 32, 32, 32, 32, 0, 0 }, // RGBA32UI,
{ 16, 16, 16, 16, 0, 0 }, // RGBA16I,
{ 16, 16, 16, 16, 0, 0 }, // RGBA16UI,
{ 8, 8, 8, 8, 0, 0 }, // RGBA8,
{ 8, 8, 8, 8, 0, 0 }, // RGBA8I,
{ 8, 8, 8, 8, 0, 0 }, // RGBA8UI,
{ 8, 8, 8, 8, 0, 0 }, // SRGB8_ALPHA8,
{ 10, 10, 10, 2, 0, 0 }, // RGB10_A2,
{ 10, 10, 10, 2, 0, 0 }, // RGB10_A2UI,
{ 4, 4, 4, 4, 0, 0 }, // RGBA4,
{ 5, 5, 5, 1, 0, 0 }, // RGB5_A1,
{ 8, 8, 8, 0, 0, 0 }, // RGB8,
{ 8, 8, 8, 0, 0, 0 }, // RGB565,
{ 32, 32, 0, 0, 0, 0 }, // RG32I,
{ 32, 32, 0, 0, 0, 0 }, // RG32UI,
{ 16, 16, 0, 0, 0, 0 }, // RG16I,
{ 16, 16, 0, 0, 0, 0 }, // RG16UI,
{ 8, 8, 0, 0, 0, 0 }, // RG8,
{ 8, 8, 0, 0, 0, 0 }, // RG8I,
{ 8, 8, 0, 0, 0, 0 }, // RG8UI,
{ 32, 0, 0, 0, 0, 0 }, // R32I,
{ 32, 0, 0, 0, 0, 0 }, // R32UI,
{ 16, 0, 0, 0, 0, 0 }, // R16I,
{ 16, 0, 0, 0, 0, 0 }, // R16UI,
{ 8, 0, 0, 0, 0, 0 }, // R8,
{ 8, 0, 0, 0, 0, 0 }, // R8I,
{ 8, 0, 0, 0, 0, 0 }, // R8UI,
// "Texture-only color formats"
{ 32, 32, 32, 32, 0, 0 }, // RGBA32F,
{ 16, 16, 16, 16, 0, 0 }, // RGBA16F,
{ 8, 8, 8, 8, 0, 0 }, // RGBA8_SNORM,
{ 32, 32, 32, 0, 0, 0 }, // RGB32F,
{ 32, 32, 32, 0, 0, 0 }, // RGB32I,
{ 32, 32, 32, 0, 0, 0 }, // RGB32UI,
{ 16, 16, 16, 0, 0, 0 }, // RGB16F,
{ 16, 16, 16, 0, 0, 0 }, // RGB16I,
{ 16, 16, 16, 0, 0, 0 }, // RGB16UI,
{ 8, 8, 8, 0, 0, 0 }, // RGB8_SNORM,
{ 8, 8, 8, 0, 0, 0 }, // RGB8I,
{ 8, 8, 8, 0, 0, 0 }, // RGB8UI,
{ 8, 8, 8, 0, 0, 0 }, // SRGB8,
{ 11, 11, 11, 0, 0, 0 }, // R11F_G11F_B10F,
{ 9, 9, 9, 0, 0, 0 }, // RGB9_E5,
{ 32, 32, 0, 0, 0, 0 }, // RG32F,
{ 16, 16, 0, 0, 0, 0 }, // RG16F,
{ 8, 8, 0, 0, 0, 0 }, // RG8_SNORM,
{ 32, 0, 0, 0, 0, 0 }, // R32F,
{ 16, 0, 0, 0, 0, 0 }, // R16F,
{ 8, 0, 0, 0, 0, 0 }, // R8_SNORM,
// "Depth formats"
{ 0, 0, 0, 0, 32, 0 }, // DEPTH_COMPONENT32F,
{ 0, 0, 0, 0, 24, 0 }, // DEPTH_COMPONENT24,
{ 0, 0, 0, 0, 16, 0 }, // DEPTH_COMPONENT16,
// "Combined depth+stencil formats"
{ 0, 0, 0, 0, 32, 8 }, // DEPTH32F_STENCIL0,
{ 0, 0, 0, 0, 24, 8 }, // DEPTH24_STENCIL8,
// GLES 3.0.4, p205-206, "Required Renderbuffer Formats"
{ 0, 0, 0, 0, 0, 8 }, // STENCIL_INDEX8,
// GLES 3.0.4, p128, table 3.12.
{ 8, 8, 8, 8, 0, 0 }, // Luminance8Alpha8,
{ 8, 8, 8, 0, 0, 0 }, // Luminance8,
{ 0, 0, 0, 8, 0, 0 }, // Alpha8,
// GLES 3.0.4, p147, table 3.19
// GLES 3.0.4, p286+, $C.1 "ETC Compressed Texture Image Formats"
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_R11_EAC,
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_SIGNED_R11_EAC,
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_RG11_EAC,
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_SIGNED_RG11_EAC,
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_RGB8_ETC2,
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_SRGB8_ETC2,
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2,
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2,
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_RGBA8_ETC2_EAC,
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_SRGB8_ALPHA8_ETC2_EAC,
// AMD_compressed_ATC_texture
{ 8, 8, 8, 0, 0, 0 }, // ATC_RGB_AMD,
{ 8, 8, 8, 8, 0, 0 }, // ATC_RGBA_EXPLICIT_ALPHA_AMD,
{ 8, 8, 8, 8, 0, 0 }, // ATC_RGBA_INTERPOLATED_ALPHA_AMD,
// EXT_texture_compression_s3tc
{ 8, 8, 8, 0, 0, 0 }, // COMPRESSED_RGB_S3TC_DXT1,
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_RGBA_S3TC_DXT1,
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_RGBA_S3TC_DXT3,
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_RGBA_S3TC_DXT5,
// IMG_texture_compression_pvrtc
{ 8, 8, 8, 0, 0, 0 }, // COMPRESSED_RGB_PVRTC_4BPPV1,
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_RGBA_PVRTC_4BPPV1,
{ 8, 8, 8, 0, 0, 0 }, // COMPRESSED_RGB_PVRTC_2BPPV1,
{ 8, 8, 8, 8, 0, 0 }, // COMPRESSED_RGBA_PVRTC_2BPPV1,
// OES_compressed_ETC1_RGB8_texture
{ 8, 8, 8, 0, 0, 0 }, // ETC1_RGB8,
// OES_texture_float
{ 32, 32, 32, 32, 0, 0 }, // Luminance32FAlpha32F,
{ 32, 32, 32, 0, 0, 0 }, // Luminance32F,
{ 0, 0, 0, 32, 0, 0 }, // Alpha32F,
// OES_texture_half_float
{ 16, 16, 16, 16, 0, 0 }, // Luminance16FAlpha16F,
{ 16, 16, 16, 0, 0, 0 }, // Luminance16F,
{ 0, 0, 0, 16, 0, 0 }, // Alpha16F,
{ 0, } // MAX
};
GLint
GetComponentSize(EffectiveFormat format, GLenum component)
{
ComponentSizes compSize = kComponentSizes[(int) format];
switch (component) {
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
case LOCAL_GL_RENDERBUFFER_RED_SIZE:
case LOCAL_GL_RED_BITS:
return compSize.redSize;
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
case LOCAL_GL_RENDERBUFFER_GREEN_SIZE:
case LOCAL_GL_GREEN_BITS:
return compSize.greenSize;
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE:
case LOCAL_GL_RENDERBUFFER_BLUE_SIZE:
case LOCAL_GL_BLUE_BITS:
return compSize.blueSize;
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE:
case LOCAL_GL_RENDERBUFFER_ALPHA_SIZE:
case LOCAL_GL_ALPHA_BITS:
return compSize.alphaSize;
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE:
case LOCAL_GL_RENDERBUFFER_DEPTH_SIZE:
case LOCAL_GL_DEPTH_BITS:
return compSize.depthSize;
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE:
case LOCAL_GL_RENDERBUFFER_STENCIL_SIZE:
case LOCAL_GL_STENCIL_BITS:
return compSize.stencilSize;
}
return 0;
}
static GLenum kComponentTypes[] = {
// GLES 3.0.4, p128-129, "Required Texture Formats"
// "Texture and renderbuffer color formats"
LOCAL_GL_INT, // RGBA32I,
LOCAL_GL_UNSIGNED_INT, // RGBA32UI,
LOCAL_GL_INT, // RGBA16I,
LOCAL_GL_UNSIGNED_INT, // RGBA16UI,
LOCAL_GL_UNSIGNED_NORMALIZED, // RGBA8,
LOCAL_GL_INT, // RGBA8I,
LOCAL_GL_UNSIGNED_INT, // RGBA8UI,
LOCAL_GL_UNSIGNED_NORMALIZED, // SRGB8_ALPHA8,
LOCAL_GL_UNSIGNED_NORMALIZED, // RGB10_A2,
LOCAL_GL_UNSIGNED_INT, // RGB10_A2UI,
LOCAL_GL_UNSIGNED_NORMALIZED, // RGBA4,
LOCAL_GL_UNSIGNED_NORMALIZED, // RGB5_A1,
LOCAL_GL_UNSIGNED_NORMALIZED, // RGB8,
LOCAL_GL_UNSIGNED_NORMALIZED, // RGB565,
LOCAL_GL_INT, // RG32I,
LOCAL_GL_UNSIGNED_INT, // RG32UI,
LOCAL_GL_INT, // RG16I,
LOCAL_GL_UNSIGNED_INT, // RG16UI,
LOCAL_GL_UNSIGNED_NORMALIZED, // RG8,
LOCAL_GL_INT, // RG8I,
LOCAL_GL_UNSIGNED_INT, // RG8UI,
LOCAL_GL_INT, // R32I,
LOCAL_GL_UNSIGNED_INT, // R32UI,
LOCAL_GL_INT, // R16I,
LOCAL_GL_UNSIGNED_INT, // R16UI,
LOCAL_GL_UNSIGNED_NORMALIZED, // R8,
LOCAL_GL_INT, // R8I,
LOCAL_GL_UNSIGNED_INT, // R8UI,
// "Texture-only color formats"
LOCAL_GL_FLOAT, // RGBA32F,
LOCAL_GL_FLOAT, // RGBA16F,
LOCAL_GL_SIGNED_NORMALIZED, // RGBA8_SNORM,
LOCAL_GL_FLOAT, // RGB32F,
LOCAL_GL_INT, // RGB32I,
LOCAL_GL_UNSIGNED_INT, // RGB32UI,
LOCAL_GL_FLOAT, // RGB16F,
LOCAL_GL_INT, // RGB16I,
LOCAL_GL_UNSIGNED_INT, // RGB16UI,
LOCAL_GL_SIGNED_NORMALIZED, // RGB8_SNORM,
LOCAL_GL_INT, // RGB8I,
LOCAL_GL_UNSIGNED_INT, // RGB8UI,
LOCAL_GL_UNSIGNED_NORMALIZED, // SRGB8,
LOCAL_GL_FLOAT, // R11F_G11F_B10F,
LOCAL_GL_FLOAT, // RGB9_E5,
LOCAL_GL_FLOAT, // RG32F,
LOCAL_GL_FLOAT, // RG16F,
LOCAL_GL_SIGNED_NORMALIZED, // RG8_SNORM,
LOCAL_GL_FLOAT, // R32F,
LOCAL_GL_FLOAT, // R16F,
LOCAL_GL_SIGNED_NORMALIZED, // R8_SNORM,
// "Depth formats"
LOCAL_GL_FLOAT, // DEPTH_COMPONENT32F,
LOCAL_GL_UNSIGNED_NORMALIZED, // DEPTH_COMPONENT24,
LOCAL_GL_UNSIGNED_NORMALIZED, // DEPTH_COMPONENT16,
// "Combined depth+stencil formats"
LOCAL_GL_FLOAT, // DEPTH32F_STENCIL8,
LOCAL_GL_UNSIGNED_NORMALIZED, // DEPTH24_STENCIL8,
// GLES 3.0.4, p205-206, "Required Renderbuffer Formats"
LOCAL_GL_UNSIGNED_NORMALIZED, // STENCIL_INDEX8,
// GLES 3.0.4, p128, table 3.12.
LOCAL_GL_UNSIGNED_NORMALIZED, // Luminance8Alpha8,
LOCAL_GL_UNSIGNED_NORMALIZED, // Luminance8,
LOCAL_GL_UNSIGNED_NORMALIZED, // Alpha8,
// GLES 3.0.4, p147, table 3.19
// GLES 3.0.4, p286+, $C.1 "ETC Compressed Texture Image Formats"
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_R11_EAC,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_SIGNED_R11_EAC,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_RG11_EAC,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_SIGNED_RG11_EAC,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_RGB8_ETC2,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_SRGB8_ETC2,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_RGBA8_ETC2_EAC,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_SRGB8_ALPHA8_ETC2_EAC,
// AMD_compressed_ATC_texture
LOCAL_GL_UNSIGNED_NORMALIZED, // ATC_RGB_AMD,
LOCAL_GL_UNSIGNED_NORMALIZED, // ATC_RGBA_EXPLICIT_ALPHA_AMD,
LOCAL_GL_UNSIGNED_NORMALIZED, // ATC_RGBA_INTERPOLATED_ALPHA_AMD,
// EXT_texture_compression_s3tc
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_RGB_S3TC_DXT1,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_RGBA_S3TC_DXT1,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_RGBA_S3TC_DXT3,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_RGBA_S3TC_DXT5,
// IMG_texture_compression_pvrtc
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_RGB_PVRTC_4BPPV1,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_RGBA_PVRTC_4BPPV1,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_RGB_PVRTC_2BPPV1,
LOCAL_GL_UNSIGNED_NORMALIZED, // COMPRESSED_RGBA_PVRTC_2BPPV1,
// OES_compressed_ETC1_RGB8_texture
LOCAL_GL_UNSIGNED_NORMALIZED, // ETC1_RGB8,
// OES_texture_float
LOCAL_GL_FLOAT, // Luminance32FAlpha32F,
LOCAL_GL_FLOAT, // Luminance32F,
LOCAL_GL_FLOAT, // Alpha32F,
// OES_texture_half_float
LOCAL_GL_FLOAT, // Luminance16FAlpha16F,
LOCAL_GL_FLOAT, // Luminance16F,
LOCAL_GL_FLOAT, // Alpha16F,
LOCAL_GL_NONE // MAX
};
GLenum
GetComponentType(EffectiveFormat format)
{
return kComponentTypes[(int) format];
}
GLenum
GetColorEncoding(EffectiveFormat format)
{
const bool isSRGB = (GetFormatInfo(format)->colorComponentType ==
ComponentType::NormUIntSRGB);
return (isSRGB) ? LOCAL_GL_SRGB : LOCAL_GL_LINEAR;
}
} // namespace webgl
} // namespace mozilla

View File

@ -252,13 +252,21 @@ public:
EffectiveFormat effectiveFormat);
FormatUsageInfo* GetUsage(EffectiveFormat format);
FormatUsageInfo* GetUsage(const FormatInfo* format)
{
if (!format)
return nullptr;
return GetUsage(format->effectiveFormat);
}
};
////////////////////////////////////////////////////////////////////////////////
GLint GetComponentSize(EffectiveFormat format, GLenum component);
GLenum GetComponentType(EffectiveFormat format);
GLenum GetColorEncoding(EffectiveFormat format);
} // namespace webgl
} // namespace mozilla

View File

@ -433,6 +433,66 @@ WebGLFBAttachPoint::FinalizeAttachment(gl::GLContext* gl,
MOZ_CRASH();
}
JS::Value
WebGLFBAttachPoint::GetParameter(WebGLContext* context, GLenum pname)
{
// TODO: WebGLTexture and WebGLRenderbuffer should store FormatInfo instead of doing
// this dance every time.
const GLenum internalFormat = EffectiveInternalFormat().get();
const webgl::FormatInfo* info = webgl::GetInfoBySizedFormat(internalFormat);
MOZ_ASSERT(info);
WebGLTexture* tex = Texture();
switch (pname) {
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE:
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE:
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE:
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE:
return JS::Int32Value(webgl::GetComponentSize(info->effectiveFormat, pname));
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
return JS::Int32Value(webgl::GetComponentType(info->effectiveFormat));
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
return JS::Int32Value(webgl::GetColorEncoding(info->effectiveFormat));
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL:
if (tex) {
return JS::Int32Value(MipLevel());
}
break;
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE:
if (tex) {
int32_t face = 0;
if (tex->Target() == LOCAL_GL_TEXTURE_CUBE_MAP) {
face = ImageTarget().get();
}
return JS::Int32Value(face);
}
break;
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER:
if (tex) {
int32_t layer = 0;
if (tex->Target() == LOCAL_GL_TEXTURE_2D_ARRAY ||
tex->Target() == LOCAL_GL_TEXTURE_3D)
{
layer = Layer();
}
return JS::Int32Value(layer);
}
break;
}
context->ErrorInvalidEnum("getFramebufferParameter: Invalid combination of "
"attachment and pname.");
return JS::NullValue();
}
////////////////////////////////////////////////////////////////////////////////
// WebGLFramebuffer
@ -987,6 +1047,110 @@ WebGLFramebuffer::ValidateForRead(const char* info, TexInternalFormat* const out
return true;
}
static bool
AttachmentsDontMatch(const WebGLFBAttachPoint& a, const WebGLFBAttachPoint& b)
{
if (a.Texture()) {
return (a.Texture() != b.Texture());
}
if (a.Renderbuffer()) {
return (a.Renderbuffer() != b.Renderbuffer());
}
return false;
}
JS::Value
WebGLFramebuffer::GetAttachmentParameter(JSContext* cx,
GLenum attachment,
GLenum pname,
ErrorResult& rv)
{
// "If a framebuffer object is bound to target, then attachment must be one of the
// attachment points of the framebuffer listed in table 4.6."
switch (attachment) {
case LOCAL_GL_DEPTH_ATTACHMENT:
case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
break;
case LOCAL_GL_STENCIL_ATTACHMENT:
// "If attachment is DEPTH_STENCIL_ATTACHMENT, and different objects are bound to
// the depth and stencil attachment points of target, the query will fail and
// generate an INVALID_OPERATION error. If the same object is bound to both
// attachment points, information about that object will be returned."
// Does this mean it has to be the same level or layer? Because the queries are
// independent of level or layer.
if (AttachmentsDontMatch(DepthAttachment(), StencilAttachment())) {
mContext->ErrorInvalidOperation("getFramebufferAttachmentParameter: "
"DEPTH_ATTACHMENT and STENCIL_ATTACHMENT "
"have different objects bound.");
return JS::NullValue();
}
break;
default:
if (attachment < LOCAL_GL_COLOR_ATTACHMENT0 ||
attachment > mContext->LastColorAttachment())
{
mContext->ErrorInvalidEnum("getFramebufferAttachmentParameter: Can only "
"query COLOR_ATTACHMENTi, DEPTH_ATTACHMENT, "
"DEPTH_STENCIL_ATTACHMENT, or STENCIL_ATTACHMENT "
"on framebuffer.");
return JS::NullValue();
}
}
if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT &&
pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE)
{
mContext->ErrorInvalidOperation("getFramebufferAttachmentParameter: Querying "
"FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE against "
"DEPTH_STENCIL_ATTACHMENT is an error.");
return JS::NullValue();
}
GLenum objectType = LOCAL_GL_NONE;
auto& fba = GetAttachPoint(attachment);
if (fba.Texture()) {
objectType = LOCAL_GL_TEXTURE;
} else if (fba.Renderbuffer()) {
objectType = LOCAL_GL_RENDERBUFFER;
}
switch (pname) {
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
return JS::Int32Value(objectType);
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:
if (objectType == LOCAL_GL_NONE) {
return JS::NullValue();
}
if (objectType == LOCAL_GL_RENDERBUFFER) {
const WebGLRenderbuffer* rb = fba.Renderbuffer();
return mContext->WebGLObjectAsJSValue(cx, rb, rv);
}
/* If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is TEXTURE, then */
if (objectType == LOCAL_GL_TEXTURE) {
const WebGLTexture* tex = fba.Texture();
return mContext->WebGLObjectAsJSValue(cx, tex, rv);
}
break;
}
if (objectType == LOCAL_GL_NONE) {
mContext->ErrorInvalidOperation("getFramebufferAttachmentParameter: No "
"attachment at %s",
mContext->EnumName(attachment));
return JS::NullValue();
}
return fba.GetParameter(mContext, pname);
}
////////////////////////////////////////////////////////////////////////////////
// Goop.

View File

@ -62,7 +62,7 @@ public:
void SetTexImageLayer(WebGLTexture* tex, TexImageTarget target, GLint level,
GLint layer);
void SetRenderbuffer(WebGLRenderbuffer* rb);
const WebGLTexture* Texture() const {
return mTexturePtr;
}
@ -95,6 +95,8 @@ public:
void FinalizeAttachment(gl::GLContext* gl,
FBAttachment attachmentLoc) const;
JS::Value GetParameter(WebGLContext* context, GLenum pname);
};
class WebGLFramebuffer final
@ -225,6 +227,9 @@ public:
}
bool ValidateForRead(const char* info, TexInternalFormat* const out_format);
JS::Value GetAttachmentParameter(JSContext* cx, GLenum attachment, GLenum pname,
ErrorResult& rv);
};
} // namespace mozilla

View File

@ -19,8 +19,6 @@
#include "WebGLObjectModel.h"
template<class> class nsRefPtr;
namespace mozilla {
class ErrorResult;
class WebGLActiveInfo;

View File

@ -31,6 +31,7 @@
#include "mozilla/dom/FileList.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/OSFileSystem.h"
#include "mozilla/dom/Promise.h"
namespace mozilla {
namespace dom {

View File

@ -20,7 +20,6 @@
#include "mozilla/Attributes.h"
#include "mozilla/EventForwards.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/Promise.h"
class nsINode;
class nsITransferable;
@ -36,6 +35,7 @@ namespace dom {
class DOMStringList;
class Element;
class FileList;
class Promise;
template<typename T> class Optional;
/**

View File

@ -67,6 +67,23 @@ Touch::Touch(int32_t aIdentifier,
nsJSContext::LikelyShortLivingObjectCreated();
}
Touch::Touch(const Touch& aOther)
: mTarget(aOther.mTarget)
, mRefPoint(aOther.mRefPoint)
, mChanged(aOther.mChanged)
, mMessage(aOther.mMessage)
, mIdentifier(aOther.mIdentifier)
, mPagePoint(aOther.mPagePoint)
, mClientPoint(aOther.mClientPoint)
, mScreenPoint(aOther.mScreenPoint)
, mRadius(aOther.mRadius)
, mRotationAngle(aOther.mRotationAngle)
, mForce(aOther.mForce)
, mPointsInitialized(aOther.mPointsInitialized)
{
nsJSContext::LikelyShortLivingObjectCreated();
}
Touch::~Touch()
{
}

View File

@ -45,6 +45,7 @@ public:
nsIntPoint aRadius,
float aRotationAngle,
float aForce);
Touch(const Touch& aOther);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Touch)

View File

@ -186,10 +186,10 @@ TouchEvent::PrefEnabled(JSContext* aCx, JSObject* aGlobal)
int32_t flag = 0;
if (NS_SUCCEEDED(Preferences::GetInt("dom.w3c_touch_events.enabled", &flag))) {
if (flag == 2) {
#ifdef XP_WIN
#if defined(XP_WIN) || MOZ_WIDGET_GTK == 3
static bool sDidCheckTouchDeviceSupport = false;
static bool sIsTouchDeviceSupportPresent = false;
// On Windows we auto-detect based on device support.
// On Windows and GTK3 we auto-detect based on device support.
if (!sDidCheckTouchDeviceSupport) {
sDidCheckTouchDeviceSupport = true;
sIsTouchDeviceSupportPresent = WidgetUtils::IsTouchDeviceSupportPresent();

View File

@ -78,6 +78,7 @@ UNIFIED_SOURCES += [
'CommandEvent.cpp',
'CompositionEvent.cpp',
'ContentEventHandler.cpp',
'CustomEvent.cpp',
'DataContainerEvent.cpp',
'DataTransfer.cpp',
'DeviceMotionEvent.cpp',
@ -117,7 +118,6 @@ UNIFIED_SOURCES += [
# nsEventStateManager.cpp should be built separately because of Mac OS X headers.
SOURCES += [
'CustomEvent.cpp',
'EventStateManager.cpp',
]

View File

@ -50,9 +50,9 @@ FetchDriver::FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal,
: mPrincipal(aPrincipal)
, mLoadGroup(aLoadGroup)
, mRequest(aRequest)
, mFetchRecursionCount(0)
, mCORSFlagEverSet(false)
, mHasBeenCrossSite(false)
, mResponseAvailableCalled(false)
, mFetchCalled(false)
{
}
@ -67,323 +67,104 @@ nsresult
FetchDriver::Fetch(FetchDriverObserver* aObserver)
{
workers::AssertIsOnMainThread();
MOZ_ASSERT(!mFetchCalled);
mFetchCalled = true;
mObserver = aObserver;
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_REQUEST_PASSTHROUGH,
mRequest->WasCreatedByFetchEvent());
return Fetch(false /* CORS flag */);
// FIXME(nsm): Deal with HSTS.
MOZ_RELEASE_ASSERT(!mRequest->IsSynchronous(),
"Synchronous fetch not supported");
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(this, &FetchDriver::ContinueFetch);
return NS_DispatchToCurrentThread(r);
}
nsresult
FetchDriver::Fetch(bool aCORSFlag)
{
// We do not currently implement parts of the spec that lead to recursion.
MOZ_ASSERT(mFetchRecursionCount == 0);
mFetchRecursionCount++;
// FIXME(nsm): Deal with HSTS.
if (!mRequest->IsSynchronous() && mFetchRecursionCount <= 1) {
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethodWithArg<bool>(this, &FetchDriver::ContinueFetch, aCORSFlag);
nsresult rv = NS_DispatchToCurrentThread(r);
if (NS_WARN_IF(NS_FAILED(rv))) {
FailWithNetworkError();
}
return rv;
}
MOZ_CRASH("Synchronous fetch not supported");
}
FetchDriver::MainFetchOp
FetchDriver::SetTaintingAndGetNextOp(bool aCORSFlag)
FetchDriver::SetTainting()
{
workers::AssertIsOnMainThread();
// If we've already been cross-site then we should be fully updated
if (mHasBeenCrossSite) {
return NS_OK;
}
nsAutoCString url;
mRequest->GetURL(url);
nsCOMPtr<nsIURI> requestURI;
nsresult rv = NS_NewURI(getter_AddRefs(requestURI), url,
nullptr, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return MainFetchOp(NETWORK_ERROR);
}
// CSP/mixed content checks.
int16_t shouldLoad;
rv = NS_CheckContentLoadPolicy(mRequest->ContentPolicyType(),
requestURI,
mPrincipal,
mDocument,
// FIXME(nsm): Should MIME be extracted from
// Content-Type header?
EmptyCString(), /* mime guess */
nullptr, /* extra */
&shouldLoad,
nsContentUtils::GetContentPolicy(),
nsContentUtils::GetSecurityManager());
if (NS_WARN_IF(NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad))) {
// Disallowed by content policy.
return MainFetchOp(NETWORK_ERROR);
}
NS_ENSURE_SUCCESS(rv, rv);
// Begin Step 8 of the Main Fetch algorithm
// https://fetch.spec.whatwg.org/#fetching
nsAutoCString scheme;
rv = requestURI->GetScheme(scheme);
if (NS_WARN_IF(NS_FAILED(rv))) {
return MainFetchOp(NETWORK_ERROR);
}
// request's current url's origin is request's origin and the CORS flag is unset
// request's current url's scheme is "data" and request's same-origin data-URL flag is set
// request's current url's scheme is "about"
rv = mPrincipal->CheckMayLoad(requestURI, false /* report */,
false /* allowIfInheritsPrincipal */);
if ((!aCORSFlag && NS_SUCCEEDED(rv)) ||
(scheme.EqualsLiteral("data") && mRequest->SameOriginDataURL()) ||
scheme.EqualsLiteral("about")) {
return MainFetchOp(BASIC_FETCH);
// We have to manually check about:blank here since it's not treated as
// an inheriting URL by CheckMayLoad.
if (NS_IsAboutBlank(requestURI) ||
NS_SUCCEEDED(mPrincipal->CheckMayLoad(requestURI, false /* report */,
true /*allowIfInheritsPrincipal*/))) {
// What the spec calls "basic fetch" is handled within our necko channel
// code. Therefore everything goes through HTTP Fetch
return NS_OK;
}
mHasBeenCrossSite = true;
// request's mode is "same-origin"
if (mRequest->Mode() == RequestMode::Same_origin) {
return MainFetchOp(NETWORK_ERROR);
return NS_ERROR_DOM_BAD_URI;
}
// request's mode is "no-cors"
if (mRequest->Mode() == RequestMode::No_cors) {
mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUE);
return MainFetchOp(BASIC_FETCH);
}
// request's current url's scheme is not one of "http" and "https"
if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) {
return MainFetchOp(NETWORK_ERROR);
}
// request's mode is "cors-with-forced-preflight"
// request's unsafe-request flag is set and either request's method is not
// a simple method or a header in request's header list is not a simple header
if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight ||
(mRequest->UnsafeRequest() && (!mRequest->HasSimpleMethod() ||
!mRequest->Headers()->HasOnlySimpleHeaders()))) {
mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS);
mRequest->SetRedirectMode(RequestRedirect::Error);
// Note, the following text from Main Fetch step 8 is handled in
// nsCORSListenerProxy when CheckRequestApproved() fails:
//
// The result of performing an HTTP fetch using request with the CORS
// flag and CORS-preflight flag set. If the result is a network error,
// clear cache entries using request.
return MainFetchOp(HTTP_FETCH, true /* cors */, true /* preflight */);
// What the spec calls "basic fetch" is handled within our necko channel
// code. Therefore everything goes through HTTP Fetch
return NS_OK;
}
// Otherwise
mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS);
return MainFetchOp(HTTP_FETCH, true /* cors */, false /* preflight */);
return NS_OK;
}
nsresult
FetchDriver::ContinueFetch(bool aCORSFlag)
FetchDriver::ContinueFetch()
{
workers::AssertIsOnMainThread();
MainFetchOp nextOp = SetTaintingAndGetNextOp(aCORSFlag);
if (nextOp.mType == NETWORK_ERROR) {
return FailWithNetworkError();
}
if (nextOp.mType == BASIC_FETCH) {
return BasicFetch();
}
if (nextOp.mType == HTTP_FETCH) {
return HttpFetch(nextOp.mCORSFlag, nextOp.mCORSPreflightFlag);
}
MOZ_ASSERT_UNREACHABLE("Unexpected main fetch operation!");
return FailWithNetworkError();
}
nsresult
FetchDriver::BasicFetch()
{
nsAutoCString url;
mRequest->GetURL(url);
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri),
url,
nullptr,
nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
nsresult rv = HttpFetch();
if (NS_FAILED(rv)) {
FailWithNetworkError();
return rv;
}
nsAutoCString scheme;
rv = uri->GetScheme(scheme);
if (NS_WARN_IF(NS_FAILED(rv))) {
FailWithNetworkError();
return rv;
}
if (scheme.LowerCaseEqualsLiteral("about")) {
if (url.EqualsLiteral("about:blank")) {
RefPtr<InternalResponse> response =
new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
ErrorResult result;
response->Headers()->Append(NS_LITERAL_CSTRING("content-type"),
NS_LITERAL_CSTRING("text/html;charset=utf-8"),
result);
MOZ_ASSERT(!result.Failed());
nsCOMPtr<nsIInputStream> body;
rv = NS_NewCStringInputStream(getter_AddRefs(body), EmptyCString());
if (NS_WARN_IF(NS_FAILED(rv))) {
FailWithNetworkError();
return rv;
}
response->SetBody(body);
BeginResponse(response);
return SucceedWithResponse();
}
return FailWithNetworkError();
}
if (scheme.LowerCaseEqualsLiteral("blob")) {
RefPtr<BlobImpl> blobImpl;
rv = NS_GetBlobForBlobURI(uri, getter_AddRefs(blobImpl));
BlobImpl* blob = static_cast<BlobImpl*>(blobImpl.get());
if (NS_WARN_IF(NS_FAILED(rv))) {
FailWithNetworkError();
return rv;
}
RefPtr<InternalResponse> response = new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
ErrorResult result;
uint64_t size = blob->GetSize(result);
if (NS_WARN_IF(result.Failed())) {
FailWithNetworkError();
return result.StealNSResult();
}
nsAutoString sizeStr;
sizeStr.AppendInt(size);
response->Headers()->Append(NS_LITERAL_CSTRING("Content-Length"), NS_ConvertUTF16toUTF8(sizeStr), result);
if (NS_WARN_IF(result.Failed())) {
FailWithNetworkError();
return result.StealNSResult();
}
nsAutoString type;
blob->GetType(type);
response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), NS_ConvertUTF16toUTF8(type), result);
if (NS_WARN_IF(result.Failed())) {
FailWithNetworkError();
return result.StealNSResult();
}
nsCOMPtr<nsIInputStream> stream;
blob->GetInternalStream(getter_AddRefs(stream), result);
if (NS_WARN_IF(result.Failed())) {
FailWithNetworkError();
return result.StealNSResult();
}
response->SetBody(stream);
BeginResponse(response);
return SucceedWithResponse();
}
if (scheme.LowerCaseEqualsLiteral("data")) {
nsAutoCString method;
mRequest->GetMethod(method);
if (method.LowerCaseEqualsASCII("get")) {
nsresult rv;
nsCOMPtr<nsIProtocolHandler> dataHandler =
do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "data", &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
nsCOMPtr<nsIChannel> channel;
rv = dataHandler->NewChannel(uri, getter_AddRefs(channel));
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
nsCOMPtr<nsIInputStream> stream;
rv = channel->Open(getter_AddRefs(stream));
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
// nsDataChannel will parse the data URI when it is Open()ed and set the
// correct content type and charset.
nsAutoCString contentType;
if (NS_SUCCEEDED(channel->GetContentType(contentType))) {
nsAutoCString charset;
if (NS_SUCCEEDED(channel->GetContentCharset(charset)) && !charset.IsEmpty()) {
contentType.AppendLiteral(";charset=");
contentType.Append(charset);
}
} else {
NS_WARNING("Could not get content type from data channel");
}
RefPtr<InternalResponse> response = new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
ErrorResult result;
response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, result);
if (NS_WARN_IF(result.Failed())) {
FailWithNetworkError();
return result.StealNSResult();
}
response->SetBody(stream);
BeginResponse(response);
return SucceedWithResponse();
}
return FailWithNetworkError();
}
if (scheme.LowerCaseEqualsLiteral("http") ||
scheme.LowerCaseEqualsLiteral("https") ||
scheme.LowerCaseEqualsLiteral("app")) {
return HttpFetch();
}
return FailWithNetworkError();
return rv;
}
// This function implements the "HTTP Fetch" algorithm from the Fetch spec.
// Functionality is often split between here, the CORS listener proxy and the
// Necko HTTP implementation.
nsresult
FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthenticationFlag)
FetchDriver::HttpFetch()
{
// Step 1. "Let response be null."
mResponse = nullptr;
nsresult rv;
// We need to track the CORS flag through redirects. Since there is no way
// for us to go from CORS mode to non-CORS mode, we just need to remember
// if it has ever been set.
mCORSFlagEverSet = mCORSFlagEverSet || aCORSFlag;
nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
FailWithNetworkError();
return rv;
}
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString url;
mRequest->GetURL(url);
@ -393,10 +174,10 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
nullptr,
nullptr,
ios);
if (NS_WARN_IF(NS_FAILED(rv))) {
FailWithNetworkError();
return rv;
}
NS_ENSURE_SUCCESS(rv, rv);
rv = SetTainting();
NS_ENSURE_SUCCESS(rv, rv);
// Step 2 deals with letting ServiceWorkers intercept requests. This is
// handled by Necko after the channel is opened.
@ -419,19 +200,18 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
// - request's credentials mode is "same-origin" and either the CORS flag
// is unset or response tainting is "opaque"
// is true, and unset otherwise."
bool useCredentials = false;
if (mRequest->GetCredentialsMode() == RequestCredentials::Include ||
(mRequest->GetCredentialsMode() == RequestCredentials::Same_origin && !aCORSFlag &&
mRequest->GetResponseTainting() != InternalRequest::RESPONSETAINT_OPAQUE)) {
useCredentials = true;
}
// This is effectivetly the opposite of the use credentials flag in "HTTP
// network or cache fetch" in the spec and decides whether to transmit
// cookies and other identifying information. LOAD_ANONYMOUS also prevents
// new cookies sent by the server from being stored. This value will
// propagate across redirects, which is what we want.
const nsLoadFlags credentialsFlag = useCredentials ? 0 : nsIRequest::LOAD_ANONYMOUS;
const nsLoadFlags credentialsFlag =
(mRequest->GetCredentialsMode() == RequestCredentials::Omit ||
(mHasBeenCrossSite &&
mRequest->GetCredentialsMode() == RequestCredentials::Same_origin &&
mRequest->Mode() == RequestMode::No_cors)) ?
nsIRequest::LOAD_ANONYMOUS : 0;
// Set skip serviceworker flag.
// While the spec also gates on the client being a ServiceWorker, we can't
@ -439,24 +219,56 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
const nsLoadFlags bypassFlag = mRequest->SkipServiceWorker() ?
nsIChannel::LOAD_BYPASS_SERVICE_WORKER : 0;
nsSecurityFlags secFlags;
if (mRequest->Mode() == RequestMode::Cors &&
mRequest->GetCredentialsMode() == RequestCredentials::Include) {
secFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS |
nsILoadInfo::SEC_REQUIRE_CORS_WITH_CREDENTIALS;
} else if (mRequest->Mode() == RequestMode::Cors) {
secFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
} else if (mRequest->Mode() == RequestMode::Same_origin) {
secFlags = nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS;
} else if (mRequest->Mode() == RequestMode::No_cors) {
secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
} else {
MOZ_ASSERT_UNREACHABLE("Unexpected request mode!");
return NS_ERROR_UNEXPECTED;
}
// From here on we create a channel and set its properties with the
// information from the InternalRequest. This is an implementation detail.
MOZ_ASSERT(mLoadGroup);
nsCOMPtr<nsIChannel> chan;
rv = NS_NewChannel(getter_AddRefs(chan),
uri,
mPrincipal,
nsILoadInfo::SEC_NORMAL,
mRequest->ContentPolicyType(),
mLoadGroup,
nullptr, /* aCallbacks */
nsIRequest::LOAD_NORMAL | credentialsFlag | bypassFlag,
ios);
mLoadGroup = nullptr;
if (NS_WARN_IF(NS_FAILED(rv))) {
FailWithNetworkError();
return rv;
// For dedicated workers mDocument refers to the parent document of the
// worker (why do we do that?). In that case we don't want to use the
// document here since that is not the correct principal.
if (mDocument && mDocument->NodePrincipal() == mPrincipal) {
rv = NS_NewChannel(getter_AddRefs(chan),
uri,
mDocument,
secFlags |
nsILoadInfo::SEC_ABOUT_BLANK_INHERITS,
mRequest->ContentPolicyType(),
mLoadGroup,
nullptr, /* aCallbacks */
nsIRequest::LOAD_NORMAL | credentialsFlag | bypassFlag,
ios);
} else {
rv = NS_NewChannel(getter_AddRefs(chan),
uri,
mPrincipal,
secFlags |
nsILoadInfo::SEC_ABOUT_BLANK_INHERITS,
mRequest->ContentPolicyType(),
mLoadGroup,
nullptr, /* aCallbacks */
nsIRequest::LOAD_NORMAL | credentialsFlag | bypassFlag,
ios);
}
NS_ENSURE_SUCCESS(rv, rv);
mLoadGroup = nullptr;
// Insert ourselves into the notification callbacks chain so we can handle
// cross-origin redirects.
@ -485,10 +297,7 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
nsAutoCString method;
mRequest->GetMethod(method);
rv = httpChan->SetRequestMethod(method);
if (NS_WARN_IF(NS_FAILED(rv))) {
FailWithNetworkError();
return rv;
}
NS_ENSURE_SUCCESS(rv, rv);
// Set the same headers.
nsAutoTArray<InternalHeaders::Entry, 5> headers;
@ -508,14 +317,10 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
rv = nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal,
mDocument,
httpChan);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
NS_ENSURE_SUCCESS(rv, rv);
} else if (referrer.IsEmpty()) {
rv = httpChan->SetReferrerWithPolicy(nullptr, net::RP_No_Referrer);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
NS_ENSURE_SUCCESS(rv, rv);
} else {
// From "Determine request's Referrer" step 3
// "If request's referrer is a URL, let referrerSource be request's
@ -527,26 +332,21 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
// someone tries to use FetchDriver for non-fetch() APIs?
nsCOMPtr<nsIURI> referrerURI;
rv = NS_NewURI(getter_AddRefs(referrerURI), referrer, nullptr, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
NS_ENSURE_SUCCESS(rv, rv);
rv =
httpChan->SetReferrerWithPolicy(referrerURI,
mDocument ? mDocument->GetReferrerPolicy() :
net::RP_Default);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
NS_ENSURE_SUCCESS(rv, rv);
}
// Step 3 "If HTTPRequest's force Origin header flag is set..."
if (mRequest->ForceOriginHeader()) {
nsAutoString origin;
rv = nsContentUtils::GetUTFOrigin(mPrincipal, origin);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
NS_ENSURE_SUCCESS(rv, rv);
httpChan->SetRequestHeader(NS_LITERAL_CSTRING("origin"),
NS_ConvertUTF16toUTF8(origin),
false /* merge */);
@ -577,7 +377,7 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
// This is an error because the Request constructor explicitly extracts and
// sets a content-type per spec.
if (result.Failed()) {
return FailWithNetworkError();
return result.StealNSResult();
}
nsCOMPtr<nsIInputStream> bodyStream;
@ -586,77 +386,49 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
nsAutoCString method;
mRequest->GetMethod(method);
rv = uploadChan->ExplicitSetUploadStream(bodyStream, contentType, -1, method, false /* aStreamHasHeaders */);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
NS_ENSURE_SUCCESS(rv, rv);
}
}
nsCOMPtr<nsIStreamListener> listener = this;
MOZ_ASSERT_IF(aCORSFlag, mRequest->Mode() == RequestMode::Cors);
// Only use nsCORSListenerProxy if we are in CORS mode. Otherwise it
// will overwrite the CorsMode flag unconditionally to "cors" or
// "cors-with-forced-preflight".
if (mRequest->Mode() == RequestMode::Cors) {
// Passing false for the credentials flag to nsCORSListenerProxy is semantically
// the same as the "same-origin" RequestCredentials value. We implement further
// blocking of credentials for "omit" by setting LOAD_ANONYMOUS manually above.
bool corsCredentials =
mRequest->GetCredentialsMode() == RequestCredentials::Include;
// Set up a CORS proxy that will handle the various requirements of the CORS
// protocol. It handles the preflight cache and CORS response headers.
// If the request is allowed, it will start our original request
// and our observer will be notified. On failure, our observer is notified
// directly.
RefPtr<nsCORSListenerProxy> corsListener =
new nsCORSListenerProxy(this, mPrincipal, corsCredentials);
rv = corsListener->Init(chan, DataURIHandling::Allow);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
listener = corsListener.forget();
}
// If preflight is required, start a "CORS preflight fetch"
// https://fetch.spec.whatwg.org/#cors-preflight-fetch-0. All the
// implementation is handled by NS_StartCORSPreflight, we just set up the
// unsafeHeaders so they can be verified against the response's
// "Access-Control-Allow-Headers" header.
if (aCORSPreflightFlag) {
MOZ_ASSERT(mRequest->Mode() != RequestMode::No_cors,
"FetchDriver::ContinueFetch() should ensure that the request is not no-cors");
MOZ_ASSERT(httpChan, "CORS preflight can only be used with HTTP channels");
// implementation is handled by the http channel calling into
// nsCORSListenerProxy. We just inform it which unsafe headers are included
// in the request.
if (IsUnsafeRequest()) {
if (mRequest->Mode() == RequestMode::No_cors) {
return NS_ERROR_DOM_BAD_URI;
}
mRequest->SetRedirectMode(RequestRedirect::Error);
nsAutoTArray<nsCString, 5> unsafeHeaders;
mRequest->Headers()->GetUnsafeHeaders(unsafeHeaders);
nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(httpChan);
rv = internalChan->SetCorsPreflightParameters(unsafeHeaders, useCredentials, mPrincipal);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
NS_ENSURE_TRUE(internalChan, NS_ERROR_DOM_BAD_URI);
rv = internalChan->SetCorsPreflightParameters(
unsafeHeaders,
mRequest->GetCredentialsMode() == RequestCredentials::Include,
mPrincipal);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = chan->AsyncOpen(listener, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
rv = chan->AsyncOpen2(this);
NS_ENSURE_SUCCESS(rv, rv);
// Step 4 onwards of "HTTP Fetch" is handled internally by Necko.
return NS_OK;
}
nsresult
FetchDriver::ContinueHttpFetchAfterNetworkFetch()
bool
FetchDriver::IsUnsafeRequest()
{
workers::AssertIsOnMainThread();
MOZ_ASSERT(mResponse);
MOZ_ASSERT(!mResponse->IsError());
return SucceedWithResponse();
return mHasBeenCrossSite &&
(mRequest->UnsafeRequest() &&
(!mRequest->HasSimpleMethod() ||
!mRequest->Headers()->HasOnlySimpleHeaders()));
}
already_AddRefed<InternalResponse>
@ -699,24 +471,6 @@ FetchDriver::BeginAndGetFilteredResponse(InternalResponse* aResponse, nsIURI* aF
return filteredResponse.forget();
}
void
FetchDriver::BeginResponse(InternalResponse* aResponse)
{
RefPtr<InternalResponse> r = BeginAndGetFilteredResponse(aResponse, nullptr);
// Release the ref.
}
nsresult
FetchDriver::SucceedWithResponse()
{
workers::AssertIsOnMainThread();
if (mObserver) {
mObserver->OnResponseEnd();
mObserver = nullptr;
}
return NS_OK;
}
nsresult
FetchDriver::FailWithNetworkError()
{
@ -785,7 +539,10 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest,
MOZ_ASSERT(mObserver);
RefPtr<InternalResponse> response;
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(aRequest);
if (httpChannel) {
uint32_t responseStatus;
httpChannel->GetResponseStatus(&responseStatus);
@ -800,11 +557,7 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest,
if (NS_WARN_IF(NS_FAILED(rv))) {
NS_WARNING("Failed to visit all headers.");
}
} else {
nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(aRequest);
// If it is not an http channel, it has to be a jar one.
MOZ_ASSERT(jarChannel);
} else if (jarChannel) {
// We simulate the http protocol for jar/app requests
uint32_t responseStatus = 200;
nsAutoCString statusText;
@ -816,6 +569,35 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest,
contentType,
result);
MOZ_ASSERT(!result.Failed());
} else {
response = new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
ErrorResult result;
nsAutoCString contentType;
rv = channel->GetContentType(contentType);
if (NS_SUCCEEDED(rv) && !contentType.IsEmpty()) {
nsAutoCString contentCharset;
channel->GetContentCharset(contentCharset);
if (NS_SUCCEEDED(rv) && !contentCharset.IsEmpty()) {
contentType += NS_LITERAL_CSTRING(";charset=") + contentCharset;
}
response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"),
contentType,
result);
MOZ_ASSERT(!result.Failed());
}
int64_t contentLength;
rv = channel->GetContentLength(&contentLength);
if (NS_SUCCEEDED(rv) && contentLength) {
nsAutoCString contentLenStr;
contentLenStr.AppendInt(contentLength);
response->Headers()->Append(NS_LITERAL_CSTRING("Content-Length"),
contentLenStr,
result);
MOZ_ASSERT(!result.Failed());
}
}
// We open a pipe so that we can immediately set the pipe's read end as the
@ -838,7 +620,6 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest,
}
response->SetBody(pipeInputStream);
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
response->InitChannelInfo(channel);
nsCOMPtr<nsIURI> channelURI;
@ -899,17 +680,23 @@ FetchDriver::OnStopRequest(nsIRequest* aRequest,
if (outputStream) {
outputStream->CloseWithStatus(NS_BINDING_FAILED);
}
// We proceed as usual here, since we've already created a successful response
// from OnStartRequest.
SucceedWithResponse();
return aStatusCode;
} else {
MOZ_ASSERT(mResponse);
MOZ_ASSERT(!mResponse->IsError());
if (mPipeOutputStream) {
mPipeOutputStream->Close();
}
}
if (mPipeOutputStream) {
mPipeOutputStream->Close();
if (mObserver) {
mObserver->OnResponseEnd();
mObserver = nullptr;
}
ContinueHttpFetchAfterNetworkFetch();
return NS_OK;
}
@ -922,6 +709,12 @@ FetchDriver::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
{
NS_PRECONDITION(aNewChannel, "Redirect without a channel?");
if (NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags) ||
NS_IsHSTSUpgradeRedirect(aOldChannel, aNewChannel, aFlags)) {
aCallback->OnRedirectVerifyCallback(NS_OK);
return NS_OK;
}
// HTTP Fetch step 5, "redirect status", step 1
if (NS_WARN_IF(mRequest->GetRedirectMode() == RequestRedirect::Error)) {
aOldChannel->Cancel(NS_BINDING_FAILED);
@ -936,10 +729,8 @@ FetchDriver::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
// count are done by Necko. The pref used is "network.http.redirection-limit"
// which is set to 20 by default.
// HTTP Fetch Step 9, "redirect status". We only unset this for spec
// compatibility. Any actions we take on mRequest here do not affect what the
//channel does.
mRequest->UnsetSameOriginDataURL();
// HTTP Fetch Step 9, "redirect status". This is enforced by the
// nsCORSListenerProxy. It forbids redirecting to data:
// HTTP Fetch step 5, "redirect status", step 10 requires us to halt the
// redirect, but successfully return an opaqueredirect Response to the
@ -994,63 +785,68 @@ FetchDriver::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
mRequest->SetURL(newUrl);
// Implement Main Fetch step 8 again on redirect.
MainFetchOp nextOp = SetTaintingAndGetNextOp(mCORSFlagEverSet);
if (nextOp.mType == NETWORK_ERROR) {
// Cancel the channel if Main Fetch blocks the redirect from continuing.
aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
return NS_ERROR_DOM_BAD_URI;
}
// Otherwise, we rely on necko and the CORS proxy to do the right thing
// as the redirect is followed. In general this means basic or http
// fetch. If we've ever been CORS, we need to stay CORS.
MOZ_ASSERT(nextOp.mType == BASIC_FETCH || nextOp.mType == HTTP_FETCH);
MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mType == HTTP_FETCH);
MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mCORSFlag);
// Examine and possibly set the LOAD_ANONYMOUS flag on the channel.
nsLoadFlags flags;
rv = aNewChannel->GetLoadFlags(&flags);
rv = SetTainting();
if (NS_FAILED(rv)) {
aOldChannel->Cancel(rv);
return rv;
}
if (mRequest->GetCredentialsMode() == RequestCredentials::Same_origin &&
mRequest->GetResponseTainting() == InternalRequest::RESPONSETAINT_OPAQUE) {
// Requests that require preflight are not permitted to redirect.
// Fetch spec section 4.2 "HTTP Fetch", step 4.9 just uses the manual
// redirect flag to decide whether to execute step 4.10 or not. We do not
// represent it in our implementation.
// The only thing we do is to check if the request requires a preflight (part
// of step 4.9), in which case we abort. This part cannot be done by
// nsCORSListenerProxy since it does not have access to mRequest.
// which case. Step 4.10.3 is handled by OnRedirectVerifyCallback(), and all
// the other steps are handled by nsCORSListenerProxy.
if (IsUnsafeRequest()) {
// We can't handle redirects that require preflight yet.
// This is especially true for no-cors requests, which much always be
// blocked if they require preflight.
// Simply fire an error here.
aOldChannel->Cancel(NS_BINDING_FAILED);
return NS_BINDING_FAILED;
}
// Otherwise, we rely on necko and the CORS proxy to do the right thing
// as the redirect is followed. In general this means http
// fetch. If we've ever been CORS, we need to stay CORS.
// Possibly set the LOAD_ANONYMOUS flag on the channel.
if (mHasBeenCrossSite &&
mRequest->GetCredentialsMode() == RequestCredentials::Same_origin &&
mRequest->Mode() == RequestMode::No_cors) {
// In the case of a "no-cors" mode request with "same-origin" credentials,
// we have to set LOAD_ANONYMOUS manually here in order to avoid sending
// credentials on a cross-origin redirect.
flags |= nsIRequest::LOAD_ANONYMOUS;
rv = aNewChannel->SetLoadFlags(flags);
nsLoadFlags flags;
rv = aNewChannel->GetLoadFlags(&flags);
if (NS_SUCCEEDED(rv)) {
flags |= nsIRequest::LOAD_ANONYMOUS;
rv = aNewChannel->SetLoadFlags(flags);
}
if (NS_FAILED(rv)) {
aOldChannel->Cancel(rv);
return rv;
}
} else if (mRequest->GetCredentialsMode() == RequestCredentials::Omit) {
// Make sure nothing in the redirect chain screws up our credentials
// settings. LOAD_ANONYMOUS must be set if we RequestCredentials is "omit".
MOZ_ASSERT(flags & nsIRequest::LOAD_ANONYMOUS);
} else if (mRequest->GetCredentialsMode() == RequestCredentials::Same_origin &&
nextOp.mCORSFlag) {
// We also want to verify the LOAD_ANONYMOUS flag is set when we are in
// "same-origin" credentials mode and the CORS flag is set. We can't
// unconditionally assert here, however, because the nsCORSListenerProxy
// will set the flag later in the redirect callback chain. Instead,
// perform a weaker assertion here by checking if CORS flag was set
// before this redirect. In that case LOAD_ANONYMOUS must still be set.
MOZ_ASSERT_IF(mCORSFlagEverSet, flags & nsIRequest::LOAD_ANONYMOUS);
} else {
// Otherwise, we should be sending credentials
MOZ_ASSERT(!(flags & nsIRequest::LOAD_ANONYMOUS));
}
// Track the CORSFlag through redirects.
mCORSFlagEverSet = mCORSFlagEverSet || nextOp.mCORSFlag;
#ifdef DEBUG
{
// Make sure nothing in the redirect chain screws up our credentials
// settings. LOAD_ANONYMOUS must be set if we RequestCredentials is "omit"
// or "same-origin".
nsLoadFlags flags;
aNewChannel->GetLoadFlags(&flags);
bool shouldBeAnon =
mRequest->GetCredentialsMode() == RequestCredentials::Omit ||
(mHasBeenCrossSite &&
mRequest->GetCredentialsMode() == RequestCredentials::Same_origin);
MOZ_ASSERT(!!(flags & nsIRequest::LOAD_ANONYMOUS) == shouldBeAnon);
}
#endif
aCallback->OnRedirectVerifyCallback(NS_OK);
@ -1063,33 +859,6 @@ FetchDriver::CheckListenerChain()
return NS_OK;
}
// Returns NS_OK if no preflight is required, error otherwise.
nsresult
FetchDriver::DoesNotRequirePreflight(nsIChannel* aChannel)
{
// If this is a same-origin request or the channel's URI inherits
// its principal, it's allowed.
if (nsContentUtils::CheckMayLoad(mPrincipal, aChannel, true)) {
return NS_OK;
}
// Check if we need to do a preflight request.
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
NS_ENSURE_TRUE(httpChannel, NS_ERROR_DOM_BAD_URI);
nsAutoCString method;
httpChannel->GetRequestMethod(method);
if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight ||
!mRequest->Headers()->HasOnlySimpleHeaders() ||
(!method.LowerCaseEqualsLiteral("get") &&
!method.LowerCaseEqualsLiteral("post") &&
!method.LowerCaseEqualsLiteral("head"))) {
return NS_ERROR_DOM_BAD_URI;
}
return NS_OK;
}
NS_IMETHODIMP
FetchDriver::GetInterface(const nsIID& aIID, void **aResult)
{
@ -1117,7 +886,7 @@ void
FetchDriver::SetDocument(nsIDocument* aDocument)
{
// Cannot set document after Fetch() has been called.
MOZ_ASSERT(mFetchRecursionCount == 0);
MOZ_ASSERT(!mFetchCalled);
mDocument = aDocument;
}

View File

@ -82,53 +82,27 @@ private:
nsCOMPtr<nsIOutputStream> mPipeOutputStream;
RefPtr<FetchDriverObserver> mObserver;
nsCOMPtr<nsIDocument> mDocument;
uint32_t mFetchRecursionCount;
bool mCORSFlagEverSet;
bool mHasBeenCrossSite;
DebugOnly<bool> mResponseAvailableCalled;
DebugOnly<bool> mFetchCalled;
FetchDriver() = delete;
FetchDriver(const FetchDriver&) = delete;
FetchDriver& operator=(const FetchDriver&) = delete;
~FetchDriver();
enum MainFetchOpType
{
NETWORK_ERROR,
BASIC_FETCH,
HTTP_FETCH,
NUM_MAIN_FETCH_OPS
};
struct MainFetchOp
{
explicit MainFetchOp(MainFetchOpType aType, bool aCORSFlag = false,
bool aCORSPreflightFlag = false)
: mType(aType), mCORSFlag(aCORSFlag),
mCORSPreflightFlag(aCORSPreflightFlag)
{ }
MainFetchOpType mType;
bool mCORSFlag;
bool mCORSPreflightFlag;
};
nsresult Fetch(bool aCORSFlag);
MainFetchOp SetTaintingAndGetNextOp(bool aCORSFlag);
nsresult ContinueFetch(bool aCORSFlag);
nsresult BasicFetch();
nsresult HttpFetch(bool aCORSFlag = false, bool aCORSPreflightFlag = false, bool aAuthenticationFlag = false);
nsresult ContinueHttpFetchAfterNetworkFetch();
nsresult SetTainting();
nsresult ContinueFetch();
nsresult HttpFetch();
bool IsUnsafeRequest();
// Returns the filtered response sent to the observer.
// Callers who don't have access to a channel can pass null for aFinalURI.
already_AddRefed<InternalResponse>
BeginAndGetFilteredResponse(InternalResponse* aResponse, nsIURI* aFinalURI);
// Utility since not all cases need to do any post processing of the filtered
// response.
void BeginResponse(InternalResponse* aResponse);
nsresult FailWithNetworkError();
nsresult SucceedWithResponse();
nsresult DoesNotRequirePreflight(nsIChannel* aChannel);
};
} // namespace dom

View File

@ -260,16 +260,6 @@ Request::Constructor(const GlobalObject& aGlobal,
fallbackCache = RequestCache::Default;
}
// CORS-with-forced-preflight is not publicly exposed and should not be
// considered a valid value.
if (aInit.mMode.WasPassed() &&
aInit.mMode.Value() == RequestMode::Cors_with_forced_preflight) {
NS_NAMED_LITERAL_STRING(sourceDescription, "'mode' member of RequestInit");
NS_NAMED_LITERAL_STRING(value, "cors-with-forced-preflight");
NS_NAMED_LITERAL_STRING(type, "RequestMode");
aRv.ThrowTypeError<MSG_INVALID_ENUM_VALUE>(&sourceDescription, &value, &type);
return nullptr;
}
RequestMode mode = aInit.mMode.WasPassed() ? aInit.mMode.Value() : fallbackMode;
RequestCredentials credentials =
aInit.mCredentials.WasPassed() ? aInit.mCredentials.Value()

View File

@ -58,9 +58,6 @@ public:
RequestMode
Mode() const
{
if (mRequest->mMode == RequestMode::Cors_with_forced_preflight) {
return RequestMode::Cors;
}
return mRequest->mMode;
}

View File

@ -2221,13 +2221,22 @@ HTMLMediaElement::ResetConnectionState()
void
HTMLMediaElement::Play(ErrorResult& aRv)
{
nsresult rv = PlayInternal(nsContentUtils::IsCallerChrome());
if (NS_FAILED(rv)) {
aRv.Throw(rv);
}
}
nsresult
HTMLMediaElement::PlayInternal(bool aCallerIsChrome)
{
// Prevent media element from being auto-started by a script when
// media.autoplay.enabled=false
if (!mHasUserInteraction
&& !IsAutoplayEnabled()
&& !EventStateManager::IsHandlingUserInput()
&& !nsContentUtils::IsCallerChrome()) {
&& !aCallerIsChrome) {
LOG(LogLevel::Debug, ("%p Blocked attempt to autoplay media.", this));
#if defined(MOZ_WIDGET_ANDROID)
nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
@ -2236,7 +2245,7 @@ HTMLMediaElement::Play(ErrorResult& aRv)
false,
false);
#endif
return;
return NS_OK;
}
// Play was not blocked so assume user interacted with the element.
@ -2257,7 +2266,7 @@ HTMLMediaElement::Play(ErrorResult& aRv)
OwnerDoc()->Hidden()) {
LOG(LogLevel::Debug, ("%p Blocked playback because owner hidden.", this));
mPlayBlockedBecauseHidden = true;
return;
return NS_OK;
}
// Even if we just did Load() or ResumeLoad(), we could already have a decoder
@ -2267,9 +2276,9 @@ HTMLMediaElement::Play(ErrorResult& aRv)
SetCurrentTime(0);
}
if (!mPausedForInactiveDocumentOrChannel) {
aRv = mDecoder->Play();
if (aRv.Failed()) {
return;
nsresult rv = mDecoder->Play();
if (NS_FAILED(rv)) {
return rv;
}
}
}
@ -2305,13 +2314,13 @@ HTMLMediaElement::Play(ErrorResult& aRv)
AddRemoveSelfReference();
UpdatePreloadAction();
UpdateSrcMediaStreamPlaying();
return NS_OK;
}
NS_IMETHODIMP HTMLMediaElement::Play()
{
ErrorResult rv;
Play(rv);
return rv.StealNSResult();
return PlayInternal(/* aCallerIsChrome = */ true);
}
HTMLMediaElement::WakeLockBoolWrapper&

View File

@ -711,6 +711,8 @@ protected:
nsCOMPtr<nsITimer> mTimer;
};
nsresult PlayInternal(bool aCallerIsChrome);
/** Use this method to change the mReadyState member, so required
* events can be fired.
*/

View File

@ -6,6 +6,5 @@
"Historical DOM features must be removed: getAttributeNode": true,
"Historical DOM features must be removed: getAttributeNodeNS": true,
"Historical DOM features must be removed: setAttributeNode": true,
"Historical DOM features must be removed: removeAttributeNode": true,
"DocumentType member must be nuked: internalSubset": true
"Historical DOM features must be removed: removeAttributeNode": true
}

View File

@ -486,7 +486,7 @@ TabParent::ShouldSwitchProcess(nsIChannel* aChannel)
nsCOMPtr<nsIURI> uri;
aChannel->GetURI(getter_AddRefs(uri));
LogChannelRelevantInfo(uri, loadingPrincipal, resultPrincipal,
loadInfo->GetContentPolicyType());
loadInfo->InternalContentPolicyType());
// Check if the signed package is loaded from the same origin.
bool sameOrigin = false;
@ -497,7 +497,7 @@ TabParent::ShouldSwitchProcess(nsIChannel* aChannel)
}
// If this is not a top level document, there's no need to switch process.
if (nsIContentPolicy::TYPE_DOCUMENT != loadInfo->GetContentPolicyType()) {
if (nsIContentPolicy::TYPE_DOCUMENT != loadInfo->InternalContentPolicyType()) {
LOG("Subresource of a document. No need to switch process.\n");
return false;
}

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