Merge mozilla-central to autoland. a=merge CLOSED TREE

This commit is contained in:
Bogdan Tara 2018-03-07 12:06:25 +02:00
Родитель 2f58dc2d51 e3282a1fb3
Коммит f93e407658
59 изменённых файлов: 1031 добавлений и 755 удалений

Просмотреть файл

@ -282,7 +282,10 @@ RootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent)
DocAccessible* targetDocument = GetAccService()->
GetDocAccessible(origTargetNode->OwnerDoc());
NS_ASSERTION(targetDocument, "No document while accessible is in document?!");
if (!targetDocument) {
// Document has ceased to exist.
return;
}
Accessible* accessible =
targetDocument->GetAccessibleOrContainer(origTargetNode);

Просмотреть файл

@ -0,0 +1,11 @@
<script>
document.addEventListener("DOMContentLoaded", function(){
document.getElementById('a').click();
window.frames[0].document.body.appendChild(document.getElementById('b'));
});
</script>
<cite id='b'>
<label>
<input/>
<strong id='a'></strong>
<iframe>

Просмотреть файл

@ -4,6 +4,7 @@ asserts-if(!browserIsRemote,2) load 884202.html
load 890760.html
load 893515.html
load 1072792.xhtml
load 1402999.html
# last_test_to_unload_testsuite.xul MUST be the last test in the list because it
# is responsible for shutting down accessibility service affecting later tests.

Просмотреть файл

