зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to b2g-inbound. a=merge
This commit is contained in:
Коммит
60d7b761cb
|
@ -693,50 +693,57 @@ pref("dom.ipc.processPriorityManager.backgroundLRUPoolLevels", 5);
|
|||
// Kernel parameters for process priorities. These affect how processes are
|
||||
// killed on low-memory and their relative CPU priorities.
|
||||
//
|
||||
// Note: The maximum nice value on Linux is 19, but the max value you should
|
||||
// use here is 18. NSPR adds 1 to some threads' nice values, to mark
|
||||
// low-priority threads. If the process priority manager were to renice a
|
||||
// process (and all its threads) to 19, all threads would have the same
|
||||
// niceness. Then when we reniced the process to (say) 10, all threads would
|
||||
// /still/ have the same niceness; we'd effectively have erased NSPR's thread
|
||||
// priorities.
|
||||
|
||||
// The kernel can only accept 6 (OomScoreAdjust, KillUnderKB) pairs. But it is
|
||||
// okay, kernel will still kill processes with larger OomScoreAdjust first even
|
||||
// its OomScoreAdjust don't have a corresponding KillUnderKB.
|
||||
|
||||
pref("hal.processPriorityManager.gonk.MASTER.OomScoreAdjust", 0);
|
||||
pref("hal.processPriorityManager.gonk.MASTER.KillUnderKB", 4096);
|
||||
pref("hal.processPriorityManager.gonk.MASTER.Nice", 0);
|
||||
pref("hal.processPriorityManager.gonk.MASTER.cgroup", "");
|
||||
|
||||
pref("hal.processPriorityManager.gonk.PREALLOC.OomScoreAdjust", 67);
|
||||
pref("hal.processPriorityManager.gonk.PREALLOC.Nice", 18);
|
||||
pref("hal.processPriorityManager.gonk.PREALLOC.cgroup", "apps/bg_non_interactive");
|
||||
|
||||
pref("hal.processPriorityManager.gonk.FOREGROUND_HIGH.OomScoreAdjust", 67);
|
||||
pref("hal.processPriorityManager.gonk.FOREGROUND_HIGH.KillUnderKB", 5120);
|
||||
pref("hal.processPriorityManager.gonk.FOREGROUND_HIGH.Nice", 0);
|
||||
pref("hal.processPriorityManager.gonk.FOREGROUND_HIGH.cgroup", "apps/critical");
|
||||
|
||||
pref("hal.processPriorityManager.gonk.FOREGROUND.OomScoreAdjust", 134);
|
||||
pref("hal.processPriorityManager.gonk.FOREGROUND.KillUnderKB", 6144);
|
||||
pref("hal.processPriorityManager.gonk.FOREGROUND.Nice", 1);
|
||||
pref("hal.processPriorityManager.gonk.FOREGROUND.cgroup", "apps");
|
||||
|
||||
pref("hal.processPriorityManager.gonk.FOREGROUND_KEYBOARD.OomScoreAdjust", 200);
|
||||
pref("hal.processPriorityManager.gonk.FOREGROUND_KEYBOARD.Nice", 1);
|
||||
pref("hal.processPriorityManager.gonk.FOREGROUND_KEYBOARD.cgroup", "apps");
|
||||
|
||||
pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.OomScoreAdjust", 400);
|
||||
pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.KillUnderKB", 7168);
|
||||
pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.Nice", 7);
|
||||
pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.cgroup", "apps/bg_perceivable");
|
||||
|
||||
pref("hal.processPriorityManager.gonk.BACKGROUND_HOMESCREEN.OomScoreAdjust", 534);
|
||||
pref("hal.processPriorityManager.gonk.BACKGROUND_HOMESCREEN.KillUnderKB", 8192);
|
||||
pref("hal.processPriorityManager.gonk.BACKGROUND_HOMESCREEN.Nice", 18);
|
||||
pref("hal.processPriorityManager.gonk.BACKGROUND_HOMESCREEN.cgroup", "apps/bg_non_interactive");
|
||||
|
||||
pref("hal.processPriorityManager.gonk.BACKGROUND.OomScoreAdjust", 667);
|
||||
pref("hal.processPriorityManager.gonk.BACKGROUND.KillUnderKB", 20480);
|
||||
pref("hal.processPriorityManager.gonk.BACKGROUND.Nice", 18);
|
||||
pref("hal.processPriorityManager.gonk.BACKGROUND.cgroup", "apps/bg_non_interactive");
|
||||
|
||||
// Processes get this niceness when they have low CPU priority.
|
||||
pref("hal.processPriorityManager.gonk.LowCPUNice", 18);
|
||||
// Control group definitions (i.e., CPU priority groups) for B2G processes.
|
||||
|
||||
// Foreground apps
|
||||
pref("hal.processPriorityManager.gonk.cgroups.apps.cpu_shares", 1024);
|
||||
pref("hal.processPriorityManager.gonk.cgroups.apps.cpu_notify_on_migrate", 0);
|
||||
|
||||
// Foreground apps with high priority, 16x more CPU than foreground ones
|
||||
pref("hal.processPriorityManager.gonk.cgroups.apps/critical.cpu_shares", 16384);
|
||||
pref("hal.processPriorityManager.gonk.cgroups.apps/critical.cpu_notify_on_migrate", 0);
|
||||
|
||||
// Background perceivable apps, ~10x less CPU than foreground ones
|
||||
pref("hal.processPriorityManager.gonk.cgroups.apps/bg_perceivable.cpu_shares", 103);
|
||||
pref("hal.processPriorityManager.gonk.cgroups.apps/bg_perceivable.cpu_notify_on_migrate", 0);
|
||||
|
||||
// Background apps, ~20x less CPU than foreground ones and ~2x less than perceivable ones
|
||||
pref("hal.processPriorityManager.gonk.cgroups.apps/bg_non_interactive.cpu_shares", 52);
|
||||
pref("hal.processPriorityManager.gonk.cgroups.apps/bg_non_interactive.cpu_notify_on_migrate", 0);
|
||||
|
||||
// By default the compositor thread on gonk runs without real-time priority. RT
|
||||
// priority can be enabled by setting this pref to a value between 1 and 99.
|
||||
|
|
|
@ -266,7 +266,7 @@ function initPluginsRow() {
|
|||
let vulnerableLabel = document.getElementById("browserBundle").getString("pluginActivateVulnerable.label");
|
||||
let pluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
|
||||
|
||||
let permissionMap = Map();
|
||||
let permissionMap = new Map();
|
||||
|
||||
for (let plugin of pluginHost.getPluginTags()) {
|
||||
if (plugin.disabled) {
|
||||
|
|
|
@ -28,7 +28,7 @@ const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {})
|
|||
|
||||
this.EXPORTED_SYMBOLS = ["BrowserToolboxProcess"];
|
||||
|
||||
let processes = Set();
|
||||
let processes = new Set();
|
||||
|
||||
/**
|
||||
* Constructor for creating a process that will hold a chrome toolbox.
|
||||
|
|
|
@ -2116,7 +2116,7 @@ Scope.prototype = {
|
|||
// Creating maps and arrays thousands of times for variables or properties
|
||||
// with a large number of children fills up a lot of memory. Make sure
|
||||
// these are instantiated only if needed.
|
||||
DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_store", Map);
|
||||
DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_store", () => new Map());
|
||||
DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_enumItems", Array);
|
||||
DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_nonEnumItems", Array);
|
||||
|
||||
|
|
|
@ -617,7 +617,7 @@ Item.prototype = {
|
|||
|
||||
// Creating maps thousands of times for widgets with a large number of children
|
||||
// fills up a lot of memory. Make sure these are instantiated only if needed.
|
||||
DevToolsUtils.defineLazyPrototypeGetter(Item.prototype, "_itemsByElement", Map);
|
||||
DevToolsUtils.defineLazyPrototypeGetter(Item.prototype, "_itemsByElement", () => new Map());
|
||||
|
||||
/**
|
||||
* Some generic Widget methods handling Item instances.
|
||||
|
|
|
@ -318,43 +318,6 @@ FindObjectClass(JSContext* cx, JSObject* aGlobalObject)
|
|||
sObjectClass = js::GetObjectJSClass(obj);
|
||||
}
|
||||
|
||||
static inline nsresult
|
||||
WrapNative(JSContext *cx, nsISupports *native,
|
||||
nsWrapperCache *cache, const nsIID* aIID, JS::MutableHandle<JS::Value> vp,
|
||||
bool aAllowWrapping)
|
||||
{
|
||||
if (!native) {
|
||||
vp.setNull();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
JSObject *wrapper = xpc_FastGetCachedWrapper(cx, cache, vp);
|
||||
if (wrapper) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
|
||||
return nsDOMClassInfo::XPConnect()->WrapNativeToJSVal(cx, scope, native,
|
||||
cache, aIID,
|
||||
aAllowWrapping, vp);
|
||||
}
|
||||
|
||||
static inline nsresult
|
||||
WrapNative(JSContext *cx, nsISupports *native, const nsIID* aIID,
|
||||
bool aAllowWrapping, JS::MutableHandle<JS::Value> vp)
|
||||
{
|
||||
return WrapNative(cx, native, nullptr, aIID, vp, aAllowWrapping);
|
||||
}
|
||||
|
||||
// Same as the WrapNative above, but use these if aIID is nsISupports' IID.
|
||||
static inline nsresult
|
||||
WrapNative(JSContext *cx, nsISupports *native,
|
||||
bool aAllowWrapping, JS::MutableHandle<JS::Value> vp)
|
||||
{
|
||||
return WrapNative(cx, native, nullptr, nullptr, vp, aAllowWrapping);
|
||||
}
|
||||
|
||||
// Helper to handle torn-down inner windows.
|
||||
static inline nsresult
|
||||
SetParentToWindow(nsGlobalWindow *win, JSObject **parent)
|
||||
|
@ -1349,7 +1312,7 @@ BaseStubConstructor(nsIWeakReference* aWeakOwner,
|
|||
}
|
||||
|
||||
js::AssertSameCompartment(cx, obj);
|
||||
return WrapNative(cx, native, true, args.rval());
|
||||
return nsContentUtils::WrapNative(cx, native, args.rval(), true);
|
||||
}
|
||||
|
||||
static nsresult
|
||||
|
@ -1792,7 +1755,7 @@ nsDOMConstructor::ToString(nsAString &aResult)
|
|||
static nsresult
|
||||
GetXPCProto(nsIXPConnect *aXPConnect, JSContext *cx, nsGlobalWindow *aWin,
|
||||
const nsGlobalNameStruct *aNameStruct,
|
||||
nsIXPConnectJSObjectHolder **aProto)
|
||||
JS::MutableHandle<JSObject*> aProto)
|
||||
{
|
||||
NS_ASSERTION(aNameStruct->mType ==
|
||||
nsGlobalNameStruct::eTypeClassConstructor ||
|
||||
|
@ -1813,18 +1776,14 @@ GetXPCProto(nsIXPConnect *aXPConnect, JSContext *cx, nsGlobalWindow *aWin,
|
|||
}
|
||||
NS_ENSURE_TRUE(ci, NS_ERROR_UNEXPECTED);
|
||||
|
||||
nsCOMPtr<nsIXPConnectJSObjectHolder> proto_holder;
|
||||
nsresult rv =
|
||||
aXPConnect->GetWrappedNativePrototype(cx, aWin->GetGlobalJSObject(), ci,
|
||||
aProto);
|
||||
getter_AddRefs(proto_holder));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
JS::Rooted<JSObject*> proto_obj(cx, (*aProto)->GetJSObject());
|
||||
if (!JS_WrapObject(cx, &proto_obj)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IF_RELEASE(*aProto);
|
||||
return aXPConnect->HoldObject(cx, proto_obj, aProto);
|
||||
aProto.set(proto_holder->GetJSObject());
|
||||
return JS_WrapObject(cx, aProto) ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Either ci_data must be non-null or name_struct must be non-null and of type
|
||||
|
@ -1852,8 +1811,9 @@ ResolvePrototype(nsIXPConnect *aXPConnect, nsGlobalWindow *aWin, JSContext *cx,
|
|||
JS::Rooted<JS::Value> v(cx);
|
||||
|
||||
js::AssertSameCompartment(cx, obj);
|
||||
rv = WrapNative(cx, constructor, &NS_GET_IID(nsIDOMDOMConstructor),
|
||||
false, &v);
|
||||
rv = nsContentUtils::WrapNative(cx, constructor,
|
||||
&NS_GET_IID(nsIDOMDOMConstructor), &v,
|
||||
false);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
FillPropertyDescriptor(ctorDesc, obj, 0, v);
|
||||
|
@ -2223,8 +2183,9 @@ nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
|
|||
|
||||
JS::Rooted<JS::Value> v(cx);
|
||||
js::AssertSameCompartment(cx, obj);
|
||||
rv = WrapNative(cx, constructor, &NS_GET_IID(nsIDOMDOMConstructor),
|
||||
false, &v);
|
||||
rv = nsContentUtils::WrapNative(cx, constructor,
|
||||
&NS_GET_IID(nsIDOMDOMConstructor), &v,
|
||||
false);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
JS::Rooted<JSObject*> class_obj(cx, &v.toObject());
|
||||
|
@ -2255,10 +2216,12 @@ nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
|
|||
// Create the XPConnect prototype for our classinfo, PostCreateProto will
|
||||
// set up the prototype chain. This will go ahead and define things on the
|
||||
// actual window's global.
|
||||
nsCOMPtr<nsIXPConnectJSObjectHolder> proto_holder;
|
||||
JS::Rooted<JSObject*> dot_prototype(cx);
|
||||
rv = GetXPCProto(nsDOMClassInfo::sXPConnect, cx, aWin, name_struct,
|
||||
getter_AddRefs(proto_holder));
|
||||
&dot_prototype);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
MOZ_ASSERT(dot_prototype);
|
||||
|
||||
bool isXray = xpc::WrapperFactory::IsXrayWrapper(obj);
|
||||
MOZ_ASSERT_IF(obj != aWin->GetGlobalJSObject(), isXray);
|
||||
if (!isXray) {
|
||||
|
@ -2269,9 +2232,6 @@ nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
|
|||
|
||||
// This is the Xray case. Look up the constructor object for this
|
||||
// prototype.
|
||||
JS::Rooted<JSObject*> dot_prototype(cx, proto_holder->GetJSObject());
|
||||
NS_ENSURE_STATE(dot_prototype);
|
||||
|
||||
const nsDOMClassInfoData *ci_data;
|
||||
if (name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor) {
|
||||
ci_data = &sClassInfoData[name_struct->mDOMClassInfoID];
|
||||
|
@ -2301,13 +2261,11 @@ nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
|
|||
// We need to use the XPConnect prototype for the DOM class that this
|
||||
// constructor is an alias for (for example for Image we need the prototype
|
||||
// for HTMLImageElement).
|
||||
nsCOMPtr<nsIXPConnectJSObjectHolder> proto_holder;
|
||||
JS::Rooted<JSObject*> dot_prototype(cx);
|
||||
rv = GetXPCProto(nsDOMClassInfo::sXPConnect, cx, aWin, alias_struct,
|
||||
getter_AddRefs(proto_holder));
|
||||
&dot_prototype);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
JSObject* dot_prototype = proto_holder->GetJSObject();
|
||||
NS_ENSURE_STATE(dot_prototype);
|
||||
MOZ_ASSERT(dot_prototype);
|
||||
|
||||
const nsDOMClassInfoData *ci_data;
|
||||
if (alias_struct->mType == nsGlobalNameStruct::eTypeClassConstructor) {
|
||||
|
@ -2332,8 +2290,9 @@ nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
|
|||
|
||||
JS::Rooted<JS::Value> val(cx);
|
||||
js::AssertSameCompartment(cx, obj);
|
||||
rv = WrapNative(cx, constructor, &NS_GET_IID(nsIDOMDOMConstructor),
|
||||
true, &val);
|
||||
rv = nsContentUtils::WrapNative(cx, constructor,
|
||||
&NS_GET_IID(nsIDOMDOMConstructor), &val,
|
||||
true);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
NS_ASSERTION(val.isObject(), "Why didn't we get a JSObject?");
|
||||
|
@ -2370,7 +2329,7 @@ nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
|
|||
NS_ENSURE_TRUE(inner, NS_ERROR_UNEXPECTED);
|
||||
}
|
||||
|
||||
rv = WrapNative(cx, native, true, &prop_val);
|
||||
rv = nsContentUtils::WrapNative(cx, native, &prop_val, true);
|
||||
}
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
|
|
@ -29,8 +29,9 @@ interface nsISettingsServiceLock : nsISupports
|
|||
void get(in string aName, in nsISettingsServiceCallback aCallback);
|
||||
};
|
||||
|
||||
[scriptable, uuid(0505acf0-8e76-11e3-baa8-0800200c9a66)]
|
||||
[scriptable, uuid(d1ed155c-9f90-47bb-91c2-7eac54d69f4b)]
|
||||
interface nsISettingsService : nsISupports
|
||||
{
|
||||
nsISettingsServiceLock createLock([optional] in nsISettingsTransactionCompleteCallback aCallback);
|
||||
void receiveMessage(in jsval aMessage);
|
||||
};
|
||||
|
|
|
@ -201,15 +201,6 @@ IsAndroidAvailable()
|
|||
#ifndef MOZ_WIDGET_ANDROID
|
||||
return false;
|
||||
#else
|
||||
// PowerVR is very slow at texture allocation for some reason, which causes poor performance.
|
||||
nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
|
||||
|
||||
nsString vendor;
|
||||
if (NS_FAILED(gfxInfo->GetAdapterVendorID(vendor)) ||
|
||||
vendor.Find("Imagination") == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We need android.media.MediaCodec which exists in API level 16 and higher.
|
||||
return AndroidBridge::Bridge()->GetAPIVersion() >= 16;
|
||||
#endif
|
||||
|
|
|
@ -70,26 +70,18 @@ public:
|
|||
return MediaCodecDataDecoder::Input(aSample);
|
||||
}
|
||||
|
||||
EGLImage CopySurface() {
|
||||
if (!EnsureGLContext()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<layers::Image> img = mImageContainer->CreateImage(ImageFormat::SURFACE_TEXTURE);
|
||||
layers::SurfaceTextureImage::Data data;
|
||||
data.mSurfTex = mSurfaceTexture.get();
|
||||
data.mSize = gfx::IntSize(mConfig.display_width, mConfig.display_height);
|
||||
data.mOriginPos = gl::OriginPos::BottomLeft;
|
||||
|
||||
layers::SurfaceTextureImage* stImg = static_cast<layers::SurfaceTextureImage*>(img.get());
|
||||
stImg->SetData(data);
|
||||
bool WantCopy() {
|
||||
// Allocating a texture is incredibly slow on PowerVR
|
||||
return mGLContext->Vendor() != GLVendor::Imagination;
|
||||
}
|
||||
|
||||
EGLImage CopySurface(layers::Image* img) {
|
||||
mGLContext->MakeCurrent();
|
||||
|
||||
GLuint tex = CreateTextureForOffscreen(mGLContext, mGLContext->GetGLFormats(), data.mSize);
|
||||
GLuint tex = CreateTextureForOffscreen(mGLContext, mGLContext->GetGLFormats(), img->GetSize());
|
||||
|
||||
GLBlitHelper helper(mGLContext);
|
||||
if (!helper.BlitImageToTexture(img, data.mSize, tex, LOCAL_GL_TEXTURE_2D)) {
|
||||
if (!helper.BlitImageToTexture(img, img->GetSize(), tex, LOCAL_GL_TEXTURE_2D)) {
|
||||
mGLContext->fDeleteTextures(1, &tex);
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -109,39 +101,53 @@ public:
|
|||
}
|
||||
|
||||
virtual nsresult PostOutput(BufferInfo::Param aInfo, MediaFormat::Param aFormat, Microseconds aDuration) MOZ_OVERRIDE {
|
||||
VideoInfo videoInfo;
|
||||
videoInfo.mDisplay = nsIntSize(mConfig.display_width, mConfig.display_height);
|
||||
|
||||
EGLImage eglImage = CopySurface();
|
||||
if (!eglImage) {
|
||||
if (!EnsureGLContext()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
EGLSync eglSync = nullptr;
|
||||
if (sEGLLibrary.IsExtensionSupported(GLLibraryEGL::KHR_fence_sync) &&
|
||||
mGLContext->IsExtensionSupported(GLContext::OES_EGL_sync))
|
||||
{
|
||||
MOZ_ASSERT(mGLContext->IsCurrent());
|
||||
eglSync = sEGLLibrary.fCreateSync(EGL_DISPLAY(),
|
||||
LOCAL_EGL_SYNC_FENCE,
|
||||
nullptr);
|
||||
if (eglSync) {
|
||||
mGLContext->fFlush();
|
||||
}
|
||||
} else {
|
||||
NS_WARNING("No EGL fence support detected, rendering artifacts may occur!");
|
||||
}
|
||||
VideoInfo videoInfo;
|
||||
videoInfo.mDisplay = nsIntSize(mConfig.display_width, mConfig.display_height);
|
||||
|
||||
nsRefPtr<layers::Image> img = mImageContainer->CreateImage(ImageFormat::EGLIMAGE);
|
||||
layers::EGLImageImage::Data data;
|
||||
data.mImage = eglImage;
|
||||
data.mSync = eglSync;
|
||||
data.mOwns = true;
|
||||
nsRefPtr<layers::Image> img = mImageContainer->CreateImage(ImageFormat::SURFACE_TEXTURE);
|
||||
layers::SurfaceTextureImage::Data data;
|
||||
data.mSurfTex = mSurfaceTexture.get();
|
||||
data.mSize = gfx::IntSize(mConfig.display_width, mConfig.display_height);
|
||||
data.mOriginPos = gl::OriginPos::BottomLeft;
|
||||
|
||||
layers::EGLImageImage* typedImg = static_cast<layers::EGLImageImage*>(img.get());
|
||||
typedImg->SetData(data);
|
||||
layers::SurfaceTextureImage* stImg = static_cast<layers::SurfaceTextureImage*>(img.get());
|
||||
stImg->SetData(data);
|
||||
|
||||
if (WantCopy()) {
|
||||
EGLImage eglImage = CopySurface(img);
|
||||
if (!eglImage) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
EGLSync eglSync = nullptr;
|
||||
if (sEGLLibrary.IsExtensionSupported(GLLibraryEGL::KHR_fence_sync) &&
|
||||
mGLContext->IsExtensionSupported(GLContext::OES_EGL_sync))
|
||||
{
|
||||
MOZ_ASSERT(mGLContext->IsCurrent());
|
||||
eglSync = sEGLLibrary.fCreateSync(EGL_DISPLAY(),
|
||||
LOCAL_EGL_SYNC_FENCE,
|
||||
nullptr);
|
||||
MOZ_ASSERT(eglSync);
|
||||
mGLContext->fFlush();
|
||||
} else {
|
||||
NS_WARNING("No EGL fence support detected, rendering artifacts may occur!");
|
||||
}
|
||||
|
||||
img = mImageContainer->CreateImage(ImageFormat::EGLIMAGE);
|
||||
layers::EGLImageImage::Data data;
|
||||
data.mImage = eglImage;
|
||||
data.mSync = eglSync;
|
||||
data.mOwns = true;
|
||||
data.mSize = gfx::IntSize(mConfig.display_width, mConfig.display_height);
|
||||
data.mOriginPos = gl::OriginPos::BottomLeft;
|
||||
|
||||
layers::EGLImageImage* typedImg = static_cast<layers::EGLImageImage*>(img.get());
|
||||
typedImg->SetData(data);
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
int32_t flags;
|
||||
|
|
|
@ -32,8 +32,7 @@ extern PRLogModuleInfo* GetMediaSourceAPILog();
|
|||
namespace mozilla {
|
||||
|
||||
ContainerParser::ContainerParser()
|
||||
: mInitData(new LargeDataBuffer())
|
||||
, mHasInitData(false)
|
||||
: mHasInitData(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -81,10 +80,15 @@ ContainerParser::GetRoundingError()
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
ContainerParser::HasCompleteInitData()
|
||||
{
|
||||
return mHasInitData && !!mInitData->Length();
|
||||
}
|
||||
|
||||
LargeDataBuffer*
|
||||
ContainerParser::InitData()
|
||||
{
|
||||
MOZ_ASSERT(mHasInitData);
|
||||
return mInitData;
|
||||
}
|
||||
|
||||
|
@ -107,7 +111,9 @@ public:
|
|||
// ...
|
||||
// DocType == "webm"
|
||||
// ...
|
||||
// 0x18538067 // Segment (must be "unknown" size)
|
||||
// 0x18538067 // Segment (must be "unknown" size or contain a value large
|
||||
// enough to include the Segment Information and Tracks
|
||||
// elements that follow)
|
||||
// 0x1549a966 // -> Segment Info
|
||||
// 0x1654ae6b // -> One or more Tracks
|
||||
if (aData->Length() >= 4 &&
|
||||
|
@ -147,6 +153,8 @@ public:
|
|||
mOffset = 0;
|
||||
mParser = WebMBufferedParser(0);
|
||||
mOverlappedMapping.Clear();
|
||||
mInitData = new LargeDataBuffer();
|
||||
mResource = new SourceBufferResource(NS_LITERAL_CSTRING("video/webm"));
|
||||
}
|
||||
|
||||
// XXX if it only adds new mappings, overlapped but not available
|
||||
|
@ -156,22 +164,27 @@ public:
|
|||
mOverlappedMapping.Clear();
|
||||
ReentrantMonitor dummy("dummy");
|
||||
mParser.Append(aData->Elements(), aData->Length(), mapping, dummy);
|
||||
if (mResource) {
|
||||
mResource->AppendData(aData);
|
||||
}
|
||||
|
||||
// XXX This is a bit of a hack. Assume if there are no timecodes
|
||||
// present and it's an init segment that it's _just_ an init segment.
|
||||
// We should be more precise.
|
||||
if (initSegment) {
|
||||
uint32_t length = aData->Length();
|
||||
if (!mapping.IsEmpty()) {
|
||||
length = mapping[0].mSyncOffset;
|
||||
MOZ_ASSERT(length <= aData->Length());
|
||||
}
|
||||
MSE_DEBUG("WebMContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
|
||||
this, length);
|
||||
if (!mInitData->ReplaceElementsAt(0, mInitData->Length(),
|
||||
aData->Elements(), length)) {
|
||||
// Unlikely OOM
|
||||
return false;
|
||||
if (initSegment || !HasCompleteInitData()) {
|
||||
if (mParser.mInitEndOffset > 0) {
|
||||
MOZ_ASSERT(mParser.mInitEndOffset <= mResource->GetLength());
|
||||
if (!mInitData->SetLength(mParser.mInitEndOffset)) {
|
||||
// Super unlikely OOM
|
||||
return false;
|
||||
}
|
||||
char* buffer = reinterpret_cast<char*>(mInitData->Elements());
|
||||
mResource->ReadFromCache(buffer, 0, mParser.mInitEndOffset);
|
||||
MSE_DEBUG("WebMContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
|
||||
this, mParser.mInitEndOffset);
|
||||
mResource = nullptr;
|
||||
} else {
|
||||
MSE_DEBUG("WebMContainerParser(%p)::ParseStartAndEndTimestamps: Incomplete init found.");
|
||||
}
|
||||
mHasInitData = true;
|
||||
}
|
||||
|
@ -273,6 +286,7 @@ public:
|
|||
// manually. This allows the ContainerParser to be shared across different
|
||||
// timestampOffsets.
|
||||
mParser = new mp4_demuxer::MoofParser(mStream, 0, 0, &mMonitor);
|
||||
mInitData = new LargeDataBuffer();
|
||||
} else if (!mStream || !mParser) {
|
||||
return false;
|
||||
}
|
||||
|
@ -284,16 +298,20 @@ public:
|
|||
byteRanges.AppendElement(mbr);
|
||||
mParser->RebuildFragmentedIndex(byteRanges);
|
||||
|
||||
if (initSegment) {
|
||||
if (initSegment || !HasCompleteInitData()) {
|
||||
const MediaByteRange& range = mParser->mInitRange;
|
||||
MSE_DEBUG("MP4ContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
|
||||
this, range.mEnd - range.mStart);
|
||||
|
||||
if (!mInitData->ReplaceElementsAt(0, mInitData->Length(),
|
||||
aData->Elements() + range.mStart,
|
||||
range.mEnd - range.mStart)) {
|
||||
// Super unlikely OOM
|
||||
return false;
|
||||
uint32_t length = range.mEnd - range.mStart;
|
||||
if (length) {
|
||||
if (!mInitData->SetLength(length)) {
|
||||
// Super unlikely OOM
|
||||
return false;
|
||||
}
|
||||
char* buffer = reinterpret_cast<char*>(mInitData->Elements());
|
||||
mResource->ReadFromCache(buffer, range.mStart, length);
|
||||
MSE_DEBUG("MP4ContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
|
||||
this, length);
|
||||
} else {
|
||||
MSE_DEBUG("MP4ContainerParser(%p)::ParseStartAndEndTimestamps: Incomplete init found.");
|
||||
}
|
||||
mHasInitData = true;
|
||||
}
|
||||
|
@ -322,7 +340,6 @@ public:
|
|||
private:
|
||||
nsRefPtr<MP4Stream> mStream;
|
||||
nsAutoPtr<mp4_demuxer::MoofParser> mParser;
|
||||
nsRefPtr<SourceBufferResource> mResource;
|
||||
Monitor mMonitor;
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
namespace mozilla {
|
||||
|
||||
class LargeDataBuffer;
|
||||
class SourceBufferResource;
|
||||
|
||||
class ContainerParser {
|
||||
public:
|
||||
|
@ -48,10 +49,13 @@ public:
|
|||
return mHasInitData;
|
||||
}
|
||||
|
||||
bool HasCompleteInitData();
|
||||
|
||||
static ContainerParser* CreateForMIMEType(const nsACString& aType);
|
||||
|
||||
protected:
|
||||
nsRefPtr<LargeDataBuffer> mInitData;
|
||||
nsRefPtr<SourceBufferResource> mResource;
|
||||
bool mHasInitData;
|
||||
};
|
||||
|
||||
|
|
|
@ -248,13 +248,11 @@ MediaSource::RemoveSourceBuffer(SourceBuffer& aSourceBuffer, ErrorResult& aRv)
|
|||
aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
|
||||
return;
|
||||
}
|
||||
if (sourceBuffer->Updating()) {
|
||||
// TODO:
|
||||
// abort stream append loop (if running)
|
||||
// set updating to false
|
||||
// fire "abort" at sourceBuffer
|
||||
// fire "updateend" at sourceBuffer
|
||||
}
|
||||
|
||||
sourceBuffer->AbortBufferAppend();
|
||||
// TODO:
|
||||
// abort stream append loop (if running)
|
||||
|
||||
// TODO:
|
||||
// For all sourceBuffer audioTracks, videoTracks, textTracks:
|
||||
// set sourceBuffer to null
|
||||
|
|
|
@ -205,7 +205,7 @@ MediaSourceDecoder::DurationChanged(double aOldDuration, double aNewDuration)
|
|||
}
|
||||
|
||||
void
|
||||
MediaSourceDecoder::SetDecodedDuration(int64_t aDuration)
|
||||
MediaSourceDecoder::SetInitialDuration(int64_t aDuration)
|
||||
{
|
||||
// Only use the decoded duration if one wasn't already
|
||||
// set.
|
||||
|
@ -218,7 +218,7 @@ MediaSourceDecoder::SetDecodedDuration(int64_t aDuration)
|
|||
if (aDuration >= 0) {
|
||||
duration /= USECS_PER_S;
|
||||
}
|
||||
DoSetMediaSourceDuration(duration);
|
||||
SetMediaSourceDuration(duration, MSRangeRemovalAction::SKIP);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -226,13 +226,6 @@ MediaSourceDecoder::SetMediaSourceDuration(double aDuration, MSRangeRemovalActio
|
|||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
double oldDuration = mMediaSourceDuration;
|
||||
DoSetMediaSourceDuration(aDuration);
|
||||
ScheduleDurationChange(oldDuration, aDuration, aAction);
|
||||
}
|
||||
|
||||
void
|
||||
MediaSourceDecoder::DoSetMediaSourceDuration(double aDuration)
|
||||
{
|
||||
if (aDuration >= 0) {
|
||||
mDecoderStateMachine->SetDuration(aDuration * USECS_PER_S);
|
||||
mMediaSourceDuration = aDuration;
|
||||
|
@ -243,6 +236,7 @@ MediaSourceDecoder::DoSetMediaSourceDuration(double aDuration)
|
|||
if (mReader) {
|
||||
mReader->SetMediaSourceDuration(mMediaSourceDuration);
|
||||
}
|
||||
ScheduleDurationChange(oldDuration, aDuration, aAction);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -56,7 +56,7 @@ public:
|
|||
void Ended();
|
||||
bool IsExpectingMoreData() MOZ_OVERRIDE;
|
||||
|
||||
void SetDecodedDuration(int64_t aDuration);
|
||||
void SetInitialDuration(int64_t aDuration);
|
||||
void SetMediaSourceDuration(double aDuration, MSRangeRemovalAction aAction);
|
||||
double GetMediaSourceDuration();
|
||||
void DurationChanged(double aOldDuration, double aNewDuration);
|
||||
|
|
|
@ -897,8 +897,6 @@ MediaSourceReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
|
|||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
int64_t maxDuration = -1;
|
||||
|
||||
if (mAudioTrack) {
|
||||
MOZ_ASSERT(mAudioTrack->IsReady());
|
||||
mAudioReader = mAudioTrack->Decoders()[0]->GetReader();
|
||||
|
@ -907,9 +905,9 @@ MediaSourceReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
|
|||
MOZ_ASSERT(info.HasAudio());
|
||||
mInfo.mAudio = info.mAudio;
|
||||
mInfo.mIsEncrypted = mInfo.mIsEncrypted || info.mIsEncrypted;
|
||||
maxDuration = std::max(maxDuration, mAudioReader->GetDecoder()->GetMediaDuration());
|
||||
MSE_DEBUG("MediaSourceReader(%p)::ReadMetadata audio reader=%p maxDuration=%lld",
|
||||
this, mAudioReader.get(), maxDuration);
|
||||
MSE_DEBUG("MediaSourceReader(%p)::ReadMetadata audio reader=%p duration=%lld",
|
||||
this, mAudioReader.get(),
|
||||
mAudioReader->GetDecoder()->GetMediaDuration());
|
||||
}
|
||||
|
||||
if (mVideoTrack) {
|
||||
|
@ -920,17 +918,11 @@ MediaSourceReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
|
|||
MOZ_ASSERT(info.HasVideo());
|
||||
mInfo.mVideo = info.mVideo;
|
||||
mInfo.mIsEncrypted = mInfo.mIsEncrypted || info.mIsEncrypted;
|
||||
maxDuration = std::max(maxDuration, mVideoReader->GetDecoder()->GetMediaDuration());
|
||||
MSE_DEBUG("MediaSourceReader(%p)::ReadMetadata video reader=%p maxDuration=%lld",
|
||||
this, mVideoReader.get(), maxDuration);
|
||||
MSE_DEBUG("MediaSourceReader(%p)::ReadMetadata video reader=%p duration=%lld",
|
||||
this, mVideoReader.get(),
|
||||
mVideoReader->GetDecoder()->GetMediaDuration());
|
||||
}
|
||||
|
||||
if (!maxDuration) {
|
||||
// Treat a duration of 0 as infinity
|
||||
maxDuration = -1;
|
||||
}
|
||||
static_cast<MediaSourceDecoder*>(mDecoder)->SetDecodedDuration(maxDuration);
|
||||
|
||||
*aInfo = mInfo;
|
||||
*aTags = nullptr; // TODO: Handle metadata.
|
||||
|
||||
|
|
|
@ -46,16 +46,18 @@ class AppendDataRunnable : public nsRunnable {
|
|||
public:
|
||||
AppendDataRunnable(SourceBuffer* aSourceBuffer,
|
||||
LargeDataBuffer* aData,
|
||||
double aTimestampOffset)
|
||||
double aTimestampOffset,
|
||||
uint32_t aUpdateID)
|
||||
: mSourceBuffer(aSourceBuffer)
|
||||
, mData(aData)
|
||||
, mTimestampOffset(aTimestampOffset)
|
||||
, mUpdateID(aUpdateID)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD Run() MOZ_OVERRIDE MOZ_FINAL {
|
||||
|
||||
mSourceBuffer->AppendData(mData, mTimestampOffset);
|
||||
mSourceBuffer->AppendData(mData, mTimestampOffset, mUpdateID);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -64,6 +66,7 @@ private:
|
|||
nsRefPtr<SourceBuffer> mSourceBuffer;
|
||||
nsRefPtr<LargeDataBuffer> mData;
|
||||
double mTimestampOffset;
|
||||
uint32_t mUpdateID;
|
||||
};
|
||||
|
||||
class RangeRemovalRunnable : public nsRunnable {
|
||||
|
@ -214,20 +217,23 @@ SourceBuffer::Abort(ErrorResult& aRv)
|
|||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
Abort();
|
||||
AbortBufferAppend();
|
||||
mTrackBuffer->ResetParserState();
|
||||
mAppendWindowStart = 0;
|
||||
mAppendWindowEnd = PositiveInfinity<double>();
|
||||
|
||||
// Discard the current decoder so no new data will be added to it.
|
||||
MSE_DEBUG("SourceBuffer(%p)::Abort() Discarding decoder", this);
|
||||
mTrackBuffer->DiscardDecoder();
|
||||
mTrackBuffer->DiscardCurrentDecoder();
|
||||
}
|
||||
|
||||
void
|
||||
SourceBuffer::Abort()
|
||||
SourceBuffer::AbortBufferAppend()
|
||||
{
|
||||
if (mUpdating) {
|
||||
// TODO: Abort segment parser loop, buffer append, and stream append loop algorithms.
|
||||
mPendingAppend.DisconnectIfExists();
|
||||
// TODO: Abort segment parser loop, and stream append loop algorithms.
|
||||
// cancel any pending buffer append.
|
||||
mTrackBuffer->AbortAppendData();
|
||||
AbortUpdating();
|
||||
}
|
||||
}
|
||||
|
@ -269,6 +275,8 @@ SourceBuffer::RangeRemoval(double aStart, double aEnd)
|
|||
void
|
||||
SourceBuffer::DoRangeRemoval(double aStart, double aEnd)
|
||||
{
|
||||
MSE_DEBUG("SourceBuffer(%p)::DoRangeRemoval (updating:%d)",
|
||||
this, mUpdating);
|
||||
if (!mUpdating) {
|
||||
// abort was called in between.
|
||||
return;
|
||||
|
@ -287,7 +295,7 @@ SourceBuffer::Detach()
|
|||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MSE_DEBUG("SourceBuffer(%p)::Detach", this);
|
||||
Abort();
|
||||
AbortBufferAppend();
|
||||
if (mTrackBuffer) {
|
||||
mTrackBuffer->Detach();
|
||||
}
|
||||
|
@ -312,6 +320,7 @@ SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
|
|||
, mTimestampOffset(0)
|
||||
, mAppendMode(SourceBufferAppendMode::Segments)
|
||||
, mUpdating(false)
|
||||
, mUpdateID(0)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aMediaSource);
|
||||
|
@ -363,6 +372,7 @@ SourceBuffer::StartUpdating()
|
|||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!mUpdating);
|
||||
mUpdating = true;
|
||||
mUpdateID++;
|
||||
QueueAsyncSimpleEvent("updatestart");
|
||||
}
|
||||
|
||||
|
@ -397,9 +407,11 @@ SourceBuffer::AbortUpdating()
|
|||
void
|
||||
SourceBuffer::CheckEndTime()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
// Check if we need to update mMediaSource duration
|
||||
double endTime = GetBufferedEnd();
|
||||
if (endTime > mMediaSource->Duration()) {
|
||||
double duration = mMediaSource->Duration();
|
||||
if (endTime > duration) {
|
||||
mMediaSource->SetDuration(endTime, MSRangeRemovalAction::SKIP);
|
||||
}
|
||||
}
|
||||
|
@ -418,14 +430,15 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR
|
|||
MOZ_ASSERT(mAppendMode == SourceBufferAppendMode::Segments,
|
||||
"We don't handle timestampOffset for sequence mode yet");
|
||||
nsRefPtr<nsIRunnable> task =
|
||||
new AppendDataRunnable(this, data, mTimestampOffset);
|
||||
new AppendDataRunnable(this, data, mTimestampOffset, mUpdateID);
|
||||
NS_DispatchToMainThread(task);
|
||||
}
|
||||
|
||||
void
|
||||
SourceBuffer::AppendData(LargeDataBuffer* aData, double aTimestampOffset)
|
||||
SourceBuffer::AppendData(LargeDataBuffer* aData, double aTimestampOffset,
|
||||
uint32_t aUpdateID)
|
||||
{
|
||||
if (!mUpdating) {
|
||||
if (!mUpdating || aUpdateID != mUpdateID) {
|
||||
// The buffer append algorithm has been interrupted by abort().
|
||||
//
|
||||
// If the sequence appendBuffer(), abort(), appendBuffer() occurs before
|
||||
|
@ -436,22 +449,53 @@ SourceBuffer::AppendData(LargeDataBuffer* aData, double aTimestampOffset)
|
|||
}
|
||||
|
||||
MOZ_ASSERT(mMediaSource);
|
||||
MOZ_ASSERT(!mPendingAppend.Exists());
|
||||
|
||||
if (aData->Length()) {
|
||||
if (!mTrackBuffer->AppendData(aData, aTimestampOffset * USECS_PER_S)) {
|
||||
AppendError(true);
|
||||
return;
|
||||
}
|
||||
if (!aData->Length()) {
|
||||
StopUpdating();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mTrackBuffer->HasInitSegment()) {
|
||||
mMediaSource->QueueInitializationEvent();
|
||||
}
|
||||
mPendingAppend.Begin(mTrackBuffer->AppendData(aData, aTimestampOffset * USECS_PER_S)
|
||||
->RefableThen(NS_GetCurrentThread(), __func__, this,
|
||||
&SourceBuffer::AppendDataCompletedWithSuccess,
|
||||
&SourceBuffer::AppendDataErrored));
|
||||
}
|
||||
|
||||
void
|
||||
SourceBuffer::AppendDataCompletedWithSuccess(bool aGotMedia)
|
||||
{
|
||||
mPendingAppend.Complete();
|
||||
if (!mUpdating) {
|
||||
// The buffer append algorithm has been interrupted by abort().
|
||||
return;
|
||||
}
|
||||
|
||||
if (mTrackBuffer->HasInitSegment()) {
|
||||
mMediaSource->QueueInitializationEvent();
|
||||
}
|
||||
|
||||
if (aGotMedia) {
|
||||
CheckEndTime();
|
||||
}
|
||||
|
||||
StopUpdating();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SourceBuffer::AppendDataErrored(nsresult aError)
|
||||
{
|
||||
mPendingAppend.Complete();
|
||||
switch (aError) {
|
||||
case NS_ERROR_ABORT:
|
||||
// Nothing further to do as the trackbuffer has been shutdown.
|
||||
// or append was aborted and abort() has handled all the events.
|
||||
break;
|
||||
default:
|
||||
AppendError(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SourceBuffer::AppendError(bool aDecoderError)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef mozilla_dom_SourceBuffer_h_
|
||||
#define mozilla_dom_SourceBuffer_h_
|
||||
|
||||
#include "MediaPromise.h"
|
||||
#include "MediaSource.h"
|
||||
#include "js/RootingAPI.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
|
@ -32,6 +33,7 @@ class ErrorResult;
|
|||
class LargeDataBuffer;
|
||||
class TrackBuffer;
|
||||
template <typename T> class AsyncEventRunner;
|
||||
typedef MediaPromise<bool, nsresult, /* IsExclusive = */ true> TrackBufferAppendPromise;
|
||||
|
||||
namespace dom {
|
||||
|
||||
|
@ -80,7 +82,7 @@ public:
|
|||
void AppendBuffer(const ArrayBufferView& aData, ErrorResult& aRv);
|
||||
|
||||
void Abort(ErrorResult& aRv);
|
||||
void Abort();
|
||||
void AbortBufferAppend();
|
||||
|
||||
void Remove(double aStart, double aEnd, ErrorResult& aRv);
|
||||
/** End WebIDL Methods. */
|
||||
|
@ -139,7 +141,8 @@ private:
|
|||
|
||||
// Shared implementation of AppendBuffer overloads.
|
||||
void AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv);
|
||||
void AppendData(LargeDataBuffer* aData, double aTimestampOffset);
|
||||
void AppendData(LargeDataBuffer* aData, double aTimestampOffset,
|
||||
uint32_t aAppendID);
|
||||
|
||||
// Implement the "Append Error Algorithm".
|
||||
// Will call endOfStream() with "decode" error if aDecodeError is true.
|
||||
|
@ -153,6 +156,9 @@ private:
|
|||
uint32_t aLength,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void AppendDataCompletedWithSuccess(bool aValue);
|
||||
void AppendDataErrored(nsresult aError);
|
||||
|
||||
nsRefPtr<MediaSource> mMediaSource;
|
||||
|
||||
uint32_t mEvictionThreshold;
|
||||
|
@ -166,6 +172,13 @@ private:
|
|||
|
||||
SourceBufferAppendMode mAppendMode;
|
||||
bool mUpdating;
|
||||
|
||||
// Each time mUpdating is set to true, mUpdateID will be incremented.
|
||||
// This allows for a queued AppendData task to identify if it was earlier
|
||||
// aborted and another AppendData queued.
|
||||
uint32_t mUpdateID;
|
||||
|
||||
MediaPromiseConsumerHolder<TrackBufferAppendPromise> mPendingAppend;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -114,6 +114,11 @@ public:
|
|||
// Used by SourceBuffer.
|
||||
void AppendData(LargeDataBuffer* aData);
|
||||
void Ended();
|
||||
bool IsEnded()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mMonitor);
|
||||
return mEnded;
|
||||
}
|
||||
// Remove data from resource if it holds more than the threshold
|
||||
// number of bytes. Returns amount evicted.
|
||||
uint32_t EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold);
|
||||
|
|
|
@ -102,9 +102,19 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
size_t Length()
|
||||
{
|
||||
return mDecoders.Length();
|
||||
}
|
||||
|
||||
void AppendElement(SourceBufferDecoder* aDecoder)
|
||||
{
|
||||
mDecoders.AppendElement(aDecoder);
|
||||
}
|
||||
|
||||
private:
|
||||
TrackBuffer* mOwner;
|
||||
nsAutoTArray<nsRefPtr<SourceBufferDecoder>,2> mDecoders;
|
||||
nsAutoTArray<nsRefPtr<SourceBufferDecoder>,1> mDecoders;
|
||||
};
|
||||
|
||||
nsRefPtr<ShutdownPromise>
|
||||
|
@ -112,6 +122,7 @@ TrackBuffer::Shutdown()
|
|||
{
|
||||
mParentDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
mShutdown = true;
|
||||
mInitializationPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
|
||||
|
||||
MOZ_ASSERT(mShutdownPromise.IsEmpty());
|
||||
nsRefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__);
|
||||
|
@ -138,48 +149,84 @@ TrackBuffer::ContinueShutdown()
|
|||
return;
|
||||
}
|
||||
|
||||
mCurrentDecoder = nullptr;
|
||||
mInitializedDecoders.Clear();
|
||||
mParentDecoder = nullptr;
|
||||
|
||||
mShutdownPromise.Resolve(true, __func__);
|
||||
}
|
||||
|
||||
bool
|
||||
nsRefPtr<TrackBufferAppendPromise>
|
||||
TrackBuffer::AppendData(LargeDataBuffer* aData, int64_t aTimestampOffset)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mInitializationPromise.IsEmpty());
|
||||
|
||||
DecodersToInitialize decoders(this);
|
||||
nsRefPtr<TrackBufferAppendPromise> p = mInitializationPromise.Ensure(__func__);
|
||||
bool hadInitData = mParser->HasInitData();
|
||||
bool hadCompleteInitData = mParser->HasCompleteInitData();
|
||||
nsRefPtr<LargeDataBuffer> oldInit = mParser->InitData();
|
||||
bool newInitData = mParser->IsInitSegmentPresent(aData);
|
||||
|
||||
// TODO: Run more of the buffer append algorithm asynchronously.
|
||||
if (mParser->IsInitSegmentPresent(aData)) {
|
||||
if (newInitData) {
|
||||
MSE_DEBUG("TrackBuffer(%p)::AppendData: New initialization segment.", this);
|
||||
if (!decoders.NewDecoder(aTimestampOffset)) {
|
||||
return false;
|
||||
}
|
||||
} else if (!mParser->HasInitData()) {
|
||||
} else if (!hadInitData) {
|
||||
MSE_DEBUG("TrackBuffer(%p)::AppendData: Non-init segment appended during initialization.", this);
|
||||
return false;
|
||||
mInitializationPromise.Reject(NS_ERROR_FAILURE, __func__);
|
||||
return p;
|
||||
}
|
||||
|
||||
int64_t start, end;
|
||||
if (mParser->ParseStartAndEndTimestamps(aData, start, end)) {
|
||||
int64_t start = 0, end = 0;
|
||||
bool gotMedia = mParser->ParseStartAndEndTimestamps(aData, start, end);
|
||||
bool gotInit = mParser->HasCompleteInitData();
|
||||
|
||||
if (newInitData) {
|
||||
if (!gotInit) {
|
||||
// We need a new decoder, but we can't initialize it yet.
|
||||
nsRefPtr<SourceBufferDecoder> decoder = NewDecoder(aTimestampOffset);
|
||||
// The new decoder is stored in mDecoders/mCurrentDecoder, so we
|
||||
// don't need to do anything with 'decoder'. It's only a placeholder.
|
||||
if (!decoder) {
|
||||
mInitializationPromise.Reject(NS_ERROR_FAILURE, __func__);
|
||||
return p;
|
||||
}
|
||||
} else {
|
||||
if (!decoders.NewDecoder(aTimestampOffset)) {
|
||||
mInitializationPromise.Reject(NS_ERROR_FAILURE, __func__);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
} else if (!hadCompleteInitData && gotInit) {
|
||||
MOZ_ASSERT(mCurrentDecoder);
|
||||
// Queue pending decoder for initialization now that we have a full
|
||||
// init segment.
|
||||
decoders.AppendElement(mCurrentDecoder);
|
||||
}
|
||||
|
||||
if (gotMedia) {
|
||||
start += aTimestampOffset;
|
||||
end += aTimestampOffset;
|
||||
if (mParser->IsMediaSegmentPresent(aData) &&
|
||||
mLastEndTimestamp &&
|
||||
if (mLastEndTimestamp &&
|
||||
(!mParser->TimestampsFuzzyEqual(start, mLastEndTimestamp.value()) ||
|
||||
mLastTimestampOffset != aTimestampOffset ||
|
||||
mDecoderPerSegment || (mCurrentDecoder && mCurrentDecoder->WasTrimmed()))) {
|
||||
mDecoderPerSegment ||
|
||||
(mCurrentDecoder && mCurrentDecoder->WasTrimmed()))) {
|
||||
MSE_DEBUG("TrackBuffer(%p)::AppendData: Data last=[%lld, %lld] overlaps [%lld, %lld]",
|
||||
this, mLastStartTimestamp, mLastEndTimestamp.value(), start, end);
|
||||
|
||||
// This data is earlier in the timeline than data we have already
|
||||
// processed, so we must create a new decoder to handle the decoding.
|
||||
if (!decoders.NewDecoder(aTimestampOffset)) {
|
||||
return false;
|
||||
if (!newInitData) {
|
||||
// This data is earlier in the timeline than data we have already
|
||||
// processed or not continuous, so we must create a new decoder
|
||||
// to handle the decoding.
|
||||
if (!hadCompleteInitData || !decoders.NewDecoder(aTimestampOffset)) {
|
||||
mInitializationPromise.Reject(NS_ERROR_FAILURE, __func__);
|
||||
return p;
|
||||
}
|
||||
MSE_DEBUG("TrackBuffer(%p)::AppendData: Decoder marked as initialized.", this);
|
||||
AppendDataToCurrentResource(oldInit, 0);
|
||||
}
|
||||
MSE_DEBUG("TrackBuffer(%p)::AppendData: Decoder marked as initialized.", this);
|
||||
nsRefPtr<LargeDataBuffer> initData = mParser->InitData();
|
||||
AppendDataToCurrentResource(initData, end - start);
|
||||
mLastStartTimestamp = start;
|
||||
} else {
|
||||
MSE_DEBUG("TrackBuffer(%p)::AppendData: Segment last=[%lld, %lld] [%lld, %lld]",
|
||||
|
@ -190,13 +237,21 @@ TrackBuffer::AppendData(LargeDataBuffer* aData, int64_t aTimestampOffset)
|
|||
}
|
||||
|
||||
if (!AppendDataToCurrentResource(aData, end - start)) {
|
||||
return false;
|
||||
mInitializationPromise.Reject(NS_ERROR_FAILURE, __func__);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Tell our reader that we have more data to ensure that playback starts if
|
||||
// required when data is appended.
|
||||
mParentDecoder->GetReader()->MaybeNotifyHaveData();
|
||||
return true;
|
||||
if (decoders.Length()) {
|
||||
// We're going to have to wait for the decoder to initialize, the promise
|
||||
// will be resolved once initialization completes.
|
||||
return p;
|
||||
} else if (gotMedia) {
|
||||
// Tell our reader that we have more data to ensure that playback starts if
|
||||
// required when data is appended.
|
||||
mParentDecoder->GetReader()->MaybeNotifyHaveData();
|
||||
}
|
||||
mInitializationPromise.Resolve(gotMedia, __func__);
|
||||
return p;
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -370,9 +425,9 @@ TrackBuffer::Buffered(dom::TimeRanges* aRanges)
|
|||
|
||||
double highestEndTime = 0;
|
||||
|
||||
for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
|
||||
for (uint32_t i = 0; i < mInitializedDecoders.Length(); ++i) {
|
||||
nsRefPtr<dom::TimeRanges> r = new dom::TimeRanges();
|
||||
mDecoders[i]->GetBuffered(r);
|
||||
mInitializedDecoders[i]->GetBuffered(r);
|
||||
if (r->Length() > 0) {
|
||||
highestEndTime = std::max(highestEndTime, r->GetEndTime());
|
||||
aRanges->Union(r, double(mParser->GetRoundingError()) / USECS_PER_S);
|
||||
|
@ -388,7 +443,7 @@ TrackBuffer::NewDecoder(int64_t aTimestampOffset)
|
|||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mParentDecoder);
|
||||
|
||||
DiscardDecoder();
|
||||
DiscardCurrentDecoder();
|
||||
|
||||
nsRefPtr<SourceBufferDecoder> decoder = mParentDecoder->CreateSubDecoder(mType, aTimestampOffset);
|
||||
if (!decoder) {
|
||||
|
@ -410,6 +465,7 @@ bool
|
|||
TrackBuffer::QueueInitializeDecoder(SourceBufferDecoder* aDecoder)
|
||||
{
|
||||
if (NS_WARN_IF(!mTaskQueue)) {
|
||||
mInitializationPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -418,8 +474,9 @@ TrackBuffer::QueueInitializeDecoder(SourceBufferDecoder* aDecoder)
|
|||
&TrackBuffer::InitializeDecoder,
|
||||
aDecoder);
|
||||
if (NS_FAILED(mTaskQueue->Dispatch(task))) {
|
||||
MSE_DEBUG("MediaSourceReader(%p): Failed to enqueue decoder initialization task", this);
|
||||
MSE_DEBUG("TrackBuffer(%p): Failed to enqueue decoder initialization task", this);
|
||||
RemoveDecoder(aDecoder);
|
||||
mInitializationPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -428,6 +485,11 @@ TrackBuffer::QueueInitializeDecoder(SourceBufferDecoder* aDecoder)
|
|||
void
|
||||
TrackBuffer::InitializeDecoder(SourceBufferDecoder* aDecoder)
|
||||
{
|
||||
if (!mParentDecoder) {
|
||||
MSE_DEBUG("TrackBuffer(%p) was shutdown. Aborting initialization.",
|
||||
this);
|
||||
return;
|
||||
}
|
||||
// ReadMetadata may block the thread waiting on data, so we must be able
|
||||
// to leave the monitor while we call it. For the rest of this function
|
||||
// we want to hold the monitor though, since we run on a different task queue
|
||||
|
@ -435,12 +497,21 @@ TrackBuffer::InitializeDecoder(SourceBufferDecoder* aDecoder)
|
|||
mParentDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
|
||||
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
|
||||
|
||||
if (mCurrentDecoder != aDecoder) {
|
||||
MSE_DEBUG("TrackBuffer(%p) append was cancelled. Aborting initialization.",
|
||||
this);
|
||||
// If we reached this point, the SourceBuffer would have disconnected
|
||||
// the promise. So no need to reject it.
|
||||
return;
|
||||
}
|
||||
|
||||
// We may be shut down at any time by the reader on another thread. So we need
|
||||
// to check for this each time we acquire the monitor. If that happens, we
|
||||
// need to abort immediately, because the reader has forgotten about us, and
|
||||
// important pieces of our state (like mTaskQueue) have also been torn down.
|
||||
if (mShutdown) {
|
||||
MSE_DEBUG("TrackBuffer(%p) was shut down. Aborting initialization.", this);
|
||||
RemoveDecoder(aDecoder);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -452,10 +523,28 @@ TrackBuffer::InitializeDecoder(SourceBufferDecoder* aDecoder)
|
|||
MediaInfo mi;
|
||||
nsAutoPtr<MetadataTags> tags; // TODO: Handle metadata.
|
||||
nsresult rv;
|
||||
|
||||
// HACK WARNING:
|
||||
// We only reach this point once we know that we have a complete init segment.
|
||||
// We don't want the demuxer to do a blocking read as no more data can be
|
||||
// appended while this routine is running. Marking the SourceBufferResource
|
||||
// as ended will cause any incomplete reads to abort.
|
||||
// As this decoder hasn't been initialized yet, the resource isn't yet in use
|
||||
// and so it is safe to do so.
|
||||
bool wasEnded = aDecoder->GetResource()->IsEnded();
|
||||
if (!wasEnded) {
|
||||
aDecoder->GetResource()->Ended();
|
||||
}
|
||||
{
|
||||
ReentrantMonitorAutoExit mon(mParentDecoder->GetReentrantMonitor());
|
||||
rv = reader->ReadMetadata(&mi, getter_Transfers(tags));
|
||||
}
|
||||
if (!wasEnded) {
|
||||
// Adding an empty buffer will reopen the SourceBufferResource
|
||||
nsRefPtr<LargeDataBuffer> emptyBuffer = new LargeDataBuffer;
|
||||
aDecoder->GetResource()->AppendData(emptyBuffer);
|
||||
}
|
||||
// HACK END.
|
||||
|
||||
reader->SetIdle();
|
||||
if (mShutdown) {
|
||||
|
@ -475,6 +564,7 @@ TrackBuffer::InitializeDecoder(SourceBufferDecoder* aDecoder)
|
|||
MSE_DEBUG("TrackBuffer(%p): Reader %p failed to initialize rv=%x audio=%d video=%d",
|
||||
this, reader, rv, mi.HasAudio(), mi.HasVideo());
|
||||
RemoveDecoder(aDecoder);
|
||||
mInitializationPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -487,18 +577,63 @@ TrackBuffer::InitializeDecoder(SourceBufferDecoder* aDecoder)
|
|||
this, reader, mi.mAudio.mRate, mi.mAudio.mChannels);
|
||||
}
|
||||
|
||||
if (!RegisterDecoder(aDecoder)) {
|
||||
// XXX: Need to signal error back to owning SourceBuffer.
|
||||
MSE_DEBUG("TrackBuffer(%p): Reader %p not activated", this, reader);
|
||||
RefPtr<nsIRunnable> task =
|
||||
NS_NewRunnableMethodWithArg<SourceBufferDecoder*>(this,
|
||||
&TrackBuffer::CompleteInitializeDecoder,
|
||||
aDecoder);
|
||||
if (NS_FAILED(NS_DispatchToMainThread(task))) {
|
||||
MSE_DEBUG("TrackBuffer(%p): Failed to enqueue decoder initialization task", this);
|
||||
RemoveDecoder(aDecoder);
|
||||
mInitializationPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TrackBuffer::CompleteInitializeDecoder(SourceBufferDecoder* aDecoder)
|
||||
{
|
||||
if (!mParentDecoder) {
|
||||
MSE_DEBUG("TrackBuffer(%p) was shutdown. Aborting initialization.",
|
||||
this);
|
||||
return;
|
||||
}
|
||||
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
|
||||
if (mCurrentDecoder != aDecoder) {
|
||||
MSE_DEBUG("TrackBuffer(%p) append was cancelled. Aborting initialization.",
|
||||
this);
|
||||
// If we reached this point, the SourceBuffer would have disconnected
|
||||
// the promise. So no need to reject it.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mShutdown) {
|
||||
MSE_DEBUG("TrackBuffer(%p) was shut down. Aborting initialization.", this);
|
||||
RemoveDecoder(aDecoder);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!RegisterDecoder(aDecoder)) {
|
||||
MSE_DEBUG("TrackBuffer(%p): Reader %p not activated",
|
||||
this, aDecoder->GetReader());
|
||||
RemoveDecoder(aDecoder);
|
||||
mInitializationPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t duration = aDecoder->GetMediaDuration();
|
||||
if (!duration) {
|
||||
// Treat a duration of 0 as infinity
|
||||
duration = -1;
|
||||
}
|
||||
mParentDecoder->SetInitialDuration(duration);
|
||||
|
||||
// Tell our reader that we have more data to ensure that playback starts if
|
||||
// required when data is appended.
|
||||
mParentDecoder->GetReader()->MaybeNotifyHaveData();
|
||||
|
||||
MSE_DEBUG("TrackBuffer(%p): Reader %p activated", this, reader);
|
||||
MSE_DEBUG("TrackBuffer(%p): Reader %p activated",
|
||||
this, aDecoder->GetReader());
|
||||
mInitializationPromise.ResolveIfExists(aDecoder->GetRealMediaDuration() > 0, __func__);
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -541,12 +676,10 @@ TrackBuffer::RegisterDecoder(SourceBufferDecoder* aDecoder)
|
|||
}
|
||||
|
||||
void
|
||||
TrackBuffer::DiscardDecoder()
|
||||
TrackBuffer::DiscardCurrentDecoder()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
|
||||
if (mCurrentDecoder) {
|
||||
mCurrentDecoder->GetResource()->Ended();
|
||||
}
|
||||
EndCurrentDecoder();
|
||||
mCurrentDecoder = nullptr;
|
||||
}
|
||||
|
||||
|
@ -564,7 +697,7 @@ TrackBuffer::Detach()
|
|||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (mCurrentDecoder) {
|
||||
DiscardDecoder();
|
||||
DiscardCurrentDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -572,7 +705,7 @@ bool
|
|||
TrackBuffer::HasInitSegment()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
|
||||
return mParser->HasInitData();
|
||||
return mParser->HasCompleteInitData();
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -580,7 +713,7 @@ TrackBuffer::IsReady()
|
|||
{
|
||||
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
|
||||
MOZ_ASSERT((mInfo.HasAudio() || mInfo.HasVideo()) || mInitializedDecoders.IsEmpty());
|
||||
return mParser->HasInitData() && (mInfo.HasAudio() || mInfo.HasVideo());
|
||||
return mInfo.HasAudio() || mInfo.HasVideo();
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -626,7 +759,24 @@ TrackBuffer::ResetDecode()
|
|||
void
|
||||
TrackBuffer::ResetParserState()
|
||||
{
|
||||
// TODO
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mParser->HasInitData() && !mParser->HasCompleteInitData()) {
|
||||
// We have an incomplete init segment pending. reset current parser and
|
||||
// discard the current decoder.
|
||||
mParser = ContainerParser::CreateForMIMEType(mType);
|
||||
DiscardCurrentDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TrackBuffer::AbortAppendData()
|
||||
{
|
||||
DiscardCurrentDecoder();
|
||||
// The SourceBuffer would have disconnected its promise.
|
||||
// However we must ensure that the MediaPromiseHolder handle all pending
|
||||
// promises.
|
||||
mInitializationPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
|
||||
}
|
||||
|
||||
const nsTArray<nsRefPtr<SourceBufferDecoder>>&
|
||||
|
@ -717,7 +867,7 @@ TrackBuffer::RemoveDecoder(SourceBufferDecoder* aDecoder)
|
|||
mDecoders.RemoveElement(aDecoder);
|
||||
|
||||
if (mCurrentDecoder == aDecoder) {
|
||||
DiscardDecoder();
|
||||
DiscardCurrentDecoder();
|
||||
}
|
||||
}
|
||||
aDecoder->GetReader()->GetTaskQueue()->Dispatch(task);
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
#ifndef MOZILLA_TRACKBUFFER_H_
|
||||
#define MOZILLA_TRACKBUFFER_H_
|
||||
|
||||
#include "SourceBuffer.h"
|
||||
#include "SourceBufferDecoder.h"
|
||||
#include "MediaPromise.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/mozalloc.h"
|
||||
|
@ -40,7 +40,8 @@ public:
|
|||
// Append data to the current decoder. Also responsible for calling
|
||||
// NotifyDataArrived on the decoder to keep buffered range computation up
|
||||
// to date. Returns false if the append failed.
|
||||
bool AppendData(LargeDataBuffer* aData, int64_t aTimestampOffset /* microseconds */);
|
||||
nsRefPtr<TrackBufferAppendPromise> AppendData(LargeDataBuffer* aData,
|
||||
int64_t aTimestampOffset /* microseconds */);
|
||||
|
||||
// Evicts data held in the current decoders SourceBufferResource from the
|
||||
// start of the buffer through to aPlaybackTime. aThreshold is used to
|
||||
|
@ -61,7 +62,7 @@ public:
|
|||
|
||||
// Mark the current decoder's resource as ended, clear mCurrentDecoder and
|
||||
// reset mLast{Start,End}Timestamp.
|
||||
void DiscardDecoder();
|
||||
void DiscardCurrentDecoder();
|
||||
// Mark the current decoder's resource as ended.
|
||||
void EndCurrentDecoder();
|
||||
|
||||
|
@ -100,6 +101,9 @@ public:
|
|||
// Times are in microseconds.
|
||||
bool RangeRemoval(int64_t aStart, int64_t aEnd);
|
||||
|
||||
// Abort any pending appendBuffer by rejecting any pending promises.
|
||||
void AbortAppendData();
|
||||
|
||||
#ifdef MOZ_EME
|
||||
nsresult SetCDMProxy(CDMProxy* aProxy);
|
||||
#endif
|
||||
|
@ -130,6 +134,11 @@ private:
|
|||
// Runs decoder initialization including calling ReadMetadata. Runs as an
|
||||
// event on the decode thread pool.
|
||||
void InitializeDecoder(SourceBufferDecoder* aDecoder);
|
||||
// Once decoder has been initialized, set mediasource duration if required
|
||||
// and resolve any pending InitializationPromise.
|
||||
// Setting the mediasource duration must be done on the main thread.
|
||||
// TODO: Why is that so?
|
||||
void CompleteInitializeDecoder(SourceBufferDecoder* aDecoder);
|
||||
|
||||
// Adds a successfully initialized decoder to mDecoders and (if it's the
|
||||
// first decoder initialized), initializes mHasAudio/mHasVideo. Called
|
||||
|
@ -193,6 +202,9 @@ private:
|
|||
MediaPromiseHolder<ShutdownPromise> mShutdownPromise;
|
||||
bool mDecoderPerSegment;
|
||||
bool mShutdown;
|
||||
|
||||
MediaPromiseHolder<TrackBufferAppendPromise> mInitializationPromise;
|
||||
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -455,6 +455,7 @@ skip-if = appname == "seamonkey"
|
|||
[test_playback.html]
|
||||
skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
|
||||
[test_playback_errors.html]
|
||||
skip-if = toolkit == 'gonk' # bug 1128845
|
||||
[test_playback_rate.html]
|
||||
skip-if = (toolkit == 'android' && processor == 'x86') #bug 845162
|
||||
[test_playback_rate_playpause.html]
|
||||
|
@ -467,7 +468,7 @@ skip-if = true # bug 493692
|
|||
[test_progress.html]
|
||||
skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
|
||||
[test_reactivate.html]
|
||||
skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
|
||||
skip-if = toolkit == 'gonk' || (toolkit == 'android' && processor == 'x86') #x86 only bug 914439 and bug 1128845 on gonk
|
||||
[test_readyState.html]
|
||||
[test_referer.html]
|
||||
skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
|
||||
|
@ -525,7 +526,7 @@ skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
|
|||
[test_trackelementevent.html]
|
||||
[test_trackevent.html]
|
||||
[test_unseekable.html]
|
||||
skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
|
||||
skip-if = toolkit == 'gonk' || (toolkit == 'android' && processor == 'x86') #x86 only and bug 1128845 on gonk
|
||||
[test_video_to_canvas.html]
|
||||
skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
|
||||
[test_video_in_audio_element.html]
|
||||
|
|
|
@ -23,6 +23,7 @@ var manager = new MediaTestManager;
|
|||
|
||||
function onloaded(event) {
|
||||
var v = event.target;
|
||||
v.removeEventListener("loadedmetadata", onloaded);
|
||||
v.currentTime = v.duration;
|
||||
return;
|
||||
}
|
||||
|
@ -31,15 +32,18 @@ function checkNotPlaying(v) {
|
|||
ok(v.currentTime == 0, "Should not be playing after seek to end and back to beginning");
|
||||
v._finished = true;
|
||||
manager.finished(v.token);
|
||||
removeNodeAndSource(v);
|
||||
}
|
||||
|
||||
function onseeked(event) {
|
||||
var v = event.target;
|
||||
v.removeEventListener("seeked", onseeked);
|
||||
setTimeout(function() { checkNotPlaying(v); }, 500);
|
||||
}
|
||||
|
||||
function onended(event) {
|
||||
var v = event.target;
|
||||
v.removeEventListener("ended", onended);
|
||||
if (v._finished)
|
||||
return;
|
||||
v.addEventListener("seeked", onseeked, false);
|
||||
|
|
|
@ -14,13 +14,16 @@ var manager = new MediaTestManager;
|
|||
|
||||
function ended(evt) {
|
||||
var v = evt.target;
|
||||
v.removeEventListener("ended", ended);
|
||||
is(v.gotPause, true, "We should have received a \"pause\" event.")
|
||||
is(v.paused, true, v._name + " must be paused after end");
|
||||
manager.finished(v.token);
|
||||
removeNodeAndSource(v);
|
||||
}
|
||||
|
||||
function pause(evt) {
|
||||
var v = evt.target;
|
||||
v.removeEventListener("pause", pause);
|
||||
v.gotPause = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ function startTest(test, token) {
|
|||
}
|
||||
vout.parentNode.removeChild(vout);
|
||||
manager.finished(vout.token);
|
||||
removeNodeAndSource(v);
|
||||
}}(test, vout, stream);
|
||||
vout.addEventListener("ended", checkEnded, false);
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ void WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
|
|||
{
|
||||
static const uint32_t SEGMENT_ID = 0x18538067;
|
||||
static const uint32_t SEGINFO_ID = 0x1549a966;
|
||||
static const uint32_t TRACKS_ID = 0x1654AE6B;
|
||||
static const uint32_t CLUSTER_ID = 0x1f43b675;
|
||||
static const uint32_t TIMECODESCALE_ID = 0x2ad7b1;
|
||||
static const unsigned char TIMECODE_ID = 0xe7;
|
||||
|
@ -115,6 +116,10 @@ void WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
|
|||
mState = READ_VINT;
|
||||
mNextState = READ_BLOCK_TIMECODE;
|
||||
break;
|
||||
case TRACKS_ID:
|
||||
mSkipBytes = mElement.mSize.mValue;
|
||||
mState = CHECK_INIT_FOUND;
|
||||
break;
|
||||
default:
|
||||
mSkipBytes = mElement.mSize.mValue;
|
||||
mState = SKIP_DATA;
|
||||
|
@ -192,6 +197,20 @@ void WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
|
|||
mState = mNextState;
|
||||
}
|
||||
break;
|
||||
case CHECK_INIT_FOUND:
|
||||
if (mSkipBytes) {
|
||||
uint32_t left = aLength - (p - aBuffer);
|
||||
left = std::min(left, mSkipBytes);
|
||||
p += left;
|
||||
mSkipBytes -= left;
|
||||
}
|
||||
if (!mSkipBytes) {
|
||||
if (mInitEndOffset < 0) {
|
||||
mInitEndOffset = mCurrentOffset + (p - aBuffer);
|
||||
}
|
||||
mState = READ_ELEMENT_ID;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -321,6 +340,7 @@ void WebMBufferedState::NotifyDataArrived(const char* aBuffer, uint32_t aLength,
|
|||
while (i + 1 < mRangeParsers.Length()) {
|
||||
if (mRangeParsers[i].mCurrentOffset >= mRangeParsers[i + 1].mStartOffset) {
|
||||
mRangeParsers[i + 1].mStartOffset = mRangeParsers[i].mStartOffset;
|
||||
mRangeParsers[i + 1].mInitEndOffset = mRangeParsers[i].mInitEndOffset;
|
||||
mRangeParsers.RemoveElementAt(i);
|
||||
} else {
|
||||
i += 1;
|
||||
|
@ -328,5 +348,12 @@ void WebMBufferedState::NotifyDataArrived(const char* aBuffer, uint32_t aLength,
|
|||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
int64_t WebMBufferedState::GetInitEndOffset()
|
||||
{
|
||||
if (mRangeParsers.IsEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
return mRangeParsers[0].mInitEndOffset;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -50,9 +50,9 @@ struct WebMTimeDataOffset
|
|||
struct WebMBufferedParser
|
||||
{
|
||||
explicit WebMBufferedParser(int64_t aOffset)
|
||||
: mStartOffset(aOffset), mCurrentOffset(aOffset), mState(READ_ELEMENT_ID),
|
||||
mVIntRaw(false), mClusterSyncPos(0), mTimecodeScale(1000000),
|
||||
mGotTimecodeScale(false)
|
||||
: mStartOffset(aOffset), mCurrentOffset(aOffset), mInitEndOffset(-1),
|
||||
mState(READ_ELEMENT_ID), mVIntRaw(false), mClusterSyncPos(0),
|
||||
mTimecodeScale(1000000), mGotTimecodeScale(false)
|
||||
{
|
||||
if (mStartOffset != 0) {
|
||||
mState = FIND_CLUSTER_SYNC;
|
||||
|
@ -95,6 +95,10 @@ struct WebMBufferedParser
|
|||
// data.
|
||||
int64_t mCurrentOffset;
|
||||
|
||||
// Tracks element's end offset. This indicates the end of the init segment.
|
||||
// Will only be set if a Segment Information has been found.
|
||||
int64_t mInitEndOffset;
|
||||
|
||||
private:
|
||||
enum State {
|
||||
// Parser start state. Expects to begin at a valid EBML element. Move
|
||||
|
@ -140,6 +144,11 @@ private:
|
|||
// for this offset.
|
||||
READ_BLOCK_TIMECODE,
|
||||
|
||||
// Will skip the current tracks element and set mInitEndOffset if an init
|
||||
// segment has been found.
|
||||
// Currently, only assumes it's the end of the tracks element.
|
||||
CHECK_INIT_FOUND,
|
||||
|
||||
// Skip mSkipBytes of data before resuming parse at mNextState.
|
||||
SKIP_DATA,
|
||||
};
|
||||
|
@ -232,6 +241,9 @@ public:
|
|||
// dependencies to arrive at aTime.
|
||||
bool GetOffsetForTime(uint64_t aTime, int64_t* aOffset);
|
||||
|
||||
// Returns end offset of init segment or -1 if none found.
|
||||
int64_t GetInitEndOffset();
|
||||
|
||||
private:
|
||||
// Private destructor, to discourage deletion outside of Release():
|
||||
~WebMBufferedState() {
|
||||
|
|
|
@ -337,7 +337,7 @@ nsresult WebMReader::ReadMetadata(MediaInfo* aInfo,
|
|||
io.tell = webm_tell;
|
||||
io.userdata = mDecoder;
|
||||
int64_t maxOffset = mDecoder->HasInitializationData() ?
|
||||
mDecoder->GetResource()->GetLength() : -1;
|
||||
mBufferedState->GetInitEndOffset() : -1;
|
||||
int r = nestegg_init(&mContext, io, &webm_log, maxOffset);
|
||||
if (r == -1) {
|
||||
return NS_ERROR_FAILURE;
|
||||
|
|
|
@ -8,7 +8,7 @@ const Cc = Components.classes;
|
|||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
this.EXPORTED_SYMBOLS = [];
|
||||
this.EXPORTED_SYMBOLS = ["SettingsRequestManager"];
|
||||
|
||||
Cu.import("resource://gre/modules/SettingsDB.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
@ -64,6 +64,9 @@ XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
|
|||
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
|
||||
"@mozilla.org/uuid-generator;1",
|
||||
"nsIUUIDGenerator");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
|
||||
"@mozilla.org/settingsService;1",
|
||||
"nsISettingsService");
|
||||
|
||||
let SettingsPermissions = {
|
||||
checkPermission: function(aPrincipal, aPerm) {
|
||||
|
@ -1036,10 +1039,18 @@ let SettingsRequestManager = {
|
|||
let mm = aMessage.target;
|
||||
|
||||
function returnMessage(name, data) {
|
||||
try {
|
||||
mm.sendAsyncMessage(name, data);
|
||||
} catch (e) {
|
||||
if (DEBUG) debug("Return message failed, " + name);
|
||||
if (mm) {
|
||||
try {
|
||||
mm.sendAsyncMessage(name, data);
|
||||
} catch (e) {
|
||||
if (DEBUG) debug("Return message failed, " + name + ": " + e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
gSettingsService.receiveMessage({ name: name, data: data });
|
||||
} catch (e) {
|
||||
if (DEBUG) debug("Direct return message failed, " + name + ": " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1193,4 +1204,5 @@ let SettingsRequestManager = {
|
|||
}
|
||||
};
|
||||
|
||||
this.SettingsRequestManager = SettingsRequestManager;
|
||||
SettingsRequestManager.init();
|
||||
|
|
|
@ -79,10 +79,8 @@ function SettingsServiceLock(aSettingsService, aTransactionCallback) {
|
|||
windowID: undefined,
|
||||
lockStack: (new Error).stack
|
||||
};
|
||||
cpmm.sendAsyncMessage("Settings:CreateLock",
|
||||
createLockPayload,
|
||||
undefined,
|
||||
Services.scriptSecurityManager.getSystemPrincipal());
|
||||
|
||||
this.returnMessage("Settings:CreateLock", createLockPayload);
|
||||
Services.tm.currentThread.dispatch(closeHelper, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
|
@ -91,12 +89,20 @@ SettingsServiceLock.prototype = {
|
|||
return !this._open;
|
||||
},
|
||||
|
||||
returnMessage: function(aMessage, aData) {
|
||||
SettingsRequestManager.receiveMessage({
|
||||
name: aMessage,
|
||||
data: aData,
|
||||
target: undefined,
|
||||
principal: Services.scriptSecurityManager.getSystemPrincipal()
|
||||
});
|
||||
},
|
||||
|
||||
runOrFinalizeQueries: function() {
|
||||
if (!this._requests || Object.keys(this._requests).length == 0) {
|
||||
this._settingsService.unregisterLock(this._id);
|
||||
cpmm.sendAsyncMessage("Settings:Finalize", {lockID: this._id}, undefined, Services.scriptSecurityManager.getSystemPrincipal());
|
||||
this.returnMessage("Settings:Finalize", {lockID: this._id});
|
||||
} else {
|
||||
cpmm.sendAsyncMessage("Settings:Run", {lockID: this._id}, undefined, Services.scriptSecurityManager.getSystemPrincipal());
|
||||
this.returnMessage("Settings:Run", {lockID: this._id});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -124,6 +130,8 @@ SettingsServiceLock.prototype = {
|
|||
default:
|
||||
if (DEBUG) debug("Message type " + aMessage.name + " is missing a requestID");
|
||||
}
|
||||
|
||||
this._settingsService.unregisterLock(this._id);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -138,7 +146,7 @@ SettingsServiceLock.prototype = {
|
|||
this._open = true;
|
||||
let settings_names = Object.keys(msg.settings);
|
||||
if (settings_names.length > 0) {
|
||||
let name = settings_names[0];
|
||||
let name = settings_names[0];
|
||||
if (DEBUG && settings_names.length > 1) {
|
||||
debug("Warning: overloaded setting:" + name);
|
||||
}
|
||||
|
@ -174,11 +182,9 @@ SettingsServiceLock.prototype = {
|
|||
}
|
||||
let reqID = uuidgen.generateUUID().toString();
|
||||
this._requests[reqID] = makeSettingsServiceRequest(aCallback, aName);
|
||||
cpmm.sendAsyncMessage("Settings:Get", {requestID: reqID,
|
||||
lockID: this._id,
|
||||
name: aName},
|
||||
undefined,
|
||||
Services.scriptSecurityManager.getSystemPrincipal());
|
||||
this.returnMessage("Settings:Get", {requestID: reqID,
|
||||
lockID: this._id,
|
||||
name: aName});
|
||||
},
|
||||
|
||||
set: function set(aName, aValue, aCallback) {
|
||||
|
@ -190,11 +196,9 @@ SettingsServiceLock.prototype = {
|
|||
this._requests[reqID] = makeSettingsServiceRequest(aCallback, aName, aValue);
|
||||
let settings = {};
|
||||
settings[aName] = aValue;
|
||||
cpmm.sendAsyncMessage("Settings:Set", {requestID: reqID,
|
||||
lockID: this._id,
|
||||
settings: settings},
|
||||
undefined,
|
||||
Services.scriptSecurityManager.getSystemPrincipal());
|
||||
this.returnMessage("Settings:Set", {requestID: reqID,
|
||||
lockID: this._id,
|
||||
settings: settings});
|
||||
},
|
||||
|
||||
callHandle: function callHandle(aCallback, aName, aValue) {
|
||||
|
@ -239,6 +243,7 @@ function SettingsService()
|
|||
{
|
||||
if (VERBOSE) debug("settingsService Constructor");
|
||||
this._locks = [];
|
||||
this._serviceLocks = {};
|
||||
this._createdLocks = 0;
|
||||
this._unregisteredLocks = 0;
|
||||
this.init();
|
||||
|
@ -263,14 +268,36 @@ SettingsService.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
if (VERBOSE) debug("Entering receiveMessage");
|
||||
|
||||
let lockID = aMessage.data.lockID;
|
||||
if (!lockID) {
|
||||
if (DEBUG) debug("No lock ID");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(lockID in this._serviceLocks)) {
|
||||
if (DEBUG) debug("Received message for lock " + lockID + " but no lock");
|
||||
return;
|
||||
}
|
||||
|
||||
if (VERBOSE) debug("Delivering message");
|
||||
this._serviceLocks[lockID].receiveMessage(aMessage);
|
||||
},
|
||||
|
||||
createLock: function createLock(aCallback) {
|
||||
if (VERBOSE) debug("Calling createLock");
|
||||
var lock = new SettingsServiceLock(this, aCallback);
|
||||
this.registerLock(lock._id);
|
||||
if (VERBOSE) debug("Created lock " + lock._id);
|
||||
this.registerLock(lock);
|
||||
return lock;
|
||||
},
|
||||
|
||||
registerLock: function(aLockID) {
|
||||
this._locks.push(aLockID);
|
||||
registerLock: function(aLock) {
|
||||
if (VERBOSE) debug("Registering lock " + aLock._id);
|
||||
this._locks.push(aLock._id);
|
||||
this._serviceLocks[aLock._id] = aLock;
|
||||
this._createdLocks++;
|
||||
},
|
||||
|
||||
|
@ -279,6 +306,8 @@ SettingsService.prototype = {
|
|||
if (lock_index != -1) {
|
||||
if (VERBOSE) debug("Unregistering lock " + aLockID);
|
||||
this._locks.splice(lock_index, 1);
|
||||
this._serviceLocks[aLockID] = null;
|
||||
delete this._serviceLocks[aLockID];
|
||||
this._unregisteredLocks++;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -10,9 +10,11 @@
|
|||
#include "nsAppRunner.h"
|
||||
#include "Composer2D.h"
|
||||
#include "Effects.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Endian.h"
|
||||
#include "mozilla/MathAlgorithms.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
#include "TexturePoolOGL.h"
|
||||
#include "mozilla/layers/CompositorOGL.h"
|
||||
#include "mozilla/layers/CompositorParent.h"
|
||||
|
@ -66,7 +68,8 @@ using namespace layerscope;
|
|||
class DebugDataSender;
|
||||
class DebugGLData;
|
||||
|
||||
/* This class handle websocket protocol which included
|
||||
/*
|
||||
* This class handle websocket protocol which included
|
||||
* handshake and data frame's header
|
||||
*/
|
||||
class LayerScopeWebSocketHandler : public nsIInputStreamCallback {
|
||||
|
@ -81,203 +84,48 @@ public:
|
|||
|
||||
LayerScopeWebSocketHandler()
|
||||
: mState(NoHandshake)
|
||||
, mConnected(false)
|
||||
{ }
|
||||
|
||||
private:
|
||||
virtual ~LayerScopeWebSocketHandler()
|
||||
{
|
||||
if (mTransport) {
|
||||
mTransport->Close(NS_OK);
|
||||
}
|
||||
}
|
||||
void OpenStream(nsISocketTransport* aTransport);
|
||||
|
||||
public:
|
||||
void OpenStream(nsISocketTransport* aTransport) {
|
||||
MOZ_ASSERT(aTransport);
|
||||
|
||||
mTransport = aTransport;
|
||||
mTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING,
|
||||
0,
|
||||
0,
|
||||
getter_AddRefs(mOutputStream));
|
||||
|
||||
nsCOMPtr<nsIInputStream> debugInputStream;
|
||||
mTransport->OpenInputStream(0,
|
||||
0,
|
||||
0,
|
||||
getter_AddRefs(debugInputStream));
|
||||
mInputStream = do_QueryInterface(debugInputStream);
|
||||
mInputStream->AsyncWait(this, 0, 0, NS_GetCurrentThread());
|
||||
}
|
||||
|
||||
bool WriteToStream(void *ptr, uint32_t size) {
|
||||
if (mState == NoHandshake) {
|
||||
// Not yet handshake, just return true in case of
|
||||
// LayerScope remove this handle
|
||||
return true;
|
||||
} else if (mState == HandshakeFailed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate WebSocket header
|
||||
uint8_t wsHeader[10];
|
||||
int wsHeaderSize = 0;
|
||||
const uint8_t opcode = 0x2;
|
||||
wsHeader[0] = 0x80 | (opcode & 0x0f); // FIN + opcode;
|
||||
if (size <= 125) {
|
||||
wsHeaderSize = 2;
|
||||
wsHeader[1] = size;
|
||||
} else if (size < 65536) {
|
||||
wsHeaderSize = 4;
|
||||
wsHeader[1] = 0x7E;
|
||||
NetworkEndian::writeUint16(wsHeader + 2, size);
|
||||
} else {
|
||||
wsHeaderSize = 10;
|
||||
wsHeader[1] = 0x7F;
|
||||
NetworkEndian::writeUint64(wsHeader + 2, size);
|
||||
}
|
||||
|
||||
// Send WebSocket header
|
||||
nsresult rv;
|
||||
uint32_t cnt;
|
||||
rv = mOutputStream->Write(reinterpret_cast<char*>(wsHeader),
|
||||
wsHeaderSize, &cnt);
|
||||
if (NS_FAILED(rv))
|
||||
return false;
|
||||
|
||||
uint32_t written = 0;
|
||||
while (written < size) {
|
||||
uint32_t cnt;
|
||||
rv = mOutputStream->Write(reinterpret_cast<char*>(ptr) + written,
|
||||
size - written, &cnt);
|
||||
if (NS_FAILED(rv))
|
||||
return false;
|
||||
|
||||
written += cnt;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
bool WriteToStream(void *aPtr, uint32_t aSize);
|
||||
|
||||
// nsIInputStreamCallback
|
||||
NS_IMETHODIMP OnInputStreamReady(nsIAsyncInputStream *stream) MOZ_OVERRIDE
|
||||
{
|
||||
nsTArray<nsCString> protocolString;
|
||||
ReadInputStreamData(protocolString);
|
||||
NS_IMETHODIMP OnInputStreamReady(nsIAsyncInputStream *aStream) MOZ_OVERRIDE;
|
||||
|
||||
if (WebSocketHandshake(protocolString)) {
|
||||
mState = HandshakeSuccess;
|
||||
} else {
|
||||
mState = HandshakeFailed;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
void ReadInputStreamData(nsTArray<nsCString>& aProtocolString)
|
||||
{
|
||||
nsLineBuffer<char> lineBuffer;
|
||||
nsCString line;
|
||||
bool more = true;
|
||||
do {
|
||||
NS_ReadLine(mInputStream.get(), &lineBuffer, line, &more);
|
||||
virtual ~LayerScopeWebSocketHandler() { CloseConnection(); }
|
||||
|
||||
if (line.Length() > 0) {
|
||||
aProtocolString.AppendElement(line);
|
||||
}
|
||||
} while (more && line.Length() > 0);
|
||||
}
|
||||
void ReadInputStreamData(nsTArray<nsCString>& aProtocolString);
|
||||
|
||||
bool WebSocketHandshake(nsTArray<nsCString>& aProtocolString)
|
||||
{
|
||||
nsresult rv;
|
||||
bool isWebSocket = false;
|
||||
nsCString version;
|
||||
nsCString wsKey;
|
||||
nsCString protocol;
|
||||
bool WebSocketHandshake(nsTArray<nsCString>& aProtocolString);
|
||||
|
||||
// Validate WebSocket client request.
|
||||
if (aProtocolString.Length() == 0)
|
||||
return false;
|
||||
nsresult HandleSocketMessage(nsIAsyncInputStream *aStream);
|
||||
|
||||
// Check that the HTTP method is GET
|
||||
const char* HTTP_METHOD = "GET ";
|
||||
if (strncmp(aProtocolString[0].get(), HTTP_METHOD, strlen(HTTP_METHOD)) != 0) {
|
||||
return false;
|
||||
}
|
||||
nsresult ProcessInput(uint8_t *aBuffer, uint32_t aCount);
|
||||
|
||||
for (uint32_t i = 1; i < aProtocolString.Length(); ++i) {
|
||||
const char* line = aProtocolString[i].get();
|
||||
const char* prop_pos = strchr(line, ':');
|
||||
if (prop_pos != nullptr) {
|
||||
nsCString key(line, prop_pos - line);
|
||||
nsCString value(prop_pos + 2);
|
||||
if (key.EqualsIgnoreCase("upgrade") &&
|
||||
value.EqualsIgnoreCase("websocket")) {
|
||||
isWebSocket = true;
|
||||
} else if (key.EqualsIgnoreCase("sec-websocket-version")) {
|
||||
version = value;
|
||||
} else if (key.EqualsIgnoreCase("sec-websocket-key")) {
|
||||
wsKey = value;
|
||||
} else if (key.EqualsIgnoreCase("sec-websocket-protocol")) {
|
||||
protocol = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copied from WebsocketChannel, helper function to decode data frame
|
||||
void ApplyMask(uint32_t aMask, uint8_t *aData, uint64_t aLen);
|
||||
|
||||
if (!isWebSocket) {
|
||||
return false;
|
||||
}
|
||||
bool HandleDataFrame(uint8_t *aData, uint32_t aSize);
|
||||
|
||||
if (!(version.EqualsLiteral("7") ||
|
||||
version.EqualsLiteral("8") ||
|
||||
version.EqualsLiteral("13"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(protocol.EqualsIgnoreCase("binary"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Client request is valid. Start to generate and send server response.
|
||||
nsAutoCString guid("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||||
nsAutoCString res;
|
||||
SHA1Sum sha1;
|
||||
nsCString combined(wsKey + guid);
|
||||
sha1.update(combined.get(), combined.Length());
|
||||
uint8_t digest[SHA1Sum::kHashSize]; // SHA1 digests are 20 bytes long.
|
||||
sha1.finish(digest);
|
||||
nsCString newString(reinterpret_cast<char*>(digest), SHA1Sum::kHashSize);
|
||||
Base64Encode(newString, res);
|
||||
|
||||
nsCString response("HTTP/1.1 101 Switching Protocols\r\n");
|
||||
response.AppendLiteral("Upgrade: websocket\r\n");
|
||||
response.AppendLiteral("Connection: Upgrade\r\n");
|
||||
response.Append(nsCString("Sec-WebSocket-Accept: ") + res + nsCString("\r\n"));
|
||||
response.AppendLiteral("Sec-WebSocket-Protocol: binary\r\n\r\n");
|
||||
uint32_t written = 0;
|
||||
uint32_t size = response.Length();
|
||||
while (written < size) {
|
||||
uint32_t cnt;
|
||||
rv = mOutputStream->Write(const_cast<char*>(response.get()) + written,
|
||||
size - written, &cnt);
|
||||
if (NS_FAILED(rv))
|
||||
return false;
|
||||
|
||||
written += cnt;
|
||||
}
|
||||
mOutputStream->Flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
void CloseConnection();
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsIOutputStream> mOutputStream;
|
||||
nsCOMPtr<nsIAsyncInputStream> mInputStream;
|
||||
nsCOMPtr<nsISocketTransport> mTransport;
|
||||
SocketStateType mState;
|
||||
bool mConnected;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(LayerScopeWebSocketHandler, nsIInputStreamCallback);
|
||||
|
||||
|
||||
/*
|
||||
* Manage Websocket connections
|
||||
*/
|
||||
class LayerScopeWebSocketManager {
|
||||
public:
|
||||
LayerScopeWebSocketManager();
|
||||
|
@ -720,6 +568,12 @@ public:
|
|||
int aWidth = 0,
|
||||
int aHeight = 0);
|
||||
|
||||
static void SetLayersTreeSendable(bool aSet) {sLayersTreeSendable = aSet;}
|
||||
|
||||
static void SetLayersBufferSendable(bool aSet) {sLayersBufferSendable = aSet;}
|
||||
|
||||
static bool GetLayersTreeSendable() {return sLayersTreeSendable;}
|
||||
|
||||
// Sender private functions
|
||||
private:
|
||||
static void SendColor(void* aLayerRef,
|
||||
|
@ -736,8 +590,16 @@ private:
|
|||
static void SendYCbCrEffect(GLContext* aGLContext,
|
||||
void* aLayerRef,
|
||||
const EffectYCbCr* aEffect);
|
||||
|
||||
// Data fields
|
||||
private:
|
||||
static bool sLayersTreeSendable;
|
||||
static bool sLayersBufferSendable;
|
||||
};
|
||||
|
||||
bool SenderHelper::sLayersTreeSendable = true;
|
||||
bool SenderHelper::sLayersBufferSendable = true;
|
||||
|
||||
|
||||
// ----------------------------------------------
|
||||
// SenderHelper implementation
|
||||
|
@ -874,6 +736,8 @@ SenderHelper::SendEffectChain(GLContext* aGLContext,
|
|||
int aWidth,
|
||||
int aHeight)
|
||||
{
|
||||
if (!sLayersBufferSendable) return;
|
||||
|
||||
const Effect* primaryEffect = aEffectChain.mPrimaryEffect;
|
||||
switch (primaryEffect->mType) {
|
||||
case EffectTypes::RGB: {
|
||||
|
@ -908,6 +772,430 @@ SenderHelper::SendEffectChain(GLContext* aGLContext,
|
|||
// TODO:
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------
|
||||
// LayerScopeWebSocketHandler implementation
|
||||
// ----------------------------------------------
|
||||
void
|
||||
LayerScopeWebSocketHandler::OpenStream(nsISocketTransport* aTransport)
|
||||
{
|
||||
MOZ_ASSERT(aTransport);
|
||||
|
||||
mTransport = aTransport;
|
||||
mTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING,
|
||||
0,
|
||||
0,
|
||||
getter_AddRefs(mOutputStream));
|
||||
|
||||
nsCOMPtr<nsIInputStream> debugInputStream;
|
||||
mTransport->OpenInputStream(0,
|
||||
0,
|
||||
0,
|
||||
getter_AddRefs(debugInputStream));
|
||||
mInputStream = do_QueryInterface(debugInputStream);
|
||||
mInputStream->AsyncWait(this, 0, 0, NS_GetCurrentThread());
|
||||
}
|
||||
|
||||
bool
|
||||
LayerScopeWebSocketHandler::WriteToStream(void *aPtr,
|
||||
uint32_t aSize)
|
||||
{
|
||||
if (mState == NoHandshake) {
|
||||
// Not yet handshake, just return true in case of
|
||||
// LayerScope remove this handle
|
||||
return true;
|
||||
} else if (mState == HandshakeFailed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mOutputStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate WebSocket header
|
||||
uint8_t wsHeader[10];
|
||||
int wsHeaderSize = 0;
|
||||
const uint8_t opcode = 0x2;
|
||||
wsHeader[0] = 0x80 | (opcode & 0x0f); // FIN + opcode;
|
||||
if (aSize <= 125) {
|
||||
wsHeaderSize = 2;
|
||||
wsHeader[1] = aSize;
|
||||
} else if (aSize < 65536) {
|
||||
wsHeaderSize = 4;
|
||||
wsHeader[1] = 0x7E;
|
||||
NetworkEndian::writeUint16(wsHeader + 2, aSize);
|
||||
} else {
|
||||
wsHeaderSize = 10;
|
||||
wsHeader[1] = 0x7F;
|
||||
NetworkEndian::writeUint64(wsHeader + 2, aSize);
|
||||
}
|
||||
|
||||
// Send WebSocket header
|
||||
nsresult rv;
|
||||
uint32_t cnt;
|
||||
rv = mOutputStream->Write(reinterpret_cast<char*>(wsHeader),
|
||||
wsHeaderSize, &cnt);
|
||||
if (NS_FAILED(rv))
|
||||
return false;
|
||||
|
||||
uint32_t written = 0;
|
||||
while (written < aSize) {
|
||||
uint32_t cnt;
|
||||
rv = mOutputStream->Write(reinterpret_cast<char*>(aPtr) + written,
|
||||
aSize - written, &cnt);
|
||||
if (NS_FAILED(rv))
|
||||
return false;
|
||||
|
||||
written += cnt;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LayerScopeWebSocketHandler::OnInputStreamReady(nsIAsyncInputStream *aStream)
|
||||
{
|
||||
MOZ_ASSERT(mInputStream);
|
||||
|
||||
if (!mInputStream) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!mConnected) {
|
||||
nsTArray<nsCString> protocolString;
|
||||
ReadInputStreamData(protocolString);
|
||||
|
||||
if (WebSocketHandshake(protocolString)) {
|
||||
mState = HandshakeSuccess;
|
||||
mConnected = true;
|
||||
mInputStream->AsyncWait(this, 0, 0, NS_GetCurrentThread());
|
||||
} else {
|
||||
mState = HandshakeFailed;
|
||||
}
|
||||
return NS_OK;
|
||||
} else {
|
||||
return HandleSocketMessage(aStream);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LayerScopeWebSocketHandler::ReadInputStreamData(nsTArray<nsCString>& aProtocolString)
|
||||
{
|
||||
nsLineBuffer<char> lineBuffer;
|
||||
nsCString line;
|
||||
bool more = true;
|
||||
do {
|
||||
NS_ReadLine(mInputStream.get(), &lineBuffer, line, &more);
|
||||
|
||||
if (line.Length() > 0) {
|
||||
aProtocolString.AppendElement(line);
|
||||
}
|
||||
} while (more && line.Length() > 0);
|
||||
}
|
||||
|
||||
bool
|
||||
LayerScopeWebSocketHandler::WebSocketHandshake(nsTArray<nsCString>& aProtocolString)
|
||||
{
|
||||
nsresult rv;
|
||||
bool isWebSocket = false;
|
||||
nsCString version;
|
||||
nsCString wsKey;
|
||||
nsCString protocol;
|
||||
|
||||
// Validate WebSocket client request.
|
||||
if (aProtocolString.Length() == 0)
|
||||
return false;
|
||||
|
||||
// Check that the HTTP method is GET
|
||||
const char* HTTP_METHOD = "GET ";
|
||||
if (strncmp(aProtocolString[0].get(), HTTP_METHOD, strlen(HTTP_METHOD)) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 1; i < aProtocolString.Length(); ++i) {
|
||||
const char* line = aProtocolString[i].get();
|
||||
const char* prop_pos = strchr(line, ':');
|
||||
if (prop_pos != nullptr) {
|
||||
nsCString key(line, prop_pos - line);
|
||||
nsCString value(prop_pos + 2);
|
||||
if (key.EqualsIgnoreCase("upgrade") &&
|
||||
value.EqualsIgnoreCase("websocket")) {
|
||||
isWebSocket = true;
|
||||
} else if (key.EqualsIgnoreCase("sec-websocket-version")) {
|
||||
version = value;
|
||||
} else if (key.EqualsIgnoreCase("sec-websocket-key")) {
|
||||
wsKey = value;
|
||||
} else if (key.EqualsIgnoreCase("sec-websocket-protocol")) {
|
||||
protocol = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isWebSocket) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(version.EqualsLiteral("7") ||
|
||||
version.EqualsLiteral("8") ||
|
||||
version.EqualsLiteral("13"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(protocol.EqualsIgnoreCase("binary"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mOutputStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Client request is valid. Start to generate and send server response.
|
||||
nsAutoCString guid("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||||
nsAutoCString res;
|
||||
SHA1Sum sha1;
|
||||
nsCString combined(wsKey + guid);
|
||||
sha1.update(combined.get(), combined.Length());
|
||||
uint8_t digest[SHA1Sum::kHashSize]; // SHA1 digests are 20 bytes long.
|
||||
sha1.finish(digest);
|
||||
nsCString newString(reinterpret_cast<char*>(digest), SHA1Sum::kHashSize);
|
||||
Base64Encode(newString, res);
|
||||
|
||||
nsCString response("HTTP/1.1 101 Switching Protocols\r\n");
|
||||
response.AppendLiteral("Upgrade: websocket\r\n");
|
||||
response.AppendLiteral("Connection: Upgrade\r\n");
|
||||
response.Append(nsCString("Sec-WebSocket-Accept: ") + res + nsCString("\r\n"));
|
||||
response.AppendLiteral("Sec-WebSocket-Protocol: binary\r\n\r\n");
|
||||
uint32_t written = 0;
|
||||
uint32_t size = response.Length();
|
||||
while (written < size) {
|
||||
uint32_t cnt;
|
||||
rv = mOutputStream->Write(const_cast<char*>(response.get()) + written,
|
||||
size - written, &cnt);
|
||||
if (NS_FAILED(rv))
|
||||
return false;
|
||||
|
||||
written += cnt;
|
||||
}
|
||||
mOutputStream->Flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LayerScopeWebSocketHandler::HandleSocketMessage(nsIAsyncInputStream *aStream)
|
||||
{
|
||||
// The reading and parsing of this input stream is customized for layer viewer.
|
||||
const uint32_t cPacketSize = 1024;
|
||||
char buffer[cPacketSize];
|
||||
uint32_t count = 0;
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
do {
|
||||
rv = mInputStream->Read((char *)buffer, cPacketSize, &count);
|
||||
|
||||
// TODO: combine packets if we have to read more than once
|
||||
|
||||
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
|
||||
mInputStream->AsyncWait(this, 0, 0, NS_GetCurrentThread());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
// NS_BASE_STREAM_CLOSED
|
||||
CloseConnection();
|
||||
break;
|
||||
}
|
||||
|
||||
rv = ProcessInput(reinterpret_cast<uint8_t *>(buffer), count);
|
||||
} while (NS_SUCCEEDED(rv) && mInputStream);
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LayerScopeWebSocketHandler::ProcessInput(uint8_t *aBuffer,
|
||||
uint32_t aCount)
|
||||
{
|
||||
uint32_t avail = aCount;
|
||||
|
||||
// Decode Websocket data frame
|
||||
if (avail <= 2) {
|
||||
NS_WARNING("Packet size is less than 2 bytes");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// First byte, data type, only care the opcode
|
||||
// rsvBits: aBuffer[0] & 0x70 (0111 0000)
|
||||
uint8_t finBit = aBuffer[0] & 0x80; // 1000 0000
|
||||
uint8_t opcode = aBuffer[0] & 0x0F; // 0000 1111
|
||||
|
||||
if (!finBit) {
|
||||
NS_WARNING("We cannot handle multi-fragments messages in Layerscope websocket parser.");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Second byte, data length
|
||||
uint8_t maskBit = aBuffer[1] & 0x80; // 1000 0000
|
||||
int64_t payloadLength64 = aBuffer[1] & 0x7F; // 0111 1111
|
||||
|
||||
if (!maskBit) {
|
||||
NS_WARNING("Client to Server should set the mask bit");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
uint32_t framingLength = 2 + 4; // 4 for masks
|
||||
|
||||
if (payloadLength64 < 126) {
|
||||
if (avail < framingLength)
|
||||
return NS_OK;
|
||||
} else if (payloadLength64 == 126) {
|
||||
// 16 bit length field
|
||||
framingLength += 2;
|
||||
if (avail < framingLength) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
payloadLength64 = aBuffer[2] << 8 | aBuffer[3];
|
||||
} else {
|
||||
// 64 bit length
|
||||
framingLength += 8;
|
||||
if (avail < framingLength) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (aBuffer[2] & 0x80) {
|
||||
// Section 4.2 says that the most significant bit MUST be
|
||||
// 0. (i.e. this is really a 63 bit value)
|
||||
NS_WARNING("High bit of 64 bit length set");
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
// copy this in case it is unaligned
|
||||
payloadLength64 = NetworkEndian::readInt64(aBuffer + 2);
|
||||
}
|
||||
|
||||
uint8_t *payload = aBuffer + framingLength;
|
||||
avail -= framingLength;
|
||||
|
||||
uint32_t payloadLength = static_cast<uint32_t>(payloadLength64);
|
||||
if (avail < payloadLength) {
|
||||
NS_WARNING("Packet size mismatch the payload length");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Apply mask
|
||||
uint32_t mask = NetworkEndian::readUint32(payload - 4);
|
||||
ApplyMask(mask, payload, payloadLength);
|
||||
|
||||
if (opcode == 0x8) {
|
||||
// opcode == 0x8 means connection close
|
||||
CloseConnection();
|
||||
return NS_BASE_STREAM_CLOSED;
|
||||
}
|
||||
|
||||
if (!HandleDataFrame(payload, payloadLength)) {
|
||||
NS_WARNING("Cannot decode payload data by the protocol buffer");
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
LayerScopeWebSocketHandler::ApplyMask(uint32_t aMask,
|
||||
uint8_t *aData,
|
||||
uint64_t aLen)
|
||||
{
|
||||
if (!aData || aLen == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimally we want to apply the mask 32 bits at a time,
|
||||
// but the buffer might not be alligned. So we first deal with
|
||||
// 0 to 3 bytes of preamble individually
|
||||
while (aLen && (reinterpret_cast<uintptr_t>(aData) & 3)) {
|
||||
*aData ^= aMask >> 24;
|
||||
aMask = RotateLeft(aMask, 8);
|
||||
aData++;
|
||||
aLen--;
|
||||
}
|
||||
|
||||
// perform mask on full words of data
|
||||
uint32_t *iData = reinterpret_cast<uint32_t *>(aData);
|
||||
uint32_t *end = iData + (aLen >> 2);
|
||||
NetworkEndian::writeUint32(&aMask, aMask);
|
||||
for (; iData < end; iData++) {
|
||||
*iData ^= aMask;
|
||||
}
|
||||
aMask = NetworkEndian::readUint32(&aMask);
|
||||
aData = (uint8_t *)iData;
|
||||
aLen = aLen % 4;
|
||||
|
||||
// There maybe up to 3 trailing bytes that need to be dealt with
|
||||
// individually
|
||||
while (aLen) {
|
||||
*aData ^= aMask >> 24;
|
||||
aMask = RotateLeft(aMask, 8);
|
||||
aData++;
|
||||
aLen--;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
LayerScopeWebSocketHandler::HandleDataFrame(uint8_t *aData,
|
||||
uint32_t aSize)
|
||||
{
|
||||
// Handle payload data by protocol buffer
|
||||
auto p = MakeUnique<CommandPacket>();
|
||||
p->ParseFromArray(static_cast<void*>(aData), aSize);
|
||||
|
||||
if (!p->has_type()) {
|
||||
MOZ_ASSERT(false, "Protocol buffer decoding failed or cannot recongize it");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (p->type()) {
|
||||
case CommandPacket::LAYERS_TREE:
|
||||
if (p->has_value()) {
|
||||
SenderHelper::SetLayersTreeSendable(p->value());
|
||||
}
|
||||
break;
|
||||
|
||||
case CommandPacket::LAYERS_BUFFER:
|
||||
if (p->has_value()) {
|
||||
SenderHelper::SetLayersBufferSendable(p->value());
|
||||
}
|
||||
break;
|
||||
|
||||
case CommandPacket::NO_OP:
|
||||
default:
|
||||
NS_WARNING("Invalid message type");
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
LayerScopeWebSocketHandler::CloseConnection()
|
||||
{
|
||||
WebSocketHelper::GetSocketManager()->CleanDebugData();
|
||||
if (mInputStream) {
|
||||
mInputStream->AsyncWait(nullptr, 0, 0, nullptr);
|
||||
mInputStream = nullptr;
|
||||
}
|
||||
if (mOutputStream) {
|
||||
mOutputStream = nullptr;
|
||||
}
|
||||
if (mTransport) {
|
||||
mTransport->Close(NS_BASE_STREAM_CLOSED);
|
||||
mTransport = nullptr;
|
||||
}
|
||||
mConnected = false;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------
|
||||
// LayerScopeWebSocketManager implementation
|
||||
// ----------------------------------------------
|
||||
|
@ -923,6 +1211,7 @@ LayerScopeWebSocketManager::LayerScopeWebSocketManager()
|
|||
|
||||
LayerScopeWebSocketManager::~LayerScopeWebSocketManager()
|
||||
{
|
||||
mServerSocket->Close();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1005,7 +1294,7 @@ void
|
|||
LayerScope::SendLayerDump(UniquePtr<Packet> aPacket)
|
||||
{
|
||||
// Protect this public function
|
||||
if (!CheckSendable()) {
|
||||
if (!CheckSendable() || !SenderHelper::GetLayersTreeSendable()) {
|
||||
return;
|
||||
}
|
||||
WebSocketHelper::GetSocketManager()->AppendDebugData(
|
||||
|
|
|
@ -546,15 +546,15 @@ TiledLayerBuffer<Derived, Tile>::Update(const nsIntRegion& aNewValidRegion,
|
|||
x += width;
|
||||
}
|
||||
|
||||
mRetainedTiles = newRetainedTiles;
|
||||
AsDerived().PostValidate(aPaintRegion);
|
||||
for (unsigned int i = 0; i < newRetainedTiles.Length(); ++i) {
|
||||
AsDerived().UnlockTile(newRetainedTiles[i]);
|
||||
for (unsigned int i = 0; i < mRetainedTiles.Length(); ++i) {
|
||||
AsDerived().UnlockTile(mRetainedTiles[i]);
|
||||
}
|
||||
|
||||
// At this point, oldTileCount should be zero
|
||||
NS_ABORT_IF_FALSE(oldTileCount == 0, "Failed to release old tiles");
|
||||
|
||||
mRetainedTiles = newRetainedTiles;
|
||||
mValidRegion = aNewValidRegion;
|
||||
mPaintedRegion.Or(mPaintedRegion, aPaintRegion);
|
||||
}
|
||||
|
|
|
@ -333,6 +333,9 @@ static TemporaryRef<gl::ShSurfHandle>
|
|||
CloneSurface(gl::SharedSurface* src, gl::SurfaceFactory* factory)
|
||||
{
|
||||
RefPtr<gl::ShSurfHandle> dest = factory->NewShSurfHandle(src->mSize);
|
||||
if (!dest) {
|
||||
return nullptr;
|
||||
}
|
||||
SharedSurface::ProdCopy(src, dest->Surf(), factory);
|
||||
return dest.forget();
|
||||
}
|
||||
|
|
|
@ -517,6 +517,7 @@ TileClient::TileClient(const TileClient& o)
|
|||
mManager = o.mManager;
|
||||
mInvalidFront = o.mInvalidFront;
|
||||
mInvalidBack = o.mInvalidBack;
|
||||
mOrigin = o.mOrigin;
|
||||
}
|
||||
|
||||
TileClient&
|
||||
|
@ -536,6 +537,7 @@ TileClient::operator=(const TileClient& o)
|
|||
mManager = o.mManager;
|
||||
mInvalidFront = o.mInvalidFront;
|
||||
mInvalidBack = o.mInvalidBack;
|
||||
mOrigin = o.mOrigin;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -1056,6 +1058,31 @@ ClientTiledLayerBuffer::PostValidate(const nsIntRegion& aPaintRegion)
|
|||
mTilingOrigin = IntPoint(std::numeric_limits<int32_t>::max(),
|
||||
std::numeric_limits<int32_t>::max());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < mRetainedTiles.Length(); ++i) {
|
||||
TileClient& tile = mRetainedTiles[i];
|
||||
if (tile.mFrontBuffer && tile.mFrontBuffer->IsLocked()) {
|
||||
// Only worry about padding when not doing low-res because it simplifies
|
||||
// the math and the artifacts won't be noticable
|
||||
// Edge padding prevents sampling artifacts when compositing.
|
||||
if (mResolution == 1) {
|
||||
nsIntRect unscaledTile = nsIntRect(tile.mOrigin.x, tile.mOrigin.y,
|
||||
GetTileSize().width, GetTileSize().height);
|
||||
nsIntRegion tileValidRegion = GetValidRegion();
|
||||
tileValidRegion.OrWith(aPaintRegion);
|
||||
|
||||
// We only need to pad out if the tile has area that's not valid
|
||||
if (!tileValidRegion.Contains(unscaledTile)) {
|
||||
tileValidRegion = tileValidRegion.Intersect(unscaledTile);
|
||||
// translate the region into tile space and pad
|
||||
tileValidRegion.MoveBy(-nsIntPoint(unscaledTile.x, unscaledTile.y));
|
||||
RefPtr<DrawTarget> drawTarget = tile.mFrontBuffer->BorrowDrawTarget();
|
||||
PadDrawTargetOutFromRegion(drawTarget, tileValidRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1144,6 +1171,8 @@ ClientTiledLayerBuffer::ValidateTile(TileClient aTile,
|
|||
}
|
||||
}
|
||||
|
||||
aTile.mOrigin = gfx::ToIntPoint(aTileOrigin);
|
||||
|
||||
if (usingTiledDrawTarget) {
|
||||
if (createdTextureClient) {
|
||||
if (!mCompositableClient->AddTextureClient(backBuffer)) {
|
||||
|
@ -1247,26 +1276,6 @@ ClientTiledLayerBuffer::ValidateTile(TileClient aTile,
|
|||
aTile.mInvalidFront.Or(aTile.mInvalidFront, nsIntRect(copyTarget.x, copyTarget.y, copyRect.width, copyRect.height));
|
||||
}
|
||||
|
||||
// only worry about padding when not doing low-res
|
||||
// because it simplifies the math and the artifacts
|
||||
// won't be noticable
|
||||
if (mResolution == 1) {
|
||||
nsIntRect unscaledTile = nsIntRect(aTileOrigin.x,
|
||||
aTileOrigin.y,
|
||||
GetTileSize().width,
|
||||
GetTileSize().height);
|
||||
|
||||
nsIntRegion tileValidRegion = GetValidRegion();
|
||||
tileValidRegion.Or(tileValidRegion, aDirtyRegion);
|
||||
// We only need to pad out if the tile has area that's not valid
|
||||
if (!tileValidRegion.Contains(unscaledTile)) {
|
||||
tileValidRegion = tileValidRegion.Intersect(unscaledTile);
|
||||
// translate the region into tile space and pad
|
||||
tileValidRegion.MoveBy(-nsIntPoint(unscaledTile.x, unscaledTile.y));
|
||||
PadDrawTargetOutFromRegion(drawTarget, tileValidRegion);
|
||||
}
|
||||
}
|
||||
|
||||
// The new buffer is now validated, remove the dirty region from it.
|
||||
aTile.mInvalidBack.SubOut(offsetScaledDirtyRegion);
|
||||
|
||||
|
|
|
@ -274,6 +274,8 @@ struct TileClient
|
|||
nsIntRegion mInvalidBack;
|
||||
nsExpirationState mExpirationState;
|
||||
|
||||
gfx::IntPoint mOrigin;
|
||||
|
||||
private:
|
||||
// Copies dirty pixels from the front buffer into the back buffer,
|
||||
// and records the copied region in aAddPaintedRegion.
|
||||
|
|
|
@ -27,6 +27,7 @@ void protobuf_ShutdownFile_LayerScopePacket_2eproto() {
|
|||
delete LayersPacket_Layer_Shadow::default_instance_;
|
||||
delete MetaPacket::default_instance_;
|
||||
delete Packet::default_instance_;
|
||||
delete CommandPacket::default_instance_;
|
||||
}
|
||||
|
||||
void protobuf_AddDesc_LayerScopePacket_2eproto() {
|
||||
|
@ -47,6 +48,7 @@ void protobuf_AddDesc_LayerScopePacket_2eproto() {
|
|||
LayersPacket_Layer_Shadow::default_instance_ = new LayersPacket_Layer_Shadow();
|
||||
MetaPacket::default_instance_ = new MetaPacket();
|
||||
Packet::default_instance_ = new Packet();
|
||||
CommandPacket::default_instance_ = new CommandPacket();
|
||||
FramePacket::default_instance_->InitAsDefaultInstance();
|
||||
ColorPacket::default_instance_->InitAsDefaultInstance();
|
||||
TexturePacket::default_instance_->InitAsDefaultInstance();
|
||||
|
@ -59,6 +61,7 @@ void protobuf_AddDesc_LayerScopePacket_2eproto() {
|
|||
LayersPacket_Layer_Shadow::default_instance_->InitAsDefaultInstance();
|
||||
MetaPacket::default_instance_->InitAsDefaultInstance();
|
||||
Packet::default_instance_->InitAsDefaultInstance();
|
||||
CommandPacket::default_instance_->InitAsDefaultInstance();
|
||||
::google::protobuf::internal::OnShutdown(&protobuf_ShutdownFile_LayerScopePacket_2eproto);
|
||||
}
|
||||
|
||||
|
@ -3573,6 +3576,220 @@ void Packet::Swap(Packet* other) {
|
|||
}
|
||||
|
||||
|
||||
// ===================================================================
|
||||
|
||||
bool CommandPacket_CmdType_IsValid(int value) {
|
||||
switch(value) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef _MSC_VER
|
||||
const CommandPacket_CmdType CommandPacket::NO_OP;
|
||||
const CommandPacket_CmdType CommandPacket::LAYERS_TREE;
|
||||
const CommandPacket_CmdType CommandPacket::LAYERS_BUFFER;
|
||||
const CommandPacket_CmdType CommandPacket::CmdType_MIN;
|
||||
const CommandPacket_CmdType CommandPacket::CmdType_MAX;
|
||||
const int CommandPacket::CmdType_ARRAYSIZE;
|
||||
#endif // _MSC_VER
|
||||
#ifndef _MSC_VER
|
||||
const int CommandPacket::kTypeFieldNumber;
|
||||
const int CommandPacket::kValueFieldNumber;
|
||||
#endif // !_MSC_VER
|
||||
|
||||
CommandPacket::CommandPacket()
|
||||
: ::google::protobuf::MessageLite() {
|
||||
SharedCtor();
|
||||
}
|
||||
|
||||
void CommandPacket::InitAsDefaultInstance() {
|
||||
}
|
||||
|
||||
CommandPacket::CommandPacket(const CommandPacket& from)
|
||||
: ::google::protobuf::MessageLite() {
|
||||
SharedCtor();
|
||||
MergeFrom(from);
|
||||
}
|
||||
|
||||
void CommandPacket::SharedCtor() {
|
||||
_cached_size_ = 0;
|
||||
type_ = 0;
|
||||
value_ = false;
|
||||
::memset(_has_bits_, 0, sizeof(_has_bits_));
|
||||
}
|
||||
|
||||
CommandPacket::~CommandPacket() {
|
||||
SharedDtor();
|
||||
}
|
||||
|
||||
void CommandPacket::SharedDtor() {
|
||||
if (this != default_instance_) {
|
||||
}
|
||||
}
|
||||
|
||||
void CommandPacket::SetCachedSize(int size) const {
|
||||
GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
|
||||
_cached_size_ = size;
|
||||
GOOGLE_SAFE_CONCURRENT_WRITES_END();
|
||||
}
|
||||
const CommandPacket& CommandPacket::default_instance() {
|
||||
if (default_instance_ == NULL) protobuf_AddDesc_LayerScopePacket_2eproto(); return *default_instance_;
|
||||
}
|
||||
|
||||
CommandPacket* CommandPacket::default_instance_ = NULL;
|
||||
|
||||
CommandPacket* CommandPacket::New() const {
|
||||
return new CommandPacket;
|
||||
}
|
||||
|
||||
void CommandPacket::Clear() {
|
||||
if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) {
|
||||
type_ = 0;
|
||||
value_ = false;
|
||||
}
|
||||
::memset(_has_bits_, 0, sizeof(_has_bits_));
|
||||
}
|
||||
|
||||
bool CommandPacket::MergePartialFromCodedStream(
|
||||
::google::protobuf::io::CodedInputStream* input) {
|
||||
#define DO_(EXPRESSION) if (!(EXPRESSION)) return false
|
||||
::google::protobuf::uint32 tag;
|
||||
while ((tag = input->ReadTag()) != 0) {
|
||||
switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
|
||||
// required .mozilla.layers.layerscope.CommandPacket.CmdType type = 1;
|
||||
case 1: {
|
||||
if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
|
||||
::google::protobuf::internal::WireFormatLite::WIRETYPE_VARINT) {
|
||||
int value;
|
||||
DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
|
||||
int, ::google::protobuf::internal::WireFormatLite::TYPE_ENUM>(
|
||||
input, &value)));
|
||||
if (::mozilla::layers::layerscope::CommandPacket_CmdType_IsValid(value)) {
|
||||
set_type(static_cast< ::mozilla::layers::layerscope::CommandPacket_CmdType >(value));
|
||||
}
|
||||
} else {
|
||||
goto handle_uninterpreted;
|
||||
}
|
||||
if (input->ExpectTag(16)) goto parse_value;
|
||||
break;
|
||||
}
|
||||
|
||||
// optional bool value = 2;
|
||||
case 2: {
|
||||
if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
|
||||
::google::protobuf::internal::WireFormatLite::WIRETYPE_VARINT) {
|
||||
parse_value:
|
||||
DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
|
||||
bool, ::google::protobuf::internal::WireFormatLite::TYPE_BOOL>(
|
||||
input, &value_)));
|
||||
set_has_value();
|
||||
} else {
|
||||
goto handle_uninterpreted;
|
||||
}
|
||||
if (input->ExpectAtEnd()) return true;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
handle_uninterpreted:
|
||||
if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
|
||||
::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) {
|
||||
return true;
|
||||
}
|
||||
DO_(::google::protobuf::internal::WireFormatLite::SkipField(input, tag));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
#undef DO_
|
||||
}
|
||||
|
||||
void CommandPacket::SerializeWithCachedSizes(
|
||||
::google::protobuf::io::CodedOutputStream* output) const {
|
||||
// required .mozilla.layers.layerscope.CommandPacket.CmdType type = 1;
|
||||
if (has_type()) {
|
||||
::google::protobuf::internal::WireFormatLite::WriteEnum(
|
||||
1, this->type(), output);
|
||||
}
|
||||
|
||||
// optional bool value = 2;
|
||||
if (has_value()) {
|
||||
::google::protobuf::internal::WireFormatLite::WriteBool(2, this->value(), output);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int CommandPacket::ByteSize() const {
|
||||
int total_size = 0;
|
||||
|
||||
if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) {
|
||||
// required .mozilla.layers.layerscope.CommandPacket.CmdType type = 1;
|
||||
if (has_type()) {
|
||||
total_size += 1 +
|
||||
::google::protobuf::internal::WireFormatLite::EnumSize(this->type());
|
||||
}
|
||||
|
||||
// optional bool value = 2;
|
||||
if (has_value()) {
|
||||
total_size += 1 + 1;
|
||||
}
|
||||
|
||||
}
|
||||
GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
|
||||
_cached_size_ = total_size;
|
||||
GOOGLE_SAFE_CONCURRENT_WRITES_END();
|
||||
return total_size;
|
||||
}
|
||||
|
||||
void CommandPacket::CheckTypeAndMergeFrom(
|
||||
const ::google::protobuf::MessageLite& from) {
|
||||
MergeFrom(*::google::protobuf::down_cast<const CommandPacket*>(&from));
|
||||
}
|
||||
|
||||
void CommandPacket::MergeFrom(const CommandPacket& from) {
|
||||
GOOGLE_CHECK_NE(&from, this);
|
||||
if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) {
|
||||
if (from.has_type()) {
|
||||
set_type(from.type());
|
||||
}
|
||||
if (from.has_value()) {
|
||||
set_value(from.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandPacket::CopyFrom(const CommandPacket& from) {
|
||||
if (&from == this) return;
|
||||
Clear();
|
||||
MergeFrom(from);
|
||||
}
|
||||
|
||||
bool CommandPacket::IsInitialized() const {
|
||||
if ((_has_bits_[0] & 0x00000001) != 0x00000001) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CommandPacket::Swap(CommandPacket* other) {
|
||||
if (other != this) {
|
||||
std::swap(type_, other->type_);
|
||||
std::swap(value_, other->value_);
|
||||
std::swap(_has_bits_[0], other->_has_bits_[0]);
|
||||
std::swap(_cached_size_, other->_cached_size_);
|
||||
}
|
||||
}
|
||||
|
||||
::std::string CommandPacket::GetTypeName() const {
|
||||
return "mozilla.layers.layerscope.CommandPacket";
|
||||
}
|
||||
|
||||
|
||||
// @@protoc_insertion_point(namespace_scope)
|
||||
|
||||
} // namespace layerscope
|
||||
|
|
|
@ -45,6 +45,7 @@ class LayersPacket_Layer_Matrix;
|
|||
class LayersPacket_Layer_Shadow;
|
||||
class MetaPacket;
|
||||
class Packet;
|
||||
class CommandPacket;
|
||||
|
||||
enum LayersPacket_Layer_LayerType {
|
||||
LayersPacket_Layer_LayerType_UnknownLayer = 0,
|
||||
|
@ -98,6 +99,16 @@ const Packet_DataType Packet_DataType_DataType_MIN = Packet_DataType_FRAMESTART;
|
|||
const Packet_DataType Packet_DataType_DataType_MAX = Packet_DataType_META;
|
||||
const int Packet_DataType_DataType_ARRAYSIZE = Packet_DataType_DataType_MAX + 1;
|
||||
|
||||
enum CommandPacket_CmdType {
|
||||
CommandPacket_CmdType_NO_OP = 0,
|
||||
CommandPacket_CmdType_LAYERS_TREE = 1,
|
||||
CommandPacket_CmdType_LAYERS_BUFFER = 2
|
||||
};
|
||||
bool CommandPacket_CmdType_IsValid(int value);
|
||||
const CommandPacket_CmdType CommandPacket_CmdType_CmdType_MIN = CommandPacket_CmdType_NO_OP;
|
||||
const CommandPacket_CmdType CommandPacket_CmdType_CmdType_MAX = CommandPacket_CmdType_LAYERS_BUFFER;
|
||||
const int CommandPacket_CmdType_CmdType_ARRAYSIZE = CommandPacket_CmdType_CmdType_MAX + 1;
|
||||
|
||||
// ===================================================================
|
||||
|
||||
class FramePacket : public ::google::protobuf::MessageLite {
|
||||
|
@ -1439,6 +1450,99 @@ class Packet : public ::google::protobuf::MessageLite {
|
|||
void InitAsDefaultInstance();
|
||||
static Packet* default_instance_;
|
||||
};
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
class CommandPacket : public ::google::protobuf::MessageLite {
|
||||
public:
|
||||
CommandPacket();
|
||||
virtual ~CommandPacket();
|
||||
|
||||
CommandPacket(const CommandPacket& from);
|
||||
|
||||
inline CommandPacket& operator=(const CommandPacket& from) {
|
||||
CopyFrom(from);
|
||||
return *this;
|
||||
}
|
||||
|
||||
static const CommandPacket& default_instance();
|
||||
|
||||
void Swap(CommandPacket* other);
|
||||
|
||||
// implements Message ----------------------------------------------
|
||||
|
||||
CommandPacket* New() const;
|
||||
void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from);
|
||||
void CopyFrom(const CommandPacket& from);
|
||||
void MergeFrom(const CommandPacket& from);
|
||||
void Clear();
|
||||
bool IsInitialized() const;
|
||||
|
||||
int ByteSize() const;
|
||||
bool MergePartialFromCodedStream(
|
||||
::google::protobuf::io::CodedInputStream* input);
|
||||
void SerializeWithCachedSizes(
|
||||
::google::protobuf::io::CodedOutputStream* output) const;
|
||||
int GetCachedSize() const { return _cached_size_; }
|
||||
private:
|
||||
void SharedCtor();
|
||||
void SharedDtor();
|
||||
void SetCachedSize(int size) const;
|
||||
public:
|
||||
|
||||
::std::string GetTypeName() const;
|
||||
|
||||
// nested types ----------------------------------------------------
|
||||
|
||||
typedef CommandPacket_CmdType CmdType;
|
||||
static const CmdType NO_OP = CommandPacket_CmdType_NO_OP;
|
||||
static const CmdType LAYERS_TREE = CommandPacket_CmdType_LAYERS_TREE;
|
||||
static const CmdType LAYERS_BUFFER = CommandPacket_CmdType_LAYERS_BUFFER;
|
||||
static inline bool CmdType_IsValid(int value) {
|
||||
return CommandPacket_CmdType_IsValid(value);
|
||||
}
|
||||
static const CmdType CmdType_MIN =
|
||||
CommandPacket_CmdType_CmdType_MIN;
|
||||
static const CmdType CmdType_MAX =
|
||||
CommandPacket_CmdType_CmdType_MAX;
|
||||
static const int CmdType_ARRAYSIZE =
|
||||
CommandPacket_CmdType_CmdType_ARRAYSIZE;
|
||||
|
||||
// accessors -------------------------------------------------------
|
||||
|
||||
// required .mozilla.layers.layerscope.CommandPacket.CmdType type = 1;
|
||||
inline bool has_type() const;
|
||||
inline void clear_type();
|
||||
static const int kTypeFieldNumber = 1;
|
||||
inline ::mozilla::layers::layerscope::CommandPacket_CmdType type() const;
|
||||
inline void set_type(::mozilla::layers::layerscope::CommandPacket_CmdType value);
|
||||
|
||||
// optional bool value = 2;
|
||||
inline bool has_value() const;
|
||||
inline void clear_value();
|
||||
static const int kValueFieldNumber = 2;
|
||||
inline bool value() const;
|
||||
inline void set_value(bool value);
|
||||
|
||||
// @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.CommandPacket)
|
||||
private:
|
||||
inline void set_has_type();
|
||||
inline void clear_has_type();
|
||||
inline void set_has_value();
|
||||
inline void clear_has_value();
|
||||
|
||||
int type_;
|
||||
bool value_;
|
||||
|
||||
mutable int _cached_size_;
|
||||
::google::protobuf::uint32 _has_bits_[(2 + 31) / 32];
|
||||
|
||||
friend void protobuf_AddDesc_LayerScopePacket_2eproto();
|
||||
friend void protobuf_AssignDesc_LayerScopePacket_2eproto();
|
||||
friend void protobuf_ShutdownFile_LayerScopePacket_2eproto();
|
||||
|
||||
void InitAsDefaultInstance();
|
||||
static CommandPacket* default_instance_;
|
||||
};
|
||||
// ===================================================================
|
||||
|
||||
|
||||
|
@ -2803,6 +2907,55 @@ inline ::mozilla::layers::layerscope::MetaPacket* Packet::release_meta() {
|
|||
return temp;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// CommandPacket
|
||||
|
||||
// required .mozilla.layers.layerscope.CommandPacket.CmdType type = 1;
|
||||
inline bool CommandPacket::has_type() const {
|
||||
return (_has_bits_[0] & 0x00000001u) != 0;
|
||||
}
|
||||
inline void CommandPacket::set_has_type() {
|
||||
_has_bits_[0] |= 0x00000001u;
|
||||
}
|
||||
inline void CommandPacket::clear_has_type() {
|
||||
_has_bits_[0] &= ~0x00000001u;
|
||||
}
|
||||
inline void CommandPacket::clear_type() {
|
||||
type_ = 0;
|
||||
clear_has_type();
|
||||
}
|
||||
inline ::mozilla::layers::layerscope::CommandPacket_CmdType CommandPacket::type() const {
|
||||
return static_cast< ::mozilla::layers::layerscope::CommandPacket_CmdType >(type_);
|
||||
}
|
||||
inline void CommandPacket::set_type(::mozilla::layers::layerscope::CommandPacket_CmdType value) {
|
||||
GOOGLE_DCHECK(::mozilla::layers::layerscope::CommandPacket_CmdType_IsValid(value));
|
||||
set_has_type();
|
||||
type_ = value;
|
||||
}
|
||||
|
||||
// optional bool value = 2;
|
||||
inline bool CommandPacket::has_value() const {
|
||||
return (_has_bits_[0] & 0x00000002u) != 0;
|
||||
}
|
||||
inline void CommandPacket::set_has_value() {
|
||||
_has_bits_[0] |= 0x00000002u;
|
||||
}
|
||||
inline void CommandPacket::clear_has_value() {
|
||||
_has_bits_[0] &= ~0x00000002u;
|
||||
}
|
||||
inline void CommandPacket::clear_value() {
|
||||
value_ = false;
|
||||
clear_has_value();
|
||||
}
|
||||
inline bool CommandPacket::value() const {
|
||||
return value_;
|
||||
}
|
||||
inline void CommandPacket::set_value(bool value) {
|
||||
set_has_value();
|
||||
value_ = value;
|
||||
}
|
||||
|
||||
|
||||
// @@protoc_insertion_point(namespace_scope)
|
||||
|
||||
|
|
|
@ -4,6 +4,9 @@ option optimize_for = LITE_RUNTIME;
|
|||
|
||||
package mozilla.layers.layerscope;
|
||||
|
||||
// ===============================
|
||||
// Server to Client messages
|
||||
// ===============================
|
||||
message FramePacket {
|
||||
optional uint64 value = 1;
|
||||
}
|
||||
|
@ -133,3 +136,17 @@ message Packet {
|
|||
optional LayersPacket layers = 5;
|
||||
optional MetaPacket meta = 6;
|
||||
}
|
||||
|
||||
|
||||
// ===============================
|
||||
// Client to Server messages
|
||||
// ===============================
|
||||
message CommandPacket {
|
||||
enum CmdType {
|
||||
NO_OP = 0;
|
||||
LAYERS_TREE = 1;
|
||||
LAYERS_BUFFER = 2;
|
||||
}
|
||||
required CmdType type = 1;
|
||||
optional bool value = 2;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/klog.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/resource.h>
|
||||
#include <time.h>
|
||||
|
@ -47,6 +48,7 @@
|
|||
#include "HalImpl.h"
|
||||
#include "HalLog.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/dom/battery/Constants.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/FileUtils.h"
|
||||
|
@ -628,28 +630,6 @@ namespace {
|
|||
/**
|
||||
* RAII class to help us remember to close file descriptors.
|
||||
*/
|
||||
const char *wakeLockFilename = "/sys/power/wake_lock";
|
||||
const char *wakeUnlockFilename = "/sys/power/wake_unlock";
|
||||
|
||||
template<ssize_t n>
|
||||
bool ReadFromFile(const char *filename, char (&buf)[n])
|
||||
{
|
||||
int fd = open(filename, O_RDONLY);
|
||||
ScopedClose autoClose(fd);
|
||||
if (fd < 0) {
|
||||
HAL_LOG("Unable to open file %s.", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t numRead = read(fd, buf, n);
|
||||
if (numRead < 0) {
|
||||
HAL_LOG("Error reading from file %s.", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
buf[std::min(numRead, n - 1)] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteToFile(const char *filename, const char *toWrite)
|
||||
{
|
||||
|
@ -775,6 +755,9 @@ static Monitor* sInternalLockCpuMonitor = nullptr;
|
|||
static void
|
||||
UpdateCpuSleepState()
|
||||
{
|
||||
const char *wakeLockFilename = "/sys/power/wake_lock";
|
||||
const char *wakeUnlockFilename = "/sys/power/wake_unlock";
|
||||
|
||||
sInternalLockCpuMonitor->AssertCurrentThreadOwns();
|
||||
bool allowed = sCpuSleepAllowed && !sInternalLockCpuCount;
|
||||
WriteToFile(allowed ? wakeUnlockFilename : wakeLockFilename, "gecko");
|
||||
|
@ -1286,7 +1269,6 @@ OomVictimLogger::Observe(
|
|||
lineTimestampFound = true;
|
||||
mLastLineChecked = lineTimestamp;
|
||||
}
|
||||
|
||||
|
||||
// Log interesting lines
|
||||
for (size_t i = 0; i < regex_count; i++) {
|
||||
|
@ -1312,6 +1294,261 @@ OomVictimLogger::Observe(
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a particular ProcessPriority, giving us easy access to the prefs that
|
||||
* are relevant to it.
|
||||
*
|
||||
* Creating a PriorityClass also ensures that the control group is created.
|
||||
*/
|
||||
class PriorityClass
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Create a PriorityClass for the given ProcessPriority. This implicitly
|
||||
* reads the relevant prefs and opens the cgroup.procs file of the relevant
|
||||
* control group caching its file descriptor for later use.
|
||||
*/
|
||||
PriorityClass(ProcessPriority aPriority);
|
||||
|
||||
/**
|
||||
* Closes the file descriptor for the cgroup.procs file of the associated
|
||||
* control group.
|
||||
*/
|
||||
~PriorityClass();
|
||||
|
||||
PriorityClass(const PriorityClass& aOther);
|
||||
PriorityClass& operator=(const PriorityClass& aOther);
|
||||
|
||||
ProcessPriority Priority()
|
||||
{
|
||||
return mPriority;
|
||||
}
|
||||
|
||||
int32_t OomScoreAdj()
|
||||
{
|
||||
return clamped<int32_t>(mOomScoreAdj, OOM_SCORE_ADJ_MIN, OOM_SCORE_ADJ_MAX);
|
||||
}
|
||||
|
||||
int32_t KillUnderKB()
|
||||
{
|
||||
return mKillUnderKB;
|
||||
}
|
||||
|
||||
nsCString CGroup()
|
||||
{
|
||||
return mGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a process to this priority class, this moves the process' PID into
|
||||
* the associated control group.
|
||||
*
|
||||
* @param aPid The PID of the process to be added.
|
||||
*/
|
||||
void AddProcess(int aPid);
|
||||
|
||||
private:
|
||||
ProcessPriority mPriority;
|
||||
int32_t mOomScoreAdj;
|
||||
int32_t mKillUnderKB;
|
||||
int mCGroupProcsFd;
|
||||
nsCString mGroup;
|
||||
|
||||
/**
|
||||
* Return a string that identifies where we can find the value of aPref
|
||||
* that's specific to mPriority. For example, we might return
|
||||
* "hal.processPriorityManager.gonk.FOREGROUND_HIGH.oomScoreAdjust".
|
||||
*/
|
||||
nsCString PriorityPrefName(const char* aPref)
|
||||
{
|
||||
return nsPrintfCString("hal.processPriorityManager.gonk.%s.%s",
|
||||
ProcessPriorityToString(mPriority), aPref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full path of the cgroup.procs file associated with the group.
|
||||
*/
|
||||
nsCString CGroupProcsFilename()
|
||||
{
|
||||
nsCString cgroupName = mGroup;
|
||||
|
||||
/* If mGroup is empty, our cgroup.procs file is the root procs file,
|
||||
* located at /dev/cpuctl/cgroup.procs. Otherwise our procs file is
|
||||
* /dev/cpuctl/NAME/cgroup.procs. */
|
||||
|
||||
if (!mGroup.IsEmpty()) {
|
||||
cgroupName.AppendLiteral("/");
|
||||
}
|
||||
|
||||
return NS_LITERAL_CSTRING("/dev/cpuctl/") + cgroupName +
|
||||
NS_LITERAL_CSTRING("cgroup.procs");
|
||||
}
|
||||
|
||||
int OpenCGroupProcs()
|
||||
{
|
||||
return open(CGroupProcsFilename().get(), O_WRONLY);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Try to create the cgroup for the given PriorityClass, if it doesn't already
|
||||
* exist. This essentially implements mkdir -p; that is, we create parent
|
||||
* cgroups as necessary. The group parameters are also set according to
|
||||
* the corresponding preferences.
|
||||
*
|
||||
* @param aGroup The name of the group.
|
||||
* @return true if we successfully created the cgroup, or if it already
|
||||
* exists. Otherwise, return false.
|
||||
*/
|
||||
static bool
|
||||
EnsureCGroupExists(const nsACString &aGroup)
|
||||
{
|
||||
NS_NAMED_LITERAL_CSTRING(kDevCpuCtl, "/dev/cpuctl/");
|
||||
NS_NAMED_LITERAL_CSTRING(kSlash, "/");
|
||||
|
||||
nsAutoCString prefPrefix("hal.processPriorityManager.gonk.cgroups.");
|
||||
|
||||
/* If cgroup is not empty, append the cgroup name and a dot to obtain the
|
||||
* group specific preferences. */
|
||||
if (!aGroup.IsEmpty()) {
|
||||
prefPrefix += aGroup + NS_LITERAL_CSTRING(".");
|
||||
}
|
||||
|
||||
nsAutoCString cpuSharesPref(prefPrefix + NS_LITERAL_CSTRING("cpu_shares"));
|
||||
int cpuShares = Preferences::GetInt(cpuSharesPref.get());
|
||||
|
||||
nsAutoCString cpuNotifyOnMigratePref(prefPrefix
|
||||
+ NS_LITERAL_CSTRING("cpu_notify_on_migrate"));
|
||||
int cpuNotifyOnMigrate = Preferences::GetInt(cpuNotifyOnMigratePref.get());
|
||||
|
||||
// Create mCGroup and its parent directories, as necessary.
|
||||
nsCString cgroupIter = aGroup + kSlash;
|
||||
|
||||
int32_t offset = 0;
|
||||
while ((offset = cgroupIter.FindChar('/', offset)) != -1) {
|
||||
nsAutoCString path = kDevCpuCtl + Substring(cgroupIter, 0, offset);
|
||||
int rv = mkdir(path.get(), 0744);
|
||||
|
||||
if (rv == -1 && errno != EEXIST) {
|
||||
HAL_LOG("Could not create the %s control group.", path.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
offset++;
|
||||
}
|
||||
|
||||
nsAutoCString pathPrefix(kDevCpuCtl + aGroup + kSlash);
|
||||
nsAutoCString cpuSharesPath(pathPrefix + NS_LITERAL_CSTRING("cpu.shares"));
|
||||
if (cpuShares && !WriteToFile(cpuSharesPath.get(),
|
||||
nsPrintfCString("%d", cpuShares).get())) {
|
||||
HAL_LOG("Could not set the cpu share for group %s", cpuSharesPath.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoCString notifyOnMigratePath(pathPrefix
|
||||
+ NS_LITERAL_CSTRING("cpu.notify_on_migrate"));
|
||||
if (!WriteToFile(notifyOnMigratePath.get(),
|
||||
nsPrintfCString("%d", cpuNotifyOnMigrate).get())) {
|
||||
HAL_LOG("Could not set the cpu migration notification flag for group %s",
|
||||
notifyOnMigratePath.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PriorityClass::PriorityClass(ProcessPriority aPriority)
|
||||
: mPriority(aPriority)
|
||||
, mOomScoreAdj(0)
|
||||
, mKillUnderKB(0)
|
||||
, mCGroupProcsFd(-1)
|
||||
{
|
||||
DebugOnly<nsresult> rv;
|
||||
|
||||
rv = Preferences::GetInt(PriorityPrefName("OomScoreAdjust").get(),
|
||||
&mOomScoreAdj);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv), "Missing oom_score_adj preference");
|
||||
|
||||
rv = Preferences::GetInt(PriorityPrefName("KillUnderKB").get(),
|
||||
&mKillUnderKB);
|
||||
|
||||
rv = Preferences::GetCString(PriorityPrefName("cgroup").get(), &mGroup);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv), "Missing control group preference");
|
||||
|
||||
if (EnsureCGroupExists(mGroup)) {
|
||||
mCGroupProcsFd = OpenCGroupProcs();
|
||||
}
|
||||
}
|
||||
|
||||
PriorityClass::~PriorityClass()
|
||||
{
|
||||
close(mCGroupProcsFd);
|
||||
}
|
||||
|
||||
PriorityClass::PriorityClass(const PriorityClass& aOther)
|
||||
: mPriority(aOther.mPriority)
|
||||
, mOomScoreAdj(aOther.mOomScoreAdj)
|
||||
, mKillUnderKB(aOther.mKillUnderKB)
|
||||
, mGroup(aOther.mGroup)
|
||||
{
|
||||
mCGroupProcsFd = OpenCGroupProcs();
|
||||
}
|
||||
|
||||
PriorityClass& PriorityClass::operator=(const PriorityClass& aOther)
|
||||
{
|
||||
mPriority = aOther.mPriority;
|
||||
mOomScoreAdj = aOther.mOomScoreAdj;
|
||||
mKillUnderKB = aOther.mKillUnderKB;
|
||||
mGroup = aOther.mGroup;
|
||||
mCGroupProcsFd = OpenCGroupProcs();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void PriorityClass::AddProcess(int aPid)
|
||||
{
|
||||
if (mCGroupProcsFd < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsPrintfCString str("%d", aPid);
|
||||
|
||||
if (write(mCGroupProcsFd, str.get(), strlen(str.get())) < 0) {
|
||||
HAL_ERR("Couldn't add PID %d to the %s control group", aPid, mGroup.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PriorityClass associated with the given ProcessPriority.
|
||||
*
|
||||
* If you pass an invalid ProcessPriority value, we return null.
|
||||
*
|
||||
* The pointers returned here are owned by GetPriorityClass (don't free them
|
||||
* yourself). They are guaranteed to stick around until shutdown.
|
||||
*/
|
||||
PriorityClass*
|
||||
GetPriorityClass(ProcessPriority aPriority)
|
||||
{
|
||||
static StaticAutoPtr<nsTArray<PriorityClass>> priorityClasses;
|
||||
|
||||
// Initialize priorityClasses if this is the first time we're running this
|
||||
// method.
|
||||
if (!priorityClasses) {
|
||||
priorityClasses = new nsTArray<PriorityClass>();
|
||||
ClearOnShutdown(&priorityClasses);
|
||||
|
||||
for (int32_t i = 0; i < NUM_PROCESS_PRIORITY; i++) {
|
||||
priorityClasses->AppendElement(PriorityClass(ProcessPriority(i)));
|
||||
}
|
||||
}
|
||||
|
||||
if (aPriority < 0 ||
|
||||
static_cast<uint32_t>(aPriority) >= priorityClasses->Length()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &(*priorityClasses)[aPriority];
|
||||
}
|
||||
|
||||
static void
|
||||
EnsureKernelLowMemKillerParamsSet()
|
||||
{
|
||||
|
@ -1352,21 +1589,12 @@ EnsureKernelLowMemKillerParamsSet()
|
|||
// The system doesn't function correctly if we're missing these prefs, so
|
||||
// crash loudly.
|
||||
|
||||
ProcessPriority priority = static_cast<ProcessPriority>(i);
|
||||
PriorityClass* pc = GetPriorityClass(static_cast<ProcessPriority>(i));
|
||||
|
||||
int32_t oomScoreAdj;
|
||||
if (!NS_SUCCEEDED(Preferences::GetInt(
|
||||
nsPrintfCString("hal.processPriorityManager.gonk.%s.OomScoreAdjust",
|
||||
ProcessPriorityToString(priority)).get(),
|
||||
&oomScoreAdj))) {
|
||||
MOZ_CRASH();
|
||||
}
|
||||
int32_t oomScoreAdj = pc->OomScoreAdj();
|
||||
int32_t killUnderKB = pc->KillUnderKB();
|
||||
|
||||
int32_t killUnderKB;
|
||||
if (!NS_SUCCEEDED(Preferences::GetInt(
|
||||
nsPrintfCString("hal.processPriorityManager.gonk.%s.KillUnderKB",
|
||||
ProcessPriorityToString(priority)).get(),
|
||||
&killUnderKB))) {
|
||||
if (killUnderKB == 0) {
|
||||
// ProcessPriority values like PROCESS_PRIORITY_FOREGROUND_KEYBOARD,
|
||||
// which has only OomScoreAdjust but lacks KillUnderMB value, will not
|
||||
// create new LMK parameters.
|
||||
|
@ -1397,7 +1625,8 @@ EnsureKernelLowMemKillerParamsSet()
|
|||
minfreeParams.Cut(minfreeParams.Length() - 1, 1);
|
||||
if (!adjParams.IsEmpty() && !minfreeParams.IsEmpty()) {
|
||||
WriteToFile("/sys/module/lowmemorykiller/parameters/adj", adjParams.get());
|
||||
WriteToFile("/sys/module/lowmemorykiller/parameters/minfree", minfreeParams.get());
|
||||
WriteToFile("/sys/module/lowmemorykiller/parameters/minfree",
|
||||
minfreeParams.get());
|
||||
}
|
||||
|
||||
// Set the low-memory-notification threshold.
|
||||
|
@ -1419,148 +1648,6 @@ EnsureKernelLowMemKillerParamsSet()
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
SetNiceForPid(int aPid, int aNice)
|
||||
{
|
||||
errno = 0;
|
||||
int origProcPriority = getpriority(PRIO_PROCESS, aPid);
|
||||
if (errno) {
|
||||
HAL_LOG("Unable to get nice for pid=%d; error %d. SetNiceForPid bailing.",
|
||||
aPid, errno);
|
||||
return;
|
||||
}
|
||||
|
||||
int rv = setpriority(PRIO_PROCESS, aPid, aNice);
|
||||
if (rv) {
|
||||
HAL_LOG("Unable to set nice for pid=%d; error %d. SetNiceForPid bailing.",
|
||||
aPid, errno);
|
||||
return;
|
||||
}
|
||||
|
||||
// On Linux, setpriority(aPid) modifies the priority only of the main
|
||||
// thread of that process. We have to modify the priorities of all of the
|
||||
// process's threads as well, so iterate over all the threads and increase
|
||||
// each of their priorites by aNice - origProcPriority (and also ensure that
|
||||
// none of the tasks has a lower priority than the main thread).
|
||||
//
|
||||
// This is horribly racy.
|
||||
|
||||
DIR* tasksDir = opendir(nsPrintfCString("/proc/%d/task/", aPid).get());
|
||||
if (!tasksDir) {
|
||||
HAL_LOG("Unable to open /proc/%d/task. SetNiceForPid bailing.", aPid);
|
||||
return;
|
||||
}
|
||||
|
||||
// Be careful not to leak tasksDir; after this point, we must call closedir().
|
||||
|
||||
while (struct dirent* de = readdir(tasksDir)) {
|
||||
char* endptr = nullptr;
|
||||
long tidlong = strtol(de->d_name, &endptr, /* base */ 10);
|
||||
if (*endptr || tidlong < 0 || tidlong > INT32_MAX || tidlong == aPid) {
|
||||
// if dp->d_name was not an integer, was negative (?!) or too large, or
|
||||
// was the same as aPid, we're not interested.
|
||||
//
|
||||
// (The |tidlong == aPid| check is very important; without it, we'll
|
||||
// renice aPid twice, and the second renice will be relative to the
|
||||
// priority set by the first renice.)
|
||||
continue;
|
||||
}
|
||||
|
||||
int tid = static_cast<int>(tidlong);
|
||||
|
||||
// Do not set the priority of threads running with a real-time policy
|
||||
// as part of the bulk process adjustment. These threads need to run
|
||||
// at their specified priority in order to meet timing guarantees.
|
||||
int schedPolicy = sched_getscheduler(tid);
|
||||
if (schedPolicy == SCHED_FIFO || schedPolicy == SCHED_RR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
// Get and set the task's new priority.
|
||||
int origtaskpriority = getpriority(PRIO_PROCESS, tid);
|
||||
if (errno) {
|
||||
HAL_LOG("Unable to get nice for tid=%d (pid=%d); error %d. This isn't "
|
||||
"necessarily a problem; it could be a benign race condition.",
|
||||
tid, aPid, errno);
|
||||
continue;
|
||||
}
|
||||
|
||||
int newtaskpriority =
|
||||
std::max(origtaskpriority - origProcPriority + aNice, aNice);
|
||||
|
||||
// Do not reduce priority of threads already running at priorities greater
|
||||
// than normal. These threads are likely special service threads that need
|
||||
// elevated priorities to process audio, display composition, etc.
|
||||
if (newtaskpriority > origtaskpriority &&
|
||||
origtaskpriority < ANDROID_PRIORITY_NORMAL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rv = setpriority(PRIO_PROCESS, tid, newtaskpriority);
|
||||
|
||||
if (rv) {
|
||||
HAL_LOG("Unable to set nice for tid=%d (pid=%d); error %d. This isn't "
|
||||
"necessarily a problem; it could be a benign race condition.",
|
||||
tid, aPid, errno);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
HAL_LOG("Changed nice for pid %d from %d to %d.",
|
||||
aPid, origProcPriority, aNice);
|
||||
|
||||
closedir(tasksDir);
|
||||
}
|
||||
|
||||
/*
|
||||
* Used to store the nice value adjustments and oom_adj values for the various
|
||||
* process priority levels.
|
||||
*/
|
||||
struct ProcessPriorityPrefs {
|
||||
bool initialized;
|
||||
int lowPriorityNice;
|
||||
struct {
|
||||
int nice;
|
||||
int oomScoreAdj;
|
||||
} priorities[NUM_PROCESS_PRIORITY];
|
||||
};
|
||||
|
||||
/*
|
||||
* Reads the preferences for the various process priority levels and sets up
|
||||
* watchers so that if they're dynamically changed the change is reflected on
|
||||
* the appropriate variables.
|
||||
*/
|
||||
void
|
||||
EnsureProcessPriorityPrefs(ProcessPriorityPrefs* prefs)
|
||||
{
|
||||
if (prefs->initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the preferences for process priority levels
|
||||
for (int i = PROCESS_PRIORITY_BACKGROUND; i < NUM_PROCESS_PRIORITY; i++) {
|
||||
ProcessPriority priority = static_cast<ProcessPriority>(i);
|
||||
|
||||
// Read the nice values
|
||||
const char* processPriorityStr = ProcessPriorityToString(priority);
|
||||
nsPrintfCString niceStr("hal.processPriorityManager.gonk.%s.Nice",
|
||||
processPriorityStr);
|
||||
Preferences::AddIntVarCache(&prefs->priorities[i].nice, niceStr.get());
|
||||
|
||||
// Read the oom_adj scores
|
||||
nsPrintfCString oomStr("hal.processPriorityManager.gonk.%s.OomScoreAdjust",
|
||||
processPriorityStr);
|
||||
Preferences::AddIntVarCache(&prefs->priorities[i].oomScoreAdj,
|
||||
oomStr.get());
|
||||
}
|
||||
|
||||
Preferences::AddIntVarCache(&prefs->lowPriorityNice,
|
||||
"hal.processPriorityManager.gonk.LowCPUNice");
|
||||
|
||||
prefs->initialized = true;
|
||||
}
|
||||
|
||||
void
|
||||
SetProcessPriority(int aPid,
|
||||
ProcessPriority aPriority,
|
||||
|
@ -1579,49 +1666,23 @@ SetProcessPriority(int aPid,
|
|||
// SetProcessPriority being called early in startup.
|
||||
EnsureKernelLowMemKillerParamsSet();
|
||||
|
||||
static ProcessPriorityPrefs prefs = { 0 };
|
||||
EnsureProcessPriorityPrefs(&prefs);
|
||||
PriorityClass* pc = GetPriorityClass(aPriority);
|
||||
|
||||
int oomScoreAdj = prefs.priorities[aPriority].oomScoreAdj;
|
||||
int oomScoreAdj = pc->OomScoreAdj();
|
||||
|
||||
RoundOomScoreAdjUpWithBackroundLRU(oomScoreAdj, aBackgroundLRU);
|
||||
|
||||
int clampedOomScoreAdj = clamped<int>(oomScoreAdj, OOM_SCORE_ADJ_MIN,
|
||||
OOM_SCORE_ADJ_MAX);
|
||||
if (clampedOomScoreAdj != oomScoreAdj) {
|
||||
HAL_LOG("Clamping OOM adjustment for pid %d to %d", aPid,
|
||||
clampedOomScoreAdj);
|
||||
} else {
|
||||
HAL_LOG("Setting OOM adjustment for pid %d to %d", aPid,
|
||||
clampedOomScoreAdj);
|
||||
}
|
||||
|
||||
// We try the newer interface first, and fall back to the older interface
|
||||
// on failure.
|
||||
|
||||
if (!WriteToFile(nsPrintfCString("/proc/%d/oom_score_adj", aPid).get(),
|
||||
nsPrintfCString("%d", clampedOomScoreAdj).get()))
|
||||
nsPrintfCString("%d", oomScoreAdj).get()))
|
||||
{
|
||||
int oomAdj = OomAdjOfOomScoreAdj(clampedOomScoreAdj);
|
||||
|
||||
WriteToFile(nsPrintfCString("/proc/%d/oom_adj", aPid).get(),
|
||||
nsPrintfCString("%d", oomAdj).get());
|
||||
nsPrintfCString("%d", OomAdjOfOomScoreAdj(oomScoreAdj)).get());
|
||||
}
|
||||
|
||||
int nice = 0;
|
||||
|
||||
if (aCPUPriority == PROCESS_CPU_PRIORITY_NORMAL) {
|
||||
nice = prefs.priorities[aPriority].nice;
|
||||
} else if (aCPUPriority == PROCESS_CPU_PRIORITY_LOW) {
|
||||
nice = prefs.lowPriorityNice;
|
||||
} else {
|
||||
HAL_ERR("Unknown aCPUPriority value %d", aCPUPriority);
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
HAL_LOG("Setting nice for pid %d to %d", aPid, nice);
|
||||
SetNiceForPid(aPid, nice);
|
||||
HAL_LOG("Assigning pid %d to cgroup %s", aPid, pc->CGroup().get());
|
||||
pc->AddProcess(aPid);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
|
|
@ -809,16 +809,24 @@ js::atomics_futexWait(JSContext *cx, unsigned argc, Value *vp)
|
|||
case JS::PerRuntimeFutexAPI::Timedout:
|
||||
r.setInt32(AtomicsObject::FutexTimedout);
|
||||
break;
|
||||
case JS::PerRuntimeFutexAPI::ErrorTooLong:
|
||||
// This is a hack, but it's serviceable.
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_ATOMICS_TOO_LONG);
|
||||
case JS::PerRuntimeFutexAPI::ErrorException:
|
||||
MOZ_ASSERT(JS_IsExceptionPending(cx));
|
||||
retval = false;
|
||||
break;
|
||||
case JS::PerRuntimeFutexAPI::InterruptForTerminate:
|
||||
// Throw an uncatchable exception.
|
||||
JS_ClearPendingException(cx);
|
||||
retval = false;
|
||||
break;
|
||||
case JS::PerRuntimeFutexAPI::WaitingNotAllowed:
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_ATOMICS_WAIT_NOT_ALLOWED);
|
||||
retval = false;
|
||||
break;
|
||||
case JS::PerRuntimeFutexAPI::ErrorTooLong:
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_ATOMICS_TOO_LONG);
|
||||
retval = false;
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
if (w.lower_pri == &w) {
|
||||
|
|
|
@ -191,7 +191,7 @@ ParseEvalStringAsJSON(JSContext *cx, const mozilla::Range<const CharT> chars, Mu
|
|||
}
|
||||
|
||||
static EvalJSONResult
|
||||
TryEvalJSON(JSContext *cx, JSFlatString *str, MutableHandleValue rval)
|
||||
TryEvalJSON(JSContext *cx, JSLinearString *str, MutableHandleValue rval)
|
||||
{
|
||||
if (str->hasLatin1Chars()) {
|
||||
AutoCheckCannotGC nogc;
|
||||
|
@ -203,13 +203,13 @@ TryEvalJSON(JSContext *cx, JSFlatString *str, MutableHandleValue rval)
|
|||
return EvalJSON_NotJSON;
|
||||
}
|
||||
|
||||
AutoStableStringChars flatChars(cx);
|
||||
if (!flatChars.init(cx, str))
|
||||
AutoStableStringChars linearChars(cx);
|
||||
if (!linearChars.init(cx, str))
|
||||
return EvalJSON_Failure;
|
||||
|
||||
return flatChars.isLatin1()
|
||||
? ParseEvalStringAsJSON(cx, flatChars.latin1Range(), rval)
|
||||
: ParseEvalStringAsJSON(cx, flatChars.twoByteRange(), rval);
|
||||
return linearChars.isLatin1()
|
||||
? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
|
||||
: ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
|
||||
}
|
||||
|
||||
// Define subset of ExecuteType so that casting performs the injection.
|
||||
|
@ -277,19 +277,19 @@ EvalKernel(JSContext *cx, const CallArgs &args, EvalType evalType, AbstractFrame
|
|||
thisv = ObjectValue(*thisobj);
|
||||
}
|
||||
|
||||
Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
|
||||
if (!flatStr)
|
||||
RootedLinearString linearStr(cx, str->ensureLinear(cx));
|
||||
if (!linearStr)
|
||||
return false;
|
||||
|
||||
RootedScript callerScript(cx, caller ? caller.script() : nullptr);
|
||||
EvalJSONResult ejr = TryEvalJSON(cx, flatStr, args.rval());
|
||||
EvalJSONResult ejr = TryEvalJSON(cx, linearStr, args.rval());
|
||||
if (ejr != EvalJSON_NotJSON)
|
||||
return ejr == EvalJSON_Success;
|
||||
|
||||
EvalScriptGuard esg(cx);
|
||||
|
||||
if (evalType == DIRECT_EVAL && caller.isNonEvalFunctionFrame())
|
||||
esg.lookupInEvalCache(flatStr, callerScript, pc);
|
||||
esg.lookupInEvalCache(linearStr, callerScript, pc);
|
||||
|
||||
if (!esg.foundScript()) {
|
||||
RootedScript maybeScript(cx);
|
||||
|
@ -323,18 +323,18 @@ EvalKernel(JSContext *cx, const CallArgs &args, EvalType evalType, AbstractFrame
|
|||
.setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset)
|
||||
.maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc));
|
||||
|
||||
AutoStableStringChars flatChars(cx);
|
||||
if (!flatChars.initTwoByte(cx, flatStr))
|
||||
AutoStableStringChars linearChars(cx);
|
||||
if (!linearChars.initTwoByte(cx, linearStr))
|
||||
return false;
|
||||
|
||||
const char16_t *chars = flatChars.twoByteRange().start().get();
|
||||
SourceBufferHolder::Ownership ownership = flatChars.maybeGiveOwnershipToCaller()
|
||||
const char16_t *chars = linearChars.twoByteRange().start().get();
|
||||
SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
|
||||
? SourceBufferHolder::GiveOwnership
|
||||
: SourceBufferHolder::NoOwnership;
|
||||
SourceBufferHolder srcBuf(chars, flatStr->length(), ownership);
|
||||
SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
|
||||
JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
|
||||
scopeobj, callerScript, staticScope,
|
||||
options, srcBuf, flatStr, staticLevel);
|
||||
options, srcBuf, linearStr, staticLevel);
|
||||
if (!compiled)
|
||||
return false;
|
||||
|
||||
|
@ -366,17 +366,17 @@ js::DirectEvalStringFromIon(JSContext *cx,
|
|||
|
||||
unsigned staticLevel = callerScript->staticLevel() + 1;
|
||||
|
||||
Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
|
||||
if (!flatStr)
|
||||
RootedLinearString linearStr(cx, str->ensureLinear(cx));
|
||||
if (!linearStr)
|
||||
return false;
|
||||
|
||||
EvalJSONResult ejr = TryEvalJSON(cx, flatStr, vp);
|
||||
EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
|
||||
if (ejr != EvalJSON_NotJSON)
|
||||
return ejr == EvalJSON_Success;
|
||||
|
||||
EvalScriptGuard esg(cx);
|
||||
|
||||
esg.lookupInEvalCache(flatStr, callerScript, pc);
|
||||
esg.lookupInEvalCache(linearStr, callerScript, pc);
|
||||
|
||||
if (!esg.foundScript()) {
|
||||
RootedScript maybeScript(cx);
|
||||
|
@ -405,18 +405,18 @@ js::DirectEvalStringFromIon(JSContext *cx,
|
|||
.setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset)
|
||||
.maybeMakeStrictMode(IsStrictEvalPC(pc));
|
||||
|
||||
AutoStableStringChars flatChars(cx);
|
||||
if (!flatChars.initTwoByte(cx, flatStr))
|
||||
AutoStableStringChars linearChars(cx);
|
||||
if (!linearChars.initTwoByte(cx, linearStr))
|
||||
return false;
|
||||
|
||||
const char16_t *chars = flatChars.twoByteRange().start().get();
|
||||
SourceBufferHolder::Ownership ownership = flatChars.maybeGiveOwnershipToCaller()
|
||||
const char16_t *chars = linearChars.twoByteRange().start().get();
|
||||
SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
|
||||
? SourceBufferHolder::GiveOwnership
|
||||
: SourceBufferHolder::NoOwnership;
|
||||
SourceBufferHolder srcBuf(chars, flatStr->length(), ownership);
|
||||
SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
|
||||
JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
|
||||
scopeobj, callerScript, staticScope,
|
||||
options, srcBuf, flatStr, staticLevel);
|
||||
options, srcBuf, linearStr, staticLevel);
|
||||
if (!compiled)
|
||||
return false;
|
||||
|
||||
|
|
|
@ -1226,6 +1226,11 @@ MapObject::construct(JSContext *cx, unsigned argc, Value *vp)
|
|||
return false;
|
||||
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// FIXME: bug 1083752
|
||||
if (!WarnIfNotConstructing(cx, args, "Map"))
|
||||
return false;
|
||||
|
||||
if (!args.get(0).isNullOrUndefined()) {
|
||||
RootedValue adderVal(cx);
|
||||
if (!GetProperty(cx, obj, obj, cx->names().set, &adderVal))
|
||||
|
@ -1870,6 +1875,11 @@ SetObject::construct(JSContext *cx, unsigned argc, Value *vp)
|
|||
return false;
|
||||
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// FIXME: bug 1083752
|
||||
if (!WarnIfNotConstructing(cx, args, "Set"))
|
||||
return false;
|
||||
|
||||
if (!args.get(0).isNullOrUndefined()) {
|
||||
RootedValue adderVal(cx);
|
||||
if (!GetProperty(cx, obj, obj, cx->names().add, &adderVal))
|
||||
|
|
|
@ -97,16 +97,16 @@ function markGCType(typeName, child, why, depth, ptrdness)
|
|||
|
||||
if (depth == 0) {
|
||||
if (!(typeName in gcTypes))
|
||||
gcTypes[typeName] = Set();
|
||||
gcTypes[typeName] = new Set();
|
||||
gcTypes[typeName].add(why);
|
||||
} else if (depth == 1) {
|
||||
if (!(typeName in gcPointers))
|
||||
gcPointers[typeName] = Set();
|
||||
gcPointers[typeName] = new Set();
|
||||
gcPointers[typeName].add(why);
|
||||
}
|
||||
|
||||
if (!(typeName in gcFields))
|
||||
gcFields[typeName] = Map();
|
||||
gcFields[typeName] = new Map();
|
||||
gcFields[typeName].set(why, [ child, ptrdness ]);
|
||||
|
||||
if (typeName in structureParents) {
|
||||
|
@ -148,7 +148,7 @@ addGCPointer('JS::AutoCheckCannotGC');
|
|||
|
||||
function explain(csu, indent, seen) {
|
||||
if (!seen)
|
||||
seen = Set();
|
||||
seen = new Set();
|
||||
seen.add(csu);
|
||||
if (!(csu in gcFields))
|
||||
return;
|
||||
|
|
|
@ -82,7 +82,7 @@ const Census = {};
|
|||
function makeBasisChecker({compare, missing, extra}) {
|
||||
return function makeWalker(basis) {
|
||||
if (typeof basis === 'object') {
|
||||
var unvisited = Set(Object.getOwnPropertyNames(basis));
|
||||
var unvisited = new Set(Object.getOwnPropertyNames(basis));
|
||||
return {
|
||||
enter: prop => {
|
||||
unvisited.delete(prop);
|
||||
|
|
|
@ -5,4 +5,5 @@
|
|||
//
|
||||
|
||||
Array.prototype.iterator = (function() { { while(0) function Uint8ClampedArray() { } } });
|
||||
assertEq(Set(["testing", "testing", 123]).size(), 2);
|
||||
var s = new Set(["testing", "testing", 123]);
|
||||
assertEq(s.size(), 2);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// |jit-test| error: TypeError
|
||||
var set = Set(['a']);
|
||||
var set = new Set(['a']);
|
||||
var n = 5;
|
||||
for (let v of set) {
|
||||
if (n === 0)
|
||||
break;
|
||||
let g = set(Set(0xffffffff, n), 1);
|
||||
let g = set(new Set(0xffffffff, n), 1);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
pSandbox = newGlobal();
|
||||
evalcx("\
|
||||
x = ArrayBuffer;\
|
||||
y = Map();\
|
||||
y = new Map();\
|
||||
x += 1;\
|
||||
w = x;\
|
||||
x += '0';\
|
||||
|
|
|
@ -19,8 +19,8 @@ assertEqArray([...[undefined]], [undefined]);
|
|||
assertEqArray([...new Int32Array([1, 2, 3])], [1, 2, 3]);
|
||||
assertEqArray([..."abc"], ["a", "b", "c"]);
|
||||
assertEqArray([...[1, 2, 3][Symbol.iterator]()], [1, 2, 3]);
|
||||
assertEqArray([...Set([1, 2, 3])], [1, 2, 3]);
|
||||
assertEqArray([...Map([["a", "A"], ["b", "B"], ["c", "C"]])].map(([k, v]) => k + v), ["aA", "bB", "cC"]);
|
||||
assertEqArray([...new Set([1, 2, 3])], [1, 2, 3]);
|
||||
assertEqArray([...new Map([["a", "A"], ["b", "B"], ["c", "C"]])].map(([k, v]) => k + v), ["aA", "bB", "cC"]);
|
||||
let itr = {};
|
||||
itr[Symbol.iterator] = function () {
|
||||
return {
|
||||
|
|
|
@ -25,7 +25,7 @@ try { // line0 + 1
|
|||
|
||||
// other iterable objects
|
||||
assertEq(eval(...["a + b"][Symbol.iterator]()), 11);
|
||||
assertEq(eval(...Set(["a + b"])), 11);
|
||||
assertEq(eval(...new Set(["a + b"])), 11);
|
||||
let itr = {};
|
||||
itr[Symbol.iterator] = function() {
|
||||
return {
|
||||
|
|
|
@ -9,7 +9,7 @@ function checkCommon(f) {
|
|||
assertEqArray(f.apply(...[null, [1, 2, 3]]), [1, 2, 3]);
|
||||
|
||||
// other iterable objects
|
||||
assertEqArray(f.apply(...Set([null, [1, 2, 3]])), [1, 2, 3]);
|
||||
assertEqArray(f.apply(...new Set([null, [1, 2, 3]])), [1, 2, 3]);
|
||||
assertEqArray(f.apply(...[null, [1, 2, 3]][Symbol.iterator]()), [1, 2, 3]);
|
||||
let itr = {};
|
||||
itr[Symbol.iterator] = function() {
|
||||
|
@ -81,7 +81,7 @@ function checkRest(f) {
|
|||
assertEqArray(f.apply(null, ...[[undefined]]), [undefined]);
|
||||
|
||||
// other iterable objects
|
||||
assertEqArray(f.apply(null, ...Map([[["a", "A"], ["b", "B"]]])).map(([k, v]) => k + v), ["aA", "bB"]);
|
||||
assertEqArray(f.apply(null, ...new Map([[["a", "A"], ["b", "B"]]])).map(([k, v]) => k + v), ["aA", "bB"]);
|
||||
}
|
||||
|
||||
checkRest(function(...x) x);
|
||||
|
|
|
@ -22,8 +22,8 @@ function checkLength(f, makeFn) {
|
|||
assertEq(makeFn("...arg")(f, new Int32Array([1, 2, 3])), 3);
|
||||
assertEq(makeFn("...arg")(f, "abc"), 3);
|
||||
assertEq(makeFn("...arg")(f, [1, 2, 3][Symbol.iterator]()), 3);
|
||||
assertEq(makeFn("...arg")(f, Set([1, 2, 3])), 3);
|
||||
assertEq(makeFn("...arg")(f, Map([["a", "A"], ["b", "B"], ["c", "C"]])), 3);
|
||||
assertEq(makeFn("...arg")(f, new Set([1, 2, 3])), 3);
|
||||
assertEq(makeFn("...arg")(f, new Map([["a", "A"], ["b", "B"], ["c", "C"]])), 3);
|
||||
let itr = {};
|
||||
itr[Symbol.iterator] = function() {
|
||||
return {
|
||||
|
|
|
@ -17,8 +17,8 @@ function checkCommon(f, makeFn) {
|
|||
assertEqArray(makeFn("...arg")(f, new Int32Array([1, 2, 3])), [1, 2, 3]);
|
||||
assertEqArray(makeFn("...arg")(f, "abc"), ["a", "b", "c"]);
|
||||
assertEqArray(makeFn("...arg")(f, [1, 2, 3][Symbol.iterator]()), [1, 2, 3]);
|
||||
assertEqArray(makeFn("...arg")(f, Set([1, 2, 3])), [1, 2, 3]);
|
||||
assertEqArray(makeFn("...arg")(f, Map([["a", "A"], ["b", "B"], ["c", "C"]])).map(([k, v]) => k + v), ["aA", "bB", "cC"]);
|
||||
assertEqArray(makeFn("...arg")(f, new Set([1, 2, 3])), [1, 2, 3]);
|
||||
assertEqArray(makeFn("...arg")(f, new Map([["a", "A"], ["b", "B"], ["c", "C"]])).map(([k, v]) => k + v), ["aA", "bB", "cC"]);
|
||||
let itr = {};
|
||||
itr[Symbol.iterator] = function() {
|
||||
return {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Clearing an empty Map has no effect.
|
||||
|
||||
var m = Map();
|
||||
var m = new Map();
|
||||
for (var i = 0; i < 2; i++) {
|
||||
m.clear();
|
||||
assertEq(m.size, 0);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Clearing a Map removes its entries; the Map remains usable afterwards.
|
||||
|
||||
var m = Map([["a", "b"], ["b", "c"]]);
|
||||
var m = new Map([["a", "b"], ["b", "c"]]);
|
||||
assertEq(m.size, 2);
|
||||
m.clear();
|
||||
assertEq(m.size, 0);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Clearing a Map with a nontrivial number of elements works.
|
||||
|
||||
var m = Map();
|
||||
var m = new Map();
|
||||
for (var i = 0; i < 100; i++)
|
||||
m.set(i, i);
|
||||
assertEq(m.size, i);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Clearing a Map after deleting some entries works.
|
||||
|
||||
var m = Map([["a", 1], ["b", 2], ["c", 3], ["d", 4]]);
|
||||
var m = new Map([["a", 1], ["b", 2], ["c", 3], ["d", 4]]);
|
||||
for (var [k, v] of m)
|
||||
if (k !== "c")
|
||||
m.delete(k);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Map.clear is unaffected by deleting/monkeypatching Map.prototype.{delete,iterator}.
|
||||
|
||||
var data = [["a", 1], ["b", 2]];
|
||||
var m1 = Map(data), m2 = Map(data);
|
||||
var m1 = new Map(data), m2 = new Map(data);
|
||||
|
||||
delete Map.prototype.delete;
|
||||
delete Map.prototype.iterator;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Clearing a Map doesn't affect expando properties.
|
||||
|
||||
var m = Map();
|
||||
var m = new Map();
|
||||
m.x = 3;
|
||||
m.clear();
|
||||
assertEq(m.x, 3);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
load(libdir + "referencesVia.js");
|
||||
|
||||
var m = Map();
|
||||
var m = new Map();
|
||||
var k = {}, v = {};
|
||||
m.set(k, v);
|
||||
assertEq(referencesVia(m, "key", k), true);
|
||||
|
|
|
@ -2,19 +2,19 @@
|
|||
|
||||
load(libdir + "iteration.js");
|
||||
|
||||
var m = Map();
|
||||
var m = new Map();
|
||||
var it = m[Symbol.iterator]();
|
||||
m.clear();
|
||||
assertIteratorDone(it, undefined);
|
||||
|
||||
m = Map([["a", 1], ["b", 2], ["c", 3], ["d", 4]]);
|
||||
m = new Map([["a", 1], ["b", 2], ["c", 3], ["d", 4]]);
|
||||
it = m[Symbol.iterator]();
|
||||
assertIteratorNext(it, ["a", 1]);
|
||||
m.clear();
|
||||
assertIteratorDone(it, undefined);
|
||||
|
||||
var log = "";
|
||||
m = Map([["a", 1], ["b", 2], ["c", 3], ["d", 4]]);
|
||||
m = new Map([["a", 1], ["b", 2], ["c", 3], ["d", 4]]);
|
||||
for (var [k, v] of m) {
|
||||
log += k + v;
|
||||
if (k == "b")
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
load(libdir + "asserts.js");
|
||||
load(libdir + "iteration.js");
|
||||
|
||||
var m = Map([["a", 1]]);
|
||||
var m = new Map([["a", 1]]);
|
||||
var it = m[Symbol.iterator]();
|
||||
assertIteratorNext(it, ["a", 1]);
|
||||
m.clear();
|
||||
|
|
|
@ -1,7 +1,17 @@
|
|||
// The Map constructor creates an empty Map by default.
|
||||
|
||||
assertEq(Map().size, 0);
|
||||
assertEq((new Map).size, 0);
|
||||
assertEq(Map(undefined).size, 0);
|
||||
assertEq(new Map(undefined).size, 0);
|
||||
assertEq(new Map(null).size, 0);
|
||||
load(libdir + "asserts.js");
|
||||
|
||||
var m = new Map();
|
||||
assertEq(m.size, 0);
|
||||
m = new Map(undefined);
|
||||
assertEq(m.size, 0);
|
||||
m = new Map(null);
|
||||
assertEq(m.size, 0);
|
||||
|
||||
// FIXME: bug 1083752
|
||||
options("werror");
|
||||
assertEq(evaluate("Map()", {catchTermination: true}), "terminated");
|
||||
// assertThrowsInstanceOf(() => Map(), TypeError);
|
||||
// assertThrowsInstanceOf(() => Map(undefined), TypeError);
|
||||
// assertThrowsInstanceOf(() => Map(null), TypeError);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// The Map constructor can take an argument that is an array of pairs.
|
||||
|
||||
var arr = [["zero", 0], ["one", 1], ["two", 2]];
|
||||
var m = Map(arr);
|
||||
var m = new Map(arr);
|
||||
for (var [k, v] of arr)
|
||||
assertEq(m.get(k), v);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Map can take an argument that is an array of singleton arrays.
|
||||
|
||||
var arr = [["a"], ["b"], ["c"]];
|
||||
var m = Map(arr);
|
||||
var m = new Map(arr);
|
||||
assertEq(m.size, 3);
|
||||
for (var [k, _] of arr) {
|
||||
assertEq(m.has(k), true);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Map(x) throws if x is not iterable (unless x is undefined).
|
||||
// new Map(x) throws if x is not iterable (unless x is undefined).
|
||||
|
||||
load(libdir + "asserts.js");
|
||||
var nonIterables = [true, 1, -0, 3.14, NaN, {}, Math, this];
|
||||
for (let k of nonIterables)
|
||||
assertThrowsInstanceOf(function () { Map(k); }, TypeError);
|
||||
assertThrowsInstanceOf(function () { new Map(k); }, TypeError);
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
// Map(arr) throws if arr contains holes (or undefined values).
|
||||
// new Map(arr) throws if arr contains holes (or undefined values).
|
||||
|
||||
load(libdir + "asserts.js");
|
||||
assertThrowsInstanceOf(function () { Map([undefined]); }, TypeError);
|
||||
assertThrowsInstanceOf(function () { Map([null]); }, TypeError);
|
||||
assertThrowsInstanceOf(function () { Map([[0, 0], [1, 1], , [3, 3]]); }, TypeError);
|
||||
assertThrowsInstanceOf(function () { Map([[0, 0], [1, 1], ,]); }, TypeError);
|
||||
assertThrowsInstanceOf(function () { new Map([undefined]); }, TypeError);
|
||||
assertThrowsInstanceOf(function () { new Map([null]); }, TypeError);
|
||||
assertThrowsInstanceOf(function () { new Map([[0, 0], [1, 1], , [3, 3]]); }, TypeError);
|
||||
assertThrowsInstanceOf(function () { new Map([[0, 0], [1, 1], ,]); }, TypeError);
|
||||
|
||||
// Map(iterable) throws if iterable doesn't have array-like objects
|
||||
// new Map(iterable) throws if iterable doesn't have array-like objects
|
||||
|
||||
assertThrowsInstanceOf(function () { Map([1, 2, 3]); }, TypeError);
|
||||
assertThrowsInstanceOf(function () { new Map([1, 2, 3]); }, TypeError);
|
||||
assertThrowsInstanceOf(function () {
|
||||
let s = new Set([1, 2, "abc"]);
|
||||
new Map(s);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// When the argument to Map contains a key multiple times, the last value is retained.
|
||||
|
||||
var arg = [["zero", 7], ["one", 1], ["two", 4], ["zero", 8], ["two", 2], ["zero", 0]];
|
||||
var m = Map(arg);
|
||||
var m = new Map(arg);
|
||||
assertEq(m.get("zero"), 0);
|
||||
assertEq(m.get("one"), 1);
|
||||
assertEq(m.get("two"), 2);
|
||||
|
|
|
@ -10,7 +10,7 @@ function data(n) {
|
|||
done = true;
|
||||
}
|
||||
|
||||
var m = Map(data(50));
|
||||
var m = new Map(data(50));
|
||||
assertEq(done, true); // the constructor consumes the argument
|
||||
assertEq(m.size, 50);
|
||||
assertEq(m.get(""), 0);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// The argument to Map can be a generator-expression.
|
||||
|
||||
var arr = [1, 2, "green", "red"];
|
||||
var m = Map([v, v] for (v of arr));
|
||||
var m = new Map([v, v] for (v of arr));
|
||||
assertEq(m.size, 4);
|
||||
|
||||
for (var i = 0; i < 4; i++)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// The argument to Map may be a generator-iterator that produces no values.
|
||||
|
||||
assertEq(Map(x for (x of [])).size, 0);
|
||||
var m = new Map(x for (x of []));
|
||||
assertEq(m.size, 0);
|
||||
|
||||
function none() {
|
||||
if (0) yield 0;
|
||||
}
|
||||
assertEq(Map(none()).size, 0);
|
||||
m = new Map(none());
|
||||
assertEq(m.size, 0);
|
||||
|
|
|
@ -9,4 +9,4 @@ function data2() {
|
|||
}
|
||||
|
||||
var it = data2();
|
||||
assertThrowsValue(function () { Map(it); }, "oops");
|
||||
assertThrowsValue(function () { new Map(it); }, "oops");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// map.delete(k) decrements the map size iff an entry was actually removed.
|
||||
|
||||
var m = Map();
|
||||
var m = new Map();
|
||||
m.delete(3);
|
||||
assertEq(m.size, 0);
|
||||
m.set({}, 'ok');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// for-of can be used to iterate over a Map twice.
|
||||
|
||||
var map = Map([['a', 0], ['b', 1], ['c', 2]]);
|
||||
var map = new Map([['a', 0], ['b', 1], ['c', 2]]);
|
||||
var log = '';
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Nested for-of loops can iterate over a Map.
|
||||
|
||||
var map = Map([['a', 0], ['b', 1]]);
|
||||
var map = new Map([['a', 0], ['b', 1]]);
|
||||
var log = '';
|
||||
for (let [k0, v0] of map) {
|
||||
log += k0 + v0 + ':'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// map.iterator() is live: entries added during iteration are visited.
|
||||
|
||||
var map = Map();
|
||||
var map = new Map();
|
||||
function force(k) {
|
||||
if (!map.has(k) && k >= 0)
|
||||
map.set(k, k - 1);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
load(libdir + "iteration.js");
|
||||
|
||||
var map = Map();
|
||||
var map = new Map();
|
||||
var iter0 = map[Symbol.iterator](), iter1 = map[Symbol.iterator]();
|
||||
assertIteratorDone(iter0, undefined); // closes iter0
|
||||
map.set(1, 2);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Removing and re-adding entries while an iterator is live causes the iterator to visit them again.
|
||||
|
||||
var map = Map([['a', 1]]);
|
||||
var map = new Map([['a', 1]]);
|
||||
var n = 5;
|
||||
for (let [k, v] of map) {
|
||||
assertEq(k, 'a');
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
load(libdir + "eqArrayHelper.js");
|
||||
|
||||
var map = Map();
|
||||
var map = new Map();
|
||||
for (var i = 7; i !== 1; i = i * 7 % 1117)
|
||||
map.set("" + i, i);
|
||||
assertEq(map.size, 557);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
load(libdir + "iteration.js");
|
||||
|
||||
var key = {};
|
||||
var map = Map([[key, 'value']]);
|
||||
var map = new Map([[key, 'value']]);
|
||||
var entry = map[Symbol.iterator]().next().value;
|
||||
assertEq(Array.isArray(entry), true);
|
||||
assertEq(Object.getPrototypeOf(entry), Array.prototype);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
load(libdir + "iteration.js");
|
||||
|
||||
var map = Map([['a', 1], ['b', 2]]);
|
||||
var map = new Map([['a', 1], ['b', 2]]);
|
||||
var iter = map[Symbol.iterator]();
|
||||
var a = iter.next(), b = iter.next();
|
||||
assertIteratorResult(a, ['a', 1], false);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
load(libdir + "iteration.js");
|
||||
|
||||
var map = Map([['a', 1]]);
|
||||
var map = new Map([['a', 1]]);
|
||||
var res = map[Symbol.iterator]().next();
|
||||
assertIteratorResult(res, ['a', 1], false);
|
||||
res.value[0] = 'b';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// for-of works on a cross-compartment wrapper of a Map.
|
||||
|
||||
var g = newGlobal();
|
||||
var mw = g.eval("Map([['a', 1], ['b', 2]])");
|
||||
var mw = g.eval("new Map([['a', 1], ['b', 2]])");
|
||||
var log = '';
|
||||
for (let [k, v] of mw)
|
||||
log += k + v;
|
||||
|
|
|
@ -8,13 +8,13 @@ var g = newGlobal();
|
|||
|
||||
var iterator_fn = Map.prototype[Symbol.iterator];
|
||||
assertThrowsInstanceOf(function () { iterator_fn.call({}); }, TypeError);
|
||||
assertThrowsInstanceOf(function () { iterator_fn.call(Set()); }, TypeError);
|
||||
var mapw = g.eval("Map([['x', 1], ['y', 2]])");
|
||||
assertThrowsInstanceOf(function () { iterator_fn.call(new Set()); }, TypeError);
|
||||
var mapw = g.eval("new Map([['x', 1], ['y', 2]])");
|
||||
assertEqArray(iterator_fn.call(mapw).next().value, ["x", 1]);
|
||||
|
||||
var next_fn = Map()[Symbol.iterator]().next;
|
||||
var next_fn = (new Map())[Symbol.iterator]().next;
|
||||
assertThrowsInstanceOf(function () { next_fn.call({}); }, TypeError);
|
||||
assertThrowsInstanceOf(function () { next_fn.call(Set()[Symbol.iterator]()); }, TypeError);
|
||||
assertThrowsInstanceOf(function () { next_fn.call((new Set())[Symbol.iterator]()); }, TypeError);
|
||||
var iterw = mapw[Symbol.iterator]();
|
||||
assertEqArray(next_fn.call(iterw).value, ["x", 1]);
|
||||
assertEqArray(next_fn.call(iterw).value, ["y", 2]);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
function test(pairs) {
|
||||
print(uneval(pairs));
|
||||
var map = Map(pairs);
|
||||
var map = new Map(pairs);
|
||||
|
||||
var all_keys = '';
|
||||
var false_keys = '';
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
load(libdir + "iteration.js");
|
||||
|
||||
var map = Map([['a', 0], ['b', 1], ['c', 2], ['d', 3]]);
|
||||
var map = new Map([['a', 0], ['b', 1], ['c', 2], ['d', 3]]);
|
||||
var iter = map[Symbol.iterator]();
|
||||
var log = '';
|
||||
for (let [k, v] of iter) {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
load(libdir + "asserts.js");
|
||||
load(libdir + "iteration.js");
|
||||
|
||||
var map = Map([['a', 0], ['b', 1], ['c', 2], ['d', 3]]);
|
||||
var map = new Map([['a', 0], ['b', 1], ['c', 2], ['d', 3]]);
|
||||
var iter = map[Symbol.iterator]();
|
||||
assertIteratorNext(iter, ['a', 0]);
|
||||
assertIteratorNext(iter, ['b', 1]);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
load(libdir + "iteration.js");
|
||||
|
||||
// Make a map.
|
||||
var map = Map();
|
||||
var map = new Map();
|
||||
var SIZE = 7;
|
||||
for (var j = 0; j < SIZE; j++)
|
||||
map.set(j, j);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Removing a Map entry already visited by an iterator does not cause any
|
||||
// entries to be skipped.
|
||||
|
||||
var map = Map();
|
||||
var map = new Map();
|
||||
for (var i = 0; i < 20; i++)
|
||||
map.set(String.fromCharCode('A'.charCodeAt(0) + i), i);
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
load(libdir + "iteration.js");
|
||||
|
||||
var map = Map();
|
||||
var map = new Map();
|
||||
for (var i = 0; i < 32; i++)
|
||||
map.set(i, i);
|
||||
var iter = map[Symbol.iterator]();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
load(libdir + "iteration.js");
|
||||
|
||||
var m = Map();
|
||||
var m = new Map();
|
||||
var it = m[Symbol.iterator]();
|
||||
assertIteratorDone(it, undefined); // close the iterator
|
||||
m.clear();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// map.set(k, v) increments the map size iff map didn't already have an entry for k.
|
||||
|
||||
var m = Map();
|
||||
var m = new Map();
|
||||
m.set('a', 0);
|
||||
assertEq(m.size, 1);
|
||||
m.set('a', 0);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Each Map has its own size.
|
||||
|
||||
var m1 = Map(), m2 = Map();
|
||||
var m1 = new Map(), m2 = new Map();
|
||||
m1.set("x", 3);
|
||||
assertEq(m1.size, 1);
|
||||
assertEq(m2.size, 0);
|
||||
|
|
|
@ -14,8 +14,7 @@ assertEq(Map.name, "Map");
|
|||
|
||||
assertEq(Object.getPrototypeOf(Map.prototype), Object.prototype);
|
||||
assertEq(Object.prototype.toString.call(Map.prototype), "[object Map]");
|
||||
assertEq(Object.prototype.toString.call(new Map), "[object Map]");
|
||||
assertEq(Object.prototype.toString.call(Map()), "[object Map]");
|
||||
assertEq(Object.prototype.toString.call(new Map()), "[object Map]");
|
||||
assertEq(Object.keys(Map.prototype).join(), "");
|
||||
assertEq(Map.prototype.constructor, Map);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// map.keys(), .values(), and .entries() on an empty map produce empty iterators.
|
||||
|
||||
var m = Map();
|
||||
var m = new Map();
|
||||
var ki = m.keys(), vi = m.values(), ei = m.entries();
|
||||
var p = Object.getPrototypeOf(ki)
|
||||
assertEq(Object.getPrototypeOf(vi), p);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
load(libdir + "iteration.js");
|
||||
|
||||
var data = [["one", 1], ["two", 2], ["three", 3], ["four", 4]];
|
||||
var m = Map(data);
|
||||
var m = new Map(data);
|
||||
|
||||
var ki = m.keys();
|
||||
assertIteratorNext(ki, "one");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// set.add(v) increments set.size iff the set did not already contain v.
|
||||
|
||||
var s = Set();
|
||||
var s = new Set();
|
||||
for (var i = 0; i < 10; i++) {
|
||||
assertEq(s.size, i);
|
||||
s.add(i);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Clearing an empty Set has no effect.
|
||||
|
||||
var s = Set();
|
||||
var s = new Set();
|
||||
for (var i = 0; i < 2; i++) {
|
||||
s.clear();
|
||||
assertEq(s.size, 0);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Clearing a Set removes its elements; the Set remains usable afterwards.
|
||||
|
||||
var s = Set(["x", "y", "z", "z", "y"]);
|
||||
var s = new Set(["x", "y", "z", "z", "y"]);
|
||||
assertEq(s.size, 3);
|
||||
s.clear();
|
||||
assertEq(s.size, 0);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Clearing a Set with a nontrivial number of elements works.
|
||||
|
||||
var s = Set();
|
||||
var s = new Set();
|
||||
for (var i = 0; i < 100; i++)
|
||||
s.add(i);
|
||||
assertEq(s.size, i);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Clearing a Set after deleting some entries works.
|
||||
|
||||
var s = Set(["a", "b", "c", "d"]);
|
||||
var s = new Set(["a", "b", "c", "d"]);
|
||||
for (var v of s)
|
||||
if (v !== "c")
|
||||
s.delete(v);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Set.clear is unaffected by deleting/monkeypatching Set.prototype.{delete,iterator}.
|
||||
|
||||
var data = ["a", 1, {}];
|
||||
var s1 = Set(data), s2 = Set(data);
|
||||
var s1 = new Set(data), s2 = new Set(data);
|
||||
|
||||
delete Set.prototype.delete;
|
||||
delete Set.prototype.iterator;
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче