Merge inbound to central, a=merge CLOSED TREE

This commit is contained in:
Wes Kocher 2016-05-10 15:20:22 -07:00
commit 729942fffa
210 changed files with 3259 additions and 2107 deletions

View File

@ -101,7 +101,7 @@ public:
if (mSubpropertyCount[aProperty] == 0) {
uint32_t count = 0;
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
p, aProperty, nsCSSProps::eEnabledForAllContent) {
p, aProperty, CSSEnabledState::eForAllContent) {
++count;
}
mSubpropertyCount[aProperty] = count;
@ -492,13 +492,13 @@ KeyframeUtils::GetAnimationPropertiesFromKeyframes(
if (nsCSSProps::IsShorthand(pair.mProperty)) {
nsCSSValueTokenStream* tokenStream = pair.mValue.GetTokenStreamValue();
if (!StyleAnimationValue::ComputeValues(pair.mProperty,
nsCSSProps::eEnabledForAllContent, aElement, aStyleContext,
CSSEnabledState::eForAllContent, aElement, aStyleContext,
tokenStream->mTokenStream, /* aUseSVGMode */ false, values)) {
continue;
}
} else {
if (!StyleAnimationValue::ComputeValues(pair.mProperty,
nsCSSProps::eEnabledForAllContent, aElement, aStyleContext,
CSSEnabledState::eForAllContent, aElement, aStyleContext,
pair.mValue, /* aUseSVGMode */ false, values)) {
continue;
}
@ -695,7 +695,7 @@ GetPropertyValuesPairs(JSContext* aCx,
}
nsCSSProperty property =
nsCSSProps::LookupPropertyByIDLName(propName,
nsCSSProps::eEnabledForAllContent);
CSSEnabledState::eForAllContent);
if (property != eCSSProperty_UNKNOWN &&
(nsCSSProps::IsShorthand(property) ||
nsCSSProps::kAnimTypeTable[property] != eStyleAnimType_None)) {
@ -1110,7 +1110,7 @@ RequiresAdditiveAnimation(const nsTArray<Keyframe>& aKeyframes,
continue;
}
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
prop, pair.mProperty, nsCSSProps::eEnabledForAllContent) {
prop, pair.mProperty, CSSEnabledState::eForAllContent) {
addToPropertySets(*prop, offsetToUse);
}
} else {

View File

@ -546,6 +546,10 @@ protected:
return true;
}
if (!JS_ObjectNotWritten(aWriter, aObj)) {
return false;
}
JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
if (NS_WARN_IF(!jsString)) {

View File

@ -100,6 +100,10 @@ WindowNamedPropertiesHandler::getOwnPropDescriptor(JSContext* aCx,
return false;
}
if(str.IsEmpty()) {
return true;
}
// Grab the DOM window.
JS::Rooted<JSObject*> global(aCx, JS_GetGlobalForObject(aCx, aProxy));
nsGlobalWindow* win = xpc::WindowOrNull(global);

View File

@ -171,8 +171,11 @@ SelectionCopyHelper(nsISelection *aSel, nsIDocument *aDoc,
nsAutoString htmlInfoBuf;
if (encodedTextHTML) {
// Redo the encoding, but this time use the passed-in flags.
// Don't allow wrapping of CJK strings.
mimeType.AssignLiteral(kHTMLMime);
rv = docEncoder->Init(domDoc, mimeType, aFlags);
rv = docEncoder->Init(domDoc, mimeType,
aFlags |
nsIDocumentEncoder::OutputDisallowLineBreaking);
NS_ENSURE_SUCCESS(rv, rv);
rv = docEncoder->SetSelection(aSel);

View File

@ -1766,98 +1766,6 @@ nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
nsresult rv = NS_OK;
if (name_struct->mType == nsGlobalNameStruct::eTypeNewDOMBinding ||
name_struct->mType == nsGlobalNameStruct::eTypeClassProto ||
name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor) {
// Lookup new DOM bindings.
DefineInterface getOrCreateInterfaceObject =
name_struct->mDefineDOMInterface;
if (getOrCreateInterfaceObject) {
if (name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor &&
!OldBindingConstructorEnabled(name_struct, aWin, cx)) {
return NS_OK;
}
ConstructorEnabled* checkEnabledForScope = name_struct->mConstructorEnabled;
// We do the enabled check on the current compartment of cx, but for the
// actual object we pass in the underlying object in the Xray case. That
// way the callee can decide whether to allow access based on the caller
// or the window being touched.
JS::Rooted<JSObject*> global(cx,
js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false));
if (!global) {
return NS_ERROR_DOM_SECURITY_ERR;
}
if (checkEnabledForScope && !checkEnabledForScope(cx, global)) {
return NS_OK;
}
// The DOM constructor resolve machinery interacts with Xrays in tricky
// ways, and there are some asymmetries that are important to understand.
//
// In the regular (non-Xray) case, we only want to resolve constructors
// once (so that if they're deleted, they don't reappear). We do this by
// stashing the constructor in a slot on the global, such that we can see
// during resolve whether we've created it already. This is rather
// memory-intensive, so we don't try to maintain these semantics when
// manipulating a global over Xray (so the properties just re-resolve if
// they've been deleted).
//
// Unfortunately, there's a bit of an impedance-mismatch between the Xray
// and non-Xray machinery. The Xray machinery wants an API that returns a
// JS::PropertyDescriptor, so that the resolve hook doesn't have to get
// snared up with trying to define a property on the Xray holder. At the
// same time, the DefineInterface callbacks are set up to define things
// directly on the global. And re-jiggering them to return property
// descriptors is tricky, because some DefineInterface callbacks define
// multiple things (like the Image() alias for HTMLImageElement).
//
// So the setup is as-follows:
//
// * The resolve function takes a JS::PropertyDescriptor, but in the
// non-Xray case, callees may define things directly on the global, and
// set the value on the property descriptor to |undefined| to indicate
// that there's nothing more for the caller to do. We assert against
// this behavior in the Xray case.
//
// * We make sure that we do a non-Xray resolve first, so that all the
// slots are set up. In the Xray case, this means unwrapping and doing
// a non-Xray resolve before doing the Xray resolve.
//
// This all could use some grand refactoring, but for now we just limp
// along.
if (xpc::WrapperFactory::IsXrayWrapper(obj)) {
JS::Rooted<JSObject*> interfaceObject(cx);
{
JSAutoCompartment ac(cx, global);
interfaceObject = getOrCreateInterfaceObject(cx, global, id, false);
}
if (NS_WARN_IF(!interfaceObject)) {
return NS_ERROR_FAILURE;
}
if (!JS_WrapObject(cx, &interfaceObject)) {
return NS_ERROR_FAILURE;
}
FillPropertyDescriptor(desc, obj, 0, JS::ObjectValue(*interfaceObject));
} else {
JS::Rooted<JSObject*> interfaceObject(cx,
getOrCreateInterfaceObject(cx, obj, id, true));
if (NS_WARN_IF(!interfaceObject)) {
return NS_ERROR_FAILURE;
}
// We've already defined the property. We indicate this to the caller
// by filling a property descriptor with JS::UndefinedValue() as the
// value. We still have to fill in a property descriptor, though, so
// that the caller knows the property is in fact on this object. It
// doesn't matter what we pass for the "readonly" argument here.
FillPropertyDescriptor(desc, obj, JS::UndefinedValue(), false);
}
return NS_OK;
}
}
if (name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor) {
if (!OldBindingConstructorEnabled(name_struct, aWin, cx)) {
return NS_OK;

View File

@ -2503,7 +2503,7 @@ nsDOMWindowUtils::ComputeAnimationDistance(nsIDOMElement* aElement,
NS_ENSURE_SUCCESS(rv, rv);
nsCSSProperty property =
nsCSSProps::LookupProperty(aProperty, nsCSSProps::eIgnoreEnabledState);
nsCSSProps::LookupProperty(aProperty, CSSEnabledState::eIgnoreEnabledState);
if (property != eCSSProperty_UNKNOWN && nsCSSProps::IsShorthand(property)) {
property = eCSSProperty_UNKNOWN;
}

View File

@ -1331,9 +1331,6 @@ GK_ATOM(percentage, "%")
GK_ATOM(A, "A")
GK_ATOM(alignment_baseline, "alignment-baseline")
GK_ATOM(allowReorder, "allowReorder")
GK_ATOM(altGlyph, "altGlyph")
GK_ATOM(altGlyphDef, "altGlyphDef")
GK_ATOM(altGlyphItem, "altGlyphItem")
GK_ATOM(amplitude, "amplitude")
GK_ATOM(animate, "animate")
GK_ATOM(animateColor, "animateColor")

View File

@ -229,6 +229,7 @@
#include "mozilla/dom/ImageBitmap.h"
#include "mozilla/dom/ServiceWorkerRegistration.h"
#include "mozilla/dom/U2F.h"
#include "mozilla/dom/WebIDLGlobalNameHash.h"
#ifdef HAVE_SIDEBAR
#include "mozilla/dom/ExternalBinding.h"
#endif
@ -4499,6 +4500,15 @@ nsGlobalWindow::DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObj,
return true;
}
bool found;
if (!WebIDLGlobalNameHash::DefineIfEnabled(aCx, aObj, aId, aDesc, &found)) {
return false;
}
if (found) {
return true;
}
nsresult rv = nsWindowSH::GlobalResolve(this, aCx, aObj, aId, aDesc);
if (NS_FAILED(rv)) {
return Throw(aCx, rv);
@ -4527,6 +4537,10 @@ nsGlobalWindow::MayResolve(jsid aId)
return true;
}
if (WebIDLGlobalNameHash::MayResolve(aId)) {
return true;
}
nsScriptNameSpaceManager *nameSpaceManager = PeekNameSpaceManager();
if (!nameSpaceManager) {
// Really shouldn't happen. Fail safe.
@ -4550,12 +4564,13 @@ nsGlobalWindow::GetOwnPropertyNames(JSContext* aCx, nsTArray<nsString>& aNames,
nsScriptNameSpaceManager* nameSpaceManager = GetNameSpaceManager();
if (nameSpaceManager) {
JS::Rooted<JSObject*> wrapper(aCx, GetWrapper());
WebIDLGlobalNameHash::GetNames(aCx, wrapper, aNames);
for (auto i = nameSpaceManager->GlobalNameIter(); !i.Done(); i.Next()) {
const GlobalNameMapEntry* entry = i.Get();
if (nsWindowSH::NameStructEnabled(aCx, this, entry->mKey,
entry->mGlobalName) &&
(!entry->mGlobalName.mConstructorEnabled ||
entry->mGlobalName.mConstructorEnabled(aCx, wrapper))) {
entry->mGlobalName)) {
aNames.AppendElement(entry->mKey);
}
}

View File

@ -25,7 +25,8 @@
#include "nsCRT.h"
#include "nsIObserverService.h"
#include "nsISimpleEnumerator.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/WebIDLGlobalNameHash.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
@ -34,6 +35,7 @@
#define NS_DOM_INTERFACE_PREFIX "nsIDOM"
using namespace mozilla;
using namespace mozilla::dom;
static PLDHashNumber
GlobalNameHashHashKey(const void *key)
@ -95,7 +97,7 @@ static const PLDHashTableOps hash_table_ops =
GlobalNameHashInitEntry
};
#define GLOBALNAME_HASHTABLE_INITIAL_LENGTH 512
#define GLOBALNAME_HASHTABLE_INITIAL_LENGTH 32
nsScriptNameSpaceManager::nsScriptNameSpaceManager()
: mGlobalNames(&hash_table_ops, sizeof(GlobalNameMapEntry),
@ -111,14 +113,17 @@ nsScriptNameSpaceManager::~nsScriptNameSpaceManager()
}
nsGlobalNameStruct *
nsScriptNameSpaceManager::AddToHash(const nsAString *aKey,
nsScriptNameSpaceManager::AddToHash(const char *aKey,
const char16_t **aClassName)
{
auto entry = static_cast<GlobalNameMapEntry*>(mGlobalNames.Add(aKey, fallible));
NS_ConvertASCIItoUTF16 key(aKey);
auto entry = static_cast<GlobalNameMapEntry*>(mGlobalNames.Add(&key, fallible));
if (!entry) {
return nullptr;
}
WebIDLGlobalNameHash::Remove(aKey, key.Length());
if (aClassName) {
*aClassName = entry->mKey.get();
}
@ -230,8 +235,7 @@ nsScriptNameSpaceManager::RegisterClassName(const char *aClassName,
return NS_OK;
}
NS_ASSERTION(s->mType == nsGlobalNameStruct::eTypeNotInitialized ||
s->mType == nsGlobalNameStruct::eTypeNewDOMBinding,
NS_ASSERTION(s->mType == nsGlobalNameStruct::eTypeNotInitialized,
"Whaaa, JS environment name clash!");
s->mType = nsGlobalNameStruct::eTypeClassConstructor;
@ -254,8 +258,7 @@ nsScriptNameSpaceManager::RegisterClassProto(const char *aClassName,
nsGlobalNameStruct *s = AddToHash(aClassName);
NS_ENSURE_TRUE(s, NS_ERROR_OUT_OF_MEMORY);
if (s->mType != nsGlobalNameStruct::eTypeNotInitialized &&
s->mType != nsGlobalNameStruct::eTypeNewDOMBinding) {
if (s->mType != nsGlobalNameStruct::eTypeNotInitialized) {
*aFoundOld = true;
return NS_OK;
@ -349,8 +352,7 @@ nsScriptNameSpaceManager::OperateCategoryEntryHash(nsICategoryManager* aCategory
nsGlobalNameStruct *s = AddToHash(categoryEntry.get());
NS_ENSURE_TRUE(s, NS_ERROR_OUT_OF_MEMORY);
if (s->mType == nsGlobalNameStruct::eTypeNotInitialized ||
s->mType == nsGlobalNameStruct::eTypeNewDOMBinding) {
if (s->mType == nsGlobalNameStruct::eTypeNotInitialized) {
s->mType = type;
s->mCID = cid;
s->mChromeOnly =
@ -414,21 +416,6 @@ nsScriptNameSpaceManager::Observe(nsISupports* aSubject, const char* aTopic,
return NS_OK;
}
void
nsScriptNameSpaceManager::RegisterDefineDOMInterface(const nsAFlatString& aName,
mozilla::dom::DefineInterface aDefineDOMInterface,
mozilla::dom::ConstructorEnabled* aConstructorEnabled)
{
nsGlobalNameStruct *s = AddToHash(&aName);
if (s) {
if (s->mType == nsGlobalNameStruct::eTypeNotInitialized) {
s->mType = nsGlobalNameStruct::eTypeNewDOMBinding;
}
s->mDefineDOMInterface = aDefineDOMInterface;
s->mConstructorEnabled = aConstructorEnabled;
}
}
MOZ_DEFINE_MALLOC_SIZE_OF(ScriptNameSpaceManagerMallocSizeOf)
NS_IMETHODIMP

View File

@ -33,35 +33,24 @@
#include "nsWeakReference.h"
#include "xpcpublic.h"
struct nsGlobalNameStruct
{
enum nametype {
eTypeNotInitialized,
eTypeNewDOMBinding,
eTypeProperty,
eTypeExternalConstructor,
eTypeClassConstructor,
eTypeClassProto,
} mType;
// mChromeOnly is only used for structs that define non-WebIDL things
// (possibly in addition to WebIDL ones). In particular, it's not even
// initialized for eTypeNewDOMBinding structs.
bool mChromeOnly : 1;
bool mAllowXBL : 1;
union {
int32_t mDOMClassInfoID; // eTypeClassConstructor
nsIID mIID; // eTypeClassProto
nsCID mCID; // All other types except eTypeNewDOMBinding
nsCID mCID; // All other types
};
// For new style DOM bindings.
mozilla::dom::DefineInterface mDefineDOMInterface;
// May be null if enabled unconditionally
mozilla::dom::ConstructorEnabled* mConstructorEnabled;
};
class GlobalNameMapEntry : public PLDHashEntryHdr
@ -112,19 +101,6 @@ public:
const nsIID *aConstructorProtoIID,
bool *aFoundOld);
void RegisterDefineDOMInterface(const nsAFlatString& aName,
mozilla::dom::DefineInterface aDefineDOMInterface,
mozilla::dom::ConstructorEnabled* aConstructorEnabled);
template<size_t N>
void RegisterDefineDOMInterface(const char16_t (&aKey)[N],
mozilla::dom::DefineInterface aDefineDOMInterface,
mozilla::dom::ConstructorEnabled* aConstructorEnabled)
{
nsLiteralString key(aKey);
return RegisterDefineDOMInterface(key, aDefineDOMInterface,
aConstructorEnabled);
}
class NameIterator : public PLDHashTable::Iterator
{
public:
@ -155,22 +131,14 @@ private:
// that aKey will be mapped to. If mType in the returned
// nsGlobalNameStruct is != eTypeNotInitialized, an entry for aKey
// already existed.
nsGlobalNameStruct *AddToHash(const nsAString *aKey,
const char16_t **aClassName = nullptr);
nsGlobalNameStruct *AddToHash(const char *aKey,
const char16_t **aClassName = nullptr)
{
NS_ConvertASCIItoUTF16 key(aKey);
return AddToHash(&key, aClassName);
}
const char16_t **aClassName = nullptr);
// Removes an existing entry from the hash.
void RemoveFromHash(const nsAString *aKey);
nsresult FillHash(nsICategoryManager *aCategoryManager,
const char *aCategory);
nsresult RegisterInterface(const char* aIfName,
const nsIID *aIfIID,
bool* aFoundOld);
/**
* Add a new category entry into the hash table.

View File

@ -280,9 +280,6 @@ nsIAtom** const kURLAttributesHTML[] = {
nsIAtom** const kElementsSVG[] = {
&nsGkAtoms::a, // a
&nsGkAtoms::altGlyph, // altGlyph
&nsGkAtoms::altGlyphDef, // altGlyphDef
&nsGkAtoms::altGlyphItem, // altGlyphItem
&nsGkAtoms::circle, // circle
&nsGkAtoms::clipPath, // clipPath
&nsGkAtoms::colorProfile, // color-profile

View File

@ -333,6 +333,8 @@ skip-if = buildapp == 'b2g' || toolkit == 'android' # b2g(clipboard undefined) b
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
[test_bug330925.xhtml]
[test_bug331959.html]
[test_bug333064.html]
skip-if = toolkit == 'android'
[test_bug333198.html]
[test_bug333673.html]
[test_bug337631.html]

View File

@ -0,0 +1,59 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=333064
-->
<head>
<title>Test for Bug 333064</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=333064">Mozilla Bug 333064</a>
<p id="display"></p>
<div id="display">
</div>
<div id="korean-text">안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안</div>
<pre id="test">
</pre>
<script class="testbody" type="application/javascript">
/** Test for Bug 333064 **/
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(function() {
var div = document.getElementById("korean-text");
var sel = window.getSelection();
// Select text node in div.
var r = document.createRange();
r.setStart(div, 0);
r.setEnd(div, 1);
sel.addRange(r);
SimpleTest.waitForClipboard(
function compare(value) {
// Make sure we got the HTML flavour we asked for and that our
// string is included without additional spaces.
return value.includes("korean-text") && value.includes("안".repeat(160));
},
function setup() {
synthesizeKey("C", {accelKey: true});
},
function onSuccess() {
SimpleTest.finish();
},
function onFailure() {
SimpleTest.finish();
},
"text/html"
);
});
</script>
</body>
</html>

View File

@ -45,6 +45,7 @@
#include "mozilla/dom/HTMLAppletElementBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ResolveSystemBinding.h"
#include "mozilla/dom/WebIDLGlobalNameHash.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
@ -2936,20 +2937,15 @@ RegisterDOMNames()
return NS_OK;
}
// Register new DOM bindings
WebIDLGlobalNameHash::Init();
nsresult rv = nsDOMClassInfo::Init();
if (NS_FAILED(rv)) {
NS_ERROR("Could not initialize nsDOMClassInfo");
return rv;
}
// Register new DOM bindings
nsScriptNameSpaceManager* nameSpaceManager = GetNameSpaceManager();
if (!nameSpaceManager) {
NS_ERROR("Could not initialize nsScriptNameSpaceManager");
return NS_ERROR_FAILURE;
}
mozilla::dom::Register(nameSpaceManager);
sRegisteredDOMNames = true;
return NS_OK;

View File

@ -12056,14 +12056,14 @@ class CGDescriptor(CGThing):
if descriptor.interface.hasInterfacePrototypeObject():
cgThings.append(CGPrototypeJSClass(descriptor, properties))
if descriptor.interface.hasInterfaceObject():
cgThings.append(CGDefineDOMInterfaceMethod(descriptor))
if ((descriptor.interface.hasInterfaceObject() or descriptor.interface.isNavigatorProperty()) and
not descriptor.interface.isExternal() and
descriptor.isExposedConditionally()):
cgThings.append(CGConstructorEnabled(descriptor))
if descriptor.registersGlobalNamesOnWindow:
cgThings.append(CGDefineDOMInterfaceMethod(descriptor))
if (descriptor.interface.hasMembersInSlots() and
descriptor.interface.hasChildInterfaces()):
raise TypeError("We don't support members in slots on "
@ -13056,48 +13056,53 @@ class CGResolveSystemBinding(CGAbstractMethod):
"\n").define()
class CGRegisterProtos(CGAbstractMethod):
def getGlobalNames(config):
names = []
for desc in config.getDescriptors(registersGlobalNamesOnWindow=True):
names.append((desc.name, desc))
names.extend((n.identifier.name, desc) for n in desc.interface.namedConstructors)
return names
class CGGlobalNamesString(CGGeneric):
def __init__(self, config):
CGAbstractMethod.__init__(self, None, 'Register', 'void',
[Argument('nsScriptNameSpaceManager*', 'aNameSpaceManager')])
globalNames = getGlobalNames(config)
currentOffset = 0
strings = []
for (name, _) in globalNames:
strings.append('/* %i */ "%s\\0"' % (currentOffset, name))
currentOffset += len(name) + 1 # Add trailing null.
define = fill("""
const uint32_t WebIDLGlobalNameHash::sCount = ${count};
const char WebIDLGlobalNameHash::sNames[] =
$*{strings}
""",
count=len(globalNames),
strings="\n".join(strings) + ";\n")
CGGeneric.__init__(self, define=define)
class CGRegisterGlobalNames(CGAbstractMethod):
def __init__(self, config):
CGAbstractMethod.__init__(self, None, 'RegisterWebIDLGlobalNames',
'void', [])
self.config = config
def _defineMacro(self):
return dedent("""
#define REGISTER_PROTO(_dom_class, _ctor_check) \\
aNameSpaceManager->RegisterDefineDOMInterface(MOZ_UTF16(#_dom_class), _dom_class##Binding::DefineDOMInterface, _ctor_check);
#define REGISTER_CONSTRUCTOR(_dom_constructor, _dom_class, _ctor_check) \\
aNameSpaceManager->RegisterDefineDOMInterface(MOZ_UTF16(#_dom_constructor), _dom_class##Binding::DefineDOMInterface, _ctor_check);
""")
def _undefineMacro(self):
return dedent("""
#undef REGISTER_CONSTRUCTOR
#undef REGISTER_PROTO
""")
def _registerProtos(self):
def definition_body(self):
def getCheck(desc):
if not desc.isExposedConditionally():
return "nullptr"
return "%sBinding::ConstructorEnabled" % desc.name
lines = []
for desc in self.config.getDescriptors(hasInterfaceObject=True,
isExternal=False,
workers=False,
isExposedInWindow=True,
register=True):
lines.append("REGISTER_PROTO(%s, %s);\n" % (desc.name, getCheck(desc)))
lines.extend("REGISTER_CONSTRUCTOR(%s, %s, %s);\n" % (n.identifier.name, desc.name, getCheck(desc))
for n in desc.interface.namedConstructors)
return ''.join(lines)
def indent_body(self, body):
# Don't indent the body of this method, as it's all preprocessor gunk.
return body
def definition_body(self):
return "\n" + self._defineMacro() + "\n" + self._registerProtos() + "\n" + self._undefineMacro()
define = ""
currentOffset = 0
for (name, desc) in getGlobalNames(self.config):
length = len(name)
define += "WebIDLGlobalNameHash::Register(%i, %i, %sBinding::DefineDOMInterface, %s);\n" % (currentOffset, length, desc.name, getCheck(desc))
currentOffset += length + 1 # Add trailing null.
return define
def dependencySortObjects(objects, dependencyGetter, nameGetter):
@ -16194,7 +16199,7 @@ class GlobalGenRoots():
@staticmethod
def RegisterBindings(config):
curr = CGRegisterProtos(config)
curr = CGList([CGGlobalNamesString(config), CGRegisterGlobalNames(config)])
# Wrap all of that in our namespaces.
curr = CGNamespace.build(['mozilla', 'dom'],
@ -16207,7 +16212,7 @@ class GlobalGenRoots():
workers=False,
isExposedInWindow=True,
register=True)]
defineIncludes.append('nsScriptNameSpaceManager.h')
defineIncludes.append('mozilla/dom/WebIDLGlobalNameHash.h')
defineIncludes.extend([CGHeaders.getDeclarationFilename(desc.interface)
for desc in config.getDescriptors(isNavigatorProperty=True,
workers=False,

View File

@ -824,6 +824,14 @@ class Descriptor(DescriptorProvider):
iface = iface.parent
return True
@property
def registersGlobalNamesOnWindow(self):
return (not self.interface.isExternal() and
self.interface.hasInterfaceObject() and
not self.workers and
self.interface.isExposedInWindow() and
self.register)
# Some utility methods
def getTypesFromDescriptor(descriptor):

View File

@ -0,0 +1,313 @@
/* -*- 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 "WebIDLGlobalNameHash.h"
#include "js/GCAPI.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/Maybe.h"
#include "mozilla/dom/DOMJSProxyHandler.h"
#include "mozilla/dom/RegisterBindings.h"
#include "nsIMemoryReporter.h"
#include "nsTHashtable.h"
namespace mozilla {
namespace dom {
struct MOZ_STACK_CLASS WebIDLNameTableKey
{
explicit WebIDLNameTableKey(JSFlatString* aJSString)
: mLength(js::GetFlatStringLength(aJSString))
{
mNogc.emplace();
JSLinearString* jsString = js::FlatStringToLinearString(aJSString);
if (js::LinearStringHasLatin1Chars(jsString)) {
mLatin1String = reinterpret_cast<const char*>(
js::GetLatin1LinearStringChars(*mNogc, jsString));
mTwoBytesString = nullptr;
mHash = mLatin1String ? HashString(mLatin1String, mLength) : 0;
} else {
mLatin1String = nullptr;
mTwoBytesString = js::GetTwoByteLinearStringChars(*mNogc, jsString);
mHash = mTwoBytesString ? HashString(mTwoBytesString, mLength) : 0;
}
}
explicit WebIDLNameTableKey(const char* aString, size_t aLength)
: mLatin1String(aString),
mTwoBytesString(nullptr),
mLength(aLength),
mHash(HashString(aString, aLength))
{
MOZ_ASSERT(aString[aLength] == '\0');
}
Maybe<JS::AutoCheckCannotGC> mNogc;
const char* mLatin1String;
const char16_t* mTwoBytesString;
size_t mLength;
uint32_t mHash;
};
struct WebIDLNameTableEntry : public PLDHashEntryHdr
{
typedef const WebIDLNameTableKey& KeyType;
typedef const WebIDLNameTableKey* KeyTypePointer;
explicit WebIDLNameTableEntry(KeyTypePointer aKey)
{}
WebIDLNameTableEntry(WebIDLNameTableEntry&& aEntry)
: mNameOffset(aEntry.mNameOffset),
mNameLength(aEntry.mNameLength),
mDefine(aEntry.mDefine),
mEnabled(aEntry.mEnabled)
{}
~WebIDLNameTableEntry()
{}
bool KeyEquals(KeyTypePointer aKey) const
{
if (mNameLength != aKey->mLength) {
return false;
}
const char* name = WebIDLGlobalNameHash::sNames + mNameOffset;
if (aKey->mLatin1String) {
return PodEqual(aKey->mLatin1String, name, aKey->mLength);
}
return nsCharTraits<char16_t>::compareASCII(aKey->mTwoBytesString, name,
aKey->mLength) == 0;
}
static KeyTypePointer KeyToPointer(KeyType aKey)
{
return &aKey;
}
static PLDHashNumber HashKey(KeyTypePointer aKey)
{
return aKey->mHash;
}
enum { ALLOW_MEMMOVE = true };
uint16_t mNameOffset;
uint16_t mNameLength;
WebIDLGlobalNameHash::DefineGlobalName mDefine;
// May be null if enabled unconditionally
WebIDLGlobalNameHash::ConstructorEnabled* mEnabled;
};
static nsTHashtable<WebIDLNameTableEntry>* sWebIDLGlobalNames;
class WebIDLGlobalNamesHashReporter final : public nsIMemoryReporter
{
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
~WebIDLGlobalNamesHashReporter() {}
public:
NS_DECL_ISUPPORTS
NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) override
{
int64_t amount =
sWebIDLGlobalNames ?
sWebIDLGlobalNames->ShallowSizeOfIncludingThis(MallocSizeOf) : 0;
return MOZ_COLLECT_REPORT("explicit/dom/webidl-globalnames", KIND_HEAP,
UNITS_BYTES, amount,
"Memory used by the hash table for WebIDL's "
"global names.");
}
};
NS_IMPL_ISUPPORTS(WebIDLGlobalNamesHashReporter, nsIMemoryReporter)
/* static */
void
WebIDLGlobalNameHash::Init()
{
sWebIDLGlobalNames = new nsTHashtable<WebIDLNameTableEntry>(sCount);
RegisterWebIDLGlobalNames();
RegisterStrongMemoryReporter(new WebIDLGlobalNamesHashReporter());
}
/* static */
void
WebIDLGlobalNameHash::Shutdown()
{
delete sWebIDLGlobalNames;
}
/* static */
void
WebIDLGlobalNameHash::Register(uint16_t aNameOffset, uint16_t aNameLength,
DefineGlobalName aDefine,
ConstructorEnabled* aEnabled)
{
const char* name = sNames + aNameOffset;
WebIDLNameTableKey key(name, aNameLength);
WebIDLNameTableEntry* entry = sWebIDLGlobalNames->PutEntry(key);
entry->mNameOffset = aNameOffset;
entry->mNameLength = aNameLength;
entry->mDefine = aDefine;
entry->mEnabled = aEnabled;
}
/* static */
void
WebIDLGlobalNameHash::Remove(const char* aName, uint32_t aLength)
{
WebIDLNameTableKey key(aName, aLength);
sWebIDLGlobalNames->RemoveEntry(key);
}
/* static */
bool
WebIDLGlobalNameHash::DefineIfEnabled(JSContext* aCx,
JS::Handle<JSObject*> aObj,
JS::Handle<jsid> aId,
JS::MutableHandle<JS::PropertyDescriptor> aDesc,
bool* aFound)
{
MOZ_ASSERT(JSID_IS_STRING(aId), "Check for string id before calling this!");
const WebIDLNameTableEntry* entry;
{
WebIDLNameTableKey key(JSID_TO_FLAT_STRING(aId));
// Rooting analysis thinks nsTHashtable<...>::GetEntry may GC because it
// ends up calling through PLDHashTableOps' matchEntry function pointer, but
// we know WebIDLNameTableEntry::KeyEquals can't cause a GC.
JS::AutoSuppressGCAnalysis suppress;
entry = sWebIDLGlobalNames->GetEntry(key);
}
if (!entry) {
*aFound = false;
return true;
}
*aFound = true;
ConstructorEnabled* checkEnabledForScope = entry->mEnabled;
// We do the enabled check on the current compartment of aCx, but for the
// actual object we pass in the underlying object in the Xray case. That
// way the callee can decide whether to allow access based on the caller
// or the window being touched.
JS::Rooted<JSObject*> global(aCx,
js::CheckedUnwrap(aObj, /* stopAtWindowProxy = */ false));
if (!global) {
return Throw(aCx, NS_ERROR_DOM_SECURITY_ERR);
}
{
DebugOnly<nsGlobalWindow*> win;
MOZ_ASSERT(NS_SUCCEEDED(UNWRAP_OBJECT(Window, global, win)));
}
if (checkEnabledForScope && !checkEnabledForScope(aCx, global)) {
return true;
}
// The DOM constructor resolve machinery interacts with Xrays in tricky
// ways, and there are some asymmetries that are important to understand.
//
// In the regular (non-Xray) case, we only want to resolve constructors
// once (so that if they're deleted, they don't reappear). We do this by
// stashing the constructor in a slot on the global, such that we can see
// during resolve whether we've created it already. This is rather
// memory-intensive, so we don't try to maintain these semantics when
// manipulating a global over Xray (so the properties just re-resolve if
// they've been deleted).
//
// Unfortunately, there's a bit of an impedance-mismatch between the Xray
// and non-Xray machinery. The Xray machinery wants an API that returns a
// JS::PropertyDescriptor, so that the resolve hook doesn't have to get
// snared up with trying to define a property on the Xray holder. At the
// same time, the DefineInterface callbacks are set up to define things
// directly on the global. And re-jiggering them to return property
// descriptors is tricky, because some DefineInterface callbacks define
// multiple things (like the Image() alias for HTMLImageElement).
//
// So the setup is as-follows:
//
// * The resolve function takes a JS::PropertyDescriptor, but in the
// non-Xray case, callees may define things directly on the global, and
// set the value on the property descriptor to |undefined| to indicate
// that there's nothing more for the caller to do. We assert against
// this behavior in the Xray case.
//
// * We make sure that we do a non-Xray resolve first, so that all the
// slots are set up. In the Xray case, this means unwrapping and doing
// a non-Xray resolve before doing the Xray resolve.
//
// This all could use some grand refactoring, but for now we just limp
// along.
if (xpc::WrapperFactory::IsXrayWrapper(aObj)) {
JS::Rooted<JSObject*> interfaceObject(aCx);
{
JSAutoCompartment ac(aCx, global);
interfaceObject = entry->mDefine(aCx, global, aId, false);
}
if (NS_WARN_IF(!interfaceObject)) {
return Throw(aCx, NS_ERROR_FAILURE);
}
if (!JS_WrapObject(aCx, &interfaceObject)) {
return Throw(aCx, NS_ERROR_FAILURE);
}
FillPropertyDescriptor(aDesc, aObj, 0, JS::ObjectValue(*interfaceObject));
return true;
}
JS::Rooted<JSObject*> interfaceObject(aCx,
entry->mDefine(aCx, aObj, aId, true));
if (NS_WARN_IF(!interfaceObject)) {
return Throw(aCx, NS_ERROR_FAILURE);
}
// We've already defined the property. We indicate this to the caller
// by filling a property descriptor with JS::UndefinedValue() as the
// value. We still have to fill in a property descriptor, though, so
// that the caller knows the property is in fact on this object. It
// doesn't matter what we pass for the "readonly" argument here.
FillPropertyDescriptor(aDesc, aObj, JS::UndefinedValue(), false);
return true;
}
/* static */
bool
WebIDLGlobalNameHash::MayResolve(jsid aId)
{
WebIDLNameTableKey key(JSID_TO_FLAT_STRING(aId));
// Rooting analysis thinks nsTHashtable<...>::Contains may GC because it ends
// up calling through PLDHashTableOps' matchEntry function pointer, but we
// know WebIDLNameTableEntry::KeyEquals can't cause a GC.
JS::AutoSuppressGCAnalysis suppress;
return sWebIDLGlobalNames->Contains(key);
}
/* static */
void
WebIDLGlobalNameHash::GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj,
nsTArray<nsString>& aNames)
{
for (auto iter = sWebIDLGlobalNames->Iter(); !iter.Done(); iter.Next()) {
const WebIDLNameTableEntry* entry = iter.Get();
if (!entry->mEnabled || entry->mEnabled(aCx, aObj)) {
AppendASCIItoUTF16(nsDependentCString(sNames + entry->mNameOffset,
entry->mNameLength),
*aNames.AppendElement());
}
}
}
} // namespace dom
} // namespace mozilla

View File

@ -0,0 +1,70 @@
/* -*- 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_WebIDLGlobalNameHash_h__
#define mozilla_dom_WebIDLGlobalNameHash_h__
#include "js/RootingAPI.h"
#include "nsTArray.h"
namespace mozilla {
namespace dom {
struct WebIDLNameTableEntry;
class WebIDLGlobalNameHash
{
public:
static void Init();
static void Shutdown();
typedef JSObject*
(*DefineGlobalName)(JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<jsid> id, bool defineOnGlobal);
// Check whether a constructor should be enabled for the given object.
// Note that the object should NOT be an Xray, since Xrays will end up
// defining constructors on the underlying object.
// This is a typedef for the function type itself, not the function
// pointer, so it's more obvious that pointers to a ConstructorEnabled
// can be null.
typedef bool
(ConstructorEnabled)(JSContext* cx, JS::Handle<JSObject*> obj);
static void Register(uint16_t aNameOffset, uint16_t aNameLength,
DefineGlobalName aDefine, ConstructorEnabled* aEnabled);
static void Remove(const char* aName, uint32_t aLength);
// Returns false if something failed. aFound is set to true if the name is in
// the hash, whether it's enabled or not.
static bool DefineIfEnabled(JSContext* aCx, JS::Handle<JSObject*> aObj,
JS::Handle<jsid> aId,
JS::MutableHandle<JS::PropertyDescriptor> aDesc,
bool* aFound);
static bool MayResolve(jsid aId);
static void GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj,
nsTArray<nsString>& aNames);
private:
friend struct WebIDLNameTableEntry;
// The total number of names that we will add to the hash.
// The value of sCount is generated by Codegen.py in RegisterBindings.cpp.
static const uint32_t sCount;
// The names that will be registered in the hash, concatenated as one big
// string with \0 as a separator between names.
// The value of sNames is generated by Codegen.py in RegisterBindings.cpp.
static const char sNames[];
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_WebIDLGlobalNameHash_h__

View File

@ -39,6 +39,7 @@ EXPORTS.mozilla.dom += [
'ToJSValue.h',
'TypedArray.h',
'UnionMember.h',
'WebIDLGlobalNameHash.h',
]
# Generated bindings reference *Binding.h, not mozilla/dom/*Binding.h. And,
@ -89,6 +90,7 @@ UNIFIED_SOURCES += [
'IterableIterator.cpp',
'SimpleGlobalObject.cpp',
'ToJSValue.cpp',
'WebIDLGlobalNameHash.cpp',
]
SOURCES += [

View File

@ -79,7 +79,7 @@ union CacheRequestOrVoid
struct CacheResponse
{
ResponseType type;
nsCString url;
nsCString[] urlList;
uint32_t status;
nsCString statusText;
HeadersEntry[] headers;

245
dom/cache/DBSchema.cpp vendored
View File

@ -37,7 +37,7 @@ const int32_t kFirstShippedSchemaVersion = 15;
namespace {
// Update this whenever the DB schema is changed.
const int32_t kLatestSchemaVersion = 20;
const int32_t kLatestSchemaVersion = 21;
// ---------
// The following constants define the SQL schema. These are defined in the
@ -95,7 +95,6 @@ const char* const kTableEntries =
"request_cache INTEGER NOT NULL, "
"request_body_id TEXT NULL, "
"response_type INTEGER NOT NULL, "
"response_url TEXT NOT NULL, "
"response_status INTEGER NOT NULL, "
"response_status_text TEXT NOT NULL, "
"response_headers_guard INTEGER NOT NULL, "
@ -143,6 +142,12 @@ const char* const kIndexResponseHeadersName =
"CREATE INDEX response_headers_name_index "
"ON response_headers (name)";
const char* const kTableResponseUrlList =
"CREATE TABLE response_url_list ("
"url TEXT NOT NULL, "
"entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
")";
// NOTE: key allows NULL below since that is how "" is represented
// in a BLOB column. We use BLOB to avoid encoding issues
// with storing DOMStrings.
@ -458,6 +463,9 @@ CreateOrMigrateSchema(mozIStorageConnection* aConn)
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexResponseHeadersName));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableResponseUrlList));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableStorage));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
@ -1653,7 +1661,6 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
"request_redirect, "
"request_body_id, "
"response_type, "
"response_url, "
"response_status, "
"response_status_text, "
"response_headers_guard, "
@ -1677,7 +1684,6 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
":request_redirect, "
":request_body_id, "
":response_type, "
":response_url, "
":response_status, "
":response_status_text, "
":response_headers_guard, "
@ -1755,10 +1761,6 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
static_cast<int32_t>(aResponse.type()));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("response_url"),
aResponse.url());
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = state->BindInt32ByName(NS_LITERAL_CSTRING("response_status"),
aResponse.status());
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
@ -1873,6 +1875,27 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
}
rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
"INSERT INTO response_url_list ("
"url, "
"entry_id "
") VALUES (:url, :entry_id)"
), getter_AddRefs(state));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
const nsTArray<nsCString>& responseUrlList = aResponse.urlList();
for (uint32_t i = 0; i < responseUrlList.Length(); ++i) {
rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("url"),
responseUrlList[i]);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = state->BindInt64ByName(NS_LITERAL_CSTRING("entry_id"), entryId);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = state->Execute();
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
}
return rv;
}
@ -1888,7 +1911,6 @@ ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId,
nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT "
"entries.response_type, "
"entries.response_url, "
"entries.response_status, "
"entries.response_status_text, "
"entries.response_headers_guard, "
@ -1914,35 +1936,32 @@ ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId,
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
aSavedResponseOut->mValue.type() = static_cast<ResponseType>(type);
rv = state->GetUTF8String(1, aSavedResponseOut->mValue.url());
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
int32_t status;
rv = state->GetInt32(2, &status);
rv = state->GetInt32(1, &status);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
aSavedResponseOut->mValue.status() = status;
rv = state->GetUTF8String(3, aSavedResponseOut->mValue.statusText());
rv = state->GetUTF8String(2, aSavedResponseOut->mValue.statusText());
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
int32_t guard;
rv = state->GetInt32(4, &guard);
rv = state->GetInt32(3, &guard);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
aSavedResponseOut->mValue.headersGuard() =
static_cast<HeadersGuardEnum>(guard);
bool nullBody = false;
rv = state->GetIsNull(5, &nullBody);
rv = state->GetIsNull(4, &nullBody);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
aSavedResponseOut->mHasBodyId = !nullBody;
if (aSavedResponseOut->mHasBodyId) {
rv = ExtractId(state, 5, &aSavedResponseOut->mBodyId);
rv = ExtractId(state, 4, &aSavedResponseOut->mBodyId);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
}
nsAutoCString serializedInfo;
rv = state->GetUTF8String(6, serializedInfo);
rv = state->GetUTF8String(5, serializedInfo);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
aSavedResponseOut->mValue.principalInfo() = void_t();
@ -1958,7 +1977,7 @@ ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId,
mozilla::ipc::ContentPrincipalInfo(attrs, originNoSuffix);
}
rv = state->GetBlobAsUTF8String(7, aSavedResponseOut->mValue.channelInfo().securityInfo());
rv = state->GetBlobAsUTF8String(6, aSavedResponseOut->mValue.channelInfo().securityInfo());
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
@ -1985,6 +2004,26 @@ ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId,
aSavedResponseOut->mValue.headers().AppendElement(header);
}
rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT "
"url "
"FROM response_url_list "
"WHERE entry_id=:entry_id;"
), getter_AddRefs(state));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = state->BindInt32ByName(NS_LITERAL_CSTRING("entry_id"), aEntryId);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
nsCString url;
rv = state->GetUTF8String(0, url);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
aSavedResponseOut->mValue.urlList().AppendElement(url);
}
return rv;
}
@ -2357,6 +2396,7 @@ Validate(mozIStorageConnection* aConn)
Expect("request_headers", "table", kTableRequestHeaders),
Expect("response_headers", "table", kTableResponseHeaders),
Expect("response_headers_name_index", "index", kIndexResponseHeadersName),
Expect("response_url_list", "table", kTableResponseUrlList),
Expect("storage", "table", kTableStorage),
Expect("sqlite_autoindex_storage_1", "index"), // auto-gen by sqlite
};
@ -2436,6 +2476,7 @@ nsresult MigrateFrom16To17(mozIStorageConnection* aConn, bool& aRewriteSchema);
nsresult MigrateFrom17To18(mozIStorageConnection* aConn, bool& aRewriteSchema);
nsresult MigrateFrom18To19(mozIStorageConnection* aConn, bool& aRewriteSchema);
nsresult MigrateFrom19To20(mozIStorageConnection* aConn, bool& aRewriteSchema);
nsresult MigrateFrom20To21(mozIStorageConnection* aConn, bool& aRewriteSchema);
// Configure migration functions to run for the given starting version.
Migration sMigrationList[] = {
@ -2444,6 +2485,7 @@ Migration sMigrationList[] = {
Migration(17, MigrateFrom17To18),
Migration(18, MigrateFrom18To19),
Migration(19, MigrateFrom19To20),
Migration(20, MigrateFrom20To21),
};
uint32_t sMigrationListLength = sizeof(sMigrationList) / sizeof(Migration);
@ -2530,9 +2572,6 @@ nsresult MigrateFrom15To16(mozIStorageConnection* aConn, bool& aRewriteSchema)
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aConn);
mozStorageTransaction trans(aConn, true,
mozIStorageConnection::TRANSACTION_IMMEDIATE);
// Add the request_redirect column with a default value of "follow". Note,
// we only use a default value here because its required by ALTER TABLE and
// we need to apply the default "follow" to existing records in the table.
@ -2690,9 +2729,6 @@ MigrateFrom17To18(mozIStorageConnection* aConn, bool& aRewriteSchema)
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aConn);
mozStorageTransaction trans(aConn, true,
mozIStorageConnection::TRANSACTION_IMMEDIATE);
// This migration is needed in order to remove "only-if-cached" RequestCache
// values from the database. This enum value was removed from the spec in
// https://github.com/whatwg/fetch/issues/39 but we unfortunately happily
@ -2720,9 +2756,6 @@ MigrateFrom18To19(mozIStorageConnection* aConn, bool& aRewriteSchema)
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aConn);
mozStorageTransaction trans(aConn, true,
mozIStorageConnection::TRANSACTION_IMMEDIATE);
// This migration is needed in order to update the RequestMode values for
// Request objects corresponding to a navigation content policy type to
// "navigate".
@ -2751,9 +2784,6 @@ nsresult MigrateFrom19To20(mozIStorageConnection* aConn, bool& aRewriteSchema)
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aConn);
mozStorageTransaction trans(aConn, true,
mozIStorageConnection::TRANSACTION_IMMEDIATE);
// Add the request_referrer_policy column with a default value of
// "no-referrer-when-downgrade". Note, we only use a default value here
// because its required by ALTER TABLE and we need to apply the default
@ -2773,6 +2803,159 @@ nsresult MigrateFrom19To20(mozIStorageConnection* aConn, bool& aRewriteSchema)
return rv;
}
nsresult MigrateFrom20To21(mozIStorageConnection* aConn, bool& aRewriteSchema)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aConn);
// This migration creates response_url_list table to store response_url and
// removes the response_url column from the entries table.
// sqlite doesn't support removing a column from a table using ALTER TABLE,
// so we need to create a new table without those columns, fill it up with the
// existing data, and then drop the original table and rename the new one to
// the old one.
// Create a new_entries table with the new fields as of version 21.
nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE new_entries ("
"id INTEGER NOT NULL PRIMARY KEY, "
"request_method TEXT NOT NULL, "
"request_url_no_query TEXT NOT NULL, "
"request_url_no_query_hash BLOB NOT NULL, "
"request_url_query TEXT NOT NULL, "
"request_url_query_hash BLOB NOT NULL, "
"request_referrer TEXT NOT NULL, "
"request_headers_guard INTEGER NOT NULL, "
"request_mode INTEGER NOT NULL, "
"request_credentials INTEGER NOT NULL, "
"request_contentpolicytype INTEGER NOT NULL, "
"request_cache INTEGER NOT NULL, "
"request_body_id TEXT NULL, "
"response_type INTEGER NOT NULL, "
"response_status INTEGER NOT NULL, "
"response_status_text TEXT NOT NULL, "
"response_headers_guard INTEGER NOT NULL, "
"response_body_id TEXT NULL, "
"response_security_info_id INTEGER NULL REFERENCES security_info(id), "
"response_principal_info TEXT NOT NULL, "
"cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, "
"request_redirect INTEGER NOT NULL, "
"request_referrer_policy INTEGER NOT NULL"
")"
));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Create a response_url_list table with the new fields as of version 21.
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE response_url_list ("
"url TEXT NOT NULL, "
"entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
")"
));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Copy all of the data to the newly created entries table.
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"INSERT INTO new_entries ("
"id, "
"request_method, "
"request_url_no_query, "
"request_url_no_query_hash, "
"request_url_query, "
"request_url_query_hash, "
"request_referrer, "
"request_headers_guard, "
"request_mode, "
"request_credentials, "
"request_contentpolicytype, "
"request_cache, "
"request_redirect, "
"request_referrer_policy, "
"request_body_id, "
"response_type, "
"response_status, "
"response_status_text, "
"response_headers_guard, "
"response_body_id, "
"response_security_info_id, "
"response_principal_info, "
"cache_id "
") SELECT "
"id, "
"request_method, "
"request_url_no_query, "
"request_url_no_query_hash, "
"request_url_query, "
"request_url_query_hash, "
"request_referrer, "
"request_headers_guard, "
"request_mode, "
"request_credentials, "
"request_contentpolicytype, "
"request_cache, "
"request_redirect, "
"request_referrer_policy, "
"request_body_id, "
"response_type, "
"response_status, "
"response_status_text, "
"response_headers_guard, "
"response_body_id, "
"response_security_info_id, "
"response_principal_info, "
"cache_id "
"FROM entries;"
));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Copy reponse_url to the newly created response_url_list table.
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"INSERT INTO response_url_list ("
"url, "
"entry_id "
") SELECT "
"response_url, "
"id "
"FROM entries;"
));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Remove the old table.
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TABLE entries;"
));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Rename new_entries to entries.
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE new_entries RENAME to entries;"
));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Now, recreate our indices.
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexEntriesRequest));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Revalidate the foreign key constraints, and ensure that there are no
// violations.
nsCOMPtr<mozIStorageStatement> state;
rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
"PRAGMA foreign_key_check;"
), getter_AddRefs(state));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
bool hasMoreData = false;
rv = state->ExecuteStep(&hasMoreData);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (NS_WARN_IF(hasMoreData)) { return NS_ERROR_FAILURE; }
rv = aConn->SetSchemaVersion(21);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
aRewriteSchema = true;
return rv;
}
} // anonymous namespace

View File

@ -203,15 +203,15 @@ TypeUtils::ToCacheResponseWithoutBody(CacheResponse& aOut,
{
aOut.type() = aIn.Type();
aIn.GetUnfilteredUrl(aOut.url());
aIn.GetUnfilteredURLList(aOut.urlList());
AutoTArray<nsCString, 4> urlList;
aIn.GetURLList(urlList);
if (aOut.url() != EmptyCString()) {
for (uint32_t i = 0; i < aOut.urlList().Length(); i++) {
MOZ_ASSERT(!aOut.urlList()[i].IsEmpty());
// Pass all Response URL schemes through... The spec only requires we take
// action on invalid schemes for Request objects.
ProcessURL(aOut.url(), nullptr, nullptr, nullptr, aRv);
if (aRv.Failed()) {
return;
}
ProcessURL(aOut.urlList()[i], nullptr, nullptr, nullptr, aRv);
}
aOut.status() = aIn.GetUnfilteredStatus();
@ -285,7 +285,7 @@ TypeUtils::ToResponse(const CacheResponse& aIn)
RefPtr<InternalResponse> ir = new InternalResponse(aIn.status(),
aIn.statusText());
ir->SetUrl(aIn.url());
ir->SetURLList(aIn.urlList());
RefPtr<InternalHeaders> internalHeaders =
ToInternalHeaders(aIn.headers(), aIn.headersGuard());
@ -332,13 +332,12 @@ TypeUtils::ToResponse(const CacheResponse& aIn)
already_AddRefed<InternalRequest>
TypeUtils::ToInternalRequest(const CacheRequest& aIn)
{
RefPtr<InternalRequest> internalRequest = new InternalRequest();
internalRequest->SetMethod(aIn.method());
nsAutoCString url(aIn.urlWithoutQuery());
url.Append(aIn.urlQuery());
internalRequest->SetURL(url);
RefPtr<InternalRequest> internalRequest = new InternalRequest(url);
internalRequest->SetMethod(aIn.method());
internalRequest->SetReferrer(aIn.referrer());
internalRequest->SetReferrerPolicy(aIn.referrerPolicy());

View File

@ -31,6 +31,12 @@ function run_test() {
ok(responseList.length > 0, 'should have at least one response in cache');
responseList.forEach(function(response) {
ok(response, 'each response in list should be non-null');
// reponse.url is a empty string in current test file. It should test for
// not being a empty string once thet test file is updated.
ok(typeof response.url === 'string', 'each response.url in list should be a string');
// reponse.redirected may be changed once test file is updated. It should
// be false since current reponse.url is a empty string.
ok(response.redirected === false, 'each response.redirected in list should be false');
do_check_eq(response.headers.get('Content-Type'), 'text/plain;charset=UTF-8',
'the response should have the correct header');
});

View File

@ -18,6 +18,7 @@
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/DragEvent.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/dom/TabParent.h"
#include "mozilla/dom/UIEvent.h"
@ -2010,34 +2011,34 @@ EventStateManager::GetContentViewer(nsIContentViewer** aCv)
{
*aCv = nullptr;
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if(!fm) return NS_ERROR_FAILURE;
nsCOMPtr<nsPIDOMWindowOuter> rootWindow;
rootWindow = mDocument->GetWindow()->GetPrivateRoot();
if (!rootWindow) return NS_ERROR_FAILURE;
nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
if (!focusedWindow) return NS_ERROR_FAILURE;
TabChild* tabChild = TabChild::GetFrom(rootWindow);
if (!tabChild) {
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (!fm) return NS_ERROR_FAILURE;
auto* ourWindow = nsPIDOMWindowOuter::From(focusedWindow);
nsCOMPtr<nsPIDOMWindowOuter> rootWindow = ourWindow->GetPrivateRoot();
if(!rootWindow) return NS_ERROR_FAILURE;
nsCOMPtr<mozIDOMWindowProxy> activeWindow;
fm->GetActiveWindow(getter_AddRefs(activeWindow));
if (rootWindow != activeWindow) return NS_OK;
} else {
if (!tabChild->ParentIsActive()) return NS_OK;
}
nsCOMPtr<nsPIDOMWindowOuter> contentWindow = nsGlobalWindow::Cast(rootWindow)->GetContent();
if(!contentWindow) return NS_ERROR_FAILURE;
if (!contentWindow) return NS_ERROR_FAILURE;
nsIDocument *doc = contentWindow->GetDoc();
if(!doc) return NS_ERROR_FAILURE;
if (!doc) return NS_ERROR_FAILURE;
nsIPresShell *presShell = doc->GetShell();
if(!presShell) return NS_ERROR_FAILURE;
nsPresContext *presContext = presShell->GetPresContext();
if(!presContext) return NS_ERROR_FAILURE;
nsCOMPtr<nsIDocShell> docshell(presContext->GetDocShell());
if(!docshell) return NS_ERROR_FAILURE;
nsCOMPtr<nsISupports> container = doc->GetContainer();
if (!container) return NS_ERROR_FAILURE;
nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(container);
docshell->GetContentViewer(aCv);
if(!*aCv) return NS_ERROR_FAILURE;
if (!*aCv) return NS_ERROR_FAILURE;
return NS_OK;
}
@ -2049,16 +2050,18 @@ EventStateManager::ChangeTextSize(int32_t change)
nsresult rv = GetContentViewer(getter_AddRefs(cv));
NS_ENSURE_SUCCESS(rv, rv);
float textzoom;
float zoomMin = ((float)Preferences::GetInt("zoom.minPercent", 50)) / 100;
float zoomMax = ((float)Preferences::GetInt("zoom.maxPercent", 300)) / 100;
cv->GetTextZoom(&textzoom);
textzoom += ((float)change) / 10;
if (textzoom < zoomMin)
textzoom = zoomMin;
else if (textzoom > zoomMax)
textzoom = zoomMax;
cv->SetTextZoom(textzoom);
if (cv) {
float textzoom;
float zoomMin = ((float)Preferences::GetInt("zoom.minPercent", 50)) / 100;
float zoomMax = ((float)Preferences::GetInt("zoom.maxPercent", 300)) / 100;
cv->GetTextZoom(&textzoom);
textzoom += ((float)change) / 10;
if (textzoom < zoomMin)
textzoom = zoomMin;
else if (textzoom > zoomMax)
textzoom = zoomMax;
cv->SetTextZoom(textzoom);
}
return NS_OK;
}
@ -2070,16 +2073,18 @@ EventStateManager::ChangeFullZoom(int32_t change)
nsresult rv = GetContentViewer(getter_AddRefs(cv));
NS_ENSURE_SUCCESS(rv, rv);
float fullzoom;
float zoomMin = ((float)Preferences::GetInt("zoom.minPercent", 50)) / 100;
float zoomMax = ((float)Preferences::GetInt("zoom.maxPercent", 300)) / 100;
cv->GetFullZoom(&fullzoom);
fullzoom += ((float)change) / 10;
if (fullzoom < zoomMin)
fullzoom = zoomMin;
else if (fullzoom > zoomMax)
fullzoom = zoomMax;
cv->SetFullZoom(fullzoom);
if (cv) {
float fullzoom;
float zoomMin = ((float)Preferences::GetInt("zoom.minPercent", 50)) / 100;
float zoomMax = ((float)Preferences::GetInt("zoom.maxPercent", 300)) / 100;
cv->GetFullZoom(&fullzoom);
fullzoom += ((float)change) / 10;
if (fullzoom < zoomMin)
fullzoom = zoomMin;
else if (fullzoom > zoomMax)
fullzoom = zoomMax;
cv->SetFullZoom(fullzoom);
}
return NS_OK;
}
@ -2113,12 +2118,12 @@ EventStateManager::DoScrollZoom(nsIFrame* aTargetFrame,
// positive adjustment to decrease zoom, negative to increase
int32_t change = (adjustment > 0) ? -1 : 1;
EnsureDocument(mPresContext);
if (Preferences::GetBool("browser.zoom.full") || content->OwnerDoc()->IsSyntheticDocument()) {
ChangeFullZoom(change);
} else {
ChangeTextSize(change);
}
EnsureDocument(mPresContext);
nsContentUtils::DispatchChromeEvent(mDocument, static_cast<nsIDocument*>(mDocument),
NS_LITERAL_STRING("ZoomChangeUsingMouseWheel"),
true, true);

View File

@ -95,7 +95,7 @@ FetchDriver::ContinueFetch()
if (NS_FAILED(rv)) {
FailWithNetworkError();
}
return rv;
}
@ -373,18 +373,15 @@ FetchDriver::HttpFetch()
already_AddRefed<InternalResponse>
FetchDriver::BeginAndGetFilteredResponse(InternalResponse* aResponse,
nsIURI* aFinalURI,
bool aFoundOpaqueRedirect)
{
MOZ_ASSERT(aResponse);
nsAutoCString reqURL;
if (aFinalURI) {
aFinalURI->GetSpec(reqURL);
} else {
mRequest->GetURL(reqURL);
}
DebugOnly<nsresult> rv = aResponse->StripFragmentAndSetUrl(reqURL);
MOZ_ASSERT(NS_SUCCEEDED(rv));
AutoTArray<nsCString, 4> reqURLList;
mRequest->GetURLList(reqURLList);
MOZ_ASSERT(!reqURLList.IsEmpty());
aResponse->SetURLList(reqURLList);
RefPtr<InternalResponse> filteredResponse;
if (aFoundOpaqueRedirect) {
@ -489,20 +486,6 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest,
// On a successful redirect we perform the following substeps of HTTP Fetch,
// step 5, "redirect status", step 11.
// Step 11.5 "Append locationURL to request's url list." so that when we set the
// Response's URL from the Request's URL in Main Fetch, step 15, we get the
// final value. Note, we still use a single URL value instead of a list.
// Because of that we only need to do this after the request finishes.
nsCOMPtr<nsIURI> newURI;
rv = NS_GetFinalChannelURI(channel, getter_AddRefs(newURI));
if (NS_FAILED(rv)) {
FailWithNetworkError();
return rv;
}
nsAutoCString newUrl;
newURI->GetSpec(newUrl);
mRequest->SetURL(newUrl);
bool foundOpaqueRedirect = false;
if (httpChannel) {
@ -607,8 +590,7 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest,
// Resolves fetch() promise which may trigger code running in a worker. Make
// sure the Response is fully initialized before calling this.
mResponse = BeginAndGetFilteredResponse(response, channelURI,
foundOpaqueRedirect);
mResponse = BeginAndGetFilteredResponse(response, foundOpaqueRedirect);
nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -687,6 +669,24 @@ FetchDriver::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
SetRequestHeaders(httpChannel);
}
// "HTTP-redirect fetch": step 14 "Append locationURL to request's URL list."
nsCOMPtr<nsIURI> uri;
MOZ_ALWAYS_SUCCEEDS(aNewChannel->GetURI(getter_AddRefs(uri)));
nsCOMPtr<nsIURI> uriClone;
nsresult rv = uri->CloneIgnoringRef(getter_AddRefs(uriClone));
if(NS_WARN_IF(NS_FAILED(rv))){
return rv;
}
nsCString spec;
rv = uriClone->GetSpec(spec);
if(NS_WARN_IF(NS_FAILED(rv))){
return rv;
}
mRequest->AddURL(spec);
aCallback->OnRedirectVerifyCallback(NS_OK);
return NS_OK;
}

View File

@ -96,9 +96,8 @@ private:
nsresult ContinueFetch();
nsresult HttpFetch();
// Returns the filtered response sent to the observer.
// Callers who don't have access to a channel can pass null for aFinalURI.
already_AddRefed<InternalResponse>
BeginAndGetFilteredResponse(InternalResponse* aResponse, nsIURI* aFinalURI,
BeginAndGetFilteredResponse(InternalResponse* aResponse,
bool aFoundOpaqueRedirect);
// Utility since not all cases need to do any post processing of the filtered
// response.

View File

@ -23,8 +23,9 @@ namespace dom {
already_AddRefed<InternalRequest>
InternalRequest::GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult& aRv) const
{
RefPtr<InternalRequest> copy = new InternalRequest();
copy->mURL.Assign(mURL);
MOZ_RELEASE_ASSERT(!mURLList.IsEmpty(), "Internal Request's urlList should not be empty when copied from constructor.");
RefPtr<InternalRequest> copy = new InternalRequest(mURLList.LastElement());
copy->SetMethod(mMethod);
copy->mHeaders = new InternalHeaders(*mHeaders);
copy->SetUnsafeRequest();
@ -77,7 +78,7 @@ InternalRequest::Clone()
InternalRequest::InternalRequest(const InternalRequest& aOther)
: mMethod(aOther.mMethod)
, mURL(aOther.mURL)
, mURLList(aOther.mURLList)
, mHeaders(new InternalHeaders(*aOther.mHeaders))
, mContentPolicyType(aOther.mContentPolicyType)
, mReferrer(aOther.mReferrer)

View File

@ -88,7 +88,7 @@ class InternalRequest final
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalRequest)
InternalRequest()
explicit InternalRequest(const nsACString& aURL)
: mMethod("GET")
, mHeaders(new InternalHeaders(HeadersGuardEnum::None))
, mContentPolicyType(nsIContentPolicy::TYPE_FETCH)
@ -112,6 +112,8 @@ public:
, mUnsafeRequest(false)
, mUseURLCredentials(false)
{
MOZ_ASSERT(!aURL.IsEmpty());
AddURL(aURL);
}
InternalRequest(const nsACString& aURL,
@ -125,7 +127,6 @@ public:
ReferrerPolicy aReferrerPolicy,
nsContentPolicyType aContentPolicyType)
: mMethod(aMethod)
, mURL(aURL)
, mHeaders(aHeaders)
, mContentPolicyType(aContentPolicyType)
, mReferrer(aReferrer)
@ -145,12 +146,8 @@ public:
, mUnsafeRequest(false)
, mUseURLCredentials(false)
{
// Normally we strip the fragment from the URL in Request::Constructor.
// If internal code is directly constructing this object they must
// strip the fragment first. Since these should be well formed URLs we
// can use a simple check for a fragment here. The full parser is
// difficult to use off the main thread.
MOZ_ASSERT(mURL.Find(NS_LITERAL_CSTRING("#")) == kNotFound);
MOZ_ASSERT(!aURL.IsEmpty());
AddURL(aURL);
}
already_AddRefed<InternalRequest> Clone();
@ -175,16 +172,34 @@ public:
mMethod.LowerCaseEqualsASCII("head");
}
// GetURL should get the request's current url. A request has an associated
// current url. It is a pointer to the last fetch URL in request's url list.
void
GetURL(nsCString& aURL) const
GetURL(nsACString& aURL) const
{
aURL.Assign(mURL);
MOZ_RELEASE_ASSERT(!mURLList.IsEmpty(), "Internal Request's urlList should not be empty.");
aURL.Assign(mURLList.LastElement());
}
// AddURL should append the url into url list.
// Normally we strip the fragment from the URL in Request::Constructor.
// If internal code is directly constructing this object they must
// strip the fragment first. Since these should be well formed URLs we
// can use a simple check for a fragment here. The full parser is
// difficult to use off the main thread.
void
AddURL(const nsACString& aURL)
{
MOZ_ASSERT(!aURL.IsEmpty());
mURLList.AppendElement(aURL);
MOZ_ASSERT(mURLList.LastElement().Find(NS_LITERAL_CSTRING("#")) == kNotFound);
}
void
SetURL(const nsACString& aURL)
GetURLList(nsTArray<nsCString>& aURLList)
{
mURL.Assign(aURL);
aURLList.Assign(mURLList);
}
void
@ -459,8 +474,8 @@ private:
IsWorkerContentPolicy(nsContentPolicyType aContentPolicyType);
nsCString mMethod;
// mURL always stores the url with the ref stripped
nsCString mURL;
// mURLList: a list of one or more fetch URLs
nsTArray<nsCString> mURLList;
RefPtr<InternalHeaders> mHeaders;
nsCOMPtr<nsIInputStream> mBodyStream;

View File

@ -87,37 +87,6 @@ InternalResponse::SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrinc
mPrincipalInfo = Move(aPrincipalInfo);
}
nsresult
InternalResponse::StripFragmentAndSetUrl(const nsACString& aUrl)
{
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIURI> iuri;
nsresult rv;
rv = NS_NewURI(getter_AddRefs(iuri), aUrl);
if(NS_WARN_IF(NS_FAILED(rv))){
return rv;
}
nsCOMPtr<nsIURI> iuriClone;
// We use CloneIgnoringRef to strip away the fragment even if the original URI
// is immutable.
rv = iuri->CloneIgnoringRef(getter_AddRefs(iuriClone));
if(NS_WARN_IF(NS_FAILED(rv))){
return rv;
}
nsCString spec;
rv = iuriClone->GetSpec(spec);
if(NS_WARN_IF(NS_FAILED(rv))){
return rv;
}
SetUrl(spec);
return NS_OK;
}
LoadTainting
InternalResponse::GetTainting() const
{
@ -160,9 +129,10 @@ already_AddRefed<InternalResponse>
InternalResponse::OpaqueRedirectResponse()
{
MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueRedirectResponse a already wrapped response");
MOZ_ASSERT(!mURLList.IsEmpty(), "URLList should not be emtpy for internalResponse");
RefPtr<InternalResponse> response = OpaqueResponse();
response->mType = ResponseType::Opaqueredirect;
response->mURL = mURL;
response->mURLList = mURLList;
return response.forget();
}
@ -172,7 +142,7 @@ InternalResponse::CreateIncompleteCopy()
RefPtr<InternalResponse> copy = new InternalResponse(mStatus, mStatusText);
copy->mType = mType;
copy->mTerminationReason = mTerminationReason;
copy->mURL = mURL;
copy->mURLList = mURLList;
copy->mChannelInfo = mChannelInfo;
if (mPrincipalInfo) {
copy->mPrincipalInfo = MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo);

View File

@ -75,28 +75,56 @@ public:
return Type() == ResponseType::Error;
}
// FIXME(nsm): Return with exclude fragment.
// GetUrl should return last fetch URL in response's url list and null if
// response's url list is the empty list.
void
GetUrl(nsCString& aURL) const
GetURL(nsCString& aURL) const
{
aURL.Assign(mURL);
}
void
GetUnfilteredUrl(nsCString& aURL) const
{
if (mWrappedResponse) {
return mWrappedResponse->GetUrl(aURL);
// Empty urlList when response is a synthetic response.
if (mURLList.IsEmpty()) {
aURL.Truncate();
return;
}
return GetUrl(aURL);
aURL.Assign(mURLList.LastElement());
}
// SetUrl should only be called when the fragment has alredy been stripped
void
SetUrl(const nsACString& aURL)
GetURLList(nsTArray<nsCString>& aURLList) const
{
mURL.Assign(aURL);
aURLList.Assign(mURLList);
}
void
GetUnfilteredURL(nsCString& aURL) const
{
if (mWrappedResponse) {
return mWrappedResponse->GetURL(aURL);
}
return GetURL(aURL);
}
void
GetUnfilteredURLList(nsTArray<nsCString>& aURLList) const
{
if (mWrappedResponse) {
return mWrappedResponse->GetURLList(aURLList);
}
return GetURLList(aURLList);
}
void
SetURLList(const nsTArray<nsCString>& aURLList)
{
mURLList.Assign(aURLList);
#ifdef DEBUG
for(uint32_t i = 0; i < mURLList.Length(); ++i) {
MOZ_ASSERT(mURLList[i].Find(NS_LITERAL_CSTRING("#")) == kNotFound);
}
#endif
}
uint16_t
@ -211,13 +239,16 @@ public:
return mPrincipalInfo;
}
bool
IsRedirected() const
{
return mURLList.Length() > 1;
}
// Takes ownership of the principal info.
void
SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo);
nsresult
StripFragmentAndSetUrl(const nsACString& aUrl);
LoadTainting
GetTainting() const;
@ -237,7 +268,10 @@ private:
ResponseType mType;
nsCString mTerminationReason;
nsCString mURL;
// A response has an associated url list (a list of zero or more fetch URLs).
// Unless stated otherwise, it is the empty list. The current url is the last
// element in mURLlist
nsTArray<nsCString> mURLList;
const uint16_t mStatus;
const nsCString mStatusText;
RefPtr<InternalHeaders> mHeaders;

View File

@ -285,18 +285,8 @@ Request::Constructor(const GlobalObject& aGlobal,
request = inputReq->GetInternalRequest();
} else {
request = new InternalRequest();
}
request = request->GetRequestConstructorCopy(global, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
RequestMode fallbackMode = RequestMode::EndGuard_;
RequestCredentials fallbackCredentials = RequestCredentials::EndGuard_;
RequestCache fallbackCache = RequestCache::EndGuard_;
if (aInput.IsUSVString()) {
// aInput is USVString.
// We need to get url before we create a InternalRequest.
nsAutoString input;
input.Assign(aInput.GetAsUSVString());
@ -317,7 +307,18 @@ Request::Constructor(const GlobalObject& aGlobal,
return nullptr;
}
request->SetURL(NS_ConvertUTF16toUTF8(requestURL));
request = new InternalRequest(NS_ConvertUTF16toUTF8(requestURL));
}
request = request->GetRequestConstructorCopy(global, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
RequestMode fallbackMode = RequestMode::EndGuard_;
RequestCredentials fallbackCredentials = RequestCredentials::EndGuard_;
RequestCache fallbackCache = RequestCache::EndGuard_;
if (aInput.IsUSVString()) {
fallbackMode = RequestMode::Cors;
fallbackCredentials = RequestCredentials::Omit;
fallbackCache = RequestCache::Default;

View File

@ -46,7 +46,9 @@ public:
void
GetUrl(nsAString& aUrl) const
{
CopyUTF8toUTF16(mRequest->mURL, aUrl);
nsAutoCString url;
mRequest->GetURL(url);
CopyUTF8toUTF16(url, aUrl);
}
void

View File

@ -53,10 +53,16 @@ public:
GetUrl(nsAString& aUrl) const
{
nsCString url;
mInternalResponse->GetUrl(url);
mInternalResponse->GetURL(url);
CopyUTF8toUTF16(url, aUrl);
}
bool
Redirected() const
{
return mInternalResponse->IsRedirected();
}
uint16_t
Status() const
{

View File

@ -274,15 +274,13 @@ HTMLTrackElement::BindToTree(nsIDocument* aDocument,
void
HTMLTrackElement::UnbindFromTree(bool aDeep, bool aNullParent)
{
if (mMediaParent) {
if (mMediaParent && aNullParent) {
// mTrack can be null if HTMLTrackElement::LoadResource has never been
// called.
if (mTrack) {
mMediaParent->RemoveTextTrack(mTrack);
}
if (aNullParent) {
mMediaParent = nullptr;
}
mMediaParent = nullptr;
}
nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);

View File

@ -1815,50 +1815,12 @@ IDBObjectStore::DeleteInternal(JSContext* aCx,
already_AddRefed<IDBIndex>
IDBObjectStore::CreateIndex(const nsAString& aName,
const nsAString& aKeyPath,
const StringOrStringSequence& aKeyPath,
const IDBIndexParameters& aOptionalParameters,
ErrorResult& aRv)
{
AssertIsOnOwningThread();
KeyPath keyPath(0);
if (NS_FAILED(KeyPath::Parse(aKeyPath, &keyPath)) ||
!keyPath.IsValid()) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
return CreateIndexInternal(aName, keyPath, aOptionalParameters, aRv);
}
already_AddRefed<IDBIndex>
IDBObjectStore::CreateIndex(const nsAString& aName,
const Sequence<nsString >& aKeyPath,
const IDBIndexParameters& aOptionalParameters,
ErrorResult& aRv)
{
AssertIsOnOwningThread();
KeyPath keyPath(0);
if (aKeyPath.IsEmpty() ||
NS_FAILED(KeyPath::Parse(aKeyPath, &keyPath)) ||
!keyPath.IsValid()) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
return CreateIndexInternal(aName, keyPath, aOptionalParameters, aRv);
}
already_AddRefed<IDBIndex>
IDBObjectStore::CreateIndexInternal(
const nsAString& aName,
const KeyPath& aKeyPath,
const IDBIndexParameters& aOptionalParameters,
ErrorResult& aRv)
{
AssertIsOnOwningThread();
if (mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE ||
mDeletedSpec) {
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
@ -1883,7 +1845,24 @@ IDBObjectStore::CreateIndexInternal(
}
}
if (aOptionalParameters.mMultiEntry && aKeyPath.IsArray()) {
KeyPath keyPath(0);
if (aKeyPath.IsString()) {
if (NS_FAILED(KeyPath::Parse(aKeyPath.GetAsString(), &keyPath)) ||
!keyPath.IsValid()) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
} else {
MOZ_ASSERT(aKeyPath.IsStringSequence());
if (aKeyPath.GetAsStringSequence().IsEmpty() ||
NS_FAILED(KeyPath::Parse(aKeyPath.GetAsStringSequence(), &keyPath)) ||
!keyPath.IsValid()) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
}
if (aOptionalParameters.mMultiEntry && keyPath.IsArray()) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return nullptr;
}
@ -1912,7 +1891,7 @@ IDBObjectStore::CreateIndexInternal(
#endif
IndexMetadata* metadata = indexes.AppendElement(
IndexMetadata(transaction->NextIndexId(), nsString(aName), aKeyPath,
IndexMetadata(transaction->NextIndexId(), nsString(aName), keyPath,
locale,
aOptionalParameters.mUnique,
aOptionalParameters.mMultiEntry,

View File

@ -30,6 +30,7 @@ class DOMStringList;
class IDBCursor;
class IDBRequest;
class IDBTransaction;
class StringOrStringSequence;
template <typename> class Sequence;
namespace indexedDB {
@ -209,13 +210,7 @@ public:
already_AddRefed<IDBIndex>
CreateIndex(const nsAString& aName,
const nsAString& aKeyPath,
const IDBIndexParameters& aOptionalParameters,
ErrorResult& aRv);
already_AddRefed<IDBIndex>
CreateIndex(const nsAString& aName,
const Sequence<nsString>& aKeyPath,
const StringOrStringSequence& aKeyPath,
const IDBIndexParameters& aOptionalParameters,
ErrorResult& aRv);
@ -345,12 +340,6 @@ private:
const Optional<uint32_t>& aLimit,
ErrorResult& aRv);
already_AddRefed<IDBIndex>
CreateIndexInternal(const nsAString& aName,
const KeyPath& aKeyPath,
const IDBIndexParameters& aOptionalParameters,
ErrorResult& aRv);
already_AddRefed<IDBRequest>
OpenCursorInternal(bool aKeysOnly,
JSContext* aCx,

View File

@ -15,7 +15,7 @@
// These constants are duplicated in `PushComponents.js`.
#define OBSERVER_TOPIC_PUSH "push-message"
#define OBSERVER_TOPIC_SUBSCRIPTION_CHANGE "push-subscription-change"
#define OBSERVER_TOPIC_SUBSCRIPTION_LOST "push-subscription-lost"
#define OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED "push-subscription-modified"
%}
interface nsIPrincipal;
@ -55,19 +55,15 @@ interface nsIPushNotifier : nsISupports
void notifySubscriptionChange(in ACString scope, in nsIPrincipal principal);
/**
* Fires a `push-subscription-lost` observer notification. Chrome code can
* listen for this notification to see when the Push service removes or
* expires a subscription for the |scope|.
* Fires a `push-subscription-modified` observer notification. Chrome code
* can listen for this notification to see when a subscription is added,
* updated, removed, or expired for any |scope|.
*
* This is useful for Dev Tools and debugging add-ons that need to be
* notified when a subscription changes state. Other chrome callers should
* listen for `push-subscription-change` and resubscribe instead.
*
* |reason| is an `nsIPushErrorReporter` unsubscribe reason. The reason is
* wrapped in an `nsISupportsPRUint16` and passed as the subject.
* This is useful for Dev Tools and debugging add-ons that passively observe
* when subscriptions are created or dropped. Other callers should listen for
* `push-subscription-change` and resubscribe instead.
*/
void notifySubscriptionLost(in ACString scope, in nsIPrincipal principal,
[optional] in uint16_t reason);
void notifySubscriptionModified(in ACString scope, in nsIPrincipal principal);
void notifyError(in ACString scope, in nsIPrincipal principal,
in DOMString message, in uint32_t flags);
@ -84,31 +80,32 @@ interface nsIPushNotifier : nsISupports
in MaybeData data);
[noscript, nostdcall]
void notifyPushObservers(in ACString scope, in MaybeData data);
void notifyPushObservers(in ACString scope, in nsIPrincipal principal,
in MaybeData data);
[noscript, nostdcall]
void notifySubscriptionChangeWorkers(in ACString scope,
in nsIPrincipal principal);
[noscript, nostdcall]
void notifySubscriptionChangeObservers(in ACString scope,
in nsIPrincipal principal);
[noscript, nostdcall]
void notifySubscriptionModifiedObservers(in ACString scope,
in nsIPrincipal principal);
[noscript, nostdcall]
void notifySubscriptionChangeObservers(in ACString scope);
[noscript, nostdcall]
void notifySubscriptionLostObservers(in ACString scope, in uint16_t reason);
[noscript, nostdcall, notxpcom]
void notifyErrorWorkers(in ACString scope, in DOMString message,
in uint32_t flags);
};
/**
* A push message sent to a subscription, used as the subject of a
* `push-message` observer notification.
*
* Provides methods for retrieving push message data in different formats.
* This interface resembles the `PushMessageData` WebIDL interface.
*/
[scriptable, builtinclass, uuid(136dc8fd-8c56-4176-9170-eaa86b6ba99e)]
interface nsIPushMessage : nsISupports
[scriptable, builtinclass, uuid(dfc4f151-cead-40df-8eb7-7a7a67c54b16)]
interface nsIPushData : nsISupports
{
/** Extracts the data as a UTF-8 text string. */
DOMString text();
@ -120,3 +117,14 @@ interface nsIPushMessage : nsISupports
void binary([optional] out uint32_t dataLen,
[array, retval, size_is(dataLen)] out uint8_t data);
};
/**
* The subject of a `push-message` observer notification. |data| may be |null|
* for messages without data.
*/
[scriptable, builtinclass, uuid(b9d063ca-0e3f-4fee-be4b-ea9103263433)]
interface nsIPushMessage : nsISupports
{
readonly attribute nsIPrincipal principal;
readonly attribute nsIPushData data;
};

View File

@ -77,7 +77,7 @@ interface nsIPushService : nsISupports
/** Observer topic names, exported for convenience. */
readonly attribute DOMString pushTopic;
readonly attribute DOMString subscriptionChangeTopic;
readonly attribute DOMString subscriptionLostTopic;
readonly attribute DOMString subscriptionModifiedTopic;
/**
* Creates a push subscription for the given |scope| URL and |principal|.
@ -91,9 +91,9 @@ interface nsIPushService : nsISupports
* an `nsIPushMessage` as the subject and the |scope| as the data.
*
* If the server drops a subscription, a `push-subscription-change` observer
* will be fired, with the subject set to `null` and the data set to |scope|.
* Servers may drop subscriptions at any time, so callers should recreate
* subscriptions if desired.
* will be fired, with the subject set to |principal| and the data set to
* |scope|. Servers may drop subscriptions at any time, so callers should
* recreate subscriptions if desired.
*/
void subscribe(in DOMString scope, in nsIPrincipal principal,
in nsIPushSubscriptionCallback callback);

View File

@ -3254,7 +3254,8 @@ ContentChild::RecvPush(const nsCString& aScope,
return true;
}
nsresult rv = pushNotifier->NotifyPushObservers(aScope, Nothing());
nsresult rv = pushNotifier->NotifyPushObservers(aScope, aPrincipal,
Nothing());
Unused << NS_WARN_IF(NS_FAILED(rv));
rv = pushNotifier->NotifyPushWorkers(aScope, aPrincipal,
@ -3277,7 +3278,8 @@ ContentChild::RecvPushWithData(const nsCString& aScope,
return true;
}
nsresult rv = pushNotifier->NotifyPushObservers(aScope, Some(aData));
nsresult rv = pushNotifier->NotifyPushObservers(aScope, aPrincipal,
Some(aData));
Unused << NS_WARN_IF(NS_FAILED(rv));
rv = pushNotifier->NotifyPushWorkers(aScope, aPrincipal,
@ -3298,7 +3300,8 @@ ContentChild::RecvPushSubscriptionChange(const nsCString& aScope,
return true;
}
nsresult rv = pushNotifier->NotifySubscriptionChangeObservers(aScope);
nsresult rv = pushNotifier->NotifySubscriptionChangeObservers(aScope,
aPrincipal);
Unused << NS_WARN_IF(NS_FAILED(rv));
rv = pushNotifier->NotifySubscriptionChangeWorkers(aScope, aPrincipal);

View File

@ -5784,6 +5784,7 @@ ContentParent::StartProfiler(nsIProfilerStartParams* aParams)
bool
ContentParent::RecvNotifyPushObservers(const nsCString& aScope,
const IPC::Principal& aPrincipal,
const nsString& aMessageId)
{
#ifndef MOZ_SIMPLEPUSH
@ -5793,7 +5794,8 @@ ContentParent::RecvNotifyPushObservers(const nsCString& aScope,
return true;
}
nsresult rv = pushNotifier->NotifyPushObservers(aScope, Nothing());
nsresult rv = pushNotifier->NotifyPushObservers(aScope, aPrincipal,
Nothing());
Unused << NS_WARN_IF(NS_FAILED(rv));
#endif
return true;
@ -5801,6 +5803,7 @@ ContentParent::RecvNotifyPushObservers(const nsCString& aScope,
bool
ContentParent::RecvNotifyPushObserversWithData(const nsCString& aScope,
const IPC::Principal& aPrincipal,
const nsString& aMessageId,
InfallibleTArray<uint8_t>&& aData)
{
@ -5811,14 +5814,16 @@ ContentParent::RecvNotifyPushObserversWithData(const nsCString& aScope,
return true;
}
nsresult rv = pushNotifier->NotifyPushObservers(aScope, Some(aData));
nsresult rv = pushNotifier->NotifyPushObservers(aScope, aPrincipal,
Some(aData));
Unused << NS_WARN_IF(NS_FAILED(rv));
#endif
return true;
}
bool
ContentParent::RecvNotifyPushSubscriptionChangeObservers(const nsCString& aScope)
ContentParent::RecvNotifyPushSubscriptionChangeObservers(const nsCString& aScope,
const IPC::Principal& aPrincipal)
{
#ifndef MOZ_SIMPLEPUSH
nsCOMPtr<nsIPushNotifier> pushNotifier =
@ -5827,15 +5832,16 @@ ContentParent::RecvNotifyPushSubscriptionChangeObservers(const nsCString& aScope
return true;
}
nsresult rv = pushNotifier->NotifySubscriptionChangeObservers(aScope);
nsresult rv = pushNotifier->NotifySubscriptionChangeObservers(aScope,
aPrincipal);
Unused << NS_WARN_IF(NS_FAILED(rv));
#endif
return true;
}
bool
ContentParent::RecvNotifyPushSubscriptionLostObservers(const nsCString& aScope,
const uint16_t& aReason)
ContentParent::RecvNotifyPushSubscriptionModifiedObservers(const nsCString& aScope,
const IPC::Principal& aPrincipal)
{
#ifndef MOZ_SIMPLEPUSH
nsCOMPtr<nsIPushNotifier> pushNotifier =
@ -5844,7 +5850,8 @@ ContentParent::RecvNotifyPushSubscriptionLostObservers(const nsCString& aScope,
return true;
}
nsresult rv = pushNotifier->NotifySubscriptionLostObservers(aScope, aReason);
nsresult rv = pushNotifier->NotifySubscriptionModifiedObservers(aScope,
aPrincipal);
Unused << NS_WARN_IF(NS_FAILED(rv));
#endif
return true;

View File

@ -1090,16 +1090,19 @@ private:
const uint32_t& aDecodeFPS) override;
virtual bool RecvNotifyPushObservers(const nsCString& aScope,
const nsString& aMessageId) override;
const IPC::Principal& aPrincipal,
const nsString& aMessageId) override;
virtual bool RecvNotifyPushObserversWithData(const nsCString& aScope,
const nsString& aMessageId,
InfallibleTArray<uint8_t>&& aData) override;
const IPC::Principal& aPrincipal,
const nsString& aMessageId,
InfallibleTArray<uint8_t>&& aData) override;
virtual bool RecvNotifyPushSubscriptionChangeObservers(const nsCString& aScope) override;
virtual bool RecvNotifyPushSubscriptionChangeObservers(const nsCString& aScope,
const IPC::Principal& aPrincipal) override;
virtual bool RecvNotifyPushSubscriptionLostObservers(const nsCString& aScope,
const uint16_t& aReason) override;
virtual bool RecvNotifyPushSubscriptionModifiedObservers(const nsCString& aScope,
const IPC::Principal& aPrincipal) override;
// If you add strong pointers to cycle collected objects here, be sure to
// release these objects in ShutDownProcess. See the comment there for more

View File

@ -1177,23 +1177,26 @@ parent:
/**
* Notify `push-message` observers without data in the parent.
*/
async NotifyPushObservers(nsCString scope, nsString messageId);
async NotifyPushObservers(nsCString scope, Principal principal,
nsString messageId);
/**
* Notify `push-message` observers with data in the parent.
*/
async NotifyPushObserversWithData(nsCString scope, nsString messageId,
uint8_t[] data);
async NotifyPushObserversWithData(nsCString scope, Principal principal,
nsString messageId, uint8_t[] data);
/**
* Notify `push-subscription-change` observers in the parent.
*/
async NotifyPushSubscriptionChangeObservers(nsCString scope);
async NotifyPushSubscriptionChangeObservers(nsCString scope,
Principal principal);
/**
* Notify `push-subscription-lost` observers in the parent.
* Notify `push-subscription-modified` observers in the parent.
*/
async NotifyPushSubscriptionLostObservers(nsCString scope, uint16_t reason);
async NotifyPushSubscriptionModifiedObservers(nsCString scope,
Principal principal);
both:
async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,

View File

@ -194,6 +194,8 @@ InterceptedErrorResponseWithURL=Failed to load %S. A ServiceWorker passed
InterceptedUsedResponseWithURL=Failed to load %S. A ServiceWorker passed a used Response to FetchEvent.respondWith(). The body of a Response may only be read once. Use Response.clone() to access the body multiple times.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "opaqueredirect", "Response", "FetchEvent.respondWith()", or "FetchEvent". %s is a URL.
BadOpaqueRedirectInterceptionWithURL=Failed to load %S. A ServiceWorker passed an opaqueredirect Response to FetchEvent.respondWith() while handling a non-navigation FetchEvent.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", "RedirectMode" or "follow". %S is a URL.
BadRedirectModeInterceptionWithURL=Failed to load %S. A ServiceWorker passed a redirected Response to FetchEvent.respondWith() while RedirectMode is not follow.
# LOCALIZATION NOTE: Do not translate "ServiceWorker" or "FetchEvent.preventDefault()". %S is a URL.
InterceptionCanceledWithURL=Failed to load %S. A ServiceWorker canceled the load by calling FetchEvent.preventDefault().
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "promise", or "FetchEvent.respondWith()". %1$S is a URL. %2$S is an error string.

View File

@ -96,10 +96,9 @@ CopyAndPackAudio(AVFrame* aFrame, uint32_t aNumChannels, uint32_t aNumAFrames)
return audio;
}
void
FFmpegAudioDecoder<LIBAV_VER>::DecodePacket(MediaRawData* aSample)
FFmpegAudioDecoder<LIBAV_VER>::DecodeResult
FFmpegAudioDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
AVPacket packet;
mLib->av_init_packet(&packet);
@ -108,8 +107,7 @@ FFmpegAudioDecoder<LIBAV_VER>::DecodePacket(MediaRawData* aSample)
if (!PrepareFrame()) {
NS_WARNING("FFmpeg audio decoder failed to allocate frame.");
mCallback->Error();
return;
return DecodeResult::DECODE_ERROR;
}
int64_t samplePosition = aSample->mOffset;
@ -122,16 +120,14 @@ FFmpegAudioDecoder<LIBAV_VER>::DecodePacket(MediaRawData* aSample)
if (bytesConsumed < 0) {
NS_WARNING("FFmpeg audio decoder error.");
mCallback->Error();
return;
return DecodeResult::DECODE_ERROR;
}
if (decoded) {
uint32_t numChannels = mCodecContext->channels;
AudioConfig::ChannelLayout layout(numChannels);
if (!layout.IsValid()) {
mCallback->Error();
return;
return DecodeResult::DECODE_ERROR;
}
uint32_t samplingRate = mCodecContext->sample_rate;
@ -143,8 +139,7 @@ FFmpegAudioDecoder<LIBAV_VER>::DecodePacket(MediaRawData* aSample)
FramesToTimeUnit(mFrame->nb_samples, samplingRate);
if (!audio || !duration.IsValid()) {
NS_WARNING("Invalid count of accumulated audio samples");
mCallback->Error();
return;
return DecodeResult::DECODE_ERROR;
}
RefPtr<AudioData> data = new AudioData(samplePosition,
@ -158,8 +153,7 @@ FFmpegAudioDecoder<LIBAV_VER>::DecodePacket(MediaRawData* aSample)
pts += duration;
if (!pts.IsValid()) {
NS_WARNING("Invalid count of accumulated audio samples");
mCallback->Error();
return;
return DecodeResult::DECODE_ERROR;
}
}
packet.data += bytesConsumed;
@ -167,24 +161,12 @@ FFmpegAudioDecoder<LIBAV_VER>::DecodePacket(MediaRawData* aSample)
samplePosition += bytesConsumed;
}
if (mTaskQueue->IsEmpty()) {
mCallback->InputExhausted();
}
}
nsresult
FFmpegAudioDecoder<LIBAV_VER>::Input(MediaRawData* aSample)
{
nsCOMPtr<nsIRunnable> runnable(NewRunnableMethod<RefPtr<MediaRawData>>(
this, &FFmpegAudioDecoder::DecodePacket, RefPtr<MediaRawData>(aSample)));
mTaskQueue->Dispatch(runnable.forget());
return NS_OK;
return DecodeResult::DECODE_FRAME;
}
void
FFmpegAudioDecoder<LIBAV_VER>::ProcessDrain()
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
ProcessFlush();
mCallback->DrainComplete();
}

View File

@ -27,8 +27,6 @@ public:
virtual ~FFmpegAudioDecoder();
RefPtr<InitPromise> Init() override;
nsresult Input(MediaRawData* aSample) override;
void ProcessDrain() override;
void InitCodecContext() override;
static AVCodecID GetCodecId(const nsACString& aMimeType);
const char* GetDescriptionName() const override
@ -37,7 +35,8 @@ public:
}
private:
void DecodePacket(MediaRawData* aSample);
DecodeResult DoDecode(MediaRawData* aSample) override;
void ProcessDrain() override;
};
} // namespace mozilla

View File

@ -26,12 +26,12 @@ StaticMutex FFmpegDataDecoder<LIBAV_VER>::sMonitor;
MediaDataDecoderCallback* aCallback,
AVCodecID aCodecID)
: mLib(aLib)
, mTaskQueue(aTaskQueue)
, mCallback(aCallback)
, mCodecContext(nullptr)
, mFrame(NULL)
, mExtraData(nullptr)
, mCodecID(aCodecID)
, mTaskQueue(aTaskQueue)
{
MOZ_ASSERT(aLib);
MOZ_COUNT_CTOR(FFmpegDataDecoder);
@ -111,6 +111,29 @@ FFmpegDataDecoder<LIBAV_VER>::Shutdown()
return NS_OK;
}
void
FFmpegDataDecoder<LIBAV_VER>::ProcessDecode(MediaRawData* aSample)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
switch (DoDecode(aSample)) {
case DecodeResult::DECODE_ERROR:
mCallback->Error();
break;
default:
if (mTaskQueue->IsEmpty()) {
mCallback->InputExhausted();
}
}
}
nsresult
FFmpegDataDecoder<LIBAV_VER>::Input(MediaRawData* aSample)
{
mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
this, &FFmpegDataDecoder::ProcessDecode, aSample));
return NS_OK;
}
nsresult
FFmpegDataDecoder<LIBAV_VER>::Flush()
{

View File

@ -32,7 +32,7 @@ public:
static bool Link();
RefPtr<InitPromise> Init() override = 0;
nsresult Input(MediaRawData* aSample) override = 0;
nsresult Input(MediaRawData* aSample) override;
nsresult Flush() override;
nsresult Drain() override;
nsresult Shutdown() override;
@ -40,16 +40,20 @@ public:
static AVCodec* FindAVCodec(FFmpegLibWrapper* aLib, AVCodecID aCodec);
protected:
enum DecodeResult {
DECODE_FRAME,
DECODE_NO_FRAME,
DECODE_ERROR
};
// Flush and Drain operation, always run
virtual void ProcessFlush();
virtual void ProcessDrain() = 0;
virtual void ProcessShutdown();
virtual void InitCodecContext() {}
AVFrame* PrepareFrame();
nsresult InitDecoder();
FFmpegLibWrapper* mLib;
RefPtr<FlushableTaskQueue> mTaskQueue;
MediaDataDecoderCallback* mCallback;
AVCodecContext* mCodecContext;
@ -58,7 +62,12 @@ protected:
AVCodecID mCodecID;
private:
void ProcessDecode(MediaRawData* aSample);
virtual DecodeResult DoDecode(MediaRawData* aSample) = 0;
virtual void ProcessDrain() = 0;
static StaticMutex sMonitor;
const RefPtr<FlushableTaskQueue> mTaskQueue;
};
} // namespace mozilla