@ -621,6 +621,16 @@ const TEST_CASES_EN = [
location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD, altKey: false, shiftKey: false,
ctrlKey: false, altGraphKey: false }
},
{ key: "a", modifiers: { ctrlKey: true }, expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
result: { key: "a", code: "KeyA", charCode: 97, keyCode: KeyboardEvent.DOM_VK_A,
location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD, altKey: false, shiftKey: false,
ctrlKey: true, altGraphKey: false }
},
{ key: "a", modifiers: { altKey: true }, expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
result: { key: "a", code: "KeyA", charCode: 97, keyCode: KeyboardEvent.DOM_VK_A,
location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD, altKey: false, shiftKey: false,
ctrlKey: false, altGraphKey: false }
},
];
async function testKeyEvent(aTab, aTestCase) {

Просмотреть файл

@ -149,10 +149,6 @@ fun:*OT*collect_glyphs*
# These look like harmless layouting-related overflows
src:*/gfx/cairo/libpixman/src/pixman-region.c
# Sorting code in layout/style/nsCSSProps.cpp that probably doesn't
# care about overflows.
fun:*SortPropertyAndCount*
# Code in ipc/chromium/src/base/file_path.cc where a function returns -1
# being cast to unsigned and then overflowed.
fun:*FilePath*Append*
@ -228,7 +224,6 @@ src:*/mozglue/misc/TimeStamp.h
#
src:*/dom/canvas/MurmurHash3.cpp
src:*/gfx/skia/skia/include/private/SkChecksum.h
src:*/HashFunctions.h
src:*/intl/icu/source/common/unifiedcache.h
src:*/mfbt/SHA1.cpp
src:*/modules/zlib/src/adler32.c
@ -242,7 +237,6 @@ src:*/security/manager/ssl/md4.c
src:*/security/nss/lib/dbm/src/h_func.c
src:*/security/nss/lib/freebl/sha512.c
src:*/security/nss/lib/freebl/md5.c
src:*/XorShift128PlusRNG.h
src:*/xpcom/ds/PLDHashTable.cpp
# Hash/Cache function in Skia
@ -254,9 +248,6 @@ fun:*_hash_mix_bits*
fun:*_cairo_hash_string*
fun:*_cairo_hash_bytes*
# Hash function in modules/libjar/nsZipArchive.cpp
fun:*HashName*
# intl code hashing functions
fun:*ustr_hash*CharsN*
fun:*hashEntry*

Просмотреть файл

@ -156,10 +156,6 @@ fun:*OT*collect_glyphs*
# These look like harmless layouting-related overflows
src:*/gfx/cairo/libpixman/src/pixman-region.c
# Sorting code in layout/style/nsCSSProps.cpp that probably doesn't
# care about overflows.
fun:*SortPropertyAndCount*
# Code in ipc/chromium/src/base/file_path.cc where a function returns -1
# being cast to unsigned and then overflowed.
fun:*FilePath*Append*
@ -235,7 +231,6 @@ src:*/mozglue/misc/TimeStamp.h
#
src:*/dom/canvas/MurmurHash3.cpp
src:*/gfx/skia/skia/include/private/SkChecksum.h
src:*/HashFunctions.h
src:*/intl/icu/source/common/unifiedcache.h
src:*/mfbt/SHA1.cpp
src:*/modules/zlib/src/adler32.c
@ -249,7 +244,6 @@ src:*/security/manager/ssl/md4.c
src:*/security/nss/lib/dbm/src/h_func.c
src:*/security/nss/lib/freebl/sha512.c
src:*/security/nss/lib/freebl/md5.c
src:*/XorShift128PlusRNG.h
src:*/xpcom/ds/PLDHashTable.cpp
# Hash/Cache function in Skia
@ -261,9 +255,6 @@ fun:*_hash_mix_bits*
fun:*_cairo_hash_string*
fun:*_cairo_hash_bytes*
# Hash function in modules/libjar/nsZipArchive.cpp
fun:*HashName*
# intl code hashing functions
fun:*ustr_hash*CharsN*
fun:*hashEntry*

Просмотреть файл

@ -3,7 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@import "chrome://devtools/skin/widgets.css";
@import "resource://devtools/client/themes/light-theme.css";
@import "resource://devtools/client/shared/components/splitter/SplitBox.css";
@import "resource://devtools/client/shared/components/tree/TreeView.css";
@import "resource://devtools/client/shared/components/tabs/Tabs.css";

Просмотреть файл

@ -2,7 +2,6 @@
* 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/. */
@import "resource://devtools/client/themes/light-theme.css";
@import "resource://devtools/client/shared/components/splitter/SplitBox.css";
@import "resource://devtools/client/shared/components/tree/TreeView.css";
@import "resource://devtools/client/shared/components/tabs/Tabs.css";

Просмотреть файл

@ -13,7 +13,6 @@ Test all-tabs menu.
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<link rel="stylesheet" type="text/css" href="resource://devtools/client/themes/variables.css">
<link rel="stylesheet" type="text/css" href="resource://devtools/client/themes/common.css">
<link rel="stylesheet" type="text/css" href="resource://devtools/client/themes/light-theme.css">
<link rel="stylesheet" type="text/css" href="resource://devtools/client/shared/components/tabs/Tabs.css">
<link rel="stylesheet" type="text/css" href="resource://devtools/client/shared/components/tabs/TabBar.css">
<link rel="stylesheet" type="text/css" href="resource://devtools/client/inspector/components/side-panel.css">

Просмотреть файл

@ -6,7 +6,6 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="chrome://devtools/skin/widgets.css"/>
<link rel="stylesheet" href="resource://devtools/client/themes/light-theme.css"/>
<link rel="stylesheet" href="chrome://devtools/skin/webconsole.css"/>
<link rel="stylesheet" href="chrome://devtools/skin/components-frame.css"/>
<link rel="stylesheet" href="resource://devtools/client/shared/components/reps/reps.css"/>

Просмотреть файл

@ -49,15 +49,10 @@ KeyboardEvent::AltKey(CallerType aCallerType)
bool
KeyboardEvent::CtrlKey(CallerType aCallerType)
{
bool ctrlState = mEvent->AsKeyboardEvent()->IsControl();
if (!ShouldResistFingerprinting(aCallerType)) {
return ctrlState;
}
// We need to give a spoofed state for Control key since it could be used as a
// modifier key in certain asian keyboard layouts.
return GetSpoofedModifierStates(Modifier::MODIFIER_CONTROL, ctrlState);
// We don't spoof this key when privacy.resistFingerprinting
// is enabled, because it is often used for command key
// combinations in web apps.
return mEvent->AsKeyboardEvent()->IsControl();
}
bool
@ -75,6 +70,9 @@ KeyboardEvent::ShiftKey(CallerType aCallerType)
bool
KeyboardEvent::MetaKey()
{
// We don't spoof this key when privacy.resistFingerprinting
// is enabled, because it is often used for command key
// combinations in web apps.
return mEvent->AsKeyboardEvent()->IsMeta();
}

Просмотреть файл

@ -85,7 +85,7 @@ CopyableCanvasRenderer::Initialize(const CanvasInitializeData& aData)
bool
CopyableCanvasRenderer::IsDataValid(const CanvasInitializeData& aData)
{
return mGLContext == aData.mGLContext;
return mGLContext == aData.mGLContext && mBufferProvider == aData.mBufferProvider;
}
void

Просмотреть файл

@ -177,6 +177,7 @@ WebRenderBridgeParent::WebRenderBridgeParent(CompositorBridgeParentBase* aCompos
, mPaused(false)
, mDestroyed(false)
, mForceRendering(false)
, mReceivedDisplayList(false)
{
MOZ_ASSERT(mAsyncImageManager);
MOZ_ASSERT(mAnimStorage);
@ -197,6 +198,7 @@ WebRenderBridgeParent::WebRenderBridgeParent(const wr::PipelineId& aPipelineId)
, mPaused(false)
, mDestroyed(true)
, mForceRendering(false)
, mReceivedDisplayList(false)
{
}
@ -597,6 +599,8 @@ WebRenderBridgeParent::RecvSetDisplayList(const gfx::IntSize& aSize,
return IPC_FAIL(this, "Failed to deserialize resource updates");
}
mReceivedDisplayList = true;
wr::Vec<uint8_t> dlData(Move(dl));
// If id namespaces do not match, it means the command is obsolete, probably
@ -1180,7 +1184,7 @@ WebRenderBridgeParent::CompositeToTarget(gfx::DrawTarget* aTarget, const gfx::In
MOZ_ASSERT(aRect == nullptr);
AUTO_PROFILER_TRACING("Paint", "CompositeToTraget");
if (mPaused) {
if (mPaused || !mReceivedDisplayList) {
return;
}
@ -1372,6 +1376,7 @@ WebRenderBridgeParent::ClearResources()
wr::TransactionBuilder txn;
txn.ClearDisplayList(wr::NewEpoch(wrEpoch), mPipelineId);
mReceivedDisplayList = false;
// Schedule generate frame to clean up Pipeline
ScheduleGenerateFrame();

Просмотреть файл

@ -272,6 +272,7 @@ private:
bool mPaused;
bool mDestroyed;
bool mForceRendering;
bool mReceivedDisplayList;
// Can only be accessed on the compositor thread.
WebRenderScrollData mScrollData;

Просмотреть файл

@ -939,19 +939,7 @@ impl RenderBackend {
&mut profile_counters.resources,
);
// If we get a generate_frame message after getting a root pipeline set,
// but that pipeline id hasn't been propagated to the current scene, then
// we should force that to happen. Otherwise we will skip a render that
// the caller is expecting to happen, and in Gecko's case, that will
// leave it wedged permanently.
let force_build = if transaction_msg.generate_frame {
let doc = self.documents.get_mut(&document_id).unwrap();
doc.pending.scene.root_pipeline_id.is_some() && !doc.current.scene.root_pipeline_id.is_some()
} else {
false
};
if op.build || force_build {
if op.build {
let doc = self.documents.get_mut(&document_id).unwrap();
let _timer = profile_counters.total_time.timer();
profile_scope!("build scene");

Просмотреть файл

@ -582,5 +582,35 @@ LoadInfoArgsToLoadInfo(const OptionalLoadInfoArgs& aOptionalLoadInfoArgs,
return NS_OK;
}
void
LoadInfoToParentLoadInfoForwarder(nsILoadInfo* aLoadInfo,
ParentLoadInfoForwarderArgs* outLoadInfoChildForwardArgs)
{
if (!aLoadInfo) {
return;
}
*outLoadInfoChildForwardArgs = ParentLoadInfoForwarderArgs(
aLoadInfo->GetAllowInsecureRedirectToDataURI()
);
}
nsresult
MergeParentLoadInfoForwarder(ParentLoadInfoForwarderArgs const& outLoadInfoChildForwardArgs,
nsILoadInfo* aLoadInfo)
{
if (!aLoadInfo) {
return NS_OK;
}
nsresult rv;
rv = aLoadInfo->SetAllowInsecureRedirectToDataURI(
outLoadInfoChildForwardArgs.allowInsecureRedirectToDataURI());
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
} // namespace ipc
} // namespace mozilla

Просмотреть файл

@ -50,6 +50,7 @@ struct ParamTraits<mozilla::OriginAttributes>
namespace mozilla {
namespace net {
class OptionalLoadInfoArgs;
class ParentLoadInfoForwarderArgs;
class RedirectHistoryEntryInfo;
} // namespace net
@ -113,6 +114,21 @@ nsresult
LoadInfoArgsToLoadInfo(const OptionalLoadInfoArgs& aOptionalLoadInfoArgs,
nsILoadInfo** outLoadInfo);
/**
* Fills ParentLoadInfoForwarderArgs with properties we want to carry to child processes.
*/
void
LoadInfoToParentLoadInfoForwarder(nsILoadInfo *aLoadInfo,
ParentLoadInfoForwarderArgs* outLoadInfoChildForwardArgs);
/**
* Merges (replaces) properties of an existing LoadInfo on a child process
* with properties carried down through ParentLoadInfoForwarderArgs.
*/
nsresult
MergeParentLoadInfoForwarder(ParentLoadInfoForwarderArgs const& outLoadInfoChildForwardArgs,
nsILoadInfo *aLoadInfo);
} // namespace ipc
} // namespace mozilla

Просмотреть файл

@ -225,3 +225,118 @@ hasExpectedLength(JSContext* cx, JS::HandleObject obj, uint32_t* len)
}
END_TEST(testArrayBuffer_externalize)
BEGIN_TEST(testArrayBuffer_refcountedContents)
{
RefCountedData data("One two three four");
JS::RootedObject buffer(cx, JS_NewExternalArrayBuffer(cx, data.len(), data.contents(),
&RefCountedData::incCallback, &RefCountedData::decCallback, &data));
CHECK(buffer);
CHECK_EQUAL(data.refcount(), size_t(2));
uint32_t len;
bool isShared;
uint8_t* bufferData;
js::GetArrayBufferLengthAndData(buffer, &len, &isShared, &bufferData);
CHECK_EQUAL(len, data.len());
CHECK(bufferData == data.contents());
CHECK(strcmp(reinterpret_cast<char*>(bufferData), data.asString()) == 0);
buffer = nullptr;
JS_GC(cx);
JS_GC(cx);
CHECK_EQUAL(data.refcount(), size_t(1));
data.decref();
CHECK_NULL(data.contents());
CHECK_EQUAL(data.refcount(), size_t(0));
return true;
}
END_TEST(testArrayBuffer_refcountedContents)
BEGIN_TEST(testArrayBuffer_customFreeFunc)
{
RefCountedData data("One two three four");
// Without passing a ref function, the buffer takes over the one existing
// reference to the data.
JS::RootedObject buffer(cx, JS_NewExternalArrayBuffer(cx, data.len(), data.contents(),
nullptr, &RefCountedData::decCallback, &data));
CHECK(buffer);
CHECK_EQUAL(data.refcount(), size_t(1));
uint32_t len;
bool isShared;
uint8_t* bufferData;
js::GetArrayBufferLengthAndData(buffer, &len, &isShared, &bufferData);
CHECK_EQUAL(len, data.len());
CHECK(bufferData == data.contents());
CHECK(strcmp(reinterpret_cast<char*>(bufferData), data.asString()) == 0);
buffer = nullptr;
JS_GC(cx);
JS_GC(cx);
CHECK_NULL(data.contents());
CHECK_EQUAL(data.refcount(), size_t(0));
return true;
}
END_TEST(testArrayBuffer_customFreeFunc)
BEGIN_TEST(testArrayBuffer_staticContents)
{
RefCountedData data("One two three four");
// When passing neither a ref nor unref function, the buffer doesn't own
// any reference.
JS::RootedObject buffer(cx, JS_NewExternalArrayBuffer(cx, data.len(), data.contents(),
nullptr, nullptr));
CHECK(buffer);
CHECK_EQUAL(data.refcount(), size_t(1));
uint32_t len;
bool isShared;
uint8_t* bufferData;
js::GetArrayBufferLengthAndData(buffer, &len, &isShared, &bufferData);
CHECK_EQUAL(len, data.len());
CHECK(bufferData == data.contents());
CHECK(strcmp(reinterpret_cast<char*>(bufferData), data.asString()) == 0);
buffer = nullptr;
JS_GC(cx);
JS_GC(cx);
CHECK_EQUAL(data.refcount(), size_t(1));
data.decref();
return true;
}
END_TEST(testArrayBuffer_staticContents)
BEGIN_TEST(testArrayBuffer_stealDetachExternal)
{
RefCountedData data("One two three four");
JS::RootedObject buffer(cx, JS_NewExternalArrayBuffer(cx, data.len(), data.contents(),
&RefCountedData::incCallback, &RefCountedData::decCallback, &data));
CHECK(buffer);
data.decref();
CHECK_EQUAL(data.refcount(), size_t(1));
void* stolenContents = JS_StealArrayBufferContents(cx, buffer);
// External buffers are currently not stealable, since stealing only
// gives you a pointer with no indication how to free it. So this should
// copy the data.
CHECK(stolenContents != data.contents());
CHECK(strcmp(reinterpret_cast<char*>(stolenContents), data.asString()) == 0);
// External buffers are currently not stealable, so this should keep the
// reference to the data and just mark the buffer as detached.
CHECK(JS_IsDetachedArrayBufferObject(buffer));
CHECK_EQUAL(data.refcount(), size_t(1));
buffer = nullptr;
JS_GC(cx);
JS_GC(cx);
CHECK_NULL(data.contents());
CHECK_EQUAL(data.refcount(), size_t(0));
return true;
}
END_TEST(testArrayBuffer_stealDetachExternal)

Просмотреть файл

@ -82,6 +82,62 @@ BEGIN_TEST(testStructuredClone_string)
}
END_TEST(testStructuredClone_string)
BEGIN_TEST(testStructuredClone_externalArrayBuffer)
{
RefCountedData data("One two three four");
JS::RootedObject g1(cx, createGlobal());
JS::RootedObject g2(cx, createGlobal());
CHECK(g1);
CHECK(g2);
JS::RootedValue v1(cx);
{
JSAutoCompartment ac(cx, g1);
JS::RootedObject obj(cx, JS_NewExternalArrayBuffer(cx, data.len(), data.contents(),
&RefCountedData::incCallback, &RefCountedData::decCallback, &data));
data.decref();
CHECK_EQUAL(data.refcount(), size_t(1));
v1 = JS::ObjectOrNullValue(obj);
CHECK(v1.isObject());
}
{
JSAutoCompartment ac(cx, g2);
JS::RootedValue v2(cx);
CHECK(JS_StructuredClone(cx, v1, &v2, nullptr, nullptr));
CHECK(v2.isObject());
JS::RootedObject obj(cx, &v2.toObject());
CHECK(&v1.toObject() != obj);
uint32_t len;
bool isShared;
uint8_t* clonedData;
js::GetArrayBufferLengthAndData(obj, &len, &isShared, &clonedData);
// The contents of the two array buffers should be equal, but not the
// same pointer, and an extra reference should not be taken.
CHECK_EQUAL(len, data.len());
CHECK(clonedData != data.contents());
CHECK(strcmp(reinterpret_cast<char*>(clonedData), data.asString()) == 0);
CHECK_EQUAL(data.refcount(), size_t(1));
}
// GC the array buffer before data goes out of scope
v1.setNull();
JS_GC(cx);
JS_GC(cx); // Trigger another to wait for background finalization to end
CHECK_EQUAL(data.refcount(), size_t(0));
return true;
}
END_TEST(testStructuredClone_externalArrayBuffer)
struct StructuredCloneTestPrincipals final : public JSPrincipals {
uint32_t rank;

Просмотреть файл

@ -441,6 +441,43 @@ class TestJSPrincipals : public JSPrincipals
}
};
// A class that simulates refcounted data, for testing with array buffers.
class RefCountedData {
char* contents_;
size_t len_;
size_t refcount_;
public:
explicit RefCountedData(const char* str) : contents_(strdup(str)),
len_(strlen(str) + 1), refcount_(1) { }
size_t len() const { return len_; }
void* contents() const { return contents_; }
char* asString() const { return contents_; }
size_t refcount() const { return refcount_; }
void incref() { refcount_++; }
void decref() {
refcount_--;
if (refcount_ == 0) {
free(contents_);
contents_ = nullptr;
}
}
static void incCallback(void* contents, void* userData) {
auto self = static_cast<RefCountedData*>(userData);
MOZ_ASSERT(self->contents() == contents);
self->incref();
}
static void decCallback(void* contents, void* userData) {
auto self = static_cast<RefCountedData*>(userData);
MOZ_ASSERT(self->contents() == contents);
self->decref();
}
};
#ifdef JS_GC_ZEAL
/*
* Temporarily disable the GC zeal setting. This is only useful in tests that

Просмотреть файл

@ -3253,6 +3253,42 @@ JS_SetAllNonReservedSlotsToUndefined(JSContext* cx, JSObject* objArg);
extern JS_PUBLIC_API(JSObject*)
JS_NewArrayBufferWithContents(JSContext* cx, size_t nbytes, void* contents);
namespace JS {
using BufferContentsRefFunc = void (*)(void* contents, void* userData);
} /* namespace JS */
/**
* Create a new array buffer with the given contents. The ref and unref
* functions should increment or decrement the reference count of the contents.
* These functions allow array buffers to be used with embedder objects that
* use reference counting, for example. The contents must not be modified by
* any reference holders, internal or external.
*
* On success, the new array buffer takes a reference, and |ref(contents,
* refUserData)| will be called. When the array buffer is ready to be disposed
* of, |unref(contents, refUserData)| will be called to release the array
* buffer's reference on the contents.
*
* The ref and unref functions must not call any JSAPI functions that could
* cause a garbage collection.
*
* The ref function is optional. If it is nullptr, the caller is responsible
* for incrementing the reference count before passing the contents to this
* function. This also allows using non-reference-counted contents that must be
* freed with some function other than free().
*
* The ref function may also be called in case the buffer is cloned in some
* way. Currently this is not used, but it may be in the future. If the ref
* function is nullptr, any operation where an extra reference would otherwise
* be taken, will either copy the data, or throw an exception.
*/
extern JS_PUBLIC_API(JSObject*)
JS_NewExternalArrayBuffer(JSContext* cx, size_t nbytes, void* contents,
JS::BufferContentsRefFunc ref, JS::BufferContentsRefFunc unref,
void* refUserData = nullptr);
/**
* Create a new array buffer with the given contents. The array buffer does not take ownership of
* contents, and JS_DetachArrayBuffer must be called before the contents are disposed of.

Просмотреть файл

@ -964,6 +964,13 @@ ArrayBufferObject::dataPointerShared() const
return SharedMem<uint8_t*>::unshared(getSlot(DATA_SLOT).toPrivate());
}
ArrayBufferObject::RefcountInfo*
ArrayBufferObject::refcountInfo() const
{
MOZ_ASSERT(isExternal());
return reinterpret_cast<RefcountInfo*>(inlineDataPointer());
}
void
ArrayBufferObject::releaseData(FreeOp* fop)
{
@ -979,8 +986,16 @@ ArrayBufferObject::releaseData(FreeOp* fop)
case WASM:
WasmArrayRawBuffer::Release(dataPointer());
break;
case KIND_MASK:
MOZ_CRASH("bad bufferKind()");
case EXTERNAL:
if (refcountInfo()->unref) {
// The analyzer can't know for sure whether the embedder-supplied
// unref function will GC. We give the analyzer a hint here.
// (Doing a GC in the unref function is considered a programmer
// error.)
JS::AutoSuppressGCAnalysis nogc;
refcountInfo()->unref(dataPointer(), refcountInfo()->refUserData);
}
break;
}
}
@ -990,6 +1005,18 @@ ArrayBufferObject::setDataPointer(BufferContents contents, OwnsState ownsData)
setSlot(DATA_SLOT, PrivateValue(contents.data()));
setOwnsData(ownsData);
setFlags((flags() & ~KIND_MASK) | contents.kind());
if (isExternal()) {
auto info = refcountInfo();
info->ref = contents.refFunc();
info->unref = contents.unrefFunc();
info->refUserData = contents.refUserData();
if (info->ref) {
// See comment in releaseData() for the explanation for this.
JS::AutoSuppressGCAnalysis nogc;
info->ref(dataPointer(), info->refUserData);
}
}
}
uint32_t
@ -1158,13 +1185,23 @@ ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents content
bool allocated = false;
if (contents) {
if (ownsState == OwnsData) {
// The ABO is taking ownership, so account the bytes against the zone.
size_t nAllocated = nbytes;
if (contents.kind() == MAPPED)
nAllocated = JS_ROUNDUP(nbytes, js::gc::SystemPageSize());
else if (contents.kind() == WASM)
nAllocated = contents.wasmBuffer()->allocatedBytes();
cx->updateMallocCounter(nAllocated);
if (contents.kind() == EXTERNAL) {
// Store the RefcountInfo in the inline data slots so that we
// don't use up slots for it in non-refcounted array buffers.
size_t refcountInfoSlots = JS_HOWMANY(sizeof(RefcountInfo), sizeof(Value));
MOZ_ASSERT(reservedSlots + refcountInfoSlots <= NativeObject::MAX_FIXED_SLOTS,
"RefcountInfo must fit in inline slots");
nslots += refcountInfoSlots;
} else {
// The ABO is taking ownership, so account the bytes against
// the zone.
size_t nAllocated = nbytes;
if (contents.kind() == MAPPED)
nAllocated = JS_ROUNDUP(nbytes, js::gc::SystemPageSize());
else if (contents.kind() == WASM)
nAllocated = contents.wasmBuffer()->allocatedBytes();
cx->updateMallocCounter(nAllocated);
}
}
} else {
MOZ_ASSERT(ownsState == OwnsData);
@ -1263,7 +1300,7 @@ ArrayBufferObject::externalizeContents(JSContext* cx, Handle<ArrayBufferObject*>
MOZ_ASSERT(!buffer->isDetached(), "must have contents to externalize");
MOZ_ASSERT_IF(hasStealableContents, buffer->hasStealableContents());
BufferContents contents(buffer->dataPointer(), buffer->bufferKind());
BufferContents contents = buffer->contents();
if (hasStealableContents) {
buffer->setOwnsData(DoesntOwnData);
@ -1291,7 +1328,7 @@ ArrayBufferObject::stealContents(JSContext* cx, Handle<ArrayBufferObject*> buffe
(buffer->isWasm() && !buffer->isPreparedForAsmJS()));
assertSameCompartment(cx, buffer);
BufferContents oldContents(buffer->dataPointer(), buffer->bufferKind());
BufferContents oldContents = buffer->contents();
if (hasStealableContents) {
// Return the old contents and reset the detached buffer's data
@ -1803,12 +1840,30 @@ JS_NewArrayBufferWithContents(JSContext* cx, size_t nbytes, void* data)
AssertHeapIsIdle();
CHECK_REQUEST(cx);
MOZ_ASSERT_IF(!data, nbytes == 0);
ArrayBufferObject::BufferContents contents =
ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(data);
return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData,
/* proto = */ nullptr, TenuredObject);
}
JS_PUBLIC_API(JSObject*)
JS_NewExternalArrayBuffer(JSContext* cx, size_t nbytes, void* data,
JS::BufferContentsRefFunc ref, JS::BufferContentsRefFunc unref,
void* refUserData)
{
AssertHeapIsIdle();
CHECK_REQUEST(cx);
MOZ_ASSERT(data);
MOZ_ASSERT(nbytes > 0);
ArrayBufferObject::BufferContents contents =
ArrayBufferObject::BufferContents::createExternal(data, ref, unref, refUserData);
return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData,
/* proto = */ nullptr, TenuredObject);
}
JS_PUBLIC_API(JSObject*)
JS_NewArrayBufferWithExternalContents(JSContext* cx, size_t nbytes, void* data)
{

Просмотреть файл

@ -180,6 +180,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
PLAIN = 0, // malloced or inline data
WASM = 1,
MAPPED = 2,
EXTERNAL = 3,
KIND_MASK = 0x3
};
@ -193,9 +194,9 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
DETACHED = 0x4,
// The dataPointer() is owned by this buffer and should be released
// when no longer in use. Releasing the pointer may be done by either
// freeing or unmapping it, and how to do this is determined by the
// buffer's other flags.
// when no longer in use. Releasing the pointer may be done by freeing,
// invoking a dereference callback function, or unmapping, as
// determined by the buffer's other flags.
//
// Array buffers which do not own their data include buffers that
// allocate their data inline, and buffers that are created lazily for
@ -225,11 +226,22 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
class BufferContents {
uint8_t* data_;
BufferKind kind_;
JS::BufferContentsRefFunc ref_;
JS::BufferContentsRefFunc unref_;
void* refUserData_;
friend class ArrayBufferObject;
BufferContents(uint8_t* data, BufferKind kind) : data_(data), kind_(kind) {
BufferContents(uint8_t* data, BufferKind kind, JS::BufferContentsRefFunc ref = nullptr,
JS::BufferContentsRefFunc unref = nullptr, void* refUserData = nullptr)
: data_(data), kind_(kind), ref_(ref), unref_(unref), refUserData_(refUserData)
{
MOZ_ASSERT((kind_ & ~KIND_MASK) == 0);
MOZ_ASSERT_IF(ref_ || unref_ || refUserData_, kind_ == EXTERNAL);
// BufferContents does not ref or unref the data since it is
// internal and short-lived. It is the caller's responsibility to
// ensure that the BufferContents does not outlive the data.
}
public:
@ -245,8 +257,18 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
return BufferContents(static_cast<uint8_t*>(data), PLAIN);
}
static BufferContents createExternal(void *data, JS::BufferContentsRefFunc ref,
JS::BufferContentsRefFunc unref,
void* refUserData = nullptr)
{
return BufferContents(static_cast<uint8_t*>(data), EXTERNAL, ref, unref, refUserData);
}
uint8_t* data() const { return data_; }
BufferKind kind() const { return kind_; }
JS::BufferContentsRefFunc refFunc() const { return ref_; }
JS::BufferContentsRefFunc unrefFunc() const { return unref_; }
void* refUserData() const { return refUserData_; }
explicit operator bool() const { return data_ != nullptr; }
WasmArrayRawBuffer* wasmBuffer() const;
@ -329,12 +351,23 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
uint8_t* inlineDataPointer() const;
struct RefcountInfo {
JS::BufferContentsRefFunc ref;
JS::BufferContentsRefFunc unref;
void* refUserData;
};
RefcountInfo* refcountInfo() const;
public:
uint8_t* dataPointer() const;
SharedMem<uint8_t*> dataPointerShared() const;
uint32_t byteLength() const;
BufferContents contents() const {
if (isExternal()) {
return BufferContents(dataPointer(), EXTERNAL, refcountInfo()->ref,
refcountInfo()->unref, refcountInfo()->refUserData);
}
return BufferContents(dataPointer(), bufferKind());
}
bool hasInlineData() const {
@ -355,6 +388,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
bool isPlain() const { return bufferKind() == PLAIN; }
bool isWasm() const { return bufferKind() == WASM; }
bool isMapped() const { return bufferKind() == MAPPED; }
bool isExternal() const { return bufferKind() == EXTERNAL; }
bool isDetached() const { return flags() & DETACHED; }
bool isPreparedForAsmJS() const { return flags() & FOR_ASMJS; }

Просмотреть файл

@ -1112,6 +1112,12 @@ JSStructuredCloneWriter::parseTransferable()
if (tObj->is<WasmMemoryObject>() && tObj->as<WasmMemoryObject>().isShared())
return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE);
// External array buffers may be able to be transferred in the future,
// but that is not currently implemented.
if (tObj->is<ArrayBufferObject>() && tObj->as<ArrayBufferObject>().isExternal())
return reportDataCloneError(JS_SCERR_TRANSFERABLE);
// No duplicates allowed
auto p = transferableObjects.lookupForAdd(tObj);
if (p)

Просмотреть файл

@ -170,6 +170,7 @@ SheetLoadData::SheetLoadData(Loader* aLoader,
, mSheetAlreadyComplete(false)
, mIsCrossOriginNoCORS(false)
, mBlockResourceTiming(false)
, mLoadFailed(false)
, mOwningElement(aOwningElement)
, mObserver(aObserver)
, mLoaderPrincipal(aLoaderPrincipal)
@ -205,6 +206,7 @@ SheetLoadData::SheetLoadData(Loader* aLoader,
, mSheetAlreadyComplete(false)
, mIsCrossOriginNoCORS(false)
, mBlockResourceTiming(false)
, mLoadFailed(false)
, mOwningElement(nullptr)
, mObserver(aObserver)
, mLoaderPrincipal(aLoaderPrincipal)
@ -250,6 +252,7 @@ SheetLoadData::SheetLoadData(Loader* aLoader,
, mSheetAlreadyComplete(false)
, mIsCrossOriginNoCORS(false)
, mBlockResourceTiming(false)
, mLoadFailed(false)
, mOwningElement(nullptr)
, mObserver(aObserver)
, mLoaderPrincipal(aLoaderPrincipal)
@ -316,9 +319,9 @@ SheetLoadData::FireLoadEvent(nsIThreadInternal* aThread)
nsContentUtils::DispatchTrustedEvent(node->OwnerDoc(),
node,
NS_SUCCEEDED(mStatus) ?
NS_LITERAL_STRING("load") :
NS_LITERAL_STRING("error"),
mLoadFailed ?
NS_LITERAL_STRING("error") :
NS_LITERAL_STRING("load"),
false, false);
// And unblock onload
@ -326,14 +329,12 @@ SheetLoadData::FireLoadEvent(nsIThreadInternal* aThread)
}
void
SheetLoadData::ScheduleLoadEventIfNeeded(nsresult aStatus)
SheetLoadData::ScheduleLoadEventIfNeeded()
{
if (!mOwningElement) {
return;
}
mStatus = aStatus;
nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
nsCOMPtr<nsIThreadInternal> internalThread = do_QueryInterface(thread);
if (NS_SUCCEEDED(internalThread->AddObserver(this))) {
@ -1815,13 +1816,21 @@ Loader::DoParseSheetServo(ServoStyleSheet* aSheet,
void
Loader::SheetComplete(SheetLoadData* aLoadData, nsresult aStatus)
{
LOG(("css::Loader::SheetComplete"));
LOG(("css::Loader::SheetComplete, status: 0x%" PRIx32, static_cast<uint32_t>(aStatus)));
// If aStatus is a failure we need to mark this data failed. We also need to
// mark any ancestors of a failing data as failed and any sibling of a
// failing data as failed. Note that SheetComplete is never called on a
// SheetLoadData that is the mNext of some other SheetLoadData.
if (NS_FAILED(aStatus)) {
MarkLoadTreeFailed(aLoadData);
}
// 8 is probably big enough for all our common cases. It's not likely that
// imports will nest more than 8 deep, and multiple sheets with the same URI
// are rare.
AutoTArray<RefPtr<SheetLoadData>, 8> datasToNotify;
DoSheetComplete(aLoadData, aStatus, datasToNotify);
DoSheetComplete(aLoadData, datasToNotify);
// Now it's safe to go ahead and notify observers
uint32_t count = datasToNotify.Length();
@ -1855,16 +1864,13 @@ Loader::SheetComplete(SheetLoadData* aLoadData, nsresult aStatus)
}
void
Loader::DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
LoadDataArray& aDatasToNotify)
Loader::DoSheetComplete(SheetLoadData* aLoadData, LoadDataArray& aDatasToNotify)
{
LOG(("css::Loader::DoSheetComplete"));
NS_PRECONDITION(aLoadData, "Must have a load data!");
NS_PRECONDITION(aLoadData->mSheet, "Must have a sheet");
NS_ASSERTION(mSheets, "mLoadingDatas should be initialized by now.");
LOG(("Load completed, status: 0x%" PRIx32, static_cast<uint32_t>(aStatus)));
// Twiddle the hashtables
if (aLoadData->mURI) {
LOG_URI(" Finished loading: '%s'", aLoadData->mURI);
@ -1897,7 +1903,7 @@ Loader::DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
MOZ_ASSERT(!data->mSheet->HasForcedUniqueInner(),
"should not get a forced unique inner during parsing");
data->mSheet->SetComplete();
data->ScheduleLoadEventIfNeeded(aStatus);
data->ScheduleLoadEventIfNeeded();
}
if (data->mMustNotify && (data->mObserver || !mObservers.IsEmpty())) {
// Don't notify here so we don't trigger script. Remember the
@ -1920,17 +1926,17 @@ Loader::DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
if (data->mParentData &&
--(data->mParentData->mPendingChildren) == 0 &&
!data->mParentData->mIsBeingParsed) {
DoSheetComplete(data->mParentData, aStatus, aDatasToNotify);
DoSheetComplete(data->mParentData, aDatasToNotify);
}
data = data->mNext;
}
// Now that it's marked complete, put the sheet in our cache.
// If we ever start doing this for failure aStatus, we'll need to
// If we ever start doing this for failed loads, we'll need to
// adjust the PostLoadEvent code that thinks anything already
// complete must have loaded succesfully.
if (NS_SUCCEEDED(aStatus) && aLoadData->mURI) {
if (!aLoadData->mLoadFailed && aLoadData->mURI) {
// Pick our sheet to cache carefully. Ideally, we want to cache
// one of the sheets that will be kept alive by a document or
// parent sheet anyway, so that if someone then accesses it via
@ -1973,6 +1979,24 @@ Loader::DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
NS_RELEASE(aLoadData); // this will release parents and siblings and all that
}
void
Loader::MarkLoadTreeFailed(SheetLoadData* aLoadData)
{
if (aLoadData->mURI) {
LOG_URI(" Load failed: '%s'", aLoadData->mURI);
}
do {
aLoadData->mLoadFailed = true;
if (aLoadData->mParentData) {
MarkLoadTreeFailed(aLoadData->mParentData);
}
aLoadData = aLoadData->mNext;
} while (aLoadData);
}
nsresult
Loader::LoadInlineStyle(nsIContent* aElement,
const nsAString& aBuffer,
@ -2249,7 +2273,12 @@ Loader::LoadChildSheet(StyleSheet* aParentSheet,
nsIPrincipal* principal = aParentSheet->Principal();
nsresult rv = CheckContentPolicy(loadingPrincipal, principal, aURL, context, false);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
if (aParentData) {
MarkLoadTreeFailed(aParentData);
}
return rv;
}
nsCOMPtr<nsICSSLoaderObserver> observer;
@ -2538,10 +2567,12 @@ Loader::PostLoadEvent(nsIURI* aURI,
evt->mSheetAlreadyComplete = true;
// If we get to this code, aSheet loaded correctly at some point, so
// we can just use NS_OK for the status. Note that we do this here
// and not from inside our SheetComplete so that we don't end up
// running the load event async.
evt->ScheduleLoadEventIfNeeded(NS_OK);
// we can just schedule a load event and don't need to touch the
// data's mLoadFailed. Note that we do this here and not from
// inside our SheetComplete so that we don't end up running the load
// event async.
MOZ_ASSERT(!evt->mLoadFailed, "Why are we marked as failed?");
evt->ScheduleLoadEventIfNeeded();
}
return rv;

Просмотреть файл

@ -614,8 +614,12 @@ private:
// The guts of SheetComplete. This may be called recursively on parent datas
// or datas that had glommed on to a single load. The array is there so load
// datas whose observers need to be notified can be added to it.
void DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
LoadDataArray& aDatasToNotify);
void DoSheetComplete(SheetLoadData* aLoadData, LoadDataArray& aDatasToNotify);
// Mark the given SheetLoadData, as well as any of its siblings, parents, etc
// transitively, as failed. The idea is to mark as failed any load that was
// directly or indirectly @importing the sheet this SheetLoadData represents.
void MarkLoadTreeFailed(SheetLoadData* aLoadData);
StyleBackendType GetStyleBackendType() const;

Просмотреть файл

@ -77,7 +77,7 @@ public:
already_AddRefed<nsIURI> GetReferrerURI();
void ScheduleLoadEventIfNeeded(nsresult aStatus);
void ScheduleLoadEventIfNeeded();
NotNull<const Encoding*> DetermineNonBOMEncoding(nsACString const& aSegment,
nsIChannel* aChannel);
@ -177,6 +177,10 @@ public:
// https://www.w3.org/TR/resource-timing/#processing-model
bool mBlockResourceTiming : 1;
// Boolean flag indicating whether the load has failed. This will be set
// to true if this load, or the load of any descendant import, fails.
bool mLoadFailed : 1;
// This is the element that imported the sheet. Needed to get the
// charset set on it and to fire load/error events.
nsCOMPtr<nsIStyleSheetLinkingElement> mOwningElement;
@ -194,12 +198,6 @@ public:
// is non-null.
const Encoding* mPreloadEncoding;
// The status our load ended up with; this determines whether we
// should fire error events or load events. This gets initialized
// by ScheduleLoadEventIfNeeded, and is only used after that has
// been called.
MOZ_INIT_OUTSIDE_CTOR nsresult mStatus;
private:
void FireLoadEvent(nsIThreadInternal* aThread);
};

Просмотреть файл

@ -12,6 +12,7 @@
#include "nsCSSProps.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Casting.h"
#include "nsCSSKeywords.h"
#include "nsLayoutUtils.h"
@ -140,9 +141,13 @@ SortPropertyAndCount(const void* s1, const void* s2, void *closure)
{
const PropertyAndCount *pc1 = static_cast<const PropertyAndCount*>(s1);
const PropertyAndCount *pc2 = static_cast<const PropertyAndCount*>(s2);
// Primary sort by count (lowest to highest)
if (pc1->count != pc2->count)
return pc1->count - pc2->count;
if (pc1->count != pc2->count) {
return AssertedCast<int32_t>(pc1->count) -
AssertedCast<int32_t>(pc2->count);
}
// Secondary sort by property index (highest to lowest)
return pc2->property - pc1->property;
}

Просмотреть файл

@ -0,0 +1 @@
#importTarget { color: red ! important }

Просмотреть файл

@ -173,6 +173,12 @@ support-files = file_bug1375944.html Ahem.ttf
support-files = bug1382568-iframe.html
[test_bug1394302.html]
skip-if = !stylo # This is a stylo test; gecko isn't deterministic here
[test_bug1443344-1.html]
scheme = https
support-files = file_bug1443344.css
[test_bug1443344-2.html]
scheme = https
support-files = file_bug1443344.css
[test_cascade.html]
[test_ch_ex_no_infloops.html]
[test_change_hint_optimizations.html]
@ -252,6 +258,7 @@ skip-if = !stylo
[test_keyframes_rules.html]
[test_keyframes_vendor_prefix.html]
[test_load_events_on_stylesheets.html]
support-files = slow_broken_sheet.sjs slow_ok_sheet.sjs
[test_logical_properties.html]
[test_media_queries.html]
skip-if = android_version == '18' #debug-only failure; timed out #Android 4.3 aws only; bug 1030419

Просмотреть файл

@ -0,0 +1,16 @@
// Make sure our timer stays alive.
let gTimer;
function handleRequest(request, response)
{
response.setHeader("Content-Type", "text/html", false);
response.setStatusLine("1.1", 404, "Not Found");
response.processAsync();
gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
// Wait for 1s before responding; this should usually make sure this load comes in last.
gTimer.init(() => {
response.write("<h1>Hello</h1>");
response.finish();
}, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
}

Просмотреть файл

@ -0,0 +1,18 @@
// Make sure our timer stays alive.
let gTimer;
function handleRequest(request, response)
{
response.setHeader("Content-Type", "text/css", false);
response.setStatusLine("1.1", 200, "OK");
response.processAsync();
gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
// Wait for 1s before responding; this should usually make sure this load comes in last.
gTimer.init(() => {
// This sheet _does_ still get applied even though its importing link
// overall reports failure...
response.write("nosuchelement { background: red }");
response.finish();
}, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
}

Просмотреть файл

@ -0,0 +1,48 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1443344
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1443344</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
/** Test for Bug 1443344 **/
SimpleTest.waitForExplicitFinish();
var sheetURL = new URL("file_bug1443344.css", location.href);
sheetURL.protocol = "http";
var link = document.createElement("link");
link.href = `data:text/css,@import url("${sheetURL}");`
link.rel = "stylesheet";
var loadFired = false, errorFired = false;
link.onload = () => loadFired = true;
link.onerror = () => errorFired = true;
document.head.appendChild(link);
addLoadEvent(() => {
is(loadFired, false, "Should not fire onload for erroring @import");
is(errorFired, true, "Should fire onerror for erroring @import");
is(getComputedStyle($("importTarget")).color, "rgb(0, 255, 0)",
"Erroring sheet should not load");
SimpleTest.finish();
});
</script>
<style>
#importTarget { color: rgb(0, 255, 0); }
</style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1443344">Mozilla Bug 1443344</a>
<p id="display"><div id="importTarget"></div></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,48 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1443344
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1443344</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
/** Test for Bug 1443344 **/
SimpleTest.waitForExplicitFinish();
var sheetURL = new URL("file_bug1443344.css", location.href);
sheetURL.protocol = "http";
var link = document.createElement("link");
link.href = `data:text/css,@import url("data:text/css,@import url('${sheetURL}');");`
link.rel = "stylesheet";
var loadFired = false, errorFired = false;
link.onload = () => loadFired = true;
link.onerror = () => errorFired = true;
document.head.appendChild(link);
addLoadEvent(() => {
is(loadFired, false, "Should not fire onload for erroring @import");
is(errorFired, true, "Should fire onerror for erroring @import");
is(getComputedStyle($("importTarget")).color, "rgb(0, 255, 0)",
"Erroring sheet should not load");
SimpleTest.finish();
});
</script>
<style>
#importTarget { color: rgb(0, 255, 0); }
</style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1443344">Mozilla Bug 1443344</a>
<p id="display"><div id="importTarget"></div></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

Просмотреть файл

@ -47,9 +47,9 @@ is(pendingEventCounter, 0, "There should be no pending events");
// need to change that.
window.onmessage = function() {
messagePosted = true;
// There are 4 pending events: two from the two direct example.com loads,
// and 2 from the two data:text/css loads that import things
is(pendingEventCounter, 4, "Load event for sheet should have fired");
// There are 6 pending events: two from the two direct example.com loads,
// and 4 from the two data:text/css loads that import things
is(pendingEventCounter, 6, "Load event for sheet should have fired");
}
window.postMessage("", "*");
@ -91,19 +91,19 @@ is(pendingEventCounter, 2, "There should be two pending events");
++pendingEventCounter;
document.write('<link rel="stylesheet" href="http://www.example.com"\
onload="--pendingEventCounter;\
ok(false, \'Load event firing on broken stylesheet\')"\
ok(false, \'Load event firing on broken stylesheet 1\')"\
onerror="--pendingEventCounter;\
ok(true, \'Error event firing on broken stylesheet\')">');
ok(true, \'Error event firing on broken stylesheet 1\')">');
++pendingEventCounter;
var link = document.createElement("link");
link.rel = "stylesheet";
link.href = "http://www.example.com";
link.onload = function() { --pendingEventCounter;
ok(false, 'Load event firing on broken stylesheet');
ok(false, 'Load event firing on broken stylesheet 2');
};
link.onerror = function() { --pendingEventCounter;
ok(true, 'Error event firing on broken stylesheet');
ok(true, 'Error event firing on broken stylesheet 2');
}
document.body.appendChild(link);
@ -144,15 +144,43 @@ link = document.createElement("link");
link.rel = "stylesheet";
link.href = "data:text/css,@import url('http://www.example.com')";
link.onload = function() { --pendingEventCounter;
ok(false, 'Load event firing on broken stylesheet');
ok(false, 'Load event firing on broken stylesheet 3');
};
link.onerror = function() { --pendingEventCounter;
ok(true, 'Error event firing on broken stylesheet');
ok(true, 'Error event firing on broken stylesheet 3');
}
document.body.appendChild(link);
function absoluteURL(relativeURL) {
return new URL(relativeURL, location.href).href;
}
++pendingEventCounter;
link = document.createElement("link");
link.rel = "stylesheet";
link.href = `data:text/css,@import url('http://www.example.com'); @import url(${absoluteURL('slow_ok_sheet.sjs')});`;
link.onload = function() { --pendingEventCounter;
ok(false, 'Load event firing on broken stylesheet 4');
};
link.onerror = function() { --pendingEventCounter;
ok(true, 'Error event firing on broken stylesheet 4');
}
document.body.appendChild(link);
++pendingEventCounter;
link = document.createElement("link");
link.rel = "stylesheet";
link.href = `data:text/css,@import url(${absoluteURL('slow_broken_sheet.sjs')}); @import url('data:text/css,');`;
link.onload = function() { --pendingEventCounter;
ok(false, 'Load event firing on broken stylesheet 5');
};
link.onerror = function() { --pendingEventCounter;
ok(true, 'Error event firing on broken stylesheet 5');
}
document.body.appendChild(link);
// Make sure the load events for all those stylesheets have not fired yet
is(pendingEventCounter, 7, "There should be one pending event");
is(pendingEventCounter, 9, "There should be nine pending events");
</script>
</pre>

Просмотреть файл

@ -56,7 +56,6 @@
#include <stdint.h>
#ifdef __cplusplus
namespace mozilla {
/**
@ -64,13 +63,6 @@ namespace mozilla {
*/
static const uint32_t kGoldenRatioU32 = 0x9E3779B9U;
inline uint32_t
RotateBitsLeft32(uint32_t aValue, uint8_t aBits)
{
MOZ_ASSERT(aBits < 32);
return (aValue << aBits) | (aValue >> (32 - aBits));
}
namespace detail {
inline uint32_t
@ -104,21 +96,21 @@ AddU32ToHash(uint32_t aHash, uint32_t aValue)
* evaluates to |aValue|.
*
* (Number-theoretic aside: Because any odd number |m| is relatively prime to
* our modulus (2^32), the list
* our modulus (2**32), the list
*
* [x * m (mod 2^32) for 0 <= x < 2^32]
* [x * m (mod 2**32) for 0 <= x < 2**32]
*
* has no duplicate elements. This means that multiplying by |m| does not
* cause us to skip any possible hash values.
*
* It's also nice if |m| has large-ish order mod 2^32 -- that is, if the
* smallest k such that m^k == 1 (mod 2^32) is large -- so we can safely
* It's also nice if |m| has large-ish order mod 2**32 -- that is, if the
* smallest k such that m**k == 1 (mod 2**32) is large -- so we can safely
* multiply our hash value by |m| a few times without negating the
* multiplicative effect. Our golden ratio constant has order 2^29, which is
* multiplicative effect. Our golden ratio constant has order 2**29, which is
* more than enough for our purposes.)
*/
return mozilla::WrappingMultiply(kGoldenRatioU32,
(RotateBitsLeft32(aHash, 5) ^ aValue));
RotateLeft(aHash, 5) ^ aValue);
}
/**
@ -360,6 +352,7 @@ private:
return mV0 ^ mV1 ^ mV2 ^ mV3;
}
MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW
void sipRound()
{
mV0 += mV1;
@ -383,6 +376,5 @@ private:
};
} /* namespace mozilla */
#endif /* __cplusplus */
#endif /* mozilla_HashFunctions_h */

Просмотреть файл

@ -480,16 +480,19 @@ RoundUpPow2(size_t aValue)
* Rotates the bits of the given value left by the amount of the shift width.
*/
template<typename T>
MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW
inline T
RotateLeft(const T aValue, uint_fast8_t aShift)
{
static_assert(IsUnsigned<T>::value, "Rotates require unsigned values");
MOZ_ASSERT(aShift < sizeof(T) * CHAR_BIT, "Shift value is too large!");
MOZ_ASSERT(aShift > 0,
"Rotation by value length is undefined behavior, but compilers "
"do not currently fold a test into the rotate instruction. "
"Please remove this restriction when compilers optimize the "
"zero case (http://blog.regehr.org/archives/1063).");
static_assert(IsUnsigned<T>::value, "Rotates require unsigned values");
return (aValue << aShift) | (aValue >> (sizeof(T) * CHAR_BIT - aShift));
}
@ -497,16 +500,19 @@ RotateLeft(const T aValue, uint_fast8_t aShift)
* Rotates the bits of the given value right by the amount of the shift width.
*/
template<typename T>
MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW
inline T
RotateRight(const T aValue, uint_fast8_t aShift)
{
static_assert(IsUnsigned<T>::value, "Rotates require unsigned values");
MOZ_ASSERT(aShift < sizeof(T) * CHAR_BIT, "Shift value is too large!");
MOZ_ASSERT(aShift > 0,
"Rotation by value length is undefined behavior, but compilers "
"do not currently fold a test into the rotate instruction. "
"Please remove this restriction when compilers optimize the "
"zero case (http://blog.regehr.org/archives/1063).");
static_assert(IsUnsigned<T>::value, "Rotates require unsigned values");
return (aValue >> aShift) | (aValue << (sizeof(T) * CHAR_BIT - aShift));
}

Просмотреть файл

@ -61,6 +61,7 @@ class XorShift128PlusRNG {
/**
* Return a pseudo-random 64-bit number.
*/
MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW
uint64_t next() {
/*
* The offsetOfState*() methods below are provided so that exceedingly-rare

Просмотреть файл

@ -24,6 +24,8 @@ class NavigationDelegateTest {
companion object {
const val HELLO_HTML_PATH = "/assets/www/hello.html";
const val HELLO2_HTML_PATH = "/assets/www/hello2.html";
const val NEW_SESSION_HTML_PATH = "/assets/www/newSession.html";
const val NEW_SESSION_CHILD_HTML_PATH = "/assets/www/newSession_child.html";
}
@get:Rule val sessionRule = GeckoSessionTestRule()
@ -225,4 +227,40 @@ class NavigationDelegateTest {
}
})
}
@Test
@GeckoSessionTestRule.WithDisplay(width = 128, height = 128)
fun onNewSession_calledForNewWindow() {
sessionRule.session.loadTestPath(NEW_SESSION_HTML_PATH)
sessionRule.waitForPageStop()
sessionRule.delegateDuringNextWait(object : Callbacks.NavigationDelegate {
@AssertCalled(count = 1)
override fun onNewSession(session: GeckoSession, uri: String, response: GeckoSession.Response<GeckoSession>) {
response.respond(null)
}
})
sessionRule.synthesizeTap(5, 5)
sessionRule.waitUntilCalled(GeckoSession.NavigationDelegate::class, "onNewSession")
}
@Test(expected = IllegalArgumentException::class)
@GeckoSessionTestRule.WithDisplay(width = 128, height = 128)
fun onNewSession_doesNotAllowOpened() {
sessionRule.session.loadTestPath(NEW_SESSION_HTML_PATH)
sessionRule.waitForPageStop()
sessionRule.delegateDuringNextWait(object : Callbacks.NavigationDelegate {
@AssertCalled(count = 1)
override fun onNewSession(session: GeckoSession, uri: String, response: GeckoSession.Response<GeckoSession>) {
var session = GeckoSession(session.settings)
session.openWindow(null)
response.respond(session)
}
})
sessionRule.synthesizeTap(5, 5)
sessionRule.waitUntilCalled(GeckoSession.NavigationDelegate::class, "onNewSession")
}
}

Просмотреть файл

@ -1,299 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* 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/. */
package org.mozilla.geckoview.test;
import org.mozilla.geckoview.GeckoSession;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class NavigationTests extends BaseGeckoViewTest {
@Test
public void testLoadUri() {
loadTestPath("hello.html", new Runnable() {
@Override public void run() {
done();
}
});
waitUntilDone();
}
@Test
public void testGoBack() {
final String startPath = "hello.html";
loadTestPath(startPath, new Runnable() {
@Override public void run() {
loadTestPath("hello2.html", new Runnable() {
@Override public void run() {
mSession.setNavigationDelegate(new GeckoSession.NavigationDelegate() {
@Override
public void onLocationChange(GeckoSession session, String url) {
assertTrue("URL should end with " + startPath + ", got " + url, url.endsWith(startPath));
done();
}
@Override
public void onCanGoBack(GeckoSession session, boolean canGoBack) {
assertFalse("Should not be able to go back", canGoBack);
}
@Override
public void onCanGoForward(GeckoSession session, boolean canGoForward) {
assertTrue("Should be able to go forward", canGoForward);
}
@Override
public boolean onLoadUri(GeckoSession session, String uri, TargetWindow where) {
return false;
}
@Override
public void onNewSession(GeckoSession session, String uri, GeckoSession.Response<GeckoSession> response) {
response.respond(null);
}
});
mSession.goBack();
}
});
}
});
waitUntilDone();
}
@Test
public void testReload() {
loadTestPath("hello.html", new Runnable() {
@Override public void run() {
mSession.setProgressDelegate(new GeckoSession.ProgressDelegate() {
@Override
public void onPageStart(GeckoSession session, String url) {
}
@Override
public void onPageStop(GeckoSession session, boolean success) {
assertTrue(success);
done();
}
@Override
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
}
});
mSession.reload();
}
});
waitUntilDone();
}
@Test
public void testExpiredCert() {
mSession.setProgressDelegate(new GeckoSession.ProgressDelegate() {
private boolean mNotBlank;
@Override
public void onPageStart(GeckoSession session, String url) {
mNotBlank = !url.equals("about:blank");
}
@Override
public void onPageStop(GeckoSession session, boolean success) {
if (mNotBlank) {
assertFalse("Expected unsuccessful page load", success);
done();
}
}
@Override
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
assertFalse(securityInfo.isSecure);
assertEquals(securityInfo.securityMode, SecurityInformation.SECURITY_MODE_UNKNOWN);
}
});
mSession.loadUri("https://expired.badssl.com/");
waitUntilDone();
}
@Test
public void testValidTLS() {
mSession.setProgressDelegate(new GeckoSession.ProgressDelegate() {
private boolean mNotBlank;
@Override
public void onPageStart(GeckoSession session, String url) {
mNotBlank = !url.equals("about:blank");
}
@Override
public void onPageStop(GeckoSession session, boolean success) {
if (mNotBlank) {
assertTrue("Expected successful page load", success);
done();
}
}
@Override
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
assertTrue(securityInfo.isSecure);
assertEquals(securityInfo.securityMode, SecurityInformation.SECURITY_MODE_IDENTIFIED);
}
});
mSession.loadUri("https://mozilla-modern.badssl.com/");
waitUntilDone();
}
@Test
public void testOnNewSession() {
mSession.setNavigationDelegate(new GeckoSession.NavigationDelegate() {
@Override
public void onLocationChange(GeckoSession session, String url) {
}
@Override
public void onCanGoBack(GeckoSession session, boolean canGoBack) {
}
@Override
public void onCanGoForward(GeckoSession session, boolean canGoForward) {
}
@Override
public boolean onLoadUri(GeckoSession session, String uri, TargetWindow where) {
return false;
}
@Override
public void onNewSession(GeckoSession session, String uri, GeckoSession.Response<GeckoSession> response) {
final GeckoSession newSession = new GeckoSession(session.getSettings());
newSession.setContentDelegate(new GeckoSession.ContentDelegate() {
@Override
public void onTitleChange(GeckoSession session, String title) {
}
@Override
public void onFocusRequest(GeckoSession session) {
}
@Override
public void onCloseRequest(GeckoSession session) {
session.closeWindow();
done();
}
@Override
public void onFullScreen(GeckoSession session, boolean fullScreen) {
}
@Override
public void onContextMenu(GeckoSession session, int screenX, int screenY, String uri, String elementSrc) {
}
});
newSession.openWindow(InstrumentationRegistry.getTargetContext());
response.respond(newSession);
}
});
mSession.setProgressDelegate(new GeckoSession.ProgressDelegate() {
@Override
public void onPageStart(GeckoSession session, String url) {
}
@Override
public void onPageStop(GeckoSession session, boolean success) {
// Send a click to open the window
sendClick(100, 100);
}
@Override
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
}
});
mSession.loadUri(buildAssetUrl("newSession.html"));
waitUntilDone();
}
@Test(expected = IllegalArgumentException.class)
public void testOnNewSessionNoExisting() {
// This makes sure that we get an exception if you try to return
// an existing GeckoSession instance from the NavigationDelegate.onNewSession()
// implementation.
mSession.setNavigationDelegate(new GeckoSession.NavigationDelegate() {
@Override
public void onLocationChange(GeckoSession session, String url) {
}
@Override
public void onCanGoBack(GeckoSession session, boolean canGoBack) {
}
@Override
public void onCanGoForward(GeckoSession session, boolean canGoForward) {
}
@Override
public boolean onLoadUri(GeckoSession session, String uri, TargetWindow where) {
return false;
}
@Override
public void onNewSession(GeckoSession session, String uri, GeckoSession.Response<GeckoSession> response) {
// This is where the throw should occur
response.respond(mSession);
}
});
mSession.setProgressDelegate(new GeckoSession.ProgressDelegate() {
@Override
public void onPageStart(GeckoSession session, String url) {
}
@Override
public void onPageStop(GeckoSession session, boolean success) {
sendClick(100, 100);
}
@Override
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
}
});
mSession.loadUri(buildAssetUrl("newSession.html"));
waitUntilDone();
}
}

Просмотреть файл

@ -93,7 +93,6 @@ public class TestRunnerActivity extends Activity {
final GeckoSession session = new GeckoSession(settings);
session.setNavigationDelegate(mNavigationDelegate);
session.openWindow(this);
return session;
}
@ -108,6 +107,7 @@ public class TestRunnerActivity extends Activity {
// We can't use e10s because we get deadlocked when quickly creating and
// destroying sessions. Bug 1348361.
mSession = createSession();
mSession.openWindow(this);
// If we were passed a URI in the Intent, open it
final Uri uri = intent.getData();

Просмотреть файл

@ -3,6 +3,7 @@
package org.mozilla.geckoview.test.rule;
import org.mozilla.gecko.gfx.GeckoDisplay;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoSessionSettings;
import org.mozilla.geckoview.test.util.Callbacks;
@ -17,14 +18,19 @@ import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import android.app.Instrumentation;
import android.graphics.Point;
import android.graphics.SurfaceTexture;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.UiThreadTestRule;
import android.view.MotionEvent;
import android.view.Surface;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
@ -70,6 +76,16 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
long value();
}
/**
* Specify the display size for the GeckoSession in device pixels
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface WithDisplay {
int width();
int height();
}
/**
* Specify a list of GeckoSession settings to be applied to the GeckoSession object
* under test. Can be used on classes or methods. Note that the settings values must
@ -421,6 +437,7 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
protected ErrorCollector mErrorCollector;
protected GeckoSession mSession;
protected Point mDisplaySize;
protected Object mCallbackProxy;
protected List<CallRecord> mCallRecords;
protected CallbackDelegates mWaitScopeDelegates;
@ -429,6 +446,9 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
protected int mLastWaitEnd;
protected MethodCall mCurrentMethodCall;
protected long mTimeoutMillis = DEFAULT_TIMEOUT_MILLIS;
protected SurfaceTexture mDisplayTexture;
protected Surface mDisplaySurface;
protected GeckoDisplay mDisplay;
public GeckoSessionTestRule() {
mDefaultSettings = new GeckoSessionSettings();
@ -524,10 +544,22 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
for (final Setting setting : ((Setting.List) annotation).value()) {
setting.key().set(settings, setting.value());
}
} else if (WithDisplay.class.equals(annotation.annotationType())) {
final WithDisplay displaySize = (WithDisplay)annotation;
mDisplaySize = new Point(displaySize.width(), displaySize.height());
}
}
}
private static RuntimeException unwrapRuntimeException(Throwable e) {
final Throwable cause = e.getCause();
if (cause != null && cause instanceof RuntimeException) {
return (RuntimeException)cause;
}
return new RuntimeException(cause);
}
protected void prepareSession(final Description description) throws Throwable {
final GeckoSessionSettings settings = new GeckoSessionSettings(mDefaultSettings);
@ -562,7 +594,7 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
return method.invoke((call != null) ? call.target
: Callbacks.Default.INSTANCE, args);
} catch (final IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e.getCause() != null ? e.getCause() : e);
throw unwrapRuntimeException(e);
} finally {
mCurrentMethodCall = null;
}
@ -575,6 +607,13 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
mSession = new GeckoSession(settings);
if (mDisplaySize != null) {
mDisplayTexture = new SurfaceTexture(0);
mDisplaySurface = new Surface(mDisplayTexture);
mDisplay = mSession.acquireDisplay();
mDisplay.surfaceChanged(mDisplaySurface, mDisplaySize.x, mDisplaySize.y);
}
for (final Class<?> cls : CALLBACK_CLASSES) {
if (cls != null) {
getCallbackSetter(cls).invoke(mSession, mCallbackProxy);
@ -601,6 +640,17 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
if (mSession.isOpen()) {
mSession.closeWindow();
}
if (mDisplay != null) {
mDisplay.surfaceDestroyed();
mSession.releaseDisplay(mDisplay);
mDisplaySurface.release();
mDisplayTexture.release();
mDisplay = null;
mDisplayTexture = null;
mDisplaySurface = null;
}
mSession = null;
mCallbackProxy = null;
mCallRecords = null;
@ -681,7 +731,7 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
try {
msg = (Message) getNextMessage.invoke(queue);
} catch (final IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e.getCause() != null ? e.getCause() : e);
throw unwrapRuntimeException(e);
}
if (msg.getTarget() == handler && msg.obj == handler) {
// Our idle signal.
@ -827,7 +877,7 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
getCallbackGetter(ifce).invoke(mSession), sameInstance(mCallbackProxy));
} catch (final NoSuchMethodException | IllegalAccessException |
InvocationTargetException e) {
throw new RuntimeException(e.getCause() != null ? e.getCause() : e);
throw unwrapRuntimeException(e);
}
}
@ -913,7 +963,7 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
mCurrentMethodCall = methodCall;
record.method.invoke(callback, record.args);
} catch (final IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e.getCause() != null ? e.getCause() : e);
throw unwrapRuntimeException(e);
} finally {
mCurrentMethodCall = null;
}
@ -966,4 +1016,16 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
public void delegateDuringNextWait(final Object callback) {
mWaitScopeDelegates.delegate(callback);
}
public void synthesizeTap(int x, int y) {
final long downTime = SystemClock.uptimeMillis();
final MotionEvent down = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
MotionEvent.ACTION_DOWN, x, y, 0);
mSession.getPanZoomController().onTouchEvent(down);
final MotionEvent up = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP, x, y, 0);
mSession.getPanZoomController().onTouchEvent(up);
}
}

Просмотреть файл

@ -9,10 +9,10 @@ import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.mozglue.JNIObject;
import org.mozilla.gecko.util.ThreadUtils;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.Log;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.InputDevice;
@ -20,6 +20,9 @@ import java.util.ArrayList;
public final class NativePanZoomController extends JNIObject {
private static final String LOGTAG = "GeckoNPZC";
private static final int EVENT_SOURCE_SCROLL = 0;
private static final int EVENT_SOURCE_MOTION = 1;
private static final int EVENT_SOURCE_MOUSE = 2;
private final LayerSession mSession;
private final Rect mTempRect = new Rect();
@ -29,6 +32,8 @@ public final class NativePanZoomController extends JNIObject {
private SynthesizedEventState mPointerState;
private ArrayList<Pair<Integer, MotionEvent>> mQueuedEvents;
@WrapForJNI(calledFrom = "ui")
private native boolean handleMotionEvent(
int action, int actionIndex, long time, int metaState,
@ -48,6 +53,7 @@ public final class NativePanZoomController extends JNIObject {
private boolean handleMotionEvent(MotionEvent event) {
if (!mAttached) {
mQueuedEvents.add(new Pair(EVENT_SOURCE_MOTION, event));
return false;
}
@ -92,6 +98,7 @@ public final class NativePanZoomController extends JNIObject {
private boolean handleScrollEvent(MotionEvent event) {
if (!mAttached) {
mQueuedEvents.add(new Pair(EVENT_SOURCE_SCROLL, event));
return false;
}
@ -120,6 +127,7 @@ public final class NativePanZoomController extends JNIObject {
private boolean handleMouseEvent(MotionEvent event) {
if (!mAttached) {
mQueuedEvents.add(new Pair(EVENT_SOURCE_MOUSE, event));
return false;
}
@ -143,6 +151,7 @@ public final class NativePanZoomController extends JNIObject {
/* package */ NativePanZoomController(final LayerSession session) {
mSession = session;
enableEventQueue();
}
/**
@ -225,13 +234,44 @@ public final class NativePanZoomController extends JNIObject {
}
}
private void enableEventQueue() {
if (mQueuedEvents != null) {
throw new IllegalStateException("Already have an event queue");
}
mQueuedEvents = new ArrayList<>();
}
private void flushEventQueue() {
if (mQueuedEvents == null) {
return;
}
ArrayList<Pair<Integer, MotionEvent>> events = mQueuedEvents;
mQueuedEvents = null;
for (Pair<Integer, MotionEvent> pair : events) {
switch (pair.first) {
case EVENT_SOURCE_MOTION:
handleMotionEvent(pair.second);
break;
case EVENT_SOURCE_SCROLL:
handleScrollEvent(pair.second);
break;
case EVENT_SOURCE_MOUSE:
handleMouseEvent(pair.second);
break;
}
}
}
@WrapForJNI(calledFrom = "ui")
private void setAttached(final boolean attached) {
if (attached) {
mAttached = true;
flushEventQueue();
} else if (mAttached) {
mAttached = false;
disposeNative();
enableEventQueue();
}
}

Просмотреть файл

@ -153,11 +153,17 @@ public class GeckoSession extends LayerSession
new Response<GeckoSession>() {
@Override
public void respond(GeckoSession session) {
if (session != null && session.isOpen() && session.isReady()) {
throw new IllegalArgumentException("Must use a new GeckoSession instance");
if (session == null) {
callback.sendSuccess(null);
return;
}
callback.sendSuccess(session != null ? session.getId() : null);
if (session.isOpen()) {
throw new IllegalArgumentException("Must use an unopened GeckoSession instance");
}
session.openWindow(null);
callback.sendSuccess(session.getId());
}
});
}

Просмотреть файл

@ -80,7 +80,9 @@ class GeckoViewNavigation extends GeckoViewModule {
const handler = {
observe(aSubject, aTopic, aData) {
if (aTopic === "geckoview-window-created" && aSubject.name === aSessionId) {
aSubject.browser.presetOpenerWindow(aOpener);
if (aOpener) {
aSubject.browser.presetOpenerWindow(aOpener);
}
Services.obs.removeObserver(handler, "geckoview-window-created");
resolve(aSubject);
}
@ -108,7 +110,8 @@ class GeckoViewNavigation extends GeckoViewModule {
let browser = undefined;
this.eventDispatcher.sendRequestForResult(message).then(sessionId => {
return this.waitAndSetOpener(sessionId, aOpener);
return this.waitAndSetOpener(sessionId,
(aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_OPENER) ? null : aOpener);
}).then(window => {
browser = (window && window.browser);
}, () => {

Просмотреть файл

@ -18,6 +18,7 @@
#include "nsISupportsUtils.h"
#include "prio.h"
#include "plstr.h"
#include "mozilla/Attributes.h"
#include "mozilla/Logging.h"
#include "mozilla/UniquePtrExtensions.h"
#include "stdlib.h"
@ -964,6 +965,7 @@ nsZipFind::~nsZipFind()
*
* returns a hash key for the entry name
*/
MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW
static uint32_t HashName(const char* aName, uint16_t len)
{
MOZ_ASSERT(aName != 0);

Просмотреть файл

@ -27,6 +27,7 @@
#include "nsGlobalWindow.h"
#include "NullPrincipal.h"
#include "nsRedirectHistoryEntry.h"
#include "LoadInfo.h"
using namespace mozilla::dom;

Просмотреть файл

@ -159,7 +159,8 @@ private:
void SetIncludeCookiesSecFlag();
friend class mozilla::dom::XMLHttpRequestMainThread;
// if you add a member, please also update the copy constructor
// if you add a member, please also update the copy constructor and consider if
// it should be merged from parent channel through ParentLoadInfoForwarderArgs.
nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
nsCOMPtr<nsIPrincipal> mPrincipalToInherit;

Просмотреть файл

@ -113,6 +113,25 @@ union OptionalLoadInfoArgs
LoadInfoArgs;
};
/**
* This structure is used to carry selected properties of a LoadInfo
* object to child processes to merge LoadInfo changes from the parent
* process. We don't want to use LoadInfoArgs for that since it's
* too huge and we only care about small subpart of properties anyway.
*/
struct ParentLoadInfoForwarderArgs
{
// WebExtextensions' WebRequest API allows extensions to intercept and
// redirect a channel to a data URI. This modifications happens in
// the parent and needs to be mirrored to the child so that security
// checks can pass.
bool allowInsecureRedirectToDataURI;
// IMPORTANT: when you add new properites here you must also update
// LoadInfoToParentLoadInfoForwarder and MergeParentLoadInfoForwarder
// in BackgroundUtils.cpp/.h!
};
//-----------------------------------------------------------------------------
// HTTP IPDL structs
//-----------------------------------------------------------------------------

Просмотреть файл

@ -424,6 +424,7 @@ class StartRequestEvent : public NeckoTargetChannelEvent<HttpChannelChild>
const nsHttpResponseHead& aResponseHead,
const bool& aUseResponseHead,
const nsHttpHeaderArray& aRequestHeaders,
const ParentLoadInfoForwarderArgs& loadInfoForwarder,
const bool& aIsFromCache,
const bool& aCacheEntryAvailable,
const uint64_t& aCacheEntryId,
@ -457,13 +458,15 @@ class StartRequestEvent : public NeckoTargetChannelEvent<HttpChannelChild>
, mAltDataType(altDataType)
, mAltDataLen(altDataLen)
, mController(Move(aController))
, mLoadInfoForwarder(loadInfoForwarder)
{}
void Run() override
{
LOG(("StartRequestEvent [this=%p]\n", mChild));
mChild->OnStartRequest(mChannelStatus, mResponseHead, mUseResponseHead,
mRequestHeaders, mIsFromCache, mCacheEntryAvailable,
mRequestHeaders, mLoadInfoForwarder,
mIsFromCache, mCacheEntryAvailable,
mCacheEntryId, mCacheFetchCount,
mCacheExpirationTime, mCachedCharset,
mSecurityInfoSerialization, mSelfAddr, mPeerAddr,
@ -490,6 +493,7 @@ class StartRequestEvent : public NeckoTargetChannelEvent<HttpChannelChild>
nsCString mAltDataType;
int64_t mAltDataLen;
Maybe<ServiceWorkerDescriptor> mController;
ParentLoadInfoForwarderArgs mLoadInfoForwarder;
};
mozilla::ipc::IPCResult
@ -497,6 +501,7 @@ HttpChannelChild::RecvOnStartRequest(const nsresult& channelStatus,
const nsHttpResponseHead& responseHead,
const bool& useResponseHead,
const nsHttpHeaderArray& requestHeaders,
const ParentLoadInfoForwarderArgs& loadInfoForwarder,
const bool& isFromCache,
const bool& cacheEntryAvailable,
const uint64_t& cacheEntryId,
@ -531,6 +536,7 @@ HttpChannelChild::RecvOnStartRequest(const nsresult& channelStatus,
mEventQ->RunOrEnqueue(new StartRequestEvent(this, channelStatus, responseHead,
useResponseHead, requestHeaders,
loadInfoForwarder,
isFromCache, cacheEntryAvailable,
cacheEntryId, cacheFetchCount,
cacheExpirationTime, cachedCharset,
@ -567,6 +573,7 @@ HttpChannelChild::OnStartRequest(const nsresult& channelStatus,
const nsHttpResponseHead& responseHead,
const bool& useResponseHead,
const nsHttpHeaderArray& requestHeaders,
const ParentLoadInfoForwarderArgs& loadInfoForwarder,
const bool& isFromCache,
const bool& cacheEntryAvailable,
const uint64_t& cacheEntryId,
@ -603,6 +610,8 @@ HttpChannelChild::OnStartRequest(const nsresult& channelStatus,
getter_AddRefs(mSecurityInfo));
}
ipc::MergeParentLoadInfoForwarder(loadInfoForwarder, mLoadInfo);
mIsFromCache = isFromCache;
mCacheEntryAvailable = cacheEntryAvailable;
mCacheEntryId = cacheEntryId;
@ -1653,7 +1662,7 @@ class Redirect1Event : public NeckoTargetChannelEvent<HttpChannelChild>
const uint32_t& registrarId,
const URIParams& newURI,
const uint32_t& redirectFlags,
const bool& allowInsecureRedirectToDataURI,
const ParentLoadInfoForwarderArgs& loadInfoForwarder,
const nsHttpResponseHead& responseHead,
const nsACString& securityInfoSerialization,
const uint64_t& channelId)
@ -1661,33 +1670,34 @@ class Redirect1Event : public NeckoTargetChannelEvent<HttpChannelChild>
, mRegistrarId(registrarId)
, mNewURI(newURI)
, mRedirectFlags(redirectFlags)
, mAllowInsecureRedirectToDataURI(allowInsecureRedirectToDataURI)
, mResponseHead(responseHead)
, mSecurityInfoSerialization(securityInfoSerialization)
, mChannelId(channelId) {}
, mChannelId(channelId)
, mLoadInfoForwarder(loadInfoForwarder)
{
}
void Run() override
{
mChild->Redirect1Begin(mRegistrarId, mNewURI, mRedirectFlags,
mAllowInsecureRedirectToDataURI, mResponseHead,
mSecurityInfoSerialization, mChannelId);
mChild->Redirect1Begin(mRegistrarId, mNewURI, mRedirectFlags, mLoadInfoForwarder,
mResponseHead, mSecurityInfoSerialization, mChannelId);
}
private:
uint32_t mRegistrarId;
URIParams mNewURI;
uint32_t mRedirectFlags;
bool mAllowInsecureRedirectToDataURI;
nsHttpResponseHead mResponseHead;
nsCString mSecurityInfoSerialization;
uint64_t mChannelId;
ParentLoadInfoForwarderArgs mLoadInfoForwarder;
};
mozilla::ipc::IPCResult
HttpChannelChild::RecvRedirect1Begin(const uint32_t& registrarId,
const URIParams& newUri,
const uint32_t& redirectFlags,
const bool& allowInsecureRedirectToDataURI,
const ParentLoadInfoForwarderArgs& loadInfoForwarder,
const nsHttpResponseHead& responseHead,
const nsCString& securityInfoSerialization,
const uint64_t& channelId,
@ -1700,7 +1710,7 @@ HttpChannelChild::RecvRedirect1Begin(const uint32_t& registrarId,
mPeerAddr = oldPeerAddr;
mEventQ->RunOrEnqueue(new Redirect1Event(this, registrarId, newUri,
redirectFlags, allowInsecureRedirectToDataURI,
redirectFlags, loadInfoForwarder,
responseHead, securityInfoSerialization,
channelId));
return IPC_OK();
@ -1773,7 +1783,7 @@ void
HttpChannelChild::Redirect1Begin(const uint32_t& registrarId,
const URIParams& newOriginalURI,
const uint32_t& redirectFlags,
const bool& allowInsecureRedirectToDataURI,
const ParentLoadInfoForwarderArgs& loadInfoForwarder,
const nsHttpResponseHead& responseHead,
const nsACString& securityInfoSerialization,
const uint64_t& channelId)
@ -1782,7 +1792,7 @@ HttpChannelChild::Redirect1Begin(const uint32_t& registrarId,
LOG(("HttpChannelChild::Redirect1Begin [this=%p]\n", this));
mLoadInfo->SetAllowInsecureRedirectToDataURI(allowInsecureRedirectToDataURI);
ipc::MergeParentLoadInfoForwarder(loadInfoForwarder, mLoadInfo);
nsCOMPtr<nsIURI> uri = DeserializeURI(newOriginalURI);
@ -2344,7 +2354,8 @@ HttpChannelChild::OnRedirectVerifyCallback(nsresult result)
NS_IMETHODIMP
HttpChannelChild::Cancel(nsresult status)
{
LOG(("HttpChannelChild::Cancel [this=%p]\n", this));
LOG(("HttpChannelChild::Cancel [this=%p, status=%" PRIx32 "]\n",
this, static_cast<uint32_t>(status)));
MOZ_ASSERT(NS_IsMainThread());
if (!mCanceled) {

Просмотреть файл

@ -132,6 +132,7 @@ protected:
const nsHttpResponseHead& responseHead,
const bool& useResponseHead,
const nsHttpHeaderArray& requestHeaders,
const ParentLoadInfoForwarderArgs& loadInfoForwarder,
const bool& isFromCache,
const bool& cacheEntryAvailable,
const uint64_t& cacheEntryId,
@ -151,7 +152,7 @@ protected:
mozilla::ipc::IPCResult RecvRedirect1Begin(const uint32_t& registrarId,
const URIParams& newURI,
const uint32_t& redirectFlags,
const bool& allowInsecureRedirectToDataURI,
const ParentLoadInfoForwarderArgs& loadInfoForwarder,
const nsHttpResponseHead& responseHead,
const nsCString& securityInfoSerialization,
const uint64_t& channelId,
@ -402,6 +403,7 @@ private:
const nsHttpResponseHead& responseHead,
const bool& useResponseHead,
const nsHttpHeaderArray& requestHeaders,
const ParentLoadInfoForwarderArgs& loadInfoForwarder,
const bool& isFromCache,
const bool& cacheEntryAvailable,
const uint64_t& cacheEntryId,
@ -435,7 +437,7 @@ private:
void Redirect1Begin(const uint32_t& registrarId,
const URIParams& newUri,
const uint32_t& redirectFlags,
const bool& allowInsecureRedirectToDataURI,
const ParentLoadInfoForwarderArgs& loadInfoForwarder,
const nsHttpResponseHead& responseHead,
const nsACString& securityInfoSerialization,
const uint64_t& channelId);

Просмотреть файл

@ -1424,6 +1424,8 @@ HttpChannelParent::RecvRemoveCorsPreflightCacheEntry(const URIParams& uri,
NS_IMETHODIMP
HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
{
nsresult rv;
LOG(("HttpChannelParent::OnStartRequest [this=%p, aRequest=%p]\n",
this, aRequest));
MOZ_ASSERT(NS_IsMainThread());
@ -1534,6 +1536,12 @@ HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
int64_t altDataLen = chan->GetAltDataLength();
nsCOMPtr<nsILoadInfo> loadInfo;
Unused << chan->GetLoadInfo(getter_AddRefs(loadInfo));
ParentLoadInfoForwarderArgs loadInfoForwarderArg;
mozilla::ipc::LoadInfoToParentLoadInfoForwarder(loadInfo, &loadInfoForwarderArg);
// Maybe pass back the ServiceWorkerDescriptor controller for this channel.
// For subresource loads the controller is already known when the channel
// is first open and comes down to us via the LoadInfo. For non-subresource
@ -1542,25 +1550,22 @@ HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
// back to the child process so the resulting window/worker can set its
// navigator.serviceWorker.controller correctly immediately.
OptionalIPCServiceWorkerDescriptor ipcController = void_t();
if (ServiceWorkerParentInterceptEnabled()) {
nsCOMPtr<nsILoadInfo> loadInfo;
Unused << chan->GetLoadInfo(getter_AddRefs(loadInfo));
if (loadInfo) {
const Maybe<ServiceWorkerDescriptor>& controller = loadInfo->GetController();
if (controller.isSome()) {
ipcController = controller.ref().ToIPC();
}
if (ServiceWorkerParentInterceptEnabled() && loadInfo) {
const Maybe<ServiceWorkerDescriptor>& controller = loadInfo->GetController();
if (controller.isSome()) {
ipcController = controller.ref().ToIPC();
}
}
// !!! We need to lock headers and please don't forget to unlock them !!!
requestHead->Enter();
nsresult rv = NS_OK;
rv = NS_OK;
if (mIPCClosed ||
!SendOnStartRequest(channelStatus,
responseHead ? *responseHead : nsHttpResponseHead(),
!!responseHead,
requestHead->Headers(),
loadInfoForwarderArg,
isFromCache,
mCacheEntry ? true : false,
cacheEntryId,
@ -1907,13 +1912,18 @@ HttpChannelParent::StartRedirect(uint32_t registrarId,
rv = httpChannel->GetChannelId(&channelId);
NS_ENSURE_SUCCESS(rv, NS_BINDING_ABORTED);
}
nsCOMPtr<nsILoadInfo> loadInfo;
mChannel->GetLoadInfo(getter_AddRefs(loadInfo));
Unused << mChannel->GetLoadInfo(getter_AddRefs(loadInfo));
ParentLoadInfoForwarderArgs loadInfoForwarderArg;
mozilla::ipc::LoadInfoToParentLoadInfoForwarder(loadInfo, &loadInfoForwarderArg);
nsHttpResponseHead *responseHead = mChannel->GetResponseHead();
bool result = false;
if (!mIPCClosed) {
result = SendRedirect1Begin(registrarId, uriParams, redirectFlags,
loadInfo->GetAllowInsecureRedirectToDataURI(),
loadInfoForwarderArg,
responseHead ? *responseHead
: nsHttpResponseHead(),
secInfoSerialization,

Просмотреть файл

@ -97,6 +97,7 @@ child:
nsHttpResponseHead responseHead,
bool useResponseHead,
nsHttpHeaderArray requestHeaders,
ParentLoadInfoForwarderArgs loadInfoForwarder,
bool isFromCache,
bool cacheEntryAvailable,
uint64_t cacheEntryId,
@ -122,7 +123,7 @@ child:
async Redirect1Begin(uint32_t registrarId,
URIParams newOriginalUri,
uint32_t redirectFlags,
bool allowInsecureRedirectToDataURI,
ParentLoadInfoForwarderArgs loadInfoForwarder,
nsHttpResponseHead responseHead,
nsCString securityInfoSerialization,
uint64_t channelId,

Просмотреть файл

@ -958,8 +958,10 @@ nsRFPService::GetSpoofedModifierStates(const nsIDocument* aDoc,
return false;
}
// We will spoof the modifer state for Alt, Shift, AltGraph and Control.
if (aModifier & (MODIFIER_ALT | MODIFIER_SHIFT | MODIFIER_ALTGRAPH | MODIFIER_CONTROL)) {
// We will spoof the modifer state for Alt, Shift, and AltGraph.
// We don't spoof the Control key, because it is often used
// for command key combinations in web apps.
if (aModifier & (MODIFIER_ALT | MODIFIER_SHIFT | MODIFIER_ALTGRAPH)) {
SpoofingKeyboardCode keyCodeInfo;
if (GetSpoofedKeyCodeInfo(aDoc, aKeyboardEvent, keyCodeInfo)) {

Просмотреть файл

@ -722,3 +722,88 @@ TEST_F(TelemetryTestFixture, AccumulateKeyedCategoricalHistogram_MultipleEnumVal
<< "The sampleKey histogram did not accumulate the correct number of Label2 samples";
}
TEST_F(TelemetryTestFixture, AccumulateTimeDelta)
{
const uint32_t kExpectedValue = 100;
const TimeStamp start = TimeStamp::Now();
const TimeDuration delta = TimeDuration::FromMilliseconds(50);
AutoJSContextWithGlobal cx(mCleanGlobal);
GetAndClearHistogram(cx.GetJSContext(), mTelemetry, NS_LITERAL_CSTRING("TELEMETRY_TEST_COUNT"),
false);
// Accumulate in the histogram
Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_COUNT, start - delta, start);
Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_COUNT, start - delta, start);
Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_COUNT, start, start);
// end > start timestamp gives zero contribution
Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_COUNT, start + delta, start);
// Get a snapshot for all the histograms
JS::RootedValue snapshot(cx.GetJSContext());
GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT", &snapshot, false);
// Get the histogram from the snapshot
JS::RootedValue histogram(cx.GetJSContext());
GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_COUNT", snapshot, &histogram);
// Get "sum" property from histogram
JS::RootedValue sum(cx.GetJSContext());
GetProperty(cx.GetJSContext(), "sum", histogram, &sum);
// Check that the "sum" stored in the histogram matches with |kExpectedValue|
uint32_t uSum = 0;
JS::ToUint32(cx.GetJSContext(), sum, &uSum);
ASSERT_EQ(uSum, kExpectedValue) << "The histogram is not returning expected value";
}
TEST_F(TelemetryTestFixture, AccumulateKeyedTimeDelta)
{
const uint32_t kExpectedValue = 100;
const TimeStamp start = TimeStamp::Now();
const TimeDuration delta = TimeDuration::FromMilliseconds(50);
AutoJSContextWithGlobal cx(mCleanGlobal);
GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
NS_LITERAL_CSTRING("TELEMETRY_TEST_KEYED_COUNT"), true);
// Accumulate time delta in the provided key within the histogram
Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_KEYED_COUNT, NS_LITERAL_CSTRING("sample"),
start - delta, start);
Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_KEYED_COUNT, NS_LITERAL_CSTRING("sample"),
start - delta, start);
// end > start timestamp gives zero contribution
Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_KEYED_COUNT, NS_LITERAL_CSTRING("sample"),
start + delta, start);
Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_KEYED_COUNT, NS_LITERAL_CSTRING("sample"),
start, start);
// Get a snapshot for all the histograms
JS::RootedValue snapshot(cx.GetJSContext());
GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_COUNT", &snapshot, true);
// Get the histogram from the snapshot
JS::RootedValue histogram(cx.GetJSContext());
GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_COUNT", snapshot, &histogram);
// Get "sample" property from histogram
JS::RootedValue expectedKeyData(cx.GetJSContext());
GetProperty(cx.GetJSContext(), "sample", histogram, &expectedKeyData);
// Get "sum" property from keyed data
JS::RootedValue sum(cx.GetJSContext());
GetProperty(cx.GetJSContext(), "sum", expectedKeyData, &sum);
// Check that the sum stored in the histogram matches with |kExpectedValue|
uint32_t uSum = 0;
JS::ToUint32(cx.GetJSContext(), sum, &uSum);
ASSERT_EQ(uSum, kExpectedValue) << "The histogram is not returning expected sum";
}

Просмотреть файл

@ -1,240 +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 https://mozilla.org/MPL/2.0/. */
#include "CertAnnotator.h"
#include "mozilla/JSONWriter.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/Services.h"
#include "mozilla/WinDllServices.h"
#include "nsExceptionHandler.h"
#include "nsIFile.h"
#include "nsIObserverService.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "nsXPCOM.h"
#include "nsXULAppAPI.h"
#include "nsWindowsHelpers.h"
#include <tlhelp32.h>
namespace mozilla {
NS_IMPL_ISUPPORTS(CertAnnotator, nsIObserver)
CertAnnotator::~CertAnnotator()
{
if (mAnnotatorThread) {
mAnnotatorThread->Shutdown();
}
}
bool
CertAnnotator::Init()
{
if (mAnnotatorThread) {
return true;
}
nsCOMPtr<nsIRunnable> initialEvent =
NewRunnableMethod("mozilla::CertAnnotator::RecordInitialCertInfo", this,
&CertAnnotator::RecordInitialCertInfo);
nsresult rv = NS_NewNamedThread("Cert Annotator",
getter_AddRefs(mAnnotatorThread),
initialEvent);
return NS_SUCCEEDED(rv);
}
NS_IMETHODIMP
CertAnnotator::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
{
MOZ_ASSERT(!strcmp(aTopic, DllServices::kTopicDllLoadedMainThread) ||
!strcmp(aTopic, DllServices::kTopicDllLoadedNonMainThread));
MOZ_ASSERT(mAnnotatorThread);
if (!mAnnotatorThread) {
return NS_OK;
}
nsCOMPtr<nsIRunnable> event =
NewRunnableMethod<nsString, bool>("mozilla::CertAnnotator::RecordCertInfo",
this, &CertAnnotator::RecordCertInfo,
aData, true);
mAnnotatorThread->Dispatch(event, NS_DISPATCH_NORMAL);
return NS_OK;
}
void
CertAnnotator::RecordInitialCertInfo()
{
MOZ_ASSERT(!NS_IsMainThread());
nsAutoHandle snapshot(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0));
MOZ_ASSERT(snapshot != INVALID_HANDLE_VALUE);
if (snapshot == INVALID_HANDLE_VALUE) {
return;
}
MODULEENTRY32W moduleEntry = { sizeof(moduleEntry) };
if (!::Module32FirstW(snapshot, &moduleEntry)) {
return;
}
do {
RecordCertInfo(nsDependentString(moduleEntry.szExePath), false);
} while(::Module32NextW(snapshot, &moduleEntry));
Serialize();
}
void
CertAnnotator::RecordCertInfo(const nsAString& aLibPath, const bool aDoSerialize)
{
MOZ_ASSERT(!NS_IsMainThread());
// (1) Get Lowercase Module Name
nsCOMPtr<nsIFile> file;
nsresult rv = NS_NewLocalFile(aLibPath, false, getter_AddRefs(file));
if (NS_FAILED(rv)) {
return;
}
nsAutoString key;
rv = file->GetLeafName(key);
if (NS_FAILED(rv)) {
return;
}
ToLowerCase(key);
// (2) Get cert subject info
auto flatLibPath = PromiseFlatString(aLibPath);
RefPtr<mozilla::DllServices> dllSvc(mozilla::DllServices::Get());
auto orgName = dllSvc->GetBinaryOrgName(flatLibPath.get());
if (!orgName) {
return;
}
// (3) Insert into hash table
auto& modulesForThisSubject = mCertTable.GetOrInsert(nsString(orgName.get()));
if (modulesForThisSubject.ContainsSorted(key)) {
return;
}
modulesForThisSubject.InsertElementSorted(Move(key));
if (!aDoSerialize) {
return;
}
// (4) Serialize and annotate
Serialize();
}
} // namespace mozilla
namespace {
class Writer final : public mozilla::JSONWriteFunc
{
public:
virtual void Write(const char* aStr) override
{
mStr += aStr;
}
const nsCString& Get() const
{
return mStr;
}
private:
nsCString mStr;
};
} // anonymous namespace
namespace mozilla {
void
CertAnnotator::Serialize()
{
MOZ_ASSERT(!NS_IsMainThread());
JSONWriter json(MakeUnique<Writer>());
#if defined(DEBUG)
const JSONWriter::CollectionStyle style = JSONWriter::MultiLineStyle;
#else
const JSONWriter::CollectionStyle style = JSONWriter::SingleLineStyle;
#endif
json.Start(style);
for (auto subjectItr = mCertTable.Iter(); !subjectItr.Done(); subjectItr.Next()) {
json.StartArrayProperty(NS_ConvertUTF16toUTF8(subjectItr.Key()).get());
auto& modules = subjectItr.Data();
for (auto&& module : modules) {
json.StringElement(NS_ConvertUTF16toUTF8(module).get());
}
json.EndArray();
}
json.End();
const nsCString& serialized = static_cast<Writer*>(json.WriteFunc())->Get();
if (XRE_IsParentProcess()) {
// Safe to do off main thread in the parent process
Annotate(serialized);
return;
}
nsCOMPtr<nsIRunnable> event =
NewRunnableMethod<nsCString>("mozilla::CertAnnotator::Annotate", this,
&CertAnnotator::Annotate, serialized);
NS_DispatchToMainThread(event);
}
void
CertAnnotator::Annotate(const nsCString& aAnnotation)
{
nsAutoCString annotationKey;
annotationKey.AppendLiteral("ModuleSignatureInfo");
if (XRE_IsParentProcess()) {
annotationKey.AppendLiteral("Parent");
} else {
MOZ_ASSERT(NS_IsMainThread());
annotationKey.AppendLiteral("Child");
}
CrashReporter::AnnotateCrashReport(annotationKey, aAnnotation);
}
void
CertAnnotator::Register()
{
RefPtr<CertAnnotator> annotator(new CertAnnotator());
if (!annotator->Init()) {
return;
}
nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService());
obsServ->AddObserver(annotator, DllServices::kTopicDllLoadedMainThread,
false);
obsServ->AddObserver(annotator, DllServices::kTopicDllLoadedNonMainThread,
false);
}
} // namespace mozilla

Просмотреть файл

@ -1,47 +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 https://mozilla.org/MPL/2.0/. */
#ifndef mozilla_CertAnnotator_h
#define mozilla_CertAnnotator_h
#include "mozilla/Move.h"
#include "mozilla/Mutex.h"
#include "nsDataHashtable.h"
#include "nsIObserver.h"
#include "nsIThread.h"
#include "nsString.h"
#include "nsTArray.h"
namespace mozilla {
class CertAnnotator final : public nsIObserver
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIOBSERVER
static void Register();
private:
CertAnnotator() = default;
virtual ~CertAnnotator();
bool Init();
void RecordInitialCertInfo();
void RecordCertInfo(const nsAString& aLibPath, const bool aDoSerialize);
void Serialize();
void Annotate(const nsCString& aAnnotation);
nsDataHashtable<nsStringHashKey, nsTArray<nsString>> mCertTable;
nsCOMPtr<nsIThread> mAnnotatorThread;
};
} // namespace mozilla
#endif // mozilla_CertAnnotator_h

Просмотреть файл

@ -35,13 +35,6 @@ if CONFIG['MOZ_CRASHREPORTER']:
if CONFIG['MOZ_CRASHREPORTER_INJECTOR']:
DIRS += ['breakpad-windows-standalone']
UNIFIED_SOURCES += [
'CertAnnotator.cpp',
]
EXPORTS.mozilla += [
'CertAnnotator.h',
]
elif CONFIG['OS_ARCH'] == 'Darwin':
DIRS += [
'breakpad-client',

Просмотреть файл

@ -84,6 +84,9 @@ GetWindowsFolder(int aFolder, nsIFile** aFile)
// Append the trailing slash
int len = wcslen(path);
if (len == 0) {
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
}
if (len > 1 && path[len - 1] != L'\\') {
path[len] = L'\\';
path[++len] = L'\0';