зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to autoland. a=merge CLOSED TREE
This commit is contained in:
Коммит
f93e407658
|
@ -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';
|
||||
|
|
Загрузка…
Ссылка в новой задаче