View File

@ -159,10 +159,8 @@ FFmpegVideoDecoder<LIBAV_VER>::InitCodecContext()
}
FFmpegVideoDecoder<LIBAV_VER>::DecodeResult
FFmpegVideoDecoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample)
FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
uint8_t* inputData = const_cast<uint8_t*>(aSample->Data());
size_t inputSize = aSample->Size();
@ -181,13 +179,12 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample)
aSample->mTime, aSample->mTimecode,
aSample->mOffset);
if (size_t(len) > inputSize) {
mCallback->Error();
return DecodeResult::DECODE_ERROR;
}
inputData += len;
inputSize -= len;
if (size) {
switch (DoDecodeFrame(aSample, data, size)) {
switch (DoDecode(aSample, data, size)) {
case DecodeResult::DECODE_ERROR:
return DecodeResult::DECODE_ERROR;
case DecodeResult::DECODE_FRAME:
@ -201,15 +198,13 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample)
return gotFrame ? DecodeResult::DECODE_FRAME : DecodeResult::DECODE_NO_FRAME;
}
#endif
return DoDecodeFrame(aSample, inputData, inputSize);
return DoDecode(aSample, inputData, inputSize);
}
FFmpegVideoDecoder<LIBAV_VER>::DecodeResult
FFmpegVideoDecoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample,
uint8_t* aData, int aSize)
FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample,
uint8_t* aData, int aSize)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
AVPacket packet;
mLib->av_init_packet(&packet);
@ -229,7 +224,6 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample,
if (!PrepareFrame()) {
NS_WARNING("FFmpeg h264 decoder failed to allocate frame.");
mCallback->Error();
return DecodeResult::DECODE_ERROR;
}
@ -248,7 +242,6 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample,
if (bytesConsumed < 0) {
NS_WARNING("FFmpeg video decoder error.");
mCallback->Error();
return DecodeResult::DECODE_ERROR;
}
@ -307,7 +300,6 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample,
if (!v) {
NS_WARNING("image allocation error.");
mCallback->Error();
return DecodeResult::DECODE_ERROR;
}
mCallback->Output(v);
@ -316,36 +308,12 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample,
return DecodeResult::DECODE_NO_FRAME;
}
void
FFmpegVideoDecoder<LIBAV_VER>::DecodeFrame(MediaRawData* aSample)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
if (DoDecodeFrame(aSample) != DecodeResult::DECODE_ERROR &&
mTaskQueue->IsEmpty()) {
mCallback->InputExhausted();
}
}
nsresult
FFmpegVideoDecoder<LIBAV_VER>::Input(MediaRawData* aSample)
{
nsCOMPtr<nsIRunnable> runnable(
NewRunnableMethod<RefPtr<MediaRawData>>(
this, &FFmpegVideoDecoder<LIBAV_VER>::DecodeFrame,
RefPtr<MediaRawData>(aSample)));
mTaskQueue->Dispatch(runnable.forget());
return NS_OK;
}
void
FFmpegVideoDecoder<LIBAV_VER>::ProcessDrain()
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
RefPtr<MediaRawData> empty(new MediaRawData());
empty->mTimecode = mPtsContext.LastDts();
while (DoDecodeFrame(empty) == DecodeResult::DECODE_FRAME) {
while (DoDecode(empty) == DecodeResult::DECODE_FRAME) {
}
mCallback->DrainComplete();
}

