Merge inbound to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2016-03-25 22:07:49 -04:00
commit e8a838517a
213 changed files with 3829 additions and 1813 deletions

View File

@ -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);

View File

@ -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":

View File

@ -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);
}

View File

@ -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);
});
});

View File

@ -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"/>

View File

@ -11,9 +11,6 @@
<!ENTITY warnOpenManyTabs.label "Warn me when opening multiple tabs might slow down &brandShortName;">
<!ENTITY warnOpenManyTabs.accesskey "d">
<!ENTITY restoreTabsOnDemand.label "Dont 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">

View File

@ -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") {

View File

@ -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

View File

@ -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

View File

@ -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);
};

View File

@ -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

View File

@ -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
{

View File

@ -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();
}

View File

@ -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__

View File

@ -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);
}
}

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View 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

View 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__

View File

@ -67,6 +67,7 @@ UNIFIED_SOURCES += [
'KeyPath.cpp',
'PermissionRequestBase.cpp',
'ReportInternalError.cpp',
'ScriptErrorHelper.cpp',
]
SOURCES += [

View File

@ -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)

View File

@ -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

View File

@ -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));

View File

@ -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)
{

View File

@ -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);

View File

@ -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 */
/**

View File

@ -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]

View 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>

View File

@ -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.

View File

@ -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;
}

View File

@ -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;
},
});

View File

@ -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);
},
};

View File

@ -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);

View File

@ -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.

View File

@ -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);

View File

@ -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);
});

View File

@ -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());

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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();

View File

@ -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());

View File

@ -335,7 +335,7 @@
}).then(SimpleTest.finish);
}
setupPrefsAndMock(mockSocket).then(_ => runTest());
setupPrefsAndMockSocket(mockSocket).then(_ => runTest());
SpecialPowers.addPermission('desktop-notification', true, document);
SimpleTest.waitForExplicitFinish();
</script>

View File

@ -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());

View File

@ -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>

View File

@ -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>

View File

@ -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");

View File

@ -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

View File

@ -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;
});

View 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
);
}
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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());
});

View File

@ -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');

View File

@ -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 });

View File

@ -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() {

View File

@ -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}) =>

View File

@ -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');

View File

@ -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(), [

View File

@ -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');

View File

@ -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'

View File

@ -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');

View File

@ -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');
});

View File

@ -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');

View File

@ -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');

View File

@ -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');
});

View File

@ -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');
});

View File

@ -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;
});

View File

@ -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');
});

View File

@ -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');
});

View File

@ -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');
});

View File

@ -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');
});

View File

@ -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',

View File

@ -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}`);

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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');

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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' \

View File

@ -10,6 +10,7 @@
#include "builtin/SelfHostingDefines.h"
#include "js/Date.h"
#include "vm/NativeObject.h"
#include "vm/Time.h"
namespace js {

View File

@ -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,

View File

@ -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

View File

@ -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']

View File

@ -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

View File

@ -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;

View File

@ -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)/..)

View File

@ -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']

View File

@ -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

View File

@ -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

View File

@ -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',

View File

@ -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"

View File

@ -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.

View File

@ -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']

View File

@ -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