mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-26 12:20:56 +00:00
Merge inbound to m-c. a=merge
This commit is contained in:
commit
e8a838517a
@ -877,8 +877,8 @@ pref("memory.system_memory_reporter", true);
|
||||
// Don't dump memory reports on OOM, by default.
|
||||
pref("memory.dump_reports_on_oom", false);
|
||||
|
||||
pref("layout.imagevisibility.numscrollportwidths", 1);
|
||||
pref("layout.imagevisibility.numscrollportheights", 1);
|
||||
pref("layout.framevisibility.numscrollportwidths", 1);
|
||||
pref("layout.framevisibility.numscrollportheights", 1);
|
||||
|
||||
// Enable native identity (persona/browserid)
|
||||
pref("dom.identity.enabled", true);
|
||||
|
@ -3353,7 +3353,7 @@ const DOMLinkHandler = {
|
||||
break;
|
||||
|
||||
case "Link:SetIcon":
|
||||
return this.setIcon(aMsg.target, aMsg.data.url, aMsg.data.loadingPrincipal);
|
||||
this.setIcon(aMsg.target, aMsg.data.url, aMsg.data.loadingPrincipal);
|
||||
break;
|
||||
|
||||
case "Link:AddSearch":
|
||||
|
@ -1,21 +1,45 @@
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
add_task(function* test() {
|
||||
let testPath = getRootDirectory(gTestPath);
|
||||
|
||||
let tab = gBrowser.addTab(testPath + "file_with_favicon.html");
|
||||
yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
|
||||
function* (tabBrowser) {
|
||||
const URI = testPath + "file_with_favicon.html";
|
||||
const expectedIcon = testPath + "file_generic_favicon.ico";
|
||||
|
||||
tab.linkedBrowser.addEventListener("DOMContentLoaded", function() {
|
||||
tab.linkedBrowser.removeEventListener("DOMContentLoaded", arguments.callee, true);
|
||||
let got_favicon = Promise.defer();
|
||||
let listener = {
|
||||
onLinkIconAvailable(browser, iconURI) {
|
||||
if (got_favicon && iconURI && browser === tabBrowser) {
|
||||
got_favicon.resolve(iconURI);
|
||||
got_favicon = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
gBrowser.addTabsProgressListener(listener);
|
||||
|
||||
let expectedIcon = testPath + "file_generic_favicon.ico";
|
||||
BrowserTestUtils.loadURI(tabBrowser, URI);
|
||||
|
||||
is(gBrowser.getIcon(tab), expectedIcon, "Correct icon before hash change.");
|
||||
tab.linkedBrowser.contentWindow.location.href += "#foo";
|
||||
is(gBrowser.getIcon(tab), expectedIcon, "Correct icon after hash change.");
|
||||
let iconURI = yield got_favicon.promise;
|
||||
is(iconURI, expectedIcon, "Correct icon before pushState.");
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
got_favicon = Promise.defer();
|
||||
got_favicon.promise.then(() => { ok(false, "shouldn't be called"); }, (e) => e);
|
||||
yield ContentTask.spawn(tabBrowser, null, function() {
|
||||
content.location.href += "#foo";
|
||||
});
|
||||
|
||||
// We've navigated and shouldn't get a call to onLinkIconAvailable.
|
||||
TestUtils.executeSoon(() => {
|
||||
got_favicon.reject(gBrowser.getIcon(gBrowser.getTabForBrowser(tabBrowser)));
|
||||
});
|
||||
try {
|
||||
yield got_favicon.promise;
|
||||
} catch (e) {
|
||||
iconURI = e;
|
||||
}
|
||||
is(iconURI, expectedIcon, "Correct icon after pushState.");
|
||||
|
||||
gBrowser.removeTabsProgressListener(listener);
|
||||
});
|
||||
});
|
||||
|
||||
finish();
|
||||
}, true);
|
||||
}
|
||||
|
@ -1,21 +1,44 @@
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
add_task(function* test() {
|
||||
let testPath = getRootDirectory(gTestPath);
|
||||
|
||||
let tab = gBrowser.addTab(testPath + "file_with_favicon.html");
|
||||
yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
|
||||
function* (tabBrowser) {
|
||||
const URI = testPath + "file_with_favicon.html";
|
||||
const expectedIcon = testPath + "file_generic_favicon.ico";
|
||||
|
||||
tab.linkedBrowser.addEventListener("DOMContentLoaded", function() {
|
||||
tab.linkedBrowser.removeEventListener("DOMContentLoaded", arguments.callee, true);
|
||||
let got_favicon = Promise.defer();
|
||||
let listener = {
|
||||
onLinkIconAvailable(browser, iconURI) {
|
||||
if (got_favicon && iconURI && browser === tabBrowser) {
|
||||
got_favicon.resolve(iconURI);
|
||||
got_favicon = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
gBrowser.addTabsProgressListener(listener);
|
||||
|
||||
let expectedIcon = testPath + "file_generic_favicon.ico";
|
||||
BrowserTestUtils.loadURI(tabBrowser, URI);
|
||||
|
||||
is(gBrowser.getIcon(tab), expectedIcon, "Correct icon before pushState.");
|
||||
tab.linkedBrowser.contentWindow.history.pushState("page2", "page2", "page2");
|
||||
is(gBrowser.getIcon(tab), expectedIcon, "Correct icon after pushState.");
|
||||
let iconURI = yield got_favicon.promise;
|
||||
is(iconURI, expectedIcon, "Correct icon before pushState.");
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
got_favicon = Promise.defer();
|
||||
got_favicon.promise.then(() => { ok(false, "shouldn't be called"); }, (e) => e);
|
||||
yield ContentTask.spawn(tabBrowser, null, function() {
|
||||
content.history.pushState("page2", "page2", "page2");
|
||||
});
|
||||
|
||||
finish();
|
||||
}, true);
|
||||
}
|
||||
// We've navigated and shouldn't get a call to onLinkIconAvailable.
|
||||
TestUtils.executeSoon(() => {
|
||||
got_favicon.reject(gBrowser.getIcon(gBrowser.getTabForBrowser(tabBrowser)));
|
||||
});
|
||||
try {
|
||||
yield got_favicon.promise;
|
||||
} catch (e) {
|
||||
iconURI = e;
|
||||
}
|
||||
is(iconURI, expectedIcon, "Correct icon after pushState.");
|
||||
|
||||
gBrowser.removeTabsProgressListener(listener);
|
||||
});
|
||||
});
|
||||
|
@ -282,10 +282,6 @@
|
||||
accesskey="&warnOpenManyTabs.accesskey;"
|
||||
preference="browser.tabs.warnOnOpen"/>
|
||||
|
||||
<checkbox id="restoreOnDemand" label="&restoreTabsOnDemand.label;"
|
||||
accesskey="&restoreTabsOnDemand.accesskey;"
|
||||
preference="browser.sessionstore.restore_on_demand"/>
|
||||
|
||||
<checkbox id="switchToNewTabs" label="&switchToNewTabs.label;"
|
||||
accesskey="&switchToNewTabs.accesskey;"
|
||||
preference="browser.tabs.loadInBackground"/>
|
||||
|
@ -11,9 +11,6 @@
|
||||
<!ENTITY warnOpenManyTabs.label "Warn me when opening multiple tabs might slow down &brandShortName;">
|
||||
<!ENTITY warnOpenManyTabs.accesskey "d">
|
||||
|
||||
<!ENTITY restoreTabsOnDemand.label "Don’t load tabs until selected">
|
||||
<!ENTITY restoreTabsOnDemand.accesskey "u">
|
||||
|
||||
<!ENTITY switchToNewTabs.label "When I open a link in a new tab, switch to it immediately">
|
||||
<!ENTITY switchToNewTabs.accesskey "h">
|
||||
|
||||
|
@ -71,47 +71,46 @@ this.ContentLinkHandler = {
|
||||
}
|
||||
break;
|
||||
case "icon":
|
||||
if (!iconAdded) {
|
||||
if (!Services.prefs.getBoolPref("browser.chrome.site_icons"))
|
||||
break;
|
||||
if (iconAdded || !Services.prefs.getBoolPref("browser.chrome.site_icons"))
|
||||
break;
|
||||
|
||||
var uri = this.getLinkIconURI(link);
|
||||
if (!uri)
|
||||
break;
|
||||
var uri = this.getLinkIconURI(link);
|
||||
if (!uri)
|
||||
break;
|
||||
|
||||
// Telemetry probes for measuring the sizes attribute
|
||||
// usage and available dimensions.
|
||||
let sizeHistogramTypes = Services.telemetry.
|
||||
getHistogramById("LINK_ICON_SIZES_ATTR_USAGE");
|
||||
let sizeHistogramDimension = Services.telemetry.
|
||||
getHistogramById("LINK_ICON_SIZES_ATTR_DIMENSION");
|
||||
let sizesType;
|
||||
if (link.sizes.length) {
|
||||
for (let size of link.sizes) {
|
||||
if (size.toLowerCase() == "any") {
|
||||
sizesType = SIZES_TELEMETRY_ENUM.ANY;
|
||||
break;
|
||||
// Telemetry probes for measuring the sizes attribute
|
||||
// usage and available dimensions.
|
||||
let sizeHistogramTypes = Services.telemetry.
|
||||
getHistogramById("LINK_ICON_SIZES_ATTR_USAGE");
|
||||
let sizeHistogramDimension = Services.telemetry.
|
||||
getHistogramById("LINK_ICON_SIZES_ATTR_DIMENSION");
|
||||
let sizesType;
|
||||
if (link.sizes.length) {
|
||||
for (let size of link.sizes) {
|
||||
if (size.toLowerCase() == "any") {
|
||||
sizesType = SIZES_TELEMETRY_ENUM.ANY;
|
||||
break;
|
||||
} else {
|
||||
let re = /^([1-9][0-9]*)x[1-9][0-9]*$/i;
|
||||
let values = re.exec(size);
|
||||
if (values && values.length > 1) {
|
||||
sizesType = SIZES_TELEMETRY_ENUM.DIMENSION;
|
||||
sizeHistogramDimension.add(parseInt(values[1]));
|
||||
} else {
|
||||
let re = /^([1-9][0-9]*)x[1-9][0-9]*$/i;
|
||||
let values = re.exec(size);
|
||||
if (values && values.length > 1) {
|
||||
sizesType = SIZES_TELEMETRY_ENUM.DIMENSION;
|
||||
sizeHistogramDimension.add(parseInt(values[1]));
|
||||
} else {
|
||||
sizesType = SIZES_TELEMETRY_ENUM.INVALID;
|
||||
break;
|
||||
}
|
||||
sizesType = SIZES_TELEMETRY_ENUM.INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sizesType = SIZES_TELEMETRY_ENUM.NO_SIZES;
|
||||
}
|
||||
sizeHistogramTypes.add(sizesType);
|
||||
|
||||
[iconAdded] = chromeGlobal.sendSyncMessage(
|
||||
"Link:SetIcon",
|
||||
{url: uri.spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
|
||||
} else {
|
||||
sizesType = SIZES_TELEMETRY_ENUM.NO_SIZES;
|
||||
}
|
||||
sizeHistogramTypes.add(sizesType);
|
||||
|
||||
chromeGlobal.sendAsyncMessage(
|
||||
"Link:SetIcon",
|
||||
{url: uri.spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
|
||||
iconAdded = true;
|
||||
break;
|
||||
case "search":
|
||||
if (!searchAdded && event.type == "DOMLinkAdded") {
|
||||
|
@ -21,8 +21,10 @@
|
||||
# @checking('for something')
|
||||
# def check(value):
|
||||
# ...
|
||||
# An optional callback can be given, that will be used to format the returned
|
||||
# value when displaying it.
|
||||
@template
|
||||
def checking(what):
|
||||
def checking(what, callback=None):
|
||||
def decorator(func):
|
||||
@advanced
|
||||
def wrapped(*args, **kwargs):
|
||||
@ -30,7 +32,9 @@ def checking(what):
|
||||
print('checking', what, end='... ')
|
||||
sys.stdout.flush()
|
||||
ret = func(*args, **kwargs)
|
||||
if ret is True:
|
||||
if callback:
|
||||
print(callback(ret))
|
||||
elif ret is True:
|
||||
print('yes')
|
||||
elif ret is False:
|
||||
print('no')
|
||||
@ -51,13 +55,12 @@ def checking(what):
|
||||
def check_prog(var, progs, allow_missing=False):
|
||||
option(env=var, nargs=1, help='Path to the %s program' % var.lower())
|
||||
|
||||
not_found = 'not found'
|
||||
if not (isinstance(progs, tuple) or isinstance(progs, list)):
|
||||
configure_error('progs should be a list or tuple!')
|
||||
progs = list(progs)
|
||||
|
||||
@depends(var)
|
||||
@checking('for %s' % var.lower())
|
||||
@checking('for %s' % var.lower(), lambda x: x or 'not found')
|
||||
def check(value):
|
||||
if value:
|
||||
progs[:] = value
|
||||
@ -65,21 +68,19 @@ def check_prog(var, progs, allow_missing=False):
|
||||
result = find_program(prog)
|
||||
if result:
|
||||
return result
|
||||
return not_found
|
||||
|
||||
@depends(check, var)
|
||||
@advanced
|
||||
def postcheck(value, raw_value):
|
||||
if value is not_found and (not allow_missing or raw_value):
|
||||
if value is None and (not allow_missing or raw_value):
|
||||
from mozbuild.shellutil import quote
|
||||
error('Cannot find %s (tried: %s)'
|
||||
% (var.lower(), ', '.join(quote(p) for p in progs)))
|
||||
return None if value is not_found else value
|
||||
|
||||
@depends(postcheck)
|
||||
@depends(check)
|
||||
def normalized_for_config(value):
|
||||
return ':' if value is None else value
|
||||
|
||||
set_config(var, normalized_for_config)
|
||||
|
||||
return postcheck
|
||||
return check
|
||||
|
@ -5,6 +5,7 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
include('util.configure')
|
||||
include('checks.configure')
|
||||
|
||||
option(env='DIST', nargs=1, help='DIST directory')
|
||||
|
||||
@ -464,6 +465,7 @@ def config_sub(shell, triplet):
|
||||
|
||||
|
||||
@depends('--host', shell)
|
||||
@checking('for host system type', lambda h: h.alias)
|
||||
@advanced
|
||||
def host(value, shell):
|
||||
if not value:
|
||||
@ -478,6 +480,7 @@ def host(value, shell):
|
||||
|
||||
|
||||
@depends('--target', host, shell)
|
||||
@checking('for target system type', lambda t: t.alias)
|
||||
def target(value, host, shell):
|
||||
if not value:
|
||||
return host
|
||||
|
@ -5,6 +5,11 @@
|
||||
|
||||
#include "imgINotificationObserver.idl"
|
||||
|
||||
%{C++
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "Visibility.h"
|
||||
%}
|
||||
|
||||
interface imgIRequest;
|
||||
interface nsIChannel;
|
||||
interface nsIStreamListener;
|
||||
@ -12,6 +17,9 @@ interface nsIURI;
|
||||
interface nsIDocument;
|
||||
interface nsIFrame;
|
||||
|
||||
[ref] native MaybeOnNonvisible(const mozilla::Maybe<mozilla::OnNonvisible>);
|
||||
native Visibility(mozilla::Visibility);
|
||||
|
||||
/**
|
||||
* This interface represents a content node that loads images. The interface
|
||||
* exists to allow getting information on the images that the content node
|
||||
@ -32,7 +40,7 @@ interface nsIFrame;
|
||||
* interface to mirror this interface when changing it.
|
||||
*/
|
||||
|
||||
[scriptable, builtinclass, uuid(770f7d84-c917-42d7-bf8d-d1b70649e733)]
|
||||
[scriptable, builtinclass, uuid(0357123d-9224-4d12-a47e-868c32689777)]
|
||||
interface nsIImageLoadingContent : imgINotificationObserver
|
||||
{
|
||||
/**
|
||||
@ -169,18 +177,17 @@ interface nsIImageLoadingContent : imgINotificationObserver
|
||||
readonly attribute unsigned long naturalHeight;
|
||||
|
||||
/**
|
||||
* A visible count is stored, if it is non-zero then this image is considered
|
||||
* visible. These methods increment, decrement, or return the visible count.
|
||||
* Called by layout to announce when the frame associated with this content
|
||||
* has changed its visibility state.
|
||||
*
|
||||
* @param aNonvisibleAction What to do if the image's visibility count is now
|
||||
* zero. If ON_NONVISIBLE_NO_ACTION, nothing will be
|
||||
* done. If ON_NONVISIBLE_REQUEST_DISCARD, the image
|
||||
* will be asked to discard its surfaces if possible.
|
||||
* @param aNewVisibility The new visibility state.
|
||||
* @param aNonvisibleAction A requested action if the frame has become
|
||||
* nonvisible. If Nothing(), no action is
|
||||
* requested. If DISCARD_IMAGES is specified, the
|
||||
* frame is requested to ask any images it's
|
||||
* associated with to discard their surfaces if
|
||||
* possible.
|
||||
*/
|
||||
[noscript, notxpcom] void IncrementVisibleCount();
|
||||
[noscript, notxpcom] void DecrementVisibleCount(in uint32_t aNonvisibleAction);
|
||||
[noscript, notxpcom] uint32_t GetVisibleCount();
|
||||
|
||||
const long ON_NONVISIBLE_NO_ACTION = 0;
|
||||
const long ON_NONVISIBLE_REQUEST_DISCARD = 1;
|
||||
[noscript, notxpcom] void onVisibilityChange(in Visibility aNewVisibility,
|
||||
in MaybeOnNonvisible aNonvisibleAction);
|
||||
};
|
||||
|
@ -726,24 +726,24 @@ nsINode::LookupPrefix(const nsAString& aNamespaceURI, nsAString& aPrefix)
|
||||
Element *element = GetNameSpaceElement();
|
||||
if (element) {
|
||||
// XXX Waiting for DOM spec to list error codes.
|
||||
|
||||
|
||||
// Trace up the content parent chain looking for the namespace
|
||||
// declaration that defines the aNamespaceURI namespace. Once found,
|
||||
// return the prefix (i.e. the attribute localName).
|
||||
for (nsIContent* content = element; content;
|
||||
content = content->GetParent()) {
|
||||
uint32_t attrCount = content->GetAttrCount();
|
||||
|
||||
|
||||
for (uint32_t i = 0; i < attrCount; ++i) {
|
||||
const nsAttrName* name = content->GetAttrNameAt(i);
|
||||
|
||||
|
||||
if (name->NamespaceEquals(kNameSpaceID_XMLNS) &&
|
||||
content->AttrValueIs(kNameSpaceID_XMLNS, name->LocalName(),
|
||||
aNamespaceURI, eCaseMatters)) {
|
||||
// If the localName is "xmlns", the prefix we output should be
|
||||
// null.
|
||||
nsIAtom *localName = name->LocalName();
|
||||
|
||||
|
||||
if (localName != nsGkAtoms::xmlns) {
|
||||
localName->ToString(aPrefix);
|
||||
}
|
||||
@ -972,7 +972,13 @@ nsINode::CompareDocumentPosition(nsINode& aOtherNode) const
|
||||
(nsIDOMNode::DOCUMENT_POSITION_PRECEDING |
|
||||
nsIDOMNode::DOCUMENT_POSITION_CONTAINS) :
|
||||
(nsIDOMNode::DOCUMENT_POSITION_FOLLOWING |
|
||||
nsIDOMNode::DOCUMENT_POSITION_CONTAINED_BY);
|
||||
nsIDOMNode::DOCUMENT_POSITION_CONTAINED_BY);
|
||||
}
|
||||
|
||||
bool
|
||||
nsINode::IsSameNode(nsINode *other)
|
||||
{
|
||||
return other == this;
|
||||
}
|
||||
|
||||
bool
|
||||
@ -1020,7 +1026,7 @@ nsINode::IsEqualNode(nsINode* aOther)
|
||||
element1->GetAttr(attrName->NamespaceID(), attrName->LocalName(),
|
||||
string1);
|
||||
NS_ASSERTION(hasAttr, "Why don't we have an attr?");
|
||||
|
||||
|
||||
if (!element2->AttrValueIs(attrName->NamespaceID(),
|
||||
attrName->LocalName(),
|
||||
string1,
|
||||
@ -1056,7 +1062,7 @@ nsINode::IsEqualNode(nsINode* aOther)
|
||||
"subtree?");
|
||||
node1->GetNodeValue(string1);
|
||||
node2->GetNodeValue(string2);
|
||||
|
||||
|
||||
// Returning here as to not bother walking subtree. And there is no
|
||||
// risk that we're half way through walking some other subtree since
|
||||
// attribute nodes doesn't appear in subtrees.
|
||||
@ -1066,7 +1072,7 @@ nsINode::IsEqualNode(nsINode* aOther)
|
||||
{
|
||||
nsCOMPtr<nsIDOMDocumentType> docType1 = do_QueryInterface(node1);
|
||||
nsCOMPtr<nsIDOMDocumentType> docType2 = do_QueryInterface(node2);
|
||||
|
||||
|
||||
NS_ASSERTION(docType1 && docType2, "Why don't we have a document type node?");
|
||||
|
||||
// Public ID
|
||||
@ -1075,7 +1081,7 @@ nsINode::IsEqualNode(nsINode* aOther)
|
||||
if (!string1.Equals(string2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// System ID
|
||||
docType1->GetSystemId(string1);
|
||||
docType2->GetSystemId(string2);
|
||||
@ -1119,7 +1125,7 @@ nsINode::IsEqualNode(nsINode* aOther)
|
||||
// node2 has a nextSibling, but node1 doesn't
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
node1 = node1->GetParentNode();
|
||||
node2 = node2->GetParentNode();
|
||||
NS_ASSERTION(node1 && node2, "no parent while walking subtree");
|
||||
@ -1795,7 +1801,7 @@ bool IsAllowedAsChild(nsIContent* aNewChild, nsINode* aParent,
|
||||
|
||||
// Now we're OK in the following two cases only:
|
||||
// 1) We're replacing something that's not before the doctype
|
||||
// 2) We're inserting before something that comes after the doctype
|
||||
// 2) We're inserting before something that comes after the doctype
|
||||
return aIsReplace ? (insertIndex >= doctypeIndex) :
|
||||
insertIndex > doctypeIndex;
|
||||
}
|
||||
@ -2036,7 +2042,7 @@ nsINode::ReplaceOrInsertBefore(bool aReplace, nsINode* aNewChild,
|
||||
// We expect one mutation (the removal) to have happened.
|
||||
if (guard.Mutated(1)) {
|
||||
// XBL destructors, yuck.
|
||||
|
||||
|
||||
// Verify that nodeToInsertBefore, if non-null, is still our child. If
|
||||
// it's not, there's no way we can do this insert sanely; just bail out.
|
||||
if (nodeToInsertBefore && nodeToInsertBefore->GetParent() != this) {
|
||||
@ -2112,7 +2118,7 @@ nsINode::ReplaceOrInsertBefore(bool aReplace, nsINode* aNewChild,
|
||||
// We expect |count| removals
|
||||
if (guard.Mutated(count)) {
|
||||
// XBL destructors, yuck.
|
||||
|
||||
|
||||
// Verify that nodeToInsertBefore, if non-null, is still our child. If
|
||||
// it's not, there's no way we can do this insert sanely; just bail out.
|
||||
if (nodeToInsertBefore && nodeToInsertBefore->GetParent() != this) {
|
||||
@ -2144,7 +2150,7 @@ nsINode::ReplaceOrInsertBefore(bool aReplace, nsINode* aNewChild,
|
||||
nodeToInsertBefore = aRefChild->GetNextSibling();
|
||||
} else {
|
||||
nodeToInsertBefore = aRefChild;
|
||||
}
|
||||
}
|
||||
|
||||
// And verify that newContent is still allowed as our child. Sadly, we
|
||||
// need to reimplement the relevant part of IsAllowedAsChild() because
|
||||
|
@ -725,7 +725,7 @@ public:
|
||||
{
|
||||
return InsertChildAt(aKid, GetChildCount(), aNotify);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a child from this node. This method handles calling UnbindFromTree
|
||||
* on the child appropriately.
|
||||
@ -888,7 +888,7 @@ public:
|
||||
virtual void* UnsetProperty(uint16_t aCategory,
|
||||
nsIAtom *aPropertyName,
|
||||
nsresult *aStatus = nullptr);
|
||||
|
||||
|
||||
bool HasProperties() const
|
||||
{
|
||||
return HasFlag(NODE_HAS_PROPERTIES);
|
||||
@ -920,7 +920,7 @@ public:
|
||||
{
|
||||
return mParent;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the parent nsINode for this node if it is an Element.
|
||||
* @return the parent node
|
||||
@ -1255,7 +1255,7 @@ protected:
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
void GetTextContent(nsAString& aTextContent,
|
||||
mozilla::ErrorResult& aError)
|
||||
@ -1310,7 +1310,7 @@ public:
|
||||
nsresult GetUserData(const nsAString& aKey, nsIVariant** aResult)
|
||||
{
|
||||
NS_IF_ADDREF(*aResult = GetUserData(aKey));
|
||||
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -1471,7 +1471,7 @@ private:
|
||||
NodeIsCCMarkedRoot,
|
||||
// Maybe set if this node is in black subtree.
|
||||
NodeIsCCBlackTree,
|
||||
// Maybe set if the node is a root of a subtree
|
||||
// Maybe set if the node is a root of a subtree
|
||||
// which needs to be kept in the purple buffer.
|
||||
NodeIsPurpleRoot,
|
||||
// Set if the node has an explicit base URI stored
|
||||
@ -1773,6 +1773,7 @@ public:
|
||||
}
|
||||
nsINode* RemoveChild(nsINode& aChild, mozilla::ErrorResult& aError);
|
||||
already_AddRefed<nsINode> CloneNode(bool aDeep, mozilla::ErrorResult& aError);
|
||||
bool IsSameNode(nsINode* aNode);
|
||||
bool IsEqualNode(nsINode* aNode);
|
||||
void GetNamespaceURI(nsAString& aNamespaceURI) const
|
||||
{
|
||||
|
@ -94,8 +94,7 @@ nsImageLoadingContent::nsImageLoadingContent()
|
||||
mStateChangerDepth(0),
|
||||
mCurrentRequestRegistered(false),
|
||||
mPendingRequestRegistered(false),
|
||||
mFrameCreateCalled(false),
|
||||
mVisibleCount(0)
|
||||
mFrameCreateCalled(false)
|
||||
{
|
||||
if (!nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
|
||||
mLoadingEnabled = false;
|
||||
@ -110,8 +109,8 @@ nsImageLoadingContent::DestroyImageLoadingContent()
|
||||
{
|
||||
// Cancel our requests so they won't hold stale refs to us
|
||||
// NB: Don't ask to discard the images here.
|
||||
ClearCurrentRequest(NS_BINDING_ABORTED, ON_NONVISIBLE_NO_ACTION);
|
||||
ClearPendingRequest(NS_BINDING_ABORTED, ON_NONVISIBLE_NO_ACTION);
|
||||
ClearCurrentRequest(NS_BINDING_ABORTED);
|
||||
ClearPendingRequest(NS_BINDING_ABORTED);
|
||||
}
|
||||
|
||||
nsImageLoadingContent::~nsImageLoadingContent()
|
||||
@ -264,12 +263,7 @@ ImageIsAnimated(imgIRequest* aRequest)
|
||||
void
|
||||
nsImageLoadingContent::OnUnlockedDraw()
|
||||
{
|
||||
if (mVisibleCount > 0) {
|
||||
// We should already be marked as visible, there is nothing more we can do.
|
||||
return;
|
||||
}
|
||||
|
||||
// It's OK for non-animated images to wait until the next image visibility
|
||||
// It's OK for non-animated images to wait until the next frame visibility
|
||||
// update to become locked. (And that's preferable, since in the case of
|
||||
// scrolling it keeps memory usage minimal.) For animated images, though, we
|
||||
// want to mark them visible right away so we can call
|
||||
@ -278,15 +272,27 @@ nsImageLoadingContent::OnUnlockedDraw()
|
||||
return;
|
||||
}
|
||||
|
||||
nsPresContext* presContext = GetFramePresContext();
|
||||
if (!presContext)
|
||||
nsIFrame* frame = GetOurPrimaryFrame();
|
||||
if (!frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame->GetVisibility() == Visibility::APPROXIMATELY_VISIBLE) {
|
||||
// This frame is already marked visible; there's nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
nsPresContext* presContext = frame->PresContext();
|
||||
if (!presContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsIPresShell* presShell = presContext->PresShell();
|
||||
if (!presShell)
|
||||
if (!presShell) {
|
||||
return;
|
||||
}
|
||||
|
||||
presShell->EnsureImageInVisibleList(this);
|
||||
presShell->EnsureFrameInApproximatelyVisibleList(frame);
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -470,11 +476,6 @@ nsImageLoadingContent::FrameCreated(nsIFrame* aFrame)
|
||||
|
||||
mFrameCreateCalled = true;
|
||||
|
||||
if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
|
||||
// Assume all images in popups are visible.
|
||||
IncrementVisibleCount();
|
||||
}
|
||||
|
||||
TrackImage(mCurrentRequest);
|
||||
TrackImage(mPendingRequest);
|
||||
|
||||
@ -518,13 +519,7 @@ nsImageLoadingContent::FrameDestroyed(nsIFrame* aFrame)
|
||||
|
||||
nsIPresShell* presShell = presContext ? presContext->GetPresShell() : nullptr;
|
||||
if (presShell) {
|
||||
presShell->RemoveImageFromVisibleList(this);
|
||||
}
|
||||
|
||||
if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
|
||||
// We assume all images in popups are visible, so this decrement balances
|
||||
// out the increment in FrameCreated above.
|
||||
DecrementVisibleCount(ON_NONVISIBLE_NO_ACTION);
|
||||
presShell->RemoveFrameFromApproximatelyVisibleList(aFrame);
|
||||
}
|
||||
}
|
||||
|
||||
@ -738,34 +733,6 @@ nsImageLoadingContent::UnblockOnload(imgIRequest* aRequest)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsImageLoadingContent::IncrementVisibleCount()
|
||||
{
|
||||
mVisibleCount++;
|
||||
if (mVisibleCount == 1) {
|
||||
TrackImage(mCurrentRequest);
|
||||
TrackImage(mPendingRequest);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsImageLoadingContent::DecrementVisibleCount(uint32_t aNonvisibleAction)
|
||||
{
|
||||
NS_ASSERTION(mVisibleCount > 0, "visible count should be positive here");
|
||||
mVisibleCount--;
|
||||
|
||||
if (mVisibleCount == 0) {
|
||||
UntrackImage(mCurrentRequest, aNonvisibleAction);
|
||||
UntrackImage(mPendingRequest, aNonvisibleAction);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
nsImageLoadingContent::GetVisibleCount()
|
||||
{
|
||||
return mVisibleCount;
|
||||
}
|
||||
|
||||
/*
|
||||
* Non-interface methods
|
||||
*/
|
||||
@ -1076,8 +1043,8 @@ void
|
||||
nsImageLoadingContent::CancelImageRequests(bool aNotify)
|
||||
{
|
||||
AutoStateChanger changer(this, aNotify);
|
||||
ClearPendingRequest(NS_BINDING_ABORTED, ON_NONVISIBLE_REQUEST_DISCARD);
|
||||
ClearCurrentRequest(NS_BINDING_ABORTED, ON_NONVISIBLE_REQUEST_DISCARD);
|
||||
ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
|
||||
ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -1089,8 +1056,8 @@ nsImageLoadingContent::UseAsPrimaryRequest(imgRequestProxy* aRequest,
|
||||
AutoStateChanger changer(this, aNotify);
|
||||
|
||||
// Get rid if our existing images
|
||||
ClearPendingRequest(NS_BINDING_ABORTED, ON_NONVISIBLE_REQUEST_DISCARD);
|
||||
ClearCurrentRequest(NS_BINDING_ABORTED, ON_NONVISIBLE_REQUEST_DISCARD);
|
||||
ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
|
||||
ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
|
||||
|
||||
// Clone the request we were given.
|
||||
RefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType);
|
||||
@ -1228,7 +1195,9 @@ nsImageLoadingContent::SetBlockedRequest(nsIURI* aURI, int16_t aContentDecision)
|
||||
// reason "image source changed". However, apparently there's some abuse
|
||||
// over in nsImageFrame where the displaying of the "broken" icon for the
|
||||
// next image depends on the cancel reason of the previous image. ugh.
|
||||
ClearPendingRequest(NS_ERROR_IMAGE_BLOCKED, ON_NONVISIBLE_REQUEST_DISCARD);
|
||||
// XXX(seth): So shouldn't we fix nsImageFrame?!
|
||||
ClearPendingRequest(NS_ERROR_IMAGE_BLOCKED,
|
||||
Some(OnNonvisible::DISCARD_IMAGES));
|
||||
|
||||
// For the blocked case, we only want to cancel the existing current request
|
||||
// if size is not available. bz says the web depends on this behavior.
|
||||
@ -1236,7 +1205,8 @@ nsImageLoadingContent::SetBlockedRequest(nsIURI* aURI, int16_t aContentDecision)
|
||||
|
||||
mImageBlockingStatus = aContentDecision;
|
||||
uint32_t keepFlags = mCurrentRequestFlags & REQUEST_IS_IMAGESET;
|
||||
ClearCurrentRequest(NS_ERROR_IMAGE_BLOCKED, ON_NONVISIBLE_REQUEST_DISCARD);
|
||||
ClearCurrentRequest(NS_ERROR_IMAGE_BLOCKED,
|
||||
Some(OnNonvisible::DISCARD_IMAGES));
|
||||
|
||||
// We still want to remember what URI we were and if it was an imageset,
|
||||
// despite not having an actual request. These are both cleared as part of
|
||||
@ -1255,7 +1225,7 @@ nsImageLoadingContent::PrepareCurrentRequest(ImageLoadType aImageLoadType)
|
||||
|
||||
// Get rid of anything that was there previously.
|
||||
ClearCurrentRequest(NS_ERROR_IMAGE_SRC_CHANGED,
|
||||
ON_NONVISIBLE_REQUEST_DISCARD);
|
||||
Some(OnNonvisible::DISCARD_IMAGES));
|
||||
|
||||
if (mNewRequestsWillNeedAnimationReset) {
|
||||
mCurrentRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET;
|
||||
@ -1274,7 +1244,7 @@ nsImageLoadingContent::PreparePendingRequest(ImageLoadType aImageLoadType)
|
||||
{
|
||||
// Get rid of anything that was there previously.
|
||||
ClearPendingRequest(NS_ERROR_IMAGE_SRC_CHANGED,
|
||||
ON_NONVISIBLE_REQUEST_DISCARD);
|
||||
Some(OnNonvisible::DISCARD_IMAGES));
|
||||
|
||||
if (mNewRequestsWillNeedAnimationReset) {
|
||||
mPendingRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET;
|
||||
@ -1339,7 +1309,7 @@ nsImageLoadingContent::MakePendingRequestCurrent()
|
||||
|
||||
void
|
||||
nsImageLoadingContent::ClearCurrentRequest(nsresult aReason,
|
||||
uint32_t aNonvisibleAction)
|
||||
const Maybe<OnNonvisible>& aNonvisibleAction)
|
||||
{
|
||||
if (!mCurrentRequest) {
|
||||
// Even if we didn't have a current request, we might have been keeping
|
||||
@ -1365,7 +1335,7 @@ nsImageLoadingContent::ClearCurrentRequest(nsresult aReason,
|
||||
|
||||
void
|
||||
nsImageLoadingContent::ClearPendingRequest(nsresult aReason,
|
||||
uint32_t aNonvisibleAction)
|
||||
const Maybe<OnNonvisible>& aNonvisibleAction)
|
||||
{
|
||||
if (!mPendingRequest)
|
||||
return;
|
||||
@ -1451,6 +1421,27 @@ nsImageLoadingContent::UnbindFromTree(bool aDeep, bool aNullParent)
|
||||
doc->UnblockOnload(false);
|
||||
}
|
||||
|
||||
void
|
||||
nsImageLoadingContent::OnVisibilityChange(Visibility aNewVisibility,
|
||||
const Maybe<OnNonvisible>& aNonvisibleAction)
|
||||
{
|
||||
switch (aNewVisibility) {
|
||||
case Visibility::APPROXIMATELY_VISIBLE:
|
||||
TrackImage(mCurrentRequest);
|
||||
TrackImage(mPendingRequest);
|
||||
break;
|
||||
|
||||
case Visibility::APPROXIMATELY_NONVISIBLE:
|
||||
UntrackImage(mCurrentRequest, aNonvisibleAction);
|
||||
UntrackImage(mPendingRequest, aNonvisibleAction);
|
||||
break;
|
||||
|
||||
case Visibility::UNTRACKED:
|
||||
MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsImageLoadingContent::TrackImage(imgIRequest* aImage)
|
||||
{
|
||||
@ -1461,32 +1452,34 @@ nsImageLoadingContent::TrackImage(imgIRequest* aImage)
|
||||
"Why haven't we heard of this request?");
|
||||
|
||||
nsIDocument* doc = GetOurCurrentDoc();
|
||||
if (doc && (mFrameCreateCalled || GetOurPrimaryFrame()) &&
|
||||
(mVisibleCount > 0)) {
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mVisibleCount == 1) {
|
||||
// Since we're becoming visible, request a decode.
|
||||
nsImageFrame* f = do_QueryFrame(GetOurPrimaryFrame());
|
||||
if (f) {
|
||||
f->MaybeDecodeForPredictedSize();
|
||||
}
|
||||
}
|
||||
// We only want to track this request if we're visible. Ordinarily we check
|
||||
// the visible count, but that requires a frame; in cases where
|
||||
// GetOurPrimaryFrame() cannot obtain a frame (e.g. <feImage>), we assume
|
||||
// we're visible if FrameCreated() was called.
|
||||
nsIFrame* frame = GetOurPrimaryFrame();
|
||||
if ((frame && frame->GetVisibility() == Visibility::APPROXIMATELY_NONVISIBLE) ||
|
||||
(!frame && !mFrameCreateCalled)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aImage == mCurrentRequest && !(mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
|
||||
mCurrentRequestFlags |= REQUEST_IS_TRACKED;
|
||||
doc->AddImage(mCurrentRequest);
|
||||
}
|
||||
if (aImage == mPendingRequest && !(mPendingRequestFlags & REQUEST_IS_TRACKED)) {
|
||||
mPendingRequestFlags |= REQUEST_IS_TRACKED;
|
||||
doc->AddImage(mPendingRequest);
|
||||
}
|
||||
if (aImage == mCurrentRequest && !(mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
|
||||
mCurrentRequestFlags |= REQUEST_IS_TRACKED;
|
||||
doc->AddImage(mCurrentRequest);
|
||||
}
|
||||
if (aImage == mPendingRequest && !(mPendingRequestFlags & REQUEST_IS_TRACKED)) {
|
||||
mPendingRequestFlags |= REQUEST_IS_TRACKED;
|
||||
doc->AddImage(mPendingRequest);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsImageLoadingContent::UntrackImage(imgIRequest* aImage,
|
||||
uint32_t aNonvisibleAction
|
||||
/* = ON_NONVISIBLE_NO_ACTION */)
|
||||
const Maybe<OnNonvisible>& aNonvisibleAction
|
||||
/* = Nothing() */)
|
||||
{
|
||||
if (!aImage)
|
||||
return;
|
||||
@ -1503,10 +1496,10 @@ nsImageLoadingContent::UntrackImage(imgIRequest* aImage,
|
||||
if (doc && (mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
|
||||
mCurrentRequestFlags &= ~REQUEST_IS_TRACKED;
|
||||
doc->RemoveImage(mCurrentRequest,
|
||||
(aNonvisibleAction == ON_NONVISIBLE_REQUEST_DISCARD)
|
||||
aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)
|
||||
? nsIDocument::REQUEST_DISCARD
|
||||
: 0);
|
||||
} else if (aNonvisibleAction == ON_NONVISIBLE_REQUEST_DISCARD) {
|
||||
} else if (aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)) {
|
||||
// If we're not in the document we may still need to be discarded.
|
||||
aImage->RequestDiscard();
|
||||
}
|
||||
@ -1515,10 +1508,10 @@ nsImageLoadingContent::UntrackImage(imgIRequest* aImage,
|
||||
if (doc && (mPendingRequestFlags & REQUEST_IS_TRACKED)) {
|
||||
mPendingRequestFlags &= ~REQUEST_IS_TRACKED;
|
||||
doc->RemoveImage(mPendingRequest,
|
||||
(aNonvisibleAction == ON_NONVISIBLE_REQUEST_DISCARD)
|
||||
aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)
|
||||
? nsIDocument::REQUEST_DISCARD
|
||||
: 0);
|
||||
} else if (aNonvisibleAction == ON_NONVISIBLE_REQUEST_DISCARD) {
|
||||
} else if (aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)) {
|
||||
// If we're not in the document we may still need to be discarded.
|
||||
aImage->RequestDiscard();
|
||||
}
|
||||
|
@ -41,6 +41,11 @@ class imgRequestProxy;
|
||||
class nsImageLoadingContent : public nsIImageLoadingContent,
|
||||
public imgIOnloadBlocker
|
||||
{
|
||||
template <typename T> using Maybe = mozilla::Maybe<T>;
|
||||
using Nothing = mozilla::Nothing;
|
||||
using OnNonvisible = mozilla::OnNonvisible;
|
||||
using Visibility = mozilla::Visibility;
|
||||
|
||||
/* METHODS */
|
||||
public:
|
||||
nsImageLoadingContent();
|
||||
@ -318,8 +323,10 @@ protected:
|
||||
* @param aNonvisibleAction An action to take if the image is no longer
|
||||
* visible as a result; see |UntrackImage|.
|
||||
*/
|
||||
void ClearCurrentRequest(nsresult aReason, uint32_t aNonvisibleAction);
|
||||
void ClearPendingRequest(nsresult aReason, uint32_t aNonvisibleAction);
|
||||
void ClearCurrentRequest(nsresult aReason,
|
||||
const Maybe<OnNonvisible>& aNonvisibleAction = Nothing());
|
||||
void ClearPendingRequest(nsresult aReason,
|
||||
const Maybe<OnNonvisible>& aNonvisibleAction = Nothing());
|
||||
|
||||
/**
|
||||
* Retrieve a pointer to the 'registered with the refresh driver' flag for
|
||||
@ -348,14 +355,16 @@ protected:
|
||||
*
|
||||
* No-op if aImage is null.
|
||||
*
|
||||
* @param aNonvisibleAction What to do if the image's visibility count is now
|
||||
* zero. If ON_NONVISIBLE_NO_ACTION, nothing will be
|
||||
* done. If ON_NONVISIBLE_REQUEST_DISCARD, the image
|
||||
* will be asked to discard its surfaces if possible.
|
||||
* @param aNonvisibleAction A requested action if the frame has become
|
||||
* nonvisible. If Nothing(), no action is
|
||||
* requested. If DISCARD_IMAGES is specified, the
|
||||
* frame is requested to ask any images it's
|
||||
* associated with to discard their surfaces if
|
||||
* possible.
|
||||
*/
|
||||
void TrackImage(imgIRequest* aImage);
|
||||
void UntrackImage(imgIRequest* aImage,
|
||||
uint32_t aNonvisibleAction = ON_NONVISIBLE_NO_ACTION);
|
||||
const Maybe<OnNonvisible>& aNonvisibleAction = Nothing());
|
||||
|
||||
/* MEMBERS */
|
||||
RefPtr<imgRequestProxy> mCurrentRequest;
|
||||
@ -439,8 +448,6 @@ private:
|
||||
|
||||
// True when FrameCreate has been called but FrameDestroy has not.
|
||||
bool mFrameCreateCalled;
|
||||
|
||||
uint32_t mVisibleCount;
|
||||
};
|
||||
|
||||
#endif // nsImageLoadingContent_h__
|
||||
|
@ -265,31 +265,13 @@ CallbackObject::CallSetup::~CallSetup()
|
||||
if (needToDealWithException) {
|
||||
// Either we're supposed to report our exceptions, or we're supposed to
|
||||
// re-throw them but we failed to get the exception value. Either way,
|
||||
// just report the pending exception, if any.
|
||||
//
|
||||
// XXXbz FIXME: bug 979525 means we don't always JS_SaveFrameChain here,
|
||||
// so we need to go ahead and do that. This is also the reason we don't
|
||||
// just rely on ~AutoJSAPI reporting the exception for us. I think if we
|
||||
// didn't need to JS_SaveFrameChain here, we could just rely on that.
|
||||
JS::Rooted<JSObject*> oldGlobal(mCx, JS::CurrentGlobalOrNull(mCx));
|
||||
MOZ_ASSERT(oldGlobal, "How can we not have a global here??");
|
||||
bool saved = JS_SaveFrameChain(mCx);
|
||||
// Make sure the JSAutoCompartment goes out of scope before the
|
||||
// JS_RestoreFrameChain call!
|
||||
{
|
||||
JSAutoCompartment ac(mCx, oldGlobal);
|
||||
MOZ_ASSERT(!JS::DescribeScriptedCaller(mCx),
|
||||
"Our comment above about JS_SaveFrameChain having been "
|
||||
"called is a lie?");
|
||||
mAutoEntryScript->ReportException();
|
||||
}
|
||||
if (saved) {
|
||||
JS_RestoreFrameChain(mCx);
|
||||
}
|
||||
|
||||
// we'll just report the pending exception, if any, once ~mAutoEntryScript
|
||||
// runs. Note that we've already run ~mAc, effectively, so we don't have
|
||||
// to worry about ordering here.
|
||||
if (mErrorResult.IsJSContextException()) {
|
||||
// XXXkhuey bug 1117269.
|
||||
// This isn't true anymore ... so throw something else.
|
||||
// This won't be true anymore because we will report the exception on
|
||||
// the JSContext ... so throw something else.
|
||||
mErrorResult.Throw(NS_ERROR_UNEXPECTED);
|
||||
}
|
||||
}
|
||||
|
@ -39,8 +39,6 @@
|
||||
#include "mozilla/ipc/InputStreamParams.h"
|
||||
#include "mozilla/ipc/InputStreamUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIConsoleService.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIObserverService.h"
|
||||
@ -49,6 +47,7 @@
|
||||
#include "nsThreadUtils.h"
|
||||
#include "ProfilerHelpers.h"
|
||||
#include "ReportInternalError.h"
|
||||
#include "ScriptErrorHelper.h"
|
||||
#include "nsQueryObject.h"
|
||||
|
||||
// Include this last to avoid path problems on Windows.
|
||||
@ -132,50 +131,6 @@ private:
|
||||
|
||||
} // namespace
|
||||
|
||||
class IDBDatabase::LogWarningRunnable final
|
||||
: public nsRunnable
|
||||
{
|
||||
nsCString mMessageName;
|
||||
nsString mFilename;
|
||||
uint32_t mLineNumber;
|
||||
uint32_t mColumnNumber;
|
||||
uint64_t mInnerWindowID;
|
||||
bool mIsChrome;
|
||||
|
||||
public:
|
||||
LogWarningRunnable(const char* aMessageName,
|
||||
const nsAString& aFilename,
|
||||
uint32_t aLineNumber,
|
||||
uint32_t aColumnNumber,
|
||||
bool aIsChrome,
|
||||
uint64_t aInnerWindowID)
|
||||
: mMessageName(aMessageName)
|
||||
, mFilename(aFilename)
|
||||
, mLineNumber(aLineNumber)
|
||||
, mColumnNumber(aColumnNumber)
|
||||
, mInnerWindowID(aInnerWindowID)
|
||||
, mIsChrome(aIsChrome)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
}
|
||||
|
||||
static void
|
||||
LogWarning(const char* aMessageName,
|
||||
const nsAString& aFilename,
|
||||
uint32_t aLineNumber,
|
||||
uint32_t aColumnNumber,
|
||||
bool aIsChrome,
|
||||
uint64_t aInnerWindowID);
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
private:
|
||||
~LogWarningRunnable()
|
||||
{ }
|
||||
|
||||
NS_DECL_NSIRUNNABLE
|
||||
};
|
||||
|
||||
class IDBDatabase::Observer final
|
||||
: public nsIObserver
|
||||
{
|
||||
@ -1257,23 +1212,13 @@ IDBDatabase::LogWarning(const char* aMessageName,
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aMessageName);
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
LogWarningRunnable::LogWarning(aMessageName,
|
||||
aFilename,
|
||||
aLineNumber,
|
||||
aColumnNumber,
|
||||
mFactory->IsChrome(),
|
||||
mFactory->InnerWindowID());
|
||||
} else {
|
||||
RefPtr<LogWarningRunnable> runnable =
|
||||
new LogWarningRunnable(aMessageName,
|
||||
aFilename,
|
||||
aLineNumber,
|
||||
aColumnNumber,
|
||||
mFactory->IsChrome(),
|
||||
mFactory->InnerWindowID());
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
|
||||
}
|
||||
ScriptErrorHelper::DumpLocalizedMessage(nsDependentCString(aMessageName),
|
||||
aFilename,
|
||||
aLineNumber,
|
||||
aColumnNumber,
|
||||
nsIScriptError::warningFlag,
|
||||
mFactory->IsChrome(),
|
||||
mFactory->InnerWindowID());
|
||||
}
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(IDBDatabase, IDBWrapperCache)
|
||||
@ -1356,85 +1301,6 @@ CancelableRunnableWrapper::Cancel()
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
// static
|
||||
void
|
||||
IDBDatabase::
|
||||
LogWarningRunnable::LogWarning(const char* aMessageName,
|
||||
const nsAString& aFilename,
|
||||
uint32_t aLineNumber,
|
||||
uint32_t aColumnNumber,
|
||||
bool aIsChrome,
|
||||
uint64_t aInnerWindowID)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aMessageName);
|
||||
|
||||
nsXPIDLString localizedMessage;
|
||||
if (NS_WARN_IF(NS_FAILED(
|
||||
nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
aMessageName,
|
||||
localizedMessage)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoCString category;
|
||||
if (aIsChrome) {
|
||||
category.AssignLiteral("chrome ");
|
||||
} else {
|
||||
category.AssignLiteral("content ");
|
||||
}
|
||||
category.AppendLiteral("javascript");
|
||||
|
||||
nsCOMPtr<nsIConsoleService> consoleService =
|
||||
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
|
||||
MOZ_ASSERT(consoleService);
|
||||
|
||||
nsCOMPtr<nsIScriptError> scriptError =
|
||||
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
|
||||
MOZ_ASSERT(consoleService);
|
||||
|
||||
if (aInnerWindowID) {
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
|
||||
scriptError->InitWithWindowID(localizedMessage,
|
||||
aFilename,
|
||||
/* aSourceLine */ EmptyString(),
|
||||
aLineNumber,
|
||||
aColumnNumber,
|
||||
nsIScriptError::warningFlag,
|
||||
category,
|
||||
aInnerWindowID)));
|
||||
} else {
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
|
||||
scriptError->Init(localizedMessage,
|
||||
aFilename,
|
||||
/* aSourceLine */ EmptyString(),
|
||||
aLineNumber,
|
||||
aColumnNumber,
|
||||
nsIScriptError::warningFlag,
|
||||
category.get())));
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(consoleService->LogMessage(scriptError)));
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED0(IDBDatabase::LogWarningRunnable, nsRunnable)
|
||||
|
||||
NS_IMETHODIMP
|
||||
IDBDatabase::
|
||||
LogWarningRunnable::Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
LogWarning(mMessageName.get(),
|
||||
mFilename,
|
||||
mLineNumber,
|
||||
mColumnNumber,
|
||||
mIsChrome,
|
||||
mInnerWindowID);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(IDBDatabase::Observer, nsIObserver)
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -53,9 +53,6 @@ class IDBDatabase final
|
||||
typedef mozilla::dom::StorageType StorageType;
|
||||
typedef mozilla::dom::quota::PersistenceType PersistenceType;
|
||||
|
||||
class LogWarningRunnable;
|
||||
friend class LogWarningRunnable;
|
||||
|
||||
class Observer;
|
||||
friend class Observer;
|
||||
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "IDBKeyRange.h"
|
||||
#include "IDBRequest.h"
|
||||
#include "ProfilerHelpers.h"
|
||||
#include "ScriptErrorHelper.h"
|
||||
#include "WorkerScope.h"
|
||||
#include "WorkerPrivate.h"
|
||||
|
||||
@ -538,45 +539,14 @@ IndexedDatabaseManager::CommonPostHandleEvent(EventChainPostVisitor& aVisitor,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsAutoCString category;
|
||||
if (aFactory->IsChrome()) {
|
||||
category.AssignLiteral("chrome ");
|
||||
} else {
|
||||
category.AssignLiteral("content ");
|
||||
}
|
||||
category.AppendLiteral("javascript");
|
||||
|
||||
// Log the error to the error console.
|
||||
nsCOMPtr<nsIConsoleService> consoleService =
|
||||
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
|
||||
MOZ_ASSERT(consoleService);
|
||||
|
||||
nsCOMPtr<nsIScriptError> scriptError =
|
||||
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
|
||||
MOZ_ASSERT(consoleService);
|
||||
|
||||
if (uint64_t innerWindowID = aFactory->InnerWindowID()) {
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
|
||||
scriptError->InitWithWindowID(errorName,
|
||||
init.mFilename,
|
||||
/* aSourceLine */ EmptyString(),
|
||||
init.mLineno,
|
||||
init.mColno,
|
||||
nsIScriptError::errorFlag,
|
||||
category,
|
||||
innerWindowID)));
|
||||
} else {
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
|
||||
scriptError->Init(errorName,
|
||||
init.mFilename,
|
||||
/* aSourceLine */ EmptyString(),
|
||||
init.mLineno,
|
||||
init.mColno,
|
||||
nsIScriptError::errorFlag,
|
||||
category.get())));
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(consoleService->LogMessage(scriptError)));
|
||||
ScriptErrorHelper::Dump(errorName,
|
||||
init.mFilename,
|
||||
init.mLineno,
|
||||
init.mColno,
|
||||
nsIScriptError::errorFlag,
|
||||
aFactory->IsChrome(),
|
||||
aFactory->InnerWindowID());
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
249
dom/indexedDB/ScriptErrorHelper.cpp
Normal file
249
dom/indexedDB/ScriptErrorHelper.cpp
Normal file
@ -0,0 +1,249 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ScriptErrorHelper.h"
|
||||
|
||||
#include "MainThreadUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIConsoleService.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "nsString.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class ScriptErrorRunnable final : public nsRunnable
|
||||
{
|
||||
nsString mMessage;
|
||||
nsCString mMessageName;
|
||||
nsString mFilename;
|
||||
uint32_t mLineNumber;
|
||||
uint32_t mColumnNumber;
|
||||
uint32_t mSeverityFlag;
|
||||
uint64_t mInnerWindowID;
|
||||
bool mIsChrome;
|
||||
|
||||
public:
|
||||
ScriptErrorRunnable(const nsAString& aMessage,
|
||||
const nsAString& aFilename,
|
||||
uint32_t aLineNumber,
|
||||
uint32_t aColumnNumber,
|
||||
uint32_t aSeverityFlag,
|
||||
bool aIsChrome,
|
||||
uint64_t aInnerWindowID)
|
||||
: mMessage(aMessage)
|
||||
, mFilename(aFilename)
|
||||
, mLineNumber(aLineNumber)
|
||||
, mColumnNumber(aColumnNumber)
|
||||
, mSeverityFlag(aSeverityFlag)
|
||||
, mInnerWindowID(aInnerWindowID)
|
||||
, mIsChrome(aIsChrome)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
mMessageName.SetIsVoid(true);
|
||||
}
|
||||
|
||||
ScriptErrorRunnable(const nsACString& aMessageName,
|
||||
const nsAString& aFilename,
|
||||
uint32_t aLineNumber,
|
||||
uint32_t aColumnNumber,
|
||||
uint32_t aSeverityFlag,
|
||||
bool aIsChrome,
|
||||
uint64_t aInnerWindowID)
|
||||
: mMessageName(aMessageName)
|
||||
, mFilename(aFilename)
|
||||
, mLineNumber(aLineNumber)
|
||||
, mColumnNumber(aColumnNumber)
|
||||
, mSeverityFlag(aSeverityFlag)
|
||||
, mInnerWindowID(aInnerWindowID)
|
||||
, mIsChrome(aIsChrome)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
mMessage.SetIsVoid(true);
|
||||
}
|
||||
|
||||
static void
|
||||
DumpLocalizedMessage(const nsCString& aMessageName,
|
||||
const nsAString& aFilename,
|
||||
uint32_t aLineNumber,
|
||||
uint32_t aColumnNumber,
|
||||
uint32_t aSeverityFlag,
|
||||
bool aIsChrome,
|
||||
uint64_t aInnerWindowID)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!aMessageName.IsEmpty());
|
||||
|
||||
nsXPIDLString localizedMessage;
|
||||
if (NS_WARN_IF(NS_FAILED(
|
||||
nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
|
||||
aMessageName.get(),
|
||||
localizedMessage)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
Dump(localizedMessage,
|
||||
aFilename,
|
||||
aLineNumber,
|
||||
aColumnNumber,
|
||||
aSeverityFlag,
|
||||
aIsChrome,
|
||||
aInnerWindowID);
|
||||
}
|
||||
|
||||
static void
|
||||
Dump(const nsAString& aMessage,
|
||||
const nsAString& aFilename,
|
||||
uint32_t aLineNumber,
|
||||
uint32_t aColumnNumber,
|
||||
uint32_t aSeverityFlag,
|
||||
bool aIsChrome,
|
||||
uint64_t aInnerWindowID)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsAutoCString category;
|
||||
if (aIsChrome) {
|
||||
category.AssignLiteral("chrome ");
|
||||
} else {
|
||||
category.AssignLiteral("content ");
|
||||
}
|
||||
category.AppendLiteral("javascript");
|
||||
|
||||
nsCOMPtr<nsIConsoleService> consoleService =
|
||||
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
|
||||
MOZ_ASSERT(consoleService);
|
||||
|
||||
nsCOMPtr<nsIScriptError> scriptError =
|
||||
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
|
||||
MOZ_ASSERT(scriptError);
|
||||
|
||||
if (aInnerWindowID) {
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
|
||||
scriptError->InitWithWindowID(aMessage,
|
||||
aFilename,
|
||||
/* aSourceLine */ EmptyString(),
|
||||
aLineNumber,
|
||||
aColumnNumber,
|
||||
aSeverityFlag,
|
||||
category,
|
||||
aInnerWindowID)));
|
||||
} else {
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
|
||||
scriptError->Init(aMessage,
|
||||
aFilename,
|
||||
/* aSourceLine */ EmptyString(),
|
||||
aLineNumber,
|
||||
aColumnNumber,
|
||||
aSeverityFlag,
|
||||
category.get())));
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(consoleService->LogMessage(scriptError)));
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
Run() override
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mMessage.IsVoid() != mMessageName.IsVoid());
|
||||
|
||||
if (!mMessage.IsVoid()) {
|
||||
Dump(mMessage,
|
||||
mFilename,
|
||||
mLineNumber,
|
||||
mColumnNumber,
|
||||
mSeverityFlag,
|
||||
mIsChrome,
|
||||
mInnerWindowID);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
DumpLocalizedMessage(mMessageName,
|
||||
mFilename,
|
||||
mLineNumber,
|
||||
mColumnNumber,
|
||||
mSeverityFlag,
|
||||
mIsChrome,
|
||||
mInnerWindowID);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual ~ScriptErrorRunnable() {}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
namespace indexedDB {
|
||||
|
||||
/*static*/ void
|
||||
ScriptErrorHelper::Dump(const nsAString& aMessage,
|
||||
const nsAString& aFilename,
|
||||
uint32_t aLineNumber,
|
||||
uint32_t aColumnNumber,
|
||||
uint32_t aSeverityFlag,
|
||||
bool aIsChrome,
|
||||
uint64_t aInnerWindowID)
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
ScriptErrorRunnable::Dump(aMessage,
|
||||
aFilename,
|
||||
aLineNumber,
|
||||
aColumnNumber,
|
||||
aSeverityFlag,
|
||||
aIsChrome,
|
||||
aInnerWindowID);
|
||||
} else {
|
||||
RefPtr<ScriptErrorRunnable> runnable =
|
||||
new ScriptErrorRunnable(aMessage,
|
||||
aFilename,
|
||||
aLineNumber,
|
||||
aColumnNumber,
|
||||
aSeverityFlag,
|
||||
aIsChrome,
|
||||
aInnerWindowID);
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
|
||||
}
|
||||
}
|
||||
|
||||
/*static*/ void
|
||||
ScriptErrorHelper::DumpLocalizedMessage(const nsACString& aMessageName,
|
||||
const nsAString& aFilename,
|
||||
uint32_t aLineNumber,
|
||||
uint32_t aColumnNumber,
|
||||
uint32_t aSeverityFlag,
|
||||
bool aIsChrome,
|
||||
uint64_t aInnerWindowID)
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
ScriptErrorRunnable::DumpLocalizedMessage(nsAutoCString(aMessageName),
|
||||
aFilename,
|
||||
aLineNumber,
|
||||
aColumnNumber,
|
||||
aSeverityFlag,
|
||||
aIsChrome,
|
||||
aInnerWindowID);
|
||||
} else {
|
||||
RefPtr<ScriptErrorRunnable> runnable =
|
||||
new ScriptErrorRunnable(aMessageName,
|
||||
aFilename,
|
||||
aLineNumber,
|
||||
aColumnNumber,
|
||||
aSeverityFlag,
|
||||
aIsChrome,
|
||||
aInnerWindowID);
|
||||
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace indexedDB
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
41
dom/indexedDB/ScriptErrorHelper.h
Normal file
41
dom/indexedDB/ScriptErrorHelper.h
Normal file
@ -0,0 +1,41 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_dom_indexeddb_scripterrorhelper_h__
|
||||
#define mozilla_dom_indexeddb_scripterrorhelper_h__
|
||||
|
||||
class nsAString;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
namespace indexedDB {
|
||||
|
||||
// Helper to report a script error to the main thread.
|
||||
class ScriptErrorHelper
|
||||
{
|
||||
public:
|
||||
static void Dump(const nsAString& aMessage,
|
||||
const nsAString& aFilename,
|
||||
uint32_t aLineNumber,
|
||||
uint32_t aColumnNumber,
|
||||
uint32_t aSeverityFlag, /* nsIScriptError::xxxFlag */
|
||||
bool aIsChrome,
|
||||
uint64_t aInnerWindowID);
|
||||
|
||||
static void DumpLocalizedMessage(const nsACString& aMessageName,
|
||||
const nsAString& aFilename,
|
||||
uint32_t aLineNumber,
|
||||
uint32_t aColumnNumber,
|
||||
uint32_t aSeverityFlag, /* nsIScriptError::xxxFlag */
|
||||
bool aIsChrome,
|
||||
uint64_t aInnerWindowID);
|
||||
};
|
||||
|
||||
} // namespace indexedDB
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_indexeddb_scripterrorhelper_h__
|
@ -67,6 +67,7 @@ UNIFIED_SOURCES += [
|
||||
'KeyPath.cpp',
|
||||
'PermissionRequestBase.cpp',
|
||||
'ReportInternalError.cpp',
|
||||
'ScriptErrorHelper.cpp',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
|
@ -8,11 +8,11 @@
|
||||
interface nsIVariant;
|
||||
|
||||
/**
|
||||
* The nsIDOMNode interface is the primary datatype for the entire
|
||||
* The nsIDOMNode interface is the primary datatype for the entire
|
||||
* Document Object Model.
|
||||
* It represents a single node in the document tree.
|
||||
*
|
||||
* For more information on this interface please see
|
||||
* For more information on this interface please see
|
||||
* http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html
|
||||
*/
|
||||
|
||||
@ -46,10 +46,10 @@ interface nsIDOMNode : nsISupports
|
||||
readonly attribute nsIDOMNode nextSibling;
|
||||
// Modified in DOM Level 2:
|
||||
readonly attribute nsIDOMDocument ownerDocument;
|
||||
nsIDOMNode insertBefore(in nsIDOMNode newChild,
|
||||
nsIDOMNode insertBefore(in nsIDOMNode newChild,
|
||||
in nsIDOMNode refChild)
|
||||
raises(DOMException);
|
||||
nsIDOMNode replaceChild(in nsIDOMNode newChild,
|
||||
nsIDOMNode replaceChild(in nsIDOMNode newChild,
|
||||
in nsIDOMNode oldChild)
|
||||
raises(DOMException);
|
||||
nsIDOMNode removeChild(in nsIDOMNode oldChild)
|
||||
|
@ -1256,10 +1256,6 @@ MediaDecoderStateMachine::SetDormant(bool aDormant)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mReader) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mMetadataRequest.Exists()) {
|
||||
if (mPendingDormant && mPendingDormant.ref() != aDormant && !aDormant) {
|
||||
// We already have a dormant request pending; the new request would have
|
||||
|
@ -1060,7 +1060,13 @@ RTCPeerConnection.prototype = {
|
||||
"InvalidParameterError");
|
||||
}
|
||||
});
|
||||
this._impl.addTrack(track, stream);
|
||||
try {
|
||||
this._impl.addTrack(track, stream);
|
||||
} catch (e if (e.result == Cr.NS_ERROR_NOT_IMPLEMENTED)) {
|
||||
throw new this._win.DOMException(
|
||||
"track in constructed stream not yet supported (see Bug 1259236).",
|
||||
"NotSupportedError");
|
||||
}
|
||||
let sender = this._win.RTCRtpSender._create(this._win,
|
||||
new RTCRtpSender(this, track,
|
||||
stream));
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/gfx/Point.h"
|
||||
#include "mozilla/SyncRunnable.h"
|
||||
|
||||
#include "AudioSegment.h"
|
||||
#include "DecodedStream.h"
|
||||
@ -269,23 +270,35 @@ DecodedStream::Start(int64_t aStartTime, const MediaInfo& aInfo)
|
||||
|
||||
class R : public nsRunnable {
|
||||
typedef MozPromiseHolder<GenericPromise> Promise;
|
||||
typedef decltype(&DecodedStream::CreateData) Method;
|
||||
public:
|
||||
R(DecodedStream* aThis, Method aMethod, PlaybackInfoInit&& aInit, Promise&& aPromise)
|
||||
: mThis(aThis), mMethod(aMethod), mInit(Move(aInit))
|
||||
R(PlaybackInfoInit&& aInit, Promise&& aPromise, OutputStreamManager* aManager)
|
||||
: mInit(Move(aInit)), mOutputStreamManager(aManager)
|
||||
{
|
||||
mPromise = Move(aPromise);
|
||||
}
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
(mThis->*mMethod)(Move(mInit), Move(mPromise));
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
// No need to create a source stream when there are no output streams. This
|
||||
// happens when RemoveOutput() is called immediately after StartPlayback().
|
||||
if (!mOutputStreamManager->Graph()) {
|
||||
// Resolve the promise to indicate the end of playback.
|
||||
mPromise.Resolve(true, __func__);
|
||||
return NS_OK;
|
||||
}
|
||||
mData = MakeUnique<DecodedStreamData>(
|
||||
mOutputStreamManager, Move(mInit), Move(mPromise));
|
||||
return NS_OK;
|
||||
}
|
||||
UniquePtr<DecodedStreamData> ReleaseData()
|
||||
{
|
||||
return Move(mData);
|
||||
}
|
||||
private:
|
||||
RefPtr<DecodedStream> mThis;
|
||||
Method mMethod;
|
||||
PlaybackInfoInit mInit;
|
||||
Promise mPromise;
|
||||
RefPtr<OutputStreamManager> mOutputStreamManager;
|
||||
UniquePtr<DecodedStreamData> mData;
|
||||
};
|
||||
|
||||
MozPromiseHolder<GenericPromise> promise;
|
||||
@ -293,8 +306,15 @@ DecodedStream::Start(int64_t aStartTime, const MediaInfo& aInfo)
|
||||
PlaybackInfoInit init {
|
||||
aStartTime, aInfo
|
||||
};
|
||||
nsCOMPtr<nsIRunnable> r = new R(this, &DecodedStream::CreateData, Move(init), Move(promise));
|
||||
AbstractThread::MainThread()->Dispatch(r.forget());
|
||||
nsCOMPtr<nsIRunnable> r = new R(Move(init), Move(promise), mOutputStreamManager);
|
||||
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
|
||||
SyncRunnable::DispatchToThread(mainThread, r);
|
||||
mData = static_cast<R*>(r.get())->ReleaseData();
|
||||
|
||||
if (mData) {
|
||||
mData->SetPlaying(mPlaying);
|
||||
SendData();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -342,77 +362,6 @@ DecodedStream::DestroyData(UniquePtr<DecodedStreamData> aData)
|
||||
AbstractThread::MainThread()->Dispatch(r.forget());
|
||||
}
|
||||
|
||||
void
|
||||
DecodedStream::CreateData(PlaybackInfoInit&& aInit, MozPromiseHolder<GenericPromise>&& aPromise)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// No need to create a source stream when there are no output streams. This
|
||||
// happens when RemoveOutput() is called immediately after StartPlayback().
|
||||
if (!mOutputStreamManager->Graph()) {
|
||||
// Resolve the promise to indicate the end of playback.
|
||||
aPromise.Resolve(true, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = new DecodedStreamData(mOutputStreamManager, Move(aInit), Move(aPromise));
|
||||
|
||||
class R : public nsRunnable {
|
||||
typedef void(DecodedStream::*Method)(UniquePtr<DecodedStreamData>);
|
||||
public:
|
||||
R(DecodedStream* aThis, Method aMethod, DecodedStreamData* aData)
|
||||
: mThis(aThis), mMethod(aMethod), mData(aData) {}
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
(mThis->*mMethod)(Move(mData));
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
virtual ~R()
|
||||
{
|
||||
// mData is not transferred when dispatch fails and Run() is not called.
|
||||
// We need to dispatch a task to ensure DecodedStreamData is destroyed
|
||||
// properly on the main thread.
|
||||
if (mData) {
|
||||
DecodedStreamData* data = mData.release();
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
||||
delete data;
|
||||
});
|
||||
// We are in tail dispatching phase. Don't call
|
||||
// AbstractThread::MainThread()->Dispatch() to avoid reentrant
|
||||
// AutoTaskDispatcher.
|
||||
NS_DispatchToMainThread(r.forget());
|
||||
}
|
||||
}
|
||||
RefPtr<DecodedStream> mThis;
|
||||
Method mMethod;
|
||||
UniquePtr<DecodedStreamData> mData;
|
||||
};
|
||||
|
||||
// Post a message to ensure |mData| is only updated on the worker thread.
|
||||
// Note this could fail when MDSM begin to shut down the worker thread.
|
||||
nsCOMPtr<nsIRunnable> r = new R(this, &DecodedStream::OnDataCreated, data);
|
||||
mOwnerThread->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
|
||||
}
|
||||
|
||||
void
|
||||
DecodedStream::OnDataCreated(UniquePtr<DecodedStreamData> aData)
|
||||
{
|
||||
AssertOwnerThread();
|
||||
MOZ_ASSERT(!mData, "Already created.");
|
||||
|
||||
// Start to send data to the stream immediately
|
||||
if (mStartTime.isSome()) {
|
||||
aData->SetPlaying(mPlaying);
|
||||
mData = Move(aData);
|
||||
SendData();
|
||||
return;
|
||||
}
|
||||
|
||||
// Playback has ended. Destroy aData which is not needed anymore.
|
||||
DestroyData(Move(aData));
|
||||
}
|
||||
|
||||
void
|
||||
DecodedStream::SetPlaying(bool aPlaying)
|
||||
{
|
||||
|
@ -66,9 +66,7 @@ protected:
|
||||
virtual ~DecodedStream();
|
||||
|
||||
private:
|
||||
void CreateData(PlaybackInfoInit&& aInit, MozPromiseHolder<GenericPromise>&& aPromise);
|
||||
void DestroyData(UniquePtr<DecodedStreamData> aData);
|
||||
void OnDataCreated(UniquePtr<DecodedStreamData> aData);
|
||||
void AdvanceTracks();
|
||||
void SendAudio(double aVolume, bool aIsSameOrigin);
|
||||
void SendVideo(bool aIsSameOrigin);
|
||||
|
@ -420,6 +420,17 @@ var listenUntil = (target, eventName, onFire) => {
|
||||
}, false));
|
||||
};
|
||||
|
||||
/* Test that a function throws the right error */
|
||||
function mustThrowWith(msg, reason, f) {
|
||||
try {
|
||||
f();
|
||||
ok(false, msg + " must throw");
|
||||
} catch (e) {
|
||||
is(e.name, reason, msg + " must throw: " + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*** Test control flow methods */
|
||||
|
||||
/**
|
||||
|
@ -70,6 +70,7 @@ skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # copied from basi
|
||||
[test_getUserMedia_peerIdentity.html]
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 1021776, too --ing slow on b2g)
|
||||
[test_peerConnection_addIceCandidate.html]
|
||||
[test_peerConnection_addTrack.html]
|
||||
[test_peerConnection_basicAudio.html]
|
||||
skip-if = toolkit == 'gonk' # B2G emulator is too slow to handle a two-way audio call reliably
|
||||
[test_peerConnection_basicAudioRequireEOC.html]
|
||||
|
31
dom/media/tests/mochitest/test_peerConnection_addTrack.html
Normal file
31
dom/media/tests/mochitest/test_peerConnection_addTrack.html
Normal file
@ -0,0 +1,31 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<script type="application/javascript" src="pc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script type="application/javascript;version=1.8">
|
||||
createHTML({
|
||||
bug: "1259236",
|
||||
title: "PeerConnection addTrack errors",
|
||||
visible: true
|
||||
});
|
||||
|
||||
runNetworkTest(function() {
|
||||
navigator.mediaDevices.getUserMedia({ video: true })
|
||||
.then(gumStream => {
|
||||
let newStream = new MediaStream(gumStream.getTracks());
|
||||
|
||||
mustThrowWith("pc.addTrack a track from a constructed MediaStream",
|
||||
"NotSupportedError",
|
||||
() => new RTCPeerConnection().addTrack(newStream.getTracks()[0],
|
||||
newStream));
|
||||
})
|
||||
.catch(e => ok(false, "unexpected failure: " + e))
|
||||
.then(networkTestFinished);
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -19,15 +19,6 @@
|
||||
pc1.onicecandidate = e => add(pc2, e.candidate, generateErrorCallback());
|
||||
pc2.onicecandidate = e => add(pc1, e.candidate, generateErrorCallback());
|
||||
|
||||
function mustThrowWith(msg, reason, f) {
|
||||
try {
|
||||
f();
|
||||
ok(false, msg + " must throw");
|
||||
} catch (e) {
|
||||
is(e.name, reason, msg + " must throw: " + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
var v1, v2;
|
||||
var delivered = new Promise(resolve => pc2.ontrack = e => {
|
||||
// Test RTCTrackEvent here.
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "nsContentPolicyUtils.h"
|
||||
#include "nsIDOMElement.h"
|
||||
#include "nsIStreamConverterService.h"
|
||||
#include "nsIStreamLoader.h"
|
||||
#include "nsIHttpChannel.h"
|
||||
#include "nsIHttpChannelInternal.h"
|
||||
#include "nsIFileChannel.h"
|
||||
@ -34,7 +35,7 @@
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsNullPrincipal.h"
|
||||
|
||||
#define MAGIC_REQUEST_CONTEXT 0x01020304
|
||||
#define BYTERANGE_REQUEST_CONTEXT 0x01020304
|
||||
|
||||
// nsPluginByteRangeStreamListener
|
||||
|
||||
@ -55,7 +56,7 @@ private:
|
||||
|
||||
nsCOMPtr<nsIStreamListener> mStreamConverter;
|
||||
nsWeakPtr mWeakPtrPluginStreamListenerPeer;
|
||||
bool mRemoveMagicNumber;
|
||||
bool mRemoveByteRangeRequest;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsPluginByteRangeStreamListener,
|
||||
@ -66,7 +67,7 @@ NS_IMPL_ISUPPORTS(nsPluginByteRangeStreamListener,
|
||||
nsPluginByteRangeStreamListener::nsPluginByteRangeStreamListener(nsIWeakReference* aWeakPtr)
|
||||
{
|
||||
mWeakPtrPluginStreamListenerPeer = aWeakPtr;
|
||||
mRemoveMagicNumber = false;
|
||||
mRemoveByteRangeRequest = false;
|
||||
}
|
||||
|
||||
nsPluginByteRangeStreamListener::~nsPluginByteRangeStreamListener()
|
||||
@ -149,7 +150,7 @@ nsPluginByteRangeStreamListener::OnStartRequest(nsIRequest *request, nsISupports
|
||||
// if server cannot continue with byte range (206 status) and sending us whole object (200 status)
|
||||
// reset this seekable stream & try serve it to plugin instance as a file
|
||||
mStreamConverter = finalStreamListener;
|
||||
mRemoveMagicNumber = true;
|
||||
mRemoveByteRangeRequest = true;
|
||||
|
||||
rv = pslp->ServeStreamAsFile(request, ctxt);
|
||||
return rv;
|
||||
@ -173,15 +174,15 @@ nsPluginByteRangeStreamListener::OnStopRequest(nsIRequest *request, nsISupports
|
||||
NS_ERROR("OnStopRequest received for untracked byte-range request!");
|
||||
}
|
||||
|
||||
if (mRemoveMagicNumber) {
|
||||
// remove magic number from container
|
||||
if (mRemoveByteRangeRequest) {
|
||||
// remove byte range request from container
|
||||
nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(ctxt);
|
||||
if (container) {
|
||||
uint32_t magicNumber = 0;
|
||||
container->GetData(&magicNumber);
|
||||
if (magicNumber == MAGIC_REQUEST_CONTEXT) {
|
||||
uint32_t byteRangeRequest = 0;
|
||||
container->GetData(&byteRangeRequest);
|
||||
if (byteRangeRequest == BYTERANGE_REQUEST_CONTEXT) {
|
||||
// to allow properly finish nsPluginStreamListenerPeer->OnStopRequest()
|
||||
// set it to something that is not the magic number.
|
||||
// set it to something that is not the byte range request.
|
||||
container->SetData(0);
|
||||
}
|
||||
} else {
|
||||
@ -660,6 +661,63 @@ nsPluginStreamListenerPeer::MakeByteRangeString(NPByteRange* aRangeList, nsACStr
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX: Converting the channel within nsPluginStreamListenerPeer
|
||||
// to use asyncOpen2() and do not want to touch the fragile logic
|
||||
// of byte range requests. Hence we just introduce this lightweight
|
||||
// wrapper to proxy the context.
|
||||
class PluginContextProxy final : public nsIStreamListener
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
PluginContextProxy(nsIStreamListener *aListener, nsISupports* aContext)
|
||||
: mListener(aListener)
|
||||
, mContext(aContext)
|
||||
{
|
||||
MOZ_ASSERT(aListener);
|
||||
MOZ_ASSERT(aContext);
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
OnDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream *aIStream,
|
||||
uint64_t aSourceOffset,
|
||||
uint32_t aLength) override
|
||||
{
|
||||
// Proxy OnDataAvailable using the internal context
|
||||
return mListener->OnDataAvailable(aRequest,
|
||||
mContext,
|
||||
aIStream,
|
||||
aSourceOffset,
|
||||
aLength);
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) override
|
||||
{
|
||||
// Proxy OnStartRequest using the internal context
|
||||
return mListener->OnStartRequest(aRequest, mContext);
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
|
||||
nsresult aStatusCode) override
|
||||
{
|
||||
// Proxy OnStopRequest using the inernal context
|
||||
return mListener->OnStopRequest(aRequest,
|
||||
mContext,
|
||||
aStatusCode);
|
||||
}
|
||||
|
||||
private:
|
||||
~PluginContextProxy() {}
|
||||
nsCOMPtr<nsIStreamListener> mListener;
|
||||
nsCOMPtr<nsISupports> mContext;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(PluginContextProxy, nsIStreamListener)
|
||||
|
||||
nsresult
|
||||
nsPluginStreamListenerPeer::RequestRead(NPByteRange* rangeList)
|
||||
{
|
||||
@ -692,22 +750,19 @@ nsPluginStreamListenerPeer::RequestRead(NPByteRange* rangeList)
|
||||
rv = NS_NewChannel(getter_AddRefs(channel),
|
||||
mURL,
|
||||
requestingNode,
|
||||
nsILoadInfo::SEC_NORMAL,
|
||||
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
|
||||
nsIContentPolicy::TYPE_OTHER,
|
||||
loadGroup,
|
||||
callbacks,
|
||||
nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
|
||||
}
|
||||
else {
|
||||
// in this else branch we really don't know where the load is coming
|
||||
// from and in fact should use something better than just using
|
||||
// a nullPrincipal as the loadingPrincipal.
|
||||
nsCOMPtr<nsIPrincipal> principal = nsNullPrincipal::Create();
|
||||
NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
|
||||
// In this else branch we really don't know where the load is coming
|
||||
// from. Let's fall back to using the SystemPrincipal for such Plugins.
|
||||
rv = NS_NewChannel(getter_AddRefs(channel),
|
||||
mURL,
|
||||
principal,
|
||||
nsILoadInfo::SEC_NORMAL,
|
||||
nsContentUtils::GetSystemPrincipal(),
|
||||
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
|
||||
nsIContentPolicy::TYPE_OTHER,
|
||||
loadGroup,
|
||||
callbacks,
|
||||
@ -743,16 +798,16 @@ nsPluginStreamListenerPeer::RequestRead(NPByteRange* rangeList)
|
||||
mPendingRequests += numRequests;
|
||||
|
||||
nsCOMPtr<nsISupportsPRUint32> container = do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
rv = container->SetData(MAGIC_REQUEST_CONTEXT);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = container->SetData(BYTERANGE_REQUEST_CONTEXT);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = channel->AsyncOpen(converter, container);
|
||||
if (NS_SUCCEEDED(rv))
|
||||
TrackRequest(channel);
|
||||
return rv;
|
||||
RefPtr<PluginContextProxy> pluginContextProxy =
|
||||
new PluginContextProxy(converter, container);
|
||||
rv = channel->AsyncOpen2(pluginContextProxy);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
TrackRequest(channel);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -842,12 +897,12 @@ NS_IMETHODIMP nsPluginStreamListenerPeer::OnDataAvailable(nsIRequest *request,
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
if (mAbort) {
|
||||
uint32_t magicNumber = 0; // set it to something that is not the magic number.
|
||||
uint32_t byteRangeRequest = 0; // set it to something that is not the byte range request.
|
||||
nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(aContext);
|
||||
if (container)
|
||||
container->GetData(&magicNumber);
|
||||
container->GetData(&byteRangeRequest);
|
||||
|
||||
if (magicNumber != MAGIC_REQUEST_CONTEXT) {
|
||||
if (byteRangeRequest != BYTERANGE_REQUEST_CONTEXT) {
|
||||
// this is not one of our range requests
|
||||
mAbort = false;
|
||||
return NS_BINDING_ABORTED;
|
||||
@ -980,9 +1035,9 @@ NS_IMETHODIMP nsPluginStreamListenerPeer::OnStopRequest(nsIRequest *request,
|
||||
// we keep our connections around...
|
||||
nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(aContext);
|
||||
if (container) {
|
||||
uint32_t magicNumber = 0; // set it to something that is not the magic number.
|
||||
container->GetData(&magicNumber);
|
||||
if (magicNumber == MAGIC_REQUEST_CONTEXT) {
|
||||
uint32_t byteRangeRequest = 0; // something other than the byte range request.
|
||||
container->GetData(&byteRangeRequest);
|
||||
if (byteRangeRequest == BYTERANGE_REQUEST_CONTEXT) {
|
||||
// this is one of our range requests
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -16,6 +16,14 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var isParent = Services.appinfo.processType === Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
|
||||
|
||||
// The default Push service implementation.
|
||||
XPCOMUtils.defineLazyGetter(this, "PushService", function() {
|
||||
const {PushService} = Cu.import("resource://gre/modules/PushService.jsm",
|
||||
{});
|
||||
PushService.init();
|
||||
return PushService;
|
||||
});
|
||||
|
||||
// Observer notification topics for system subscriptions. These are duplicated
|
||||
// and used in `PushNotifier.cpp`. They're exposed on `nsIPushService` instead
|
||||
// of `nsIPushNotifier` so that JS callers only need to import this service.
|
||||
@ -99,14 +107,6 @@ PushServiceParent.prototype = Object.create(PushServiceBase.prototype);
|
||||
XPCOMUtils.defineLazyServiceGetter(PushServiceParent.prototype, "_mm",
|
||||
"@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(PushServiceParent.prototype, "_service",
|
||||
function() {
|
||||
const {PushService} = Cu.import("resource://gre/modules/PushService.jsm",
|
||||
{});
|
||||
PushService.init();
|
||||
return PushService;
|
||||
});
|
||||
|
||||
Object.assign(PushServiceParent.prototype, {
|
||||
_xpcom_factory: XPCOMUtils.generateSingletonFactory(PushServiceParent),
|
||||
|
||||
@ -164,11 +164,11 @@ Object.assign(PushServiceParent.prototype, {
|
||||
// nsIPushQuotaManager methods
|
||||
|
||||
notificationForOriginShown(origin) {
|
||||
this._service.notificationForOriginShown(origin);
|
||||
this.service.notificationForOriginShown(origin);
|
||||
},
|
||||
|
||||
notificationForOriginClosed(origin) {
|
||||
this._service.notificationForOriginClosed(origin);
|
||||
this.service.notificationForOriginClosed(origin);
|
||||
},
|
||||
|
||||
receiveMessage(message) {
|
||||
@ -201,7 +201,7 @@ Object.assign(PushServiceParent.prototype, {
|
||||
},
|
||||
|
||||
_handleReady() {
|
||||
this._service.init();
|
||||
this.service.init();
|
||||
},
|
||||
|
||||
_toPageRecord(principal, data) {
|
||||
@ -228,7 +228,7 @@ Object.assign(PushServiceParent.prototype, {
|
||||
|
||||
_handleRequest(name, principal, data) {
|
||||
if (name == "Push:Clear") {
|
||||
return this._service.clear(data);
|
||||
return this.service.clear(data);
|
||||
}
|
||||
|
||||
let pageRecord;
|
||||
@ -239,13 +239,13 @@ Object.assign(PushServiceParent.prototype, {
|
||||
}
|
||||
|
||||
if (name === "Push:Register") {
|
||||
return this._service.register(pageRecord);
|
||||
return this.service.register(pageRecord);
|
||||
}
|
||||
if (name === "Push:Registration") {
|
||||
return this._service.registration(pageRecord);
|
||||
return this.service.registration(pageRecord);
|
||||
}
|
||||
if (name === "Push:Unregister") {
|
||||
return this._service.unregister(pageRecord);
|
||||
return this.service.unregister(pageRecord);
|
||||
}
|
||||
|
||||
return Promise.reject(new Error("Invalid request: unknown name"));
|
||||
@ -259,12 +259,22 @@ Object.assign(PushServiceParent.prototype, {
|
||||
// Methods used for mocking in tests.
|
||||
|
||||
replaceServiceBackend(options) {
|
||||
this._service.changeTestServer(options.serverURI, options);
|
||||
this.service.changeTestServer(options.serverURI, options);
|
||||
},
|
||||
|
||||
restoreServiceBackend() {
|
||||
var defaultServerURL = Services.prefs.getCharPref("dom.push.serverURL");
|
||||
this._service.changeTestServer(defaultServerURL);
|
||||
this.service.changeTestServer(defaultServerURL);
|
||||
},
|
||||
});
|
||||
|
||||
// Used to replace the implementation with a mock.
|
||||
Object.defineProperty(PushServiceParent.prototype, "service", {
|
||||
get() {
|
||||
return this._service || PushService;
|
||||
},
|
||||
set(impl) {
|
||||
this._service = impl;
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -14,7 +14,15 @@ this.EXPORTED_SYMBOLS = ['PushCrypto', 'concatArray',
|
||||
'base64UrlDecode'];
|
||||
|
||||
var UTF8 = new TextEncoder('utf-8');
|
||||
var ENCRYPT_INFO = UTF8.encode('Content-Encoding: aesgcm128');
|
||||
|
||||
// Legacy encryption scheme (draft-thomson-http-encryption-02).
|
||||
var AESGCM128_ENCODING = 'aesgcm128';
|
||||
var AESGCM128_ENCRYPT_INFO = UTF8.encode('Content-Encoding: aesgcm128');
|
||||
|
||||
// New encryption scheme (draft-ietf-httpbis-encryption-encoding-01).
|
||||
var AESGCM_ENCODING = 'aesgcm';
|
||||
var AESGCM_ENCRYPT_INFO = UTF8.encode('Content-Encoding: aesgcm');
|
||||
|
||||
var NONCE_INFO = UTF8.encode('Content-Encoding: nonce');
|
||||
var AUTH_INFO = UTF8.encode('Content-Encoding: auth\0'); // note nul-terminus
|
||||
var P256DH_INFO = UTF8.encode('P-256\0');
|
||||
@ -53,14 +61,28 @@ this.getCryptoParams = function(headers) {
|
||||
}
|
||||
|
||||
var requiresAuthenticationSecret = true;
|
||||
var keymap = getEncryptionKeyParams(headers.crypto_key);
|
||||
if (!keymap) {
|
||||
requiresAuthenticationSecret = false;
|
||||
keymap = getEncryptionKeyParams(headers.encryption_key);
|
||||
var keymap;
|
||||
var padSize;
|
||||
if (headers.encoding == AESGCM_ENCODING) {
|
||||
// aesgcm uses the Crypto-Key header, 2 bytes for the pad length, and an
|
||||
// authentication secret.
|
||||
keymap = getEncryptionKeyParams(headers.crypto_key);
|
||||
padSize = 2;
|
||||
} else if (headers.encoding == AESGCM128_ENCODING) {
|
||||
// aesgcm128 uses Crypto-Key or Encryption-Key, and 1 byte for the pad
|
||||
// length.
|
||||
keymap = getEncryptionKeyParams(headers.crypto_key);
|
||||
padSize = 1;
|
||||
if (!keymap) {
|
||||
return null;
|
||||
// Encryption-Key header indicates unauthenticated encryption.
|
||||
requiresAuthenticationSecret = false;
|
||||
keymap = getEncryptionKeyParams(headers.encryption_key);
|
||||
}
|
||||
}
|
||||
if (!keymap) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var enc = getEncryptionParams(headers.encryption);
|
||||
if (!enc) {
|
||||
return null;
|
||||
@ -69,10 +91,10 @@ this.getCryptoParams = function(headers) {
|
||||
var salt = enc.salt;
|
||||
var rs = (enc.rs)? parseInt(enc.rs, 10) : 4096;
|
||||
|
||||
if (!dh || !salt || isNaN(rs) || (rs <= 1)) {
|
||||
if (!dh || !salt || isNaN(rs) || (rs <= padSize)) {
|
||||
return null;
|
||||
}
|
||||
return {dh, salt, rs, auth: requiresAuthenticationSecret};
|
||||
return {dh, salt, rs, auth: requiresAuthenticationSecret, padSize};
|
||||
}
|
||||
|
||||
var parseHeaderFieldParams = (m, v) => {
|
||||
@ -195,8 +217,8 @@ this.PushCrypto = {
|
||||
]));
|
||||
},
|
||||
|
||||
decodeMsg(aData, aPrivateKey, aPublicKey, aSenderPublicKey,
|
||||
aSalt, aRs, aAuthenticationSecret) {
|
||||
decodeMsg(aData, aPrivateKey, aPublicKey, aSenderPublicKey, aSalt, aRs,
|
||||
aAuthenticationSecret, aPadSize) {
|
||||
|
||||
if (aData.byteLength === 0) {
|
||||
// Zero length messages will be passed as null.
|
||||
@ -219,7 +241,8 @@ this.PushCrypto = {
|
||||
.then(([appServerKey, subscriptionPrivateKey]) =>
|
||||
crypto.subtle.deriveBits({ name: 'ECDH', public: appServerKey },
|
||||
subscriptionPrivateKey, 256))
|
||||
.then(ikm => this._deriveKeyAndNonce(new Uint8Array(ikm),
|
||||
.then(ikm => this._deriveKeyAndNonce(aPadSize,
|
||||
new Uint8Array(ikm),
|
||||
base64UrlDecode(aSalt),
|
||||
aPublicKey,
|
||||
senderKey,
|
||||
@ -227,13 +250,15 @@ this.PushCrypto = {
|
||||
.then(r =>
|
||||
// AEAD_AES_128_GCM expands ciphertext to be 16 octets longer.
|
||||
Promise.all(chunkArray(aData, aRs + 16).map((slice, index) =>
|
||||
this._decodeChunk(slice, index, r[1], r[0]))))
|
||||
this._decodeChunk(aPadSize, slice, index, r[1], r[0]))))
|
||||
.then(r => concatArray(r));
|
||||
},
|
||||
|
||||
_deriveKeyAndNonce(ikm, salt, receiverKey, senderKey, authenticationSecret) {
|
||||
_deriveKeyAndNonce(padSize, ikm, salt, receiverKey, senderKey,
|
||||
authenticationSecret) {
|
||||
var kdfPromise;
|
||||
var context;
|
||||
var encryptInfo;
|
||||
// The authenticationSecret, when present, is mixed with the ikm using HKDF.
|
||||
// This is its primary purpose. However, since the authentication secret
|
||||
// was added at the same time that the info string was changed, we also use
|
||||
@ -260,12 +285,19 @@ this.PushCrypto = {
|
||||
this._encodeLength(receiverKey), receiverKey,
|
||||
this._encodeLength(senderKey), senderKey
|
||||
]);
|
||||
// Finally, we use the pad size to infer the content encoding.
|
||||
encryptInfo = padSize == 2 ? AESGCM_ENCRYPT_INFO :
|
||||
AESGCM128_ENCRYPT_INFO;
|
||||
} else {
|
||||
if (padSize == 2) {
|
||||
throw new Error("aesgcm encoding requires an authentication secret");
|
||||
}
|
||||
kdfPromise = Promise.resolve(new hkdf(salt, ikm));
|
||||
context = new Uint8Array(0);
|
||||
encryptInfo = AESGCM128_ENCRYPT_INFO;
|
||||
}
|
||||
return kdfPromise.then(kdf => Promise.all([
|
||||
kdf.extract(concatArray([ENCRYPT_INFO, context]), 16)
|
||||
kdf.extract(concatArray([encryptInfo, context]), 16)
|
||||
.then(gcmBits => crypto.subtle.importKey('raw', gcmBits, 'AES-GCM', false,
|
||||
['decrypt'])),
|
||||
kdf.extract(concatArray([NONCE_INFO, context]), 12)
|
||||
@ -276,27 +308,44 @@ this.PushCrypto = {
|
||||
return new Uint8Array([0, buffer.byteLength]);
|
||||
},
|
||||
|
||||
_decodeChunk(aSlice, aIndex, aNonce, aKey) {
|
||||
_decodeChunk(aPadSize, aSlice, aIndex, aNonce, aKey) {
|
||||
let params = {
|
||||
name: 'AES-GCM',
|
||||
iv: generateNonce(aNonce, aIndex)
|
||||
};
|
||||
return crypto.subtle.decrypt(params, aKey, aSlice)
|
||||
.then(decoded => {
|
||||
decoded = new Uint8Array(decoded);
|
||||
if (decoded.length == 0) {
|
||||
return Promise.reject(new Error('Decoded array is too short!'));
|
||||
} else if (decoded[0] > decoded.length) {
|
||||
return Promise.reject(new Error ('Padding is wrong!'));
|
||||
} else {
|
||||
// All padded bytes must be zero except the first one.
|
||||
for (var i = 1; i <= decoded[0]; i++) {
|
||||
if (decoded[i] != 0) {
|
||||
return Promise.reject(new Error('Padding is wrong!'));
|
||||
}
|
||||
}
|
||||
return decoded.slice(decoded[0] + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
.then(decoded => this._unpadChunk(aPadSize, new Uint8Array(decoded)));
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes padding from a decrypted chunk.
|
||||
*
|
||||
* @param {Number} padSize The size of the padding length prepended to each
|
||||
* chunk. For aesgcm, the padding length is expressed as a 16-bit unsigned
|
||||
* big endian integer. For aesgcm128, the padding is an 8-bit integer.
|
||||
* @param {Uint8Array} decoded The decrypted, padded chunk.
|
||||
* @returns {Uint8Array} The chunk with padding removed.
|
||||
*/
|
||||
_unpadChunk(padSize, decoded) {
|
||||
if (padSize < 1 || padSize > 2) {
|
||||
throw new Error('Unsupported pad size');
|
||||
}
|
||||
if (decoded.length < padSize) {
|
||||
throw new Error('Decoded array is too short!');
|
||||
}
|
||||
var pad = decoded[0];
|
||||
if (padSize == 2) {
|
||||
pad = (pad << 8) | decoded[1];
|
||||
}
|
||||
if (pad > decoded.length) {
|
||||
throw new Error ('Padding is wrong!');
|
||||
}
|
||||
// All padded bytes must be zero except the first one.
|
||||
for (var i = padSize; i <= pad; i++) {
|
||||
if (decoded[i] !== 0) {
|
||||
throw new Error('Padding is wrong!');
|
||||
}
|
||||
}
|
||||
return decoded.slice(pad + padSize);
|
||||
},
|
||||
};
|
||||
|
@ -811,7 +811,8 @@ this.PushService = {
|
||||
cryptoParams.dh,
|
||||
cryptoParams.salt,
|
||||
cryptoParams.rs,
|
||||
cryptoParams.auth ? record.authenticationSecret : null
|
||||
cryptoParams.auth ? record.authenticationSecret : null,
|
||||
cryptoParams.padSize
|
||||
);
|
||||
} else {
|
||||
decodedPromise = Promise.resolve(null);
|
||||
|
@ -114,6 +114,7 @@ this.PushServiceAndroidGCM = {
|
||||
encryption_key: data.enckey,
|
||||
crypto_key: data.cryptokey,
|
||||
encryption: data.enc,
|
||||
encoding: data.con,
|
||||
};
|
||||
cryptoParams = getCryptoParams(headers);
|
||||
// Ciphertext is (urlsafe) Base 64 encoded.
|
||||
|
@ -155,6 +155,7 @@ PushChannelListener.prototype = {
|
||||
encryption_key: getHeaderField(aRequest, "Encryption-Key"),
|
||||
crypto_key: getHeaderField(aRequest, "Crypto-Key"),
|
||||
encryption: getHeaderField(aRequest, "Encryption"),
|
||||
encoding: getHeaderField(aRequest, "Content-Encoding"),
|
||||
};
|
||||
let cryptoParams = getCryptoParams(headers);
|
||||
let msg = concatArray(this._message);
|
||||
|
@ -48,7 +48,7 @@ MockWebSocketParent.prototype = {
|
||||
},
|
||||
|
||||
sendMsg(msg) {
|
||||
sendAsyncMessage("client-msg", msg);
|
||||
sendAsyncMessage("socket-client-msg", msg);
|
||||
},
|
||||
|
||||
close() {
|
||||
@ -83,7 +83,7 @@ var pushService = Cc["@mozilla.org/push/Service;1"].
|
||||
|
||||
var mockWebSocket;
|
||||
|
||||
addMessageListener("setup", function () {
|
||||
addMessageListener("socket-setup", function () {
|
||||
mockWebSocket = new Promise((resolve, reject) => {
|
||||
var mockSocket = null;
|
||||
pushService.replaceServiceBackend({
|
||||
@ -101,15 +101,72 @@ addMessageListener("setup", function () {
|
||||
});
|
||||
});
|
||||
|
||||
addMessageListener("teardown", function () {
|
||||
addMessageListener("socket-teardown", function () {
|
||||
mockWebSocket.then(socket => {
|
||||
socket.close();
|
||||
pushService.restoreServiceBackend();
|
||||
});
|
||||
});
|
||||
|
||||
addMessageListener("server-msg", function (msg) {
|
||||
addMessageListener("socket-server-msg", function (msg) {
|
||||
mockWebSocket.then(socket => {
|
||||
socket.serverSendMsg(msg);
|
||||
});
|
||||
});
|
||||
|
||||
var MockService = {
|
||||
requestID: 1,
|
||||
resolvers: new Map(),
|
||||
|
||||
sendRequest(name, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let id = this.requestID++;
|
||||
this.resolvers.set(id, { resolve, reject });
|
||||
sendAsyncMessage("service-request", {
|
||||
name: name,
|
||||
id: id,
|
||||
params: params,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
handleResponse(response) {
|
||||
if (!this.resolvers.has(response.id)) {
|
||||
Cu.reportError(`Unexpected response for request ${response.id}`);
|
||||
return;
|
||||
}
|
||||
let resolver = this.resolvers.get(response.id);
|
||||
this.resolvers.delete(response.id);
|
||||
if (response.error) {
|
||||
resolver.reject(response.error);
|
||||
} else {
|
||||
resolver.resolve(response.result);
|
||||
}
|
||||
},
|
||||
|
||||
init() {},
|
||||
|
||||
register(pageRecord) {
|
||||
return this.sendRequest("register", pageRecord);
|
||||
},
|
||||
|
||||
registration(pageRecord) {
|
||||
return this.sendRequest("registration", pageRecord);
|
||||
},
|
||||
|
||||
unregister(pageRecord) {
|
||||
return this.sendRequest("unregister", pageRecord);
|
||||
},
|
||||
};
|
||||
|
||||
addMessageListener("service-replace", function () {
|
||||
pushService.service = MockService;
|
||||
});
|
||||
|
||||
addMessageListener("service-restore", function () {
|
||||
pushService.service = null;
|
||||
});
|
||||
|
||||
addMessageListener("service-response", function (response) {
|
||||
MockService.handleResponse(response);
|
||||
});
|
||||
|
@ -42,7 +42,7 @@ http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
var registration;
|
||||
add_task(function* start() {
|
||||
yield setupPrefsAndMock(mockSocket);
|
||||
yield setupPrefsAndMockSocket(mockSocket);
|
||||
yield setPushPermission(true);
|
||||
|
||||
var url = "worker.js" + "?" + (Math.random());
|
||||
|
@ -119,7 +119,7 @@ http://creativecommons.org/licenses/publicdomain/
|
||||
}).then(SimpleTest.finish);
|
||||
}
|
||||
|
||||
setupPrefsAndMock(new MockWebSocket()).then(_ => runTest());
|
||||
setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest());
|
||||
SpecialPowers.addPermission("desktop-notification", true, document);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
</script>
|
||||
|
@ -114,7 +114,7 @@ http://creativecommons.org/licenses/publicdomain/
|
||||
}).then(SimpleTest.finish);
|
||||
}
|
||||
|
||||
setupPrefsAndMock(new MockWebSocket()).then(_ => runTest());
|
||||
setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest());
|
||||
SpecialPowers.addPermission("desktop-notification", true, document);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
</script>
|
||||
|
@ -57,8 +57,8 @@ http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
function setupMultipleSubscriptions(swr) {
|
||||
// We need to do this to restart service so that a queue will be formed.
|
||||
teardownMockPushService();
|
||||
setupMockPushService(new MockWebSocket());
|
||||
teardownMockPushSocket();
|
||||
setupMockPushSocket(new MockWebSocket());
|
||||
|
||||
return Promise.all([
|
||||
subscribe(swr),
|
||||
@ -99,7 +99,7 @@ http://creativecommons.org/licenses/publicdomain/
|
||||
}).then(SimpleTest.finish);
|
||||
}
|
||||
|
||||
setupPrefsAndMock(new MockWebSocket()).then(_ => runTest());
|
||||
setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest());
|
||||
SpecialPowers.addPermission("desktop-notification", true, document);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
</script>
|
||||
|
@ -31,7 +31,7 @@ http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
var registration;
|
||||
add_task(function* start() {
|
||||
yield setupPrefsAndMock(new MockWebSocket());
|
||||
yield setupPrefsAndMockSocket(new MockWebSocket());
|
||||
yield setPushPermission(false);
|
||||
|
||||
var url = "worker.js" + "?" + Math.random();
|
||||
|
@ -46,7 +46,7 @@ http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
var registration;
|
||||
add_task(function* start() {
|
||||
yield setupPrefsAndMock(mockSocket);
|
||||
yield setupPrefsAndMockSocket(mockSocket);
|
||||
yield setPushPermission(true);
|
||||
|
||||
var url = "worker.js" + "?" + (Math.random());
|
||||
|
@ -335,7 +335,7 @@
|
||||
}).then(SimpleTest.finish);
|
||||
}
|
||||
|
||||
setupPrefsAndMock(mockSocket).then(_ => runTest());
|
||||
setupPrefsAndMockSocket(mockSocket).then(_ => runTest());
|
||||
SpecialPowers.addPermission('desktop-notification', true, document);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
</script>
|
||||
|
@ -27,7 +27,7 @@ http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
var registration;
|
||||
add_task(function* start() {
|
||||
yield setupPrefsAndMock(new MockWebSocket());
|
||||
yield setupPrefsAndMockSocket(new MockWebSocket());
|
||||
yield setPushPermission(true);
|
||||
|
||||
var url = "worker.js" + "?" + (Math.random());
|
||||
|
@ -296,7 +296,7 @@ http://creativecommons.org/licenses/publicdomain/
|
||||
.then(SimpleTest.finish);
|
||||
}
|
||||
|
||||
setupPrefsAndMock(new MockWebSocket()).then(_ => runTest());
|
||||
setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest());
|
||||
SpecialPowers.addPermission("desktop-notification", true, document);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
</script>
|
||||
|
@ -80,7 +80,7 @@ http://creativecommons.org/licenses/publicdomain/
|
||||
}).then(SimpleTest.finish);
|
||||
}
|
||||
|
||||
setupPrefsAndMock(new MockWebSocket()).then(_ => runTest());
|
||||
setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest());
|
||||
SpecialPowers.addPermission("desktop-notification", true, document);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
</script>
|
||||
|
@ -4,23 +4,62 @@
|
||||
let url = SimpleTest.getTestFileURL("mockpushserviceparent.js");
|
||||
let chromeScript = SpecialPowers.loadChromeScript(url);
|
||||
|
||||
/**
|
||||
* Replaces `PushService.jsm` with a mock implementation that handles requests
|
||||
* from the DOM API. This allows tests to simulate local errors and error
|
||||
* reporting, bypassing the `PushService.jsm` machinery.
|
||||
*/
|
||||
function replacePushService(mockService) {
|
||||
chromeScript.sendSyncMessage("service-replace");
|
||||
chromeScript.addMessageListener("service-request", function(msg) {
|
||||
let promise;
|
||||
try {
|
||||
let handler = mockService[msg.name];
|
||||
promise = Promise.resolve(handler(msg.params));
|
||||
} catch (error) {
|
||||
promise = Promise.reject(error);
|
||||
}
|
||||
promise.then(result => {
|
||||
chromeScript.sendAsyncMessage("service-response", {
|
||||
id: msg.id,
|
||||
result: result,
|
||||
});
|
||||
}, error => {
|
||||
chromeScript.sendAsyncMessage("service-response", {
|
||||
id: msg.id,
|
||||
error: error,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function restorePushService() {
|
||||
chromeScript.sendSyncMessage("service-restore");
|
||||
}
|
||||
|
||||
let userAgentID = "8e1c93a9-139b-419c-b200-e715bb1e8ce8";
|
||||
|
||||
let currentMockSocket = null;
|
||||
|
||||
function setupMockPushService(mockWebSocket) {
|
||||
/**
|
||||
* Sets up a mock connection for the WebSocket backend. This only replaces
|
||||
* the transport layer; `PushService.jsm` still handles DOM API requests,
|
||||
* observes permission changes, writes to IndexedDB, and notifies service
|
||||
* workers of incoming push messages.
|
||||
*/
|
||||
function setupMockPushSocket(mockWebSocket) {
|
||||
currentMockSocket = mockWebSocket;
|
||||
currentMockSocket._isActive = true;
|
||||
chromeScript.sendSyncMessage("setup");
|
||||
chromeScript.addMessageListener("client-msg", function(msg) {
|
||||
chromeScript.sendSyncMessage("socket-setup");
|
||||
chromeScript.addMessageListener("socket-client-msg", function(msg) {
|
||||
mockWebSocket.handleMessage(msg);
|
||||
});
|
||||
}
|
||||
|
||||
function teardownMockPushService() {
|
||||
function teardownMockPushSocket() {
|
||||
if (currentMockSocket) {
|
||||
currentMockSocket._isActive = false;
|
||||
chromeScript.sendSyncMessage("teardown");
|
||||
chromeScript.sendSyncMessage("socket-teardown");
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,14 +129,16 @@
|
||||
|
||||
serverSendMsg(msg) {
|
||||
if (this._isActive) {
|
||||
chromeScript.sendAsyncMessage("server-msg", msg);
|
||||
chromeScript.sendAsyncMessage("socket-server-msg", msg);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
g.MockWebSocket = MockWebSocket;
|
||||
g.setupMockPushService = setupMockPushService;
|
||||
g.teardownMockPushService = teardownMockPushService;
|
||||
g.setupMockPushSocket = setupMockPushSocket;
|
||||
g.teardownMockPushSocket = teardownMockPushSocket;
|
||||
g.replacePushService = replacePushService;
|
||||
g.restorePushService = restorePushService;
|
||||
}(this));
|
||||
|
||||
// Remove permissions and prefs when the test finishes.
|
||||
@ -107,7 +148,8 @@ SimpleTest.registerCleanupFunction(() => {
|
||||
SpecialPowers.flushPrefEnv(resolve);
|
||||
});
|
||||
}).then(_ => {
|
||||
teardownMockPushService();
|
||||
teardownMockPushSocket();
|
||||
restorePushService();
|
||||
});
|
||||
});
|
||||
|
||||
@ -119,9 +161,8 @@ function setPushPermission(allow) {
|
||||
});
|
||||
}
|
||||
|
||||
function setupPrefsAndMock(mockSocket) {
|
||||
function setupPrefs() {
|
||||
return new Promise(resolve => {
|
||||
setupMockPushService(mockSocket);
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
["dom.push.enabled", true],
|
||||
["dom.push.connection.enabled", true],
|
||||
@ -132,6 +173,16 @@ function setupPrefsAndMock(mockSocket) {
|
||||
});
|
||||
}
|
||||
|
||||
function setupPrefsAndReplaceService(mockService) {
|
||||
replacePushService(mockService);
|
||||
return setupPrefs();
|
||||
}
|
||||
|
||||
function setupPrefsAndMockSocket(mockSocket) {
|
||||
setupMockPushSocket(mockSocket);
|
||||
return setupPrefs();
|
||||
}
|
||||
|
||||
function injectControlledFrame(target = document.body) {
|
||||
return new Promise(function(res, rej) {
|
||||
var iframe = document.createElement("iframe");
|
||||
|
@ -22,8 +22,6 @@ XPCOMUtils.defineLazyServiceGetter(this, 'PushServiceComponent',
|
||||
const serviceExports = Cu.import('resource://gre/modules/PushService.jsm', {});
|
||||
const servicePrefs = new Preferences('dom.push.');
|
||||
|
||||
const DEFAULT_TIMEOUT = 5000;
|
||||
|
||||
const WEBSOCKET_CLOSE_GOING_AWAY = 1001;
|
||||
|
||||
var isParent = Cc['@mozilla.org/xre/runtime;1']
|
||||
@ -98,31 +96,6 @@ function promiseObserverNotification(topic, matchFunc) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a promise to settle. Returns a rejected promise if the promise
|
||||
* is not resolved or rejected within the given delay.
|
||||
*
|
||||
* @param {Promise} promise The pending promise.
|
||||
* @param {Number} delay The time to wait before rejecting the promise.
|
||||
* @param {String} [message] The rejection message if the promise times out.
|
||||
* @returns {Promise} A promise that settles with the value of the pending
|
||||
* promise, or rejects if the pending promise times out.
|
||||
*/
|
||||
function waitForPromise(promise, delay, message = 'Timed out waiting on promise') {
|
||||
let timeoutDefer = Promise.defer();
|
||||
let id = setTimeout(() => timeoutDefer.reject(new Error(message)), delay);
|
||||
return Promise.race([
|
||||
promise.then(value => {
|
||||
clearTimeout(id);
|
||||
return value;
|
||||
}, error => {
|
||||
clearTimeout(id);
|
||||
throw error;
|
||||
}),
|
||||
timeoutDefer.promise
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an object in a proxy that traps property gets and returns stubs. If
|
||||
* the stub is a function, the original value will be passed as the first
|
||||
|
@ -137,6 +137,5 @@ add_task(function* test_webapps_cleardata() {
|
||||
yield clearForPattern(testRecords, { inIsolatedMozBrowser: true });
|
||||
|
||||
equal(testRecords.length, 0, 'Should remove all test records');
|
||||
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for unregister');
|
||||
yield unregisterPromise;
|
||||
});
|
||||
|
245
dom/push/test/xpcshell/test_crypto.js
Normal file
245
dom/push/test/xpcshell/test_crypto.js
Normal file
@ -0,0 +1,245 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
base64UrlDecode,
|
||||
getCryptoParams,
|
||||
PushCrypto,
|
||||
} = Cu.import('resource://gre/modules/PushCrypto.jsm', {});
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_crypto_getCryptoParams() {
|
||||
let testData = [
|
||||
// These headers should parse correctly.
|
||||
{
|
||||
desc: 'aesgcm with multiple keys',
|
||||
headers: {
|
||||
encoding: 'aesgcm',
|
||||
crypto_key: 'keyid=p256dh;dh=Iy1Je2Kv11A,p256ecdsa=o2M8QfiEKuI',
|
||||
encryption: 'keyid=p256dh;salt=upk1yFkp1xI',
|
||||
},
|
||||
params: {
|
||||
dh: 'Iy1Je2Kv11A',
|
||||
salt: 'upk1yFkp1xI',
|
||||
rs: 4096,
|
||||
auth: true,
|
||||
padSize: 2,
|
||||
},
|
||||
}, {
|
||||
desc: 'aesgcm with quoted key param',
|
||||
headers: {
|
||||
encoding: 'aesgcm',
|
||||
crypto_key: 'dh="byfHbUffc-k"',
|
||||
encryption: 'salt=C11AvAsp6Gc',
|
||||
},
|
||||
params: {
|
||||
dh: 'byfHbUffc-k',
|
||||
salt: 'C11AvAsp6Gc',
|
||||
rs: 4096,
|
||||
auth: true,
|
||||
padSize: 2,
|
||||
},
|
||||
}, {
|
||||
desc: 'aesgcm with Crypto-Key and rs = 24',
|
||||
headers: {
|
||||
encoding: 'aesgcm',
|
||||
crypto_key: 'dh="ybuT4VDz-Bg"',
|
||||
encryption: 'salt=H7U7wcIoIKs; rs=24',
|
||||
},
|
||||
params: {
|
||||
dh: 'ybuT4VDz-Bg',
|
||||
salt: 'H7U7wcIoIKs',
|
||||
rs: 24,
|
||||
auth: true,
|
||||
padSize: 2,
|
||||
},
|
||||
}, {
|
||||
desc: 'aesgcm128 with Encryption-Key and rs = 2',
|
||||
headers: {
|
||||
encoding: 'aesgcm128',
|
||||
encryption_key: 'keyid=legacy; dh=LqrDQuVl9lY',
|
||||
encryption: 'keyid=legacy; salt=YngI8B7YapM; rs=2',
|
||||
},
|
||||
params: {
|
||||
dh: 'LqrDQuVl9lY',
|
||||
salt: 'YngI8B7YapM',
|
||||
rs: 2,
|
||||
auth: false,
|
||||
padSize: 1,
|
||||
},
|
||||
}, {
|
||||
desc: 'aesgcm128 with Encryption-Key',
|
||||
headers: {
|
||||
encoding: 'aesgcm128',
|
||||
encryption_key: 'keyid=v2; dh=VA6wmY1IpiE',
|
||||
encryption: 'keyid=v2; salt=khtpyXhpDKM',
|
||||
},
|
||||
params: {
|
||||
dh: 'VA6wmY1IpiE',
|
||||
salt: 'khtpyXhpDKM',
|
||||
rs: 4096,
|
||||
auth: false,
|
||||
padSize: 1,
|
||||
}
|
||||
}, {
|
||||
desc: 'aesgcm128 with Crypto-Key',
|
||||
headers: {
|
||||
encoding: 'aesgcm128',
|
||||
crypto_key: 'keyid=v2; dh=VA6wmY1IpiE',
|
||||
encryption: 'keyid=v2; salt=F0Im7RtGgNY',
|
||||
},
|
||||
params: {
|
||||
dh: 'VA6wmY1IpiE',
|
||||
salt: 'F0Im7RtGgNY',
|
||||
rs: 4096,
|
||||
auth: true,
|
||||
padSize: 1,
|
||||
},
|
||||
},
|
||||
|
||||
// These headers should be rejected.
|
||||
{
|
||||
desc: 'Invalid encoding',
|
||||
headers: {
|
||||
encoding: 'nonexistent',
|
||||
},
|
||||
params: null,
|
||||
}, {
|
||||
desc: 'Invalid record size',
|
||||
headers: {
|
||||
encoding: 'aesgcm',
|
||||
crypto_key: 'dh=pbmv1QkcEDY',
|
||||
encryption: 'dh=Esao8aTBfIk;rs=bad',
|
||||
},
|
||||
params: null,
|
||||
}, {
|
||||
desc: 'Insufficiently large record size',
|
||||
headers: {
|
||||
encoding: 'aesgcm',
|
||||
crypto_key: 'dh=fK0EXaw5IU8',
|
||||
encryption: 'salt=orbLLmlbJfM;rs=1',
|
||||
},
|
||||
params: null,
|
||||
}, {
|
||||
desc: 'aesgcm with Encryption-Key',
|
||||
headers: {
|
||||
encoding: 'aesgcm',
|
||||
encryption_key: 'dh=FplK5KkvUF0',
|
||||
encryption: 'salt=p6YHhFF3BQY',
|
||||
},
|
||||
params: null,
|
||||
}];
|
||||
|
||||
for (let test of testData) {
|
||||
let params = getCryptoParams(test.headers);
|
||||
deepEqual(params, test.params, test.desc);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_crypto_decodeMsg() {
|
||||
let privateKey = {
|
||||
"crv": "P-256",
|
||||
"d": "4h23G_KkXC9TvBSK2v0Q7ImpS2YAuRd8hQyN0rFAwBg",
|
||||
"ext": true,
|
||||
"key_ops": ["deriveBits"],
|
||||
"kty":"EC",
|
||||
"x":"sd85ZCbEG6dEkGMCmDyGBIt454Qy-Yo-1xhbaT2Jlk4",
|
||||
"y":"vr3cKpQ-Sp1kpZ9HipNjUCwSA55yy0uM8N9byE8dmLs",
|
||||
};
|
||||
let publicKey = base64UrlDecode('BLHfOWQmxBunRJBjApg8hgSLeOeEMvmKPtcYW2k9iZZOvr3cKpQ-Sp1kpZ9HipNjUCwSA55yy0uM8N9byE8dmLs');
|
||||
|
||||
let expectedSuccesses = [{
|
||||
desc: 'padSize = 2, rs = 24, pad = 0',
|
||||
result: 'Some message',
|
||||
data: 'Oo34w2F9VVnTMFfKtdx48AZWQ9Li9M6DauWJVgXU',
|
||||
senderPublicKey: 'BCHFVrflyxibGLlgztLwKelsRZp4gqX3tNfAKFaxAcBhpvYeN1yIUMrxaDKiLh4LNKPtj0BOXGdr-IQ-QP82Wjo',
|
||||
salt: 'zCU18Rw3A5aB_Xi-vfixmA',
|
||||
rs: 24,
|
||||
authSecret: 'aTDc6JebzR6eScy2oLo4RQ',
|
||||
padSize: 2,
|
||||
}, {
|
||||
desc: 'padSize = 2, rs = 8, pad = 16',
|
||||
result: 'Yet another message',
|
||||
data: 'uEC5B_tR-fuQ3delQcrzrDCp40W6ipMZjGZ78USDJ5sMj-6bAOVG3AK6JqFl9E6AoWiBYYvMZfwThVxmDnw6RHtVeLKFM5DWgl1EwkOohwH2EhiDD0gM3io-d79WKzOPZE9rDWUSv64JstImSfX_ADQfABrvbZkeaWxh53EG59QMOElFJqHue4dMURpsMXg',
|
||||
senderPublicKey: 'BEaA4gzA3i0JDuirGhiLgymS4hfFX7TNTdEhSk_HBlLpkjgCpjPL5c-GL9uBGIfa_fhGNKKFhXz1k9Kyens2ZpQ',
|
||||
salt: 'ZFhzj0S-n29g9P2p4-I7tA',
|
||||
rs: 8,
|
||||
authSecret: '6plwZnSpVUbF7APDXus3UQ',
|
||||
padSize: 2,
|
||||
}, {
|
||||
desc: 'padSize = 1, rs = 4096, pad = 2',
|
||||
result: 'aesgcm128 encrypted message',
|
||||
data: 'ljBJ44NPzJFH9EuyT5xWMU4vpZ90MdAqaq1TC1kOLRoPNHtNFXeJ0GtuSaE',
|
||||
senderPublicKey: 'BOmnfg02vNd6RZ7kXWWrCGFF92bI-rQ-bV0Pku3-KmlHwbGv4ejWqgasEdLGle5Rhmp6SKJunZw2l2HxKvrIjfI',
|
||||
salt: 'btxxUtclbmgcc30b9rT3Bg',
|
||||
rs: 4096,
|
||||
padSize: 1,
|
||||
}, {
|
||||
desc: 'padSize = 2, rs = 3, pad = 0',
|
||||
result: 'Small record size',
|
||||
data: 'oY4e5eDatDVt2fpQylxbPJM-3vrfhDasfPc8Q1PWt4tPfMVbz_sDNL_cvr0DXXkdFzS1lxsJsj550USx4MMl01ihjImXCjrw9R5xFgFrCAqJD3GwXA1vzS4T5yvGVbUp3SndMDdT1OCcEofTn7VC6xZ-zP8rzSQfDCBBxmPU7OISzr8Z4HyzFCGJeBfqiZ7yUfNlKF1x5UaZ4X6iU_TXx5KlQy_toV1dXZ2eEAMHJUcSdArvB6zRpFdEIxdcHcJyo1BIYgAYTDdAIy__IJVCPY_b2CE5W_6ohlYKB7xDyH8giNuWWXAgBozUfScLUVjPC38yJTpAUi6w6pXgXUWffende5FreQpnMFL1L4G-38wsI_-ISIOzdO8QIrXHxmtc1S5xzYu8bMqSgCinvCEwdeGFCmighRjj8t1zRWo0D14rHbQLPR_b1P5SvEeJTtS9Nm3iibM',
|
||||
senderPublicKey: 'BCg6ZIGuE2ZNm2ti6Arf4CDVD_8--aLXAGLYhpghwjl1xxVjTLLpb7zihuEOGGbyt8Qj0_fYHBP4ObxwJNl56bk',
|
||||
salt: '5LIDBXbvkBvvb7ZdD-T4PQ',
|
||||
rs: 3,
|
||||
authSecret: 'g2rWVHUCpUxgcL9Tz7vyeQ',
|
||||
padSize: 2,
|
||||
}, {
|
||||
desc: 'padSize = 1, rs = 4096, auth secret, pad = 8',
|
||||
result: 'aesgcm128 with auth secret',
|
||||
data: 'h0FmyldY8aT5EQ6CJrbfRn_IdDvytoLeHb9_q5CjtdFRfgDRknxLmOzavLaVG4oOiS0r',
|
||||
senderPublicKey: 'BCXHk7O8CE-9AOp6xx7g7c-NCaNpns1PyyHpdcmDaijLbT6IdGq0ezGatBwtFc34BBfscFxdk4Tjksa2Mx5rRCM',
|
||||
salt: 'aGBpoKklLtrLcAUCcCr7JQ',
|
||||
rs: 4096,
|
||||
authSecret: 'Sxb6u0gJIhGEogyLawjmCw',
|
||||
padSize: 1,
|
||||
}];
|
||||
for (let test of expectedSuccesses) {
|
||||
let authSecret = test.authSecret ? base64UrlDecode(test.authSecret) : null;
|
||||
let result = yield PushCrypto.decodeMsg(base64UrlDecode(test.data),
|
||||
privateKey, publicKey,
|
||||
test.senderPublicKey, test.salt,
|
||||
test.rs, authSecret, test.padSize);
|
||||
let decoder = new TextDecoder('utf-8');
|
||||
equal(decoder.decode(new Uint8Array(result)), test.result, test.desc);
|
||||
}
|
||||
|
||||
let expectedFailures = [{
|
||||
desc: 'Missing padding',
|
||||
data: 'anvsHj7oBQTPMhv7XSJEsvyMS4-8EtbC7HgFZsKaTg',
|
||||
senderPublicKey: 'BMSqfc3ohqw2DDgu3nsMESagYGWubswQPGxrW1bAbYKD18dIHQBUmD3ul_lu7MyQiT5gNdzn5JTXQvCcpf-oZE4',
|
||||
salt: 'Czx2i18rar8XWOXAVDnUuw',
|
||||
rs: 4096,
|
||||
padSize: 1,
|
||||
}, {
|
||||
desc: 'padSize > rs',
|
||||
data: 'Ct_h1g7O55e6GvuhmpjLsGnv8Rmwvxgw8iDESNKGxk_8E99iHKDzdV8wJPyHA-6b2E6kzuVa5UWiQ7s4Zms1xzJ4FKgoxvBObXkc_r_d4mnb-j245z3AcvRmcYGk5_HZ0ci26SfhAN3lCgxGzTHS4nuHBRkGwOb4Tj4SFyBRlLoTh2jyVK2jYugNjH9tTrGOBg7lP5lajLTQlxOi91-RYZSfFhsLX3LrAkXuRoN7G1CdiI7Y3_eTgbPIPabDcLCnGzmFBTvoJSaQF17huMl_UnWoCj2WovA4BwK_TvWSbdgElNnQ4CbArJ1h9OqhDOphVu5GUGr94iitXRQR-fqKPMad0ULLjKQWZOnjuIdV1RYEZ873r62Yyd31HoveJcSDb1T8l_QK2zVF8V4k0xmK9hGuC0rF5YJPYPHgl5__usknzxMBnRrfV5_MOL5uPZwUEFsu',
|
||||
senderPublicKey: 'BAcMdWLJRGx-kPpeFtwqR3GE1LWzd1TYh2rg6CEFu53O-y3DNLkNe_BtGtKRR4f7ZqpBMVS6NgfE2NwNPm3Ndls',
|
||||
salt: 'NQVTKhB0rpL7ZzKkotTGlA',
|
||||
rs: 1,
|
||||
authSecret: '6plwZnSpVUbF7APDXus3UQ',
|
||||
padSize: 2,
|
||||
}, {
|
||||
desc: 'Encrypted with padSize = 1, decrypted with padSize = 2 and auth secret',
|
||||
data: 'fwkuwTTChcLnrzsbDI78Y2EoQzfnbMI8Ax9Z27_rwX8',
|
||||
senderPublicKey: 'BCHn-I-J3dfPRLJBlNZ3xFoAqaBLZ6qdhpaz9W7Q00JW1oD-hTxyEECn6KYJNK8AxKUyIDwn6Icx_PYWJiEYjQ0',
|
||||
salt: 'c6JQl9eJ0VvwrUVCQDxY7Q',
|
||||
rs: 4096,
|
||||
authSecret: 'BhbpNTWyO5wVJmVKTV6XaA',
|
||||
padSize: 2,
|
||||
}, {
|
||||
desc: 'Truncated input',
|
||||
data: 'AlDjj6NvT5HGyrHbT8M5D6XBFSra6xrWS9B2ROaCIjwSu3RyZ1iyuv0',
|
||||
rs: 25,
|
||||
}];
|
||||
for (let test of expectedFailures) {
|
||||
let authSecret = test.authSecret ? base64UrlDecode(test.authSecret) : null;
|
||||
yield rejects(
|
||||
PushCrypto.decodeMsg(base64UrlDecode(test.data), privateKey, publicKey,
|
||||
test.senderPublicKey, test.salt, test.rs,
|
||||
authSecret, test.padSize),
|
||||
test.desc
|
||||
);
|
||||
}
|
||||
});
|
@ -120,8 +120,7 @@ add_task(function* setUp() {
|
||||
},
|
||||
});
|
||||
|
||||
yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for subscription change event on startup');
|
||||
yield subChangePromise;
|
||||
});
|
||||
|
||||
add_task(function* test_site_visited() {
|
||||
@ -133,8 +132,7 @@ add_task(function* test_site_visited() {
|
||||
yield visitURI(quotaURI, Date.now());
|
||||
PushService.observe(null, 'idle-daily', '');
|
||||
|
||||
yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for subscription change event after visit');
|
||||
yield subChangePromise;
|
||||
});
|
||||
|
||||
add_task(function* test_perm_restored() {
|
||||
@ -146,6 +144,5 @@ add_task(function* test_perm_restored() {
|
||||
Services.perms.add(permURI, 'desktop-notification',
|
||||
Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
|
||||
yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for subscription change event after permission');
|
||||
yield subChangePromise;
|
||||
});
|
||||
|
@ -118,8 +118,6 @@ add_task(function* test_notification_ack() {
|
||||
}
|
||||
});
|
||||
|
||||
yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for notifications');
|
||||
yield waitForPromise(ackPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for multiple acknowledgements');
|
||||
yield notifyPromise;
|
||||
yield ackPromise;
|
||||
});
|
||||
|
@ -114,8 +114,7 @@ add_task(function* test_notification_ack_data_setup() {
|
||||
});
|
||||
}
|
||||
});
|
||||
yield waitForPromise(setupDonePromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for notifications');
|
||||
yield setupDonePromise;
|
||||
});
|
||||
|
||||
add_task(function* test_notification_ack_data() {
|
||||
@ -127,6 +126,7 @@ add_task(function* test_notification_ack_data() {
|
||||
headers: {
|
||||
encryption_key: 'keyid="notification1"; dh="BO_tgGm-yvYAGLeRe16AvhzaUcpYRiqgsGOlXpt0DRWDRGGdzVLGlEVJMygqAUECarLnxCiAOHTP_znkedrlWoU"',
|
||||
encryption: 'keyid="notification1";salt="uAZaiXpOSfOLJxtOCZ09dA"',
|
||||
encoding: 'aesgcm128',
|
||||
},
|
||||
data: 'NwrrOWPxLE8Sv5Rr0Kep7n0-r_j3rsYrUw_CXPo',
|
||||
version: 'v1',
|
||||
@ -143,6 +143,7 @@ add_task(function* test_notification_ack_data() {
|
||||
headers: {
|
||||
encryption_key: 'keyid="notification2"; dh="BKVdQcgfncpNyNWsGrbecX0zq3eHIlHu5XbCGmVcxPnRSbhjrA6GyBIeGdqsUL69j5Z2CvbZd-9z1UBH0akUnGQ"',
|
||||
encryption: 'keyid="notification2";salt="vFn3t3M_k42zHBdpch3VRw"',
|
||||
encoding: 'aesgcm128',
|
||||
},
|
||||
data: 'Zt9dEdqgHlyAL_l83385aEtb98ZBilz5tgnGgmwEsl5AOCNgesUUJ4p9qUU',
|
||||
},
|
||||
@ -158,6 +159,7 @@ add_task(function* test_notification_ack_data() {
|
||||
headers: {
|
||||
encryption_key: 'keyid="notification3";dh="BD3xV_ACT8r6hdIYES3BJj1qhz9wyv7MBrG9vM2UCnjPzwE_YFVpkD-SGqE-BR2--0M-Yf31wctwNsO1qjBUeMg"',
|
||||
encryption: 'keyid="notification3"; salt="DFq188piWU7osPBgqn4Nlg"; rs=24',
|
||||
encoding: 'aesgcm128',
|
||||
},
|
||||
data: 'LKru3ZzxBZuAxYtsaCfaj_fehkrIvqbVd1iSwnwAUgnL-cTeDD-83blxHXTq7r0z9ydTdMtC3UjAcWi8LMnfY-BFzi0qJAjGYIikDA',
|
||||
},
|
||||
@ -175,6 +177,7 @@ add_task(function* test_notification_ack_data() {
|
||||
headers: {
|
||||
crypto_key: 'keyid=v4;dh="BHqG01j7rOfp12BEDzxWXxlCaU4cdOx2DZAwCt3QuzEsnXN9lCna9QmZCkVpXsx7sAlaEmtl_VfF1lHlFS7XWcA"',
|
||||
encryption: 'keyid="v4";salt="X5-iy5rzhm4naNmMHdSYJw"',
|
||||
encoding: 'aesgcm128',
|
||||
},
|
||||
data: '7YlxyNlZsNX4UNknHxzTqFrcrzz58W95uXBa0iY',
|
||||
},
|
||||
@ -183,6 +186,23 @@ add_task(function* test_notification_ack_data() {
|
||||
data: 'Some message'
|
||||
}
|
||||
},
|
||||
// A message encoded with `aesgcm` (2 bytes of padding).
|
||||
{
|
||||
channelID: 'subscription1',
|
||||
version: 'v5',
|
||||
send: {
|
||||
headers: {
|
||||
crypto_key: 'dh="BMh_vsnqu79ZZkMTYkxl4gWDLdPSGE72Lr4w2hksSFW398xCMJszjzdblAWXyhSwakRNEU_GopAm4UGzyMVR83w"',
|
||||
encryption: 'salt="C14Wb7rQTlXzrgcPHtaUzw"',
|
||||
encoding: 'aesgcm',
|
||||
},
|
||||
data: 'pus4kUaBWzraH34M-d_oN8e0LPpF_X6acx695AMXovDe',
|
||||
},
|
||||
receive: {
|
||||
scope: 'https://example.com/page/1',
|
||||
data: 'Another message'
|
||||
}
|
||||
},
|
||||
// A message with 17 bytes of padding and rs of 24
|
||||
{
|
||||
channelID: 'subscription2',
|
||||
@ -191,6 +211,7 @@ add_task(function* test_notification_ack_data() {
|
||||
headers: {
|
||||
crypto_key: 'keyid="v5"; dh="BJhyKIH5P30YUKn1bolj_LMnael1-KZT_aGXgD2CRspBfv9gcUhVAmpxToZrw7QQEKl9K83b3zcqNY6G_dFhEsI"',
|
||||
encryption: 'keyid=v5;salt="bLmqCy550eK1Ao41tD7orA";rs=24',
|
||||
encoding: 'aesgcm128',
|
||||
},
|
||||
data: 'SQDlDg1ftLkM_ruZlmyB2bk9L78HYtkcbA-y4-uAxwL-G4KtOA-J-A_rJ007Vi6NUkQe9K4kSZeIBrIUpmGv',
|
||||
},
|
||||
@ -207,6 +228,7 @@ add_task(function* test_notification_ack_data() {
|
||||
headers: {
|
||||
crypto_key: 'dh="BEgnDmVw9Gcn1fWA5t53Jtpsgfewk_pzsjSc_PBPpPmROWGQA2v8ESrSsQgosNXx0o-uMMhi9tDAUeks3380kd8"',
|
||||
encryption: 'salt=T9DM8bNxuMHRVTn4LzkJDQ',
|
||||
encoding: 'aesgcm128',
|
||||
},
|
||||
data: '7KUCi0dBBJbWmsYTqEqhFrgTv4ZOo_BmQRQ_2kY',
|
||||
},
|
||||
@ -244,10 +266,7 @@ add_task(function* test_notification_ack_data() {
|
||||
return Promise.all([messageReceived, ackReceived]);
|
||||
};
|
||||
|
||||
yield waitForPromise(
|
||||
allTestData.reduce((p, testData) => {
|
||||
return p.then(_ => sendAndReceive(testData));
|
||||
}, Promise.resolve()),
|
||||
DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for message exchange to complete');
|
||||
yield allTestData.reduce((p, testData) => {
|
||||
return p.then(_ => sendAndReceive(testData));
|
||||
}, Promise.resolve());
|
||||
});
|
||||
|
@ -73,10 +73,8 @@ add_task(function* test_notification_duplicate() {
|
||||
}
|
||||
});
|
||||
|
||||
yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for notifications');
|
||||
yield waitForPromise(ackPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for stale acknowledgement');
|
||||
yield notifyPromise;
|
||||
yield ackPromise;
|
||||
|
||||
let staleRecord = yield db.getByKeyID(
|
||||
'8d2d9400-3597-4c5a-8a38-c546b0043bcc');
|
||||
|
@ -87,18 +87,13 @@ add_task(function* test_notification_error() {
|
||||
}
|
||||
});
|
||||
|
||||
yield waitForPromise(
|
||||
notifyPromise,
|
||||
DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for notifications'
|
||||
);
|
||||
yield notifyPromise;
|
||||
ok(scopes.includes('https://example.com/a'),
|
||||
'Missing scope for notification A');
|
||||
ok(scopes.includes('https://example.com/c'),
|
||||
'Missing scope for notification C');
|
||||
|
||||
yield waitForPromise(ackPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for acknowledgements');
|
||||
yield ackPromise;
|
||||
|
||||
let aRecord = yield db.getByIdentifiers({scope: 'https://example.com/a',
|
||||
originAttributes: originAttributes });
|
||||
|
@ -6,6 +6,7 @@
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const {PushDB, PushService, PushServiceHttp2} = serviceExports;
|
||||
const {base64UrlDecode} = Cu.import('resource://gre/modules/PushCrypto.jsm', {});
|
||||
|
||||
var prefs;
|
||||
var tlsProfile;
|
||||
@ -56,6 +57,8 @@ add_task(function* test_pushNotifications() {
|
||||
// length 16.
|
||||
// /pushNotifications/subscription3 will send a message with rs equal 24 and
|
||||
// padding length 16.
|
||||
// /pushNotifications/subscription4 will send a message with no rs and padding
|
||||
// length 256.
|
||||
|
||||
let db = PushServiceHttp2.newPushDB();
|
||||
do_register_cleanup(() => {
|
||||
@ -121,6 +124,26 @@ add_task(function* test_pushNotifications() {
|
||||
{ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
|
||||
quota: Infinity,
|
||||
systemRecord: true,
|
||||
}, {
|
||||
subscriptionUri: serverURL + '/pushNotifications/subscription4',
|
||||
pushEndpoint: serverURL + '/pushEndpoint4',
|
||||
pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint4',
|
||||
scope: 'https://example.com/page/4',
|
||||
p256dhPublicKey: base64UrlDecode('BEcvDzkWCrUtjU_wygL98sbQCQrW1lY9irtgGnlCc4B0JJXLCHB9MTM73qD6GZYfL0YOvKo8XLOflh-J4dMGklU'),
|
||||
p256dhPrivateKey: {
|
||||
crv: 'P-256',
|
||||
d: 'fWi7tZaX0Pk6WnLrjQ3kiRq_g5XStL5pdH4pllNCqXw',
|
||||
ext: true,
|
||||
key_ops: ["deriveBits"],
|
||||
kty: 'EC',
|
||||
x: 'Ry8PORYKtS2NT_DKAv3yxtAJCtbWVj2Ku2AaeUJzgHQ',
|
||||
y: 'JJXLCHB9MTM73qD6GZYfL0YOvKo8XLOflh-J4dMGklU'
|
||||
},
|
||||
authenticationSecret: base64UrlDecode('cwDVC1iwAn8E37mkR3tMSg'),
|
||||
originAttributes: ChromeUtils.originAttributesToSuffix(
|
||||
{ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
|
||||
quota: Infinity,
|
||||
systemRecord: true,
|
||||
}];
|
||||
|
||||
for (let record of records) {
|
||||
@ -148,7 +171,14 @@ add_task(function* test_pushNotifications() {
|
||||
equal(message.text(), "Some message", "decoded message is incorrect");
|
||||
return true;
|
||||
}
|
||||
})
|
||||
}),
|
||||
promiseObserverNotification(PushServiceComponent.pushTopic, function(subject, data) {
|
||||
var message = subject.QueryInterface(Ci.nsIPushMessage);
|
||||
if (message && (data == "https://example.com/page/4")){
|
||||
equal(message.text(), "Yet another message", "decoded message is incorrect");
|
||||
return true;
|
||||
}
|
||||
}),
|
||||
]);
|
||||
|
||||
PushService.init({
|
||||
@ -156,8 +186,7 @@ add_task(function* test_pushNotifications() {
|
||||
db
|
||||
});
|
||||
|
||||
yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for notifications');
|
||||
yield notifyPromise;
|
||||
});
|
||||
|
||||
add_task(function* test_complete() {
|
||||
|
@ -108,8 +108,7 @@ add_task(function* test_notification_incomplete() {
|
||||
}
|
||||
});
|
||||
|
||||
yield waitForPromise(notificationPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for incomplete notifications');
|
||||
yield notificationPromise;
|
||||
|
||||
let storeRecords = yield db.getAllKeyIDs();
|
||||
storeRecords.sort(({pushEndpoint: a}, {pushEndpoint: b}) =>
|
||||
|
@ -57,15 +57,10 @@ add_task(function* test_notification_version_string() {
|
||||
}
|
||||
});
|
||||
|
||||
let {subject: notification, data: scope} = yield waitForPromise(
|
||||
notifyPromise,
|
||||
DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for string notification'
|
||||
);
|
||||
let {subject: notification, data: scope} = yield notifyPromise;
|
||||
equal(notification, null, 'Unexpected data for Simple Push message');
|
||||
|
||||
yield waitForPromise(ackPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for string acknowledgement');
|
||||
yield ackPromise;
|
||||
|
||||
let storeRecord = yield db.getByKeyID(
|
||||
'6ff97d56-d0c0-43bc-8f5b-61b855e1d93b');
|
||||
|
@ -124,8 +124,7 @@ add_task(function* setUp() {
|
||||
});
|
||||
}
|
||||
});
|
||||
yield waitForPromise(handshakePromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for handshake');
|
||||
yield handshakePromise;
|
||||
});
|
||||
|
||||
add_task(function* test_permissions_allow_added() {
|
||||
@ -135,8 +134,7 @@ add_task(function* test_permissions_allow_added() {
|
||||
makePushPermission('https://example.info', 'ALLOW_ACTION'),
|
||||
'added'
|
||||
);
|
||||
let notifiedScopes = yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for notifications after adding allow');
|
||||
let notifiedScopes = yield subChangePromise;
|
||||
|
||||
deepEqual(notifiedScopes, [
|
||||
'https://example.info/page/2',
|
||||
@ -159,8 +157,7 @@ add_task(function* test_permissions_allow_deleted() {
|
||||
'deleted'
|
||||
);
|
||||
|
||||
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for unregister after deleting allow');
|
||||
yield unregisterPromise;
|
||||
|
||||
let record = yield db.getByKeyID('active-allow');
|
||||
ok(record.isExpired(),
|
||||
@ -179,8 +176,7 @@ add_task(function* test_permissions_deny_added() {
|
||||
makePushPermission('https://example.net', 'DENY_ACTION'),
|
||||
'added'
|
||||
);
|
||||
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for notifications after adding deny');
|
||||
yield unregisterPromise;
|
||||
|
||||
let isExpired = yield allExpired(
|
||||
'active-deny-added-1',
|
||||
@ -210,8 +206,7 @@ add_task(function* test_permissions_allow_changed() {
|
||||
'changed'
|
||||
);
|
||||
|
||||
let notifiedScopes = yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for notifications after changing to allow');
|
||||
let notifiedScopes = yield subChangePromise;
|
||||
|
||||
deepEqual(notifiedScopes, [
|
||||
'https://example.net/eggs',
|
||||
@ -237,8 +232,7 @@ add_task(function* test_permissions_deny_changed() {
|
||||
'changed'
|
||||
);
|
||||
|
||||
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for unregister after changing to deny');
|
||||
yield unregisterPromise;
|
||||
|
||||
let record = yield db.getByKeyID('active-deny-changed');
|
||||
ok(record.isExpired(),
|
||||
@ -259,8 +253,7 @@ add_task(function* test_permissions_clear() {
|
||||
|
||||
yield PushService._onPermissionChange(null, 'cleared');
|
||||
|
||||
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for unregister requests after clearing permissions');
|
||||
yield unregisterPromise;
|
||||
|
||||
records = yield db.getAllKeyIDs();
|
||||
deepEqual(records.map(record => record.keyID).sort(), [
|
||||
|
@ -132,11 +132,9 @@ add_task(function* test_expiration_origin_threshold() {
|
||||
},
|
||||
});
|
||||
|
||||
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for unregister request');
|
||||
yield unregisterPromise;
|
||||
|
||||
yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for notifications');
|
||||
yield notifyPromise;
|
||||
|
||||
let expiredRecord = yield db.getByKeyID('eb33fc90-c883-4267-b5cb-613969e8e349');
|
||||
strictEqual(expiredRecord.quota, 0, 'Expired record not updated');
|
||||
|
@ -98,10 +98,8 @@ add_task(function* test_expiration_history_observer() {
|
||||
}
|
||||
});
|
||||
|
||||
yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for subscription change event on startup');
|
||||
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for unregister request');
|
||||
yield subChangePromise;
|
||||
yield unregisterPromise;
|
||||
|
||||
let expiredRecord = yield db.getByKeyID('379c0668-8323-44d2-a315-4ee83f1a9ee9');
|
||||
strictEqual(expiredRecord.quota, 0, 'Expired record not updated');
|
||||
@ -134,8 +132,7 @@ add_task(function* test_expiration_history_observer() {
|
||||
Services.obs.notifyObservers(null, 'idle-daily', '');
|
||||
|
||||
// And we should receive notifications for both scopes.
|
||||
yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for subscription change events');
|
||||
yield subChangePromise;
|
||||
deepEqual(notifiedScopes.sort(), [
|
||||
'https://example.com/auctions',
|
||||
'https://example.com/deals'
|
||||
|
@ -103,11 +103,9 @@ add_task(function* test_expiration_origin_threshold() {
|
||||
},
|
||||
});
|
||||
|
||||
yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for notifications');
|
||||
yield notifyPromise;
|
||||
|
||||
yield waitForPromise(updateQuotaPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for quota to be updated');
|
||||
yield updateQuotaPromise;
|
||||
|
||||
let expiredRecord = yield db.getByKeyID('f56645a9-1f32-4655-92ad-ddc37f6d54fb');
|
||||
notStrictEqual(expiredRecord.quota, 0, 'Expired record not updated');
|
||||
|
@ -68,7 +68,7 @@ add_task(function* test_reconnect_retry() {
|
||||
originAttributes: ChromeUtils.originAttributesToSuffix(
|
||||
{ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
|
||||
});
|
||||
notEqual(registration.endpoint, retryEndpoint, 'Wrong endpoint for new request')
|
||||
notEqual(registration.endpoint, retryEndpoint, 'Wrong endpoint for new request');
|
||||
|
||||
equal(registers, 3, 'Wrong registration count');
|
||||
});
|
||||
|
@ -43,15 +43,11 @@ add_task(function* test_register_case() {
|
||||
}
|
||||
});
|
||||
|
||||
let newRecord = yield waitForPromise(
|
||||
PushService.register({
|
||||
scope: 'https://example.net/case',
|
||||
originAttributes: ChromeUtils.originAttributesToSuffix(
|
||||
{ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
|
||||
}),
|
||||
DEFAULT_TIMEOUT,
|
||||
'Mixed-case register response timed out'
|
||||
);
|
||||
let newRecord = yield PushService.register({
|
||||
scope: 'https://example.net/case',
|
||||
originAttributes: ChromeUtils.originAttributesToSuffix(
|
||||
{ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
|
||||
});
|
||||
equal(newRecord.endpoint, 'https://example.com/update/case',
|
||||
'Wrong push endpoint in registration record');
|
||||
|
||||
|
@ -80,12 +80,10 @@ add_task(function* test_register_flush() {
|
||||
equal(newRecord.endpoint, 'https://example.org/update/2',
|
||||
'Wrong push endpoint in record');
|
||||
|
||||
let {data: scope} = yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for notification');
|
||||
let {data: scope} = yield notifyPromise;
|
||||
equal(scope, 'https://example.com/page/1', 'Wrong notification scope');
|
||||
|
||||
yield waitForPromise(ackPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for acknowledgements');
|
||||
yield ackPromise;
|
||||
|
||||
let prevRecord = yield db.getByKeyID(
|
||||
'9bcc7efb-86c7-4457-93ea-e24e6eb59b74');
|
||||
|
@ -54,7 +54,6 @@ add_task(function* test_register_invalid_json() {
|
||||
'Expected error for invalid JSON response'
|
||||
);
|
||||
|
||||
yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
|
||||
'Reconnect after invalid JSON response timed out');
|
||||
yield helloPromise;
|
||||
equal(registers, 1, 'Wrong register count');
|
||||
});
|
||||
|
@ -58,7 +58,6 @@ add_task(function* test_register_no_id() {
|
||||
'Expected error for incomplete register response'
|
||||
);
|
||||
|
||||
yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
|
||||
'Reconnect after incomplete register response timed out');
|
||||
yield helloPromise;
|
||||
equal(registers, 1, 'Wrong register count');
|
||||
});
|
||||
|
@ -53,11 +53,10 @@ add_task(function* test_register_request_queue() {
|
||||
{ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
|
||||
});
|
||||
|
||||
yield waitForPromise(Promise.all([
|
||||
yield Promise.all([
|
||||
rejects(firstRegister, 'Should time out the first request'),
|
||||
rejects(secondRegister, 'Should time out the second request')
|
||||
]), DEFAULT_TIMEOUT, 'Queued requests did not time out');
|
||||
]);
|
||||
|
||||
yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for reconnect');
|
||||
yield helloPromise;
|
||||
});
|
||||
|
@ -81,8 +81,7 @@ add_task(function* test_register_rollback() {
|
||||
);
|
||||
|
||||
// Should send an out-of-band unregister request.
|
||||
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
|
||||
'Unregister request timed out');
|
||||
yield unregisterPromise;
|
||||
equal(handshakes, 1, 'Wrong handshake count');
|
||||
equal(registers, 1, 'Wrong register count');
|
||||
});
|
||||
|
@ -83,10 +83,6 @@ add_task(function* test_register_timeout() {
|
||||
let record = yield db.getByKeyID(channelID);
|
||||
ok(!record, 'Should not store records for timed-out responses');
|
||||
|
||||
yield waitForPromise(
|
||||
timeoutPromise,
|
||||
DEFAULT_TIMEOUT,
|
||||
'Reconnect timed out'
|
||||
);
|
||||
yield timeoutPromise;
|
||||
equal(registers, 1, 'Should not handle timed-out register requests');
|
||||
});
|
||||
|
@ -64,7 +64,6 @@ add_task(function* test_register_wrong_id() {
|
||||
'Expected error for mismatched register reply'
|
||||
);
|
||||
|
||||
yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
|
||||
'Reconnect after mismatched register reply timed out');
|
||||
yield helloPromise;
|
||||
equal(registers, 1, 'Wrong register count');
|
||||
});
|
||||
|
@ -58,7 +58,6 @@ add_task(function* test_register_wrong_type() {
|
||||
'Expected error for non-string channel ID'
|
||||
);
|
||||
|
||||
yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
|
||||
'Reconnect after sending non-string channel ID timed out');
|
||||
yield helloPromise;
|
||||
equal(registers, 1, 'Wrong register count');
|
||||
});
|
||||
|
@ -64,11 +64,7 @@ add_task(function* test_registration_success() {
|
||||
}
|
||||
});
|
||||
|
||||
yield waitForPromise(
|
||||
handshakePromise,
|
||||
DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for handshake'
|
||||
);
|
||||
yield handshakePromise;
|
||||
|
||||
let registration = yield PushService.registration({
|
||||
scope: 'https://example.net/a',
|
||||
|
@ -58,11 +58,7 @@ add_task(function* test_ws_retry() {
|
||||
},
|
||||
});
|
||||
|
||||
yield waitForPromise(
|
||||
handshakePromise,
|
||||
45000,
|
||||
'Timed out waiting for successful handshake'
|
||||
);
|
||||
yield handshakePromise;
|
||||
[25, 50, 100, 200, 400, 800, 1600, 3200, 6400, 10000].forEach(function(minDelay, index) {
|
||||
ok(alarmDelays[index] >= minDelay, `Should wait at least ${
|
||||
minDelay}ms before attempt ${index + 1}`);
|
||||
|
@ -65,6 +65,5 @@ add_task(function* test_unregister_error() {
|
||||
ok(!result, 'Deleted push record exists');
|
||||
|
||||
// Make sure we send a request to the server.
|
||||
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for unregister');
|
||||
yield unregisterPromise;
|
||||
});
|
||||
|
@ -82,6 +82,5 @@ add_task(function* test_unregister_invalid_json() {
|
||||
ok(!record,
|
||||
'Failed to delete unregistered record after receiving invalid JSON');
|
||||
|
||||
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for unregister');
|
||||
yield unregisterPromise;
|
||||
});
|
||||
|
@ -60,6 +60,5 @@ add_task(function* test_unregister_success() {
|
||||
let record = yield db.getByKeyID(channelID);
|
||||
ok(!record, 'Unregister did not remove record');
|
||||
|
||||
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for unregister');
|
||||
yield unregisterPromise;
|
||||
});
|
||||
|
@ -67,8 +67,7 @@ add_task(function* test1() {
|
||||
db
|
||||
});
|
||||
|
||||
yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
|
||||
'Timed out waiting for notifications');
|
||||
yield notifyPromise;
|
||||
|
||||
let aRecord = yield db.getByKeyID(serverURL + '/subscriptionNoKey');
|
||||
ok(aRecord, 'The record should still be there');
|
||||
|
@ -5,6 +5,7 @@ tail =
|
||||
skip-if = toolkit == 'android'
|
||||
|
||||
[test_clear_origin_data.js]
|
||||
[test_crypto.js]
|
||||
[test_drop_expired.js]
|
||||
[test_handler_service.js]
|
||||
support-files = PushServiceHandler.js PushServiceHandler.manifest
|
||||
|
@ -105,6 +105,14 @@ DoCORSChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo,
|
||||
nsCOMPtr<nsIStreamListener>& aInAndOutListener)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(aInAndOutListener, "can not perform CORS checks without a listener");
|
||||
|
||||
// No need to set up CORS if TriggeringPrincipal is the SystemPrincipal.
|
||||
// For example, allow user stylesheets to load XBL from external files
|
||||
// without requiring CORS.
|
||||
if (nsContentUtils::IsSystemPrincipal(aLoadInfo->TriggeringPrincipal())) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsIPrincipal* loadingPrincipal = aLoadInfo->LoadingPrincipal();
|
||||
RefPtr<nsCORSListenerProxy> corsListener =
|
||||
new nsCORSListenerProxy(aInAndOutListener,
|
||||
@ -476,10 +484,11 @@ nsContentSecurityManager::CheckChannel(nsIChannel* aChannel)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Allow the load if TriggeringPrincipal is the SystemPrincipal which
|
||||
// is e.g. necessary to allow user user stylesheets to load XBL from
|
||||
// external files.
|
||||
if (nsContentUtils::IsSystemPrincipal(loadInfo->TriggeringPrincipal())) {
|
||||
// Allow subresource loads if TriggeringPrincipal is the SystemPrincipal.
|
||||
// For example, allow user stylesheets to load XBL from external files.
|
||||
if (nsContentUtils::IsSystemPrincipal(loadInfo->TriggeringPrincipal()) &&
|
||||
loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT &&
|
||||
loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_SUBDOCUMENT) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,8 @@ interface Node : EventTarget {
|
||||
[Throws]
|
||||
Node cloneNode(optional boolean deep = false);
|
||||
[Pure]
|
||||
boolean isSameNode(Node? node);
|
||||
[Pure]
|
||||
boolean isEqualNode(Node? node);
|
||||
|
||||
const unsigned short DOCUMENT_POSITION_DISCONNECTED = 0x01;
|
||||
|
@ -341,11 +341,7 @@ class ChildImpl final : public BackgroundChildImpl
|
||||
// create the background thread after application shutdown has started.
|
||||
static bool sShutdownHasStarted;
|
||||
|
||||
#ifdef RELEASE_BUILD
|
||||
#ifdef DEBUG
|
||||
nsIThread* mBoundThread;
|
||||
#endif
|
||||
#else
|
||||
#if defined(DEBUG) || !defined(RELEASE_BUILD)
|
||||
nsIThread* mBoundThread;
|
||||
#endif
|
||||
|
||||
@ -382,7 +378,9 @@ public:
|
||||
}
|
||||
|
||||
ChildImpl()
|
||||
#if defined(DEBUG) || !defined(RELEASE_BUILD)
|
||||
: mBoundThread(nullptr)
|
||||
#endif
|
||||
#ifdef DEBUG
|
||||
, mActorDestroyed(false)
|
||||
#endif
|
||||
|
@ -25,20 +25,91 @@ var topFrame = null;
|
||||
var debuggeeValues = {};
|
||||
var nextDebuggeeValueIndex = 1;
|
||||
var lastExc = null;
|
||||
var todo = [];
|
||||
var activeTask;
|
||||
var options = { 'pretty': true,
|
||||
'emacs': (os.getenv('EMACS') == 't') };
|
||||
var rerun = true;
|
||||
|
||||
// Cleanup functions to run when we next re-enter the repl.
|
||||
var replCleanups = [];
|
||||
|
||||
// Redirect debugger printing functions to go to the original output
|
||||
// destination, unaffected by any redirects done by the debugged script.
|
||||
var initialOut = os.file.redirect();
|
||||
var initialErr = os.file.redirectErr();
|
||||
|
||||
function wrap(global, name) {
|
||||
var orig = global[name];
|
||||
global[name] = function(...args) {
|
||||
|
||||
var oldOut = os.file.redirect(initialOut);
|
||||
var oldErr = os.file.redirectErr(initialErr);
|
||||
try {
|
||||
return orig.apply(global, args);
|
||||
} finally {
|
||||
os.file.redirect(oldOut);
|
||||
os.file.redirectErr(oldErr);
|
||||
}
|
||||
};
|
||||
}
|
||||
wrap(this, 'print');
|
||||
wrap(this, 'printErr');
|
||||
wrap(this, 'putstr');
|
||||
|
||||
// Convert a debuggee value v to a string.
|
||||
function dvToString(v) {
|
||||
return (typeof v !== 'object' || v === null) ? uneval(v) : "[object " + v.class + "]";
|
||||
}
|
||||
|
||||
function showDebuggeeValue(dv) {
|
||||
function summaryObject(dv) {
|
||||
var obj = {};
|
||||
for (var name of dv.getOwnPropertyNames()) {
|
||||
var v = dv.getOwnPropertyDescriptor(name).value;
|
||||
if (v instanceof Debugger.Object) {
|
||||
v = "(...)";
|
||||
}
|
||||
obj[name] = v;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function debuggeeValueToString(dv, style) {
|
||||
var dvrepr = dvToString(dv);
|
||||
if (!style.pretty || (typeof dv !== 'object'))
|
||||
return [dvrepr, undefined];
|
||||
|
||||
if (dv.class == "Error") {
|
||||
let errval = debuggeeGlobalWrapper.executeInGlobalWithBindings("$" + i + ".toString()", debuggeeValues);
|
||||
return [dvrepr, errval.return];
|
||||
}
|
||||
|
||||
if (style.brief)
|
||||
return [dvrepr, JSON.stringify(summaryObject(dv), null, 4)];
|
||||
|
||||
let str = debuggeeGlobalWrapper.executeInGlobalWithBindings("JSON.stringify(v, null, 4)", {v: dv});
|
||||
if ('throw' in str) {
|
||||
if (style.noerror)
|
||||
return [dvrepr, undefined];
|
||||
|
||||
let substyle = {};
|
||||
Object.assign(substyle, style);
|
||||
substyle.noerror = true;
|
||||
return [dvrepr, debuggeeValueToString(str.throw, substyle)];
|
||||
}
|
||||
|
||||
return [dvrepr, str.return];
|
||||
}
|
||||
|
||||
// Problem! Used to do [object Object] followed by details. Now just details?
|
||||
|
||||
function showDebuggeeValue(dv, style={pretty: options.pretty}) {
|
||||
var i = nextDebuggeeValueIndex++;
|
||||
debuggeeValues["$" + i] = dv;
|
||||
print("$" + i + " = " + dvrepr);
|
||||
let [brief, full] = debuggeeValueToString(dv, style);
|
||||
print("$" + i + " = " + brief);
|
||||
if (full !== undefined)
|
||||
print(full);
|
||||
}
|
||||
|
||||
Object.defineProperty(Debugger.Frame.prototype, "num", {
|
||||
@ -70,6 +141,16 @@ Debugger.Frame.prototype.positionDescription = function positionDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
Debugger.Frame.prototype.location = function () {
|
||||
if (this.script) {
|
||||
var { lineNumber, columnNumber, isEntryPoint } = this.script.getOffsetLocation(this.offset);
|
||||
if (this.script.url)
|
||||
return this.script.url + ":" + lineNumber;
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Debugger.Frame.prototype.fullDescription = function fullDescription() {
|
||||
var fr = this.frameDescription();
|
||||
var pos = this.positionDescription();
|
||||
@ -121,6 +202,50 @@ function saveExcursion(fn) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(str) {
|
||||
return str.split(" ");
|
||||
}
|
||||
|
||||
function describedRv(r, desc) {
|
||||
desc = "[" + desc + "] ";
|
||||
if (r === undefined) {
|
||||
print(desc + "Returning undefined");
|
||||
} else if (r === null) {
|
||||
print(desc + "Returning null");
|
||||
} else if (r.length === undefined) {
|
||||
print(desc + "Returning object " + JSON.stringify(r));
|
||||
} else {
|
||||
print(desc + "Returning length-" + r.length + " list");
|
||||
if (r.length > 0) {
|
||||
print(" " + r[0]);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Rerun the program (reloading it from the file)
|
||||
function runCommand(args) {
|
||||
print("Restarting program");
|
||||
if (args)
|
||||
activeTask.scriptArgs = parseArgs(args);
|
||||
rerun = true;
|
||||
for (var f = topFrame; f; f = f.older) {
|
||||
print(f.script.url + ":" + f.script.getOffsetLine(f.offset) +" was " + f.onPop);
|
||||
if (f.older) {
|
||||
f.onPop = function() {
|
||||
print("Resumifying " + this.script.url + ":" + this.script.getOffsetLine(this.offset));
|
||||
return null;
|
||||
};
|
||||
} else {
|
||||
f.onPop = function() {
|
||||
return { 'return': 0 };
|
||||
};
|
||||
}
|
||||
}
|
||||
//return describedRv([{ 'return': 0 }], "runCommand");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Evaluate an expression in the Debugger global
|
||||
function evalCommand(expr) {
|
||||
eval(expr);
|
||||
@ -138,12 +263,47 @@ function backtraceCommand() {
|
||||
showFrame(f, i);
|
||||
}
|
||||
|
||||
function printCommand(rest) {
|
||||
function setCommand(rest) {
|
||||
var space = rest.indexOf(' ');
|
||||
if (space == -1) {
|
||||
print("Invalid set <option> <value> command");
|
||||
} else {
|
||||
var name = rest.substr(0, space);
|
||||
var value = rest.substr(space + 1);
|
||||
|
||||
if (name == 'args') {
|
||||
activeTask.scriptArgs = parseArgs(value);
|
||||
} else {
|
||||
var yes = ["1", "yes", "true", "on"];
|
||||
var no = ["0", "no", "false", "off"];
|
||||
|
||||
if (yes.indexOf(value) !== -1)
|
||||
options[name] = true;
|
||||
else if (no.indexOf(value) !== -1)
|
||||
options[name] = false;
|
||||
else
|
||||
options[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function split_print_options(s, style) {
|
||||
var m = /^\/(\w+)/.exec(s);
|
||||
if (!m)
|
||||
return [ s, style ];
|
||||
if (m[1].indexOf("p") != -1)
|
||||
style.pretty = true;
|
||||
if (m[1].indexOf("b") != -1)
|
||||
style.brief = true;
|
||||
return [ s.substr(m[0].length).trimLeft(), style ];
|
||||
}
|
||||
|
||||
function doPrint(expr, style) {
|
||||
// This is the real deal.
|
||||
var cv = saveExcursion(
|
||||
() => focusedFrame == null
|
||||
? debuggeeGlobalWrapper.executeInGlobalWithBindings(rest, debuggeeValues)
|
||||
: focusedFrame.evalWithBindings(rest, debuggeeValues));
|
||||
? debuggeeGlobalWrapper.executeInGlobalWithBindings(expr, debuggeeValues)
|
||||
: focusedFrame.evalWithBindings(expr, debuggeeValues));
|
||||
if (cv === null) {
|
||||
if (!dbg.enabled)
|
||||
return [cv];
|
||||
@ -151,26 +311,39 @@ function printCommand(rest) {
|
||||
} else if ('return' in cv) {
|
||||
if (!dbg.enabled)
|
||||
return [undefined];
|
||||
showDebuggeeValue(cv.return);
|
||||
showDebuggeeValue(cv.return, style);
|
||||
} else {
|
||||
if (!dbg.enabled)
|
||||
return [cv];
|
||||
print("Exception caught. (To rethrow it, type 'throw'.)");
|
||||
lastExc = cv.throw;
|
||||
showDebuggeeValue(lastExc);
|
||||
showDebuggeeValue(lastExc, style);
|
||||
}
|
||||
}
|
||||
|
||||
function printCommand(rest) {
|
||||
var [expr, style] = split_print_options(rest, {pretty: options.pretty});
|
||||
return doPrint(expr, style);
|
||||
}
|
||||
|
||||
function keysCommand(rest) { return doPrint("Object.keys(" + rest + ")"); }
|
||||
|
||||
function detachCommand() {
|
||||
dbg.enabled = false;
|
||||
return [undefined];
|
||||
}
|
||||
|
||||
function continueCommand() {
|
||||
function continueCommand(rest) {
|
||||
if (focusedFrame === null) {
|
||||
print("No stack.");
|
||||
return;
|
||||
}
|
||||
|
||||
var match = rest.match(/^(\d+)$/);
|
||||
if (match) {
|
||||
return doStepOrNext({upto:true, stopLine:match[1]});
|
||||
}
|
||||
|
||||
return [undefined];
|
||||
}
|
||||
|
||||
@ -220,12 +393,15 @@ function frameCommand(rest) {
|
||||
f = f.older;
|
||||
}
|
||||
focusedFrame = f;
|
||||
updateLocation(focusedFrame);
|
||||
showFrame(f, n);
|
||||
} else if (rest !== '') {
|
||||
if (topFrame === null)
|
||||
} else if (rest === '') {
|
||||
if (topFrame === null) {
|
||||
print("No stack.");
|
||||
else
|
||||
} else {
|
||||
updateLocation(focusedFrame);
|
||||
showFrame();
|
||||
}
|
||||
} else {
|
||||
print("do what now?");
|
||||
}
|
||||
@ -239,6 +415,7 @@ function upCommand() {
|
||||
else {
|
||||
focusedFrame.older.younger = focusedFrame;
|
||||
focusedFrame = focusedFrame.older;
|
||||
updateLocation(focusedFrame);
|
||||
showFrame();
|
||||
}
|
||||
}
|
||||
@ -250,6 +427,7 @@ function downCommand() {
|
||||
print("Youngest frame selected; you cannot go down.");
|
||||
else {
|
||||
focusedFrame = focusedFrame.younger;
|
||||
updateLocation(focusedFrame);
|
||||
showFrame();
|
||||
}
|
||||
}
|
||||
@ -284,7 +462,7 @@ function printPop(f, c) {
|
||||
var fdesc = f.fullDescription();
|
||||
if (c.return) {
|
||||
print("frame returning (still selected): " + fdesc);
|
||||
showDebuggeeValue(c.return);
|
||||
showDebuggeeValue(c.return, {brief: true});
|
||||
} else if (c.throw) {
|
||||
print("frame threw exception: " + fdesc);
|
||||
showDebuggeeValue(c.throw);
|
||||
@ -303,11 +481,19 @@ function setUntilRepl(obj, prop, value) {
|
||||
replCleanups.push(function () { obj[prop] = saved; });
|
||||
}
|
||||
|
||||
function updateLocation(frame) {
|
||||
if (options.emacs) {
|
||||
var loc = frame.location();
|
||||
if (loc)
|
||||
print("\032\032" + loc + ":1");
|
||||
}
|
||||
}
|
||||
|
||||
function doStepOrNext(kind) {
|
||||
var startFrame = topFrame;
|
||||
var startLine = startFrame.line;
|
||||
print("stepping in: " + startFrame.fullDescription());
|
||||
print("starting line: " + uneval(startLine));
|
||||
// print("stepping in: " + startFrame.fullDescription());
|
||||
// print("starting line: " + uneval(startLine));
|
||||
|
||||
function stepPopped(completion) {
|
||||
// Note that we're popping this frame; we need to watch for
|
||||
@ -315,19 +501,47 @@ function doStepOrNext(kind) {
|
||||
this.reportedPop = true;
|
||||
printPop(this, completion);
|
||||
topFrame = focusedFrame = this;
|
||||
if (kind.finish) {
|
||||
// We want to continue, but this frame is going to be invalid as
|
||||
// soon as this function returns, which will make the replCleanups
|
||||
// assert when it tries to access the dead frame's 'onPop'
|
||||
// property. So clear it out now while the frame is still valid,
|
||||
// and trade it for an 'onStep' callback on the frame we're popping to.
|
||||
preReplCleanups();
|
||||
setUntilRepl(this.older, 'onStep', stepStepped);
|
||||
return undefined;
|
||||
}
|
||||
updateLocation(this);
|
||||
return repl();
|
||||
}
|
||||
|
||||
function stepEntered(newFrame) {
|
||||
print("entered frame: " + newFrame.fullDescription());
|
||||
updateLocation(newFrame);
|
||||
topFrame = focusedFrame = newFrame;
|
||||
return repl();
|
||||
}
|
||||
|
||||
function stepStepped() {
|
||||
print("stepStepped: " + this.fullDescription());
|
||||
// If we've changed frame or line, then report that.
|
||||
if (this !== startFrame || this.line != startLine) {
|
||||
// print("stepStepped: " + this.fullDescription());
|
||||
updateLocation(this);
|
||||
var stop = false;
|
||||
|
||||
if (kind.finish) {
|
||||
// 'finish' set a one-time onStep for stopping at the frame it
|
||||
// wants to return to
|
||||
stop = true;
|
||||
} else if (kind.upto) {
|
||||
// running until a given line is reached
|
||||
if (this.line == kind.stopLine)
|
||||
stop = true;
|
||||
} else {
|
||||
// regular step; stop whenever the line number changes
|
||||
if ((this.line != startLine) || (this != startFrame))
|
||||
stop = true;
|
||||
}
|
||||
|
||||
if (stop) {
|
||||
topFrame = focusedFrame = this;
|
||||
if (focusedFrame != startFrame)
|
||||
print(focusedFrame.fullDescription());
|
||||
@ -347,7 +561,8 @@ function doStepOrNext(kind) {
|
||||
if (!stepFrame || !stepFrame.script)
|
||||
stepFrame = null;
|
||||
if (stepFrame) {
|
||||
setUntilRepl(stepFrame, 'onStep', stepStepped);
|
||||
if (!kind.finish)
|
||||
setUntilRepl(stepFrame, 'onStep', stepStepped);
|
||||
setUntilRepl(stepFrame, 'onPop', stepPopped);
|
||||
}
|
||||
|
||||
@ -357,32 +572,53 @@ function doStepOrNext(kind) {
|
||||
|
||||
function stepCommand() { return doStepOrNext({step:true}); }
|
||||
function nextCommand() { return doStepOrNext({next:true}); }
|
||||
function finishCommand() { return doStepOrNext({finish:true}); }
|
||||
|
||||
// FIXME: DOES NOT WORK YET
|
||||
function breakpointCommand(where) {
|
||||
print("Sorry, breakpoints don't work yet.");
|
||||
var script = focusedFrame.script;
|
||||
var offsets = script.getLineOffsets(Number(where));
|
||||
if (offsets.length == 0) {
|
||||
print("Unable to break at line " + where);
|
||||
return;
|
||||
}
|
||||
for (var offset of offsets) {
|
||||
script.setBreakpoint(offset, { hit: handleBreakpoint });
|
||||
}
|
||||
print("Set breakpoint in " + script.url + ":" + script.startLine + " at line " + where + ", " + offsets.length);
|
||||
}
|
||||
|
||||
// Build the table of commands.
|
||||
var commands = {};
|
||||
var commandArray = [
|
||||
backtraceCommand, "bt", "where",
|
||||
breakpointCommand, "b", "break",
|
||||
continueCommand, "c",
|
||||
detachCommand,
|
||||
downCommand, "d",
|
||||
evalCommand, "!",
|
||||
forcereturnCommand,
|
||||
frameCommand, "f",
|
||||
finishCommand, "fin",
|
||||
nextCommand, "n",
|
||||
printCommand, "p",
|
||||
keysCommand, "k",
|
||||
quitCommand, "q",
|
||||
runCommand, "run",
|
||||
stepCommand, "s",
|
||||
setCommand,
|
||||
throwCommand, "t",
|
||||
upCommand, "u",
|
||||
helpCommand, "h",
|
||||
evalCommand, "!",
|
||||
];
|
||||
var last = null;
|
||||
];
|
||||
var currentCmd = null;
|
||||
for (var i = 0; i < commandArray.length; i++) {
|
||||
var cmd = commandArray[i];
|
||||
if (typeof cmd === "string")
|
||||
commands[cmd] = last;
|
||||
commands[cmd] = currentCmd;
|
||||
else
|
||||
last = commands[cmd.name.replace(/Command$/, '')] = cmd;
|
||||
currentCmd = commands[cmd.name.replace(/Command$/, '')] = cmd;
|
||||
}
|
||||
|
||||
function helpCommand(rest) {
|
||||
@ -404,15 +640,23 @@ function helpCommand(rest) {
|
||||
}
|
||||
|
||||
// Break cmd into two parts: its first word and everything else. If it begins
|
||||
// with punctuation, treat that as a separate word.
|
||||
// with punctuation, treat that as a separate word. The first word is
|
||||
// terminated with whitespace or the '/' character. So:
|
||||
//
|
||||
// print x => ['print', 'x']
|
||||
// print => ['print', '']
|
||||
// !print x => ['!', 'print x']
|
||||
// ?!wtf!? => ['?', '!wtf!?']
|
||||
// print/b x => ['print', '/b x']
|
||||
//
|
||||
function breakcmd(cmd) {
|
||||
cmd = cmd.trimLeft();
|
||||
if ("!@#$%^&*_+=/?.,<>:;'\"".indexOf(cmd.substr(0, 1)) != -1)
|
||||
return [cmd.substr(0, 1), cmd.substr(1).trimLeft()];
|
||||
var m = /\s/.exec(cmd);
|
||||
var m = /\s+|(?=\/)/.exec(cmd);
|
||||
if (m === null)
|
||||
return [cmd, ''];
|
||||
return [cmd.slice(0, m.index), cmd.slice(m.index).trimLeft()];
|
||||
return [cmd.slice(0, m.index), cmd.slice(m.index + m[0].length)];
|
||||
}
|
||||
|
||||
function runcmd(cmd) {
|
||||
@ -435,9 +679,14 @@ function runcmd(cmd) {
|
||||
return cmd(rest);
|
||||
}
|
||||
|
||||
function repl() {
|
||||
function preReplCleanups() {
|
||||
while (replCleanups.length > 0)
|
||||
replCleanups.pop()();
|
||||
}
|
||||
|
||||
var prevcmd = undefined;
|
||||
function repl() {
|
||||
preReplCleanups();
|
||||
|
||||
var cmd;
|
||||
for (;;) {
|
||||
@ -445,15 +694,20 @@ function repl() {
|
||||
cmd = readline();
|
||||
if (cmd === null)
|
||||
return null;
|
||||
else if (cmd === "")
|
||||
cmd = prevcmd;
|
||||
|
||||
try {
|
||||
prevcmd = cmd;
|
||||
var result = runcmd(cmd);
|
||||
if (result === undefined)
|
||||
; // do nothing
|
||||
; // do nothing, return to prompt
|
||||
else if (Array.isArray(result))
|
||||
return result[0];
|
||||
else if (result === null)
|
||||
return null;
|
||||
else
|
||||
throw new Error("Internal error: result of runcmd wasn't array or undefined");
|
||||
throw new Error("Internal error: result of runcmd wasn't array or undefined: " + result);
|
||||
} catch (exc) {
|
||||
print("*** Internal error: exception in the debugger code.");
|
||||
print(" " + exc);
|
||||
@ -468,7 +722,9 @@ dbg.onDebuggerStatement = function (frame) {
|
||||
topFrame = focusedFrame = frame;
|
||||
print("'debugger' statement hit.");
|
||||
showFrame();
|
||||
return repl();
|
||||
updateLocation(focusedFrame);
|
||||
backtrace();
|
||||
return describedRv(repl(), "debugger.saveExc");
|
||||
});
|
||||
};
|
||||
dbg.onThrow = function (frame, exc) {
|
||||
@ -482,6 +738,17 @@ dbg.onThrow = function (frame, exc) {
|
||||
});
|
||||
};
|
||||
|
||||
function handleBreakpoint (frame) {
|
||||
print("Breakpoint hit!");
|
||||
return saveExcursion(() => {
|
||||
topFrame = focusedFrame = frame;
|
||||
print("breakpoint hit.");
|
||||
showFrame();
|
||||
updateLocation(focusedFrame);
|
||||
return repl();
|
||||
});
|
||||
};
|
||||
|
||||
// The depth of jorendb nesting.
|
||||
var jorendbDepth;
|
||||
if (typeof jorendbDepth == 'undefined') jorendbDepth = 0;
|
||||
@ -493,18 +760,137 @@ var debuggeeGlobalWrapper = dbg.addDebuggee(debuggeeGlobal);
|
||||
print("jorendb version -0.0");
|
||||
prompt = '(' + Array(jorendbDepth+1).join('meta-') + 'jorendb) ';
|
||||
|
||||
var args = arguments;
|
||||
var args = scriptArgs.slice(0);
|
||||
print("INITIAL ARGS: " + args);
|
||||
|
||||
// Find the script to run and its arguments. The script may have been given as
|
||||
// a plain script name, in which case all remaining arguments belong to the
|
||||
// script. Or there may have been any number of arguments to the JS shell,
|
||||
// followed by -f scriptName, followed by additional arguments to the JS shell,
|
||||
// followed by the script arguments. There may be multiple -e or -f options in
|
||||
// the JS shell arguments, and we want to treat each one as a debuggable
|
||||
// script.
|
||||
//
|
||||
// The difficulty is that the JS shell has a mixture of
|
||||
//
|
||||
// --boolean
|
||||
//
|
||||
// and
|
||||
//
|
||||
// --value VAL
|
||||
//
|
||||
// parameters, and there's no way to know whether --option takes an argument or
|
||||
// not. We will assume that VAL will never end in .js, or rather that the first
|
||||
// argument that does not start with "-" but does end in ".js" is the name of
|
||||
// the script.
|
||||
//
|
||||
// If you need to pass other options and not have them given to the script,
|
||||
// pass them before the -f jorendb.js argument. Thus, the safe ways to pass
|
||||
// arguments are:
|
||||
//
|
||||
// js [JS shell options] -f jorendb.js (-e SCRIPT | -f FILE)+ -- [script args]
|
||||
// js [JS shell options] -f jorendb.js (-e SCRIPT | -f FILE)* script.js [script args]
|
||||
//
|
||||
// Additionally, if you want to run a script that is *NOT* debugged, put it in
|
||||
// as part of the leading [JS shell options].
|
||||
|
||||
|
||||
// Compute actualScriptArgs by finding the script to be run and grabbing every
|
||||
// non-script argument. The script may be given by -f scriptname or just plain
|
||||
// scriptname. In the latter case, it will be in the global variable
|
||||
// 'scriptPath' (and NOT in scriptArgs.)
|
||||
var actualScriptArgs = [];
|
||||
var scriptSeen;
|
||||
|
||||
if (scriptPath !== undefined) {
|
||||
todo.push({
|
||||
'action': 'load',
|
||||
'script': scriptPath,
|
||||
});
|
||||
scriptSeen = true;
|
||||
}
|
||||
|
||||
while(args.length > 0) {
|
||||
var arg = args.shift();
|
||||
if (arg == '-f') {
|
||||
arg = args.shift();
|
||||
debuggeeGlobal.evaluate(read(arg), { fileName: arg, lineNumber: 1 });
|
||||
} else if (arg == '-e') {
|
||||
arg = args.shift();
|
||||
debuggeeGlobal.eval(arg);
|
||||
print("arg: " + arg);
|
||||
if (arg == '-e') {
|
||||
print(" eval");
|
||||
todo.push({
|
||||
'action': 'eval',
|
||||
'code': args.shift()
|
||||
});
|
||||
} else if (arg == '-f') {
|
||||
var script = args.shift();
|
||||
print(" load -f " + script);
|
||||
scriptSeen = true;
|
||||
todo.push({
|
||||
'action': 'load',
|
||||
'script': script,
|
||||
});
|
||||
} else if (arg.indexOf("-") == 0) {
|
||||
if (arg == '--') {
|
||||
print(" pass remaining args to script");
|
||||
actualScriptArgs.push(...args);
|
||||
break;
|
||||
} else if ((args.length > 0) && (args[0].indexOf(".js") + 3 == args[0].length)) {
|
||||
// Ends with .js, assume we are looking at --boolean script.js
|
||||
print(" load script.js after --boolean");
|
||||
todo.push({
|
||||
'action': 'load',
|
||||
'script': args.shift(),
|
||||
});
|
||||
scriptSeen = true;
|
||||
} else {
|
||||
// Does not end with .js, assume we are looking at JS shell arg
|
||||
// --value VAL
|
||||
print(" ignore");
|
||||
args.shift();
|
||||
}
|
||||
} else {
|
||||
throw("jorendb does not implement command-line argument '" + arg + "'");
|
||||
if (!scriptSeen) {
|
||||
print(" load general");
|
||||
scriptSeen = true;
|
||||
todo.push({
|
||||
'action': 'load',
|
||||
'script': arg,
|
||||
});
|
||||
} else {
|
||||
print(" arg " + arg);
|
||||
actualScriptArgs.push(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
print("jorendb: scriptPath = " + scriptPath);
|
||||
print("jorendb: scriptArgs = " + scriptArgs);
|
||||
print("jorendb: actualScriptArgs = " + actualScriptArgs);
|
||||
|
||||
for (var task of todo) {
|
||||
task['scriptArgs'] = actualScriptArgs;
|
||||
}
|
||||
|
||||
// If nothing to run, just drop into a repl
|
||||
if (todo.length == 0) {
|
||||
todo.push({ 'action': 'repl' });
|
||||
}
|
||||
|
||||
while (rerun) {
|
||||
print("Top of run loop");
|
||||
rerun = false;
|
||||
for (var task of todo) {
|
||||
activeTask = task;
|
||||
if (task.action == 'eval') {
|
||||
debuggeeGlobal.eval(task.code);
|
||||
} else if (task.action == 'load') {
|
||||
debuggeeGlobal['scriptArgs'] = task.scriptArgs;
|
||||
debuggeeGlobal['scriptPath'] = task.script;
|
||||
print("Loading JavaScript file " + task.script);
|
||||
debuggeeGlobal.evaluate(read(task.script), { 'fileName': task.script, 'lineNumber': 1 });
|
||||
} else if (task.action == 'repl') {
|
||||
repl();
|
||||
}
|
||||
if (rerun)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
repl();
|
||||
quit(0);
|
||||
|
@ -41,16 +41,6 @@ symverscript: symverscript.in
|
||||
EXTRA_DEPS += symverscript
|
||||
endif
|
||||
|
||||
export_files = js-config.h
|
||||
ifdef HAVE_DTRACE
|
||||
export_files += $(CURDIR)/javascript-trace.h
|
||||
endif
|
||||
|
||||
INSTALL_TARGETS += jsconfig
|
||||
jsconfig_FILES = $(export_files)
|
||||
jsconfig_DEST = $(DIST)/include
|
||||
jsconfig_TARGET := export
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
# check_vanilla_allocations.py is tailored to Linux, so only run it there.
|
||||
@ -253,7 +243,7 @@ install:: ETWProvider.mof ETWProvider.man
|
||||
endif
|
||||
|
||||
ifdef HAVE_DTRACE
|
||||
$(CURDIR)/javascript-trace.h: $(srcdir)/devtools/javascript-trace.d
|
||||
javascript-trace.h: $(srcdir)/devtools/javascript-trace.d
|
||||
dtrace -x nolibs -h -s $(srcdir)/devtools/javascript-trace.d -o javascript-trace.h.in
|
||||
sed -e 's/if _DTRACE_VERSION/ifdef INCLUDE_MOZILLA_DTRACE/' \
|
||||
-e '/const/!s/char \*/const char */g' \
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "builtin/SelfHostingDefines.h"
|
||||
#include "js/Date.h"
|
||||
#include "vm/NativeObject.h"
|
||||
#include "vm/Time.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
|
@ -76,6 +76,7 @@ var ignoreCallees = {
|
||||
"mozilla::CycleCollectedJSRuntime.NoteCustomGCThingXPCOMChildren" : true, // During tracing, cannot GC.
|
||||
"PLDHashTableOps.hashKey" : true,
|
||||
"z_stream_s.zfree" : true,
|
||||
"z_stream_s.zalloc" : true,
|
||||
"GrGLInterface.fCallback" : true,
|
||||
"std::strstreambuf._M_alloc_fun" : true,
|
||||
"std::strstreambuf._M_free_fun" : true,
|
||||
|
@ -1,15 +0,0 @@
|
||||
# -*- Mode: makefile -*-
|
||||
#
|
||||
# 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/.
|
||||
|
||||
# Place a GDB Python auto-load file next to the gdb-tests executable, both
|
||||
# in the build directory and in the dist/bin directory.
|
||||
PP_TARGETS += GDB_AUTOLOAD
|
||||
GDB_AUTOLOAD := gdb-tests-gdb.py.in
|
||||
GDB_AUTOLOAD_FLAGS := -Dtopsrcdir=$(abspath $(srcdir)/..)
|
||||
|
||||
INSTALL_TARGETS += GDB_INSTALL_AUTOLOAD
|
||||
GDB_INSTALL_AUTOLOAD_FILES := $(CURDIR)/gdb-tests-gdb.py
|
||||
GDB_INSTALL_AUTOLOAD_DEST := $(DIST)/bin
|
@ -38,3 +38,7 @@ OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
|
||||
# This is intended as a temporary workaround to enable VS2015.
|
||||
if CONFIG['_MSC_VER']:
|
||||
CXXFLAGS += ['-wd4312']
|
||||
|
||||
DEFINES['topsrcdir'] = '%s/js/src' % TOPSRCDIR
|
||||
FINAL_TARGET_PP_FILES += ['gdb-tests-gdb.py.in']
|
||||
OBJDIR_FILES.js.src.gdb += ['!/dist/bin/gdb-tests-gdb.py']
|
||||
|
@ -911,6 +911,13 @@ class EDtrAddr
|
||||
uint32_t encode() const {
|
||||
return data;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
Register maybeOffsetRegister() const {
|
||||
if (data & IsImmEDTR)
|
||||
return InvalidReg;
|
||||
return Register::FromCode(data & 0xf);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
class VFPOff
|
||||
|
@ -1092,6 +1092,8 @@ MacroAssemblerARM::ma_ldrd(EDtrAddr addr, Register rt, DebugOnly<Register> rt2,
|
||||
{
|
||||
MOZ_ASSERT((rt.code() & 1) == 0);
|
||||
MOZ_ASSERT(rt2.value.code() == rt.code() + 1);
|
||||
MOZ_ASSERT(addr.maybeOffsetRegister() != rt); // Undefined behavior if rm == rt/rt2.
|
||||
MOZ_ASSERT(addr.maybeOffsetRegister() != rt2);
|
||||
as_extdtr(IsLoad, 64, true, mode, rt, addr, cc);
|
||||
}
|
||||
|
||||
@ -3170,7 +3172,15 @@ MacroAssemblerARMCompat::loadValue(const BaseIndex& addr, ValueOperand val)
|
||||
Register tmpIdx;
|
||||
if (addr.offset == 0) {
|
||||
if (addr.scale == TimesOne) {
|
||||
tmpIdx = addr.index;
|
||||
// If the offset register is the same as one of the destination
|
||||
// registers, LDRD's behavior is undefined. Use the scratch
|
||||
// register to avoid this.
|
||||
if (val.aliases(addr.index)) {
|
||||
ma_mov(addr.index, scratch);
|
||||
tmpIdx = scratch;
|
||||
} else {
|
||||
tmpIdx = addr.index;
|
||||
}
|
||||
} else {
|
||||
ma_lsl(Imm32(addr.scale), addr.index, scratch);
|
||||
tmpIdx = scratch;
|
||||
|
@ -7,9 +7,3 @@
|
||||
ifdef QEMU_EXE
|
||||
MOZ_POST_PROGRAM_COMMAND = $(topsrcdir)/build/qemu-wrap --qemu $(QEMU_EXE) --libdir $(CROSS_LIB)
|
||||
endif
|
||||
|
||||
# Place a GDB Python auto-load file next to the jsapi-tests executable in
|
||||
# the build directory.
|
||||
PP_TARGETS += JSAPI_TESTS_AUTOLOAD
|
||||
JSAPI_TESTS_AUTOLOAD := jsapi-tests-gdb.py.in
|
||||
JSAPI_TESTS_AUTOLOAD_FLAGS := -Dtopsrcdir=$(abspath $(srcdir)/..)
|
||||
|
@ -133,3 +133,6 @@ OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
|
||||
# This is intended as a temporary workaround to enable VS2015.
|
||||
if CONFIG['_MSC_VER']:
|
||||
CXXFLAGS += ['-wd4312']
|
||||
|
||||
DEFINES['topsrcdir'] = '%s/js/src' % TOPSRCDIR
|
||||
OBJDIR_PP_FILES.js.src['jsapi-tests'] += ['jsapi-tests-gdb.py.in']
|
||||
|
@ -1350,11 +1350,6 @@ JSFunction::createScriptForLazilyInterpretedFunction(JSContext* cx, HandleFuncti
|
||||
if (cx->zone()->needsIncrementalBarrier())
|
||||
LazyScript::writeBarrierPre(lazy);
|
||||
|
||||
// Suppress GC for now although we should be able to remove this by
|
||||
// making 'lazy' a Rooted<LazyScript*> (which requires adding a
|
||||
// THING_ROOT_LAZY_SCRIPT).
|
||||
AutoSuppressGC suppressGC(cx);
|
||||
|
||||
RootedScript script(cx, lazy->maybeScript());
|
||||
|
||||
// Only functions without inner functions or direct eval are
|
||||
|
@ -87,9 +87,10 @@ SetFrameArgumentsObject(JSContext* cx, AbstractFramePtr frame,
|
||||
inline JSFunction*
|
||||
LazyScript::functionDelazifying(JSContext* cx) const
|
||||
{
|
||||
if (function_ && !function_->getOrCreateScript(cx))
|
||||
Rooted<const LazyScript*> self(cx, this);
|
||||
if (self->function_ && !self->function_->getOrCreateScript(cx))
|
||||
return nullptr;
|
||||
return function_;
|
||||
return self->function_;
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
@ -77,10 +77,16 @@ CONFIGURE_DEFINE_FILES += [
|
||||
'js-config.h',
|
||||
]
|
||||
|
||||
if CONFIG['HAVE_DTRACE']:
|
||||
GENERATED_FILES += ['javascript-trace.h']
|
||||
|
||||
EXPORTS += ['!javascript-trace.h']
|
||||
|
||||
# Changes to internal header files, used externally, massively slow down
|
||||
# browser builds. Don't add new files here unless you know what you're
|
||||
# doing!
|
||||
EXPORTS += [
|
||||
'!js-config.h',
|
||||
'js.msg',
|
||||
'jsalloc.h',
|
||||
'jsapi.h',
|
||||
|
@ -973,9 +973,9 @@ case "$target" in
|
||||
*-android*|*-linuxandroid*)
|
||||
AC_DEFINE(NO_PW_GECOS)
|
||||
MOZ_GFX_OPTIMIZE_MOBILE=1
|
||||
MOZ_OPTIMIZE_FLAGS="-O3 -fno-reorder-functions"
|
||||
MOZ_OPTIMIZE_FLAGS="-O3"
|
||||
if test -z "$CLANG_CC"; then
|
||||
MOZ_OPTIMIZE_FLAGS="-freorder-blocks $MOZ_OPTIMIZE_FLAGS"
|
||||
MOZ_OPTIMIZE_FLAGS="-freorder-blocks -fno-reorder-functions $MOZ_OPTIMIZE_FLAGS"
|
||||
fi
|
||||
# The Maemo builders don't know about this flag
|
||||
MOZ_ARM_VFP_FLAGS="-mfpu=vfp"
|
||||
|
@ -8,16 +8,6 @@ ifdef QEMU_EXE
|
||||
MOZ_POST_PROGRAM_COMMAND = $(topsrcdir)/build/qemu-wrap --qemu $(QEMU_EXE) --libdir $(CROSS_LIB)
|
||||
endif
|
||||
|
||||
# Place a GDB Python auto-load file next to the shell executable, both in
|
||||
# the build directory and in the dist/bin directory.
|
||||
PP_TARGETS += SHELL_AUTOLOAD
|
||||
SHELL_AUTOLOAD := js-gdb.py.in
|
||||
SHELL_AUTOLOAD_FLAGS := -Dtopsrcdir=$(abspath $(srcdir)/..)
|
||||
|
||||
INSTALL_TARGETS += SHELL_INSTALL_AUTOLOAD
|
||||
SHELL_INSTALL_AUTOLOAD_FILES := $(CURDIR)/js-gdb.py
|
||||
SHELL_INSTALL_AUTOLOAD_DEST := $(DIST)/bin
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
# People expect the js shell to wind up in the top-level JS dir.
|
||||
|
@ -48,3 +48,9 @@ shellmoduleloader.inputs = [
|
||||
# This is intended as a temporary workaround to enable VS2015.
|
||||
if CONFIG['_MSC_VER']:
|
||||
CXXFLAGS += ['-wd4312']
|
||||
|
||||
# Place a GDB Python auto-load file next to the shell executable, both in
|
||||
# the build directory and in the dist/bin directory.
|
||||
DEFINES['topsrcdir'] = '%s/js/src' % TOPSRCDIR
|
||||
FINAL_TARGET_PP_FILES += ['js-gdb.py.in']
|
||||
OBJDIR_FILES.js.src.shell += ['!/dist/bin/js-gdb.py']
|
||||
|
@ -5105,9 +5105,11 @@ ChooseScaleAndSetTransform(FrameLayerBuilder* aLayerBuilder,
|
||||
scale = nsLayoutUtils::ComputeSuitableScaleForAnimation(
|
||||
aContainerFrame, aVisibleRect.Size(),
|
||||
displaySize);
|
||||
// multiply by the scale inherited from ancestors
|
||||
scale.width *= aIncomingScale.mXScale;
|
||||
scale.height *= aIncomingScale.mYScale;
|
||||
// multiply by the scale inherited from ancestors--we use a uniform
|
||||
// scale factor to prevent blurring when the layer is rotated.
|
||||
float incomingScale = std::max(aIncomingScale.mXScale, aIncomingScale.mYScale);
|
||||
scale.width *= incomingScale;
|
||||
scale.height *= incomingScale;
|
||||
} else {
|
||||
// Scale factors are normalized to a power of 2 to reduce the number of resolution changes
|
||||
scale = RoundToFloatPrecision(ThebesMatrix(transform2d).ScaleFactors(true));
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user