View File

@ -26,12 +26,6 @@ class FFmpegVideoDecoder<LIBAV_VER> : public FFmpegDataDecoder<LIBAV_VER>
typedef mozilla::layers::Image Image;
typedef mozilla::layers::ImageContainer ImageContainer;
enum DecodeResult {
DECODE_FRAME,
DECODE_NO_FRAME,
DECODE_ERROR
};
public:
FFmpegVideoDecoder(FFmpegLibWrapper* aLib, FlushableTaskQueue* aTaskQueue,
MediaDataDecoderCallback* aCallback,
@ -40,9 +34,6 @@ public:
virtual ~FFmpegVideoDecoder();
RefPtr<InitPromise> Init() override;
nsresult Input(MediaRawData* aSample) override;
void ProcessDrain() override;
void ProcessFlush() override;
void InitCodecContext() override;
const char* GetDescriptionName() const override
{
@ -55,10 +46,10 @@ public:
static AVCodecID GetCodecId(const nsACString& aMimeType);
private:
void DecodeFrame(MediaRawData* aSample);
DecodeResult DoDecodeFrame(MediaRawData* aSample);
DecodeResult DoDecodeFrame(MediaRawData* aSample, uint8_t* aData, int aSize);
void DoDrain();
DecodeResult DoDecode(MediaRawData* aSample) override;
DecodeResult DoDecode(MediaRawData* aSample, uint8_t* aData, int aSize);
void ProcessDrain() override;
void ProcessFlush() override;
void OutputDelayedFrames();
/**

View File

@ -609,6 +609,7 @@ skip-if = os == 'win' && !debug # bug 1140675
[test_bug1113600.html]
tags=capturestream
[test_bug1242338.html]
[test_bug1242594.html]
[test_bug1248229.html]
tags=capturestream
[test_can_play_type.html]

View File

@ -0,0 +1,45 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1242594
-->
<head>
<meta charset='utf-8'>
<title>Bug 1242594 - Unbind a video element with HTMLTrackElement
should not remove the TextTrack</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
var video = document.createElement("video");
video.src = "seek.webm";
video.preload = "auto";
var trackElement = document.createElement("track");
trackElement.src = "basic.vtt";
trackElement.kind = "subtitles";
document.getElementById("content").appendChild(video);
video.appendChild(trackElement);
video.addEventListener("loadedmetadata", function run_tests() {
is(video.textTracks.length, 1, "Video should have one TextTrack.");
var parent = video.parentNode;
parent.removeChild(video);
is(video.textTracks.length, 1, "After unbind the video element, should have one TextTrack.");
parent.appendChild(video);
is(video.textTracks.length, 1, "After bind the video element, should have one TextTrack.");
SimpleTest.finish();
});
</script>
</pre>
</body>
</html>

View File

@ -224,19 +224,24 @@ function realCreateHTML(meta) {
document.body.appendChild(content);
}
function getMediaElement(label, direction, streamId) {
var id = label + '_' + direction + '_' + streamId;
return document.getElementById(id);
}
/**
* Create the HTML element if it doesn't exist yet and attach
* it to the content node.
*
* @param {string} type
* Type of media element to create ('audio' or 'video')
* @param {string} label
* Description to use for the element
* Prefix to use for the element
* @param {direction} "local" or "remote"
* @param {stream} A MediaStream id.
* @param {audioOnly} Use <audio> element instead of <video>
* @return {HTMLMediaElement} The created HTML media element
*/
function createMediaElement(type, label) {
var id = label + '_' + type;
function createMediaElement(label, direction, streamId, audioOnly) {
var id = label + '_' + direction + '_' + streamId;
var element = document.getElementById(id);
// Sanity check that we haven't created the element already
@ -244,7 +249,12 @@ function createMediaElement(type, label) {
return element;
}
element = document.createElement(type === 'audio' ? 'audio' : 'video');
if (!audioOnly) {
// Even if this is just audio now, we might add video later.
element = document.createElement('video');
} else {
element = document.createElement('audio');
}
element.setAttribute('id', id);
element.setAttribute('height', 100);
element.setAttribute('width', 150);

View File

@ -721,9 +721,6 @@ function PeerConnectionWrapper(label, configuration) {
this.constraints = [ ];
this.offerOptions = {};
this.streams = [ ];
this.streamsForLocalTracks = [ ];
this.mediaElements = [ ];
this.dataChannels = [ ];
@ -732,6 +729,8 @@ function PeerConnectionWrapper(label, configuration) {
this.localRequiresTrickleIce = false;
this.remoteRequiresTrickleIce = false;
this.localMediaElements = [];
this.remoteMediaElements = [];
this.audioElementsOnly = false;
this.expectedLocalTrackInfoById = {};
this.expectedRemoteTrackInfoById = {};
@ -831,6 +830,26 @@ PeerConnectionWrapper.prototype = {
this._pc.setIdentityProvider(provider, protocol, identity);
},
ensureMediaElement : function(track, stream, direction) {
var element = getMediaElement(this.label, direction, stream.id);
if (!element) {
element = createMediaElement(this.label, direction, stream.id,
this.audioElementsOnly);
if (direction == "local") {
this.localMediaElements.push(element);
} else if (direction == "remote") {
this.remoteMediaElements.push(element);
}
}
// We do this regardless, because sometimes we end up with a new stream with
// an old id (ie; the rollback tests cause the same stream to be added
// twice)
element.srcObject = stream;
element.play();
},
/**
* Attaches a local track to this RTCPeerConnection using
* RTCPeerConnection.addTrack().
@ -858,83 +877,46 @@ PeerConnectionWrapper.prototype = {
streamId: stream.id,
};
var mappedStream =
this.streamsForLocalTracks.find(o => o.fromStreamId == stream.id);
this.ensureMediaElement(track, stream, "local");
if (mappedStream) {
mappedStream.stream.addTrack(track);
return this.observedNegotiationNeeded;
}
mappedStream = new MediaStream([track]);
this.streamsForLocalTracks.push(
{ fromStreamId: stream.id
, stream: mappedStream
}
);
var element = createMediaElement(track.kind, this.label + '_tracks_' + this.streamsForLocalTracks.length);
this.mediaElements.push(element);
element.srcObject = mappedStream;
element.play();
// Store local media elements so that we can stop them when done.
// Don't store remote ones because they should stop when the PC does.
this.localMediaElements.push(element);
return this.observedNegotiationNeeded;
},
/**
* Callback when we get media from either side. Also an appropriate
* HTML media element will be created.
* Callback when we get local media. Also an appropriate HTML media element
* will be created, which may be obtained later with |getMediaElement|.
*
* @param {MediaStream} stream
* Media stream to handle
* @param {string} type
* The type of media stream ('audio' or 'video')
* @param {string} side
* The location the stream is coming from ('local' or 'remote')
*/
attachMedia : function(stream, type, side) {
info("Got media stream: " + type + " (" + side + ")");
this.streams.push(stream);
if (side === 'local') {
this.expectNegotiationNeeded();
// In order to test both the addStream and addTrack APIs, we do video one
// way and audio + audiovideo the other.
if (type == "video") {
this._pc.addStream(stream);
ok(this._pc.getSenders().find(sender => sender.track == stream.getVideoTracks()[0]),
"addStream adds sender");
} else {
stream.getTracks().forEach(track => {
var sender = this._pc.addTrack(track, stream);
is(sender.track, track, "addTrack returns sender");
});
}
attachLocalStream : function(stream) {
info("Got local media stream: (" + stream.id + ")");
this.expectNegotiationNeeded();
// In order to test both the addStream and addTrack APIs, we do half one
// way, half the other, at random.
if (Math.random() < 0.5) {
info("Using addStream.");
this._pc.addStream(stream);
ok(this._pc.getSenders().find(sender => sender.track == stream.getTracks()[0]),
"addStream returns sender");
} else {
info("Using addTrack (on PC).");
stream.getTracks().forEach(track => {
ok(track.id, "track has id");
ok(track.kind, "track has kind");
this.expectedLocalTrackInfoById[track.id] = {
type: track.kind,
streamId: stream.id
};
var sender = this._pc.addTrack(track, stream);
is(sender.track, track, "addTrack returns sender");
});
}
var element = createMediaElement(type, this.label + '_' + side + this.streams.length);
this.mediaElements.push(element);
element.srcObject = stream;
element.play();
// Store local media elements so that we can stop them when done.
// Don't store remote ones because they should stop when the PC does.
if (side === 'local') {
this.localMediaElements.push(element);
return this.observedNegotiationNeeded;
}
stream.getTracks().forEach(track => {
ok(track.id, "track has id");
ok(track.kind, "track has kind");
this.expectedLocalTrackInfoById[track.id] = {
type: track.kind,
streamId: stream.id
};
this.ensureMediaElement(track, stream, "local");
});
},
removeSender : function(index) {
@ -970,22 +952,19 @@ PeerConnectionWrapper.prototype = {
info("Get " + constraintsList.length + " local streams");
return Promise.all(constraintsList.map(constraints => {
return getUserMedia(constraints).then(stream => {
var type = '';
if (constraints.audio) {
type = 'audio';
stream.getAudioTracks().map(track => {
info(this + " gUM local stream " + stream.id +
" with audio track " + track.id);
});
}
if (constraints.video) {
type += 'video';
stream.getVideoTracks().map(track => {
info(this + " gUM local stream " + stream.id +
" with video track " + track.id);
});
}
return this.attachMedia(stream, type, 'local');
return this.attachLocalStream(stream);
});
}));
},
@ -1154,46 +1133,26 @@ PeerConnectionWrapper.prototype = {
return Object.keys(expected).every(trackId => observed[trackId]);
},
setupAddStreamEventHandler: function() {
var resolveAllAddStreamEventsDone;
setupTrackEventHandler: function() {
var resolveAllTrackEventsDone;
// checkMediaTracks waits on this promise later on in the test.
this.allAddStreamEventsDonePromise =
new Promise(resolve => resolveAllAddStreamEventsDone = resolve);
this.allTrackEventsDonePromise =
new Promise(resolve => resolveAllTrackEventsDone = resolve);
this._pc.addEventListener('addstream', event => {
info(this + ": 'onaddstream' event fired for " + JSON.stringify(event.stream));
this._pc.addEventListener('track', event => {
info(this + ": 'ontrack' event fired for " + JSON.stringify(event.track));
// TODO(bug 1130185): We need to handle addtrack events once we start
// testing addTrack on pre-existing streams.
event.stream.getTracks().forEach(track => {
this.checkTrackIsExpected(track,
this.expectedRemoteTrackInfoById,
this.observedRemoteTrackInfoById);
});
this.checkTrackIsExpected(event.track,
this.expectedRemoteTrackInfoById,
this.observedRemoteTrackInfoById);
if (this.allExpectedTracksAreObserved(this.expectedRemoteTrackInfoById,
this.observedRemoteTrackInfoById)) {
resolveAllAddStreamEventsDone();
resolveAllTrackEventsDone();
}
var type = '';
if (event.stream.getAudioTracks().length > 0) {
type = 'audio';
event.stream.getAudioTracks().map(track => {
info(this + " remote stream " + event.stream.id + " with audio track " +
track.id);
});
}
if (event.stream.getVideoTracks().length > 0) {
type += 'video';
event.stream.getVideoTracks().map(track => {
info(this + " remote stream " + event.stream.id + " with video track " +
track.id);
});
}
this.attachMedia(event.stream, type, 'remote');
this.ensureMediaElement(event.track, event.streams[0], 'remote');
});
},
@ -1389,7 +1348,7 @@ PeerConnectionWrapper.prototype = {
return;
}
return timerGuard(this.allAddStreamEventsDonePromise, 60000, "onaddstream never fired");
return timerGuard(this.allTrackEventsDonePromise, 60000, "The expected ontrack events never fired");
},
checkMsids: function() {
@ -1408,6 +1367,16 @@ PeerConnectionWrapper.prototype = {
"remote");
},
markRemoteTracksAsNegotiated: function() {
Object.values(this.observedRemoteTrackInfoById).forEach(
trackInfo => trackInfo.negotiated = true);
},
rollbackRemoteTracksIfNotNegotiated: function() {
Object.keys(this.observedRemoteTrackInfoById).forEach(
id => delete this.observedRemoteTrackInfoById[id]);
},
/**
* Check that media flow is present on the given media element by waiting for
* it to reach ready state HAVE_ENOUGH_DATA and progress time further than
@ -1490,7 +1459,8 @@ PeerConnectionWrapper.prototype = {
*/
waitForMediaFlow : function() {
return Promise.all([].concat(
this.mediaElements.map(element => this.waitForMediaElementFlow(element)),
this.localMediaElements.map(element => this.waitForMediaElementFlow(element)),
this.remoteMediaElements.map(element => this.waitForMediaElementFlow(element)),
this._pc.getSenders().map(sender => this.waitForRtpFlow(sender.track)),
this._pc.getReceivers().map(receiver => this.waitForRtpFlow(receiver.track))));
},

View File

@ -165,12 +165,12 @@ var commandsPeerConnectionInitial = [
test.pcRemote.logSignalingState();
},
function PC_LOCAL_SETUP_ADDSTREAM_HANDLER(test) {
test.pcLocal.setupAddStreamEventHandler();
function PC_LOCAL_SETUP_TRACK_HANDLER(test) {
test.pcLocal.setupTrackEventHandler();
},
function PC_REMOTE_SETUP_ADDSTREAM_HANDLER(test) {
test.pcRemote.setupAddStreamEventHandler();
function PC_REMOTE_SETUP_TRACK_HANDLER(test) {
test.pcRemote.setupTrackEventHandler();
},
function PC_LOCAL_CHECK_INITIAL_SIGNALINGSTATE(test) {
@ -353,7 +353,8 @@ var commandsPeerConnectionOfferAnswer = [
.then(() => {
is(test.pcRemote.signalingState, STABLE,
"signalingState after remote setLocalDescription is 'stable'");
});
})
.then(() => test.pcRemote.markRemoteTracksAsNegotiated());
},
function PC_LOCAL_GET_ANSWER(test) {
@ -375,7 +376,8 @@ var commandsPeerConnectionOfferAnswer = [
.then(() => {
is(test.pcLocal.signalingState, STABLE,
"signalingState after local setRemoteDescription is 'stable'");
});
})
.then(() => test.pcLocal.markRemoteTracksAsNegotiated());
},
function PC_REMOTE_SANE_LOCAL_SDP(test) {
test.pcRemote.localRequiresTrickleIce =

View File

@ -15,6 +15,8 @@
runNetworkTest(function (options) {
test = new PeerConnectionTest(options);
test.setMediaConstraints([{audio: true}], [{audio: true}]);
// pc.js uses video elements by default, we want to test audio elements here
test.pcLocal.audioElementsOnly = true;
test.run();
});
</script>

View File

@ -15,7 +15,7 @@ createHTML({
runNetworkTest(() => {
var test = new PeerConnectionTest();
var vremote;
var mediaElement;
var h = new CaptureStreamTestHelper2D();
var canvas = document.createElement('canvas');
var stream;
@ -25,32 +25,30 @@ runNetworkTest(() => {
test.setMediaConstraints([{video: true}], []);
test.chain.replace("PC_LOCAL_GUM", [
function DRAW_INITIAL_LOCAL_GREEN(test) {
function PC_LOCAL_DRAW_INITIAL_LOCAL_GREEN(test) {
h.drawColor(canvas, h.green);
},
function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
stream = canvas.captureStream(0);
test.pcLocal.attachMedia(stream, 'video', 'local');
test.pcLocal.attachLocalStream(stream);
}
]);
test.chain.append([
function FIND_REMOTE_VIDEO() {
vremote = document.getElementById('pcRemote_remote1_video');
ok(!!vremote, "Should have remote video element for pcRemote");
},
function WAIT_FOR_REMOTE_GREEN() {
return h.waitForPixelColor(vremote, h.green, 128,
function PC_REMOTE_WAIT_FOR_REMOTE_GREEN() {
mediaElement = test.pcRemote.remoteMediaElements[0];
ok(!!mediaElement, "Should have remote video element for pcRemote");
return h.waitForPixelColor(mediaElement, h.green, 128,
"pcRemote's remote should become green");
},
function DRAW_LOCAL_RED() {
function PC_LOCAL_DRAW_LOCAL_RED() {
// After requesting a frame it will be captured at the time of next render.
// Next render will happen at next stable state, at the earliest,
// i.e., this order of `requestFrame(); draw();` should work.
stream.requestFrame();
h.drawColor(canvas, h.red);
},
function WAIT_FOR_REMOTE_RED() {
return h.waitForPixelColor(vremote, h.red, 128,
function PC_REMOTE_WAIT_FOR_REMOTE_RED() {
return h.waitForPixelColor(mediaElement, h.red, 128,
"pcRemote's remote should become red");
}
]);

View File

@ -80,12 +80,12 @@ runNetworkTest(() => {
function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
test.pcLocal.canvasStream = canvas.captureStream(0.0);
is(test.pcLocal.canvasStream.canvas, canvas, "Canvas attribute is correct");
test.pcLocal.attachMedia(test.pcLocal.canvasStream, 'video', 'local');
test.pcLocal.attachLocalStream(test.pcLocal.canvasStream);
}
]);
test.chain.append([
function FIND_REMOTE_VIDEO() {
vremote = document.getElementById('pcRemote_remote1_video');
vremote = test.pcRemote.remoteMediaElements[0];
ok(!!vremote, "Should have remote video element for pcRemote");
},
function WAIT_FOR_REMOTE_GREEN() {

View File

@ -61,10 +61,7 @@ function startTest(media, token) {
test.setMediaConstraints([{ video: hasVideo, audio: hasAudio }], []);
test.chain.replace("PC_LOCAL_GUM", [
function PC_LOCAL_CAPTUREVIDEO(test) {
var type = "";
if (hasAudio) { type += "audio"; }
if (hasVideo) { type += "video"; }
test.pcLocal.attachMedia(stream, type, "local");
test.pcLocal.attachLocalStream(stream);
video.play();
}
]);

View File

@ -25,7 +25,7 @@ runNetworkTest(function() {
var streams = gumTest.pcRemote._pc.getRemoteStreams();
is(streams.length, 1, "One stream to forward");
is(streams[0].getTracks().length, 2, "Forwarded stream has 2 tracks");
forwardingTest.pcLocal.attachMedia(streams[0], 'audiovideo', 'local');
forwardingTest.pcLocal.attachLocalStream(streams[0]);
return Promise.resolve();
}
]);

View File

@ -38,18 +38,17 @@ runNetworkTest(() => {
},
function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
stream1 = canvas1.captureStream(0); // fps = 0 to capture single frame
test.pcLocal.attachMedia(stream1, 'video', 'local');
test.pcLocal.attachLocalStream(stream1);
stream2 = canvas2.captureStream(0); // fps = 0 to capture single frame
test.pcLocal.attachMedia(stream2, 'video', 'local');
test.pcLocal.attachLocalStream(stream2);
}
]);
test.chain.append([
function CHECK_REMOTE_VIDEO() {
var testremote = document.getElementById('pcRemote_remote3_video');
ok(!testremote, "Should not have remote3 video element for pcRemote");
vremote1 = document.getElementById('pcRemote_remote1_video');
vremote2 = document.getElementById('pcRemote_remote2_video');
is(test.pcRemote.remoteMediaElements.length, 2, "pcRemote Should have 2 remote media elements");
vremote1 = test.pcRemote.remoteMediaElements[0];
vremote2 = test.pcRemote.remoteMediaElements[1];
// since we don't know which remote video is created first, we don't know
// which should be blue or green, but this will make sure that one is

View File

@ -39,7 +39,8 @@
return test.setRemoteDescription(
test.pcRemote,
new RTCSessionDescription({ type: "rollback" }),
STABLE);
STABLE)
.then(() => test.pcRemote.rollbackRemoteTracksIfNotNegotiated());
},
function PC_LOCAL_ROLLBACK(test) {

View File

@ -23,7 +23,8 @@
return test.setRemoteDescription(
test.pcRemote,
new RTCSessionDescription({ type: "rollback" }),
STABLE);
STABLE)
.then(() => test.pcRemote.rollbackRemoteTracksIfNotNegotiated());
},
function PC_REMOTE_CHECK_CAN_TRICKLE_REVERT_SYNC(test) {

View File

@ -66,7 +66,7 @@
test.chain.replace("PC_LOCAL_GUM", [
function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
stream = canvas.captureStream(10);
test.pcLocal.attachMedia(stream, 'video', 'local');
test.pcLocal.attachLocalStream(stream);
},
function PC_LOCAL_CANVAS_ALTERNATE_COLOR(test) {
alternateRedAndGreen(helper, canvas, stream);
@ -107,13 +107,13 @@
test.chain.append([
function PC_REMOTE_WAIT_FOR_COLOR_CHANGE_1() {
var vremote = document.getElementById('pcRemote_remote1_video');
var vremote = test.pcRemote.remoteMediaElements[0];
ok(vremote, "Should have remote video element for pcRemote");
return waitForColorChange(helper, vremote);
},
function PC_REMOTE_CHECK_SIZE_1() {
var vlocal = document.getElementById('pcLocal_local1_video');
var vremote = document.getElementById('pcRemote_remote1_video');
var vlocal = test.pcLocal.localMediaElements[0];
var vremote = test.pcRemote.remoteMediaElements[0];
ok(vlocal, "Should have local video element for pcLocal");
ok(vremote, "Should have remote video element for pcRemote");
ok(vlocal.videoWidth > 0, "source width is positive");
@ -130,13 +130,13 @@
return test.pcRemote.waitForMediaFlow();
},
function PC_REMOTE_WAIT_FOR_COLOR_CHANGE_2() {
var vremote = document.getElementById('pcRemote_remote1_video');
var vremote = test.pcRemote.remoteMediaElements[0];
ok(vremote, "Should have remote video element for pcRemote");
return waitForColorChange(helper, vremote);
},
function PC_REMOTE_CHECK_SIZE_2() {
var vlocal = document.getElementById('pcLocal_local1_video');
var vremote = document.getElementById('pcRemote_remote1_video');
var vlocal = test.pcLocal.localMediaElements[0];
var vremote = test.pcRemote.remoteMediaElements[0];
ok(vlocal, "Should have local video element for pcLocal");
ok(vremote, "Should have remote video element for pcRemote");
ok(vlocal.videoWidth > 0, "source width is positive");
@ -150,7 +150,7 @@
selectRecvSsrc(test.pcRemote, 2);
},
function PC_REMOTE_ENSURE_NO_COLOR_CHANGE() {
var vremote = document.getElementById('pcRemote_remote1_video');
var vremote = test.pcRemote.remoteMediaElements[0];
ok(vremote, "Should have remote video element for pcRemote");
return ensureNoColorChange(helper, vremote);
},

View File

@ -20,19 +20,19 @@ runNetworkTest(() => {
test.setMediaConstraints([{audio: true, video: true, fake: true}], []);
test.chain.append([
function CHECK_ASSUMPTIONS() {
is(test.pcLocal.mediaElements.length, 1,
"pcLocal should only have one media element");
is(test.pcRemote.mediaElements.length, 1,
"pcRemote should only have one media element");
is(test.pcLocal.streams.length, 1,
"pcLocal should only have one stream (the local one)");
is(test.pcRemote.streams.length, 1,
"pcRemote should only have one stream (the remote one)");
is(test.pcLocal.localMediaElements.length, 1,
"pcLocal should have one media element");
is(test.pcRemote.remoteMediaElements.length, 1,
"pcRemote should have one media element");
is(test.pcLocal._pc.getLocalStreams().length, 1,
"pcLocal should have one stream");
is(test.pcRemote._pc.getRemoteStreams().length, 1,
"pcRemote should have one stream");
},
function CHECK_VIDEO() {
var h = new CaptureStreamTestHelper2D();
var localVideo = test.pcLocal.mediaElements[0];
var remoteVideo = test.pcRemote.mediaElements[0];
var localVideo = test.pcLocal.localMediaElements[0];
var remoteVideo = test.pcRemote.remoteMediaElements[0];
// We check a pixel somewhere away from the top left corner since
// MediaEngineDefault puts semi-transparent time indicators there.
const offsetX = 50;
@ -54,7 +54,7 @@ runNetworkTest(() => {
.then(() => checkVideoEnabled(remoteVideo))
.then(() => info("Disabling original"))
.then(() => test.pcLocal.streams[0].getVideoTracks()[0].enabled = false)
.then(() => test.pcLocal._pc.getLocalStreams()[0].getVideoTracks()[0].enabled = false)
.then(() => info("Checking local video disabled"))
.then(() => checkVideoDisabled(localVideo))
@ -63,8 +63,8 @@ runNetworkTest(() => {
},
function CHECK_AUDIO() {
var ac = new AudioContext();
var localAnalyser = new AudioStreamAnalyser(ac, test.pcLocal.streams[0]);
var remoteAnalyser = new AudioStreamAnalyser(ac, test.pcRemote.streams[0]);
var localAnalyser = new AudioStreamAnalyser(ac, test.pcLocal._pc.getLocalStreams()[0]);
var remoteAnalyser = new AudioStreamAnalyser(ac, test.pcRemote._pc.getRemoteStreams()[0]);
var checkAudio = (analyser, fun) => analyser.waitForAnalysisSuccess(fun);
@ -80,7 +80,7 @@ runNetworkTest(() => {
.then(() => info("Checking remote audio enabled"))
.then(() => checkAudioEnabled(remoteAnalyser))
.then(() => test.pcLocal.streams[0].getAudioTracks()[0].enabled = false)
.then(() => test.pcLocal._pc.getLocalStreams()[0].getAudioTracks()[0].enabled = false)
.then(() => info("Checking local audio disabled"))
.then(() => checkAudioDisabled(localAnalyser))

View File

@ -28,26 +28,26 @@ runNetworkTest(() => {
localVideoOriginal =
createMediaElement("audiovideo", "local-original");
localVideoOriginal.srcObject = stream;
test.pcLocal.attachMedia(originalStream.clone(), "audiovideo", "local");
test.pcLocal.attachLocalStream(originalStream.clone());
});
}
]);
test.chain.append([
function CHECK_ASSUMPTIONS() {
is(test.pcLocal.mediaElements.length, 1,
is(test.pcLocal.localMediaElements.length, 1,
"pcLocal should have one media element");
is(test.pcRemote.mediaElements.length, 1,
is(test.pcRemote.remoteMediaElements.length, 1,
"pcRemote should have one media element");
is(test.pcLocal.streams.length, 1,
is(test.pcLocal._pc.getLocalStreams().length, 1,
"pcLocal should have one stream");
is(test.pcRemote.streams.length, 1,
is(test.pcRemote._pc.getRemoteStreams().length, 1,
"pcRemote should have one stream");
},
function CHECK_VIDEO() {
info("Checking video");
var h = new CaptureStreamTestHelper2D();
var localVideoClone = test.pcLocal.mediaElements[0];
var remoteVideoClone = test.pcRemote.mediaElements[0];
var localVideoClone = test.pcLocal.localMediaElements[0];
var remoteVideoClone = test.pcRemote.remoteMediaElements[0];
// We check a pixel somewhere away from the top left corner since
// MediaEngineDefault puts semi-transparent time indicators there.
@ -85,7 +85,7 @@ runNetworkTest(() => {
.then(() => info("Re-enabling original; disabling clone"))
.then(() => originalStream.getVideoTracks()[0].enabled = true)
.then(() => test.pcLocal.streams[0].getVideoTracks()[0].enabled = false)
.then(() => test.pcLocal._pc.getLocalStreams()[0].getVideoTracks()[0].enabled = false)
.then(() => info("Checking local original enabled"))
.then(() => checkVideoEnabled(localVideoOriginal))
@ -99,9 +99,9 @@ runNetworkTest(() => {
var ac = new AudioContext();
var localAnalyserOriginal = new AudioStreamAnalyser(ac, originalStream);
var localAnalyserClone =
new AudioStreamAnalyser(ac, test.pcLocal.streams[0]);
new AudioStreamAnalyser(ac, test.pcLocal._pc.getLocalStreams()[0]);
var remoteAnalyserClone =
new AudioStreamAnalyser(ac, test.pcRemote.streams[0]);
new AudioStreamAnalyser(ac, test.pcRemote._pc.getRemoteStreams()[0]);
var freq1k = localAnalyserOriginal.binIndexForFrequency(1000);
var checkAudioEnabled = analyser =>
@ -129,7 +129,7 @@ runNetworkTest(() => {
.then(() => info("Re-enabling original; disabling clone"))
.then(() => originalStream.getAudioTracks()[0].enabled = true)
.then(() => test.pcLocal.streams[0].getAudioTracks()[0].enabled = false)
.then(() => test.pcLocal._pc.getLocalStreams()[0].getAudioTracks()[0].enabled = false)
.then(() => info("Checking local original enabled"))
.then(() => checkAudioEnabled(localAnalyserOriginal))

View File

@ -31,18 +31,18 @@
test.chain.append([
function CHECK_ASSUMPTIONS() {
is(test.pcLocal.mediaElements.length, 1,
"pcLocal should only have one media element");
is(test.pcRemote.mediaElements.length, 1,
"pcRemote should only have one media element");
is(test.pcLocal.streams.length, 1,
"pcLocal should only have one stream (the local one)");
is(test.pcRemote.streams.length, 1,
"pcRemote should only have one stream (the remote one)");
is(test.pcLocal.localMediaElements.length, 1,
"pcLocal should have one media element");
is(test.pcRemote.remoteMediaElements.length, 1,
"pcRemote should have one media element");
is(test.pcLocal._pc.getLocalStreams().length, 1,
"pcLocal should have one stream");
is(test.pcRemote._pc.getRemoteStreams().length, 1,
"pcRemote should have one stream");
},
function CHECK_AUDIO() {
local1Analyser = new AudioStreamAnalyser(ac, test.pcLocal.streams[0]);
remote1Analyser = new AudioStreamAnalyser(ac, test.pcRemote.streams[0]);
local1Analyser = new AudioStreamAnalyser(ac, test.pcLocal._pc.getLocalStreams()[0]);
remote1Analyser = new AudioStreamAnalyser(ac, test.pcRemote._pc.getRemoteStreams()[0]);
freq = local1Analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
@ -52,7 +52,7 @@
.then(() => info("Checking remote audio enabled"))
.then(() => checkAudioEnabled(remote1Analyser, freq))
.then(() => test.pcLocal.streams[0].getAudioTracks()[0].enabled = false)
.then(() => test.pcLocal._pc.getLocalStreams()[0].getAudioTracks()[0].enabled = false)
.then(() => info("Checking local audio disabled"))
.then(() => checkAudioDisabled(local1Analyser, freq))
@ -73,18 +73,18 @@
test.chain.append([
function CHECK_ASSUMPTIONS2() {
is(test.pcLocal.mediaElements.length, 2,
is(test.pcLocal.localMediaElements.length, 2,
"pcLocal should have two media elements");
is(test.pcRemote.mediaElements.length, 2,
is(test.pcRemote.remoteMediaElements.length, 2,
"pcRemote should have two media elements");
is(test.pcLocal.streams.length, 2,
is(test.pcLocal._pc.getLocalStreams().length, 2,
"pcLocal should have two streams");
is(test.pcRemote.streams.length, 2,
is(test.pcRemote._pc.getRemoteStreams().length, 2,
"pcRemote should have two streams");
},
function RE_CHECK_AUDIO() {
local2Analyser = new AudioStreamAnalyser(ac, test.pcLocal.streams[1]);
remote2Analyser = new AudioStreamAnalyser(ac, test.pcRemote.streams[1]);
local2Analyser = new AudioStreamAnalyser(ac, test.pcLocal._pc.getLocalStreams()[1]);
remote2Analyser = new AudioStreamAnalyser(ac, test.pcRemote._pc.getRemoteStreams()[1]);
freq = local2Analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
@ -99,8 +99,8 @@
.then(() => info("Checking remote2 audio enabled"))
.then(() => checkAudioEnabled(remote2Analyser, freq))
.then(() => test.pcLocal.streams[1].getAudioTracks()[0].enabled = false)
.then(() => test.pcLocal.streams[0].getAudioTracks()[0].enabled = true)
.then(() => test.pcLocal._pc.getLocalStreams()[1].getAudioTracks()[0].enabled = false)
.then(() => test.pcLocal._pc.getLocalStreams()[0].getAudioTracks()[0].enabled = true)
.then(() => info("Checking local2 audio disabled"))
.then(() => checkAudioDisabled(local2Analyser, freq))

View File

@ -32,13 +32,13 @@ runNetworkTest(() => {
},
function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
stream1 = canvas1.captureStream(0);
test.pcLocal.attachMedia(stream1, 'video', 'local');
test.pcLocal.attachLocalStream(stream1);
}
]);
test.chain.append([
function FIND_REMOTE_VIDEO() {
vremote1 = document.getElementById('pcRemote_remote1_video');
vremote1 = test.pcRemote.remoteMediaElements[0];
ok(!!vremote1, "Should have remote video element for pcRemote");
},
function WAIT_FOR_REMOTE_GREEN() {
@ -67,14 +67,14 @@ runNetworkTest(() => {
// can't use test.pcLocal.getAllUserMedia([{video: true}]);
// because it doesn't let us substitute the capture stream
return test.pcLocal.attachMedia(stream2, 'video', 'local');
test.pcLocal.attachLocalStream(stream2);
}
]
);
test.chain.append([
function FIND_REMOTE2_VIDEO() {
vremote2 = document.getElementById('pcRemote_remote2_video');
vremote2 = test.pcRemote.remoteMediaElements[1];
ok(!!vremote2, "Should have remote2 video element for pcRemote");
},
function WAIT_FOR_REMOTE2_BLUE() {

View File

@ -27,7 +27,7 @@ runNetworkTest(function() {
oscillator.start();
var dest = test.audioContext.createMediaStreamDestination();
oscillator.connect(dest);
test.pcLocal.attachMedia(dest.stream, 'audio', 'local');
test.pcLocal.attachLocalStream(dest.stream);
}
]);
test.chain.append([

View File

@ -515,8 +515,6 @@ protected:
private:
void Init();
void AppendSamplesToTrack(int16_t *audio10ms, int length);
webrtc::VoiceEngine* mVoiceEngine;
RefPtr<mozilla::AudioInput> mAudioInput;
RefPtr<WebRTCAudioDataListener> mListener;

View File

@ -495,11 +495,7 @@ MediaEngineWebRTCMicrophoneSource::NotifyInputData(MediaStreamGraph* aGraph,
int16_t *packet = mInputBuffer.get();
mPacketizer->Output(packet);
if (mEchoOn || mAgcOn || mNoiseOn) {
mVoERender->ExternalRecordingInsertData(packet, samplesPerPacket, aRate, 0);
} else {
AppendSamplesToTrack(packet, samplesPerPacket);
}
mVoERender->ExternalRecordingInsertData(packet, samplesPerPacket, aRate, 0);
}
}
@ -708,16 +704,9 @@ MediaEngineWebRTCMicrophoneSource::Process(int channel,
}
MonitorAutoLock lock(mMonitor);
if (mState != kStarted) {
if (mState != kStarted)
return;
}
AppendSamplesToTrack(audio10ms, length);
}
void
MediaEngineWebRTCMicrophoneSource::AppendSamplesToTrack(sample *audio10ms, int length)
{
uint32_t len = mSources.Length();
for (uint32_t i = 0; i < len; i++) {
RefPtr<SharedBuffer> buffer = SharedBuffer::Create(length * sizeof(sample));
@ -748,6 +737,8 @@ MediaEngineWebRTCMicrophoneSource::AppendSamplesToTrack(sample *audio10ms, int l
NS_DISPATCH_NORMAL);
}
}
return;
}
void

View File

@ -29,7 +29,7 @@ XPCOMUtils.defineLazyGetter(this, "PushService", function() {
// on `nsIPushService` so that JS callers only need to import this service.
const OBSERVER_TOPIC_PUSH = "push-message";
const OBSERVER_TOPIC_SUBSCRIPTION_CHANGE = "push-subscription-change";
const OBSERVER_TOPIC_SUBSCRIPTION_LOST = "push-subscription-lost";
const OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED = "push-subscription-modified";
/**
* `PushServiceBase`, `PushServiceParent`, and `PushServiceContent` collectively
@ -61,7 +61,7 @@ PushServiceBase.prototype = {
pushTopic: OBSERVER_TOPIC_PUSH,
subscriptionChangeTopic: OBSERVER_TOPIC_SUBSCRIPTION_CHANGE,
subscriptionLostTopic: OBSERVER_TOPIC_SUBSCRIPTION_LOST,
subscriptionModifiedTopic: OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED,
_handleReady() {},

View File

@ -7,8 +7,6 @@
#include "nsContentUtils.h"
#include "nsCOMPtr.h"
#include "nsICategoryManager.h"
#include "nsIPushErrorReporter.h"
#include "nsISupportsPrimitives.h"
#include "nsIXULRuntime.h"
#include "nsNetUtil.h"
#include "nsXPCOM.h"
@ -70,7 +68,7 @@ NS_IMETHODIMP
PushNotifier::NotifySubscriptionChange(const nsACString& aScope,
nsIPrincipal* aPrincipal)
{
nsresult rv = NotifySubscriptionChangeObservers(aScope);
nsresult rv = NotifySubscriptionChangeObservers(aScope, aPrincipal);
Unused << NS_WARN_IF(NS_FAILED(rv));
if (XRE_IsContentProcess()) {
@ -79,7 +77,7 @@ PushNotifier::NotifySubscriptionChange(const nsACString& aScope,
if (!NS_WARN_IF(!parentActor)) {
Unused << NS_WARN_IF(
!parentActor->SendNotifyPushSubscriptionChangeObservers(
PromiseFlatCString(aScope)));
PromiseFlatCString(aScope), IPC::Principal(aPrincipal)));
}
}
@ -91,23 +89,18 @@ PushNotifier::NotifySubscriptionChange(const nsACString& aScope,
}
NS_IMETHODIMP
PushNotifier::NotifySubscriptionLost(const nsACString& aScope,
nsIPrincipal* aPrincipal,
uint16_t aReason)
PushNotifier::NotifySubscriptionModified(const nsACString& aScope,
nsIPrincipal* aPrincipal)
{
if (NS_WARN_IF(aReason < nsIPushErrorReporter::UNSUBSCRIBE_MANUAL ||
aReason > nsIPushErrorReporter::UNSUBSCRIBE_PERMISSION_REVOKED)) {
return NS_ERROR_INVALID_ARG;
}
nsresult rv = NotifySubscriptionLostObservers(aScope, aReason);
nsresult rv = NotifySubscriptionModifiedObservers(aScope, aPrincipal);
Unused << NS_WARN_IF(NS_FAILED(rv));
if (XRE_IsContentProcess()) {
ContentChild* parentActor = ContentChild::GetSingleton();
if (!NS_WARN_IF(!parentActor)) {
Unused << NS_WARN_IF(
!parentActor->SendNotifyPushSubscriptionLostObservers(
PromiseFlatCString(aScope), aReason));
!parentActor->SendNotifyPushSubscriptionModifiedObservers(
PromiseFlatCString(aScope), IPC::Principal(aPrincipal)));
}
}
@ -141,7 +134,7 @@ PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
const Maybe<nsTArray<uint8_t>>& aData)
{
// Notify XPCOM observers in the current process.
nsresult rv = NotifyPushObservers(aScope, aData);
nsresult rv = NotifyPushObservers(aScope, aPrincipal, aData);
Unused << NS_WARN_IF(NS_FAILED(rv));
if (XRE_IsContentProcess()) {
@ -153,12 +146,13 @@ PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
if (aData) {
Unused << NS_WARN_IF(
!parentActor->SendNotifyPushObserversWithData(
PromiseFlatCString(aScope), PromiseFlatString(aMessageId),
aData.ref()));
PromiseFlatCString(aScope), IPC::Principal(aPrincipal),
PromiseFlatString(aMessageId), aData.ref()));
} else {
Unused << NS_WARN_IF(
!parentActor->SendNotifyPushObservers(
PromiseFlatCString(aScope), PromiseFlatString(aMessageId)));
PromiseFlatCString(aScope), IPC::Principal(aPrincipal),
PromiseFlatString(aMessageId)));
}
}
}
@ -177,9 +171,7 @@ PushNotifier::NotifyPushWorkers(const nsACString& aScope,
const Maybe<nsTArray<uint8_t>>& aData)
{
AssertIsOnMainThread();
if (!aPrincipal) {
return NS_ERROR_INVALID_ARG;
}
NS_ENSURE_ARG(aPrincipal);
if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) {
// Notify the worker from the current process. Either we're running in
@ -222,9 +214,7 @@ PushNotifier::NotifySubscriptionChangeWorkers(const nsACString& aScope,
nsIPrincipal* aPrincipal)
{
AssertIsOnMainThread();
if (!aPrincipal) {
return NS_ERROR_INVALID_ARG;
}
NS_ENSURE_ARG(aPrincipal);
if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) {
// Content process or e10s disabled.
@ -308,31 +298,31 @@ PushNotifier::NotifyErrorWorkers(const nsACString& aScope,
nsresult
PushNotifier::NotifyPushObservers(const nsACString& aScope,
nsIPrincipal* aPrincipal,
const Maybe<nsTArray<uint8_t>>& aData)
{
nsCOMPtr<nsIPushMessage> message = nullptr;
nsCOMPtr<nsIPushData> data;
if (aData) {
message = new PushMessage(aData.ref());
data = new PushData(aData.ref());
}
nsCOMPtr<nsIPushMessage> message = new PushMessage(aPrincipal, data);
return DoNotifyObservers(message, OBSERVER_TOPIC_PUSH, aScope);
}
nsresult
PushNotifier::NotifySubscriptionChangeObservers(const nsACString& aScope)
PushNotifier::NotifySubscriptionChangeObservers(const nsACString& aScope,
nsIPrincipal* aPrincipal)
{
return DoNotifyObservers(nullptr, OBSERVER_TOPIC_SUBSCRIPTION_CHANGE, aScope);
return DoNotifyObservers(aPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_CHANGE,
aScope);
}
nsresult
PushNotifier::NotifySubscriptionLostObservers(const nsACString& aScope,
uint16_t aReason)
PushNotifier::NotifySubscriptionModifiedObservers(const nsACString& aScope,
nsIPrincipal* aPrincipal)
{
nsCOMPtr<nsISupportsPRUint16> wrapper =
do_CreateInstance(NS_SUPPORTS_PRUINT16_CONTRACTID);
if (NS_WARN_IF(!wrapper || NS_FAILED(wrapper->SetData(aReason)))) {
return NS_ERROR_FAILURE;
}
return DoNotifyObservers(wrapper, OBSERVER_TOPIC_SUBSCRIPTION_LOST, aScope);
return DoNotifyObservers(aPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED,
aScope);
}
nsresult
@ -372,25 +362,25 @@ PushNotifier::ShouldNotifyWorkers(nsIPrincipal* aPrincipal)
Preferences::GetBool("dom.push.testing.notifyWorkers", true);
}
PushMessage::PushMessage(const nsTArray<uint8_t>& aData)
PushData::PushData(const nsTArray<uint8_t>& aData)
: mData(aData)
{}
PushMessage::~PushMessage()
PushData::~PushData()
{}
NS_IMPL_CYCLE_COLLECTION_0(PushMessage)
NS_IMPL_CYCLE_COLLECTION_0(PushData)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushMessage)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushMessage)
NS_INTERFACE_MAP_ENTRY(nsIPushMessage)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushData)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushData)
NS_INTERFACE_MAP_ENTRY(nsIPushData)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMessage)
NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessage)
NS_IMPL_CYCLE_COLLECTING_ADDREF(PushData)
NS_IMPL_CYCLE_COLLECTING_RELEASE(PushData)
nsresult
PushMessage::EnsureDecodedText()
PushData::EnsureDecodedText()
{
if (mData.IsEmpty() || !mDecodedText.IsEmpty()) {
return NS_OK;
@ -408,7 +398,7 @@ PushMessage::EnsureDecodedText()
}
NS_IMETHODIMP
PushMessage::Text(nsAString& aText)
PushData::Text(nsAString& aText)
{
nsresult rv = EnsureDecodedText();
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -419,8 +409,8 @@ PushMessage::Text(nsAString& aText)
}
NS_IMETHODIMP
PushMessage::Json(JSContext* aCx,
JS::MutableHandle<JS::Value> aResult)
PushData::Json(JSContext* aCx,
JS::MutableHandle<JS::Value> aResult)
{
nsresult rv = EnsureDecodedText();
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -432,11 +422,11 @@ PushMessage::Json(JSContext* aCx,
}
NS_IMETHODIMP
PushMessage::Binary(uint32_t* aDataLen, uint8_t** aData)
PushData::Binary(uint32_t* aDataLen, uint8_t** aData)
{
if (!aDataLen || !aData) {
return NS_ERROR_INVALID_ARG;
}
NS_ENSURE_ARG_POINTER(aDataLen);
NS_ENSURE_ARG_POINTER(aData);
*aData = nullptr;
if (mData.IsEmpty()) {
*aDataLen = 0;
@ -453,5 +443,43 @@ PushMessage::Binary(uint32_t* aDataLen, uint8_t** aData)
return NS_OK;
}
PushMessage::PushMessage(nsIPrincipal* aPrincipal, nsIPushData* aData)
: mPrincipal(aPrincipal)
, mData(aData)
{}
PushMessage::~PushMessage()
{}
NS_IMPL_CYCLE_COLLECTION(PushMessage, mPrincipal, mData)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushMessage)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushMessage)
NS_INTERFACE_MAP_ENTRY(nsIPushMessage)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMessage)
NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessage)
NS_IMETHODIMP
PushMessage::GetPrincipal(nsIPrincipal** aPrincipal)
{
NS_ENSURE_ARG_POINTER(aPrincipal);
nsCOMPtr<nsIPrincipal> principal = mPrincipal;
principal.forget(aPrincipal);
return NS_OK;
}
NS_IMETHODIMP
PushMessage::GetData(nsIPushData** aData)
{
NS_ENSURE_ARG_POINTER(aData);
nsCOMPtr<nsIPushData> data = mData;
data.forget(aData);
return NS_OK;
}
} // namespace dom
} // namespace mozilla

View File

@ -43,22 +43,20 @@ private:
};
/**
* `PushMessage` implements the `nsIPushMessage` interface, similar to
* the `PushMessageData` WebIDL interface. Instances of this class are
* passed as the subject of `push-message` observer notifications.
* `PushData` provides methods for retrieving push message data in different
* formats. This class is similar to the `PushMessageData` WebIDL interface.
*/
class PushMessage final : public nsIPushMessage
class PushData final : public nsIPushData
{
public:
explicit PushMessage(const nsTArray<uint8_t>& aData);
explicit PushData(const nsTArray<uint8_t>& aData);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PushMessage,
nsIPushMessage)
NS_DECL_NSIPUSHMESSAGE
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PushData, nsIPushData)
NS_DECL_NSIPUSHDATA
private:
virtual ~PushMessage();
virtual ~PushData();
nsresult EnsureDecodedText();
@ -66,6 +64,27 @@ private:
nsString mDecodedText;
};
/**
* `PushMessage` exposes the subscription principal and data for a push
* message. Each `push-message` observer receives an instance of this class
* as the subject.
*/
class PushMessage final : public nsIPushMessage
{
public:
PushMessage(nsIPrincipal* aPrincipal, nsIPushData* aData);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PushMessage, nsIPushMessage)
NS_DECL_NSIPUSHMESSAGE
private:
virtual ~PushMessage();
nsCOMPtr<nsIPrincipal> mPrincipal;
nsCOMPtr<nsIPushData> mData;
};
} // namespace dom
} // namespace mozilla

View File

@ -357,8 +357,7 @@ this.PushService = {
console.debug("backgroundUnregister: Notifying server", record);
this._sendUnregister(record, reason).then(() => {
gPushNotifier.notifySubscriptionLost(record.scope, record.principal,
reason);
gPushNotifier.notifySubscriptionModified(record.scope, record.principal);
}).catch(e => {
console.error("backgroundUnregister: Error notifying server", e);
});
@ -848,6 +847,9 @@ this.PushService = {
return newRecord;
});
});
}).then(record => {
gPushNotifier.notifySubscriptionModified(record.scope, record.principal);
return record;
});
},
@ -899,13 +901,18 @@ this.PushService = {
}
return record;
}).then(record => {
if (record && record.isExpired()) {
this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_EXPIRED);
// Drop the registration in the background. If the user returns to the
// site, the service worker will be notified on the next `idle-daily`
// event.
this._backgroundUnregister(record,
Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED);
if (record) {
if (record.isExpired()) {
this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_EXPIRED);
// Drop the registration in the background. If the user returns to the
// site, the service worker will be notified on the next `idle-daily`
// event.
this._backgroundUnregister(record,
Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED);
} else {
gPushNotifier.notifySubscriptionModified(record.scope,
record.principal);
}
}
if (this._updateQuotaTestCallback) {
// Callback so that test may be notified when the quota update is complete.
@ -1032,6 +1039,8 @@ this.PushService = {
err => this._onRegisterError(err))
.then(record => {
this._deletePendingRequest(aPageRecord);
gPushNotifier.notifySubscriptionModified(record.scope,
record.principal);
return record.toSubscription();
}, err => {
this._deletePendingRequest(aPageRecord);
@ -1179,8 +1188,8 @@ this.PushService = {
this._sendUnregister(record, reason),
this._db.delete(record.keyID),
]).then(() => {
gPushNotifier.notifySubscriptionLost(record.scope, record.principal,
reason);
gPushNotifier.notifySubscriptionModified(record.scope,
record.principal);
return true;
});
});
@ -1284,19 +1293,7 @@ this.PushService = {
console.debug("onPermissionChange()");
if (data == "cleared") {
// If the permission list was cleared, drop all registrations
// that are subject to quota.
return this._db.clearIf(record => {
if (record.quotaApplies()) {
if (!record.isExpired()) {
// Drop the registration in the background.
this._backgroundUnregister(record,
Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED);
}
return true;
}
return false;
});
return this._clearPermissions();
}
let permission = subject.QueryInterface(Ci.nsIPermission);
@ -1307,6 +1304,32 @@ this.PushService = {
return this._updatePermission(permission, data);
},
_clearPermissions() {
console.debug("clearPermissions()");
let subscriptionModifications = [];
return this._db.clearIf(record => {
if (!record.quotaApplies()) {
// Only drop registrations that are subject to quota.
return false;
}
if (record.isExpired()) {
// Fire subscription modified notifications for expired
// records after the IndexedDB transaction has committed.
subscriptionModifications.push(record);
} else {
// Drop unexpired registrations in the background.
this._backgroundUnregister(record,
Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED);
}
return true;
}).then(() => {
subscriptionModifications.forEach(record =>
gPushNotifier.notifySubscriptionModified(record.scope, record.principal)
);
});
},
_updatePermission: function(permission, type) {
console.debug("updatePermission()");

View File

@ -16,7 +16,7 @@ function PushServiceHandler() {
this.observed = [];
Services.obs.addObserver(this, pushService.pushTopic, false);
Services.obs.addObserver(this, pushService.subscriptionChangeTopic, false);
Services.obs.addObserver(this, pushService.subscriptionLostTopic, false);
Services.obs.addObserver(this, pushService.subscriptionModifiedTopic, false);
}
PushServiceHandler.prototype = {

View File

@ -24,21 +24,23 @@ add_test(function test_service_instantiation() {
equal(handlerService.observed.length, 1);
equal(handlerService.observed[0].topic, pushService.pushTopic);
let message = handlerService.observed[0].subject.QueryInterface(Ci.nsIPushMessage);
equal(message.principal, principal);
strictEqual(message.data, null);
equal(handlerService.observed[0].data, scope);
// and a subscription change.
pushNotifier.notifySubscriptionChange(scope, principal);
equal(handlerService.observed.length, 2);
equal(handlerService.observed[1].topic, pushService.subscriptionChangeTopic);
equal(handlerService.observed[1].subject, principal);
equal(handlerService.observed[1].data, scope);
// and a subscription lost event.
let reason = Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED;
pushNotifier.notifySubscriptionLost(scope, principal, reason);
// and a subscription modified event.
pushNotifier.notifySubscriptionModified(scope, principal);
equal(handlerService.observed.length, 3);
equal(handlerService.observed[2].topic, pushService.subscriptionLostTopic);
let wrapper = handlerService.observed[2].subject.QueryInterface(Ci.nsISupportsPRUint16);
equal(wrapper.data, reason);
equal(handlerService.observed[2].topic, pushService.subscriptionModifiedTopic);
equal(handlerService.observed[2].subject, principal);
equal(handlerService.observed[2].data, scope);
run_next_test();

View File

@ -12,26 +12,39 @@ add_task(function* test_observer_notifications() {
PushServiceComponent.pushTopic);
let subChangePromise = promiseObserverNotification(
PushServiceComponent.subscriptionChangeTopic);
let subLostPromise = promiseObserverNotification(
PushServiceComponent.subscriptionLostTopic);
let subModifiedPromise = promiseObserverNotification(
PushServiceComponent.subscriptionModifiedTopic);
yield run_test_in_child('./test_handler_service.js');
let {data: notifyScope} = yield notifyPromise;
equal(notifyScope, 'chrome://test-scope',
'Should forward push notifications with the correct scope');
let {data: subChangeScope} = yield subChangePromise;
equal(subChangeScope, 'chrome://test-scope',
'Should forward subscription change notifications with the correct scope');
let principal = Services.scriptSecurityManager.getSystemPrincipal();
let {
subject: subLostSubject,
data: subLostScope
} = yield subLostPromise;
equal(subLostScope, 'chrome://test-scope',
'Should forward subscription lost notifications with the correct scope');
let wrapper = subLostSubject.QueryInterface(Ci.nsISupportsPRUint16);
equal(wrapper.data, Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED,
'Should forward subscription lost reasons');
data: notifyScope,
subject: notifySubject,
} = yield notifyPromise;
equal(notifyScope, 'chrome://test-scope',
'Should forward push notifications with the correct scope');
let message = notifySubject.QueryInterface(Ci.nsIPushMessage);
equal(message.principal, principal,
'Should include the principal in the push message');
strictEqual(message.data, null, 'Should not include data');
let {
data: subChangeScope,
subject: subChangePrincipal,
} = yield subChangePromise;
equal(subChangeScope, 'chrome://test-scope',
'Should forward subscription change notifications with the correct scope');
equal(subChangePrincipal, principal,
'Should pass the principal as the subject of a change notification');
let {
data: subModifiedScope,
subject: subModifiedPrincipal,
} = yield subModifiedPromise;
equal(subModifiedScope, 'chrome://test-scope',
'Should forward subscription modified notifications with the correct scope');
equal(subModifiedPrincipal, principal,
'Should pass the principal as the subject of a modified notification');
});

View File

@ -245,7 +245,7 @@ add_task(function* test_notification_ack_data() {
let sendAndReceive = testData => {
let messageReceived = testData.receive ? promiseObserverNotification(PushServiceComponent.pushTopic, (subject, data) => {
let notification = subject.QueryInterface(Ci.nsIPushMessage);
let notification = subject.QueryInterface(Ci.nsIPushMessage).data;
equal(notification.text(), testData.receive.data,
'Check data for notification ' + testData.version);
equal(data, testData.receive.scope,

View File

@ -150,28 +150,28 @@ add_task(function* test_pushNotifications() {
let notifyPromise = Promise.all([
promiseObserverNotification(PushServiceComponent.pushTopic, function(subject, data) {
var message = subject.QueryInterface(Ci.nsIPushMessage);
var message = subject.QueryInterface(Ci.nsIPushMessage).data;
if (message && (data == "https://example.com/page/1")){
equal(message.text(), "Some message", "decoded message is incorrect");
return true;
}
}),
promiseObserverNotification(PushServiceComponent.pushTopic, function(subject, data) {
var message = subject.QueryInterface(Ci.nsIPushMessage);
var message = subject.QueryInterface(Ci.nsIPushMessage).data;
if (message && (data == "https://example.com/page/2")){
equal(message.text(), "Some message", "decoded message is incorrect");
return true;
}
}),
promiseObserverNotification(PushServiceComponent.pushTopic, function(subject, data) {
var message = subject.QueryInterface(Ci.nsIPushMessage);
var message = subject.QueryInterface(Ci.nsIPushMessage).data;
if (message && (data == "https://example.com/page/3")){
equal(message.text(), "Some message", "decoded message is incorrect");
return true;
}
}),
promiseObserverNotification(PushServiceComponent.pushTopic, function(subject, data) {
var message = subject.QueryInterface(Ci.nsIPushMessage);
var message = subject.QueryInterface(Ci.nsIPushMessage).data;
if (message && (data == "https://example.com/page/4")){
equal(message.text(), "Yet another message", "decoded message is incorrect");
return true;

View File

@ -56,8 +56,9 @@ add_task(function* test_notification_version_string() {
}
});
let {subject: notification, data: scope} = yield notifyPromise;
equal(notification, null, 'Unexpected data for Simple Push message');
let {subject: message, data: scope} = yield notifyPromise;
equal(message.QueryInterface(Ci.nsIPushMessage).data, null,
'Unexpected data for Simple Push message');
yield ackPromise;

View File

@ -10,20 +10,21 @@ function run_test() {
add_task(function* test_notifyWithData() {
let textData = '{"hello":"world"}';
let data = new TextEncoder('utf-8').encode(textData);
let payload = new TextEncoder('utf-8').encode(textData);
let notifyPromise =
promiseObserverNotification(PushServiceComponent.pushTopic);
pushNotifier.notifyPushWithData('chrome://notify-test', systemPrincipal,
'' /* messageId */, data.length, data);
'' /* messageId */, payload.length, payload);
let message = (yield notifyPromise).subject.QueryInterface(Ci.nsIPushMessage);
deepEqual(message.json(), {
let data = (yield notifyPromise).subject.QueryInterface(
Ci.nsIPushMessage).data;
deepEqual(data.json(), {
hello: 'world',
}, 'Should extract JSON values');
deepEqual(message.binary(), Array.from(data),
deepEqual(data.binary(), Array.from(payload),
'Should extract raw binary data');
equal(message.text(), textData, 'Should extract text data');
equal(data.text(), textData, 'Should extract text data');
});
add_task(function* test_empty_notifyWithData() {
@ -32,9 +33,10 @@ add_task(function* test_empty_notifyWithData() {
pushNotifier.notifyPushWithData('chrome://notify-test', systemPrincipal,
'' /* messageId */, 0, null);
let message = (yield notifyPromise).subject.QueryInterface(Ci.nsIPushMessage);
throws(_ => message.json(),
let data = (yield notifyPromise).subject.QueryInterface(
Ci.nsIPushMessage).data;
throws(_ => data.json(),
'Should throw an error when parsing an empty string as JSON');
strictEqual(message.text(), '', 'Should return an empty string');
deepEqual(message.binary(), [], 'Should return an empty array');
strictEqual(data.text(), '', 'Should return an empty string');
deepEqual(data.binary(), [], 'Should return an empty array');
});

View File

@ -50,15 +50,25 @@ function makePushPermission(url, capability) {
};
}
function promiseSubscriptionChanges(count) {
function promiseObserverNotifications(topic, count) {
let notifiedScopes = [];
let subChangePromise = promiseObserverNotification(PushServiceComponent.subscriptionChangeTopic, (subject, data) => {
let subChangePromise = promiseObserverNotification(topic, (subject, data) => {
notifiedScopes.push(data);
return notifiedScopes.length == count;
});
return subChangePromise.then(_ => notifiedScopes.sort());
}
function promiseSubscriptionChanges(count) {
return promiseObserverNotifications(
PushServiceComponent.subscriptionChangeTopic, count);
}
function promiseSubscriptionModifications(count) {
return promiseObserverNotifications(
PushServiceComponent.subscriptionModifiedTopic, count);
}
function allExpired(...keyIDs) {
return Promise.all(keyIDs.map(
keyID => db.getByKeyID(keyID)
@ -150,6 +160,8 @@ add_task(function* test_permissions_allow_added() {
});
add_task(function* test_permissions_allow_deleted() {
let subModifiedPromise = promiseSubscriptionModifications(1);
let unregisterPromise = new Promise(resolve => unregisterDefers[
'active-allow'] = resolve);
@ -160,12 +172,19 @@ add_task(function* test_permissions_allow_deleted() {
yield unregisterPromise;
let notifiedScopes = yield subModifiedPromise;
deepEqual(notifiedScopes, [
'https://example.info/page/1',
], 'Wrong scopes modified after deleting allow');
let record = yield db.getByKeyID('active-allow');
ok(record.isExpired(),
'Should expire active record after deleting allow');
});
add_task(function* test_permissions_deny_added() {
let subModifiedPromise = promiseSubscriptionModifications(2);
let unregisterPromise = Promise.all([
new Promise(resolve => unregisterDefers[
'active-deny-added-1'] = resolve),
@ -179,6 +198,12 @@ add_task(function* test_permissions_deny_added() {
);
yield unregisterPromise;
let notifiedScopes = yield subModifiedPromise;
deepEqual(notifiedScopes, [
'https://example.net/green',
'https://example.net/ham',
], 'Wrong scopes modified after adding deny');
let isExpired = yield allExpired(
'active-deny-added-1',
'expired-deny-added'
@ -225,6 +250,8 @@ add_task(function* test_permissions_allow_changed() {
});
add_task(function* test_permissions_deny_changed() {
let subModifiedPromise = promiseSubscriptionModifications(1);
let unregisterPromise = new Promise(resolve => unregisterDefers[
'active-deny-changed'] = resolve);
@ -235,12 +262,19 @@ add_task(function* test_permissions_deny_changed() {
yield unregisterPromise;
let notifiedScopes = yield subModifiedPromise;
deepEqual(notifiedScopes, [
'https://example.xyz/page/1',
], 'Wrong scopes modified after changing to deny');
let record = yield db.getByKeyID('active-deny-changed');
ok(record.isExpired(),
'Should expire active record after changing to allow');
'Should expire active record after changing to deny');
});
add_task(function* test_permissions_clear() {
let subModifiedPromise = promiseSubscriptionModifications(3);
let records = yield db.getAllKeyIDs();
deepEqual(records.map(record => record.keyID).sort(), [
'active-allow',
@ -256,6 +290,13 @@ add_task(function* test_permissions_clear() {
yield unregisterPromise;
let notifiedScopes = yield subModifiedPromise;
deepEqual(notifiedScopes, [
'https://example.edu/lonely',
'https://example.info/page/1',
'https://example.xyz/page/1',
], 'Wrong scopes modified after clearing registrations');
records = yield db.getAllKeyIDs();
deepEqual(records.map(record => record.keyID).sort(), [
'never-expires',

View File

@ -58,6 +58,14 @@ add_task(function* test_expiration_origin_threshold() {
return updates == numMessages;
});
let modifications = 0;
let modifiedPromise = promiseObserverNotification(PushServiceComponent.subscriptionModifiedTopic, (subject, data) => {
// Each subscription should be modified twice: once to update the message
// count and last push time, and the second time to update the quota.
modifications++;
return modifications == numMessages * 2;
});
let updateQuotaPromise = new Promise((resolve, reject) => {
let quotaUpdateCount = 0;
PushService._updateQuotaTestCallback = function() {
@ -105,6 +113,7 @@ add_task(function* test_expiration_origin_threshold() {
yield notifyPromise;
yield updateQuotaPromise;
yield modifiedPromise;
let expiredRecord = yield db.getByKeyID('f56645a9-1f32-4655-92ad-ddc37f6d54fb');
notStrictEqual(expiredRecord.quota, 0, 'Expired record not updated');

View File

@ -52,6 +52,9 @@ add_task(function* test_register_success() {
}
});
let subModifiedPromise = promiseObserverNotification(
PushServiceComponent.subscriptionModifiedTopic);
let newRecord = yield PushService.register({
scope: 'https://example.org/1',
originAttributes: ChromeUtils.originAttributesToSuffix(
@ -60,6 +63,10 @@ add_task(function* test_register_success() {
equal(newRecord.endpoint, 'https://example.com/update/1',
'Wrong push endpoint in registration record');
let {data: subModifiedScope} = yield subModifiedPromise;
equal(subModifiedScope, 'https://example.org/1',
'Should fire a subscription modified event after subscribing');
let record = yield db.getByKeyID(channelID);
equal(record.channelID, channelID,
'Wrong channel ID in database record');

View File

@ -53,10 +53,18 @@ add_task(function* test_unregister_success() {
}
});
let subModifiedPromise = promiseObserverNotification(
PushServiceComponent.subscriptionModifiedTopic);
yield PushService.unregister({
scope: 'https://example.com/page/unregister-success',
originAttributes: '',
});
let {data: subModifiedScope} = yield subModifiedPromise;
equal(subModifiedScope, 'https://example.com/page/unregister-success',
'Should fire a subscription modified event after unsubscribing');
let record = yield db.getByKeyID(channelID);
ok(!record, 'Unregister did not remove record');

View File

@ -686,7 +686,7 @@ nsSMILAnimationController::GetTargetIdentifierForAnimation(
} else {
nsCSSProperty prop =
nsCSSProps::LookupProperty(nsDependentAtomString(attributeName),
nsCSSProps::eEnabledForAllContent);
CSSEnabledState::eForAllContent);
isCSS = nsSMILCSSProperty::IsPropertyAnimatable(prop);
}
}

View File

@ -126,7 +126,7 @@ nsSMILCompositor::CreateSMILAttr()
if (mKey.mIsCSS) {
nsCSSProperty propId =
nsCSSProps::LookupProperty(nsDependentAtomString(mKey.mAttributeName),
nsCSSProps::eEnabledForAllContent);
CSSEnabledState::eForAllContent);
if (nsSMILCSSProperty::IsPropertyAnimatable(propId)) {
return new nsSMILCSSProperty(propId, mKey.mElement.get());
}

View File

@ -1,113 +0,0 @@
/* -*- 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 "mozilla/dom/SVGAltGlyphElement.h"
#include "mozilla/dom/SVGAltGlyphElementBinding.h"
NS_IMPL_NS_NEW_NAMESPACED_SVG_ELEMENT(AltGlyph)
namespace mozilla {
namespace dom {
JSObject*
SVGAltGlyphElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
{
return SVGAltGlyphElementBinding::Wrap(aCx, this, aGivenProto);
}
nsSVGElement::StringInfo SVGAltGlyphElement::sStringInfo[1] =
{
{ &nsGkAtoms::href, kNameSpaceID_XLink, false }
};
//----------------------------------------------------------------------
// Implementation
SVGAltGlyphElement::SVGAltGlyphElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
: SVGAltGlyphElementBase(aNodeInfo)
{
}
nsSVGElement::EnumAttributesInfo
SVGAltGlyphElement::GetEnumInfo()
{
return EnumAttributesInfo(mEnumAttributes, sEnumInfo,
ArrayLength(sEnumInfo));
}
nsSVGElement::LengthAttributesInfo
SVGAltGlyphElement::GetLengthInfo()
{
return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
ArrayLength(sLengthInfo));
}
//----------------------------------------------------------------------
// nsIDOMNode methods
NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGAltGlyphElement)
already_AddRefed<SVGAnimatedString>
SVGAltGlyphElement::Href()
{
return mStringAttributes[HREF].ToDOMAnimatedString(this);
}
void
SVGAltGlyphElement::GetGlyphRef(nsAString & aGlyphRef)
{
GetAttr(kNameSpaceID_None, nsGkAtoms::glyphRef, aGlyphRef);
}
void
SVGAltGlyphElement::SetGlyphRef(const nsAString & aGlyphRef, ErrorResult& rv)
{
rv = SetAttr(kNameSpaceID_None, nsGkAtoms::glyphRef, aGlyphRef, true);
}
void
SVGAltGlyphElement::GetFormat(nsAString & aFormat)
{
GetAttr(kNameSpaceID_None, nsGkAtoms::format, aFormat);
}
void
SVGAltGlyphElement::SetFormat(const nsAString & aFormat, ErrorResult& rv)
{
rv = SetAttr(kNameSpaceID_None, nsGkAtoms::format, aFormat, true);
}
//----------------------------------------------------------------------
// nsIContent methods
NS_IMETHODIMP_(bool)
SVGAltGlyphElement::IsAttributeMapped(const nsIAtom* name) const
{
static const MappedAttributeEntry* const map[] = {
sColorMap,
sFillStrokeMap,
sFontSpecificationMap,
sGraphicsMap,
sTextContentElementsMap
};
return FindAttributeDependence(name, map) ||
SVGAltGlyphElementBase::IsAttributeMapped(name);
}
//----------------------------------------------------------------------
// nsSVGElement overrides
nsSVGElement::StringAttributesInfo
SVGAltGlyphElement::GetStringInfo()
{
return StringAttributesInfo(mStringAttributes, sStringInfo,
ArrayLength(sStringInfo));
}
} // namespace dom
} // namespace mozilla

View File

@ -1,65 +0,0 @@
/* -*- 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_SVGAltGlyphElement_h
#define mozilla_dom_SVGAltGlyphElement_h
#include "mozilla/dom/SVGTextPositioningElement.h"
#include "nsSVGString.h"
nsresult NS_NewSVGAltGlyphElement(nsIContent **aResult,
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
namespace mozilla {
namespace dom {
typedef SVGTextPositioningElement SVGAltGlyphElementBase;
class SVGAltGlyphElement final : public SVGAltGlyphElementBase
{
protected:
friend nsresult (::NS_NewSVGAltGlyphElement(nsIContent **aResult,
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo));
explicit SVGAltGlyphElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
virtual JSObject* WrapNode(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
public:
// nsIContent interface
NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
// WebIDL
already_AddRefed<SVGAnimatedString> Href();
void GetGlyphRef(nsAString & aGlyphRef);
void SetGlyphRef(const nsAString & aGlyphRef, ErrorResult& rv);
void GetFormat(nsAString & aFormat);
void SetFormat(const nsAString & aFormat, ErrorResult& rv);
protected:
// nsSVGElement overrides
virtual EnumAttributesInfo GetEnumInfo() override;
virtual LengthAttributesInfo GetLengthInfo() override;
virtual StringAttributesInfo GetStringInfo() override;
enum { HREF };
nsSVGString mStringAttributes[1];
static StringInfo sStringInfo[1];
nsSVGEnum mEnumAttributes[1];
virtual nsSVGEnum* EnumAttributes() override
{ return mEnumAttributes; }
nsSVGLength2 mLengthAttributes[1];
virtual nsSVGLength2* LengthAttributes() override
{ return mLengthAttributes; }
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_SVGAltGlyphElement_h

View File

@ -29,7 +29,6 @@
******/
SVG_TAG(a, A)
SVG_TAG(altGlyph, AltGlyph)
SVG_TAG(animate, Animate)
SVG_TAG(animateMotion, AnimateMotion)
SVG_TAG(animateTransform, AnimateTransform)

View File

@ -19,7 +19,6 @@ EXPORTS += [
EXPORTS.mozilla.dom += [
'nsSVGAnimatedTransformList.h',
'SVGAElement.h',
'SVGAltGlyphElement.h',
'SVGAngle.h',
'SVGAnimatedAngle.h',
'SVGAnimatedBoolean.h',
@ -137,7 +136,6 @@ UNIFIED_SOURCES += [
'nsSVGTransform.cpp',
'nsSVGViewBox.cpp',
'SVGAElement.cpp',
'SVGAltGlyphElement.cpp',
'SVGAngle.cpp',
'SVGAnimatedAngle.cpp',
'SVGAnimatedBoolean.cpp',

View File

@ -1186,7 +1186,7 @@ MappedAttrParser::ParseMappedAttrValue(nsIAtom* aMappedAttrName,
// Get the nsCSSProperty ID for our mapped attribute.
nsCSSProperty propertyID =
nsCSSProps::LookupProperty(nsDependentAtomString(aMappedAttrName),
nsCSSProps::eEnabledForAllContent);
CSSEnabledState::eForAllContent);
if (propertyID != eCSSProperty_UNKNOWN) {
bool changed = false; // outparam for ParseProperty.
mParser.ParseProperty(propertyID, aMappedAttrValue, mDocURI, mBaseURI,
@ -1196,7 +1196,7 @@ MappedAttrParser::ParseMappedAttrValue(nsIAtom* aMappedAttrName,
// since it doesn't have a sheet.
if (nsCSSProps::IsShorthand(propertyID)) {
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, propertyID,
nsCSSProps::eEnabledForAllContent) {
CSSEnabledState::eForAllContent) {
UseCounter useCounter = nsCSSProps::UseCounterFor(*subprop);
if (useCounter != eUseCounter_UNKNOWN) {
mElement->OwnerDoc()->SetDocumentAndPageUseCounter(useCounter);
@ -2544,7 +2544,7 @@ nsSVGElement::GetAnimatedAttr(int32_t aNamespaceID, nsIAtom* aName)
if (IsAttributeMapped(aName)) {
nsCSSProperty prop =
nsCSSProps::LookupProperty(nsDependentAtomString(aName),
nsCSSProps::eEnabledForAllContent);
CSSEnabledState::eForAllContent);
// Check IsPropertyAnimatable to avoid attributes that...
// - map to explicitly unanimatable properties (e.g. 'direction')
// - map to unsupported attributes (e.g. 'glyph-orientation-horizontal')

View File

@ -32,9 +32,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=589640
<!-- NOTE: This test relies on the ids being the same as the element names -->
<svg id="svg1">
<a id="a" />
<altGlyph id="altGlyph" />
<altGlyphDef id="altGlyphDef" />
<altGlyphItem id="altGlyphItem" />
<animate id="animate" />
<animateColor id="animateColor" />
<animateMotion id="animateMotion" />

View File

@ -1058,8 +1058,6 @@ var interfaceNamesInGlobalScope =
"SubtleCrypto",
// IMPORTANT: Do not change this list without review from a DOM peer!
"SVGAElement",
// IMPORTANT: Do not change this list without review from a DOM peer!
"SVGAltGlyphElement",
// IMPORTANT: Do not change this list without review from a DOM peer!
"SVGAngle",
// IMPORTANT: Do not change this list without review from a DOM peer!

View File

@ -7,14 +7,14 @@
[Constructor(DOMString type, optional HashChangeEventInit eventInitDict), LegacyEventInit]
interface HashChangeEvent : Event
{
readonly attribute DOMString? oldURL;
readonly attribute DOMString? newURL;
readonly attribute DOMString oldURL;
readonly attribute DOMString newURL;
void initHashChangeEvent(DOMString typeArg,
boolean canBubbleArg,
boolean cancelableArg,
DOMString? oldURLArg,
DOMString? newURLArg);
DOMString oldURLArg,
DOMString newURLArg);
};
dictionary HashChangeEventInit : EventInit

View File

@ -42,14 +42,8 @@ interface IDBObjectStore {
[Throws]
IDBRequest openCursor (optional any range, optional IDBCursorDirection direction = "next");
// Bug 899972
// IDBIndex createIndex (DOMString name, (DOMString or sequence<DOMString>) keyPath, optional IDBIndexParameters optionalParameters);
[Throws]
IDBIndex createIndex (DOMString name, DOMString keyPath, optional IDBIndexParameters optionalParameters);
[Throws]
IDBIndex createIndex (DOMString name, sequence<DOMString> keyPath, optional IDBIndexParameters optionalParameters);
IDBIndex createIndex (DOMString name, (DOMString or sequence<DOMString>) keyPath, optional IDBIndexParameters optionalParameters);
[Throws]
IDBIndex index (DOMString name);

View File

@ -17,6 +17,7 @@ interface Response {
readonly attribute ResponseType type;
readonly attribute USVString url;
readonly attribute boolean redirected;
readonly attribute unsigned short status;
readonly attribute boolean ok;
readonly attribute ByteString statusText;

View File

@ -1,21 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/.
*
* The origin of this IDL file is
* http://www.w3.org/TR/SVG2/
*
* Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
* liability, trademark and document use rules apply.
*/
interface SVGAltGlyphElement : SVGTextPositioningElement {
[SetterThrows]
attribute DOMString glyphRef;
[SetterThrows]
attribute DOMString format;
};
SVGAltGlyphElement implements SVGURIReference;

View File

@ -423,7 +423,6 @@ WEBIDL_FILES = [
'StyleSheetList.webidl',
'SubtleCrypto.webidl',
'SVGAElement.webidl',
'SVGAltGlyphElement.webidl',
'SVGAngle.webidl',
'SVGAnimatedAngle.webidl',
'SVGAnimatedBoolean.webidl',

View File

@ -250,7 +250,7 @@ public:
nsresult rv;
nsCOMPtr<nsIURI> uri;
nsAutoCString url;
mInternalResponse->GetUnfilteredUrl(url);
mInternalResponse->GetUnfilteredURL(url);
if (url.IsEmpty()) {
// Synthetic response. The buck stops at the worker script.
url = mScriptSpec;
@ -587,6 +587,8 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
// * request's mode is not "no-cors" and response's type is "opaque".
// * request's redirect mode is not "manual" and response's type is
// "opaqueredirect".
// * request's redirect mode is not "follow" and response's url list
// has more than one item.
if (response->Type() == ResponseType::Error) {
autoCancel.SetCancelMessage(
@ -615,6 +617,12 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
return;
}
if (mRequestRedirectMode != RequestRedirect::Follow && response->Redirected()) {
autoCancel.SetCancelMessage(
NS_LITERAL_CSTRING("BadRedirectModeInterceptionWithURL"), mRequestURL);
return;
}
if (NS_WARN_IF(response->BodyUsed())) {
autoCancel.SetCancelMessage(
NS_LITERAL_CSTRING("InterceptedUsedResponseWithURL"), mRequestURL);
@ -631,7 +639,7 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
// cross-origin responses, which are treated as same-origin by consumers.
nsCString responseURL;
if (response->Type() == ResponseType::Opaque) {
ir->GetUnfilteredUrl(responseURL);
ir->GetUnfilteredURL(responseURL);
if (NS_WARN_IF(responseURL.IsEmpty())) {
return;
}

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