mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge a PGO-green inbound changeset to m-c.
This commit is contained in:
commit
6017a8e2ad
@ -189,6 +189,10 @@ var shell = {
|
||||
window.removeEventListener('sizemodechange', this);
|
||||
this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true);
|
||||
ppmm.removeMessageListener("content-handler", this);
|
||||
if (this.timer) {
|
||||
this.timer.cancel();
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
#ifndef MOZ_WIDGET_GONK
|
||||
delete Services.audioManager;
|
||||
@ -254,8 +258,11 @@ var shell = {
|
||||
this.sendChromeEvent({type: type});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
lastHardwareButtonEventType: null, // property for the hack above
|
||||
needBufferSysMsgs: true,
|
||||
bufferedSysMsgs: [],
|
||||
timer: null,
|
||||
|
||||
handleEvent: function shell_handleEvent(evt) {
|
||||
let content = this.contentBrowser.contentWindow;
|
||||
@ -340,6 +347,18 @@ var shell = {
|
||||
ObjectWrapper.wrap(details, getContentWindow()));
|
||||
},
|
||||
|
||||
sendSystemMessage: function shell_sendSystemMessage(msg) {
|
||||
let origin = Services.io.newURI(msg.manifest, null, null).prePath;
|
||||
this.sendChromeEvent({
|
||||
type: 'open-app',
|
||||
url: msg.uri,
|
||||
origin: origin,
|
||||
manifest: msg.manifest,
|
||||
isActivity: (msg.type == 'activity'),
|
||||
target: msg.target
|
||||
});
|
||||
},
|
||||
|
||||
receiveMessage: function shell_receiveMessage(message) {
|
||||
if (message.name != 'content-handler') {
|
||||
return;
|
||||
@ -389,15 +408,13 @@ nsBrowserAccess.prototype = {
|
||||
// Listen for system messages and relay them to Gaia.
|
||||
Services.obs.addObserver(function onSystemMessage(subject, topic, data) {
|
||||
let msg = JSON.parse(data);
|
||||
let origin = Services.io.newURI(msg.manifest, null, null).prePath;
|
||||
shell.sendChromeEvent({
|
||||
type: 'open-app',
|
||||
url: msg.uri,
|
||||
origin: origin,
|
||||
manifest: msg.manifest,
|
||||
isActivity: (msg.type == 'activity'),
|
||||
target: msg.target
|
||||
});
|
||||
// Buffer non-activity messages until content starts to load for 10 seconds.
|
||||
// We'll revisit this later if new kind of messages don't need to be cached.
|
||||
if (shell.needBufferSysMsgs && msg.type !== 'activity') {
|
||||
shell.bufferedSysMsgs.push(msg);
|
||||
return;
|
||||
}
|
||||
shell.sendSystemMessage(msg);
|
||||
}, 'system-messages-open-app', false);
|
||||
|
||||
Services.obs.addObserver(function(aSubject, aTopic, aData) {
|
||||
@ -461,6 +478,18 @@ var CustomEventManager = {
|
||||
window.addEventListener("ContentStart", (function(evt) {
|
||||
let content = shell.contentBrowser.contentWindow;
|
||||
content.addEventListener("mozContentEvent", this, false, true);
|
||||
|
||||
// After content starts to load for 10 seconds, send and
|
||||
// clean up the buffered system messages if there is any.
|
||||
shell.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
shell.timer.initWithCallback(function timerCallback() {
|
||||
shell.bufferedSysMsgs.forEach(function sendSysMsg(msg) {
|
||||
shell.sendSystemMessage(msg);
|
||||
});
|
||||
shell.bufferedSysMsgs.length = 0;
|
||||
shell.needBufferSysMsgs = false;
|
||||
shell.timer = null;
|
||||
}, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}).bind(this), false);
|
||||
},
|
||||
|
||||
|
@ -274,6 +274,7 @@
|
||||
<panel id="social-flyout-panel"
|
||||
onpopupshown="SocialFlyout.onShown()"
|
||||
onpopuphidden="SocialFlyout.onHidden()"
|
||||
side="right"
|
||||
type="arrow"
|
||||
hidden="true"
|
||||
noautofocus="true"
|
||||
|
@ -1066,8 +1066,8 @@ nsEventListenerManager::HasUnloadListeners()
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsEventListenerManager::SetEventHandlerToJsval(nsIAtom *aEventName,
|
||||
JSContext *cx,
|
||||
nsEventListenerManager::SetEventHandlerToJsval(nsIAtom* aEventName,
|
||||
JSContext* cx,
|
||||
JSObject* aScope,
|
||||
const jsval& v,
|
||||
bool aExpectScriptContext)
|
||||
|
@ -272,8 +272,8 @@ public:
|
||||
* be in the same compartment. If aExpectScriptContext is false,
|
||||
* not finding an nsIScriptContext does not cause failure.
|
||||
*/
|
||||
nsresult SetEventHandlerToJsval(nsIAtom *aEventName, JSContext *cx,
|
||||
JSObject *aScope, const jsval &v,
|
||||
nsresult SetEventHandlerToJsval(nsIAtom* aEventName, JSContext* cx,
|
||||
JSObject* aScope, const jsval& v,
|
||||
bool aExpectScriptContext);
|
||||
/**
|
||||
* Get the value of the "inline" event listener for aEventName.
|
||||
|
25
content/mathml/content/crashtests/770710-1.html
Normal file
25
content/mathml/content/crashtests/770710-1.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
|
||||
function boom()
|
||||
{
|
||||
var mo = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mo");
|
||||
var text = document.createTextNode(" ");
|
||||
mo.appendChild(text);
|
||||
document.body.appendChild(mo);
|
||||
|
||||
window.getSelection().removeAllRanges();
|
||||
var r = document.createRange();
|
||||
r.setStart(mo.firstChild, 0);
|
||||
r.setEnd(mo.firstChild, 1);
|
||||
window.getSelection().addRange(r);
|
||||
|
||||
window.getSelection().toString();
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="boom();"></body>
|
||||
</html>
|
@ -1 +1,2 @@
|
||||
load 462929-1.html
|
||||
load 770710-1.html
|
||||
|
@ -989,9 +989,23 @@ DOMApplicationManifest.prototype = {
|
||||
},
|
||||
|
||||
fullLaunchPath: function(aStartPoint) {
|
||||
let startPoint = aStartPoint || "";
|
||||
let launchPath = this._localeProp("launch_path") || "";
|
||||
return this._origin.resolve(launchPath + startPoint);
|
||||
// If no start point is specified, we use the root launch path.
|
||||
// In all error cases, we just return null.
|
||||
if ((aStartPoint || "") === "") {
|
||||
return this._origin.resolve(this._localeProp("launch_path") || "");
|
||||
}
|
||||
|
||||
// Search for the l10n entry_points property.
|
||||
let entryPoints = this._localeProp("entry_points");
|
||||
if (!entryPoints) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (entryPoints[aStartPoint]) {
|
||||
return this._origin.resolve(entryPoints[aStartPoint].launch_path || "");
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
resolveFromOrigin: function(aURI) {
|
||||
|
@ -28,7 +28,7 @@ public:
|
||||
|
||||
nsresult Init(nsPIDOMWindow* aWindow, const nsAString &aType);
|
||||
|
||||
void SetRootFileForType(const nsAString& aType);
|
||||
void SetRootDirectoryForType(const nsAString& aType);
|
||||
|
||||
static void CreateDeviceStoragesFor(nsPIDOMWindow* aWin,
|
||||
const nsAString &aType,
|
||||
@ -50,15 +50,15 @@ private:
|
||||
bool aEditable,
|
||||
nsIDOMDeviceStorageCursor** aRetval);
|
||||
|
||||
int32_t mStorageType;
|
||||
nsCOMPtr<nsIFile> mFile;
|
||||
nsString mStorageType;
|
||||
nsCOMPtr<nsIFile> mRootDirectory;
|
||||
|
||||
nsCOMPtr<nsIPrincipal> mPrincipal;
|
||||
|
||||
bool mIsWatchingFile;
|
||||
bool mAllowedToWatchFile;
|
||||
|
||||
nsresult Notify(const char* aReason, nsIFile* aFile);
|
||||
nsresult Notify(const char* aReason, class DeviceStorageFile* aFile);
|
||||
|
||||
friend class WatchFileEvent;
|
||||
friend class DeviceStorageRequest;
|
||||
|
@ -83,7 +83,7 @@ DeviceStorageRequestChild::Recv__delete__(const DeviceStorageResponseValue& aVal
|
||||
continue;
|
||||
}
|
||||
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(r.paths()[i].type(), f);
|
||||
dsf->SetPath(r.paths()[i].name());
|
||||
cursor->mFiles.AppendElement(dsf);
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ DeviceStorageRequestParent::DeviceStorageRequestParent(const DeviceStorageParams
|
||||
nsCOMPtr<nsIFile> f;
|
||||
NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
|
||||
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(p.type(), f);
|
||||
|
||||
BlobParent* bp = static_cast<BlobParent*>(p.blobParent());
|
||||
nsCOMPtr<nsIDOMBlob> blob = bp->GetBlob();
|
||||
@ -53,7 +53,7 @@ DeviceStorageRequestParent::DeviceStorageRequestParent(const DeviceStorageParams
|
||||
nsCOMPtr<nsIFile> f;
|
||||
NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
|
||||
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(p.type(), f);
|
||||
dsf->SetPath(p.name());
|
||||
nsRefPtr<CancelableRunnable> r = new ReadFileEvent(this, dsf);
|
||||
|
||||
@ -70,7 +70,7 @@ DeviceStorageRequestParent::DeviceStorageRequestParent(const DeviceStorageParams
|
||||
nsCOMPtr<nsIFile> f;
|
||||
NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
|
||||
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(p.type(), f);
|
||||
nsRefPtr<CancelableRunnable> r = new DeleteFileEvent(this, dsf);
|
||||
|
||||
nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
|
||||
@ -86,7 +86,7 @@ DeviceStorageRequestParent::DeviceStorageRequestParent(const DeviceStorageParams
|
||||
nsCOMPtr<nsIFile> f;
|
||||
NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
|
||||
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(p.type(), f);
|
||||
nsRefPtr<StatFileEvent> r = new StatFileEvent(this, dsf);
|
||||
|
||||
nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
|
||||
@ -102,7 +102,7 @@ DeviceStorageRequestParent::DeviceStorageRequestParent(const DeviceStorageParams
|
||||
nsCOMPtr<nsIFile> f;
|
||||
NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
|
||||
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(p.type(), f);
|
||||
nsRefPtr<CancelableRunnable> r = new EnumerateFileEvent(this, dsf, p.since());
|
||||
|
||||
nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
|
||||
@ -312,14 +312,15 @@ DeviceStorageRequestParent::StatFileEvent::CancelableRun()
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
nsCOMPtr<nsIRunnable> r;
|
||||
uint64_t diskUsage = DeviceStorageFile::DirectoryDiskUsage(mFile->mFile);
|
||||
int64_t freeSpace;
|
||||
uint64_t diskUsage = 0;
|
||||
DeviceStorageFile::DirectoryDiskUsage(mFile->mFile, &diskUsage);
|
||||
int64_t freeSpace = 0;
|
||||
nsresult rv = mFile->mFile->GetDiskSpaceAvailable(&freeSpace);
|
||||
if (NS_FAILED(rv)) {
|
||||
freeSpace = 0;
|
||||
}
|
||||
|
||||
r = new PostStatResultEvent(mParent, diskUsage, freeSpace);
|
||||
r = new PostStatResultEvent(mParent, freeSpace, diskUsage);
|
||||
NS_DispatchToMainThread(r);
|
||||
return NS_OK;
|
||||
}
|
||||
@ -406,7 +407,7 @@ DeviceStorageRequestParent::EnumerateFileEvent::CancelableRun()
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
nsString fullpath;
|
||||
files[i]->mFile->GetPath(fullpath);
|
||||
DeviceStorageFileValue dsvf(files[i]->mPath, fullpath);
|
||||
DeviceStorageFileValue dsvf(mFile->mStorageType, files[i]->mPath, fullpath);
|
||||
values.AppendElement(dsvf);
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ struct BlobResponse
|
||||
|
||||
struct DeviceStorageFileValue
|
||||
{
|
||||
nsString type;
|
||||
nsString name;
|
||||
nsString fullpath;
|
||||
};
|
||||
|
@ -36,6 +36,8 @@
|
||||
#include "nsIObserverService.h"
|
||||
#include "GeneratedEvents.h"
|
||||
#include "mozilla/dom/PermissionMessageUtils.h"
|
||||
#include "nsIMIMEService.h"
|
||||
#include "nsCExternalHandlerService.h"
|
||||
|
||||
// Microsoft's API Name hackery sucks
|
||||
#undef CreateEvent
|
||||
@ -45,6 +47,12 @@
|
||||
#include "nsIVolumeService.h"
|
||||
#endif
|
||||
|
||||
#define DEBUG_ISTYPE 1
|
||||
|
||||
#ifdef DEBUG_ISTYPE
|
||||
#include "nsIConsoleService.h"
|
||||
#endif
|
||||
|
||||
using namespace mozilla::dom;
|
||||
using namespace mozilla::dom::devicestorage;
|
||||
|
||||
@ -53,7 +61,7 @@ using namespace mozilla::dom::devicestorage;
|
||||
class IOEventComplete : public nsRunnable
|
||||
{
|
||||
public:
|
||||
IOEventComplete(nsIFile *aFile, const char *aType)
|
||||
IOEventComplete(DeviceStorageFile *aFile, const char *aType)
|
||||
: mFile(aFile)
|
||||
, mType(aType)
|
||||
{
|
||||
@ -67,17 +75,18 @@ public:
|
||||
nsString data;
|
||||
CopyASCIItoUTF16(mType, data);
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
obs->NotifyObservers(mFile, "file-watcher-update", data.get());
|
||||
obs->NotifyObservers(mFile, "file-watcher-update", data.get());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsIFile> mFile;
|
||||
nsRefPtr<DeviceStorageFile> mFile;
|
||||
nsCString mType;
|
||||
};
|
||||
|
||||
DeviceStorageFile::DeviceStorageFile(nsIFile* aFile, const nsAString& aPath)
|
||||
DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType, nsIFile* aFile, const nsAString& aPath)
|
||||
: mPath(aPath)
|
||||
, mStorageType(aStorageType)
|
||||
, mEditable(false)
|
||||
{
|
||||
NS_ASSERTION(aFile, "Must not create a DeviceStorageFile with a null nsIFile");
|
||||
@ -89,8 +98,9 @@ DeviceStorageFile::DeviceStorageFile(nsIFile* aFile, const nsAString& aPath)
|
||||
NormalizeFilePath();
|
||||
}
|
||||
|
||||
DeviceStorageFile::DeviceStorageFile(nsIFile* aFile)
|
||||
: mEditable(false)
|
||||
DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType, nsIFile* aFile)
|
||||
: mStorageType(aStorageType)
|
||||
, mEditable(false)
|
||||
{
|
||||
NS_ASSERTION(aFile, "Must not create a DeviceStorageFile with a null nsIFile");
|
||||
// always take a clone
|
||||
@ -140,6 +150,75 @@ DeviceStorageFile::IsSafePath()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
DeviceStorageFile::IsType(nsAString& aType)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
// in testing, we ignore filtering for the testing types
|
||||
if (mozilla::Preferences::GetBool("device.storage.testing", false) &&
|
||||
(aType.Equals(NS_LITERAL_STRING("testing")) ||
|
||||
aType.Equals(NS_LITERAL_STRING("testing-other")))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ISTYPE
|
||||
nsCOMPtr<nsIConsoleService> svc = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
|
||||
char buffer[1024];
|
||||
nsCString path;
|
||||
mFile->GetNativePath(path);
|
||||
|
||||
PRIntervalTime iStart = PR_IntervalNow();
|
||||
#endif
|
||||
|
||||
nsCAutoString mimeType;
|
||||
nsCOMPtr<nsIMIMEService> mimeService = do_GetService(NS_MIMESERVICE_CONTRACTID);
|
||||
if (!mimeService) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsresult rv = mimeService->GetTypeFromFile(mFile, mimeType);
|
||||
if (NS_FAILED(rv)) {
|
||||
#ifdef DEBUG_ISTYPE
|
||||
sprintf(buffer, "GetTypeFromFile failed for %s (took: %dms)\n",
|
||||
path.get(),
|
||||
PR_IntervalToMilliseconds(PR_IntervalNow() - iStart));
|
||||
|
||||
nsString data;
|
||||
CopyASCIItoUTF16(buffer, data);
|
||||
svc->LogStringMessage(data.get());
|
||||
printf("%s\n", buffer);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ISTYPE
|
||||
sprintf(buffer, "IsType of %s is %s (took: %dms)\n",
|
||||
path.get(),
|
||||
mimeType.get(),
|
||||
PR_IntervalToMilliseconds(PR_IntervalNow() - iStart));
|
||||
|
||||
nsString data;
|
||||
CopyASCIItoUTF16(buffer, data);
|
||||
svc->LogStringMessage(data.get());
|
||||
printf("%s\n", buffer);
|
||||
#endif
|
||||
|
||||
if (aType.Equals(NS_LITERAL_STRING("pictures"))) {
|
||||
return StringBeginsWith(mimeType, NS_LITERAL_CSTRING("image/"));
|
||||
}
|
||||
|
||||
if (aType.Equals(NS_LITERAL_STRING("videos"))) {
|
||||
return StringBeginsWith(mimeType, NS_LITERAL_CSTRING("video/"));
|
||||
}
|
||||
|
||||
if (aType.Equals(NS_LITERAL_STRING("music"))) {
|
||||
return StringBeginsWith(mimeType, NS_LITERAL_CSTRING("audio/"));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
DeviceStorageFile::NormalizeFilePath() {
|
||||
#if defined(XP_WIN)
|
||||
@ -185,8 +264,8 @@ DeviceStorageFile::Write(nsIInputStream* aInputStream)
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<IOEventComplete> iocomplete = new IOEventComplete(mFile, "created");
|
||||
NS_DispatchToMainThread(iocomplete);
|
||||
nsCOMPtr<IOEventComplete> iocomplete = new IOEventComplete(this, "created");
|
||||
NS_DispatchToMainThread(iocomplete);
|
||||
|
||||
uint64_t bufSize = 0;
|
||||
aInputStream->Available(&bufSize);
|
||||
@ -219,7 +298,7 @@ DeviceStorageFile::Write(nsIInputStream* aInputStream)
|
||||
bufSize -= wrote;
|
||||
}
|
||||
|
||||
iocomplete = new IOEventComplete(mFile, "modified");
|
||||
iocomplete = new IOEventComplete(this, "modified");
|
||||
NS_DispatchToMainThread(iocomplete);
|
||||
|
||||
bufferedOutputStream->Close();
|
||||
@ -238,7 +317,7 @@ DeviceStorageFile::Write(InfallibleTArray<uint8_t>& aBits) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<IOEventComplete> iocomplete = new IOEventComplete(mFile, "created");
|
||||
nsCOMPtr<IOEventComplete> iocomplete = new IOEventComplete(this, "created");
|
||||
NS_DispatchToMainThread(iocomplete);
|
||||
|
||||
nsCOMPtr<nsIOutputStream> outputStream;
|
||||
@ -252,7 +331,7 @@ DeviceStorageFile::Write(InfallibleTArray<uint8_t>& aBits) {
|
||||
outputStream->Write((char*) aBits.Elements(), aBits.Length(), &wrote);
|
||||
outputStream->Close();
|
||||
|
||||
iocomplete = new IOEventComplete(mFile, "modified");
|
||||
iocomplete = new IOEventComplete(this, "modified");
|
||||
NS_DispatchToMainThread(iocomplete);
|
||||
|
||||
if (aBits.Length() != wrote) {
|
||||
@ -281,7 +360,7 @@ DeviceStorageFile::Remove()
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<IOEventComplete> iocomplete = new IOEventComplete(mFile, "deleted");
|
||||
nsCOMPtr<IOEventComplete> iocomplete = new IOEventComplete(this, "deleted");
|
||||
NS_DispatchToMainThread(iocomplete);
|
||||
return NS_OK;
|
||||
}
|
||||
@ -344,22 +423,22 @@ DeviceStorageFile::collectFilesInternal(nsTArray<nsRefPtr<DeviceStorageFile> > &
|
||||
nsDependentSubstring newPath = Substring(fullpath, len);
|
||||
|
||||
if (isDir) {
|
||||
DeviceStorageFile dsf(f);
|
||||
DeviceStorageFile dsf(mStorageType, f);
|
||||
dsf.SetPath(newPath);
|
||||
dsf.collectFilesInternal(aFiles, aSince, aRootPath);
|
||||
} else if (isFile) {
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, f);
|
||||
dsf->SetPath(newPath);
|
||||
aFiles.AppendElement(dsf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t
|
||||
DeviceStorageFile::DirectoryDiskUsage(nsIFile* aFile, uint64_t aSoFar)
|
||||
void
|
||||
DeviceStorageFile::DirectoryDiskUsage(nsIFile* aFile, uint64_t* aSoFar)
|
||||
{
|
||||
if (!aFile) {
|
||||
return aSoFar;
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
@ -367,7 +446,7 @@ DeviceStorageFile::DirectoryDiskUsage(nsIFile* aFile, uint64_t aSoFar)
|
||||
rv = aFile->GetDirectoryEntries(getter_AddRefs(e));
|
||||
|
||||
if (NS_FAILED(rv) || !e) {
|
||||
return aSoFar;
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
|
||||
@ -392,21 +471,19 @@ DeviceStorageFile::DirectoryDiskUsage(nsIFile* aFile, uint64_t aSoFar)
|
||||
if (NS_FAILED(rv)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isLink) {
|
||||
// for now, lets just totally ignore symlinks.
|
||||
NS_WARNING("DirectoryDiskUsage ignores symlinks");
|
||||
} else if (isDir) {
|
||||
aSoFar += DirectoryDiskUsage(f, aSoFar);
|
||||
DirectoryDiskUsage(f, aSoFar);
|
||||
} else if (isFile) {
|
||||
int64_t size;
|
||||
rv = f->GetFileSize(&size);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
aSoFar += size;
|
||||
*aSoFar += size;
|
||||
}
|
||||
}
|
||||
}
|
||||
return aSoFar;
|
||||
}
|
||||
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS0(DeviceStorageFile)
|
||||
@ -456,7 +533,7 @@ UnregisterForSDCardChanges(nsIObserver* aObserver)
|
||||
#endif
|
||||
|
||||
void
|
||||
nsDOMDeviceStorage::SetRootFileForType(const nsAString& aType)
|
||||
nsDOMDeviceStorage::SetRootDirectoryForType(const nsAString& aType)
|
||||
{
|
||||
nsCOMPtr<nsIFile> f;
|
||||
nsCOMPtr<nsIProperties> dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
|
||||
@ -465,7 +542,7 @@ nsDOMDeviceStorage::SetRootFileForType(const nsAString& aType)
|
||||
// Picture directory
|
||||
if (aType.Equals(NS_LITERAL_STRING("pictures"))) {
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
NS_NewLocalFile(NS_LITERAL_STRING("/sdcard/DCIM"), false, getter_AddRefs(f));
|
||||
NS_NewLocalFile(NS_LITERAL_STRING("/sdcard"), false, getter_AddRefs(f));
|
||||
#elif defined (MOZ_WIDGET_COCOA)
|
||||
dirService->Get(NS_OSX_PICTURE_DOCUMENTS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
|
||||
#elif defined (XP_UNIX)
|
||||
@ -476,7 +553,7 @@ nsDOMDeviceStorage::SetRootFileForType(const nsAString& aType)
|
||||
// Video directory
|
||||
else if (aType.Equals(NS_LITERAL_STRING("videos"))) {
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
NS_NewLocalFile(NS_LITERAL_STRING("/sdcard/Movies"), false, getter_AddRefs(f));
|
||||
NS_NewLocalFile(NS_LITERAL_STRING("/sdcard"), false, getter_AddRefs(f));
|
||||
#elif defined (MOZ_WIDGET_COCOA)
|
||||
dirService->Get(NS_OSX_MOVIE_DOCUMENTS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
|
||||
#elif defined (XP_UNIX)
|
||||
@ -487,7 +564,7 @@ nsDOMDeviceStorage::SetRootFileForType(const nsAString& aType)
|
||||
// Music directory
|
||||
else if (aType.Equals(NS_LITERAL_STRING("music"))) {
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
NS_NewLocalFile(NS_LITERAL_STRING("/sdcard/Music"), false, getter_AddRefs(f));
|
||||
NS_NewLocalFile(NS_LITERAL_STRING("/sdcard"), false, getter_AddRefs(f));
|
||||
#elif defined (MOZ_WIDGET_COCOA)
|
||||
dirService->Get(NS_OSX_MUSIC_DOCUMENTS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
|
||||
#elif defined (XP_UNIX)
|
||||
@ -499,7 +576,8 @@ nsDOMDeviceStorage::SetRootFileForType(const nsAString& aType)
|
||||
if (mozilla::Preferences::GetBool("device.storage.testing", false)) {
|
||||
|
||||
// testing directory
|
||||
if (aType.Equals(NS_LITERAL_STRING("testing"))) {
|
||||
if (aType.Equals(NS_LITERAL_STRING("testing")) ||
|
||||
aType.Equals(NS_LITERAL_STRING("testing-other"))) {
|
||||
dirService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
|
||||
if (f) {
|
||||
f->AppendRelativeNativePath(NS_LITERAL_CSTRING("device-storage-testing"));
|
||||
@ -507,16 +585,7 @@ nsDOMDeviceStorage::SetRootFileForType(const nsAString& aType)
|
||||
f->Normalize();
|
||||
}
|
||||
}
|
||||
|
||||
if (aType.Equals(NS_LITERAL_STRING("testing-other"))) {
|
||||
dirService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
|
||||
if (f) {
|
||||
f->AppendRelativeNativePath(NS_LITERAL_CSTRING("device-storage-testing-other"));
|
||||
f->Create(nsIFile::DIRECTORY_TYPE, 0777);
|
||||
f->Normalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
RegisterForSDCardChanges(this);
|
||||
@ -524,7 +593,8 @@ nsDOMDeviceStorage::SetRootFileForType(const nsAString& aType)
|
||||
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
obs->AddObserver(this, "file-watcher-update", false);
|
||||
mFile = f;
|
||||
mRootDirectory = f;
|
||||
mStorageType = aType;
|
||||
}
|
||||
|
||||
jsval InterfaceToJsval(nsPIDOMWindow* aWindow, nsISupports* aObject, const nsIID* aIID)
|
||||
@ -729,18 +799,21 @@ NS_IMETHODIMP
|
||||
ContinueCursorEvent::Run() {
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
jsval val;
|
||||
jsval val = JSVAL_NULL;
|
||||
|
||||
nsDOMDeviceStorageCursor* cursor = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
|
||||
if (cursor->mFiles.Length() == 0) {
|
||||
val = JSVAL_NULL;
|
||||
}
|
||||
else {
|
||||
nsString cursorStorageType;
|
||||
cursor->GetStorageType(cursorStorageType);
|
||||
|
||||
while (cursor->mFiles.Length() > 0) {
|
||||
nsRefPtr<DeviceStorageFile> file = cursor->mFiles[0];
|
||||
cursor->mFiles.RemoveElementAt(0);
|
||||
|
||||
// todo, this blob needs to be opened in the parent. This will be signifincally easier when bent lands
|
||||
if (!file->IsType(cursorStorageType)) {
|
||||
continue;
|
||||
}
|
||||
val = nsIFileToJsval(cursor->GetOwner(), file);
|
||||
cursor->mOkToCallContinue = true;
|
||||
break;
|
||||
}
|
||||
|
||||
mRequest->FireSuccess(val);
|
||||
@ -816,6 +889,12 @@ nsDOMDeviceStorageCursor::~nsDOMDeviceStorageCursor()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
nsDOMDeviceStorageCursor::GetStorageType(nsAString & aType)
|
||||
{
|
||||
aType = mFile->mStorageType;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorageCursor::GetType(nsACString & aType)
|
||||
{
|
||||
@ -876,7 +955,7 @@ nsDOMDeviceStorageCursor::Allow()
|
||||
}
|
||||
|
||||
PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(this, mFile);
|
||||
DeviceStorageEnumerationParams params(fullpath, mSince);
|
||||
DeviceStorageEnumerationParams params(mFile->mStorageType, fullpath, mSince);
|
||||
ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
|
||||
return NS_OK;
|
||||
}
|
||||
@ -933,7 +1012,7 @@ nsDOMDeviceStorageCursor::IPDLRelease()
|
||||
class PostStatResultEvent : public nsRunnable
|
||||
{
|
||||
public:
|
||||
PostStatResultEvent(nsRefPtr<DOMRequest>& aRequest, int64_t aFreeBytes, int64_t aTotalBytes)
|
||||
PostStatResultEvent(nsRefPtr<DOMRequest>& aRequest, uint64_t aFreeBytes, uint64_t aTotalBytes)
|
||||
: mFreeBytes(aFreeBytes)
|
||||
, mTotalBytes(aTotalBytes)
|
||||
{
|
||||
@ -951,9 +1030,7 @@ public:
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
nsresult rv = GetSDCardStatus(state);
|
||||
if (NS_FAILED(rv)) {
|
||||
mRequest->FireError(NS_ERROR_FAILURE);
|
||||
mRequest = nullptr;
|
||||
return NS_OK;
|
||||
state.Assign(NS_LITERAL_STRING("unavailable"));
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -969,7 +1046,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
int64_t mFreeBytes, mTotalBytes;
|
||||
uint64_t mFreeBytes, mTotalBytes;
|
||||
nsString mState;
|
||||
nsRefPtr<DOMRequest> mRequest;
|
||||
};
|
||||
@ -1144,14 +1221,15 @@ public:
|
||||
{
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
|
||||
nsCOMPtr<nsIRunnable> r;
|
||||
uint64_t diskUsage = DeviceStorageFile::DirectoryDiskUsage(mFile->mFile);
|
||||
int64_t freeSpace;
|
||||
uint64_t diskUsage = 0;
|
||||
DeviceStorageFile::DirectoryDiskUsage(mFile->mFile, &diskUsage);
|
||||
int64_t freeSpace = 0;
|
||||
nsresult rv = mFile->mFile->GetDiskSpaceAvailable(&freeSpace);
|
||||
if (NS_FAILED(rv)) {
|
||||
freeSpace = 0;
|
||||
}
|
||||
|
||||
r = new PostStatResultEvent(mRequest, diskUsage, freeSpace);
|
||||
r = new PostStatResultEvent(mRequest, freeSpace, diskUsage);
|
||||
NS_DispatchToMainThread(r);
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1306,6 +1384,7 @@ public:
|
||||
|
||||
DeviceStorageAddParams params;
|
||||
params.blobChild() = actor;
|
||||
params.type() = mFile->mStorageType;
|
||||
params.name() = mFile->mPath;
|
||||
params.fullpath() = fullpath;
|
||||
|
||||
@ -1321,7 +1400,7 @@ public:
|
||||
{
|
||||
if (XRE_GetProcessType() != GeckoProcessType_Default) {
|
||||
PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
|
||||
DeviceStorageGetParams params(mFile->mPath, fullpath);
|
||||
DeviceStorageGetParams params(mFile->mStorageType, mFile->mPath, fullpath);
|
||||
ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1334,7 +1413,7 @@ public:
|
||||
{
|
||||
if (XRE_GetProcessType() != GeckoProcessType_Default) {
|
||||
PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
|
||||
DeviceStorageDeleteParams params(fullpath);
|
||||
DeviceStorageDeleteParams params(mFile->mStorageType, fullpath);
|
||||
ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1346,7 +1425,7 @@ public:
|
||||
{
|
||||
if (XRE_GetProcessType() != GeckoProcessType_Default) {
|
||||
PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
|
||||
DeviceStorageStatParams params(fullpath);
|
||||
DeviceStorageStatParams params(mFile->mStorageType, fullpath);
|
||||
ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1452,8 +1531,8 @@ nsDOMDeviceStorage::Init(nsPIDOMWindow* aWindow, const nsAString &aType)
|
||||
{
|
||||
NS_ASSERTION(aWindow, "Must have a content dom");
|
||||
|
||||
SetRootFileForType(aType);
|
||||
if (!mFile) {
|
||||
SetRootDirectoryForType(aType);
|
||||
if (!mRootDirectory) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
@ -1530,7 +1609,7 @@ nsDOMDeviceStorage::AddNamed(nsIDOMBlob *aBlob,
|
||||
|
||||
nsCOMPtr<nsIRunnable> r;
|
||||
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile, aPath);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, mRootDirectory, aPath);
|
||||
|
||||
if (!dsf->IsSafePath()) {
|
||||
r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_FILE_NAME, dsf);
|
||||
@ -1578,7 +1657,7 @@ nsDOMDeviceStorage::GetInternal(const JS::Value & aPath,
|
||||
JSString* jsstr = JS_ValueToString(aCx, aPath);
|
||||
nsDependentJSString path;
|
||||
if (!path.init(aCx, jsstr)) {
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, mRootDirectory);
|
||||
r = new PostErrorEvent(request,
|
||||
POST_ERROR_EVENT_NON_STRING_TYPE_UNSUPPORTED,
|
||||
dsf);
|
||||
@ -1586,7 +1665,7 @@ nsDOMDeviceStorage::GetInternal(const JS::Value & aPath,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile, path);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, mRootDirectory, path);
|
||||
dsf->SetEditable(aEditable);
|
||||
|
||||
if (!dsf->IsSafePath()) {
|
||||
@ -1615,13 +1694,13 @@ nsDOMDeviceStorage::Delete(const JS::Value & aPath, JSContext* aCx, nsIDOMDOMReq
|
||||
JSString* jsstr = JS_ValueToString(aCx, aPath);
|
||||
nsDependentJSString path;
|
||||
if (!path.init(aCx, jsstr)) {
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, mRootDirectory);
|
||||
r = new PostErrorEvent(request, POST_ERROR_EVENT_NON_STRING_TYPE_UNSUPPORTED, dsf);
|
||||
NS_DispatchToMainThread(r);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile, path);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, mRootDirectory, path);
|
||||
|
||||
if (!dsf->IsSafePath()) {
|
||||
r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_FILE_NAME, dsf);
|
||||
@ -1645,7 +1724,7 @@ nsDOMDeviceStorage::Stat(nsIDOMDOMRequest** aRetval)
|
||||
nsRefPtr<DOMRequest> request = new DOMRequest(win);
|
||||
NS_ADDREF(*aRetval = request);
|
||||
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, mRootDirectory);
|
||||
nsCOMPtr<nsIRunnable> r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_STAT,
|
||||
win,
|
||||
mPrincipal,
|
||||
@ -1729,7 +1808,7 @@ nsDOMDeviceStorage::EnumerateInternal(const JS::Value & aName,
|
||||
since = ExtractDateFromOptions(aCx, aOptions);
|
||||
}
|
||||
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile, path);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, mRootDirectory, path);
|
||||
dsf->SetEditable(aEditable);
|
||||
|
||||
nsRefPtr<nsDOMDeviceStorageCursor> cursor = new nsDOMDeviceStorageCursor(win, mPrincipal,
|
||||
@ -1780,7 +1859,7 @@ nsDOMDeviceStorage::DispatchMountChangeEvent(nsAString& aType)
|
||||
nsCOMPtr<nsIDOMDeviceStorageChangeEvent> ce = do_QueryInterface(event);
|
||||
nsresult rv = ce->InitDeviceStorageChangeEvent(NS_LITERAL_STRING("change"),
|
||||
true, false,
|
||||
NS_LITERAL_STRING(""),
|
||||
NS_LITERAL_STRING(""),
|
||||
aType);
|
||||
if (NS_FAILED(rv)) {
|
||||
return;
|
||||
@ -1839,10 +1918,7 @@ nsDOMDeviceStorage::Observe(nsISupports *aSubject, const char *aTopic, const PRU
|
||||
{
|
||||
if (!strcmp(aTopic, "file-watcher-update")) {
|
||||
|
||||
nsCOMPtr<nsIFile> file = do_QueryInterface(aSubject);
|
||||
if (!file) {
|
||||
return NS_OK;
|
||||
}
|
||||
DeviceStorageFile* file = static_cast<DeviceStorageFile*>(aSubject);
|
||||
Notify(NS_ConvertUTF16toUTF8(aData).get(), file);
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1884,34 +1960,34 @@ nsDOMDeviceStorage::Observe(nsISupports *aSubject, const char *aTopic, const PRU
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDOMDeviceStorage::Notify(const char* aReason, nsIFile* aFile)
|
||||
nsDOMDeviceStorage::Notify(const char* aReason, DeviceStorageFile* aFile)
|
||||
{
|
||||
if (!mAllowedToWatchFile) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!mFile) {
|
||||
if (!mStorageType.Equals(aFile->mStorageType)) {
|
||||
// Ignore this
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!mRootDirectory) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsString rootpath;
|
||||
nsresult rv = mFile->GetPath(rootpath);
|
||||
nsresult rv = mRootDirectory->GetPath(rootpath);
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
nsString fullpath;
|
||||
rv = aFile->GetPath(fullpath);
|
||||
rv = aFile->mFile->GetPath(fullpath);
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_ASSERTION(fullpath.Length() >= rootpath.Length(), "Root path longer than full path!");
|
||||
|
||||
if (!StringBeginsWith(fullpath, rootpath)) {
|
||||
NS_WARNING("Observing a path outside of our root!");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsAString::size_type len = rootpath.Length() + 1; // +1 for the trailing /
|
||||
nsDependentSubstring newPath (fullpath, len, fullpath.Length() - len);
|
||||
@ -1944,7 +2020,7 @@ nsDOMDeviceStorage::AddEventListener(const nsAString & aType,
|
||||
}
|
||||
|
||||
nsRefPtr<DOMRequest> request = new DOMRequest(win);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, mRootDirectory);
|
||||
nsCOMPtr<nsIRunnable> r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_WATCH,
|
||||
win, mPrincipal, dsf, request, this, aListener);
|
||||
NS_DispatchToMainThread(r);
|
||||
|
@ -44,10 +44,11 @@ class DeviceStorageFile MOZ_FINAL
|
||||
public:
|
||||
nsCOMPtr<nsIFile> mFile;
|
||||
nsString mPath;
|
||||
nsString mStorageType;
|
||||
bool mEditable;
|
||||
|
||||
DeviceStorageFile(nsIFile* aFile, const nsAString& aPath);
|
||||
DeviceStorageFile(nsIFile* aFile);
|
||||
DeviceStorageFile(const nsAString& aStorageType, nsIFile* aFile, const nsAString& aPath);
|
||||
DeviceStorageFile(const nsAString& aStorageType, nsIFile* aFile);
|
||||
void SetPath(const nsAString& aPath);
|
||||
void SetEditable(bool aEditable);
|
||||
|
||||
@ -56,6 +57,7 @@ public:
|
||||
// we want to make sure that the names of file can't reach
|
||||
// outside of the type of storage the user asked for.
|
||||
bool IsSafePath();
|
||||
bool IsType(nsAString& aType);
|
||||
|
||||
nsresult Remove();
|
||||
nsresult Write(nsIInputStream* aInputStream);
|
||||
@ -63,7 +65,7 @@ public:
|
||||
void CollectFiles(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles, uint64_t aSince = 0);
|
||||
void collectFilesInternal(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles, uint64_t aSince, nsAString& aRootPath);
|
||||
|
||||
static uint64_t DirectoryDiskUsage(nsIFile* aFile, uint64_t aSoFar = 0);
|
||||
static void DirectoryDiskUsage(nsIFile* aFile, uint64_t* aSoFar);
|
||||
|
||||
private:
|
||||
void NormalizeFilePath();
|
||||
@ -105,6 +107,8 @@ public:
|
||||
virtual bool Recv__delete__(const bool& allow);
|
||||
virtual void IPDLRelease();
|
||||
|
||||
void GetStorageType(nsAString & aType);
|
||||
|
||||
private:
|
||||
~nsDOMDeviceStorageCursor();
|
||||
|
||||
|
@ -974,15 +974,17 @@ ContentChild::RecvLastPrivateDocShellDestroyed()
|
||||
}
|
||||
|
||||
bool
|
||||
ContentChild::RecvFilePathUpdate(const nsString& path, const nsCString& aReason)
|
||||
ContentChild::RecvFilePathUpdate(const nsString& type, const nsString& path, const nsCString& aReason)
|
||||
{
|
||||
nsCOMPtr<nsIFile> file;
|
||||
NS_NewLocalFile(path, false, getter_AddRefs(file));
|
||||
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(type, file);
|
||||
|
||||
nsString reason;
|
||||
CopyASCIItoUTF16(aReason, reason);
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
obs->NotifyObservers(file, "file-watcher-update", reason.get());
|
||||
obs->NotifyObservers(dsf, "file-watcher-update", reason.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -173,7 +173,7 @@ public:
|
||||
|
||||
virtual bool RecvLastPrivateDocShellDestroyed();
|
||||
|
||||
virtual bool RecvFilePathUpdate(const nsString& path, const nsCString& reason);
|
||||
virtual bool RecvFilePathUpdate(const nsString& type, const nsString& path, const nsCString& reason);
|
||||
virtual bool RecvFileSystemUpdate(const nsString& aFsName, const nsString& aName, const int32_t& aState);
|
||||
|
||||
#ifdef ANDROID
|
||||
|
@ -1036,14 +1036,12 @@ ContentParent::Observe(nsISupports* aSubject,
|
||||
else if (!strcmp(aTopic, "file-watcher-update")) {
|
||||
nsCString creason;
|
||||
CopyUTF16toUTF8(aData, creason);
|
||||
nsCOMPtr<nsIFile> file = do_QueryInterface(aSubject);
|
||||
if (!file) {
|
||||
return NS_OK;
|
||||
}
|
||||
DeviceStorageFile* file = static_cast<DeviceStorageFile*>(aSubject);
|
||||
|
||||
nsString path;
|
||||
file->GetPath(path);
|
||||
unused << SendFilePathUpdate(path, creason);
|
||||
file->mFile->GetPath(path);
|
||||
|
||||
unused << SendFilePathUpdate(file->mStorageType, path, creason);
|
||||
}
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
else if(!strcmp(aTopic, NS_VOLUME_STATE_CHANGED)) {
|
||||
|
@ -72,11 +72,13 @@ struct FontListEntry {
|
||||
|
||||
struct DeviceStorageStatParams
|
||||
{
|
||||
nsString type;
|
||||
nsString fullpath;
|
||||
};
|
||||
|
||||
struct DeviceStorageAddParams
|
||||
{
|
||||
nsString type;
|
||||
PBlob blob;
|
||||
nsString name;
|
||||
nsString fullpath;
|
||||
@ -84,17 +86,20 @@ struct DeviceStorageAddParams
|
||||
|
||||
struct DeviceStorageGetParams
|
||||
{
|
||||
nsString type;
|
||||
nsString name;
|
||||
nsString fullpath;
|
||||
};
|
||||
|
||||
struct DeviceStorageDeleteParams
|
||||
{
|
||||
nsString type;
|
||||
nsString fullpath;
|
||||
};
|
||||
|
||||
struct DeviceStorageEnumerationParams
|
||||
{
|
||||
nsString type;
|
||||
nsString fullpath;
|
||||
uint64_t since;
|
||||
};
|
||||
@ -237,7 +242,7 @@ child:
|
||||
// Notify child that last-pb-context-exited notification was observed
|
||||
LastPrivateDocShellDestroyed();
|
||||
|
||||
FilePathUpdate(nsString filepath, nsCString reasons);
|
||||
FilePathUpdate(nsString type, nsString filepath, nsCString reasons);
|
||||
|
||||
FileSystemUpdate(nsString fsName, nsString mountPoint, int32_t fsState);
|
||||
|
||||
|
@ -397,18 +397,49 @@ FunctionBox::FunctionBox(ObjectBox* traceListHead, JSObject *obj, ParseContext *
|
||||
bufEnd(0),
|
||||
ndefaults(0),
|
||||
strictModeState(sms),
|
||||
inWith(outerpc->parsingWith),
|
||||
inWith(false), // initialized below
|
||||
inGenexpLambda(false),
|
||||
cxFlags(outerpc->sc->context) // the cxFlags are set in LeaveFunction
|
||||
cxFlags(outerpc->sc->context) // the cxFlags are set in LeaveFunction
|
||||
{
|
||||
isFunctionBox = true;
|
||||
if (!outerpc->sc->inFunction()) {
|
||||
|
||||
if (outerpc->parsingWith) {
|
||||
// This covers cases that don't involve eval(). For example:
|
||||
//
|
||||
// with (o) { (function() { g(); })(); }
|
||||
//
|
||||
// In this case, |outerpc| corresponds to global code, and
|
||||
// outerpc->parsingWith is true.
|
||||
inWith = true;
|
||||
|
||||
} else if (!outerpc->sc->inFunction()) {
|
||||
// This covers the case where a function is nested within an eval()
|
||||
// within a |with| statement.
|
||||
//
|
||||
// with (o) { eval("(function() { g(); })();"); }
|
||||
//
|
||||
// In this case, |outerpc| corresponds to the eval(),
|
||||
// outerpc->parsingWith is false because the eval() breaks the
|
||||
// ParseContext chain, and |parent| is NULL (again because of the
|
||||
// eval(), so we have to look at |outerpc|'s scopeChain.
|
||||
//
|
||||
JSObject *scope = outerpc->sc->scopeChain();
|
||||
while (scope) {
|
||||
if (scope->isWith())
|
||||
inWith = true;
|
||||
scope = scope->enclosingScope();
|
||||
}
|
||||
} else {
|
||||
// This is like the above case, but for more deeply nested functions.
|
||||
// For example:
|
||||
//
|
||||
// with (o) { eval("(function() { (function() { g(); })(); })();"); } }
|
||||
//
|
||||
// In this case, the inner anonymous function needs to inherit the
|
||||
// setting of |inWith| from the outer one.
|
||||
FunctionBox *parent = outerpc->sc->funbox();
|
||||
if (parent && parent->inWith)
|
||||
inWith = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,7 +189,8 @@ struct ParseContext /* tree context for semantic checks */
|
||||
exclude 'in' */
|
||||
bool parsingWith:1; /* true while we are within a
|
||||
with-statement or E4X filter-expression
|
||||
in the current ParseContext chain */
|
||||
in the current ParseContext chain
|
||||
(which stops at the top-level or an eval() */
|
||||
|
||||
// Set when parsing a declaration-like destructuring pattern. This flag
|
||||
// causes PrimaryExpr to create PN_NAME parse nodes for variable references
|
||||
|
@ -12,8 +12,6 @@
|
||||
|
||||
#include "mozilla/TypeTraits.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
|
||||
#include "js/TemplateLib.h"
|
||||
#include "js/Utility.h"
|
||||
|
||||
@ -236,6 +234,9 @@ typedef MutableHandle<Value> MutableHandleValue;
|
||||
* rooted.
|
||||
*/
|
||||
typedef JSObject * RawObject;
|
||||
typedef JSString * RawString;
|
||||
|
||||
extern mozilla::ThreadLocal<JSRuntime *> TlsRuntime;
|
||||
|
||||
/*
|
||||
* By default, pointers should use the inheritance hierarchy to find their
|
||||
@ -267,10 +268,10 @@ class RootedBase {};
|
||||
template <typename T>
|
||||
class Rooted : public RootedBase<T>
|
||||
{
|
||||
void init(JSContext *cx_)
|
||||
void init(JSContext *cxArg)
|
||||
{
|
||||
#if defined(JSGC_ROOT_ANALYSIS) || defined(JSGC_USE_EXACT_ROOTING)
|
||||
ContextFriendFields *cx = ContextFriendFields::get(cx_);
|
||||
ContextFriendFields *cx = ContextFriendFields::get(cxArg);
|
||||
|
||||
ThingRootKind kind = RootMethods<T>::kind();
|
||||
this->stack = reinterpret_cast<Rooted<T>**>(&cx->thingGCRooters[kind]);
|
||||
@ -281,7 +282,27 @@ class Rooted : public RootedBase<T>
|
||||
#endif
|
||||
}
|
||||
|
||||
void init(JSRuntime *rtArg)
|
||||
{
|
||||
#if defined(JSGC_ROOT_ANALYSIS) || defined(JSGC_USE_EXACT_ROOTING)
|
||||
RuntimeFriendFields *rt = const_cast<RuntimeFriendFields *>(RuntimeFriendFields::get(rtArg));
|
||||
|
||||
ThingRootKind kind = RootMethods<T>::kind();
|
||||
this->stack = reinterpret_cast<Rooted<T>**>(&rt->thingGCRooters[kind]);
|
||||
this->prev = *stack;
|
||||
*stack = this;
|
||||
|
||||
JS_ASSERT(!RootMethods<T>::poisoned(ptr));
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
Rooted() : ptr(RootMethods<T>::initial()) { init(JS::TlsRuntime); }
|
||||
Rooted(const T &initial) : ptr(initial) { init(JS::TlsRuntime); }
|
||||
|
||||
Rooted(JSRuntime *rt) : ptr(RootMethods<T>::initial()) { init(rt); }
|
||||
Rooted(JSRuntime *rt, T initial) : ptr(initial) { init(rt); }
|
||||
|
||||
Rooted(JSContext *cx) : ptr(RootMethods<T>::initial()) { init(cx); }
|
||||
Rooted(JSContext *cx, T initial) : ptr(initial) { init(cx); }
|
||||
|
||||
@ -318,13 +339,11 @@ class Rooted : public RootedBase<T>
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
#if defined(JSGC_ROOT_ANALYSIS) || defined(JSGC_USE_EXACT_ROOTING)
|
||||
Rooted<T> **stack, *prev;
|
||||
#endif
|
||||
T ptr;
|
||||
|
||||
Rooted() MOZ_DELETE;
|
||||
Rooted(const Rooted &) MOZ_DELETE;
|
||||
};
|
||||
|
||||
|
32
js/src/jit-test/tests/basic/bug786114.js
Normal file
32
js/src/jit-test/tests/basic/bug786114.js
Normal file
@ -0,0 +1,32 @@
|
||||
let expected = 'o!o!o!';
|
||||
let actual = '';
|
||||
|
||||
// g is a function that needs an implicit |this| if called within a |with|
|
||||
// statement. If we fail to provide that implicit |this|, it will append
|
||||
// "[object global]" instead of "o!".
|
||||
let o = {
|
||||
g: function() { actual += this.toString(); },
|
||||
toString: function() { return "o!"; }
|
||||
}
|
||||
|
||||
// g's presence within the |with| is detected by simple tracking of |with|s
|
||||
// during parsing.
|
||||
with (o) {
|
||||
(function() { g(); })();
|
||||
}
|
||||
|
||||
// The eval() defeats the tracking of |with| during parsing. Instead, g's
|
||||
// presence within the |with| is detected by looking at the scopeChain of the
|
||||
// ParseContext.
|
||||
with (o) {
|
||||
eval("(function() { g(); })()");
|
||||
}
|
||||
|
||||
// This is like the above case, but the knowledge of the |with| presence must
|
||||
// be inherited by the inner function. This is the case that was missed in bug
|
||||
// 786114.
|
||||
with (o) {
|
||||
eval("(function() { (function() { g(); })(); })()");
|
||||
}
|
||||
|
||||
assertEq(actual, expected);
|
@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
#include "mozilla/FloatingPoint.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
@ -719,6 +720,13 @@ JS_IsBuiltinFunctionConstructor(JSFunction *fun)
|
||||
*/
|
||||
static JSBool js_NewRuntimeWasCalled = JS_FALSE;
|
||||
|
||||
/*
|
||||
* Thread Local Storage slot for storing the runtime for a thread.
|
||||
*/
|
||||
namespace JS {
|
||||
mozilla::ThreadLocal<JSRuntime *> TlsRuntime;
|
||||
}
|
||||
|
||||
static const JSSecurityCallbacks NullSecurityCallbacks = { };
|
||||
|
||||
JSRuntime::JSRuntime()
|
||||
@ -877,6 +885,8 @@ JSRuntime::init(uint32_t maxbytes)
|
||||
ownerThread_ = PR_GetCurrentThread();
|
||||
#endif
|
||||
|
||||
JS::TlsRuntime.set(this);
|
||||
|
||||
#ifdef JS_METHODJIT_SPEW
|
||||
JMCheckLogging();
|
||||
#endif
|
||||
@ -938,7 +948,9 @@ JSRuntime::init(uint32_t maxbytes)
|
||||
|
||||
JSRuntime::~JSRuntime()
|
||||
{
|
||||
JS_ASSERT(onOwnerThread());
|
||||
#ifdef JS_THREADSAFE
|
||||
clearOwnerThread();
|
||||
#endif
|
||||
|
||||
delete_(debugScopes);
|
||||
|
||||
@ -994,7 +1006,10 @@ JSRuntime::setOwnerThread()
|
||||
{
|
||||
JS_ASSERT(ownerThread_ == (void *)0xc1ea12); /* "clear" */
|
||||
JS_ASSERT(requestDepth == 0);
|
||||
JS_ASSERT(js_NewRuntimeWasCalled);
|
||||
JS_ASSERT(JS::TlsRuntime.get() == NULL);
|
||||
ownerThread_ = PR_GetCurrentThread();
|
||||
JS::TlsRuntime.set(this);
|
||||
nativeStackBase = GetNativeStackBase();
|
||||
if (nativeStackQuota)
|
||||
JS_SetNativeStackQuota(this, nativeStackQuota);
|
||||
@ -1003,9 +1018,11 @@ JSRuntime::setOwnerThread()
|
||||
void
|
||||
JSRuntime::clearOwnerThread()
|
||||
{
|
||||
JS_ASSERT(onOwnerThread());
|
||||
assertValidThread();
|
||||
JS_ASSERT(requestDepth == 0);
|
||||
JS_ASSERT(js_NewRuntimeWasCalled);
|
||||
ownerThread_ = (void *)0xc1ea12; /* "clear" */
|
||||
JS::TlsRuntime.set(NULL);
|
||||
nativeStackBase = 0;
|
||||
#if JS_STACK_GROWTH_DIRECTION > 0
|
||||
nativeStackLimit = UINTPTR_MAX;
|
||||
@ -1014,10 +1031,20 @@ JSRuntime::clearOwnerThread()
|
||||
#endif
|
||||
}
|
||||
|
||||
JS_FRIEND_API(bool)
|
||||
JSRuntime::onOwnerThread() const
|
||||
JS_FRIEND_API(void)
|
||||
JSRuntime::abortIfWrongThread() const
|
||||
{
|
||||
return ownerThread_ == PR_GetCurrentThread();
|
||||
if (ownerThread_ != PR_GetCurrentThread())
|
||||
MOZ_CRASH();
|
||||
if (this != JS::TlsRuntime.get())
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
JS_FRIEND_API(void)
|
||||
JSRuntime::assertValidThread() const
|
||||
{
|
||||
JS_ASSERT(ownerThread_ == PR_GetCurrentThread());
|
||||
JS_ASSERT(this == JS::TlsRuntime.get());
|
||||
}
|
||||
#endif /* JS_THREADSAFE */
|
||||
|
||||
@ -1054,6 +1081,9 @@ JS_NewRuntime(uint32_t maxbytes)
|
||||
|
||||
InitMemorySubsystem();
|
||||
|
||||
if (!JS::TlsRuntime.init())
|
||||
return false;
|
||||
|
||||
js_NewRuntimeWasCalled = JS_TRUE;
|
||||
}
|
||||
|
||||
@ -1101,7 +1131,7 @@ static void
|
||||
StartRequest(JSContext *cx)
|
||||
{
|
||||
JSRuntime *rt = cx->runtime;
|
||||
JS_ASSERT(rt->onOwnerThread());
|
||||
rt->assertValidThread();
|
||||
|
||||
if (rt->requestDepth) {
|
||||
rt->requestDepth++;
|
||||
@ -1118,7 +1148,7 @@ static void
|
||||
StopRequest(JSContext *cx)
|
||||
{
|
||||
JSRuntime *rt = cx->runtime;
|
||||
JS_ASSERT(rt->onOwnerThread());
|
||||
rt->assertValidThread();
|
||||
JS_ASSERT(rt->requestDepth != 0);
|
||||
if (rt->requestDepth != 1) {
|
||||
rt->requestDepth--;
|
||||
@ -1166,7 +1196,7 @@ JS_SuspendRequest(JSContext *cx)
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
JSRuntime *rt = cx->runtime;
|
||||
JS_ASSERT(rt->onOwnerThread());
|
||||
rt->assertValidThread();
|
||||
|
||||
unsigned saveDepth = rt->requestDepth;
|
||||
if (!saveDepth)
|
||||
@ -1186,7 +1216,7 @@ JS_ResumeRequest(JSContext *cx, unsigned saveDepth)
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
JSRuntime *rt = cx->runtime;
|
||||
JS_ASSERT(rt->onOwnerThread());
|
||||
rt->assertValidThread();
|
||||
if (saveDepth == 0)
|
||||
return;
|
||||
JS_ASSERT(saveDepth >= 1);
|
||||
@ -1202,7 +1232,7 @@ JS_PUBLIC_API(JSBool)
|
||||
JS_IsInRequest(JSRuntime *rt)
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
JS_ASSERT(rt->onOwnerThread());
|
||||
rt->assertValidThread();
|
||||
return rt->requestDepth != 0;
|
||||
#else
|
||||
return false;
|
||||
@ -1213,7 +1243,7 @@ JS_PUBLIC_API(JSBool)
|
||||
JS_IsInSuspendedRequest(JSRuntime *rt)
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
JS_ASSERT(rt->onOwnerThread());
|
||||
rt->assertValidThread();
|
||||
return rt->suspendCount != 0;
|
||||
#else
|
||||
return false;
|
||||
@ -7025,10 +7055,7 @@ JS_SetRuntimeThread(JSRuntime *rt)
|
||||
extern JS_NEVER_INLINE JS_PUBLIC_API(void)
|
||||
JS_AbortIfWrongThread(JSRuntime *rt)
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
if (!rt->onOwnerThread())
|
||||
MOZ_CRASH();
|
||||
#endif
|
||||
rt->abortIfWrongThread();
|
||||
}
|
||||
|
||||
#ifdef JS_GC_ZEAL
|
||||
|
@ -14,6 +14,9 @@
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/FloatingPoint.h"
|
||||
#include "mozilla/StandardInteger.h"
|
||||
#ifdef __cplusplus
|
||||
# include "mozilla/ThreadLocal.h"
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
@ -3018,6 +3021,8 @@ JS_END_EXTERN_C
|
||||
|
||||
namespace JS {
|
||||
|
||||
extern mozilla::ThreadLocal<JSRuntime *> TlsRuntime;
|
||||
|
||||
inline bool
|
||||
IsPoisonedId(jsid iden)
|
||||
{
|
||||
|
@ -1364,39 +1364,6 @@ JSObject::makeDenseArraySlow(JSContext *cx, HandleObject obj)
|
||||
}
|
||||
|
||||
#if JS_HAS_TOSOURCE
|
||||
class ArraySharpDetector
|
||||
{
|
||||
JSContext *cx;
|
||||
bool success;
|
||||
bool alreadySeen;
|
||||
bool sharp;
|
||||
|
||||
public:
|
||||
ArraySharpDetector(JSContext *cx)
|
||||
: cx(cx),
|
||||
success(false),
|
||||
alreadySeen(false),
|
||||
sharp(false)
|
||||
{}
|
||||
|
||||
bool init(HandleObject obj) {
|
||||
success = js_EnterSharpObject(cx, obj, NULL, &alreadySeen, &sharp);
|
||||
if (!success)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initiallySharp() const {
|
||||
JS_ASSERT_IF(sharp, alreadySeen);
|
||||
return sharp;
|
||||
}
|
||||
|
||||
~ArraySharpDetector() {
|
||||
if (success && !sharp)
|
||||
js_LeaveSharpObject(cx, NULL);
|
||||
}
|
||||
};
|
||||
|
||||
JS_ALWAYS_INLINE bool
|
||||
IsArray(const Value &v)
|
||||
{
|
||||
@ -1411,13 +1378,13 @@ array_toSource_impl(JSContext *cx, CallArgs args)
|
||||
Rooted<JSObject*> obj(cx, &args.thisv().toObject());
|
||||
RootedValue elt(cx);
|
||||
|
||||
ArraySharpDetector detector(cx);
|
||||
if (!detector.init(obj))
|
||||
AutoCycleDetector detector(cx, obj);
|
||||
if (!detector.init())
|
||||
return false;
|
||||
|
||||
StringBuffer sb(cx);
|
||||
|
||||
if (detector.initiallySharp()) {
|
||||
if (detector.foundCycle()) {
|
||||
if (!sb.append("[]"))
|
||||
return false;
|
||||
goto make_string;
|
||||
@ -1481,52 +1448,6 @@ array_toSource(JSContext *cx, unsigned argc, Value *vp)
|
||||
}
|
||||
#endif
|
||||
|
||||
class AutoArrayCycleDetector
|
||||
{
|
||||
JSContext *cx;
|
||||
JSObject *obj;
|
||||
uint32_t genBefore;
|
||||
BusyArraysSet::AddPtr hashPointer;
|
||||
bool cycle;
|
||||
JS_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
|
||||
public:
|
||||
AutoArrayCycleDetector(JSContext *cx, JSObject *obj JS_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: cx(cx),
|
||||
obj(obj),
|
||||
cycle(true)
|
||||
{
|
||||
JS_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
}
|
||||
|
||||
bool init()
|
||||
{
|
||||
BusyArraysSet &set = cx->busyArrays;
|
||||
hashPointer = set.lookupForAdd(obj);
|
||||
if (!hashPointer) {
|
||||
if (!set.add(hashPointer, obj))
|
||||
return false;
|
||||
cycle = false;
|
||||
genBefore = set.generation();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
~AutoArrayCycleDetector()
|
||||
{
|
||||
if (!cycle) {
|
||||
if (genBefore == cx->busyArrays.generation())
|
||||
cx->busyArrays.remove(hashPointer);
|
||||
else
|
||||
cx->busyArrays.remove(obj);
|
||||
}
|
||||
}
|
||||
|
||||
bool foundCycle() { return cycle; }
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
static bool
|
||||
array_join_sub(JSContext *cx, CallArgs &args, bool locale)
|
||||
{
|
||||
@ -1539,7 +1460,7 @@ array_join_sub(JSContext *cx, CallArgs &args, bool locale)
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
AutoArrayCycleDetector detector(cx, obj);
|
||||
AutoCycleDetector detector(cx, obj);
|
||||
if (!detector.init())
|
||||
return false;
|
||||
|
||||
@ -1553,7 +1474,6 @@ array_join_sub(JSContext *cx, CallArgs &args, bool locale)
|
||||
if (!GetLengthProperty(cx, obj, &length))
|
||||
return false;
|
||||
|
||||
|
||||
// Steps 4 and 5
|
||||
RootedString sepstr(cx, NULL);
|
||||
if (!locale && args.hasDefined(0)) {
|
||||
|
@ -63,6 +63,41 @@
|
||||
using namespace js;
|
||||
using namespace js::gc;
|
||||
|
||||
bool
|
||||
js::AutoCycleDetector::init()
|
||||
{
|
||||
ObjectSet &set = cx->cycleDetectorSet;
|
||||
hashsetAddPointer = set.lookupForAdd(obj);
|
||||
if (!hashsetAddPointer) {
|
||||
if (!set.add(hashsetAddPointer, obj))
|
||||
return false;
|
||||
cyclic = false;
|
||||
hashsetGenerationAtInit = set.generation();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
js::AutoCycleDetector::~AutoCycleDetector()
|
||||
{
|
||||
if (!cyclic) {
|
||||
if (hashsetGenerationAtInit == cx->cycleDetectorSet.generation())
|
||||
cx->cycleDetectorSet.remove(hashsetAddPointer);
|
||||
else
|
||||
cx->cycleDetectorSet.remove(obj);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
js::TraceCycleDetectionSet(JSTracer *trc, js::ObjectSet &set)
|
||||
{
|
||||
for (js::ObjectSet::Enum e(set); !e.empty(); e.popFront()) {
|
||||
JSObject *prior = e.front();
|
||||
MarkObjectRoot(trc, const_cast<JSObject **>(&e.front()), "cycle detector table entry");
|
||||
if (prior != e.front())
|
||||
e.rekeyFront(e.front());
|
||||
}
|
||||
}
|
||||
|
||||
struct CallbackData
|
||||
{
|
||||
CallbackData(JSMallocSizeOfFun f) : mallocSizeOf(f), n(0) {}
|
||||
@ -296,7 +331,7 @@ js::NewContext(JSRuntime *rt, size_t stackChunkSize)
|
||||
|
||||
JS_ASSERT(cx->findVersion() == JSVERSION_DEFAULT);
|
||||
|
||||
if (!cx->busyArrays.init()) {
|
||||
if (!cx->cycleDetectorSet.init()) {
|
||||
Foreground::delete_(cx);
|
||||
return NULL;
|
||||
}
|
||||
@ -1069,7 +1104,7 @@ JSContext::JSContext(JSRuntime *rt)
|
||||
defaultCompartmentObject_(NULL),
|
||||
stack(thisDuringConstruction()),
|
||||
parseMapPool_(NULL),
|
||||
sharpObjectMap(thisDuringConstruction()),
|
||||
cycleDetectorSet(thisDuringConstruction()),
|
||||
argumentFormatMap(NULL),
|
||||
lastMessage(NULL),
|
||||
errorReporter(NULL),
|
||||
@ -1376,7 +1411,7 @@ JSContext::sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf) const
|
||||
* ones have been found by DMD to be worth measuring. More stuff may be
|
||||
* added later.
|
||||
*/
|
||||
return mallocSizeOf(this) + busyArrays.sizeOfExcludingThis(mallocSizeOf);
|
||||
return mallocSizeOf(this) + cycleDetectorSet.sizeOfExcludingThis(mallocSizeOf);
|
||||
}
|
||||
|
||||
void
|
||||
@ -1390,8 +1425,7 @@ JSContext::mark(JSTracer *trc)
|
||||
if (isExceptionPending())
|
||||
MarkValueRoot(trc, &exception, "exception");
|
||||
|
||||
if (sharpObjectMap.depth > 0)
|
||||
js_TraceSharpMap(trc, &sharpObjectMap);
|
||||
TraceCycleDetectionSet(trc, cycleDetectorSet);
|
||||
|
||||
MarkValueRoot(trc, &iterValue, "iterValue");
|
||||
}
|
||||
@ -1404,7 +1438,7 @@ AutoCheckRequestDepth::AutoCheckRequestDepth(JSContext *cx)
|
||||
: cx(cx)
|
||||
{
|
||||
JS_ASSERT(cx->runtime->requestDepth || cx->runtime->isHeapBusy());
|
||||
JS_ASSERT(cx->runtime->onOwnerThread());
|
||||
cx->runtime->assertValidThread();
|
||||
cx->runtime->checkRequestDepth++;
|
||||
}
|
||||
|
||||
|
@ -43,27 +43,39 @@ JS_BEGIN_EXTERN_C
|
||||
struct DtoaState;
|
||||
JS_END_EXTERN_C
|
||||
|
||||
struct JSSharpInfo {
|
||||
bool hasGen;
|
||||
bool isSharp;
|
||||
|
||||
JSSharpInfo() : hasGen(false), isSharp(false) {}
|
||||
};
|
||||
|
||||
typedef js::HashMap<JSObject *, JSSharpInfo> JSSharpTable;
|
||||
|
||||
struct JSSharpObjectMap {
|
||||
unsigned depth;
|
||||
uint32_t sharpgen;
|
||||
JSSharpTable table;
|
||||
|
||||
JSSharpObjectMap(JSContext *cx) : depth(0), sharpgen(0), table(js::TempAllocPolicy(cx)) {
|
||||
table.init();
|
||||
}
|
||||
};
|
||||
|
||||
namespace js {
|
||||
|
||||
typedef HashSet<JSObject *> ObjectSet;
|
||||
|
||||
/* Detects cycles when traversing an object graph. */
|
||||
class AutoCycleDetector
|
||||
{
|
||||
JSContext *cx;
|
||||
JSObject *obj;
|
||||
bool cyclic;
|
||||
uint32_t hashsetGenerationAtInit;
|
||||
ObjectSet::AddPtr hashsetAddPointer;
|
||||
JS_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
|
||||
public:
|
||||
AutoCycleDetector(JSContext *cx, JSObject *obj
|
||||
JS_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: cx(cx), obj(obj), cyclic(true)
|
||||
{
|
||||
JS_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
}
|
||||
|
||||
~AutoCycleDetector();
|
||||
|
||||
bool init();
|
||||
|
||||
bool foundCycle() { return cyclic; }
|
||||
};
|
||||
|
||||
/* Updates references in the cycle detection set if the GC moves them. */
|
||||
extern void
|
||||
TraceCycleDetectionSet(JSTracer *trc, ObjectSet &set);
|
||||
|
||||
namespace mjit {
|
||||
class JaegerRuntime;
|
||||
}
|
||||
@ -362,13 +374,15 @@ struct JSRuntime : js::RuntimeFriendFields
|
||||
void *ownerThread() const { return ownerThread_; }
|
||||
void clearOwnerThread();
|
||||
void setOwnerThread();
|
||||
JS_FRIEND_API(bool) onOwnerThread() const;
|
||||
JS_FRIEND_API(void) abortIfWrongThread() const;
|
||||
JS_FRIEND_API(void) assertValidThread() const;
|
||||
private:
|
||||
void *ownerThread_;
|
||||
public:
|
||||
#else
|
||||
public:
|
||||
bool onOwnerThread() const { return true; }
|
||||
void abortIfWrongThread() const {}
|
||||
void assertValidThread() const {}
|
||||
#endif
|
||||
|
||||
/* Keeper of the contiguous stack used by all contexts in this thread. */
|
||||
@ -1100,10 +1114,6 @@ VersionIsKnown(JSVersion version)
|
||||
return VersionNumber(version) != JSVERSION_UNKNOWN;
|
||||
}
|
||||
|
||||
typedef HashSet<JSObject *,
|
||||
DefaultHasher<JSObject *>,
|
||||
SystemAllocPolicy> BusyArraysSet;
|
||||
|
||||
inline void
|
||||
FreeOp::free_(void* p) {
|
||||
if (shouldFreeLater()) {
|
||||
@ -1227,8 +1237,7 @@ struct JSContext : js::ContextFriendFields
|
||||
|
||||
public:
|
||||
/* State for object and array toSource conversion. */
|
||||
JSSharpObjectMap sharpObjectMap;
|
||||
js::BusyArraysSet busyArrays;
|
||||
js::ObjectSet cycleDetectorSet;
|
||||
|
||||
/* Argument formatter support for JS_{Convert,Push}Arguments{,VA}. */
|
||||
JSArgumentFormatMap *argumentFormatMap;
|
||||
|
@ -183,25 +183,6 @@ JS_SetSourceHook(JSRuntime *rt, JS_SourceHook hook);
|
||||
|
||||
namespace js {
|
||||
|
||||
struct RuntimeFriendFields {
|
||||
/*
|
||||
* If non-zero, we were been asked to call the operation callback as soon
|
||||
* as possible.
|
||||
*/
|
||||
volatile int32_t interrupt;
|
||||
|
||||
/* Limit pointer for checking native stack consumption. */
|
||||
uintptr_t nativeStackLimit;
|
||||
|
||||
RuntimeFriendFields()
|
||||
: interrupt(0),
|
||||
nativeStackLimit(0) { }
|
||||
|
||||
static const RuntimeFriendFields *get(const JSRuntime *rt) {
|
||||
return reinterpret_cast<const RuntimeFriendFields *>(rt);
|
||||
}
|
||||
};
|
||||
|
||||
inline JSRuntime *
|
||||
GetRuntime(const JSContext *cx)
|
||||
{
|
||||
|
@ -2599,7 +2599,7 @@ TriggerOperationCallback(JSRuntime *rt, gcreason::Reason reason)
|
||||
void
|
||||
TriggerGC(JSRuntime *rt, gcreason::Reason reason)
|
||||
{
|
||||
JS_ASSERT(rt->onOwnerThread());
|
||||
rt->assertValidThread();
|
||||
|
||||
if (rt->isHeapBusy())
|
||||
return;
|
||||
@ -2612,7 +2612,7 @@ void
|
||||
TriggerCompartmentGC(JSCompartment *comp, gcreason::Reason reason)
|
||||
{
|
||||
JSRuntime *rt = comp->rt;
|
||||
JS_ASSERT(rt->onOwnerThread());
|
||||
rt->assertValidThread();
|
||||
|
||||
if (rt->isHeapBusy())
|
||||
return;
|
||||
@ -2636,7 +2636,7 @@ void
|
||||
MaybeGC(JSContext *cx)
|
||||
{
|
||||
JSRuntime *rt = cx->runtime;
|
||||
JS_ASSERT(rt->onOwnerThread());
|
||||
rt->assertValidThread();
|
||||
|
||||
if (rt->gcZeal() == ZealAllocValue || rt->gcZeal() == ZealPokeValue) {
|
||||
PrepareForFullGC(rt);
|
||||
@ -4824,6 +4824,16 @@ SetValidateGC(JSContext *cx, bool enabled)
|
||||
|
||||
#if defined(DEBUG) && defined(JS_GC_ZEAL) && defined(JSGC_ROOT_ANALYSIS) && !defined(JS_THREADSAFE)
|
||||
|
||||
JS_ALWAYS_INLINE void
|
||||
CheckStackRootThings(uintptr_t *w, Rooted<void*> *rooter, bool *matched)
|
||||
{
|
||||
while (rooter) {
|
||||
if (rooter->address() == static_cast<void*>(w))
|
||||
*matched = true;
|
||||
rooter = rooter->previous();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
CheckStackRoot(JSTracer *trc, uintptr_t *w)
|
||||
{
|
||||
@ -4837,21 +4847,17 @@ CheckStackRoot(JSTracer *trc, uintptr_t *w)
|
||||
if (test == CGCT_VALID) {
|
||||
bool matched = false;
|
||||
JSRuntime *rt = trc->runtime;
|
||||
for (ContextIter cx(rt); !cx.done(); cx.next()) {
|
||||
for (unsigned i = 0; i < THING_ROOT_LIMIT; i++) {
|
||||
Rooted<void*> *rooter = cx->thingGCRooters[i];
|
||||
while (rooter) {
|
||||
if (rooter->address() == static_cast<void*>(w))
|
||||
for (unsigned i = 0; i < THING_ROOT_LIMIT; i++) {
|
||||
CheckStackRootThings(w, rt->thingGCRooters[i], &matched);
|
||||
for (ContextIter cx(rt); !cx.done(); cx.next()) {
|
||||
CheckStackRootThings(w, cx->thingGCRooters[i], &matched);
|
||||
SkipRoot *skip = cx->skipGCRooters;
|
||||
while (skip) {
|
||||
if (skip->contains(reinterpret_cast<uint8_t*>(w), sizeof(w)))
|
||||
matched = true;
|
||||
rooter = rooter->previous();
|
||||
skip = skip->previous();
|
||||
}
|
||||
}
|
||||
SkipRoot *skip = cx->skipGCRooters;
|
||||
while (skip) {
|
||||
if (skip->contains(reinterpret_cast<uint8_t*>(w), sizeof(w)))
|
||||
matched = true;
|
||||
skip = skip->previous();
|
||||
}
|
||||
}
|
||||
if (!matched) {
|
||||
/*
|
||||
|
332
js/src/jsobj.cpp
332
js/src/jsobj.cpp
@ -102,266 +102,24 @@ JS_ObjectToOuterObject(JSContext *cx, JSObject *obj_)
|
||||
return GetOuterObject(cx, obj);
|
||||
}
|
||||
|
||||
static bool
|
||||
MarkSharpObjects(JSContext *cx, HandleObject obj, JSIdArray **idap, JSSharpInfo *value)
|
||||
{
|
||||
JS_CHECK_RECURSION(cx, return false);
|
||||
|
||||
JSIdArray *ida;
|
||||
|
||||
JSSharpObjectMap *map = &cx->sharpObjectMap;
|
||||
JS_ASSERT(map->depth >= 1);
|
||||
JSSharpInfo sharpid;
|
||||
JSSharpTable::Ptr p = map->table.lookup(obj);
|
||||
if (!p) {
|
||||
if (!map->table.put(obj.get(), sharpid))
|
||||
return false;
|
||||
|
||||
ida = JS_Enumerate(cx, obj);
|
||||
if (!ida)
|
||||
return false;
|
||||
|
||||
bool ok = true;
|
||||
RootedId id(cx);
|
||||
for (int i = 0, length = ida->length; i < length; i++) {
|
||||
id = ida->vector[i];
|
||||
RootedObject obj2(cx);
|
||||
RootedShape prop(cx);
|
||||
ok = JSObject::lookupGeneric(cx, obj, id, &obj2, &prop);
|
||||
if (!ok)
|
||||
break;
|
||||
if (!prop)
|
||||
continue;
|
||||
bool hasGetter, hasSetter;
|
||||
RootedValue value(cx), setter(cx);
|
||||
if (obj2->isNative()) {
|
||||
Shape *shape = (Shape *) prop;
|
||||
hasGetter = shape->hasGetterValue();
|
||||
hasSetter = shape->hasSetterValue();
|
||||
if (hasGetter)
|
||||
value = shape->getterValue();
|
||||
if (hasSetter)
|
||||
setter = shape->setterValue();
|
||||
} else {
|
||||
hasGetter = hasSetter = false;
|
||||
}
|
||||
if (hasSetter) {
|
||||
/* Mark the getter, then set val to setter. */
|
||||
if (hasGetter && value.isObject()) {
|
||||
Rooted<JSObject*> vobj(cx, &value.toObject());
|
||||
ok = MarkSharpObjects(cx, vobj, NULL, NULL);
|
||||
if (!ok)
|
||||
break;
|
||||
}
|
||||
value = setter;
|
||||
} else if (!hasGetter) {
|
||||
ok = JSObject::getGeneric(cx, obj, obj, id, &value);
|
||||
if (!ok)
|
||||
break;
|
||||
}
|
||||
if (value.isObject()) {
|
||||
Rooted<JSObject*> vobj(cx, &value.toObject());
|
||||
if (!MarkSharpObjects(cx, vobj, NULL, NULL)) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ok || !idap)
|
||||
JS_DestroyIdArray(cx, ida);
|
||||
if (!ok)
|
||||
return false;
|
||||
} else {
|
||||
if (!p->value.hasGen && !p->value.isSharp) {
|
||||
p->value.hasGen = true;
|
||||
}
|
||||
sharpid = p->value;
|
||||
ida = NULL;
|
||||
}
|
||||
if (idap)
|
||||
*idap = ida;
|
||||
if (value)
|
||||
*value = sharpid;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
js_EnterSharpObject(JSContext *cx, HandleObject obj, JSIdArray **idap, bool *alreadySeen, bool *isSharp)
|
||||
{
|
||||
if (!JS_CHECK_OPERATION_LIMIT(cx))
|
||||
return false;
|
||||
|
||||
*alreadySeen = false;
|
||||
|
||||
JSSharpObjectMap *map = &cx->sharpObjectMap;
|
||||
|
||||
JS_ASSERT_IF(map->depth == 0, map->table.count() == 0);
|
||||
JS_ASSERT_IF(map->table.count() == 0, map->depth == 0);
|
||||
|
||||
JSSharpTable::Ptr p;
|
||||
JSSharpInfo sharpid;
|
||||
JSIdArray *ida = NULL;
|
||||
|
||||
/* From this point the control must flow either through out: or bad:. */
|
||||
if (map->depth == 0) {
|
||||
JS_KEEP_ATOMS(cx->runtime);
|
||||
|
||||
/*
|
||||
* Although MarkSharpObjects tries to avoid invoking getters,
|
||||
* it ends up doing so anyway under some circumstances; for
|
||||
* example, if a wrapped object has getters, the wrapper will
|
||||
* prevent MarkSharpObjects from recognizing them as such.
|
||||
* This could lead to js_LeaveSharpObject being called while
|
||||
* MarkSharpObjects is still working.
|
||||
*
|
||||
* Increment map->depth while we call MarkSharpObjects, to
|
||||
* ensure that such a call doesn't free the hash table we're
|
||||
* still using.
|
||||
*/
|
||||
map->depth = 1;
|
||||
bool success = MarkSharpObjects(cx, obj, &ida, &sharpid);
|
||||
JS_ASSERT(map->depth == 1);
|
||||
map->depth = 0;
|
||||
if (!success)
|
||||
goto bad;
|
||||
JS_ASSERT(!sharpid.isSharp);
|
||||
if (!idap) {
|
||||
JS_DestroyIdArray(cx, ida);
|
||||
ida = NULL;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* It's possible that the value of a property has changed from the
|
||||
* first time the object's properties are traversed (when the property
|
||||
* ids are entered into the hash table) to the second (when they are
|
||||
* converted to strings), i.e., the JSObject::getProperty() call is not
|
||||
* idempotent.
|
||||
*/
|
||||
p = map->table.lookup(obj);
|
||||
if (!p) {
|
||||
if (!map->table.put(obj.get(), sharpid))
|
||||
goto bad;
|
||||
goto out;
|
||||
}
|
||||
sharpid = p->value;
|
||||
}
|
||||
|
||||
if (sharpid.isSharp || sharpid.hasGen)
|
||||
*alreadySeen = true;
|
||||
|
||||
out:
|
||||
if (!sharpid.isSharp) {
|
||||
if (idap && !ida) {
|
||||
ida = JS_Enumerate(cx, obj);
|
||||
if (!ida)
|
||||
goto bad;
|
||||
}
|
||||
map->depth++;
|
||||
}
|
||||
|
||||
if (idap)
|
||||
*idap = ida;
|
||||
*isSharp = sharpid.isSharp;
|
||||
return true;
|
||||
|
||||
bad:
|
||||
/* Clean up the sharpObjectMap table on outermost error. */
|
||||
if (map->depth == 0) {
|
||||
JS_UNKEEP_ATOMS(cx->runtime);
|
||||
map->sharpgen = 0;
|
||||
map->table.clear();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
js_LeaveSharpObject(JSContext *cx, JSIdArray **idap)
|
||||
{
|
||||
JSSharpObjectMap *map = &cx->sharpObjectMap;
|
||||
JS_ASSERT(map->depth > 0);
|
||||
if (--map->depth == 0) {
|
||||
JS_UNKEEP_ATOMS(cx->runtime);
|
||||
map->sharpgen = 0;
|
||||
map->table.clear();
|
||||
}
|
||||
if (idap) {
|
||||
if (JSIdArray *ida = *idap) {
|
||||
JS_DestroyIdArray(cx, ida);
|
||||
*idap = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
js_TraceSharpMap(JSTracer *trc, JSSharpObjectMap *map)
|
||||
{
|
||||
JS_ASSERT(map->depth > 0);
|
||||
|
||||
/*
|
||||
* During recursive calls to MarkSharpObjects a non-native object or
|
||||
* object with a custom getProperty method can potentially return an
|
||||
* unrooted value or even cut from the object graph an argument of one of
|
||||
* MarkSharpObjects recursive invocations. So we must protect map->table
|
||||
* entries against GC.
|
||||
*
|
||||
* We can not simply use JSTempValueRooter to mark the obj argument of
|
||||
* MarkSharpObjects during recursion as we have to protect *all* entries
|
||||
* in JSSharpObjectMap including those that contains otherwise unreachable
|
||||
* objects just allocated through custom getProperty. Otherwise newer
|
||||
* allocations can re-use the address of an object stored in the hashtable
|
||||
* confusing js_EnterSharpObject. So to address the problem we simply
|
||||
* mark all objects from map->table.
|
||||
*
|
||||
* An alternative "proper" solution is to use JSTempValueRooter in
|
||||
* MarkSharpObjects with code to remove during finalization entries
|
||||
* with otherwise unreachable objects. But this is way too complex
|
||||
* to justify spending efforts.
|
||||
*/
|
||||
for (JSSharpTable::Range r = map->table.all(); !r.empty(); r.popFront()) {
|
||||
JSObject *tmp = r.front().key;
|
||||
MarkObjectRoot(trc, &tmp, "sharp table entry");
|
||||
JS_ASSERT(tmp == r.front().key);
|
||||
}
|
||||
}
|
||||
|
||||
#if JS_HAS_TOSOURCE
|
||||
static JSBool
|
||||
obj_toSource(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
bool comma = false;
|
||||
const jschar *vchars;
|
||||
size_t vlength;
|
||||
Value *val;
|
||||
JSString *gsop[2];
|
||||
SkipRoot skipGsop(cx, &gsop, 2);
|
||||
|
||||
JS_CHECK_RECURSION(cx, return JS_FALSE);
|
||||
|
||||
Value localroot[4];
|
||||
PodArrayZero(localroot);
|
||||
AutoArrayRooter tvr(cx, ArrayLength(localroot), localroot);
|
||||
|
||||
/* If outermost, we need parentheses to be an expression, not a block. */
|
||||
bool outermost = (cx->sharpObjectMap.depth == 0);
|
||||
|
||||
RootedObject obj(cx, ToObject(cx, args.thisv()));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
JSIdArray *ida;
|
||||
bool alreadySeen = false;
|
||||
bool isSharp = false;
|
||||
if (!js_EnterSharpObject(cx, obj, &ida, &alreadySeen, &isSharp))
|
||||
return false;
|
||||
/* If outermost, we need parentheses to be an expression, not a block. */
|
||||
bool outermost = (cx->cycleDetectorSet.count() == 0);
|
||||
|
||||
if (!ida) {
|
||||
/*
|
||||
* We've already seen obj, so don't serialize it again (particularly as
|
||||
* we might recur in the process): just serialize an empty object.
|
||||
*/
|
||||
JS_ASSERT(alreadySeen);
|
||||
AutoCycleDetector detector(cx, obj);
|
||||
if (!detector.init())
|
||||
return false;
|
||||
if (detector.foundCycle()) {
|
||||
JSString *str = js_NewStringCopyZ(cx, "{}");
|
||||
if (!str)
|
||||
return false;
|
||||
@ -369,62 +127,36 @@ obj_toSource(JSContext *cx, unsigned argc, Value *vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_ASSERT(!isSharp);
|
||||
if (alreadySeen) {
|
||||
JSSharpTable::Ptr p = cx->sharpObjectMap.table.lookup(obj);
|
||||
JS_ASSERT(p);
|
||||
JS_ASSERT(!p->value.isSharp);
|
||||
p->value.isSharp = true;
|
||||
}
|
||||
|
||||
/* Automatically call js_LeaveSharpObject when we leave this frame. */
|
||||
class AutoLeaveSharpObject {
|
||||
JSContext *cx;
|
||||
JSIdArray *ida;
|
||||
public:
|
||||
AutoLeaveSharpObject(JSContext *cx, JSIdArray *ida) : cx(cx), ida(ida) {}
|
||||
~AutoLeaveSharpObject() {
|
||||
js_LeaveSharpObject(cx, &ida);
|
||||
}
|
||||
} autoLeaveSharpObject(cx, ida);
|
||||
|
||||
StringBuffer buf(cx);
|
||||
if (outermost && !buf.append('('))
|
||||
return false;
|
||||
if (!buf.append('{'))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* We have four local roots for cooked and raw value GC safety. Hoist the
|
||||
* "localroot + 2" out of the loop using the val local, which refers to
|
||||
* the raw (unconverted, "uncooked") values.
|
||||
*/
|
||||
val = localroot + 2;
|
||||
Value val[2];
|
||||
PodArrayZero(val);
|
||||
AutoArrayRooter tvr2(cx, ArrayLength(val), val);
|
||||
|
||||
RootedId id(cx);
|
||||
for (int i = 0; i < ida->length; i++) {
|
||||
/* Get strings for id and value and GC-root them via vp. */
|
||||
id = ida->vector[i];
|
||||
Rooted<JSLinearString*> idstr(cx);
|
||||
JSString *gsop[2];
|
||||
SkipRoot skipGsop(cx, &gsop, 2);
|
||||
|
||||
AutoIdVector idv(cx);
|
||||
if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &idv))
|
||||
return false;
|
||||
|
||||
bool comma = false;
|
||||
for (size_t i = 0; i < idv.length(); ++i) {
|
||||
RootedId id(cx, idv[i]);
|
||||
RootedObject obj2(cx);
|
||||
RootedShape prop(cx);
|
||||
if (!JSObject::lookupGeneric(cx, obj, id, &obj2, &prop))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Convert id to a value and then to a string. Decide early whether we
|
||||
* prefer get/set or old getter/setter syntax.
|
||||
*/
|
||||
JSString *s = ToString(cx, IdToValue(id));
|
||||
if (!s || !(idstr = s->ensureLinear(cx)))
|
||||
RootedShape shape(cx);
|
||||
if (!JSObject::lookupGeneric(cx, obj, id, &obj2, &shape))
|
||||
return false;
|
||||
|
||||
/* Decide early whether we prefer get/set or old getter/setter syntax. */
|
||||
int valcnt = 0;
|
||||
if (prop) {
|
||||
if (shape) {
|
||||
bool doGet = true;
|
||||
if (obj2->isNative()) {
|
||||
Shape *shape = (Shape *) prop;
|
||||
unsigned attrs = shape->attributes();
|
||||
if (attrs & JSPROP_GETTER) {
|
||||
doGet = false;
|
||||
@ -448,13 +180,22 @@ obj_toSource(JSContext *cx, unsigned argc, Value *vp)
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert id to a linear string. */
|
||||
RawString s = ToString(cx, IdToValue(id));
|
||||
if (!s)
|
||||
return false;
|
||||
Rooted<JSLinearString*> idstr(cx, s->ensureLinear(cx));
|
||||
if (!idstr)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* If id is a string that's not an identifier, or if it's a negative
|
||||
* integer, then it must be quoted.
|
||||
*/
|
||||
if (JSID_IS_ATOM(id)
|
||||
? !IsIdentifier(idstr)
|
||||
: (!JSID_IS_INT(id) || JSID_TO_INT(id) < 0)) {
|
||||
: (!JSID_IS_INT(id) || JSID_TO_INT(id) < 0))
|
||||
{
|
||||
s = js_QuoteString(cx, idstr, jschar('\''));
|
||||
if (!s || !(idstr = s->ensureLinear(cx)))
|
||||
return false;
|
||||
@ -469,14 +210,13 @@ obj_toSource(JSContext *cx, unsigned argc, Value *vp)
|
||||
continue;
|
||||
|
||||
/* Convert val[j] to its canonical source form. */
|
||||
JSString *valstr = js_ValueToSource(cx, val[j]);
|
||||
RootedString valstr(cx, js_ValueToSource(cx, val[j]));
|
||||
if (!valstr)
|
||||
return false;
|
||||
localroot[j].setString(valstr); /* local root */
|
||||
vchars = valstr->getChars(cx);
|
||||
const jschar *vchars = valstr->getChars(cx);
|
||||
if (!vchars)
|
||||
return false;
|
||||
vlength = valstr->length();
|
||||
size_t vlength = valstr->length();
|
||||
|
||||
/*
|
||||
* Remove '(function ' from the beginning of valstr and ')' from the
|
||||
@ -536,7 +276,7 @@ obj_toSource(JSContext *cx, unsigned argc, Value *vp)
|
||||
if (outermost && !buf.append(')'))
|
||||
return false;
|
||||
|
||||
JSString *str = buf.finishString();
|
||||
RawString str = buf.finishString();
|
||||
if (!str)
|
||||
return false;
|
||||
args.rval().setString(str);
|
||||
|
@ -1080,20 +1080,6 @@ class ValueArray {
|
||||
ValueArray(js::Value *v, size_t c) : array(v), length(c) {}
|
||||
};
|
||||
|
||||
/* For manipulating JSContext::sharpObjectMap. */
|
||||
extern bool
|
||||
js_EnterSharpObject(JSContext *cx, js::HandleObject obj, JSIdArray **idap, bool *alreadySeen, bool *isSharp);
|
||||
|
||||
extern void
|
||||
js_LeaveSharpObject(JSContext *cx, JSIdArray **idap);
|
||||
|
||||
/*
|
||||
* Mark objects stored in map if GC happens between js_EnterSharpObject
|
||||
* and js_LeaveSharpObject. GC calls this when map->depth > 0.
|
||||
*/
|
||||
extern void
|
||||
js_TraceSharpMap(JSTracer *trc, JSSharpObjectMap *map);
|
||||
|
||||
extern JSBool
|
||||
js_HasOwnPropertyHelper(JSContext *cx, js::LookupGenericOp lookup, js::HandleObject obj,
|
||||
js::HandleId id, js::MutableHandleValue rval);
|
||||
|
@ -51,7 +51,6 @@ typedef struct JSArgumentFormatMap JSArgumentFormatMap;
|
||||
typedef struct JSGCThing JSGCThing;
|
||||
typedef struct JSGenerator JSGenerator;
|
||||
typedef struct JSNativeEnumerator JSNativeEnumerator;
|
||||
typedef struct JSSharpObjectMap JSSharpObjectMap;
|
||||
typedef struct JSTryNote JSTryNote;
|
||||
|
||||
/* Friend "Advanced API" typedefs. */
|
||||
|
@ -290,6 +290,33 @@ struct ContextFriendFields {
|
||||
#endif
|
||||
};
|
||||
|
||||
struct RuntimeFriendFields {
|
||||
/*
|
||||
* If non-zero, we were been asked to call the operation callback as soon
|
||||
* as possible.
|
||||
*/
|
||||
volatile int32_t interrupt;
|
||||
|
||||
/* Limit pointer for checking native stack consumption. */
|
||||
uintptr_t nativeStackLimit;
|
||||
|
||||
#if defined(JSGC_ROOT_ANALYSIS) || defined(JSGC_USE_EXACT_ROOTING)
|
||||
/*
|
||||
* Stack allocated GC roots for stack GC heap pointers, which may be
|
||||
* overwritten if moved during a GC.
|
||||
*/
|
||||
Rooted<void*> *thingGCRooters[THING_ROOT_LIMIT];
|
||||
#endif
|
||||
|
||||
RuntimeFriendFields()
|
||||
: interrupt(0),
|
||||
nativeStackLimit(0) { }
|
||||
|
||||
static const RuntimeFriendFields *get(const JSRuntime *rt) {
|
||||
return reinterpret_cast<const RuntimeFriendFields *>(rt);
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace JS */
|
||||
|
||||
#endif /* __cplusplus */
|
||||
|
@ -1456,7 +1456,8 @@ mjit::Compiler::finishThisUp()
|
||||
}
|
||||
|
||||
JSScript *script =
|
||||
(from.inlineIndex == UINT32_MAX) ? outerScript : inlineFrames[from.inlineIndex]->script;
|
||||
(from.inlineIndex == UINT32_MAX) ? outerScript.get()
|
||||
: inlineFrames[from.inlineIndex]->script;
|
||||
uint32_t codeOffset = from.ool
|
||||
? masm.size() + from.returnOffset
|
||||
: from.returnOffset;
|
||||
|
@ -23,15 +23,7 @@ function test()
|
||||
q = [];
|
||||
q.__defineGetter__("0", q.toString);
|
||||
q[2] = q;
|
||||
try
|
||||
{
|
||||
q.toSource();
|
||||
throw new Error("didn't throw");
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
assertEq(e instanceof InternalError, true, "bad error: " + e);
|
||||
}
|
||||
assertEq(q.toSource(), "[\"\", , []]", "wrong string");
|
||||
|
||||
reportCompare(expect, actual, summary);
|
||||
|
||||
|
@ -244,6 +244,10 @@ typedef uint64_t nsFrameState;
|
||||
// placeholder. This can happen for two reasons: (1) the float was
|
||||
// split, and this piece is the continuation, or (2) the entire float
|
||||
// didn't fit on the page.
|
||||
// Note that this bit is also shared by text frames for
|
||||
// TEXT_FORCE_TRIM_WHITESPACE. That's OK because we only check the
|
||||
// NS_FRAME_IS_PUSHED_FLOAT bit on frames which we already know are
|
||||
// out-of-flow.
|
||||
#define NS_FRAME_IS_PUSHED_FLOAT NS_FRAME_STATE_BIT(32)
|
||||
|
||||
// This bit acts as a loop flag for recursive paint server drawing.
|
||||
|
@ -21,6 +21,10 @@ class PropertyProvider;
|
||||
// reflow
|
||||
#define TEXT_HAS_NONCOLLAPSED_CHARACTERS NS_FRAME_STATE_BIT(31)
|
||||
|
||||
// This state bit is set on frames which are forced to trim their leading and
|
||||
// trailing whitespaces
|
||||
#define TEXT_FORCE_TRIM_WHITESPACE NS_FRAME_STATE_BIT(32)
|
||||
|
||||
#define TEXT_HAS_FONT_INFLATION NS_FRAME_STATE_BIT(61)
|
||||
|
||||
typedef nsFrame nsTextFrameBase;
|
||||
|
@ -196,6 +196,9 @@ NS_DECLARE_FRAME_PROPERTY(FontSizeInflationProperty, nullptr)
|
||||
// nsTextFrame.h has
|
||||
// #define TEXT_HAS_NONCOLLAPSED_CHARACTERS NS_FRAME_STATE_BIT(31)
|
||||
|
||||
// nsTextFrame.h has
|
||||
// #define TEXT_FORCE_TRIM_WHITESPACE NS_FRAME_STATE_BIT(32)
|
||||
|
||||
// Set when this text frame is mentioned in the userdata for the
|
||||
// uninflated textrun property
|
||||
#define TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA NS_FRAME_STATE_BIT(60)
|
||||
@ -7612,7 +7615,8 @@ nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
|
||||
length = newLineOffset + 1 - offset;
|
||||
}
|
||||
}
|
||||
if (atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) {
|
||||
if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) ||
|
||||
(GetStateBits() & TEXT_FORCE_TRIM_WHITESPACE)) {
|
||||
// Skip leading whitespace. Make sure we don't skip a 'pre-line'
|
||||
// newline if there is one.
|
||||
int32_t skipLength = newLineOffset >= 0 ? length - 1 : length;
|
||||
@ -7770,7 +7774,8 @@ nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
|
||||
bool usedHyphenation;
|
||||
gfxFloat trimmedWidth = 0;
|
||||
gfxFloat availWidth = aAvailableWidth;
|
||||
bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant();
|
||||
bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() ||
|
||||
(GetStateBits() & TEXT_FORCE_TRIM_WHITESPACE);
|
||||
int32_t unusedOffset;
|
||||
gfxBreakPriority breakPriority;
|
||||
aLineLayout.GetLastOptionalBreakPosition(&unusedOffset, &breakPriority);
|
||||
@ -7839,11 +7844,12 @@ nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
|
||||
// the line. (If we actually do end up at the end of the line, we'll have
|
||||
// to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid
|
||||
// having to re-do it.)
|
||||
if (brokeText) {
|
||||
if (brokeText ||
|
||||
(GetStateBits() & TEXT_FORCE_TRIM_WHITESPACE)) {
|
||||
// We're definitely going to break so our trailing whitespace should
|
||||
// definitely be timmed. Record that we've already done it.
|
||||
// definitely be trimmed. Record that we've already done it.
|
||||
AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
|
||||
} else {
|
||||
} else if (!(GetStateBits() & TEXT_FORCE_TRIM_WHITESPACE)) {
|
||||
// We might not be at the end of the line. (Note that even if this frame
|
||||
// ends in breakable whitespace, it might not be at the end of the line
|
||||
// because it might be followed by breakable, but preformatted, whitespace.)
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsCSSFrameConstructor.h"
|
||||
#include "nsMathMLTokenFrame.h"
|
||||
#include "nsTextFrame.h"
|
||||
|
||||
nsIFrame*
|
||||
NS_NewMathMLTokenFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
|
||||
@ -68,6 +69,7 @@ nsMathMLTokenFrame::GetMathMLFrameType()
|
||||
else if(style.EqualsLiteral("invariant")) {
|
||||
nsAutoString data;
|
||||
nsContentUtils::GetNodeTextContent(mContent, false, data);
|
||||
data.CompressWhitespace();
|
||||
eMATHVARIANT variant = nsMathMLOperators::LookupInvariantChar(data);
|
||||
|
||||
switch (variant) {
|
||||
@ -85,35 +87,19 @@ nsMathMLTokenFrame::GetMathMLFrameType()
|
||||
return eMathMLFrameType_UprightIdentifier;
|
||||
}
|
||||
|
||||
static void
|
||||
CompressWhitespace(nsIContent* aContent)
|
||||
void
|
||||
nsMathMLTokenFrame::ForceTrimChildTextFrames()
|
||||
{
|
||||
uint32_t numKids = aContent->GetChildCount();
|
||||
for (uint32_t kid = 0; kid < numKids; kid++) {
|
||||
nsIContent* cont = aContent->GetChildAt(kid);
|
||||
if (cont && cont->IsNodeOfType(nsINode::eTEXT)) {
|
||||
nsAutoString text;
|
||||
cont->AppendTextTo(text);
|
||||
text.CompressWhitespace();
|
||||
cont->SetText(text, false); // not meant to be used if notify is needed
|
||||
// Set flags on child text frames to force them to trim their leading and
|
||||
// trailing whitespaces.
|
||||
for (nsIFrame* childFrame = GetFirstPrincipalChild(); childFrame;
|
||||
childFrame = childFrame->GetNextSibling()) {
|
||||
if (childFrame->GetType() == nsGkAtoms::textFrame) {
|
||||
childFrame->AddStateBits(TEXT_FORCE_TRIM_WHITESPACE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMathMLTokenFrame::Init(nsIContent* aContent,
|
||||
nsIFrame* aParent,
|
||||
nsIFrame* aPrevInFlow)
|
||||
{
|
||||
// leading and trailing whitespace doesn't count -- bug 15402
|
||||
// brute force removal for people who do <mi> a </mi> instead of <mi>a</mi>
|
||||
// XXX the best fix is to skip these in nsTextFrame
|
||||
CompressWhitespace(aContent);
|
||||
|
||||
// let the base class do its Init()
|
||||
return nsMathMLContainerFrame::Init(aContent, aParent, aPrevInFlow);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMathMLTokenFrame::SetInitialChildList(ChildListID aListID,
|
||||
nsFrameList& aChildList)
|
||||
@ -123,11 +109,41 @@ nsMathMLTokenFrame::SetInitialChildList(ChildListID aListID,
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
ForceTrimChildTextFrames();
|
||||
|
||||
SetQuotes(false);
|
||||
ProcessTextData();
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMathMLTokenFrame::AppendFrames(ChildListID aListID,
|
||||
nsFrameList& aChildList)
|
||||
{
|
||||
nsresult rv = nsMathMLContainerFrame::AppendFrames(aListID, aChildList);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
ForceTrimChildTextFrames();
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMathMLTokenFrame::InsertFrames(ChildListID aListID,
|
||||
nsIFrame* aPrevFrame,
|
||||
nsFrameList& aChildList)
|
||||
{
|
||||
nsresult rv = nsMathMLContainerFrame::InsertFrames(aListID, aPrevFrame,
|
||||
aChildList);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
ForceTrimChildTextFrames();
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsMathMLTokenFrame::Reflow(nsPresContext* aPresContext,
|
||||
nsHTMLReflowMetrics& aDesiredSize,
|
||||
@ -297,6 +313,7 @@ nsMathMLTokenFrame::SetTextStyle()
|
||||
// Get the text content that we enclose and its length
|
||||
nsAutoString data;
|
||||
nsContentUtils::GetNodeTextContent(mContent, false, data);
|
||||
data.CompressWhitespace();
|
||||
int32_t length = data.Length();
|
||||
if (!length)
|
||||
return false;
|
||||
|
@ -34,15 +34,19 @@ public:
|
||||
|
||||
virtual eMathMLFrameType GetMathMLFrameType();
|
||||
|
||||
NS_IMETHOD
|
||||
Init(nsIContent* aContent,
|
||||
nsIFrame* aParent,
|
||||
nsIFrame* aPrevInFlow);
|
||||
|
||||
NS_IMETHOD
|
||||
SetInitialChildList(ChildListID aListID,
|
||||
nsFrameList& aChildList);
|
||||
|
||||
NS_IMETHOD
|
||||
AppendFrames(ChildListID aListID,
|
||||
nsFrameList& aChildList);
|
||||
|
||||
NS_IMETHOD
|
||||
InsertFrames(ChildListID aListID,
|
||||
nsIFrame* aPrevFrame,
|
||||
nsFrameList& aChildList);
|
||||
|
||||
NS_IMETHOD
|
||||
Reflow(nsPresContext* aPresContext,
|
||||
nsHTMLReflowMetrics& aDesiredSize,
|
||||
@ -83,6 +87,8 @@ protected:
|
||||
|
||||
// helper to set the quotes of <ms>
|
||||
void SetQuotes(bool aNotify);
|
||||
|
||||
void ForceTrimChildTextFrames();
|
||||
};
|
||||
|
||||
#endif /* nsMathMLTokentFrame_h___ */
|
||||
|
@ -122,6 +122,7 @@ nsMathMLmoFrame::ProcessTextData()
|
||||
|
||||
nsAutoString data;
|
||||
nsContentUtils::GetNodeTextContent(mContent, false, data);
|
||||
data.CompressWhitespace();
|
||||
int32_t length = data.Length();
|
||||
PRUnichar ch = (length == 0) ? kNullCh : data[0];
|
||||
|
||||
|
@ -104,3 +104,8 @@ fails == mstyle-5.xhtml mstyle-5-ref.xhtml # See bug 569125#c29
|
||||
== maction-dynamic-2.html maction-dynamic-2-ref.html
|
||||
== mo-lspace-rspace.html mo-lspace-rspace-ref.html
|
||||
== maction-dynamic-3.html maction-dynamic-3-ref.html
|
||||
== whitespace-trim-1.html whitespace-trim-1-ref.html
|
||||
== whitespace-trim-2.html whitespace-trim-2-ref.html
|
||||
== whitespace-trim-3.html whitespace-trim-3-ref.html
|
||||
fails == whitespace-trim-4.html whitespace-trim-4-ref.html # Bug 560100
|
||||
== whitespace-trim-5.html whitespace-trim-5-ref.html
|
||||
|
2
layout/reftests/mathml/whitespace-trim-1-ref.html
Normal file
2
layout/reftests/mathml/whitespace-trim-1-ref.html
Normal file
@ -0,0 +1,2 @@
|
||||
<!DOCTYPE html>
|
||||
<math><mo minsize="10em">(</mo></math>
|
2
layout/reftests/mathml/whitespace-trim-1.html
Normal file
2
layout/reftests/mathml/whitespace-trim-1.html
Normal file
@ -0,0 +1,2 @@
|
||||
<!DOCTYPE html>
|
||||
<math><mo minsize="10em"> ( </mo></math>
|
2
layout/reftests/mathml/whitespace-trim-2-ref.html
Normal file
2
layout/reftests/mathml/whitespace-trim-2-ref.html
Normal file
@ -0,0 +1,2 @@
|
||||
<!DOCTYPE html>
|
||||
<math><mi>(</mi></math>
|
2
layout/reftests/mathml/whitespace-trim-2.html
Normal file
2
layout/reftests/mathml/whitespace-trim-2.html
Normal file
@ -0,0 +1,2 @@
|
||||
<!DOCTYPE html>
|
||||
<math><mi> ( </mi></math>
|
2
layout/reftests/mathml/whitespace-trim-3-ref.html
Normal file
2
layout/reftests/mathml/whitespace-trim-3-ref.html
Normal file
@ -0,0 +1,2 @@
|
||||
<!DOCTYPE html>
|
||||
<math><mi mathvariant="italic">ℎ</mi></math>
|
2
layout/reftests/mathml/whitespace-trim-3.html
Normal file
2
layout/reftests/mathml/whitespace-trim-3.html
Normal file
@ -0,0 +1,2 @@
|
||||
<!DOCTYPE html>
|
||||
<math><mi mathvariant="italic"> ℎ </mi></math>
|
2
layout/reftests/mathml/whitespace-trim-4-ref.html
Normal file
2
layout/reftests/mathml/whitespace-trim-4-ref.html
Normal file
@ -0,0 +1,2 @@
|
||||
<!DOCTYPE html>
|
||||
<math><ms>x</ms></math>
|
2
layout/reftests/mathml/whitespace-trim-4.html
Normal file
2
layout/reftests/mathml/whitespace-trim-4.html
Normal file
@ -0,0 +1,2 @@
|
||||
<!DOCTYPE html>
|
||||
<math><ms> x </ms></math>
|
5
layout/reftests/mathml/whitespace-trim-5-ref.html
Normal file
5
layout/reftests/mathml/whitespace-trim-5-ref.html
Normal file
@ -0,0 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<math><mtext>|</mtext><mi>x</mi><mtext>|</mtext></math>
|
||||
<math><mtext>|</mtext><mn>x</mn><mtext>|</mtext></math>
|
||||
<math><mtext>|</mtext><mo>x</mo><mtext>|</mtext></math>
|
||||
<math><mtext>|</mtext><mtext>x</mtext><mtext>|</mtext></math>
|
5
layout/reftests/mathml/whitespace-trim-5.html
Normal file
5
layout/reftests/mathml/whitespace-trim-5.html
Normal file
@ -0,0 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<math><mtext>|</mtext><mi> x </mi><mtext>|</mtext></math>
|
||||
<math><mtext>|</mtext><mn> x </mn><mtext>|</mtext></math>
|
||||
<math><mtext>|</mtext><mo> x </mo><mtext>|</mtext></math>
|
||||
<math><mtext>|</mtext><mtext> x </mtext><mtext>|</mtext></math>
|
@ -14,7 +14,6 @@ import org.mozilla.gecko.sync.ThreadPool;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.config.AccountPickler;
|
||||
import org.mozilla.gecko.sync.config.ClientRecordTerminator;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
|
||||
import org.mozilla.gecko.sync.setup.activities.SetupSyncActivity;
|
||||
|
||||
import android.accounts.AbstractAccountAuthenticator;
|
||||
@ -222,7 +221,7 @@ public class SyncAuthenticatorService extends Service {
|
||||
* Account disappeared.
|
||||
*/
|
||||
@Override
|
||||
public Bundle getAccountRemovalAllowed(final AccountAuthenticatorResponse response, final Account account)
|
||||
public Bundle getAccountRemovalAllowed(final AccountAuthenticatorResponse response, Account account)
|
||||
throws NetworkErrorException {
|
||||
Bundle result = super.getAccountRemovalAllowed(response, account);
|
||||
|
||||
@ -237,11 +236,13 @@ public class SyncAuthenticatorService extends Service {
|
||||
return result;
|
||||
}
|
||||
|
||||
final String accountName = account.name;
|
||||
|
||||
// Delete the Account pickle in the background.
|
||||
ThreadPool.run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Logger.info(LOG_TAG, "Account named " + account.name + " being removed; " +
|
||||
Logger.info(LOG_TAG, "Account named " + accountName + " being removed; " +
|
||||
"deleting saved pickle file '" + Constants.ACCOUNT_PICKLE_FILENAME + "'.");
|
||||
try {
|
||||
AccountPickler.deletePickle(mContext, Constants.ACCOUNT_PICKLE_FILENAME);
|
||||
@ -252,26 +253,34 @@ public class SyncAuthenticatorService extends Service {
|
||||
}
|
||||
});
|
||||
|
||||
// Bug 770785: delete the Account's client record in the background. We want
|
||||
// to get the Account's data synchronously, though, since it is possible the
|
||||
// Account object will be invalid by the time the Runnable executes. We
|
||||
// don't need to worry about accessing prefs too early since deleting the
|
||||
// Account doesn't remove them -- at least, not yet.
|
||||
SyncAccountParameters tempParams = null;
|
||||
try {
|
||||
tempParams = SyncAccounts.blockingFromAndroidAccountV0(mContext, AccountManager.get(mContext), account);
|
||||
} catch (Exception e) {
|
||||
// Do nothing. Null parameters are handled in the Runnable.
|
||||
}
|
||||
final SyncAccountParameters params = tempParams;
|
||||
// Bug 770785: delete the Account's client record in the background. We
|
||||
// want to get the Account's data synchronously, though, since it is
|
||||
// possible the Account object will be invalid by the time the Runnable
|
||||
// executes. We don't need to worry about accessing prefs too early since
|
||||
// deleting the Account doesn't remove them -- at least, not yet. We would
|
||||
// prefer to use SyncAccounts.blockingFromAndroidAccountV0, but that
|
||||
// hangs, possibly because the Account Manager doesn't appreciate giving
|
||||
// out an auth token while deleting the account.
|
||||
|
||||
final AccountManager accountManager = AccountManager.get(mContext);
|
||||
final String password = accountManager.getPassword(account);
|
||||
final String serverURL = accountManager.getUserData(account, Constants.OPTION_SERVER);
|
||||
|
||||
ThreadPool.run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Logger.info(LOG_TAG, "Account named " + account.name + " being removed; " +
|
||||
Logger.info(LOG_TAG, "Account named " + accountName + " being removed; " +
|
||||
"deleting client record from server.");
|
||||
|
||||
if (params == null || params.username == null || params.password == null) {
|
||||
String encodedUsername;
|
||||
try {
|
||||
encodedUsername = Utils.usernameFromAccount(accountName);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception deleting client record from server; ignoring.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (accountName == null || encodedUsername == null || password == null || serverURL == null) {
|
||||
Logger.warn(LOG_TAG, "Account parameters were null; not deleting client record from server.");
|
||||
return;
|
||||
}
|
||||
@ -287,7 +296,7 @@ public class SyncAuthenticatorService extends Service {
|
||||
|
||||
SharedPreferences prefs;
|
||||
try {
|
||||
prefs = Utils.getSharedPreferences(mContext, product, params.username, params.serverURL, profile, version);
|
||||
prefs = Utils.getSharedPreferences(mContext, product, encodedUsername, serverURL, profile, version);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Caught exception fetching preferences; not deleting client record from server.", e);
|
||||
return;
|
||||
@ -307,7 +316,7 @@ public class SyncAuthenticatorService extends Service {
|
||||
}
|
||||
|
||||
try {
|
||||
ClientRecordTerminator.deleteClientRecord(params.username, params.password, clusterURL, clientGuid);
|
||||
ClientRecordTerminator.deleteClientRecord(encodedUsername, password, clusterURL, clientGuid);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception deleting client record from server; ignoring.", e);
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
// 2) #include IPDL boilerplate, and then undef LOG so our LOG wins.
|
||||
// 3) nsNetModule.cpp does its own crazy stuff with #including prlog.h
|
||||
// multiple times; allow it to define ALLOW_LATE_NSHTTP_H_INCLUDE to bypass
|
||||
// check.
|
||||
// check.
|
||||
#if defined(PR_LOG) && !defined(ALLOW_LATE_NSHTTP_H_INCLUDE)
|
||||
#error "If nsHttp.h #included it must come before any IPDL-generated files or other files that #include prlog.h"
|
||||
#endif
|
||||
@ -181,7 +181,7 @@ struct nsHttp
|
||||
}
|
||||
|
||||
// Declare all atoms
|
||||
//
|
||||
//
|
||||
// The atom names and values are stored in nsHttpAtomList.h and are brought
|
||||
// to you by the magic of C preprocessing. Add new atoms to nsHttpAtomList
|
||||
// and all support logic will be auto-generated.
|
||||
@ -203,8 +203,8 @@ PRTimeToSeconds(PRTime t_usec)
|
||||
|
||||
#define NowInSeconds() PRTimeToSeconds(PR_Now())
|
||||
|
||||
// round q-value to one decimal place; return most significant digit as uint.
|
||||
#define QVAL_TO_UINT(q) ((unsigned int) ((q + 0.05) * 10.0))
|
||||
// Round q-value to 2 decimal places; return 2 most significant digits as uint.
|
||||
#define QVAL_TO_UINT(q) ((unsigned int) ((q + 0.005) * 100.0))
|
||||
|
||||
#define HTTP_LWS " \t"
|
||||
#define HTTP_HEADER_VALUE_SEPS HTTP_LWS ","
|
||||
|
@ -234,7 +234,7 @@ nsHttpHandler::Init()
|
||||
if (prefBranch) {
|
||||
prefBranch->AddObserver(HTTP_PREF_PREFIX, this, true);
|
||||
prefBranch->AddObserver(UA_PREF_PREFIX, this, true);
|
||||
prefBranch->AddObserver(INTL_ACCEPT_LANGUAGES, this, true);
|
||||
prefBranch->AddObserver(INTL_ACCEPT_LANGUAGES, this, true);
|
||||
prefBranch->AddObserver(NETWORK_ENABLEIDN, this, true);
|
||||
prefBranch->AddObserver(BROWSER_PREF("disk_cache_ssl"), this, true);
|
||||
prefBranch->AddObserver(DONOTTRACK_HEADER_ENABLED, this, true);
|
||||
@ -292,8 +292,8 @@ nsHttpHandler::Init()
|
||||
// Bring alive the objects in the http-protocol-startup category
|
||||
NS_CreateServicesFromCategory(NS_HTTP_STARTUP_CATEGORY,
|
||||
static_cast<nsISupports*>(static_cast<void*>(this)),
|
||||
NS_HTTP_STARTUP_TOPIC);
|
||||
|
||||
NS_HTTP_STARTUP_TOPIC);
|
||||
|
||||
mObserverService = mozilla::services::GetObserverService();
|
||||
if (mObserverService) {
|
||||
mObserverService->AddObserver(this, "profile-change-net-teardown", true);
|
||||
@ -303,7 +303,7 @@ nsHttpHandler::Init()
|
||||
mObserverService->AddObserver(this, "net:prune-dead-connections", true);
|
||||
mObserverService->AddObserver(this, "net:failed-to-process-uri-content", true);
|
||||
}
|
||||
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -363,10 +363,10 @@ nsHttpHandler::AddStandardRequestHeaders(nsHttpHeaderArray *request,
|
||||
// transparent proxies) can result.
|
||||
//
|
||||
// However, we need to send something so that we can use keepalive
|
||||
// with HTTP/1.0 servers/proxies. We use "Proxy-Connection:" when
|
||||
// with HTTP/1.0 servers/proxies. We use "Proxy-Connection:" when
|
||||
// we're talking to an http proxy, and "Connection:" otherwise.
|
||||
// We no longer send the Keep-Alive request header.
|
||||
|
||||
|
||||
NS_NAMED_LITERAL_CSTRING(close, "close");
|
||||
NS_NAMED_LITERAL_CSTRING(keepAlive, "keep-alive");
|
||||
|
||||
@ -435,7 +435,7 @@ nsHttpHandler::GetCookieService()
|
||||
return mCookieService;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsresult
|
||||
nsHttpHandler::GetIOService(nsIIOService** result)
|
||||
{
|
||||
NS_ADDREF(*result = mIOService);
|
||||
@ -452,7 +452,7 @@ nsHttpHandler::Get32BitsOfPseudoRandom()
|
||||
// 15 or 31 bits are common amounts.
|
||||
|
||||
PR_STATIC_ASSERT(RAND_MAX >= 0xfff);
|
||||
|
||||
|
||||
#if RAND_MAX < 0xffffU
|
||||
return ((uint16_t) rand() << 20) |
|
||||
(((uint16_t) rand() & 0xfff) << 8) |
|
||||
@ -521,10 +521,10 @@ nsHttpHandler::BuildUserAgent()
|
||||
|
||||
// preallocate to worst-case size, which should always be better
|
||||
// than if we didn't preallocate at all.
|
||||
mUserAgent.SetCapacity(mLegacyAppName.Length() +
|
||||
mLegacyAppVersion.Length() +
|
||||
mUserAgent.SetCapacity(mLegacyAppName.Length() +
|
||||
mLegacyAppVersion.Length() +
|
||||
#ifndef UA_SPARE_PLATFORM
|
||||
mPlatform.Length() +
|
||||
mPlatform.Length() +
|
||||
#endif
|
||||
mOscpu.Length() +
|
||||
mMisc.Length() +
|
||||
@ -674,7 +674,7 @@ nsHttpHandler::InitUserAgentComponents()
|
||||
}
|
||||
#elif defined (XP_UNIX)
|
||||
struct utsname name;
|
||||
|
||||
|
||||
int ret = uname(&name);
|
||||
if (ret >= 0) {
|
||||
nsCAutoString buf;
|
||||
@ -981,7 +981,7 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
|
||||
if (NS_SUCCEEDED(rv))
|
||||
SetAccept(accept);
|
||||
}
|
||||
|
||||
|
||||
if (PREF_CHANGED(HTTP_PREF("accept-encoding"))) {
|
||||
nsXPIDLCString acceptEncodings;
|
||||
rv = prefs->GetCharPref(HTTP_PREF("accept-encoding"),
|
||||
@ -1034,7 +1034,7 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
|
||||
mEnforceAssocReq = cVar;
|
||||
}
|
||||
|
||||
// enable Persistent caching for HTTPS - bug#205921
|
||||
// enable Persistent caching for HTTPS - bug#205921
|
||||
if (PREF_CHANGED(BROWSER_PREF("disk_cache_ssl"))) {
|
||||
cVar = false;
|
||||
rv = prefs->GetBoolPref(BROWSER_PREF("disk_cache_ssl"), &cVar);
|
||||
@ -1141,7 +1141,7 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
|
||||
pls->ToString(getter_Copies(uval));
|
||||
if (uval)
|
||||
SetAcceptLanguages(NS_ConvertUTF16toUTF8(uval).get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@ -1221,7 +1221,7 @@ PrepareAcceptLanguages(const char *i_AcceptLanguages, nsACString &o_AcceptLangua
|
||||
if (!i_AcceptLanguages)
|
||||
return NS_OK;
|
||||
|
||||
uint32_t n, size, wrote;
|
||||
uint32_t n, count_n, size, wrote;
|
||||
double q, dec;
|
||||
char *p, *p2, *token, *q_Accept, *o_Accept;
|
||||
const char *comma;
|
||||
@ -1244,7 +1244,7 @@ PrepareAcceptLanguages(const char *i_AcceptLanguages, nsACString &o_AcceptLangua
|
||||
*q_Accept = '\0';
|
||||
q = 1.0;
|
||||
dec = q / (double) n;
|
||||
n = 0;
|
||||
count_n = 0;
|
||||
p2 = q_Accept;
|
||||
for (token = nsCRT::strtok(o_Accept, ",", &p);
|
||||
token != (char *) 0;
|
||||
@ -1257,12 +1257,28 @@ PrepareAcceptLanguages(const char *i_AcceptLanguages, nsACString &o_AcceptLangua
|
||||
*trim = '\0';
|
||||
|
||||
if (*token != '\0') {
|
||||
comma = n++ != 0 ? "," : ""; // delimiter if not first item
|
||||
comma = count_n++ != 0 ? "," : ""; // delimiter if not first item
|
||||
uint32_t u = QVAL_TO_UINT(q);
|
||||
if (u < 10)
|
||||
wrote = PR_snprintf(p2, available, "%s%s;q=0.%u", comma, token, u);
|
||||
else
|
||||
|
||||
// Only display q-value if less than 1.00.
|
||||
if (u < 100) {
|
||||
const char *qval_str;
|
||||
|
||||
// With a small number of languages, one decimal place is enough to prevent duplicate q-values.
|
||||
// Also, trailing zeroes do not add any information, so they can be removed.
|
||||
if ((n < 10) || ((u % 10) == 0)) {
|
||||
u = (u + 5) / 10;
|
||||
qval_str = "%s%s;q=0.%u";
|
||||
} else {
|
||||
// Values below 10 require zero padding.
|
||||
qval_str = "%s%s;q=0.%02u";
|
||||
}
|
||||
|
||||
wrote = PR_snprintf(p2, available, qval_str, comma, token, u);
|
||||
} else {
|
||||
wrote = PR_snprintf(p2, available, "%s%s", comma, token);
|
||||
}
|
||||
|
||||
q -= dec;
|
||||
p2 += wrote;
|
||||
available -= wrote;
|
||||
@ -1278,7 +1294,7 @@ PrepareAcceptLanguages(const char *i_AcceptLanguages, nsACString &o_AcceptLangua
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsHttpHandler::SetAcceptLanguages(const char *aAcceptLanguages)
|
||||
nsHttpHandler::SetAcceptLanguages(const char *aAcceptLanguages)
|
||||
{
|
||||
nsCAutoString buf;
|
||||
nsresult rv = PrepareAcceptLanguages(aAcceptLanguages, buf);
|
||||
@ -1288,14 +1304,14 @@ nsHttpHandler::SetAcceptLanguages(const char *aAcceptLanguages)
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsHttpHandler::SetAccept(const char *aAccept)
|
||||
nsHttpHandler::SetAccept(const char *aAccept)
|
||||
{
|
||||
mAccept = aAccept;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsHttpHandler::SetAcceptEncodings(const char *aAcceptEncodings)
|
||||
nsHttpHandler::SetAcceptEncodings(const char *aAcceptEncodings)
|
||||
{
|
||||
mAcceptEncodings = aAcceptEncodings;
|
||||
return NS_OK;
|
||||
@ -1373,10 +1389,10 @@ nsHttpHandler::NewChannel(nsIURI *uri, nsIChannel **result)
|
||||
return NewProxiedChannel(uri, nullptr, result);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
NS_IMETHODIMP
|
||||
nsHttpHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
|
||||
{
|
||||
// don't override anything.
|
||||
// don't override anything.
|
||||
*_retval = false;
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1394,7 +1410,7 @@ nsHttpHandler::NewProxiedChannel(nsIURI *uri,
|
||||
|
||||
LOG(("nsHttpHandler::NewProxiedChannel [proxyInfo=%p]\n",
|
||||
givenProxyInfo));
|
||||
|
||||
|
||||
nsCOMPtr<nsProxyInfo> proxyInfo;
|
||||
if (givenProxyInfo) {
|
||||
proxyInfo = do_QueryInterface(givenProxyInfo);
|
||||
@ -1532,7 +1548,7 @@ nsHttpHandler::Observe(nsISupports *subject,
|
||||
if (uri && mConnMgr)
|
||||
mConnMgr->ReportFailedToProcess(uri);
|
||||
}
|
||||
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -1547,7 +1563,7 @@ nsHttpHandler::SpeculativeConnect(nsIURI *aURI,
|
||||
bool isStsHost = false;
|
||||
if (!stss)
|
||||
return NS_OK;
|
||||
|
||||
|
||||
nsCOMPtr<nsIURI> clone;
|
||||
if (NS_SUCCEEDED(stss->IsStsURI(aURI, &isStsHost)) && isStsHost) {
|
||||
if (NS_SUCCEEDED(aURI->Clone(getter_AddRefs(clone)))) {
|
||||
@ -1657,7 +1673,7 @@ nsHttpsHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval)
|
||||
NS_IMETHODIMP
|
||||
nsHttpsHandler::AllowPort(int32_t aPort, const char *aScheme, bool *_retval)
|
||||
{
|
||||
// don't override anything.
|
||||
// don't override anything.
|
||||
*_retval = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
88
netwerk/test/unit/test_header_Accept-Language.js
Normal file
88
netwerk/test/unit/test_header_Accept-Language.js
Normal file
@ -0,0 +1,88 @@
|
||||
//
|
||||
// HTTP Accept-Language header test
|
||||
//
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
var testpath = "/bug672448";
|
||||
|
||||
function run_test() {
|
||||
let intlPrefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).getBranch("intl.");
|
||||
|
||||
// Save old value of preference for later.
|
||||
let oldPref = intlPrefs.getCharPref("accept_languages");
|
||||
|
||||
// Test different numbers of languages, to test different fractions.
|
||||
let acceptLangTests = [
|
||||
"qaa", // 1
|
||||
"qaa,qab", // 2
|
||||
"qaa,qab,qac,qad", // 4
|
||||
"qaa,qab,qac,qad,qae,qaf,qag,qah", // 8
|
||||
"qaa,qab,qac,qad,qae,qaf,qag,qah,qai,qaj", // 10
|
||||
"qaa,qab,qac,qad,qae,qaf,qag,qah,qai,qaj,qak", // 11
|
||||
"qaa,qab,qac,qad,qae,qaf,qag,qah,qai,qaj,qak,qal,qam,qan,qao,qap,qaq,qar,qas,qat,qau", // 21
|
||||
oldPref, // Restore old value of preference (and test it).
|
||||
];
|
||||
|
||||
let acceptLangTestsNum = acceptLangTests.length;
|
||||
|
||||
for (let i = 0; i < acceptLangTestsNum; i++) {
|
||||
// Set preference to test value.
|
||||
intlPrefs.setCharPref("accept_languages", acceptLangTests[i]);
|
||||
|
||||
// Test value.
|
||||
test_accepted_languages();
|
||||
}
|
||||
}
|
||||
|
||||
function test_accepted_languages() {
|
||||
let channel = setupChannel(testpath);
|
||||
|
||||
let AcceptLanguage = channel.getRequestHeader("Accept-Language");
|
||||
|
||||
let acceptedLanguages = AcceptLanguage.split(",");
|
||||
|
||||
let acceptedLanguagesLength = acceptedLanguages.length;
|
||||
|
||||
for (let i = 0; i < acceptedLanguagesLength; i++) {
|
||||
let acceptedLanguage, qualityValue;
|
||||
|
||||
try {
|
||||
// The q-value must conform to the definition in HTTP/1.1 Section 3.9.
|
||||
[_, acceptedLanguage, qualityValue] = acceptedLanguages[i].trim().match(/^([a-z0-9_-]*?)(?:;q=(1(?:\.0{0,3})?|0(?:\.[0-9]{0,3})))?$/i);
|
||||
} catch(e) {
|
||||
do_throw("Invalid language tag or quality value: " + e);
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
// The first language shouldn't have a quality value.
|
||||
do_check_eq(qualityValue, undefined);
|
||||
} else {
|
||||
let decimalPlaces;
|
||||
|
||||
// When the number of languages is small, we keep the quality value to only one decimal place.
|
||||
// Otherwise, it can be up to two decimal places.
|
||||
if (acceptedLanguagesLength < 10) {
|
||||
do_check_true(qualityValue.length == 3);
|
||||
|
||||
decimalPlaces = 1;
|
||||
} else {
|
||||
do_check_true(qualityValue.length >= 3);
|
||||
do_check_true(qualityValue.length <= 4);
|
||||
|
||||
decimalPlaces = 2;
|
||||
}
|
||||
|
||||
// All the other languages should have an evenly-spaced quality value.
|
||||
do_check_eq(parseFloat(qualityValue).toFixed(decimalPlaces), (1.0 - ((1 / acceptedLanguagesLength) * i)).toFixed(decimalPlaces));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupChannel(path) {
|
||||
let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
|
||||
let chan = ios.newChannel("http://localhost:4444" + path, "", null);
|
||||
chan.QueryInterface(Ci.nsIHttpChannel);
|
||||
return chan;
|
||||
}
|
@ -42,7 +42,7 @@ skip-if = os == "android"
|
||||
[test_bug455311.js]
|
||||
[test_bug455598.js]
|
||||
[test_bug468426.js]
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
[test_bug468594.js]
|
||||
[test_bug470716.js]
|
||||
@ -53,7 +53,7 @@ skip-if = os == "android"
|
||||
[test_bug490095.js]
|
||||
[test_bug504014.js]
|
||||
[test_bug510359.js]
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
[test_bug515583.js]
|
||||
[test_bug528292.js]
|
||||
@ -71,16 +71,16 @@ skip-if = os == "android"
|
||||
[test_bug652761.js]
|
||||
[test_bug651100.js]
|
||||
# Bug 675044: test fails consistently on Android
|
||||
fail-if = os == "android"
|
||||
fail-if = os == "android"
|
||||
[test_bug654926.js]
|
||||
# Bug 675049: test fails consistently on Android
|
||||
fail-if = os == "android"
|
||||
fail-if = os == "android"
|
||||
[test_bug654926_doom_and_read.js]
|
||||
# Bug 675049: test fails consistently on Android
|
||||
fail-if = os == "android"
|
||||
fail-if = os == "android"
|
||||
[test_bug654926_test_seek.js]
|
||||
# Bug 675049: test fails consistently on Android
|
||||
fail-if = os == "android"
|
||||
fail-if = os == "android"
|
||||
[test_bug659569.js]
|
||||
[test_bug660066.js]
|
||||
[test_bug667907.js]
|
||||
@ -103,28 +103,28 @@ fail-if = os == "android"
|
||||
[test_extract_charset_from_content_type.js]
|
||||
[test_force_sniffing.js]
|
||||
[test_fallback_no-cache-entry_canceled.js]
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
[test_fallback_no-cache-entry_passing.js]
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
[test_fallback_redirect-to-different-origin_canceled.js]
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
[test_fallback_redirect-to-different-origin_passing.js]
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
[test_fallback_request-error_canceled.js]
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
[test_fallback_request-error_passing.js]
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
[test_fallback_response-error_canceled.js]
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
[test_fallback_response-error_passing.js]
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
# Bug 675039: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
[test_file_partial_inputstream.js]
|
||||
[test_file_protocol.js]
|
||||
@ -132,6 +132,7 @@ skip-if = os == "android"
|
||||
[test_gre_resources.js]
|
||||
[test_gzipped_206.js]
|
||||
[test_head.js]
|
||||
[test_header_Accept-Language.js]
|
||||
[test_headers.js]
|
||||
[test_http_headers.js]
|
||||
[test_httpcancel.js]
|
||||
|
@ -37,7 +37,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=366936
|
||||
"webkit01.dat",
|
||||
"webkit02.dat"];
|
||||
const isOSXLion = navigator.userAgent.indexOf("Mac OS X 10.7") != -1;
|
||||
if (!isOSXLion) {
|
||||
const isOSXMtnLion = navigator.userAgent.indexOf("Mac OS X 10.8") != -1;
|
||||
if (!isOSXLion && !isOSXMtnLion) {
|
||||
parserDatFiles.push("tests19.dat");
|
||||
parserDatFiles.push("tests7.dat");
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ AbstractFile.prototype = {
|
||||
* bytes read and the number of bytes read. Note that |buffer| may be
|
||||
* larger than the number of bytes actually read.
|
||||
*/
|
||||
readAll: function readAll(bytes) {
|
||||
read: function read(bytes) {
|
||||
if (bytes == null) {
|
||||
bytes = this.stat().size;
|
||||
}
|
||||
@ -88,7 +88,7 @@ AbstractFile.prototype = {
|
||||
options.offset);
|
||||
let pos = 0;
|
||||
while (pos < bytes) {
|
||||
let chunkSize = this.read(pointer, bytes - pos, options);
|
||||
let chunkSize = this._read(pointer, bytes - pos, options);
|
||||
if (chunkSize == 0) {
|
||||
break;
|
||||
}
|
||||
@ -117,7 +117,7 @@ AbstractFile.prototype = {
|
||||
* @return {number} The number of bytes actually written, which may be
|
||||
* less than |bytes| if |options.once| was set.
|
||||
*/
|
||||
writeFrom: function writeFrom(buffer, bytes, options) {
|
||||
write: function write(buffer, bytes, options) {
|
||||
options = options || noOptions;
|
||||
|
||||
let pointer = AbstractFile.normalizeToPointer(buffer, bytes,
|
||||
@ -125,7 +125,7 @@ AbstractFile.prototype = {
|
||||
|
||||
let pos = 0;
|
||||
while (pos < bytes) {
|
||||
let chunkSize = this.write(pointer, bytes - pos, options);
|
||||
let chunkSize = this._write(pointer, bytes - pos, options);
|
||||
pos += chunkSize;
|
||||
pointer = exports.OS.Shared.offsetBy(pointer, chunkSize);
|
||||
}
|
||||
|
@ -92,7 +92,7 @@
|
||||
* the end of the file has been reached.
|
||||
* @throws {OS.File.Error} In case of I/O error.
|
||||
*/
|
||||
File.prototype.read = function read(buffer, nbytes, options) {
|
||||
File.prototype._read = function _read(buffer, nbytes, options) {
|
||||
return throw_on_negative("read",
|
||||
UnixFile.read(this.fd, buffer, nbytes)
|
||||
);
|
||||
@ -111,7 +111,7 @@
|
||||
* @return {number} The number of bytes effectively written.
|
||||
* @throws {OS.File.Error} In case of I/O error.
|
||||
*/
|
||||
File.prototype.write = function write(buffer, nbytes, options) {
|
||||
File.prototype._write = function _write(buffer, nbytes, options) {
|
||||
return throw_on_negative("write",
|
||||
UnixFile.write(this.fd, buffer, nbytes)
|
||||
);
|
||||
@ -159,6 +159,16 @@
|
||||
return new File.Info(gStatData);
|
||||
};
|
||||
|
||||
/**
|
||||
* Flushes the file's buffers and causes all buffered data
|
||||
* to be written.
|
||||
*
|
||||
* @throws {OS.File.Error} In case of I/O error.
|
||||
*/
|
||||
File.prototype.flush = function flush() {
|
||||
throw_on_negative("flush", UnixFile.fsync(this.fd));
|
||||
};
|
||||
|
||||
|
||||
// Constant used to normalize options.
|
||||
const noOptions = {};
|
||||
@ -401,8 +411,8 @@
|
||||
if (!pump_buffer || pump_buffer.length < bufSize) {
|
||||
pump_buffer = new (ctypes.ArrayType(ctypes.char))(bufSize);
|
||||
}
|
||||
let read = source.read.bind(source);
|
||||
let write = dest.write.bind(dest);
|
||||
let read = source._read.bind(source);
|
||||
let write = dest._write.bind(dest);
|
||||
// Perform actual copy
|
||||
let total_read = 0;
|
||||
while (true) {
|
||||
|
@ -320,6 +320,11 @@
|
||||
/*nbytes_wr*/Types.DWORD.out_ptr,
|
||||
/*overlapped*/Types.void_t.inout_ptr // FIXME: Implement?
|
||||
);
|
||||
|
||||
WinFile.FlushFileBuffers =
|
||||
declareFFI("FlushFileBuffers", ctypes.winapi_abi,
|
||||
/*return*/ Types.zero_or_nothing,
|
||||
/*file*/ Types.HANDLE);
|
||||
};
|
||||
exports.OS.Win.File._init = init;
|
||||
})(this);
|
||||
|
@ -111,7 +111,7 @@
|
||||
* the end of the file has been reached.
|
||||
* @throws {OS.File.Error} In case of I/O error.
|
||||
*/
|
||||
File.prototype.read = function read(buffer, nbytes, options) {
|
||||
File.prototype._read = function _read(buffer, nbytes, options) {
|
||||
// |gBytesReadPtr| is a pointer to |gBytesRead|.
|
||||
throw_on_zero("read",
|
||||
WinFile.ReadFile(this.fd, buffer, nbytes, gBytesReadPtr, null)
|
||||
@ -132,7 +132,7 @@
|
||||
* @return {number} The number of bytes effectively written.
|
||||
* @throws {OS.File.Error} In case of I/O error.
|
||||
*/
|
||||
File.prototype.write = function write(buffer, nbytes, options) {
|
||||
File.prototype._write = function _write(buffer, nbytes, options) {
|
||||
// |gBytesWrittenPtr| is a pointer to |gBytesWritten|.
|
||||
throw_on_zero("write",
|
||||
WinFile.WriteFile(this.fd, buffer, nbytes, gBytesWrittenPtr, null)
|
||||
@ -182,6 +182,16 @@
|
||||
return new File.Info(gFileInfo);
|
||||
};
|
||||
|
||||
/**
|
||||
* Flushes the file's buffers and causes all buffered data
|
||||
* to be written.
|
||||
*
|
||||
* @throws {OS.File.Error} In case of I/O error.
|
||||
*/
|
||||
File.prototype.flush = function flush() {
|
||||
throw_on_zero("flush", WinFile.FlushFileBuffers(this.fd));
|
||||
};
|
||||
|
||||
// Constant used to normalize options.
|
||||
const noOptions = {};
|
||||
|
||||
|
@ -19,8 +19,8 @@ self.onmessage = function onmessage_start(msg) {
|
||||
test_offsetby();
|
||||
test_open_existing_file();
|
||||
test_open_non_existing_file();
|
||||
test_flush_open_file();
|
||||
test_copy_existing_file();
|
||||
test_read_write_file();
|
||||
test_readall_writeall_file();
|
||||
test_position();
|
||||
test_move_file();
|
||||
@ -160,6 +160,20 @@ function test_open_non_existing_file()
|
||||
ok(exn.becauseNoSuchFile, "test_open_non_existing_file: Exception confirms that the file does not exist");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that to ensure that |foo.flush()| does not
|
||||
* cause an error, where |foo| is an open file.
|
||||
*/
|
||||
function test_flush_open_file()
|
||||
{
|
||||
ok(true, "Starting test_flush_open_file");
|
||||
let tmp = "test_flush.tmp";
|
||||
let file = OS.File.open(tmp, {create: true, write: true});
|
||||
file.flush();
|
||||
file.close();
|
||||
OS.File.remove(tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function for comparing two files (or a prefix of two files).
|
||||
*
|
||||
@ -178,90 +192,38 @@ function compare_files(test, sourcePath, destPath, prefix)
|
||||
let source = OS.File.open(sourcePath);
|
||||
let dest = OS.File.open(destPath);
|
||||
ok(true, "Files are open");
|
||||
let array1 = new (ctypes.ArrayType(ctypes.char, 4096))();
|
||||
let array2 = new (ctypes.ArrayType(ctypes.char, 4096))();
|
||||
ok(true, "Arrays are created");
|
||||
let pos = 0;
|
||||
while (true) {
|
||||
ok(true, "Position: "+pos);
|
||||
let chunkSize;
|
||||
let sourceResult, destResult;
|
||||
try {
|
||||
if (prefix != undefined) {
|
||||
chunkSize = Math.min(4096, prefix - pos);
|
||||
sourceResult = source.read(prefix);
|
||||
destResult = dest.read(prefix);
|
||||
} else {
|
||||
chunkSize = 4096;
|
||||
sourceResult = source.read();
|
||||
destResult = dest.read();
|
||||
}
|
||||
let bytes_read1 = source.read(array1, chunkSize);
|
||||
let bytes_read2 = dest.read(array2, chunkSize);
|
||||
is (bytes_read1 > 0, bytes_read2 > 0,
|
||||
test + ": Both files contain data or neither does " +
|
||||
bytes_read1 + ", " + bytes_read2);
|
||||
if ((bytes_read1 > 0) != (bytes_read2 > 0)) {
|
||||
break;
|
||||
}
|
||||
if (bytes_read1 == 0) {
|
||||
break;
|
||||
}
|
||||
let bytes;
|
||||
if (bytes_read1 != bytes_read2) {
|
||||
// This would be surprising, but theoretically possible with a
|
||||
// remote file system, I believe.
|
||||
bytes = Math.min(bytes_read1, bytes_read2);
|
||||
pos += bytes;
|
||||
source.setPosition(pos, OS.File.POS_START);
|
||||
dest.setPosition(pos, OS.File.POS_START);
|
||||
} else {
|
||||
bytes = bytes_read1;
|
||||
pos += bytes;
|
||||
}
|
||||
for (let i = 0; i < bytes; ++i) {
|
||||
if (array1[i] != array2[i]) {
|
||||
ok(false, test + ": Files do not match at position " + i
|
||||
+ " (" + array1[i] + "/ " + array2[i] + ")");
|
||||
is(sourceResult.bytes, destResult.bytes, test + ": Both files have the same size");
|
||||
let sourceView = new Uint8Array(sourceResult.buffer);
|
||||
let destView = new Uint8Array(destResult.buffer);
|
||||
for (let i = 0; i < sourceResult.bytes; ++i) {
|
||||
if (sourceView[i] != destView[i]) {
|
||||
is(sourceView[i] != destView[i], test + ": Comparing char " + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
source.close();
|
||||
dest.close();
|
||||
}
|
||||
source.close();
|
||||
dest.close();
|
||||
ok(true, test + ": Comparison complete");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test basic read/write through an ArrayBuffer
|
||||
*/
|
||||
function test_read_write_file()
|
||||
{
|
||||
let src_file_name = "chrome/toolkit/components/osfile/tests/mochi/worker_test_osfile_unix.js";
|
||||
let tmp_file_name = "test_osfile_front.tmp";
|
||||
ok(true, "Starting test_read_write_file");
|
||||
|
||||
let source = OS.File.open(src_file_name);
|
||||
let dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
|
||||
|
||||
let buf = new ArrayBuffer(4096);
|
||||
for (let bytesAvailable = source.read(buf, 4096);
|
||||
bytesAvailable != 0;
|
||||
bytesAvailable = source.read(buf, 4096)) {
|
||||
let bytesWritten = dest.write(buf, bytesAvailable);
|
||||
if (bytesWritten != bytesAvailable) {
|
||||
is(bytesWritten, bytesAvailable, "test_read_write_file: writing all bytes");
|
||||
}
|
||||
}
|
||||
|
||||
ok(true, "test_read_write_file: copy complete");
|
||||
source.close();
|
||||
dest.close();
|
||||
|
||||
compare_files("test_read_write_file", src_file_name, tmp_file_name);
|
||||
OS.File.remove(tmp_file_name);
|
||||
}
|
||||
|
||||
function test_readall_writeall_file()
|
||||
{
|
||||
let src_file_name = "chrome/toolkit/components/osfile/tests/mochi/worker_test_osfile_unix.js";
|
||||
let tmp_file_name = "test_osfile_front.tmp";
|
||||
ok(true, "Starting test_readall_writeall_file");
|
||||
|
||||
// readTo, ArrayBuffer
|
||||
// read, ArrayBuffer
|
||||
|
||||
let source = OS.File.open(src_file_name);
|
||||
let dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
|
||||
@ -271,7 +233,7 @@ function test_readall_writeall_file()
|
||||
let readResult = source.readTo(buf, size);
|
||||
is(readResult, size, "test_readall_writeall_file: read the right number of bytes");
|
||||
|
||||
dest.writeFrom(buf, size);
|
||||
dest.write(buf, size);
|
||||
|
||||
ok(true, "test_readall_writeall_file: copy complete (manual allocation)");
|
||||
source.close();
|
||||
@ -280,7 +242,7 @@ function test_readall_writeall_file()
|
||||
compare_files("test_readall_writeall_file (manual allocation)", src_file_name, tmp_file_name);
|
||||
OS.File.remove(tmp_file_name);
|
||||
|
||||
// readTo, C buffer
|
||||
// read, C buffer
|
||||
|
||||
source = OS.File.open(src_file_name);
|
||||
dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
|
||||
@ -289,7 +251,7 @@ function test_readall_writeall_file()
|
||||
readResult = source.readTo(ptr, size);
|
||||
is(readResult, size, "test_readall_writeall_file: read the right number of bytes (C buffer)");
|
||||
|
||||
dest.writeFrom(ptr, readResult, {bytes: size});
|
||||
dest.write(ptr, readResult, {bytes: size});
|
||||
|
||||
ok(true, "test_readall_writeall_file: copy complete (C buffer)");
|
||||
source.close();
|
||||
@ -308,7 +270,7 @@ function test_readall_writeall_file()
|
||||
readResult = source.readTo(buf, LEFT, {offset: OFFSET});
|
||||
is(readResult, LEFT, "test_readall_writeall_file: read the right number of bytes (with offset)");
|
||||
|
||||
dest.writeFrom(buf, LEFT, {offset: OFFSET});
|
||||
dest.write(buf, LEFT, {offset: OFFSET});
|
||||
is(dest.stat().size, LEFT, "test_readall_writeall_file: wrote the right number of bytes (with offset)");
|
||||
|
||||
ok(true, "test_readall_writeall_file: copy complete (with offset)");
|
||||
@ -328,7 +290,7 @@ function test_readall_writeall_file()
|
||||
readResult = source.readTo(ptr, LEFT, {offset: OFFSET});
|
||||
is(readResult, LEFT, "test_readall_writeall_file: read the right number of bytes (with offset)");
|
||||
|
||||
dest.writeFrom(ptr, LEFT, {offset: OFFSET});
|
||||
dest.write(ptr, LEFT, {offset: OFFSET});
|
||||
is(dest.stat().size, LEFT, "test_readall_writeall_file: wrote the right number of bytes (with offset)");
|
||||
|
||||
ok(true, "test_readall_writeall_file: copy complete (with offset)");
|
||||
@ -338,12 +300,12 @@ function test_readall_writeall_file()
|
||||
compare_files("test_readall_writeall_file (with offset)", src_file_name, tmp_file_name, LEFT);
|
||||
OS.File.remove(tmp_file_name);
|
||||
|
||||
// readAll
|
||||
// read
|
||||
buf = new ArrayBuffer(size);
|
||||
source = OS.File.open(src_file_name);
|
||||
dest = OS.File.open(tmp_file_name, {write: true, trunc:true});
|
||||
|
||||
readResult = source.readAll();
|
||||
readResult = source.read();
|
||||
is(readResult.bytes, size, "test_readall_writeall_file: read the right number of bytes (auto allocation)");
|
||||
|
||||
dest.write(readResult.buffer, readResult.bytes);
|
||||
@ -527,7 +489,7 @@ function test_info() {
|
||||
|
||||
let file = OS.File.open(filename, {trunc: true});
|
||||
let buf = new ArrayBuffer(size);
|
||||
file.write(buf, size);
|
||||
file._write(buf, size);
|
||||
file.close();
|
||||
|
||||
// Test OS.File.stat on new file
|
||||
|
@ -9,6 +9,9 @@ const Cr = Components.results;
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
|
||||
"resource://gre/modules/DeferredTask.jsm");
|
||||
|
||||
const PERMS_FILE = 0644;
|
||||
const PERMS_DIRECTORY = 0755;
|
||||
|
||||
@ -279,64 +282,6 @@ function ENSURE_WARN(assertion, message, resultCode) {
|
||||
throw Components.Exception(message, resultCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* A delayed treatment that may be delayed even further.
|
||||
*
|
||||
* Use this for instance if you write data to a file and you expect
|
||||
* that you may have to rewrite data very soon afterwards. With
|
||||
* |Lazy|, the treatment is delayed by a few milliseconds and,
|
||||
* should a new change to the data occur during this period,
|
||||
* 1/ only the final version of the data is actually written;
|
||||
* 2/ a further grace delay is added to take into account other
|
||||
* changes.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Function} code The code to execute after the delay.
|
||||
* @param {number=} delay An optional delay, in milliseconds.
|
||||
*/
|
||||
function Lazy(code, delay) {
|
||||
LOG("Lazy: Creating a Lazy");
|
||||
this._callback =
|
||||
(function(){
|
||||
code();
|
||||
this._timer = null;
|
||||
}).bind(this);
|
||||
this._delay = delay || LAZY_SERIALIZE_DELAY;
|
||||
this._timer = null;
|
||||
}
|
||||
Lazy.prototype = {
|
||||
/**
|
||||
* Start (or postpone) treatment.
|
||||
*/
|
||||
go: function Lazy_go() {
|
||||
LOG("Lazy_go: starting");
|
||||
if (this._timer) {
|
||||
LOG("Lazy_go: reusing active timer");
|
||||
this._timer.delay = this._delay;
|
||||
} else {
|
||||
LOG("Lazy_go: creating timer");
|
||||
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this._timer.
|
||||
initWithCallback(this._callback,
|
||||
this._delay,
|
||||
Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Perform any postponed treatment immediately.
|
||||
*/
|
||||
flush: function Lazy_flush() {
|
||||
LOG("Lazy_flush: starting");
|
||||
if (!this._timer) {
|
||||
return;
|
||||
}
|
||||
this._timer.cancel();
|
||||
this._timer = null;
|
||||
this._callback();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function loadListener(aChannel, aEngine, aCallback) {
|
||||
this._channel = aChannel;
|
||||
this._bytes = [];
|
||||
@ -3825,10 +3770,10 @@ var engineMetadataService = {
|
||||
let istream = converter.convertToInputStream(JSON.stringify(store));
|
||||
NetUtil.asyncCopy(istream, ostream, callback);
|
||||
}
|
||||
this._lazyWriter = new Lazy(writeCommit);
|
||||
this._lazyWriter = new DeferredTask(writeCommit, LAZY_SERIALIZE_DELAY);
|
||||
}
|
||||
LOG("epsCommit: (re)setting timer");
|
||||
this._lazyWriter.go();
|
||||
this._lazyWriter.start();
|
||||
},
|
||||
_lazyWriter: null,
|
||||
};
|
||||
|
86
toolkit/content/DeferredTask.jsm
Normal file
86
toolkit/content/DeferredTask.jsm
Normal file
@ -0,0 +1,86 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const EXPORTED_SYMBOLS = ["DeferredTask"];
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
/**
|
||||
* A task that will run after a delay. Multiple attempts to run the same task
|
||||
* before the delay will be coalesced
|
||||
*
|
||||
* Use this for instance if you write data to a file and you expect that you
|
||||
* may have to rewrite data very soon afterwards. With |DeferredTask|, the
|
||||
* task is delayed by a few milliseconds and, should a new change to the data
|
||||
* occur during this period,
|
||||
* 1/ only the final version of the data is actually written;
|
||||
* 2/ a further grace delay is added to take into account other changes.
|
||||
*
|
||||
* Constructor
|
||||
* @param aDelay The delay time in milliseconds.
|
||||
* @param aCallback The code to execute after the delay.
|
||||
*/
|
||||
function DeferredTask(aCallback, aDelay) {
|
||||
this._callback = function onCallback() {
|
||||
this._timer = null;
|
||||
try {
|
||||
aCallback();
|
||||
} catch(e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}.bind(this);
|
||||
this._delay = aDelay;
|
||||
}
|
||||
|
||||
DeferredTask.prototype = {
|
||||
/* Callback */
|
||||
_callback: null,
|
||||
/* Delay */
|
||||
_delay: null,
|
||||
/* Timer */
|
||||
_timer: null,
|
||||
|
||||
/**
|
||||
* Check the current task state.
|
||||
* @returns true if pending, false otherwise.
|
||||
*/
|
||||
get isPending() {
|
||||
return (this._timer != null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Start (or postpone) task.
|
||||
*/
|
||||
start: function DeferredTask_start() {
|
||||
if (this._timer) {
|
||||
this._timer.delay = this._delay;
|
||||
} else {
|
||||
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this._timer.initWithCallback(
|
||||
this._callback, this._delay, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform any postponed task immediately.
|
||||
*/
|
||||
flush: function DeferredTask_flush() {
|
||||
if (this._timer) {
|
||||
this.cancel();
|
||||
this._callback();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel any pending task.
|
||||
*/
|
||||
cancel: function DeferredTask_cancel() {
|
||||
if (this._timer) {
|
||||
this._timer.cancel();
|
||||
this._timer = null;
|
||||
}
|
||||
}
|
||||
};
|
@ -58,13 +58,14 @@ TEST_DIRS += tests
|
||||
|
||||
EXTRA_JS_MODULES = \
|
||||
debug.js \
|
||||
DeferredTask.jsm \
|
||||
Dict.jsm \
|
||||
Geometry.jsm \
|
||||
InlineSpellChecker.jsm \
|
||||
PopupNotifications.jsm \
|
||||
Dict.jsm \
|
||||
PageMenu.jsm \
|
||||
PropertyListUtils.jsm \
|
||||
PopupNotifications.jsm \
|
||||
PrivateBrowsingUtils.jsm \
|
||||
PropertyListUtils.jsm \
|
||||
Task.jsm \
|
||||
$(NULL)
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width; user-scalable=false;">
|
||||
<title>about:buildconfig</title>
|
||||
<link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css">
|
||||
<style type="text/css">
|
||||
|
@ -26,6 +26,7 @@ MOCHITEST_BROWSER_FILES = \
|
||||
browser_save_resend_postdata.js \
|
||||
browser_browserDrop.js \
|
||||
browser_Services.js \
|
||||
browser_DeferredTask.js \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
166
toolkit/content/tests/browser/browser_DeferredTask.js
Normal file
166
toolkit/content/tests/browser/browser_DeferredTask.js
Normal file
@ -0,0 +1,166 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
let DeferredTask =
|
||||
Components.utils.import("resource://gre/modules/DeferredTask.jsm", {}).DeferredTask;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
nextTest();
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
let test = tests.shift();
|
||||
if (test) {
|
||||
info("Starting " + test.name);
|
||||
executeSoon(test.bind(null, nextTest));
|
||||
} else {
|
||||
executeSoon(finish);
|
||||
}
|
||||
}
|
||||
|
||||
let tests = [
|
||||
function checkRanOnce(next) {
|
||||
// Test that a deferred task runs only once.
|
||||
let deferredTaskCounter = 0;
|
||||
let deferredTask = new DeferredTask(function checkDeferred() {
|
||||
is(++deferredTaskCounter, 1, "Deferred task ran once");
|
||||
if (deferredTaskCounter > 1)
|
||||
return; // shouldn't happen!
|
||||
next();
|
||||
}, 100);
|
||||
|
||||
deferredTask.start();
|
||||
deferredTask.start();
|
||||
deferredTask.start();
|
||||
},
|
||||
|
||||
function testOrdering(next) {
|
||||
// Test the deferred task running order.
|
||||
let ARan, BRan;
|
||||
let deferredTaskA = new DeferredTask(function () {
|
||||
info("Deferred task A running");
|
||||
ARan = true;
|
||||
ok(!BRan, "Deferred task B shouldn't have run yet");
|
||||
}, 10);
|
||||
let deferredTaskB = new DeferredTask(function () {
|
||||
info("Deferred task B running");
|
||||
BRan = true;
|
||||
ok(ARan, "Deferred task A should have run already");
|
||||
executeSoon(next);
|
||||
}, 100);
|
||||
|
||||
deferredTaskA.start();
|
||||
deferredTaskB.start();
|
||||
},
|
||||
|
||||
function testFlush(next) {
|
||||
// Test the flush method.
|
||||
let deferedTaskCounter = 0;
|
||||
let deferredTask = new DeferredTask(function () {
|
||||
is(++deferedTaskCounter, 1, "Deferred task should only run once");
|
||||
}, 10);
|
||||
|
||||
is(deferedTaskCounter, 0, "Deferred task shouldn't run right away");
|
||||
deferredTask.flush();
|
||||
is(deferedTaskCounter, 0, "Deferred task shouldn't run after flush() if it wasn't started");
|
||||
deferredTask.start();
|
||||
is(deferedTaskCounter, 0, "Deferred task shouldn't run immediately after start");
|
||||
deferredTask.flush();
|
||||
is(deferedTaskCounter, 1, "Deferred task should run after flush() after being started");
|
||||
next();
|
||||
},
|
||||
|
||||
function testCancel(next) {
|
||||
// Test the cancel method.
|
||||
let deferredTask1, deferredTask2, deferredTask3;
|
||||
let deferredTask2Ran = false;
|
||||
deferredTask1 = new DeferredTask(function () {
|
||||
info("Deferred task 1 running");
|
||||
deferredTask2.cancel();
|
||||
info("Deferred task 2 canceled");
|
||||
}, 10);
|
||||
deferredTask2 = new DeferredTask(function () {
|
||||
info("Deferred task 2 running");
|
||||
deferredTask2Ran = true;
|
||||
}, 100);
|
||||
deferredTask3 = new DeferredTask(function () {
|
||||
info("Deferred task 3 running");
|
||||
is(deferredTask2Ran, false, "Deferred task 2 shouldn't run");
|
||||
next();
|
||||
}, 200);
|
||||
|
||||
deferredTask1.start();
|
||||
deferredTask2.start();
|
||||
deferredTask3.start();
|
||||
},
|
||||
|
||||
function testIsPending(next) {
|
||||
// Test the pending property.
|
||||
let deferredTask1, deferredTask2;
|
||||
let deferredTask1 = new DeferredTask(function () {
|
||||
info("Deferred task 1 running");
|
||||
is(deferredTask1.isPending, false, "Deferred task 1 shouldn't be pending");
|
||||
is(deferredTask2.isPending, true, "Deferred task 2 should be pending");
|
||||
}, 100);
|
||||
let deferredTask2 = new DeferredTask(function () {
|
||||
info("Deferred task 2 running");
|
||||
is(deferredTask1.isPending, false, "Deferred task 1 shouldn't be pending");
|
||||
is(deferredTask2.isPending, false, "Deferred task 2 shouldn't be pending");
|
||||
next();
|
||||
}, 200);
|
||||
|
||||
is(deferredTask1.isPending, false, "Deferred task 1 shouldn't be pending");
|
||||
is(deferredTask2.isPending, false, "Deferred task 2 shouldn't be pending");
|
||||
deferredTask1.start();
|
||||
deferredTask2.start();
|
||||
is(deferredTask1.isPending, true, "Deferred task 1 should be pending");
|
||||
is(deferredTask2.isPending, true, "Deferred task 2 should be pending");
|
||||
},
|
||||
|
||||
function testRestartingTask(next) {
|
||||
// Test restarting a task from other task.
|
||||
let deferredTask1, deferredTask2, deferredTask3;
|
||||
let deferredTask1 = new DeferredTask(function () {
|
||||
info("Deferred task 1 running");
|
||||
is(deferredTask1.isPending, false, "Deferred task 1 shouldn't be pending");
|
||||
is(deferredTask2.isPending, false, "Deferred task 2 shouldn't be pending");
|
||||
is(deferredTask3.isPending, false, "Deferred task 3 shouldn't be pending");
|
||||
next();
|
||||
}, 100);
|
||||
let deferredTask2 = new DeferredTask(function () {
|
||||
info("Deferred task 2 running");
|
||||
deferredTask1.start();
|
||||
deferredTask3.start();
|
||||
}, 50);
|
||||
let deferredTask3 = new DeferredTask(function () {
|
||||
info("Deferred task 3 running");
|
||||
is(deferredTask1.isPending, true, "Deferred task 1 should be pending");
|
||||
}, 75);
|
||||
|
||||
deferredTask1.start();
|
||||
deferredTask2.start();
|
||||
},
|
||||
|
||||
function testRestartItselfTask(next) {
|
||||
// Test restarting a task from the same task callback.
|
||||
let deferredTask;
|
||||
let taskReRun = false;
|
||||
let deferredTask = new DeferredTask(function () {
|
||||
info("Deferred task running");
|
||||
is(deferredTask.isPending, false, "Deferred task shouldn't be pending");
|
||||
if (!taskReRun) {
|
||||
taskReRun = true;
|
||||
info("Deferred task restart");
|
||||
deferredTask.start();
|
||||
is(deferredTask.isPending, true, "Deferred task should be pending");
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
deferredTask.start();
|
||||
}
|
||||
];
|
@ -3,3 +3,5 @@ head =
|
||||
tail =
|
||||
|
||||
[test_content_annotation.js]
|
||||
# Bug 706751: test fails on 10.7 and 10.8
|
||||
skip-if = os == "mac"
|
||||
|
@ -712,6 +712,12 @@ TISInputSourceWrapper::InitKeyEvent(NSEvent *aNativeKeyEvent,
|
||||
UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
|
||||
|
||||
bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode);
|
||||
if (isPrintableKey &&
|
||||
[aNativeKeyEvent type] != NSKeyDown &&
|
||||
[aNativeKeyEvent type] != NSKeyUp) {
|
||||
NS_WARNING("Why the printable key doesn't cause NSKeyDown or NSKeyUp?");
|
||||
isPrintableKey = false;
|
||||
}
|
||||
|
||||
// Decide what string will be input.
|
||||
nsAutoString insertString;
|
||||
|
Loading…
Reference in New Issue
Block a user