mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 05:37:11 +00:00
import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1167459 - Skip rendering function name nodes if there's no name available (e.g. for C++ pseudoframes), r=jsantell (4a69ed224)
- missing bits Bug 1102219 - Part 4: Replace String.prototype.contains with `String.prototype.includes` in chrome code. r=till (73cd2d2b1)
- Bug 1165045 - Don't create nodes with empty text in the call tree, r=jsantell (b013aa82d)
- Bug 1166122 - Fix regression in the call tree caused by bug 1165045, r=jsantell (ee3f16901)
- Bug 1167975 - CallView._displaySelf sets this.document just because other functions use it; it should pass it as an argument instead, r=jsantell (5ef560c4f)
- Bug 1122662 - Resize graphs when window resizes;r=vporof (25c108e4e)
- Bug 1164784 - Eliminate CSS duplication with perf tool record button r=jsantell (43c9bb999)
- Bug 1150761 - Rename the performance tool's details view names to better describe the data visualizations. r=vp (04ceb6a37)
- Bug 1144424 - Rename '{self,total} allocations' to '{self,total} sampled allocations' in the performance tool. r=jsantell (ae79ad54f)
- Bug 1069910 - Add tooltips explaining what each column in the profiler's tree view represents; r=jsantell (8756f88b6)
- Bug 1107849 - Define a min/max width for the performance panel sidebar. r=vporof (e1769e831)
- Bug 11663354 - A locked recording button should appear disabled in the performance tool. r=vp (4a359d39e)
- Bug 1023546 - DevTools - Support HDPI resolutions for Windows. r=bgrins (ef1a3ecb8)
- Bug 1168125 - Cleanup performance xul and css, r=jsantell (8ec794e46)
- Bug 1168125 - Replace the waterfall view with a tree, r=jsantell (ea76514fe)
- Bug 1168125 - Add marker folding logic, r=jsantell (1d3748d2a)
- Bug 862341 Part 1: Move the network request storage from the console frontend to the console client so the netmonitor can reuse it. r=vporof (d29fb2b73)
- remove gre from resource path (126b00df1)
- Bug 943306 - Allow persisting console input history between sessions;r=past (146ebb486)
- Bug 1134845 - Add clearHistory jsterm helper to remove persisted console input history. r=past (22237e95b)
- Bug 1143497 - Offer a way to extend WebConsole commands. r=bgrins (84e2d2957)
- Bug 1125205 - Display console API messages from shared or service workers to the web console, r=past (b4b701a2c)
- Bug 1169342 - Remove nsIDOMDeviceStorage. Cleanup nsDOMDeviceStorage event wrappers. r=dhylands (41338e16f)
- Bug 1151610 - Manage the case where two extensions fight over the same command. r=bgrins (63f9d2064)
- Bug 862341 Part 2: Display cached network requests in the web console. r=vporof (83c0e7263)
- Bug 1144211 - Improve code coverage of camera mochitests. r=mikeh (ba9f3de89)
- Bug 1152500 - Fix how stop recording may be handled out-of-order. r=dhylands (d8bdd379c)
- Bug 862341 Part 3: Display cached network requests in the network panel. r=vporof (a1a6f151d)
- Bug 862341 Part 4: Start recording network requests when the toolbox opens. r=vporof (7a2bdf847)
- Bug 1151499 - Correct the FM playable state. r=baku (8af26fff2)
- Bug 1180347 - Split media.useAudioChannelService to support turning the service on without turning the Firefox OS specific APIs on; r=baku (3fa29291a)
- Bug 862341 Part 5: Tests. r=vporof (82fb944c6)
This commit is contained in:
@@ -856,6 +856,8 @@ pref("general.useragent.device_id", "");
|
||||
|
||||
// Make <audio> and <video> talk to the AudioChannelService.
|
||||
pref("media.useAudioChannelService", true);
|
||||
// Add Mozilla AudioChannel APIs.
|
||||
pref("media.useAudioChannelAPI", true);
|
||||
|
||||
pref("b2g.version", @MOZ_B2G_VERSION@);
|
||||
pref("b2g.osName", @MOZ_B2G_OS_NAME@);
|
||||
|
||||
@@ -193,7 +193,6 @@
|
||||
@RESPATH@/components/dom_alarm.xpt
|
||||
@RESPATH@/components/dom_core.xpt
|
||||
@RESPATH@/components/dom_css.xpt
|
||||
@RESPATH@/components/dom_devicestorage.xpt
|
||||
@RESPATH@/components/dom_events.xpt
|
||||
@RESPATH@/components/dom_geolocation.xpt
|
||||
@RESPATH@/components/dom_media.xpt
|
||||
|
||||
@@ -1110,6 +1110,17 @@ pref("devtools.gcli.imgurClientID", '0df414e888d7240');
|
||||
// Imgur's upload URL
|
||||
pref("devtools.gcli.imgurUploadURL", "https://api.imgur.com/3/image");
|
||||
|
||||
pref("devtools.webconsole.filter.serviceworkers", false);
|
||||
pref("devtools.webconsole.filter.sharedworkers", false);
|
||||
pref("devtools.webconsole.filter.windowlessworkers", false);
|
||||
|
||||
pref("devtools.browserconsole.filter.serviceworkers", true);
|
||||
pref("devtools.browserconsole.filter.sharedworkers", true);
|
||||
pref("devtools.browserconsole.filter.windowlessworkers", true);
|
||||
|
||||
// Max number of inputs to store in web console history.
|
||||
pref("devtools.webconsole.inputHistoryCount", 50);
|
||||
|
||||
// Whether the character encoding menu is under the main Firefox button. This
|
||||
// preference is a string so that localizers can alter it.
|
||||
pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties");
|
||||
|
||||
@@ -216,7 +216,6 @@
|
||||
@RESPATH@/components/dom_alarm.xpt
|
||||
@RESPATH@/components/dom_core.xpt
|
||||
@RESPATH@/components/dom_css.xpt
|
||||
@RESPATH@/components/dom_devicestorage.xpt
|
||||
@RESPATH@/components/dom_events.xpt
|
||||
@RESPATH@/components/dom_geolocation.xpt
|
||||
@RESPATH@/components/dom_media.xpt
|
||||
|
||||
@@ -52,6 +52,7 @@ var tests = [
|
||||
// Preferences
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.ipc.browser_frames.oop_by_default", true],
|
||||
["media.useAudioChannelAPI", true],
|
||||
["media.useAudioChannelService", true],
|
||||
["media.defaultAudioChannel", "telephony"],
|
||||
["dom.mozBrowserFramesEnabled", true],
|
||||
|
||||
@@ -469,7 +469,7 @@ private:
|
||||
id = NS_LITERAL_STRING("Worker");
|
||||
}
|
||||
|
||||
mCallData->SetIDs(id, frame.mFilename);
|
||||
mCallData->SetIDs(frame.mFilename, id);
|
||||
}
|
||||
|
||||
// Now we could have the correct window (if we are not window-less).
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "nsIFile.h"
|
||||
#include "nsProxyRelease.h"
|
||||
#include "AutoRwLock.h"
|
||||
#include "nsIDOMDeviceStorage.h"
|
||||
#include "ICameraControl.h"
|
||||
#include "CameraCommon.h"
|
||||
#include "DeviceStorage.h"
|
||||
|
||||
@@ -74,9 +74,15 @@ MozCameraTestHardware.prototype = {
|
||||
},
|
||||
|
||||
dispatchEvent: function(evt) {
|
||||
if (this._handler) {
|
||||
this._handler.handleEvent(evt);
|
||||
}
|
||||
var self = this;
|
||||
/* We should not dispatch the event in the current thread
|
||||
context because it may be directly from a driver call
|
||||
and we could hit a deadlock situation. */
|
||||
this._window.setTimeout(function() {
|
||||
if (self._handler) {
|
||||
self._handler.handleEvent(evt);
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
|
||||
reset: function(aWindow) {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "mozilla/unused.h"
|
||||
#include "nsIAppsService.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIDOMDeviceStorage.h"
|
||||
#include "nsIDOMEventListener.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
#include "Navigator.h"
|
||||
@@ -202,6 +201,7 @@ nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
|
||||
, mGetCameraPromise(aPromise)
|
||||
, mWindow(aWindow)
|
||||
, mPreviewState(CameraControlListener::kPreviewStopped)
|
||||
, mRecording(false)
|
||||
, mSetInitialConfig(false)
|
||||
{
|
||||
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
||||
@@ -743,40 +743,39 @@ nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mStartRecordingPromise) {
|
||||
if (mStartRecordingPromise || mRecording) {
|
||||
promise->MaybeReject(NS_ERROR_IN_PROGRESS);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
|
||||
|
||||
#ifdef MOZ_B2G
|
||||
if (!mAudioChannelAgent) {
|
||||
mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
|
||||
if (mAudioChannelAgent) {
|
||||
// Camera app will stop recording when it falls to the background, so no callback is necessary.
|
||||
mAudioChannelAgent->Init(mWindow, (int32_t)AudioChannel::Content, nullptr);
|
||||
// Video recording doesn't output any sound, so it's not necessary to check canPlay.
|
||||
int32_t canPlay;
|
||||
mAudioChannelAgent->StartPlaying(&canPlay);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
nsCOMPtr<nsIDOMDOMRequest> request;
|
||||
mDSFileDescriptor = new DeviceStorageFileDescriptor();
|
||||
aRv = aStorageArea.CreateFileDescriptor(aFilename, mDSFileDescriptor.get(),
|
||||
getter_AddRefs(request));
|
||||
aRv = NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mDSFileDescriptor = new DeviceStorageFileDescriptor();
|
||||
nsRefPtr<DOMRequest> request = aStorageArea.CreateFileDescriptor(aFilename,
|
||||
mDSFileDescriptor.get(),
|
||||
aRv);
|
||||
if (aRv.Failed()) {
|
||||
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mStartRecordingPromise = promise;
|
||||
mOptions = aOptions;
|
||||
|
||||
EventListenerManager* elm = request->GetOrCreateListenerManager();
|
||||
if (!elm) {
|
||||
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mRecording = true;
|
||||
nsCOMPtr<nsIDOMEventListener> listener = new StartRecordingHelper(this);
|
||||
request->AddEventListener(NS_LITERAL_STRING("success"), listener, false);
|
||||
request->AddEventListener(NS_LITERAL_STRING("error"), listener, false);
|
||||
elm->AddEventListener(NS_LITERAL_STRING("success"), listener, false, false);
|
||||
elm->AddEventListener(NS_LITERAL_STRING("error"), listener, false, false);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
@@ -787,6 +786,10 @@ nsDOMCameraControl::OnCreatedFileDescriptor(bool aSucceeded)
|
||||
|
||||
if (!mCameraControl) {
|
||||
rv = NS_ERROR_NOT_AVAILABLE;
|
||||
} else if (!mRecording) {
|
||||
// Race condition where StopRecording comes in before we issue
|
||||
// the start recording request to Gonk
|
||||
rv = NS_ERROR_ABORT;
|
||||
} else if (aSucceeded && mDSFileDescriptor->mFileDescriptor.IsValid()) {
|
||||
ICameraControl::StartRecordingOptions o;
|
||||
|
||||
@@ -818,13 +821,8 @@ nsDOMCameraControl::StopRecording(ErrorResult& aRv)
|
||||
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
||||
THROW_IF_NO_CAMERACONTROL();
|
||||
|
||||
#ifdef MOZ_B2G
|
||||
if (mAudioChannelAgent) {
|
||||
mAudioChannelAgent->StopPlaying();
|
||||
mAudioChannelAgent = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
ReleaseAudioChannelAgent();
|
||||
mRecording = false;
|
||||
aRv = mCameraControl->StopRecording();
|
||||
}
|
||||
|
||||
@@ -958,8 +956,10 @@ nsDOMCameraControl::TakePicture(const CameraPictureOptions& aOptions,
|
||||
if (s.width && s.height) {
|
||||
mCameraControl->Set(CAMERA_PARAM_PICTURE_SIZE, s);
|
||||
}
|
||||
if (!aOptions.mFileFormat.IsEmpty()) {
|
||||
mCameraControl->Set(CAMERA_PARAM_PICTURE_FILEFORMAT, aOptions.mFileFormat);
|
||||
}
|
||||
mCameraControl->Set(CAMERA_PARAM_PICTURE_ROTATION, aOptions.mRotation);
|
||||
mCameraControl->Set(CAMERA_PARAM_PICTURE_FILEFORMAT, aOptions.mFileFormat);
|
||||
mCameraControl->Set(CAMERA_PARAM_PICTURE_DATETIME, aOptions.mDateTime);
|
||||
mCameraControl->SetLocation(p);
|
||||
}
|
||||
@@ -1032,15 +1032,50 @@ nsDOMCameraControl::Shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDOMCameraControl::ReleaseAudioChannelAgent()
|
||||
{
|
||||
#ifdef MOZ_B2G
|
||||
if (mAudioChannelAgent) {
|
||||
mAudioChannelAgent->StopPlaying();
|
||||
mAudioChannelAgent = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDOMCameraControl::NotifyRecordingStatusChange(const nsString& aMsg)
|
||||
{
|
||||
NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE);
|
||||
|
||||
return MediaManager::NotifyRecordingStatusChange(mWindow,
|
||||
aMsg,
|
||||
true /* aIsAudio */,
|
||||
true /* aIsVideo */);
|
||||
if (aMsg.EqualsLiteral("shutdown")) {
|
||||
ReleaseAudioChannelAgent();
|
||||
}
|
||||
|
||||
nsresult rv = MediaManager::NotifyRecordingStatusChange(mWindow,
|
||||
aMsg,
|
||||
true /* aIsAudio */,
|
||||
true /* aIsVideo */);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
#ifdef MOZ_B2G
|
||||
if (aMsg.EqualsLiteral("starting") && !mAudioChannelAgent) {
|
||||
mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
|
||||
if (!mAudioChannelAgent) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
// Camera app will stop recording when it falls to the background, so no callback is necessary.
|
||||
mAudioChannelAgent->Init(mWindow, (int32_t)AudioChannel::Content, nullptr);
|
||||
// Video recording doesn't output any sound, so it's not necessary to check canPlay.
|
||||
int32_t canPlay;
|
||||
mAudioChannelAgent->StartPlaying(&canPlay);
|
||||
}
|
||||
#endif
|
||||
return rv;
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
@@ -1224,7 +1259,7 @@ nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState a
|
||||
int32_t aArg, int32_t aTrackNum)
|
||||
{
|
||||
// For now, we do nothing with 'aStatus' and 'aTrackNum'.
|
||||
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
|
||||
DOM_CAMERA_LOGT("%s:%d : this=%p, state=%u\n", __func__, __LINE__, this, aState);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
ErrorResult ignored;
|
||||
@@ -1417,7 +1452,7 @@ nsDOMCameraControl::OnTakePictureComplete(nsIDOMBlob* aPicture)
|
||||
void
|
||||
nsDOMCameraControl::OnUserError(CameraControlListener::UserContext aContext, nsresult aError)
|
||||
{
|
||||
DOM_CAMERA_LOGI("DOM OnUserError : this=%paContext=%u, aError=0x%x\n",
|
||||
DOM_CAMERA_LOGI("DOM OnUserError : this=%p, aContext=%u, aError=0x%x\n",
|
||||
this, aContext, aError);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
@@ -1470,6 +1505,8 @@ nsDOMCameraControl::OnUserError(CameraControlListener::UserContext aContext, nsr
|
||||
|
||||
case CameraControlListener::kInStartRecording:
|
||||
promise = mStartRecordingPromise.forget();
|
||||
mRecording = false;
|
||||
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
|
||||
break;
|
||||
|
||||
case CameraControlListener::kInStartFaceDetection:
|
||||
|
||||
@@ -182,6 +182,7 @@ protected:
|
||||
bool IsWindowStillActive();
|
||||
nsresult SelectPreviewSize(const dom::CameraSize& aRequestedPreviewSize, ICameraControl::Size& aSelectedPreviewSize);
|
||||
|
||||
void ReleaseAudioChannelAgent();
|
||||
nsresult NotifyRecordingStatusChange(const nsString& aMsg);
|
||||
|
||||
already_AddRefed<dom::Promise> CreatePromise(ErrorResult& aRv);
|
||||
@@ -223,7 +224,7 @@ protected:
|
||||
dom::CameraStartRecordingOptions mOptions;
|
||||
nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
|
||||
DOMCameraControlListener::PreviewState mPreviewState;
|
||||
|
||||
bool mRecording;
|
||||
bool mSetInitialConfig;
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
|
||||
@@ -269,6 +269,8 @@ namespace android {
|
||||
return 320;
|
||||
} else if (strcmp(aParameter, "vid.height") == 0) {
|
||||
return 240;
|
||||
} else if (strcmp(aParameter, "vid.fps") == 0) {
|
||||
return 30;
|
||||
}
|
||||
return 0;
|
||||
case CAMCORDER_QUALITY_HIGH:
|
||||
@@ -277,6 +279,8 @@ namespace android {
|
||||
return 640;
|
||||
} else if (strcmp(aParameter, "vid.height") == 0) {
|
||||
return 480;
|
||||
} else if (strcmp(aParameter, "vid.fps") == 0) {
|
||||
return 30;
|
||||
}
|
||||
return 0;
|
||||
default:
|
||||
|
||||
@@ -1238,7 +1238,7 @@ nsGonkCameraControl::StopRecordingImpl()
|
||||
class RecordingComplete : public nsRunnable
|
||||
{
|
||||
public:
|
||||
RecordingComplete(DeviceStorageFile* aFile)
|
||||
RecordingComplete(already_AddRefed<DeviceStorageFile> aFile)
|
||||
: mFile(aFile)
|
||||
{ }
|
||||
|
||||
@@ -1268,6 +1268,11 @@ nsGonkCameraControl::StopRecordingImpl()
|
||||
|
||||
mRecorder->stop();
|
||||
mRecorder = nullptr;
|
||||
#else
|
||||
if (!mVideoFile) {
|
||||
return NS_OK;
|
||||
}
|
||||
#endif
|
||||
OnRecorderStateChange(CameraControlListener::kRecorderStopped);
|
||||
|
||||
{
|
||||
@@ -1282,10 +1287,7 @@ nsGonkCameraControl::StopRecordingImpl()
|
||||
}
|
||||
|
||||
// notify DeviceStorage that the new video file is closed and ready
|
||||
return NS_DispatchToMainThread(new RecordingComplete(mVideoFile));
|
||||
#else
|
||||
return NS_OK;
|
||||
#endif
|
||||
return NS_DispatchToMainThread(new RecordingComplete(mVideoFile.forget()));
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
||||
@@ -416,6 +416,32 @@ TestGonkCameraHardware::AutoFocus()
|
||||
return OK;
|
||||
}
|
||||
|
||||
int
|
||||
TestGonkCameraHardware::CancelAutoFocus()
|
||||
{
|
||||
class Delegate : public ControlMessage
|
||||
{
|
||||
public:
|
||||
Delegate(TestGonkCameraHardware* aTestHw)
|
||||
: ControlMessage(aTestHw)
|
||||
{ }
|
||||
|
||||
protected:
|
||||
NS_IMETHOD
|
||||
RunImpl() override
|
||||
{
|
||||
return mJSTestWrapper->CancelAutoFocus();
|
||||
}
|
||||
};
|
||||
|
||||
DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
|
||||
nsresult rv = WaitWhileRunningOnMainThread(new Delegate(this));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
int
|
||||
TestGonkCameraHardware::StartFaceDetection()
|
||||
{
|
||||
|
||||
@@ -33,6 +33,7 @@ class TestGonkCameraHardware : public android::GonkCameraHardware
|
||||
public:
|
||||
virtual nsresult Init() override;
|
||||
virtual int AutoFocus() override;
|
||||
virtual int CancelAutoFocus() override;
|
||||
virtual int StartFaceDetection() override;
|
||||
virtual int StopFaceDetection() override;
|
||||
virtual int TakePicture() override;
|
||||
|
||||
@@ -103,6 +103,14 @@ CameraTestSuite.prototype = {
|
||||
_lowMemSet: false,
|
||||
_reloading: false,
|
||||
|
||||
_setupPermission: function(permission) {
|
||||
if (!SpecialPowers.hasPermission(permission, document)) {
|
||||
info("requesting " + permission + " permission");
|
||||
SpecialPowers.addPermission(permission, true, document);
|
||||
this._reloading = true;
|
||||
}
|
||||
},
|
||||
|
||||
/* Returns a promise which is resolved when the test suite is ready
|
||||
to be executing individual test cases. One may provide the expected
|
||||
hardware type here if desired; the default is to use the JS test
|
||||
@@ -111,24 +119,28 @@ CameraTestSuite.prototype = {
|
||||
/* Depending on how we run the mochitest, we may not have the necessary
|
||||
permissions yet. If we do need to request them, then we have to reload
|
||||
the window to ensure the reconfiguration propogated properly. */
|
||||
if (!SpecialPowers.hasPermission("camera", document)) {
|
||||
info("requesting camera permission");
|
||||
this._reloading = true;
|
||||
SpecialPowers.addPermission("camera", true, document);
|
||||
this._setupPermission("camera");
|
||||
this._setupPermission("device-storage:videos");
|
||||
this._setupPermission("device-storage:videos-create");
|
||||
this._setupPermission("device-storage:videos-write");
|
||||
|
||||
if (this._reloading) {
|
||||
window.location.reload();
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
info("has camera permission");
|
||||
info("has necessary permissions");
|
||||
if (!isDefined(hwType)) {
|
||||
hwType = 'hardware';
|
||||
}
|
||||
|
||||
this._hwType = hwType;
|
||||
return new Promise(function(resolve, reject) {
|
||||
SpecialPowers.pushPrefEnv({'set': [['camera.control.test.permission', true]]}, function() {
|
||||
SpecialPowers.pushPrefEnv({'set': [['camera.control.test.enabled', hwType]]}, function() {
|
||||
resolve();
|
||||
SpecialPowers.pushPrefEnv({'set': [['device.storage.prompt.testing', true]]}, function() {
|
||||
SpecialPowers.pushPrefEnv({'set': [['camera.control.test.permission', true]]}, function() {
|
||||
SpecialPowers.pushPrefEnv({'set': [['camera.control.test.enabled', hwType]]}, function() {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
[DEFAULT]
|
||||
support-files = camera_common.js
|
||||
|
||||
[test_camera_configuration.html]
|
||||
[test_camera_release.html]
|
||||
[test_camera_auto_focus.html]
|
||||
[test_camera_take_picture.html]
|
||||
[test_camera_record.html]
|
||||
skip-if = toolkit == 'gonk'
|
||||
[test_camera_face_detection.html]
|
||||
[test_camera_fake_parameters.html]
|
||||
[test_camera_hardware_init_failure.html]
|
||||
[test_camera.html]
|
||||
skip-if = toolkit != 'gonk'
|
||||
[test_camera_2.html]
|
||||
skip-if = toolkit != 'gonk'
|
||||
[test_camera_3.html]
|
||||
skip-if = toolkit != 'gonk'
|
||||
[test_camera_hardware_init_failure.html]
|
||||
[test_camera_hardware_failures.html]
|
||||
[test_bug975472.html]
|
||||
[test_camera_fake_parameters.html]
|
||||
[test_camera_hardware_face_detection.html]
|
||||
[test_camera_hardware_auto_focus_moving_cb.html]
|
||||
[test_bug1022766.html]
|
||||
[test_bug1037322.html]
|
||||
[test_bug1099390.html]
|
||||
[test_bug1104913.html]
|
||||
skip-if = toolkit != 'gonk'
|
||||
[test_camera_bad_initial_config.html]
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for bug 1022766</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="camera_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<video id="viewfinder" width="200" height="200" autoplay></video>
|
||||
<img src="#" alt="This image is going to load" id="testimage"/>
|
||||
<script class="testbody" type="text/javascript;version=1.7">
|
||||
|
||||
var suite = new CameraTestSuite();
|
||||
|
||||
suite.test('bug-1022766', function() {
|
||||
function triggerAutoFocus(p) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var firstCall = false;
|
||||
var secondCall = false;
|
||||
|
||||
function end() {
|
||||
if (firstCall && secondCall) {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// It doesn't matter if the emulator supports focus or not;
|
||||
// this is just testing the sequencing.
|
||||
suite.camera.autoFocus().then(function(p) {
|
||||
ok(false, "First call to autoFocus() succeeded unexpectedly");
|
||||
firstCall = true;
|
||||
end();
|
||||
}, function(e) {
|
||||
ok(e.name === 'NS_ERROR_IN_PROGRESS', 'First call to autoFocus() failed with: ' + e);
|
||||
firstCall = true;
|
||||
end();
|
||||
});
|
||||
|
||||
suite.camera.autoFocus().then(function(p) {
|
||||
ok(true, "Second call to autoFocus() succeeded");
|
||||
secondCall = true;
|
||||
end();
|
||||
}, function(e) {
|
||||
ok(false, "Second call to autoFocus() failed unexpectedly with: " + e);
|
||||
secondCall = true;
|
||||
end();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(triggerAutoFocus, suite.rejectGetCamera)
|
||||
});
|
||||
|
||||
suite.setup()
|
||||
.then(suite.run);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,69 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for bug 1037322</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="camera_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<video id="viewfinder" width="200" height="200" autoplay></video>
|
||||
<img src="#" alt="This image is going to load" id="testimage"/>
|
||||
<script class="testbody" type="text/javascript;version=1.7">
|
||||
|
||||
var suite = new CameraTestSuite();
|
||||
|
||||
suite.test('bug-1037322', function() {
|
||||
var cameraManager = navigator.mozCameras;
|
||||
var whichCamera = cameraManager.getListOfCameras()[0];
|
||||
|
||||
var postConfig = {
|
||||
mode: 'picture',
|
||||
recorderProfile: 'low',
|
||||
previewSize: {
|
||||
width: 320,
|
||||
height: 240
|
||||
}
|
||||
};
|
||||
|
||||
function resolveGetCamera(p) {
|
||||
suite.camera = p.camera;
|
||||
|
||||
// Check the default configuration
|
||||
var cfg = p.configuration;
|
||||
ok(cfg.mode === "unspecified", "Initial mode = " + cfg.mode);
|
||||
ok(cfg.previewSize.width === 0 && cfg.previewSize.height === 0,
|
||||
"Initial preview size = " + cfg.previewSize.width + "x" + cfg.previewSize.height);
|
||||
ok(cfg.recorderProfile === "default",
|
||||
"Initial recorder profile = '" + cfg.recorderProfile + "'");
|
||||
}
|
||||
|
||||
function configure(p) {
|
||||
// Apply our specific configuration
|
||||
return suite.camera.setConfiguration(postConfig);
|
||||
}
|
||||
|
||||
function resolveConfigure(cfg) {
|
||||
// Check our specific configuration
|
||||
ok(cfg.mode === postConfig.mode, "Configured mode = " + cfg.mode);
|
||||
ok(cfg.previewSize.width === postConfig.previewSize.width &&
|
||||
cfg.previewSize.height === postConfig.previewSize.height,
|
||||
"Configured preview size = " + cfg.previewSize.width + "x" + cfg.previewSize.height);
|
||||
ok(cfg.recorderProfile === postConfig.recorderProfile,
|
||||
"Configured recorder profile = '" + cfg.recorderProfile + "'");
|
||||
}
|
||||
|
||||
return cameraManager.getCamera(whichCamera, {mode: 'unspecified'})
|
||||
.then(resolveGetCamera, suite.rejectGetCamera)
|
||||
.then(configure)
|
||||
.then(resolveConfigure, suite.rejectConfigure);
|
||||
});
|
||||
|
||||
suite.setup()
|
||||
.then(suite.run);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,55 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for bug 1099390</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="camera_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<video id="viewfinder" width="200" height="200" autoplay></video>
|
||||
<img src="#" alt="This image is going to load" id="testimage"/>
|
||||
<script class="testbody" type="text/javascript;version=1.7">
|
||||
|
||||
var suite = new CameraTestSuite();
|
||||
|
||||
suite.test('bug-1099390', function() {
|
||||
function release(p) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var gotCloseEvent = false;
|
||||
var gotReleasePromise = false;
|
||||
|
||||
var onClosed = function(e) {
|
||||
suite.camera.removeEventListener('close', onClosed);
|
||||
ok(!gotCloseEvent, "gotCloseEvent was " + gotCloseEvent);
|
||||
ok(e.reason === "HardwareReleased", "'close' event reason is: " + e.reason);
|
||||
gotCloseEvent = true;
|
||||
if (gotReleasePromise) {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
suite.camera.addEventListener('close', onClosed);
|
||||
|
||||
suite.camera.release().then(function(p) {
|
||||
ok(true, "released camera");
|
||||
gotReleasePromise = true;
|
||||
if (gotCloseEvent) {
|
||||
resolve();
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(release, suite.rejectGetCamera);
|
||||
});
|
||||
|
||||
suite.setup()
|
||||
.then(suite.run);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+95
-30
@@ -1,20 +1,15 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=940424
|
||||
-->
|
||||
<head>
|
||||
<title>Bug 940424 - Test camera hardware API failure handling</title>
|
||||
<title>Test for auto focus</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="camera_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=940424">Mozilla Bug 940424</a>
|
||||
<video id="viewfinder" width = "200" height = "200" autoplay></video>
|
||||
<img src="#" alt="This image is going to load" id="testimage"/>
|
||||
|
||||
<video id="viewfinder" width="200" height="200" autoplay></video>
|
||||
<img src="#" alt="This image is going to load" id="testimage"/>
|
||||
<script class="testbody" type="text/javascript;version=1.7">
|
||||
|
||||
var suite = new CameraTestSuite();
|
||||
@@ -129,39 +124,109 @@ suite.test('auto-focus-failures', function() {
|
||||
.then(suite.expectedRejectAutoFocus, rejectAutoFocusError)
|
||||
});
|
||||
|
||||
suite.test('take-picture-failures', function() {
|
||||
function startTakePictureProcessError(p) {
|
||||
suite.hw.attach({
|
||||
takePicture: function() {
|
||||
suite.hw.fireTakePictureError();
|
||||
suite.test('auto-focus-moving', function() {
|
||||
function triggerAutoFocusMoving(p) {
|
||||
var sync = new Promise(function(resolve, reject) {
|
||||
function onEvent(e) {
|
||||
suite.camera.removeEventListener('focus', onEvent);
|
||||
ok(e.newState === 'focusing', 'autofocus event state focusing == ' + e.newState);
|
||||
resolve();
|
||||
}
|
||||
suite.camera.addEventListener('focus', onEvent);
|
||||
});
|
||||
return suite.camera.takePicture();
|
||||
|
||||
suite.hw.fireAutoFocusMoving(true);
|
||||
return sync;
|
||||
}
|
||||
|
||||
function rejectTakePictureProcessError(e) {
|
||||
ok(e.name === 'NS_ERROR_FAILURE', 'takePicture() process should fail: ' + e);
|
||||
}
|
||||
|
||||
function startTakePictureError(p) {
|
||||
suite.hw.attach({
|
||||
takePicture: function() {
|
||||
throw SpecialPowers.Cr.NS_ERROR_FAILURE;
|
||||
function waitAutoFocusComplete(p) {
|
||||
var sync = new Promise(function(resolve, reject) {
|
||||
function onEvent(e) {
|
||||
suite.camera.removeEventListener('focus', onEvent);
|
||||
ok(e.newState === 'focused', 'autofocus event state focused == ' + e.newState);
|
||||
resolve();
|
||||
}
|
||||
suite.camera.addEventListener('focus', onEvent);
|
||||
});
|
||||
return suite.camera.takePicture();
|
||||
|
||||
// Missing the fireAutoFocusComplete but it should timeout on its own
|
||||
suite.hw.fireAutoFocusMoving(false);
|
||||
return sync;
|
||||
}
|
||||
|
||||
function rejectTakePictureError(e) {
|
||||
ok(e.name === 'NS_ERROR_FAILURE', 'takePicture() should fail: ' + e);
|
||||
function runAutoFocusCycle(p) {
|
||||
return triggerAutoFocusMoving(p)
|
||||
.then(waitAutoFocusComplete);
|
||||
}
|
||||
|
||||
/* If the driver doesn't supply an onAutoFocusComplete notification,
|
||||
gecko will timeout and provide it. After three times, it will no
|
||||
longer rely upon a timeout and fire it immediately. */
|
||||
return suite.getCamera()
|
||||
.then(runAutoFocusCycle)
|
||||
.then(runAutoFocusCycle)
|
||||
.then(runAutoFocusCycle)
|
||||
.then(runAutoFocusCycle);
|
||||
});
|
||||
|
||||
suite.test('auto-focus-interrupted', function() {
|
||||
// bug 1022766
|
||||
function triggerAutoFocus(p) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var firstCall = false;
|
||||
var secondCall = false;
|
||||
|
||||
function end() {
|
||||
if (firstCall && secondCall) {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// It doesn't matter if the emulator supports focus or not;
|
||||
// this is just testing the sequencing.
|
||||
suite.camera.autoFocus().then(function(p) {
|
||||
ok(false, "First call to autoFocus() succeeded unexpectedly");
|
||||
firstCall = true;
|
||||
end();
|
||||
}, function(e) {
|
||||
ok(e.name === 'NS_ERROR_IN_PROGRESS', 'First call to autoFocus() failed with: ' + e);
|
||||
firstCall = true;
|
||||
end();
|
||||
});
|
||||
|
||||
suite.camera.autoFocus().then(function(p) {
|
||||
ok(true, "Second call to autoFocus() succeeded");
|
||||
secondCall = true;
|
||||
end();
|
||||
}, function(e) {
|
||||
ok(false, "Second call to autoFocus() failed unexpectedly with: " + e);
|
||||
secondCall = true;
|
||||
end();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.catch(suite.rejectGetCamera)
|
||||
.then(startTakePictureProcessError)
|
||||
.then(suite.expectedRejectTakePicture, rejectTakePictureProcessError)
|
||||
.then(startTakePictureError)
|
||||
.then(suite.expectedRejectTakePicture, rejectTakePictureError)
|
||||
.then(triggerAutoFocus, suite.rejectGetCamera)
|
||||
});
|
||||
|
||||
suite.test('cancel-auto-focus', function() {
|
||||
function cancelAutoFocus(p) {
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
suite.hw.attach({
|
||||
cancelAutoFocus: function() {
|
||||
ok(true, 'got cancel auto focus');
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
suite.camera.resumeContinuousFocus();
|
||||
return promise;
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(cancelAutoFocus, suite.rejectGetCamera);
|
||||
});
|
||||
|
||||
suite.setup()
|
||||
@@ -1,44 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for bad initial configuration</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="camera_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<video id="viewfinder" width="200" height="200" autoplay></video>
|
||||
<img src="#" alt="This image is going to load" id="testimage"/>
|
||||
<script class="testbody" type="text/javascript;version=1.7">
|
||||
|
||||
var suite = new CameraTestSuite();
|
||||
|
||||
suite.test('bad-initial-config', function() {
|
||||
function getCamera() {
|
||||
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
|
||||
var config = {
|
||||
mode: 'picture',
|
||||
recorderProfile: 'foobar',
|
||||
};
|
||||
|
||||
return navigator.mozCameras.getCamera(whichCamera, config);
|
||||
}
|
||||
|
||||
function rejectGetCamera(error) {
|
||||
ok(error.name === "NS_ERROR_NOT_AVAILABLE",
|
||||
"getCamera() failed with: " + error.name);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return getCamera()
|
||||
.then(suite.expectedRejectGetCamera, rejectGetCamera);
|
||||
});
|
||||
|
||||
suite.setup()
|
||||
.then(suite.run);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,545 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for camera configuration</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="camera_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<video id="viewfinder" width="200" height="200" autoplay></video>
|
||||
<img src="#" alt="This image is going to load" id="testimage"/>
|
||||
<script class="testbody" type="text/javascript;version=1.7">
|
||||
|
||||
var suite = new CameraTestSuite();
|
||||
|
||||
function verifyConfig(cfg, expConfig)
|
||||
{
|
||||
ok(cfg.mode === expConfig.mode, "Configured mode = " + cfg.mode +
|
||||
", expected = " + expConfig.mode);
|
||||
ok(cfg.previewSize.width === expConfig.previewSize.width &&
|
||||
cfg.previewSize.height === expConfig.previewSize.height,
|
||||
"Configured preview size = " + cfg.previewSize.width + "x" + cfg.previewSize.height +
|
||||
", expected = " + expConfig.previewSize.width + "x" + expConfig.previewSize.height);
|
||||
ok(cfg.pictureSize.width === expConfig.pictureSize.width &&
|
||||
cfg.pictureSize.height === expConfig.pictureSize.height,
|
||||
"Configured picture size = " + cfg.pictureSize.width + "x" + cfg.pictureSize.height +
|
||||
", expected = " + expConfig.pictureSize.width + "x" + expConfig.pictureSize.height);
|
||||
ok(cfg.recorderProfile === expConfig.recorderProfile,
|
||||
"Configured recorder profile = '" + cfg.recorderProfile + "'" +
|
||||
", expected = '" + expConfig.recorderProfile + "'");
|
||||
}
|
||||
|
||||
function setAndVerifyConfig(setConfig, expConfig)
|
||||
{
|
||||
return suite.getCamera(undefined, setConfig)
|
||||
.catch(suite.rejectGetCamera)
|
||||
.then(function(p) {
|
||||
verifyConfig(p.configuration, expConfig);
|
||||
});
|
||||
}
|
||||
|
||||
suite.test('bad-initial-config', function() {
|
||||
function getCamera() {
|
||||
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
|
||||
var config = {
|
||||
mode: 'picture',
|
||||
recorderProfile: 'foobar',
|
||||
};
|
||||
|
||||
return navigator.mozCameras.getCamera(whichCamera, config);
|
||||
}
|
||||
|
||||
function rejectGetCamera(error) {
|
||||
ok(error.name === "NS_ERROR_NOT_AVAILABLE",
|
||||
"getCamera() failed with: " + error.name);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return getCamera()
|
||||
.then(suite.expectedRejectGetCamera, rejectGetCamera);
|
||||
});
|
||||
|
||||
suite.test('start-unspecified', function() {
|
||||
// bug 1037322
|
||||
var cameraManager = navigator.mozCameras;
|
||||
var whichCamera = cameraManager.getListOfCameras()[0];
|
||||
|
||||
var postConfig = {
|
||||
mode: 'picture',
|
||||
recorderProfile: 'low',
|
||||
previewSize: {
|
||||
width: 320,
|
||||
height: 240
|
||||
},
|
||||
pictureSize: {
|
||||
width: 320,
|
||||
height: 240
|
||||
}
|
||||
};
|
||||
|
||||
function resolveGetCamera(p) {
|
||||
suite.camera = p.camera;
|
||||
|
||||
// Check the default configuration
|
||||
var cfg = p.configuration;
|
||||
ok(cfg.mode === "unspecified", "Initial mode = " + cfg.mode);
|
||||
ok(cfg.previewSize.width === 0 && cfg.previewSize.height === 0,
|
||||
"Initial preview size = " + cfg.previewSize.width + "x" + cfg.previewSize.height);
|
||||
ok(cfg.recorderProfile === "default",
|
||||
"Initial recorder profile = '" + cfg.recorderProfile + "'");
|
||||
}
|
||||
|
||||
function configure(p) {
|
||||
// Apply our specific configuration
|
||||
return suite.camera.setConfiguration(postConfig);
|
||||
}
|
||||
|
||||
function resolveConfigure(cfg) {
|
||||
// Check our specific configuration
|
||||
verifyConfig(cfg, postConfig);
|
||||
}
|
||||
|
||||
return cameraManager.getCamera(whichCamera, {mode: 'unspecified'})
|
||||
.then(resolveGetCamera, suite.rejectGetCamera)
|
||||
.then(configure)
|
||||
.then(resolveConfigure, suite.rejectConfigure);
|
||||
});
|
||||
|
||||
suite.test('picture-mode', function() {
|
||||
suite.hw.params['preview-size'] = '1x1';
|
||||
suite.hw.params['picture-size'] = '1x1';
|
||||
suite.hw.params['preview-size-values'] = '640x480,320x240,1x1';
|
||||
suite.hw.params['picture-size-values'] = '640x480,320x240,1x1';
|
||||
suite.hw.params['video-size-values'] = '320x240';
|
||||
|
||||
var setConfig = {
|
||||
mode: 'picture',
|
||||
previewSize: {
|
||||
width: 640,
|
||||
height: 480
|
||||
}
|
||||
};
|
||||
|
||||
var expConfig = {
|
||||
mode: 'picture',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 640,
|
||||
height: 480
|
||||
},
|
||||
pictureSize: {
|
||||
width: 640,
|
||||
height: 480
|
||||
}
|
||||
};
|
||||
|
||||
return setAndVerifyConfig(setConfig, expConfig);
|
||||
});
|
||||
|
||||
suite.test('picture-mode-larger-picture-size', function() {
|
||||
suite.hw.params['preview-size'] = '1x1';
|
||||
suite.hw.params['picture-size'] = '1x1';
|
||||
suite.hw.params['preview-size-values'] = '640x480,320x240,1x1';
|
||||
suite.hw.params['picture-size-values'] = '1280x960,640x480,320x240,1x1';
|
||||
suite.hw.params['video-size-values'] = '320x240';
|
||||
|
||||
var setConfig = {
|
||||
mode: 'picture',
|
||||
previewSize: {
|
||||
width: 640,
|
||||
height: 480
|
||||
}
|
||||
};
|
||||
|
||||
var expConfig = {
|
||||
mode: 'picture',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 640,
|
||||
height: 480
|
||||
},
|
||||
pictureSize: {
|
||||
width: 1280,
|
||||
height: 960
|
||||
}
|
||||
};
|
||||
|
||||
return setAndVerifyConfig(setConfig, expConfig);
|
||||
});
|
||||
|
||||
suite.test('picture-mode-size-unsupported-big', function() {
|
||||
suite.hw.params['preview-size'] = '1x1';
|
||||
suite.hw.params['picture-size'] = '1x1';
|
||||
suite.hw.params['preview-size-values'] = '640x480,320x240,1x1';
|
||||
suite.hw.params['picture-size-values'] = '1280x960,640x480,320x240,1x1';
|
||||
suite.hw.params['video-size-values'] = '320x240';
|
||||
|
||||
var setConfig = {
|
||||
mode: 'picture',
|
||||
previewSize: {
|
||||
width: 640,
|
||||
height: 480
|
||||
},
|
||||
pictureSize: {
|
||||
width: 2000,
|
||||
height: 2000
|
||||
},
|
||||
};
|
||||
|
||||
var expConfig = {
|
||||
mode: 'picture',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 640,
|
||||
height: 480
|
||||
},
|
||||
pictureSize: {
|
||||
width: 1280,
|
||||
height: 960
|
||||
},
|
||||
};
|
||||
|
||||
return setAndVerifyConfig(setConfig, expConfig);
|
||||
});
|
||||
|
||||
suite.test('picture-mode-size-unsupported', function() {
|
||||
suite.hw.params['preview-size'] = '1x1';
|
||||
suite.hw.params['picture-size'] = '1x1';
|
||||
suite.hw.params['preview-size-values'] = '640x480,320x240,1x1';
|
||||
suite.hw.params['picture-size-values'] = '1280x960,640x480,320x240,100x100,1x1';
|
||||
suite.hw.params['video-size-values'] = '320x240';
|
||||
|
||||
var setConfig = {
|
||||
mode: 'picture',
|
||||
previewSize: {
|
||||
width: 640,
|
||||
height: 480
|
||||
},
|
||||
pictureSize: {
|
||||
width: 641,
|
||||
height: 481,
|
||||
},
|
||||
};
|
||||
|
||||
var expConfig = {
|
||||
mode: 'picture',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 640,
|
||||
height: 480
|
||||
},
|
||||
pictureSize: {
|
||||
width: 640,
|
||||
height: 480
|
||||
},
|
||||
};
|
||||
|
||||
return setAndVerifyConfig(setConfig, expConfig);
|
||||
});
|
||||
|
||||
suite.test('picture-mode-asr-mismatch', function() {
|
||||
suite.hw.params['preview-size'] = '320x240';
|
||||
suite.hw.params['picture-size'] = '640x480';
|
||||
suite.hw.params['preview-size-values'] = '640x480,320x240,50x50';
|
||||
suite.hw.params['picture-size-values'] = '1280x960,640x480,320x240,100x100';
|
||||
suite.hw.params['video-size-values'] = '320x240';
|
||||
|
||||
var setConfig = {
|
||||
mode: 'picture',
|
||||
previewSize: {
|
||||
width: 640,
|
||||
height: 480
|
||||
},
|
||||
pictureSize: {
|
||||
width: 100,
|
||||
height: 100,
|
||||
},
|
||||
};
|
||||
|
||||
var expConfig = {
|
||||
mode: 'picture',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 50,
|
||||
height: 50
|
||||
},
|
||||
pictureSize: {
|
||||
width: 100,
|
||||
height: 100,
|
||||
},
|
||||
};
|
||||
|
||||
return setAndVerifyConfig(setConfig, expConfig);
|
||||
});
|
||||
|
||||
suite.test('picture-mode-update-video-size', function() {
|
||||
suite.hw.params['preview-size'] = '320x240';
|
||||
suite.hw.params['picture-size'] = '640x480';
|
||||
suite.hw.params['video-size'] = '50x50';
|
||||
suite.hw.params['preview-size-values'] = '640x480,320x240,50x50';
|
||||
suite.hw.params['video-size-values'] = '1280x960,640x480,320x240,50x50';
|
||||
suite.hw.params['picture-size-values'] = '1280x960,640x480,320x240,100x100';
|
||||
|
||||
var setConfig = {
|
||||
mode: 'picture',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 320,
|
||||
height: 240
|
||||
},
|
||||
pictureSize: {
|
||||
width: 320,
|
||||
height: 240,
|
||||
},
|
||||
};
|
||||
|
||||
var expConfig = {
|
||||
mode: 'picture',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 320,
|
||||
height: 240
|
||||
},
|
||||
pictureSize: {
|
||||
width: 320,
|
||||
height: 240,
|
||||
},
|
||||
};
|
||||
|
||||
function checkVideoSize(p) {
|
||||
ok(suite.hw.params['video-size'] === '320x240', 'video size reset with picture mode switch');
|
||||
}
|
||||
|
||||
return setAndVerifyConfig(setConfig, expConfig)
|
||||
.then(checkVideoSize);
|
||||
});
|
||||
|
||||
suite.test('picture-mode-no-update-video-size', function() {
|
||||
suite.hw.params['preview-size'] = '320x240';
|
||||
suite.hw.params['picture-size'] = '640x480';
|
||||
suite.hw.params['video-size'] = '1280x960';
|
||||
suite.hw.params['preview-size-values'] = '640x480,320x240,50x50';
|
||||
suite.hw.params['video-size-values'] = '1280x960,640x480,320x240,50x50';
|
||||
suite.hw.params['picture-size-values'] = '1280x960,640x480,320x240,100x100';
|
||||
|
||||
var setConfig = {
|
||||
mode: 'picture',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 640,
|
||||
height: 480
|
||||
},
|
||||
pictureSize: {
|
||||
width: 640,
|
||||
height: 480,
|
||||
},
|
||||
};
|
||||
|
||||
var expConfig = {
|
||||
mode: 'picture',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 640,
|
||||
height: 480
|
||||
},
|
||||
pictureSize: {
|
||||
width: 640,
|
||||
height: 480,
|
||||
},
|
||||
};
|
||||
|
||||
function checkVideoSize(p) {
|
||||
ok(suite.hw.params['video-size'] === '1280x960', 'video size retained with picture mode switch');
|
||||
}
|
||||
|
||||
return setAndVerifyConfig(setConfig, expConfig)
|
||||
.then(checkVideoSize);
|
||||
});
|
||||
|
||||
suite.test('video-mode-preview-size', function() {
|
||||
suite.hw.params['preview-size'] = '1x1';
|
||||
suite.hw.params['picture-size'] = '1x1';
|
||||
suite.hw.params['recording-hint'] = 'false';
|
||||
suite.hw.params['preview-size-values'] = '640x480,320x240,1x1';
|
||||
suite.hw.params['picture-size-values'] = '700x700,640x480,320x240,1x1';
|
||||
|
||||
var setConfig = {
|
||||
mode: 'video',
|
||||
recorderProfile: 'qvga'
|
||||
};
|
||||
|
||||
var expConfig = {
|
||||
mode: 'video',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 320,
|
||||
height: 240
|
||||
},
|
||||
pictureSize: {
|
||||
width: 700,
|
||||
height: 700
|
||||
}
|
||||
};
|
||||
|
||||
function checkRecordingHint(p) {
|
||||
ok(suite.hw.params['recording-hint'] === 'true', 'recording hint enabled');
|
||||
}
|
||||
|
||||
return setAndVerifyConfig(setConfig, expConfig)
|
||||
.then(checkRecordingHint);
|
||||
});
|
||||
|
||||
suite.test('video-mode', function() {
|
||||
suite.hw.params['preview-size'] = '1x1';
|
||||
suite.hw.params['picture-size'] = '1x1';
|
||||
suite.hw.params['video-size'] = '1x1';
|
||||
suite.hw.params['recording-hint'] = 'false';
|
||||
suite.hw.params['preferred-preview-size-for-video'] = '640x480';
|
||||
suite.hw.params['preview-size-values'] = '640x480,320x240,1x1';
|
||||
suite.hw.params['picture-size-values'] = '700x700,640x480,320x240,1x1';
|
||||
suite.hw.params['video-size-values'] = '700x700,640x480,320x240,1x1';
|
||||
|
||||
var setConfig = {
|
||||
mode: 'video',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 320,
|
||||
height: 240
|
||||
}
|
||||
};
|
||||
|
||||
var expConfig = {
|
||||
mode: 'video',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 320,
|
||||
height: 240
|
||||
},
|
||||
pictureSize: {
|
||||
width: 700,
|
||||
height: 700
|
||||
}
|
||||
};
|
||||
|
||||
function checkRecordingHint(p) {
|
||||
ok(suite.hw.params['recording-hint'] === 'true', 'recording hint enabled');
|
||||
}
|
||||
|
||||
return setAndVerifyConfig(setConfig, expConfig)
|
||||
.then(checkRecordingHint);
|
||||
});
|
||||
|
||||
suite.test('video-mode-larger-preview-size', function() {
|
||||
suite.hw.params['preview-size'] = '1x1';
|
||||
suite.hw.params['picture-size'] = '1x1';
|
||||
suite.hw.params['video-size'] = '1x1';
|
||||
suite.hw.params['recording-hint'] = 'false';
|
||||
suite.hw.params['preferred-preview-size-for-video'] = '640x480';
|
||||
suite.hw.params['preview-size-values'] = '640x480,320x240,1x1';
|
||||
suite.hw.params['picture-size-values'] = '700x700,640x480,320x240,1x1';
|
||||
suite.hw.params['video-size-values'] = '700x700,640x480,320x240,1x1';
|
||||
|
||||
var setConfig = {
|
||||
mode: 'video',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 640,
|
||||
height: 480
|
||||
}
|
||||
};
|
||||
|
||||
var expConfig = {
|
||||
mode: 'video',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 320,
|
||||
height: 240
|
||||
},
|
||||
pictureSize: {
|
||||
width: 700,
|
||||
height: 700
|
||||
}
|
||||
};
|
||||
|
||||
return setAndVerifyConfig(setConfig, expConfig);
|
||||
});
|
||||
|
||||
suite.test('video-mode-smaller-preview-size', function() {
|
||||
suite.hw.params['preview-size'] = '1x1';
|
||||
suite.hw.params['picture-size'] = '1x1';
|
||||
suite.hw.params['video-size'] = '1x1';
|
||||
suite.hw.params['recording-hint'] = 'false';
|
||||
suite.hw.params['preferred-preview-size-for-video'] = '640x480';
|
||||
suite.hw.params['preview-size-values'] = '640x480,320x240,160x120,1x1';
|
||||
suite.hw.params['picture-size-values'] = '700x700,640x480,320x240,1x1';
|
||||
suite.hw.params['video-size-values'] = '700x700,640x480,320x240,1x1';
|
||||
|
||||
var setConfig = {
|
||||
mode: 'video',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 160,
|
||||
height: 120
|
||||
}
|
||||
};
|
||||
|
||||
var expConfig = {
|
||||
mode: 'video',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 160,
|
||||
height: 120
|
||||
},
|
||||
pictureSize: {
|
||||
width: 700,
|
||||
height: 700
|
||||
}
|
||||
};
|
||||
|
||||
return setAndVerifyConfig(setConfig, expConfig);
|
||||
});
|
||||
|
||||
suite.test('video-mode-larger-preview-size-than-preferred', function() {
|
||||
suite.hw.params['preview-size'] = '1x1';
|
||||
suite.hw.params['picture-size'] = '1x1';
|
||||
suite.hw.params['video-size'] = '1x1';
|
||||
suite.hw.params['recording-hint'] = 'false';
|
||||
suite.hw.params['preferred-preview-size-for-video'] = '200x200';
|
||||
suite.hw.params['preview-size-values'] = '640x480,320x240,160x120,1x1';
|
||||
suite.hw.params['picture-size-values'] = '700x700,640x480,320x240,1x1';
|
||||
suite.hw.params['video-size-values'] = '700x700,640x480,400x400,320x240,1x1';
|
||||
|
||||
var setConfig = {
|
||||
mode: 'video',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 320,
|
||||
height: 240
|
||||
}
|
||||
};
|
||||
|
||||
var expConfig = {
|
||||
mode: 'video',
|
||||
recorderProfile: 'qvga',
|
||||
previewSize: {
|
||||
width: 160,
|
||||
height: 120
|
||||
},
|
||||
pictureSize: {
|
||||
width: 700,
|
||||
height: 700
|
||||
}
|
||||
};
|
||||
|
||||
return setAndVerifyConfig(setConfig, expConfig);
|
||||
});
|
||||
|
||||
suite.setup()
|
||||
.then(suite.run);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+124
-3
@@ -92,8 +92,93 @@ function compareFace(aFace, expected)
|
||||
|
||||
var suite = new CameraTestSuite();
|
||||
|
||||
suite.test('face-detection-op', function() {
|
||||
function start(p) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
suite.hw.attach({
|
||||
startFaceDetection: function() {
|
||||
ok(true, "startFaceDetection() requested");
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
try {
|
||||
suite.camera.startFaceDetection();
|
||||
ok(true, "startFaceDetection() succeeded");
|
||||
} catch(e) {
|
||||
ok(false, "startFaceDetection() failed with: " + e.name);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function stop(p) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
suite.hw.attach({
|
||||
stopFaceDetection: function() {
|
||||
ok(true, "stopFaceDetection() requested");
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
try {
|
||||
suite.camera.stopFaceDetection();
|
||||
ok(true, "stopFaceDetection() succeeded");
|
||||
} catch(e) {
|
||||
ok(false, "stopFaceDetection() failed with: " + e.name);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function startFailure(p) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
suite.hw.attach({
|
||||
startFaceDetection: function() {
|
||||
ok(true, "startFaceDetection() requested and failed");
|
||||
resolve();
|
||||
throw SpecialPowers.Cr.NS_ERROR_FAILURE;
|
||||
}
|
||||
});
|
||||
try {
|
||||
suite.camera.startFaceDetection();
|
||||
ok(true, "startFaceDetection() succeeded and error swallowed");
|
||||
} catch(e) {
|
||||
ok(false, "startFaceDetection() failed with: " + e.name);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function stopFailure(p) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
suite.hw.attach({
|
||||
stopFaceDetection: function() {
|
||||
ok(true, "stopFaceDetection() requested and failed");
|
||||
resolve();
|
||||
throw SpecialPowers.Cr.NS_ERROR_FAILURE;
|
||||
}
|
||||
});
|
||||
try {
|
||||
suite.camera.stopFaceDetection();
|
||||
ok(true, "stopFaceDetection() succeeded and error swallowed");
|
||||
} catch(e) {
|
||||
ok(false, "stopFaceDetection() failed with: " + e.name);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(start, suite.rejectGetCamera)
|
||||
.then(stop)
|
||||
.then(startFailure)
|
||||
.then(stopFailure);
|
||||
});
|
||||
|
||||
suite.test('face-detection', function() {
|
||||
function detectFace(msg, expected) {
|
||||
function detectFace(msg, given, expected) {
|
||||
if (expected === undefined) {
|
||||
expected = given;
|
||||
}
|
||||
var sync = new Promise(function(resolve, reject) {
|
||||
function onEvent(evt) {
|
||||
try {
|
||||
@@ -108,7 +193,7 @@ suite.test('face-detection', function() {
|
||||
suite.camera.addEventListener('facesdetected', onEvent);
|
||||
});
|
||||
|
||||
suite.hw.fireFacesDetected(expected);
|
||||
suite.hw.fireFacesDetected(given);
|
||||
return sync;
|
||||
}
|
||||
|
||||
@@ -220,11 +305,47 @@ suite.test('face-detection', function() {
|
||||
);
|
||||
}
|
||||
|
||||
function detectOneFaceExcessScore() {
|
||||
return detectFace('one-face-excess-score',
|
||||
{
|
||||
faces: [ {
|
||||
id: 1,
|
||||
score: 120,
|
||||
bounds: {
|
||||
left: 3,
|
||||
top: 4,
|
||||
right: 5,
|
||||
bottom: 6
|
||||
},
|
||||
leftEye: null,
|
||||
rightEye: null,
|
||||
mouth: null
|
||||
} ]
|
||||
},
|
||||
{
|
||||
faces: [ {
|
||||
id: 1,
|
||||
score: 100,
|
||||
bounds: {
|
||||
left: 3,
|
||||
top: 4,
|
||||
right: 5,
|
||||
bottom: 6
|
||||
},
|
||||
leftEye: null,
|
||||
rightEye: null,
|
||||
mouth: null
|
||||
} ]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(detectOneFace, suite.rejectGetCamera)
|
||||
.then(detectTwoFaces)
|
||||
.then(detectOneFaceNoFeatures)
|
||||
.then(detectNoFaces);
|
||||
.then(detectNoFaces)
|
||||
.then(detectOneFaceExcessScore);
|
||||
});
|
||||
|
||||
suite.setup()
|
||||
@@ -132,6 +132,133 @@ suite.test('fake-low-memory-platform', function() {
|
||||
.then(resolve, suite.rejectGetCamera);
|
||||
});
|
||||
|
||||
suite.test('fake-effects', function() {
|
||||
var supportedValues = ['none', 'mono', 'negative', 'solarize', 'sepia', 'posterize', 'whiteboard', 'blackboard', 'aqua'];
|
||||
suite.hw.params['effect'] = 'none';
|
||||
suite.hw.params['effect-values'] = supportedValues.join(',');
|
||||
|
||||
function resolve(p) {
|
||||
var cap = suite.camera.capabilities;
|
||||
ok(cap.effects.length == supportedValues.length, "Effects length = " + cap.effects.length);
|
||||
|
||||
// make sure expected values are present
|
||||
supportedValues.forEach(function(val) {
|
||||
ok(cap.effects.indexOf(val) != -1, "Effect '" + val + "' is present");
|
||||
});
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(resolve, suite.rejectGetCamera);
|
||||
});
|
||||
|
||||
suite.test('fake-flash-modes', function() {
|
||||
var supportedValues = ['off', 'auto', 'on', 'red-eye', 'torch'];
|
||||
suite.hw.params['flash-mode'] = 'auto';
|
||||
suite.hw.params['flash-mode-values'] = supportedValues.join(',');
|
||||
|
||||
function resolve(p) {
|
||||
var cam = suite.camera;
|
||||
var cap = cam.capabilities;
|
||||
ok(cap.flashModes.length == supportedValues.length, "Flash modes length = " + cap.flashModes.length);
|
||||
|
||||
// make sure expected values are present
|
||||
supportedValues.forEach(function(val) {
|
||||
ok(cap.flashModes.indexOf(val) != -1, "Flash mode '" + val + "' is present");
|
||||
});
|
||||
|
||||
// test setters/getters
|
||||
cap.flashModes.forEach(function(val, index) {
|
||||
cam.flashMode = val;
|
||||
ok(cam.flashMode === val,
|
||||
"Flash Mode [" + index + "] = " + val + ", cam.flashMode = " + cam.flashMode);
|
||||
});
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(resolve, suite.rejectGetCamera);
|
||||
});
|
||||
|
||||
suite.test('fake-focus-modes', function() {
|
||||
var supportedValues = ['auto', 'infinity', 'macro', 'fixed', 'edof', 'continuous-video'];
|
||||
suite.hw.params['focus-mode'] = 'auto';
|
||||
suite.hw.params['focus-mode-values'] = supportedValues.join(',');
|
||||
|
||||
function resolve(p) {
|
||||
var cam = suite.camera;
|
||||
var cap = cam.capabilities;
|
||||
ok(cap.focusModes.length == supportedValues.length, "Focus modes length = " + cap.focusModes.length);
|
||||
|
||||
// make sure expected values are present
|
||||
supportedValues.forEach(function(val) {
|
||||
ok(cap.focusModes.indexOf(val) != -1, "Focus mode '" + val + "' is present");
|
||||
});
|
||||
|
||||
// test setters/getters
|
||||
cap.focusModes.forEach(function(val, index) {
|
||||
cam.focusMode = val;
|
||||
ok(cam.focusMode === val,
|
||||
"Focus Mode [" + index + "] = " + val + ", cam.focusMode = " + cam.focusMode);
|
||||
});
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(resolve, suite.rejectGetCamera);
|
||||
});
|
||||
|
||||
suite.test('fake-white-balance-modes', function() {
|
||||
var supportedValues = ['auto', 'incandescent', 'fluorescent', 'warm-fluorescent', 'daylight', 'cloudy-daylight', 'twilight', 'shade'];
|
||||
suite.hw.params['whitebalance'] = 'auto';
|
||||
suite.hw.params['whitebalance-values'] = supportedValues.join(',');
|
||||
|
||||
function resolve(p) {
|
||||
var cam = suite.camera;
|
||||
var cap = cam.capabilities;
|
||||
ok(cap.whiteBalanceModes.length == supportedValues.length, "White balance modes length = " + cap.whiteBalanceModes.length);
|
||||
|
||||
// make sure expected values are present
|
||||
supportedValues.forEach(function(val) {
|
||||
ok(cap.whiteBalanceModes.indexOf(val) != -1, "White balance mode '" + val + "' is present");
|
||||
});
|
||||
|
||||
// test setters/getters
|
||||
cap.whiteBalanceModes.forEach(function(val, index) {
|
||||
cam.whiteBalanceMode = val;
|
||||
ok(cam.whiteBalanceMode === val,
|
||||
"White balance mode [" + index + "] = " + val + ", cam.whiteBalanceMode = " + cam.whiteBalanceMode);
|
||||
});
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(resolve, suite.rejectGetCamera);
|
||||
});
|
||||
|
||||
suite.test('fake-video-sizes', function() {
|
||||
var supportedValues = ['auto', 'incandescent', 'fluorescent', 'warm-fluorescent', 'daylight', 'cloudy-daylight', 'twilight', 'shade'];
|
||||
suite.hw.params['whitebalance'] = 'auto';
|
||||
suite.hw.params['whitebalance-values'] = supportedValues.join(',');
|
||||
|
||||
function resolve(p) {
|
||||
var cam = suite.camera;
|
||||
var cap = cam.capabilities;
|
||||
ok(cap.whiteBalanceModes.length == supportedValues.length, "White balance modes length = " + cap.whiteBalanceModes.length);
|
||||
|
||||
// make sure expected values are present
|
||||
supportedValues.forEach(function(val) {
|
||||
ok(cap.whiteBalanceModes.indexOf(val) != -1, "White balance mode '" + val + "' is present");
|
||||
});
|
||||
|
||||
// test setters/getters
|
||||
cap.whiteBalanceModes.forEach(function(val, index) {
|
||||
cam.whiteBalanceMode = val;
|
||||
ok(cam.whiteBalanceMode === val,
|
||||
"White balance mode [" + index + "] = " + val + ", cam.whiteBalanceMode = " + cam.whiteBalanceMode);
|
||||
});
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(resolve, suite.rejectGetCamera);
|
||||
});
|
||||
|
||||
suite.test('fake-iso', function() {
|
||||
suite.hw.params['iso'] = 'auto';
|
||||
suite.hw.params['iso-values'] = 'auto,ISO_HJR,ISO100,foo,ISObar,ISO150moz,ISO200,400,ISO800,1600';
|
||||
@@ -158,9 +285,9 @@ suite.test('fake-iso', function() {
|
||||
|
||||
// test setters/getters for individual ISO modes
|
||||
cap.isoModes.forEach(function(iso, index) {
|
||||
cam.iso = iso;
|
||||
ok(cam.iso === iso,
|
||||
"ISO[" + index + "] = " + iso + ", cam.iso = " + cam.iso);
|
||||
cam.isoMode = iso;
|
||||
ok(cam.isoMode === iso,
|
||||
"ISO[" + index + "] = " + iso + ", cam.iso = " + cam.isoMode);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -168,6 +295,19 @@ suite.test('fake-iso', function() {
|
||||
.then(resolve, suite.rejectGetCamera);
|
||||
});
|
||||
|
||||
suite.test('fake-faces-detected', function() {
|
||||
suite.hw.params['max-num-detected-faces-hw'] = '5';
|
||||
|
||||
function resolve(p) {
|
||||
var cap = suite.camera.capabilities;
|
||||
|
||||
ok(cap.maxDetectedFaces == 5, "maxDetectedFaces = " + cap.maxDetectedFaces);
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(resolve, suite.rejectGetCamera);
|
||||
});
|
||||
|
||||
suite.test('fake-metering-areas', function() {
|
||||
suite.hw.params['max-num-metering-areas'] = '1';
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Camera Recording</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="camera_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<video id="viewfinder" width = "200" height = "200" autoplay></video>
|
||||
<img src="#" alt="This image is going to load" id="testimage"/>
|
||||
|
||||
<script class="testbody" type="text/javascript;version=1.7">
|
||||
|
||||
var suite = new CameraTestSuite();
|
||||
|
||||
var baseConfig = {
|
||||
mode: 'video',
|
||||
};
|
||||
|
||||
var testFilePath = 'test.3gp';
|
||||
var storage = navigator.getDeviceStorage("videos");
|
||||
|
||||
function cleanup()
|
||||
{
|
||||
return storage.delete(testFilePath).then(function(p) {
|
||||
}, function(e) {
|
||||
Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
suite.test('recording', function() {
|
||||
function startRecording(p) {
|
||||
var eventPromise = new Promise(function(resolve, reject) {
|
||||
function onEvent(evt) {
|
||||
ok(evt.newState === 'Started', 'recorder state change event = ' + evt.newState);
|
||||
suite.camera.removeEventListener('recorderstatechange', onEvent);
|
||||
resolve();
|
||||
}
|
||||
suite.camera.addEventListener('recorderstatechange', onEvent);
|
||||
});
|
||||
|
||||
var domPromise = suite.camera.startRecording({}, storage, testFilePath);
|
||||
return Promise.all([domPromise, eventPromise]);
|
||||
}
|
||||
|
||||
function stopRecording(p) {
|
||||
var eventPromise = new Promise(function(resolve, reject) {
|
||||
function onEvent(evt) {
|
||||
ok(evt.newState === 'Stopped', 'recorder state change event = ' + evt.newState);
|
||||
suite.camera.removeEventListener('recorderstatechange', onEvent);
|
||||
resolve();
|
||||
}
|
||||
suite.camera.addEventListener('recorderstatechange', onEvent);
|
||||
});
|
||||
|
||||
var domPromise = new Promise(function(resolve, reject) {
|
||||
try {
|
||||
suite.camera.stopRecording();
|
||||
resolve();
|
||||
} catch(e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all([domPromise, eventPromise]);
|
||||
}
|
||||
|
||||
return suite.getCamera(undefined, baseConfig)
|
||||
.then(cleanup, suite.rejectGetCamera)
|
||||
.then(startRecording)
|
||||
.then(stopRecording, suite.rejectStartRecording)
|
||||
.catch(suite.rejectStopRecording);
|
||||
});
|
||||
|
||||
// bug 1152500
|
||||
suite.test('interrupt-record', function() {
|
||||
function startRecording(p) {
|
||||
var startPromise = suite.camera.startRecording({}, storage, testFilePath);
|
||||
suite.camera.stopRecording();
|
||||
return startPromise;
|
||||
}
|
||||
|
||||
function rejectStartRecording(e) {
|
||||
ok(e.name === 'NS_ERROR_ABORT', 'onError called correctly on startRecording interrupted: ' + e);
|
||||
}
|
||||
|
||||
return suite.getCamera(undefined, baseConfig)
|
||||
.then(cleanup, suite.rejectGetCamera)
|
||||
.then(startRecording)
|
||||
.then(suite.expectedRejectStartRecording, rejectStartRecording);
|
||||
});
|
||||
|
||||
// bug 1152500
|
||||
suite.test('already-initiated-recording', function() {
|
||||
function startRecording(p) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var firstCall = false;
|
||||
var secondCall = false;
|
||||
|
||||
function end() {
|
||||
if (firstCall && secondCall) {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
suite.camera.startRecording({}, storage, testFilePath).then(function(p) {
|
||||
ok(true, "First call to startRecording() succeeded");
|
||||
firstCall = true;
|
||||
end();
|
||||
}, function(e) {
|
||||
ok(false, "First call to startRecording() failed unexpectedly with: " + e);
|
||||
firstCall = true;
|
||||
end();
|
||||
});
|
||||
|
||||
suite.camera.startRecording({}, storage, testFilePath).then(function(p) {
|
||||
ok(false, "Second call to startRecording() succeeded unexpectedly");
|
||||
secondCall = true;
|
||||
end();
|
||||
}, function(e) {
|
||||
ok(e.name === 'NS_ERROR_IN_PROGRESS', "Second call to startRecording() failed expectedly with: " + e);
|
||||
secondCall = true;
|
||||
end();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return suite.getCamera(undefined, baseConfig)
|
||||
.then(cleanup, suite.rejectGetCamera)
|
||||
.then(startRecording);
|
||||
});
|
||||
|
||||
// bug 1152500
|
||||
suite.test('already-started-recording', function() {
|
||||
function startRecording(p) {
|
||||
return suite.camera.startRecording({}, storage, testFilePath);
|
||||
}
|
||||
|
||||
function startRecordingAgain(p) {
|
||||
return suite.camera.startRecording({}, storage, testFilePath);
|
||||
}
|
||||
|
||||
function rejectStartRecordingAgain(e) {
|
||||
ok(e.name === 'NS_ERROR_IN_PROGRESS', "Second call to startRecording() failed expectedly with: " + e);
|
||||
}
|
||||
|
||||
return suite.getCamera(undefined, baseConfig)
|
||||
.then(cleanup, suite.rejectGetCamera)
|
||||
.then(startRecording)
|
||||
.then(startRecordingAgain, suite.rejectStartRecording)
|
||||
.then(suite.expectedRejectStartRecording, rejectStartRecordingAgain)
|
||||
});
|
||||
|
||||
suite.setup()
|
||||
.then(suite.run);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -18,6 +18,39 @@ function cameraRelease(p) {
|
||||
return suite.camera.release();
|
||||
}
|
||||
|
||||
suite.test('release-close-event', function() {
|
||||
// bug 1099390
|
||||
function release(p) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var gotCloseEvent = false;
|
||||
var gotReleasePromise = false;
|
||||
|
||||
var onClosed = function(e) {
|
||||
suite.camera.removeEventListener('close', onClosed);
|
||||
ok(!gotCloseEvent, "gotCloseEvent was " + gotCloseEvent);
|
||||
ok(e.reason === "HardwareReleased", "'close' event reason is: " + e.reason);
|
||||
gotCloseEvent = true;
|
||||
if (gotReleasePromise) {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
suite.camera.addEventListener('close', onClosed);
|
||||
|
||||
suite.camera.release().then(function(p) {
|
||||
ok(true, "released camera");
|
||||
gotReleasePromise = true;
|
||||
if (gotCloseEvent) {
|
||||
resolve();
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(release, suite.rejectGetCamera);
|
||||
});
|
||||
|
||||
suite.test('release-after-release', function() {
|
||||
return suite.getCamera()
|
||||
.then(cameraRelease, suite.rejectGetCamera)
|
||||
@@ -157,6 +190,33 @@ suite.test('stop-recording-after-release', function() {
|
||||
.then(stopRecording, suite.rejectRelease);
|
||||
});
|
||||
|
||||
suite.test('face-detection-after-release', function() {
|
||||
function startFaceDetection(p) {
|
||||
try {
|
||||
suite.camera.startFaceDetection();
|
||||
ok(false, "startFaceDetection() should have failed");
|
||||
} catch(e) {
|
||||
ok(e.result === SpecialPowers.Cr.NS_ERROR_NOT_AVAILABLE,
|
||||
"startFaceDetection() failed with: " + e.name);
|
||||
}
|
||||
}
|
||||
|
||||
function stopFaceDetection(p) {
|
||||
try {
|
||||
suite.camera.stopFaceDetection();
|
||||
ok(false, "stopFaceDetection() should have failed");
|
||||
} catch(e) {
|
||||
ok(e.result === SpecialPowers.Cr.NS_ERROR_NOT_AVAILABLE,
|
||||
"stopFaceDetection() failed with: " + e.name);
|
||||
}
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(cameraRelease, suite.rejectGetCamera)
|
||||
.then(startFaceDetection, suite.rejectRelease)
|
||||
.then(stopFaceDetection);
|
||||
});
|
||||
|
||||
suite.test('set-configuration-after-release', function() {
|
||||
function configure(p) {
|
||||
return suite.camera.setConfiguration(null);
|
||||
@@ -0,0 +1,165 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=940424
|
||||
-->
|
||||
<head>
|
||||
<title>Bug 940424 - Test camera hardware API failure handling</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="camera_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=940424">Mozilla Bug 940424</a>
|
||||
<video id="viewfinder" width = "200" height = "200" autoplay></video>
|
||||
<img src="#" alt="This image is going to load" id="testimage"/>
|
||||
|
||||
<script class="testbody" type="text/javascript;version=1.7">
|
||||
|
||||
var suite = new CameraTestSuite();
|
||||
|
||||
suite.test('take-picture-failures', function() {
|
||||
function startTakePictureProcessError(p) {
|
||||
suite.hw.attach({
|
||||
takePicture: function() {
|
||||
suite.hw.fireTakePictureError();
|
||||
}
|
||||
});
|
||||
return suite.camera.takePicture();
|
||||
}
|
||||
|
||||
function rejectTakePictureProcessError(e) {
|
||||
ok(e.name === 'NS_ERROR_FAILURE', 'takePicture() process should fail: ' + e);
|
||||
}
|
||||
|
||||
function startTakePictureError(p) {
|
||||
suite.hw.attach({
|
||||
takePicture: function() {
|
||||
throw SpecialPowers.Cr.NS_ERROR_FAILURE;
|
||||
}
|
||||
});
|
||||
return suite.camera.takePicture();
|
||||
}
|
||||
|
||||
function rejectTakePictureError(e) {
|
||||
ok(e.name === 'NS_ERROR_FAILURE', 'takePicture() should fail: ' + e);
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.catch(suite.rejectGetCamera)
|
||||
.then(startTakePictureProcessError)
|
||||
.then(suite.expectedRejectTakePicture, rejectTakePictureProcessError)
|
||||
.then(startTakePictureError)
|
||||
.then(suite.expectedRejectTakePicture, rejectTakePictureError)
|
||||
});
|
||||
|
||||
suite.test('shutter', function() {
|
||||
function shutter(p) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
function onShutter(e) {
|
||||
ok(true, 'received shutter event');
|
||||
suite.camera.removeEventListener('shutter', onShutter);
|
||||
resolve();
|
||||
}
|
||||
suite.camera.addEventListener('shutter', onShutter);
|
||||
suite.hw.fireShutter();
|
||||
});
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(shutter, suite.rejectGetCamera)
|
||||
});
|
||||
|
||||
suite.test('take-picture', function() {
|
||||
suite.hw.params['picture-format-values'] = 'jpeg,png';
|
||||
suite.hw.params['picture-format'] = 'jpeg';
|
||||
|
||||
var config = {
|
||||
fileFormat: 'png',
|
||||
latitude: 1.0,
|
||||
longitude: 2.0,
|
||||
altitude: 3.0,
|
||||
timestamp: 4
|
||||
};
|
||||
|
||||
var data = 'this is a test';
|
||||
|
||||
var eventPromise;
|
||||
|
||||
function takePicture(p) {
|
||||
eventPromise = new Promise(function(resolve, reject) {
|
||||
function onPicture(evt) {
|
||||
ok(true, 'got picture event');
|
||||
try {
|
||||
verifyPicture(evt.data);
|
||||
} catch(e) {
|
||||
reject(e);
|
||||
}
|
||||
suite.camera.removeEventListener('picture', onPicture);
|
||||
resolve();
|
||||
}
|
||||
suite.camera.addEventListener('picture', onPicture);
|
||||
});
|
||||
|
||||
suite.hw.attach({
|
||||
takePicture: function() {
|
||||
ok(suite.hw.params['picture-format'] === 'png', "requested format is '" + suite.hw.params['picture-format'] + "'");
|
||||
suite.hw.fireTakePictureComplete(new window.Blob([data], {'type': config.fileFormat}));
|
||||
}
|
||||
});
|
||||
|
||||
return suite.camera.takePicture(config);
|
||||
}
|
||||
|
||||
function verifyPicture(blob) {
|
||||
ok(blob.size == data.length, "picture blob is " + blob.size + " bytes");
|
||||
ok(blob.type === 'image/' + config.fileFormat, "picture blob format is '" + blob.type + "'");
|
||||
}
|
||||
|
||||
function tookPicture(p) {
|
||||
ok(true, 'got picture promise');
|
||||
verifyPicture(p);
|
||||
return eventPromise;
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(takePicture, suite.rejectGetCamera)
|
||||
.then(tookPicture, suite.rejectTakePicture);
|
||||
});
|
||||
|
||||
suite.test('take-picture-no-config', function() {
|
||||
var data = 'this is a test';
|
||||
var format = 'jpeg';
|
||||
|
||||
suite.hw.params['picture-format-values'] = 'jpeg,png';
|
||||
suite.hw.params['picture-format'] = format;
|
||||
|
||||
function takePicture(p) {
|
||||
suite.hw.attach({
|
||||
takePicture: function() {
|
||||
ok(suite.hw.params['picture-format'] === format, "requested format is '" + suite.hw.params['picture-format'] + "'");
|
||||
suite.hw.fireTakePictureComplete(new window.Blob([data], {'type': format}));
|
||||
}
|
||||
});
|
||||
|
||||
return suite.camera.takePicture();
|
||||
}
|
||||
|
||||
function verifyPicture(blob) {
|
||||
ok(blob.size == data.length, "picture blob is " + blob.size + " bytes");
|
||||
ok(blob.type === 'image/' + format, "picture blob format is '" + blob.type + "'");
|
||||
}
|
||||
|
||||
return suite.getCamera()
|
||||
.then(takePicture, suite.rejectGetCamera)
|
||||
.then(verifyPicture, suite.rejectTakePicture);
|
||||
});
|
||||
|
||||
suite.setup()
|
||||
.then(suite.run);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -7,7 +7,6 @@
|
||||
#ifndef DeviceStorage_h
|
||||
#define DeviceStorage_h
|
||||
|
||||
#include "nsIDOMDeviceStorage.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIPrincipal.h"
|
||||
#include "nsIObserver.h"
|
||||
@@ -25,6 +24,7 @@
|
||||
|
||||
class nsIInputStream;
|
||||
class nsIOutputStream;
|
||||
struct DeviceStorageFileDescriptor;
|
||||
|
||||
namespace mozilla {
|
||||
class EventListenerManager;
|
||||
@@ -159,7 +159,6 @@ class FileUpdateDispatcher final
|
||||
|
||||
class nsDOMDeviceStorage final
|
||||
: public mozilla::DOMEventTargetHelper
|
||||
, public nsIDOMDeviceStorage
|
||||
, public nsIObserver
|
||||
{
|
||||
typedef mozilla::ErrorResult ErrorResult;
|
||||
@@ -173,27 +172,12 @@ public:
|
||||
typedef nsTArray<nsString> VolumeNameArray;
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_NSIDOMDEVICESTORAGE
|
||||
|
||||
NS_DECL_NSIOBSERVER
|
||||
NS_DECL_NSIDOMEVENTTARGET
|
||||
NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
|
||||
|
||||
virtual mozilla::EventListenerManager*
|
||||
GetExistingListenerManager() const override;
|
||||
virtual mozilla::EventListenerManager*
|
||||
GetOrCreateListenerManager() override;
|
||||
|
||||
virtual void
|
||||
AddEventListener(const nsAString& aType,
|
||||
mozilla::dom::EventListener* aListener,
|
||||
bool aUseCapture,
|
||||
const mozilla::dom::Nullable<bool>& aWantsUntrusted,
|
||||
ErrorResult& aRv) override;
|
||||
|
||||
virtual void RemoveEventListener(const nsAString& aType,
|
||||
mozilla::dom::EventListener* aListener,
|
||||
bool aUseCapture,
|
||||
ErrorResult& aRv) override;
|
||||
void EventListenerWasAdded(const nsAString& aType,
|
||||
ErrorResult& aRv,
|
||||
JSCompartment* aCompartment) override;
|
||||
|
||||
explicit nsDOMDeviceStorage(nsPIDOMWindow* aWindow);
|
||||
|
||||
@@ -273,13 +257,16 @@ public:
|
||||
already_AddRefed<DOMRequest> Mount(ErrorResult& aRv);
|
||||
already_AddRefed<DOMRequest> Unmount(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<DOMRequest> CreateFileDescriptor(const nsAString& aPath,
|
||||
DeviceStorageFileDescriptor* aDSFD,
|
||||
ErrorResult& aRv);
|
||||
|
||||
bool CanBeMounted();
|
||||
bool CanBeFormatted();
|
||||
bool CanBeShared();
|
||||
bool IsRemovable();
|
||||
bool Default();
|
||||
|
||||
// Uses XPCOM GetStorageName
|
||||
void GetStorageName(nsAString& aStorageName);
|
||||
|
||||
already_AddRefed<Promise>
|
||||
GetRoot(ErrorResult& aRv);
|
||||
@@ -366,13 +353,6 @@ private:
|
||||
void DispatchStorageStatusChangeEvent(nsAString& aStorageStatus);
|
||||
#endif
|
||||
|
||||
// nsIDOMDeviceStorage.type
|
||||
enum {
|
||||
DEVICE_STORAGE_TYPE_DEFAULT = 0,
|
||||
DEVICE_STORAGE_TYPE_SHARED,
|
||||
DEVICE_STORAGE_TYPE_EXTERNAL
|
||||
};
|
||||
|
||||
nsRefPtr<DeviceStorageFileSystem> mFileSystem;
|
||||
};
|
||||
|
||||
|
||||
@@ -3360,8 +3360,7 @@ NS_IMPL_CYCLE_COLLECTION(DeviceStorageRequest,
|
||||
mDeviceStorage)
|
||||
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN(nsDOMDeviceStorage)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIDOMDeviceStorage)
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMDeviceStorage)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIObserver)
|
||||
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
||||
|
||||
@@ -3741,15 +3740,6 @@ nsDOMDeviceStorage::IsAvailable()
|
||||
return dsf->IsAvailable();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorage::Add(nsIDOMBlob *aBlob, nsIDOMDOMRequest * *_retval)
|
||||
{
|
||||
ErrorResult rv;
|
||||
nsRefPtr<DOMRequest> request = Add(static_cast<Blob*>(aBlob), rv);
|
||||
request.forget(_retval);
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsDOMDeviceStorage::Add(Blob* aBlob, ErrorResult& aRv)
|
||||
{
|
||||
@@ -3788,17 +3778,6 @@ nsDOMDeviceStorage::Add(Blob* aBlob, ErrorResult& aRv)
|
||||
return AddNamed(aBlob, NS_ConvertASCIItoUTF16(path), aRv);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorage::AddNamed(nsIDOMBlob *aBlob,
|
||||
const nsAString & aPath,
|
||||
nsIDOMDOMRequest * *_retval)
|
||||
{
|
||||
ErrorResult rv;
|
||||
nsRefPtr<DOMRequest> request = AddNamed(static_cast<Blob*>(aBlob), aPath, rv);
|
||||
request.forget(_retval);
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsDOMDeviceStorage::AddNamed(Blob* aBlob, const nsAString& aPath,
|
||||
ErrorResult& aRv)
|
||||
@@ -3886,25 +3865,6 @@ nsDOMDeviceStorage::AddOrAppendNamed(Blob* aBlob, const nsAString& aPath,
|
||||
return request.forget();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorage::Get(const nsAString& aPath, nsIDOMDOMRequest** aRetval)
|
||||
{
|
||||
ErrorResult rv;
|
||||
nsRefPtr<DOMRequest> request = Get(aPath, rv);
|
||||
request.forget(aRetval);
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorage::GetEditable(const nsAString& aPath,
|
||||
nsIDOMDOMRequest** aRetval)
|
||||
{
|
||||
ErrorResult rv;
|
||||
nsRefPtr<DOMRequest> request = GetEditable(aPath, rv);
|
||||
request.forget(aRetval);
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsDOMDeviceStorage::GetInternal(const nsAString& aPath, bool aEditable,
|
||||
ErrorResult& aRv)
|
||||
@@ -3963,15 +3923,6 @@ nsDOMDeviceStorage::GetInternal(nsPIDOMWindow *aWin,
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorage::Delete(const nsAString& aPath, nsIDOMDOMRequest** aRetval)
|
||||
{
|
||||
ErrorResult rv;
|
||||
nsRefPtr<DOMRequest> request = Delete(aPath, rv);
|
||||
request.forget(aRetval);
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsDOMDeviceStorage::Delete(const nsAString& aPath, ErrorResult& aRv)
|
||||
{
|
||||
@@ -4025,15 +3976,6 @@ nsDOMDeviceStorage::DeleteInternal(nsPIDOMWindow *aWin,
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorage::FreeSpace(nsIDOMDOMRequest** aRetval)
|
||||
{
|
||||
ErrorResult rv;
|
||||
nsRefPtr<DOMRequest> request = FreeSpace(rv);
|
||||
request.forget(aRetval);
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsDOMDeviceStorage::FreeSpace(ErrorResult& aRv)
|
||||
{
|
||||
@@ -4059,15 +4001,6 @@ nsDOMDeviceStorage::FreeSpace(ErrorResult& aRv)
|
||||
return request.forget();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorage::UsedSpace(nsIDOMDOMRequest** aRetval)
|
||||
{
|
||||
ErrorResult rv;
|
||||
nsRefPtr<DOMRequest> request = UsedSpace(rv);
|
||||
request.forget(aRetval);
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsDOMDeviceStorage::UsedSpace(ErrorResult& aRv)
|
||||
{
|
||||
@@ -4097,15 +4030,6 @@ nsDOMDeviceStorage::UsedSpace(ErrorResult& aRv)
|
||||
return request.forget();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorage::Available(nsIDOMDOMRequest** aRetval)
|
||||
{
|
||||
ErrorResult rv;
|
||||
nsRefPtr<DOMRequest> request = Available(rv);
|
||||
request.forget(aRetval);
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsDOMDeviceStorage::Available(ErrorResult& aRv)
|
||||
{
|
||||
@@ -4231,27 +4155,28 @@ nsDOMDeviceStorage::Unmount(ErrorResult& aRv)
|
||||
return request.forget();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
already_AddRefed<DOMRequest>
|
||||
nsDOMDeviceStorage::CreateFileDescriptor(const nsAString& aPath,
|
||||
DeviceStorageFileDescriptor* aDSFileDescriptor,
|
||||
nsIDOMDOMRequest** aRequest)
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aDSFileDescriptor);
|
||||
|
||||
nsCOMPtr<nsPIDOMWindow> win = GetOwner();
|
||||
if (!win) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DeviceStorageTypeChecker* typeChecker
|
||||
= DeviceStorageTypeChecker::CreateOrGet();
|
||||
if (!typeChecker) {
|
||||
return NS_ERROR_FAILURE;
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> r;
|
||||
nsresult rv;
|
||||
|
||||
if (IsFullPath(aPath)) {
|
||||
nsString storagePath;
|
||||
@@ -4259,14 +4184,13 @@ nsDOMDeviceStorage::CreateFileDescriptor(const nsAString& aPath,
|
||||
if (!ds) {
|
||||
nsRefPtr<DOMRequest> request = new DOMRequest(win);
|
||||
r = new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN);
|
||||
rv = NS_DispatchToCurrentThread(r);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
aRv = NS_DispatchToCurrentThread(r);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
request.forget(aRequest);
|
||||
return NS_OK;
|
||||
return request.forget();
|
||||
}
|
||||
return ds->CreateFileDescriptor(storagePath, aDSFileDescriptor, aRequest);
|
||||
return ds->CreateFileDescriptor(storagePath, aDSFileDescriptor, aRv);
|
||||
}
|
||||
|
||||
nsRefPtr<DOMRequest> request = new DOMRequest(win);
|
||||
@@ -4284,12 +4208,11 @@ nsDOMDeviceStorage::CreateFileDescriptor(const nsAString& aPath,
|
||||
aDSFileDescriptor);
|
||||
}
|
||||
|
||||
rv = NS_DispatchToCurrentThread(r);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
aRv = NS_DispatchToCurrentThread(r);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
request.forget(aRequest);
|
||||
return NS_OK;
|
||||
return request.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -4336,18 +4259,10 @@ nsDOMDeviceStorage::GetRoot(ErrorResult& aRv)
|
||||
return mozilla::dom::Directory::GetRoot(mFileSystem, aRv);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorage::GetDefault(bool* aDefault)
|
||||
{
|
||||
*aDefault = Default();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
void
|
||||
nsDOMDeviceStorage::GetStorageName(nsAString& aStorageName)
|
||||
{
|
||||
aStorageName = mStorageName;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
already_AddRefed<DOMCursor>
|
||||
@@ -4550,46 +4465,19 @@ nsDOMDeviceStorage::Notify(const char* aReason, DeviceStorageFile* aFile)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorage::AddEventListener(const nsAString & aType,
|
||||
nsIDOMEventListener *aListener,
|
||||
bool aUseCapture,
|
||||
bool aWantsUntrusted,
|
||||
uint8_t aArgc)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsCOMPtr<nsPIDOMWindow> win = GetOwner();
|
||||
if (!win) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
nsRefPtr<DOMRequest> request = new DOMRequest(win);
|
||||
nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
|
||||
mStorageName);
|
||||
nsCOMPtr<nsIRunnable> r
|
||||
= new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_WATCH,
|
||||
win, mPrincipal, dsf, request, this);
|
||||
nsresult rv = NS_DispatchToCurrentThread(r);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return DOMEventTargetHelper::AddEventListener(aType, aListener, aUseCapture,
|
||||
aWantsUntrusted, aArgc);
|
||||
}
|
||||
|
||||
void
|
||||
nsDOMDeviceStorage::AddEventListener(const nsAString & aType,
|
||||
EventListener *aListener,
|
||||
bool aUseCapture,
|
||||
const Nullable<bool>& aWantsUntrusted,
|
||||
ErrorResult& aRv)
|
||||
nsDOMDeviceStorage::EventListenerWasAdded(const nsAString& aType,
|
||||
ErrorResult& aRv,
|
||||
JSCompartment* aCompartment)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!aType.EqualsLiteral("change")) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsPIDOMWindow> win = GetOwner();
|
||||
if (!win) {
|
||||
if (NS_WARN_IF(!win)) {
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return;
|
||||
}
|
||||
@@ -4602,138 +4490,6 @@ nsDOMDeviceStorage::AddEventListener(const nsAString & aType,
|
||||
win, mPrincipal, dsf, request, this);
|
||||
nsresult rv = NS_DispatchToCurrentThread(r);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
}
|
||||
DOMEventTargetHelper::AddEventListener(aType, aListener, aUseCapture,
|
||||
aWantsUntrusted, aRv);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorage::AddSystemEventListener(const nsAString & aType,
|
||||
nsIDOMEventListener *aListener,
|
||||
bool aUseCapture,
|
||||
bool aWantsUntrusted,
|
||||
uint8_t aArgc)
|
||||
{
|
||||
if (!mIsWatchingFile) {
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
obs->AddObserver(this, kFileWatcherUpdate, false);
|
||||
mIsWatchingFile = true;
|
||||
}
|
||||
|
||||
return nsDOMDeviceStorage::AddEventListener(aType, aListener, aUseCapture,
|
||||
aWantsUntrusted, aArgc);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorage::RemoveEventListener(const nsAString & aType,
|
||||
nsIDOMEventListener *aListener,
|
||||
bool aUseCapture)
|
||||
{
|
||||
DOMEventTargetHelper::RemoveEventListener(aType, aListener, false);
|
||||
|
||||
if (mIsWatchingFile && !HasListenersFor(nsGkAtoms::onchange)) {
|
||||
mIsWatchingFile = false;
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
obs->RemoveObserver(this, kFileWatcherUpdate);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsDOMDeviceStorage::RemoveEventListener(const nsAString& aType,
|
||||
EventListener* aListener,
|
||||
bool aCapture,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
DOMEventTargetHelper::RemoveEventListener(aType, aListener, aCapture, aRv);
|
||||
|
||||
if (mIsWatchingFile && !HasListenersFor(nsGkAtoms::onchange)) {
|
||||
mIsWatchingFile = false;
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
obs->RemoveObserver(this, kFileWatcherUpdate);
|
||||
aRv.Throw(rv);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorage::RemoveSystemEventListener(const nsAString & aType,
|
||||
nsIDOMEventListener *aListener,
|
||||
bool aUseCapture)
|
||||
{
|
||||
return nsDOMDeviceStorage::RemoveEventListener(aType, aListener, aUseCapture);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMDeviceStorage::DispatchEvent(nsIDOMEvent *aEvt,
|
||||
bool *aRetval)
|
||||
{
|
||||
return DOMEventTargetHelper::DispatchEvent(aEvt, aRetval);
|
||||
}
|
||||
|
||||
EventTarget*
|
||||
nsDOMDeviceStorage::GetTargetForDOMEvent()
|
||||
{
|
||||
return DOMEventTargetHelper::GetTargetForDOMEvent();
|
||||
}
|
||||
|
||||
EventTarget *
|
||||
nsDOMDeviceStorage::GetTargetForEventTargetChain()
|
||||
{
|
||||
return DOMEventTargetHelper::GetTargetForEventTargetChain();
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDOMDeviceStorage::PreHandleEvent(EventChainPreVisitor& aVisitor)
|
||||
{
|
||||
return DOMEventTargetHelper::PreHandleEvent(aVisitor);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDOMDeviceStorage::WillHandleEvent(EventChainPostVisitor& aVisitor)
|
||||
{
|
||||
return DOMEventTargetHelper::WillHandleEvent(aVisitor);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDOMDeviceStorage::PostHandleEvent(EventChainPostVisitor& aVisitor)
|
||||
{
|
||||
return DOMEventTargetHelper::PostHandleEvent(aVisitor);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDOMDeviceStorage::DispatchDOMEvent(WidgetEvent* aEvent,
|
||||
nsIDOMEvent* aDOMEvent,
|
||||
nsPresContext* aPresContext,
|
||||
nsEventStatus* aEventStatus)
|
||||
{
|
||||
return DOMEventTargetHelper::DispatchDOMEvent(aEvent,
|
||||
aDOMEvent,
|
||||
aPresContext,
|
||||
aEventStatus);
|
||||
}
|
||||
|
||||
EventListenerManager*
|
||||
nsDOMDeviceStorage::GetOrCreateListenerManager()
|
||||
{
|
||||
return DOMEventTargetHelper::GetOrCreateListenerManager();
|
||||
}
|
||||
|
||||
EventListenerManager*
|
||||
nsDOMDeviceStorage::GetExistingListenerManager() const
|
||||
{
|
||||
return DOMEventTargetHelper::GetExistingListenerManager();
|
||||
}
|
||||
|
||||
nsIScriptContext *
|
||||
nsDOMDeviceStorage::GetContextForEventHandlers(nsresult *aRv)
|
||||
{
|
||||
return DOMEventTargetHelper::GetContextForEventHandlers(aRv);
|
||||
}
|
||||
|
||||
JSContext *
|
||||
nsDOMDeviceStorage::GetJSContextForEventHandlers()
|
||||
{
|
||||
return DOMEventTargetHelper::GetJSContextForEventHandlers();
|
||||
}
|
||||
|
||||
NS_IMPL_EVENT_HANDLER(nsDOMDeviceStorage, change)
|
||||
|
||||
@@ -503,7 +503,7 @@ FMRadio::EnableAudioChannelAgent()
|
||||
NS_IMETHODIMP
|
||||
FMRadio::CanPlayChanged(int32_t aCanPlay)
|
||||
{
|
||||
SetCanPlay(aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
|
||||
SetCanPlay(!(aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_MUTED));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -1062,6 +1062,11 @@ static bool UseAudioChannelService()
|
||||
return Preferences::GetBool("media.useAudioChannelService");
|
||||
}
|
||||
|
||||
static bool UseAudioChannelAPI()
|
||||
{
|
||||
return Preferences::GetBool("media.useAudioChannelAPI");
|
||||
}
|
||||
|
||||
void HTMLMediaElement::UpdatePreloadAction()
|
||||
{
|
||||
PreloadAction nextAction = PRELOAD_UNDEFINED;
|
||||
@@ -4448,11 +4453,15 @@ nsresult HTMLMediaElement::UpdateChannelMuteState(AudioChannelState aCanPlay)
|
||||
// We have to mute this channel.
|
||||
if (aCanPlay == AUDIO_CHANNEL_STATE_MUTED && !(mMuted & MUTED_BY_AUDIO_CHANNEL)) {
|
||||
SetMutedInternal(mMuted | MUTED_BY_AUDIO_CHANNEL);
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptbegin"));
|
||||
if (UseAudioChannelAPI()) {
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptbegin"));
|
||||
}
|
||||
} else if (aCanPlay != AUDIO_CHANNEL_STATE_MUTED &&
|
||||
(mMuted & MUTED_BY_AUDIO_CHANNEL)) {
|
||||
SetMutedInternal(mMuted & ~MUTED_BY_AUDIO_CHANNEL);
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptend"));
|
||||
if (UseAudioChannelAPI()) {
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptend"));
|
||||
}
|
||||
}
|
||||
|
||||
SuspendOrResumeElement(mMuted & MUTED_BY_AUDIO_CHANNEL, false);
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIDOMDeviceStorage.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'dom_devicestorage'
|
||||
@@ -1,46 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "domstubs.idl"
|
||||
#include "nsIDOMEventTarget.idl"
|
||||
interface nsIDOMBlob;
|
||||
interface nsIDOMDOMRequest;
|
||||
interface nsIDOMDOMCursor;
|
||||
interface nsIDOMDeviceStorageChangeEvent;
|
||||
interface nsIDOMEventListener;
|
||||
interface nsIFile;
|
||||
|
||||
%{C++
|
||||
struct DeviceStorageFileDescriptor;
|
||||
%}
|
||||
[ptr] native DeviceStorageFdPtr(DeviceStorageFileDescriptor);
|
||||
|
||||
[uuid(25e4e387-1974-4f77-83b5-e6f3cf1beae8), builtinclass]
|
||||
interface nsIDOMDeviceStorage : nsIDOMEventTarget
|
||||
{
|
||||
[implicit_jscontext] attribute jsval onchange;
|
||||
nsIDOMDOMRequest add(in nsIDOMBlob aBlob);
|
||||
nsIDOMDOMRequest addNamed(in nsIDOMBlob aBlob, in DOMString aName);
|
||||
|
||||
nsIDOMDOMRequest get([Null(Stringify)] in DOMString aName);
|
||||
nsIDOMDOMRequest getEditable([Null(Stringify)] in DOMString aName);
|
||||
nsIDOMDOMRequest delete([Null(Stringify)] in DOMString aName);
|
||||
|
||||
nsIDOMDOMRequest freeSpace();
|
||||
nsIDOMDOMRequest usedSpace();
|
||||
nsIDOMDOMRequest available();
|
||||
|
||||
// Note that the storageName is just a name (like sdcard), and doesn't
|
||||
// include any path information.
|
||||
readonly attribute DOMString storageName;
|
||||
|
||||
// Determines if this storage area is the one which will be used by default
|
||||
// for storing new files.
|
||||
readonly attribute bool default;
|
||||
|
||||
// Note: aFileDescriptor is reference counted, which is why we're using
|
||||
// a pointer rather than a reference.
|
||||
[noscript] nsIDOMDOMRequest createFileDescriptor(in DOMString aName,
|
||||
in DeviceStorageFdPtr aFileDescriptor);
|
||||
};
|
||||
@@ -293,6 +293,11 @@ static bool UseAudioChannelService()
|
||||
return Preferences::GetBool("media.useAudioChannelService");
|
||||
}
|
||||
|
||||
static bool UseAudioChannelAPI()
|
||||
{
|
||||
return Preferences::GetBool("media.useAudioChannelAPI");
|
||||
}
|
||||
|
||||
class EventProxyHandler final : public nsIDOMEventListener
|
||||
{
|
||||
public:
|
||||
@@ -543,9 +548,11 @@ AudioDestinationNode::CanPlayChanged(int32_t aCanPlay)
|
||||
mAudioChannelAgentPlaying = playing;
|
||||
SetCanPlay(playing);
|
||||
|
||||
Context()->DispatchTrustedEvent(
|
||||
playing ? NS_LITERAL_STRING("mozinterruptend")
|
||||
: NS_LITERAL_STRING("mozinterruptbegin"));
|
||||
if (UseAudioChannelAPI()) {
|
||||
Context()->DispatchTrustedEvent(
|
||||
playing ? NS_LITERAL_STRING("mozinterruptend")
|
||||
: NS_LITERAL_STRING("mozinterruptbegin"));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ function test() {
|
||||
"dom/media/webaudio/test/browser_mozAudioChannel.html";
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.defaultAudioChannel", "content" ],
|
||||
["media.useAudioChannelAPI", true ],
|
||||
["media.useAudioChannelService", true ]]},
|
||||
function() {
|
||||
let tab1 = gBrowser.addTab(testURL);
|
||||
|
||||
@@ -35,6 +35,7 @@ function test() {
|
||||
"dom/media/webaudio/test/browser_mozAudioChannel_muted.html";
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.defaultAudioChannel", "content" ],
|
||||
["media.useAudioChannelAPI", true ],
|
||||
["media.useAudioChannelService", true ]]},
|
||||
function() {
|
||||
let tab1 = gBrowser.addTab(testURL);
|
||||
|
||||
@@ -141,7 +141,8 @@ function runTest() {
|
||||
test();
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.useAudioChannelService", true ]]}, runTest);
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.useAudioChannelAPI", true ],
|
||||
["media.useAudioChannelService", true ]]}, runTest);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestLongerTimeout(5);
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ interfaces = [
|
||||
'core',
|
||||
'html',
|
||||
'events',
|
||||
'devicestorage',
|
||||
'settings',
|
||||
'stylesheets',
|
||||
'sidebar',
|
||||
|
||||
@@ -95,16 +95,16 @@ interface AudioContext : EventTarget {
|
||||
// Mozilla extensions
|
||||
partial interface AudioContext {
|
||||
// Read AudioChannel.webidl for more information about this attribute.
|
||||
[Pref="media.useAudioChannelService"]
|
||||
[Pref="media.useAudioChannelAPI"]
|
||||
readonly attribute AudioChannel mozAudioChannelType;
|
||||
|
||||
// These 2 events are dispatched when the AudioContext object is muted by
|
||||
// the AudioChannelService. It's call 'interrupt' because when this event is
|
||||
// dispatched on a HTMLMediaElement, the audio stream is paused.
|
||||
[Pref="media.useAudioChannelService"]
|
||||
[Pref="media.useAudioChannelAPI"]
|
||||
attribute EventHandler onmozinterruptbegin;
|
||||
|
||||
[Pref="media.useAudioChannelService"]
|
||||
[Pref="media.useAudioChannelAPI"]
|
||||
attribute EventHandler onmozinterruptend;
|
||||
|
||||
// This method is for test only.
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
if (aTopic == "console-api-log-event") {
|
||||
var obj = aSubject.wrappedJSObject;
|
||||
is (obj.arguments[0], "Hello world from a SharedWorker!", "A message from a SharedWorker \\o/");
|
||||
is (aData, "SharedWorker", "The ID is SharedWorker");
|
||||
is (obj.ID, "http://mochi.test:8888/tests/dom/workers/test/sharedWorker_console.js", "The ID is SharedWorker");
|
||||
is (obj.innerID, "SharedWorker", "The ID is SharedWorker");
|
||||
is (order++, 1, "Then a log message.");
|
||||
|
||||
SpecialPowers.removeObserver(this, "console-api-log-event");
|
||||
|
||||
@@ -165,6 +165,7 @@ function TabTarget(tab) {
|
||||
this._handleThreadState = this._handleThreadState.bind(this);
|
||||
this.on("thread-resumed", this._handleThreadState);
|
||||
this.on("thread-paused", this._handleThreadState);
|
||||
this.activeTab = this.activeConsole = null;
|
||||
// Only real tabs need initialization here. Placeholder objects for remote
|
||||
// targets will be initialized after a makeRemote method call.
|
||||
if (tab && !["client", "form", "chrome"].every(tab.hasOwnProperty, tab)) {
|
||||
@@ -413,6 +414,19 @@ TabTarget.prototype = {
|
||||
}
|
||||
this.activeTab = aTabClient;
|
||||
this.threadActor = aResponse.threadActor;
|
||||
attachConsole();
|
||||
});
|
||||
};
|
||||
|
||||
let attachConsole = () => {
|
||||
this._client.attachConsole(this._form.consoleActor,
|
||||
[ "NetworkActivity" ],
|
||||
(aResponse, aWebConsoleClient) => {
|
||||
if (!aWebConsoleClient) {
|
||||
this._remote.reject("Unable to attach to the console");
|
||||
return;
|
||||
}
|
||||
this.activeConsole = aWebConsoleClient;
|
||||
this._remote.resolve(null);
|
||||
});
|
||||
};
|
||||
@@ -449,7 +463,7 @@ TabTarget.prototype = {
|
||||
} else {
|
||||
// AddonActor and chrome debugging on RootActor doesn't inherits from TabActor and
|
||||
// doesn't need to be attached.
|
||||
this._remote.resolve(null);
|
||||
attachConsole();
|
||||
}
|
||||
|
||||
return this._remote.promise;
|
||||
@@ -603,17 +617,15 @@ TabTarget.prototype = {
|
||||
// We started with a local tab and created the client ourselves, so we
|
||||
// should close it.
|
||||
this._client.close(cleanupAndResolve);
|
||||
} else {
|
||||
} else if (this.activeTab) {
|
||||
// The client was handed to us, so we are not responsible for closing
|
||||
// it. We just need to detach from the tab, if already attached.
|
||||
if (this.activeTab) {
|
||||
// |detach| may fail if the connection is already dead, so proceed
|
||||
// cleanup directly after this.
|
||||
this.activeTab.detach();
|
||||
cleanupAndResolve();
|
||||
} else {
|
||||
cleanupAndResolve();
|
||||
}
|
||||
// |detach| may fail if the connection is already dead, so proceed with
|
||||
// cleanup directly after this.
|
||||
this.activeTab.detach();
|
||||
cleanupAndResolve();
|
||||
} else {
|
||||
cleanupAndResolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,6 +642,7 @@ TabTarget.prototype = {
|
||||
promiseTargets.delete(this._form);
|
||||
}
|
||||
this.activeTab = null;
|
||||
this.activeConsole = null;
|
||||
this._client = null;
|
||||
this._tab = null;
|
||||
this._form = null;
|
||||
|
||||
@@ -109,6 +109,9 @@ function Toolbox(target, selectedTool, hostType, hostOptions) {
|
||||
this._toolPanels = new Map();
|
||||
this._telemetry = new Telemetry();
|
||||
|
||||
this._initInspector = null;
|
||||
this._inspector = null;
|
||||
|
||||
this._toolRegistered = this._toolRegistered.bind(this);
|
||||
this._toolUnregistered = this._toolUnregistered.bind(this);
|
||||
this._refreshHostTitle = this._refreshHostTitle.bind(this);
|
||||
@@ -366,7 +369,7 @@ Toolbox.prototype = {
|
||||
|
||||
this._pingTelemetry();
|
||||
|
||||
let panel = yield this.selectTool(this._defaultToolId);
|
||||
yield this.selectTool(this._defaultToolId);
|
||||
|
||||
// Wait until the original tool is selected so that the split
|
||||
// console input will receive focus.
|
||||
|
||||
@@ -196,7 +196,9 @@ let NetMonitorController = {
|
||||
|
||||
/**
|
||||
* Initiates remote or chrome network monitoring based on the current target,
|
||||
* wiring event handlers as necessary.
|
||||
* wiring event handlers as necessary. Since the TabTarget will have already
|
||||
* started listening to network requests by now, this is largely
|
||||
* netmonitor-specific initialization.
|
||||
*
|
||||
* @return object
|
||||
* A promise that is resolved when the monitor finishes connecting.
|
||||
@@ -209,15 +211,18 @@ let NetMonitorController = {
|
||||
let deferred = promise.defer();
|
||||
this._connection = deferred.promise;
|
||||
|
||||
let target = this._target;
|
||||
let { client, form } = target;
|
||||
this.client = this._target.client;
|
||||
// Some actors like AddonActor or RootActor for chrome debugging
|
||||
// do not support attach/detach and can be used directly
|
||||
if (!target.isTabActor) {
|
||||
this._startChromeMonitoring(client, form.consoleActor, deferred.resolve);
|
||||
} else {
|
||||
this._startMonitoringTab(client, form, deferred.resolve);
|
||||
// aren't actual tabs.
|
||||
if (this._target.isTabActor) {
|
||||
this.tabClient = this._target.activeTab;
|
||||
}
|
||||
this.webConsoleClient = this._target.activeConsole;
|
||||
this.webConsoleClient.setPreferences(NET_PREFS, () => {
|
||||
this.TargetEventsHandler.connect();
|
||||
this.NetworkEventsHandler.connect();
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
yield deferred.promise;
|
||||
window.emit(EVENTS.CONNECTED);
|
||||
@@ -243,82 +248,6 @@ let NetMonitorController = {
|
||||
return !!this.client;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up a monitoring session.
|
||||
*
|
||||
* @param DebuggerClient aClient
|
||||
* The debugger client.
|
||||
* @param object aTabGrip
|
||||
* The remote protocol grip of the tab.
|
||||
* @param function aCallback
|
||||
* A function to invoke once the client attached to the console client.
|
||||
*/
|
||||
_startMonitoringTab: function(aClient, aTabGrip, aCallback) {
|
||||
if (!aClient) {
|
||||
Cu.reportError("No client found!");
|
||||
return;
|
||||
}
|
||||
this.client = aClient;
|
||||
|
||||
aClient.attachTab(aTabGrip.actor, (aResponse, aTabClient) => {
|
||||
if (!aTabClient) {
|
||||
Cu.reportError("No tab client found!");
|
||||
return;
|
||||
}
|
||||
this.tabClient = aTabClient;
|
||||
|
||||
aClient.attachConsole(aTabGrip.consoleActor, LISTENERS, (aResponse, aWebConsoleClient) => {
|
||||
if (!aWebConsoleClient) {
|
||||
Cu.reportError("Couldn't attach to console: " + aResponse.error);
|
||||
return;
|
||||
}
|
||||
this.webConsoleClient = aWebConsoleClient;
|
||||
this.webConsoleClient.setPreferences(NET_PREFS, () => {
|
||||
this.TargetEventsHandler.connect();
|
||||
this.NetworkEventsHandler.connect();
|
||||
|
||||
if (aCallback) {
|
||||
aCallback();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up a chrome monitoring session.
|
||||
*
|
||||
* @param DebuggerClient aClient
|
||||
* The debugger client.
|
||||
* @param object aConsoleActor
|
||||
* The remote protocol grip of the chrome debugger.
|
||||
* @param function aCallback
|
||||
* A function to invoke once the client attached to the console client.
|
||||
*/
|
||||
_startChromeMonitoring: function(aClient, aConsoleActor, aCallback) {
|
||||
if (!aClient) {
|
||||
Cu.reportError("No client found!");
|
||||
return;
|
||||
}
|
||||
this.client = aClient;
|
||||
|
||||
aClient.attachConsole(aConsoleActor, LISTENERS, (aResponse, aWebConsoleClient) => {
|
||||
if (!aWebConsoleClient) {
|
||||
Cu.reportError("Couldn't attach to console: " + aResponse.error);
|
||||
return;
|
||||
}
|
||||
this.webConsoleClient = aWebConsoleClient;
|
||||
this.webConsoleClient.setPreferences(NET_PREFS, () => {
|
||||
this.TargetEventsHandler.connect();
|
||||
this.NetworkEventsHandler.connect();
|
||||
|
||||
if (aCallback) {
|
||||
aCallback();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the activity currently performed by the frontend.
|
||||
* @return number
|
||||
@@ -440,7 +369,6 @@ function TargetEventsHandler() {
|
||||
|
||||
TargetEventsHandler.prototype = {
|
||||
get target() NetMonitorController._target,
|
||||
get webConsoleClient() NetMonitorController.webConsoleClient,
|
||||
|
||||
/**
|
||||
* Listen for events emitted by the current tab target.
|
||||
@@ -528,8 +456,28 @@ NetworkEventsHandler.prototype = {
|
||||
*/
|
||||
connect: function() {
|
||||
dumpn("NetworkEventsHandler is connecting...");
|
||||
this.client.addListener("networkEvent", this._onNetworkEvent);
|
||||
this.client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
this.webConsoleClient.on("networkEvent", this._onNetworkEvent);
|
||||
this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
this._displayCachedEvents();
|
||||
},
|
||||
|
||||
/**
|
||||
* Display any network events already in the cache.
|
||||
*/
|
||||
_displayCachedEvents: function() {
|
||||
for (let cachedEvent of this.webConsoleClient.getNetworkEvents()) {
|
||||
// First add the request to the timeline.
|
||||
this._onNetworkEvent("networkEvent", cachedEvent);
|
||||
// Then replay any updates already received.
|
||||
for (let update of cachedEvent.updates) {
|
||||
this._onNetworkEventUpdate("networkEventUpdate", {
|
||||
packet: {
|
||||
updateType: update
|
||||
},
|
||||
networkInfo: cachedEvent
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -540,25 +488,21 @@ NetworkEventsHandler.prototype = {
|
||||
return;
|
||||
}
|
||||
dumpn("NetworkEventsHandler is disconnecting...");
|
||||
this.client.removeListener("networkEvent", this._onNetworkEvent);
|
||||
this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
this.webConsoleClient.off("networkEvent", this._onNetworkEvent);
|
||||
this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
},
|
||||
|
||||
/**
|
||||
* The "networkEvent" message type handler.
|
||||
*
|
||||
* @param string aType
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object aPacket
|
||||
* The message received from the server.
|
||||
* @param object networkInfo
|
||||
* The network request information.
|
||||
*/
|
||||
_onNetworkEvent: function(aType, aPacket) {
|
||||
if (aPacket.from != this.webConsoleClient.actor) {
|
||||
// Skip events from different console actors.
|
||||
return;
|
||||
}
|
||||
_onNetworkEvent: function(type, networkInfo) {
|
||||
let { actor, startedDateTime, request: { method, url }, isXHR, fromCache } = networkInfo;
|
||||
|
||||
let { actor, startedDateTime, method, url, isXHR, fromCache } = aPacket.eventActor;
|
||||
NetMonitorView.RequestsMenu.addRequest(
|
||||
actor, startedDateTime, method, url, isXHR, fromCache
|
||||
);
|
||||
@@ -568,19 +512,17 @@ NetworkEventsHandler.prototype = {
|
||||
/**
|
||||
* The "networkEventUpdate" message type handler.
|
||||
*
|
||||
* @param string aType
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object aPacket
|
||||
* @param object packet
|
||||
* The message received from the server.
|
||||
* @param object networkInfo
|
||||
* The network request information.
|
||||
*/
|
||||
_onNetworkEventUpdate: function(aType, aPacket) {
|
||||
let actor = aPacket.from;
|
||||
if (!NetMonitorView.RequestsMenu.getItemByValue(actor)) {
|
||||
// Skip events from unknown actors.
|
||||
return;
|
||||
}
|
||||
_onNetworkEventUpdate: function(type, { packet, networkInfo }) {
|
||||
let actor = networkInfo.actor;
|
||||
|
||||
switch (aPacket.updateType) {
|
||||
switch (packet.updateType) {
|
||||
case "requestHeaders":
|
||||
this.webConsoleClient.getRequestHeaders(actor, this._onRequestHeaders);
|
||||
window.emit(EVENTS.UPDATING_REQUEST_HEADERS, actor);
|
||||
@@ -594,8 +536,8 @@ NetworkEventsHandler.prototype = {
|
||||
window.emit(EVENTS.UPDATING_REQUEST_POST_DATA, actor);
|
||||
break;
|
||||
case "securityInfo":
|
||||
NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
|
||||
securityState: aPacket.state,
|
||||
NetMonitorView.RequestsMenu.updateRequest(actor, {
|
||||
securityState: networkInfo.securityInfo,
|
||||
});
|
||||
this.webConsoleClient.getSecurityInfo(actor, this._onSecurityInfo);
|
||||
window.emit(EVENTS.UPDATING_SECURITY_INFO, actor);
|
||||
@@ -609,28 +551,28 @@ NetworkEventsHandler.prototype = {
|
||||
window.emit(EVENTS.UPDATING_RESPONSE_COOKIES, actor);
|
||||
break;
|
||||
case "responseStart":
|
||||
NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
|
||||
httpVersion: aPacket.response.httpVersion,
|
||||
remoteAddress: aPacket.response.remoteAddress,
|
||||
remotePort: aPacket.response.remotePort,
|
||||
status: aPacket.response.status,
|
||||
statusText: aPacket.response.statusText,
|
||||
headersSize: aPacket.response.headersSize
|
||||
NetMonitorView.RequestsMenu.updateRequest(actor, {
|
||||
httpVersion: networkInfo.response.httpVersion,
|
||||
remoteAddress: networkInfo.response.remoteAddress,
|
||||
remotePort: networkInfo.response.remotePort,
|
||||
status: networkInfo.response.status,
|
||||
statusText: networkInfo.response.statusText,
|
||||
headersSize: networkInfo.response.headersSize
|
||||
});
|
||||
window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
|
||||
break;
|
||||
case "responseContent":
|
||||
NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
|
||||
contentSize: aPacket.contentSize,
|
||||
transferredSize: aPacket.transferredSize,
|
||||
mimeType: aPacket.mimeType
|
||||
NetMonitorView.RequestsMenu.updateRequest(actor, {
|
||||
contentSize: networkInfo.response.bodySize,
|
||||
transferredSize: networkInfo.response.transferredSize,
|
||||
mimeType: networkInfo.response.content.mimeType
|
||||
});
|
||||
this.webConsoleClient.getResponseContent(actor, this._onResponseContent);
|
||||
window.emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor);
|
||||
break;
|
||||
case "eventTimings":
|
||||
NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
|
||||
totalTime: aPacket.totalTime
|
||||
NetMonitorView.RequestsMenu.updateRequest(actor, {
|
||||
totalTime: networkInfo.totalTime
|
||||
});
|
||||
this.webConsoleClient.getEventTimings(actor, this._onEventTimings);
|
||||
window.emit(EVENTS.UPDATING_EVENT_TIMINGS, actor);
|
||||
|
||||
@@ -15,9 +15,6 @@ function test() {
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
reqMenu = RequestsMenu;
|
||||
|
||||
is(reqMenu.itemCount, 0,
|
||||
"The request menu should empty before reloading");
|
||||
|
||||
let button = document.querySelector("#requests-menu-reload-notice-button");
|
||||
button.click();
|
||||
})
|
||||
|
||||
@@ -149,6 +149,11 @@ function initNetMonitor(aUrl, aWindow, aEnableCache) {
|
||||
if(!aEnableCache) {
|
||||
yield toggleCache(target, true);
|
||||
info("Cache disabled when the current and all future toolboxes are open.");
|
||||
// Remove any requests generated by the reload while toggling the cache to
|
||||
// avoid interfering with the test.
|
||||
isnot([...target.activeConsole.getNetworkEvents()].length, 0,
|
||||
"Request to reconfigure the tab was recorded.");
|
||||
target.activeConsole.clearNetworkRequests();
|
||||
}
|
||||
|
||||
let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
|
||||
|
||||
@@ -104,6 +104,21 @@ const CATEGORY_MAPPINGS = {
|
||||
* for `.marker-details-bullet.{COLORNAME}` for the equivilent
|
||||
* entry in ./browser/themes/shared/devtools/performance.inc.css
|
||||
* https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors
|
||||
* - collapseFunc: A function determining how markers are collapsed together.
|
||||
* Invoked with 3 arguments: the current parent marker, the
|
||||
* current marker and a method for peeking i markers ahead. If
|
||||
* nothing is returned, the marker is added as a standalone entry
|
||||
* in the waterfall. Otherwise, an object needs to be returned
|
||||
* with the following properties:
|
||||
* - toParent: The parent marker name (needs to be an entry in
|
||||
* the `TIMELINE_BLUEPRINT` itself).
|
||||
* - withData: An object containing some properties to staple
|
||||
* on the parent marker.
|
||||
* - forceNew: True if a new parent marker needs to be created
|
||||
* even though there is one currently available
|
||||
* with the same name.
|
||||
* - forceEnd: True if the current parent marker is full after
|
||||
* this collapse operation and should be finalized.
|
||||
* - fields: An optional array of marker properties you wish to display in the
|
||||
* marker details view. For example, a field in the array such as
|
||||
* { property: "aCauseName", label: "Cause" } would render a string
|
||||
@@ -127,51 +142,64 @@ const TIMELINE_BLUEPRINT = {
|
||||
"Styles": {
|
||||
group: 0,
|
||||
colorName: "graphs-purple",
|
||||
collapseFunc: collapseConsecutiveIdentical,
|
||||
label: L10N.getStr("timeline.label.styles2"),
|
||||
fields: getStylesFields,
|
||||
},
|
||||
"Reflow": {
|
||||
group: 0,
|
||||
colorName: "graphs-purple",
|
||||
label: L10N.getStr("timeline.label.reflow2")
|
||||
collapseFunc: collapseConsecutiveIdentical,
|
||||
label: L10N.getStr("timeline.label.reflow2"),
|
||||
},
|
||||
"Paint": {
|
||||
group: 0,
|
||||
colorName: "graphs-green",
|
||||
label: L10N.getStr("timeline.label.paint")
|
||||
collapseFunc: collapseConsecutiveIdentical,
|
||||
label: L10N.getStr("timeline.label.paint"),
|
||||
},
|
||||
|
||||
/* Group 1 - JS */
|
||||
"DOMEvent": {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
collapseFunc: collapseDOMIntoDOMJS,
|
||||
label: L10N.getStr("timeline.label.domevent"),
|
||||
fields: getDOMEventFields,
|
||||
},
|
||||
"Javascript": {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
collapseFunc: either(collapseJSIntoDOMJS, collapseConsecutiveIdentical),
|
||||
label: getJSLabel,
|
||||
fields: getJSFields,
|
||||
},
|
||||
"meta::DOMEvent+JS": {
|
||||
colorName: "graphs-yellow",
|
||||
label: getDOMJSLabel,
|
||||
fields: getDOMEventFields,
|
||||
},
|
||||
"Parse HTML": {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
label: L10N.getStr("timeline.label.parseHTML")
|
||||
collapseFunc: collapseConsecutiveIdentical,
|
||||
label: L10N.getStr("timeline.label.parseHTML"),
|
||||
},
|
||||
"Parse XML": {
|
||||
group: 1,
|
||||
colorName: "graphs-yellow",
|
||||
label: L10N.getStr("timeline.label.parseXML")
|
||||
collapseFunc: collapseConsecutiveIdentical,
|
||||
label: L10N.getStr("timeline.label.parseXML"),
|
||||
},
|
||||
"GarbageCollection": {
|
||||
group: 1,
|
||||
colorName: "graphs-red",
|
||||
collapseFunc: collapseAdjacentGC,
|
||||
label: getGCLabel,
|
||||
fields: [
|
||||
{ property: "causeName", label: "Reason:" },
|
||||
{ property: "nonincrementalReason", label: "Non-incremental Reason:" }
|
||||
]
|
||||
],
|
||||
},
|
||||
|
||||
/* Group 2 - User Controlled */
|
||||
@@ -182,7 +210,7 @@ const TIMELINE_BLUEPRINT = {
|
||||
fields: [{
|
||||
property: "causeName",
|
||||
label: L10N.getStr("timeline.markerDetail.consoleTimerName")
|
||||
}]
|
||||
}],
|
||||
},
|
||||
"TimeStamp": {
|
||||
group: 2,
|
||||
@@ -191,10 +219,83 @@ const TIMELINE_BLUEPRINT = {
|
||||
fields: [{
|
||||
property: "causeName",
|
||||
label: "Label:"
|
||||
}]
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper for creating a function that returns the first defined result from
|
||||
* a list of functions passed in as params, in order.
|
||||
* @param ...function fun
|
||||
* @return any
|
||||
*/
|
||||
function either(...fun) {
|
||||
return function() {
|
||||
for (let f of fun) {
|
||||
let result = f.apply(null, arguments);
|
||||
if (result !== undefined) return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A series of collapsers used by the blueprint. These functions are
|
||||
* consecutively invoked on a moving window of two markers.
|
||||
*/
|
||||
|
||||
function collapseConsecutiveIdentical(parent, curr, peek) {
|
||||
// If there is a parent marker currently being filled and the current marker
|
||||
// should go into the parent marker, make it so.
|
||||
if (parent && parent.name == curr.name) {
|
||||
return { toParent: parent.name };
|
||||
}
|
||||
// Otherwise if the current marker is the same type as the next marker type,
|
||||
// create a new parent marker containing the current marker.
|
||||
let next = peek(1);
|
||||
if (next && curr.name == next.name) {
|
||||
return { toParent: curr.name };
|
||||
}
|
||||
}
|
||||
|
||||
function collapseAdjacentGC(parent, curr, peek) {
|
||||
let next = peek(1);
|
||||
if (next && (next.start < curr.end || next.start - curr.end <= 10 /* ms */)) {
|
||||
return collapseConsecutiveIdentical(parent, curr, peek);
|
||||
}
|
||||
}
|
||||
|
||||
function collapseDOMIntoDOMJS(parent, curr, peek) {
|
||||
// If the next marker is a JavaScript marker, create a new meta parent marker
|
||||
// containing the current marker.
|
||||
let next = peek(1);
|
||||
if (next && next.name == "Javascript") {
|
||||
return {
|
||||
forceNew: true,
|
||||
toParent: "meta::DOMEvent+JS",
|
||||
withData: {
|
||||
type: curr.type,
|
||||
eventPhase: curr.eventPhase
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function collapseJSIntoDOMJS(parent, curr, peek) {
|
||||
// If there is a parent marker currently being filled, and it's the one
|
||||
// created from a `DOMEvent` via `collapseDOMIntoDOMJS`, then the current
|
||||
// marker has to go into that one.
|
||||
if (parent && parent.name == "meta::DOMEvent+JS") {
|
||||
return {
|
||||
forceEnd: true,
|
||||
toParent: "meta::DOMEvent+JS",
|
||||
withData: {
|
||||
stack: curr.stack,
|
||||
endStack: curr.endStack
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A series of formatters used by the blueprint.
|
||||
*/
|
||||
@@ -236,6 +337,10 @@ function getJSLabel (marker={}) {
|
||||
return generic;
|
||||
}
|
||||
|
||||
function getDOMJSLabel (marker={}) {
|
||||
return `Event (${marker.type})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash for computing a fields object for a JS marker. If the cause
|
||||
* is considered content (so an entry exists in the JS_MARKER_MAP), do not display it
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Utility functions for collapsing markers into a waterfall.
|
||||
*/
|
||||
|
||||
loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
|
||||
"devtools/performance/global", true);
|
||||
|
||||
/**
|
||||
* Collapses markers into a tree-like structure. Currently, this only goes
|
||||
* one level deep.
|
||||
* @param object markerNode
|
||||
* @param array markersList
|
||||
*/
|
||||
function collapseMarkersIntoNode({ markerNode, markersList }) {
|
||||
let [getOrCreateParentNode, getCurrentParentNode, clearParentNode] = makeParentNodeFactory();
|
||||
|
||||
for (let i = 0, len = markersList.length; i < len; i++) {
|
||||
let curr = markersList[i];
|
||||
let blueprint = TIMELINE_BLUEPRINT[curr.name];
|
||||
|
||||
let parentNode = getCurrentParentNode();
|
||||
let collapse = blueprint.collapseFunc || (() => null);
|
||||
let peek = distance => markersList[i + distance];
|
||||
let collapseInfo = collapse(parentNode, curr, peek);
|
||||
|
||||
if (collapseInfo) {
|
||||
let { toParent, withData, forceNew, forceEnd } = collapseInfo;
|
||||
|
||||
// If the `forceNew` prop is set on the collapse info, then a new parent
|
||||
// marker needs to be created even if there is one already available.
|
||||
if (forceNew) {
|
||||
clearParentNode();
|
||||
}
|
||||
// If the `toParent` prop is set on the collapse info, then this marker
|
||||
// can be collapsed into a higher-level parent marker.
|
||||
if (toParent) {
|
||||
let parentNode = getOrCreateParentNode(markerNode, toParent, curr.start);
|
||||
parentNode.end = curr.end;
|
||||
parentNode.submarkers.push(curr);
|
||||
for (let key in withData) {
|
||||
parentNode[key] = withData[key];
|
||||
}
|
||||
}
|
||||
// If the `forceEnd` prop is set on the collapse info, then the higher-level
|
||||
// parent marker is full and should be finalized.
|
||||
if (forceEnd) {
|
||||
clearParentNode();
|
||||
}
|
||||
} else {
|
||||
clearParentNode();
|
||||
markerNode.submarkers.push(curr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty parent marker, which functions like a regular marker,
|
||||
* but is able to hold additional child markers.
|
||||
* @param string name
|
||||
* @param number start [optional]
|
||||
* @param number end [optional]
|
||||
* @return object
|
||||
*/
|
||||
function makeEmptyMarkerNode(name, start, end) {
|
||||
return {
|
||||
name: name,
|
||||
start: start,
|
||||
end: end,
|
||||
submarkers: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a factory for markers containing other markers.
|
||||
* @return array[function]
|
||||
*/
|
||||
function makeParentNodeFactory() {
|
||||
let marker;
|
||||
|
||||
return [
|
||||
/**
|
||||
* Gets the current parent marker for the given marker name. If it doesn't
|
||||
* exist, it creates it and appends it to another parent marker.
|
||||
* @param object owner
|
||||
* @param string name
|
||||
* @param number start
|
||||
* @return object
|
||||
*/
|
||||
function getOrCreateParentNode(owner, name, start) {
|
||||
if (marker && marker.name == name) {
|
||||
return marker;
|
||||
} else {
|
||||
marker = makeEmptyMarkerNode(name, start);
|
||||
owner.submarkers.push(marker);
|
||||
return marker;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the current marker marker.
|
||||
* @return object
|
||||
*/
|
||||
function getCurrentParentNode() {
|
||||
return marker;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the current marker marker.
|
||||
*/
|
||||
function clearParentNode() {
|
||||
marker = null;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
exports.makeEmptyMarkerNode = makeEmptyMarkerNode;
|
||||
exports.collapseMarkersIntoNode = collapseMarkersIntoNode;
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
|
||||
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
@@ -29,27 +28,29 @@ loader.lazyRequireGetter(this, "MarkerUtils",
|
||||
*/
|
||||
function MarkerDetails(parent, splitter) {
|
||||
EventEmitter.decorate(this);
|
||||
this._onClick = this._onClick.bind(this);
|
||||
|
||||
this._document = parent.ownerDocument;
|
||||
this._parent = parent;
|
||||
this._splitter = splitter;
|
||||
this._splitter.addEventListener("mouseup", () => this.emit("resize"));
|
||||
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onSplitterMouseUp = this._onSplitterMouseUp.bind(this);
|
||||
|
||||
this._parent.addEventListener("click", this._onClick);
|
||||
this._splitter.addEventListener("mouseup", this._onSplitterMouseUp);
|
||||
}
|
||||
|
||||
MarkerDetails.prototype = {
|
||||
/**
|
||||
* Removes any node references from this view.
|
||||
* Sets this view's width.
|
||||
* @param boolean
|
||||
*/
|
||||
destroy: function() {
|
||||
this.empty();
|
||||
this._parent.removeEventListener("click", this._onClick);
|
||||
this._parent = null;
|
||||
this._splitter = null;
|
||||
set width(value) {
|
||||
this._parent.setAttribute("width", value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the view.
|
||||
* Clears the marker details from this view.
|
||||
*/
|
||||
empty: function() {
|
||||
this._parent.innerHTML = "";
|
||||
@@ -60,8 +61,8 @@ MarkerDetails.prototype = {
|
||||
*
|
||||
* @param object params
|
||||
* An options object holding:
|
||||
* marker - The marker to display.
|
||||
* frames - Array of stack frame information; see stack.js.
|
||||
* - marker: The marker to display.
|
||||
* - frames: Array of stack frame information; see stack.js.
|
||||
*/
|
||||
render: function({ marker, frames }) {
|
||||
this.empty();
|
||||
@@ -69,10 +70,10 @@ MarkerDetails.prototype = {
|
||||
let elements = [];
|
||||
elements.push(MarkerUtils.DOM.buildTitle(this._document, marker));
|
||||
elements.push(MarkerUtils.DOM.buildDuration(this._document, marker));
|
||||
MarkerUtils.DOM.buildFields(this._document, marker).forEach(field => elements.push(field));
|
||||
MarkerUtils.DOM.buildFields(this._document, marker).forEach(f => elements.push(f));
|
||||
|
||||
// Build a stack element -- and use the "startStack" label if
|
||||
// we have both a star and endStack.
|
||||
// we have both a startStack and endStack.
|
||||
if (marker.stack) {
|
||||
let type = marker.endStack ? "startStack" : "stack";
|
||||
elements.push(MarkerUtils.DOM.buildStackTrace(this._document, {
|
||||
@@ -98,6 +99,13 @@ MarkerDetails.prototype = {
|
||||
this.emit("view-source", data.url, data.line);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the "mouseup" event on the marker details view splitter.
|
||||
*/
|
||||
_onSplitterMouseUp: function() {
|
||||
this.emit("resize");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file contains the "marker" view, essentially a detailed list
|
||||
* of all the markers in the timeline data.
|
||||
*/
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
const { AbstractTreeItem } = require("resource:///modules/devtools/AbstractTreeItem.jsm");
|
||||
const { TIMELINE_BLUEPRINT: ORIGINAL_BP } = require("devtools/performance/global");
|
||||
|
||||
loader.lazyRequireGetter(this, "MarkerUtils",
|
||||
"devtools/performance/marker-utils");
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const LEVEL_INDENT = 10; // px
|
||||
const ARROW_NODE_OFFSET = -15; // px
|
||||
const WATERFALL_MARKER_SIDEBAR_WIDTH = 175; // px
|
||||
const WATERFALL_MARKER_TIMEBAR_WIDTH_MIN = 5; // px
|
||||
|
||||
/**
|
||||
* A detailed waterfall view for the timeline data.
|
||||
*
|
||||
* @param MarkerView owner
|
||||
* The MarkerView considered the "owner" marker. This newly created
|
||||
* instance will be represent the "submarker". Should be null for root nodes.
|
||||
* @param object marker
|
||||
* Details about this marker, like { name, start, end, submarkers } etc.
|
||||
* @param number level [optional]
|
||||
* The indentation level in the waterfall tree. The root node is at level 0.
|
||||
* @param boolean hidden [optional]
|
||||
* Whether this node should be hidden and not contribute to depth/level
|
||||
* calculations. Defaults to false.
|
||||
*/
|
||||
function MarkerView({ owner, marker, level, hidden }) {
|
||||
AbstractTreeItem.call(this, {
|
||||
parent: owner,
|
||||
level: level|0 - (hidden ? 1 : 0)
|
||||
});
|
||||
|
||||
this.marker = marker;
|
||||
this.hidden = !!hidden;
|
||||
|
||||
this._onItemBlur = this._onItemBlur.bind(this);
|
||||
this._onItemFocus = this._onItemFocus.bind(this);
|
||||
}
|
||||
|
||||
MarkerView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
/**
|
||||
* Calculates and stores the available width for the waterfall.
|
||||
* This should be invoked every time the container node is resized.
|
||||
*/
|
||||
recalculateBounds: function() {
|
||||
this.root._waterfallWidth = this.bounds.width - WATERFALL_MARKER_SIDEBAR_WIDTH;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a list of names and colors used to paint markers.
|
||||
* @see TIMELINE_BLUEPRINT in timeline/widgets/global.js
|
||||
* @param object blueprint
|
||||
*/
|
||||
set blueprint(blueprint) {
|
||||
this.root._blueprint = blueprint;
|
||||
},
|
||||
get blueprint() {
|
||||
return this.root._blueprint;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the { startTime, endTime }, in milliseconds.
|
||||
* @param object interval
|
||||
*/
|
||||
set interval(interval) {
|
||||
this.root._interval = interval;
|
||||
},
|
||||
get interval() {
|
||||
return this.root._interval;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the current waterfall width.
|
||||
* @return number
|
||||
*/
|
||||
getWaterfallWidth: function() {
|
||||
return this._waterfallWidth;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the data scale amount for the current width and interval.
|
||||
* @return number
|
||||
*/
|
||||
getDataScale: function() {
|
||||
let startTime = this.root._interval.startTime|0;
|
||||
let endTime = this.root._interval.endTime|0;
|
||||
return this.root._waterfallWidth / (endTime - startTime);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the view for this waterfall node.
|
||||
* @param nsIDOMNode document
|
||||
* @param nsIDOMNode arrowNode
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
_displaySelf: function(document, arrowNode) {
|
||||
let targetNode = document.createElement("hbox");
|
||||
targetNode.className = "waterfall-tree-item";
|
||||
|
||||
if (this == this.root) {
|
||||
// Bounds are needed for properly positioning and scaling markers in
|
||||
// the waterfall, but it's sufficient to make those calculations only
|
||||
// for the root node.
|
||||
this.root.recalculateBounds();
|
||||
// The AbstractTreeItem propagates events to the root, so we don't
|
||||
// need to listen them on descendant items in the tree.
|
||||
this._addEventListeners();
|
||||
} else {
|
||||
// Root markers are an implementation detail and shouldn't be shown.
|
||||
this._buildMarkerCells(document, targetNode, arrowNode);
|
||||
}
|
||||
|
||||
if (this.hidden) {
|
||||
targetNode.style.display = "none";
|
||||
}
|
||||
|
||||
return targetNode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates this node in the waterfall tree with the corresponding "markers".
|
||||
* @param array:AbstractTreeItem children
|
||||
*/
|
||||
_populateSelf: function(children) {
|
||||
let submarkers = this.marker.submarkers;
|
||||
if (!submarkers || !submarkers.length) {
|
||||
return;
|
||||
}
|
||||
let blueprint = this.root._blueprint;
|
||||
let startTime = this.root._interval.startTime;
|
||||
let endTime = this.root._interval.endTime;
|
||||
let newLevel = this.level + 1;
|
||||
|
||||
for (let i = 0, len = submarkers.length; i < len; i++) {
|
||||
let marker = submarkers[i];
|
||||
|
||||
// If this marker isn't in the global timeline blueprint, don't display
|
||||
// it, but dump a warning message to the console.
|
||||
if (!(marker.name in blueprint)) {
|
||||
if (!(marker.name in ORIGINAL_BP)) {
|
||||
console.warn(`Marker not found in timeline blueprint: ${marker.name}.`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!isMarkerInRange(marker, startTime|0, endTime|0)) {
|
||||
continue;
|
||||
}
|
||||
children.push(new MarkerView({
|
||||
owner: this,
|
||||
marker: marker,
|
||||
level: newLevel,
|
||||
inverted: this.inverted
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Builds all the nodes representing a marker in the waterfall.
|
||||
* @param nsIDOMNode document
|
||||
* @param nsIDOMNode targetNode
|
||||
* @param nsIDOMNode arrowNode
|
||||
*/
|
||||
_buildMarkerCells: function(doc, targetNode, arrowNode) {
|
||||
// Root markers are an implementation detail and shouldn't be shown.
|
||||
let marker = this.marker;
|
||||
if (marker.name == "(root)") {
|
||||
return;
|
||||
}
|
||||
|
||||
let style = this.root._blueprint[marker.name];
|
||||
let startTime = this.root._interval.startTime;
|
||||
let endTime = this.root._interval.endTime;
|
||||
|
||||
let sidebarCell = this._buildMarkerSidebar(
|
||||
doc, style, marker);
|
||||
|
||||
let timebarCell = this._buildMarkerTimebar(
|
||||
doc, style, marker, startTime, endTime, arrowNode);
|
||||
|
||||
targetNode.appendChild(sidebarCell);
|
||||
targetNode.appendChild(timebarCell);
|
||||
|
||||
// Don't render an expando-arrow for leaf nodes.
|
||||
let submarkers = this.marker.submarkers;
|
||||
let hasDescendants = submarkers && submarkers.length > 0;
|
||||
if (hasDescendants) {
|
||||
targetNode.setAttribute("expandable", "");
|
||||
} else {
|
||||
arrowNode.setAttribute("invisible", "");
|
||||
}
|
||||
|
||||
targetNode.setAttribute("level", this.level);
|
||||
},
|
||||
|
||||
/**
|
||||
* Functions creating each cell in this waterfall view.
|
||||
* Invoked by `_displaySelf`.
|
||||
*/
|
||||
_buildMarkerSidebar: function(doc, style, marker) {
|
||||
let cell = doc.createElement("hbox");
|
||||
cell.className = "waterfall-sidebar theme-sidebar";
|
||||
cell.setAttribute("width", WATERFALL_MARKER_SIDEBAR_WIDTH);
|
||||
cell.setAttribute("align", "center");
|
||||
|
||||
let bullet = doc.createElement("hbox");
|
||||
bullet.className = `waterfall-marker-bullet marker-color-${style.colorName}`;
|
||||
bullet.style.transform = `translateX(${this.level * LEVEL_INDENT}px)`;
|
||||
bullet.setAttribute("type", marker.name);
|
||||
cell.appendChild(bullet);
|
||||
|
||||
let name = doc.createElement("description");
|
||||
let label = MarkerUtils.getMarkerLabel(marker);
|
||||
name.className = "plain waterfall-marker-name";
|
||||
name.style.transform = `translateX(${this.level * LEVEL_INDENT}px)`;
|
||||
name.setAttribute("crop", "end");
|
||||
name.setAttribute("flex", "1");
|
||||
name.setAttribute("value", label);
|
||||
name.setAttribute("tooltiptext", label);
|
||||
cell.appendChild(name);
|
||||
|
||||
return cell;
|
||||
},
|
||||
_buildMarkerTimebar: function(doc, style, marker, startTime, endTime, arrowNode) {
|
||||
let cell = doc.createElement("hbox");
|
||||
cell.className = "waterfall-marker waterfall-background-ticks";
|
||||
cell.setAttribute("align", "center");
|
||||
cell.setAttribute("flex", "1");
|
||||
|
||||
let dataScale = this.getDataScale();
|
||||
let offset = (marker.start - startTime) * dataScale;
|
||||
let width = (marker.end - marker.start) * dataScale;
|
||||
|
||||
arrowNode.style.transform =`translateX(${offset + ARROW_NODE_OFFSET}px)`;
|
||||
cell.appendChild(arrowNode);
|
||||
|
||||
let bar = doc.createElement("hbox");
|
||||
bar.className = `waterfall-marker-bar marker-color-${style.colorName}`;
|
||||
bar.style.transform = `translateX(${offset}px)`;
|
||||
bar.setAttribute("type", marker.name);
|
||||
bar.setAttribute("width", Math.max(width, WATERFALL_MARKER_TIMEBAR_WIDTH_MIN));
|
||||
cell.appendChild(bar);
|
||||
|
||||
return cell;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the event listeners for this particular tree item.
|
||||
*/
|
||||
_addEventListeners: function() {
|
||||
this.on("focus", this._onItemFocus);
|
||||
this.on("blur", this._onItemBlur);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "blur" event on the root item.
|
||||
*/
|
||||
_onItemBlur: function() {
|
||||
this.root.emit("unselected");
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "mousedown" event on the root item.
|
||||
*/
|
||||
_onItemFocus: function(e, item) {
|
||||
this.root.emit("selected", item.marker);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks if a given marker is in the specified time range.
|
||||
*
|
||||
* @param object e
|
||||
* The marker containing the { start, end } timestamps.
|
||||
* @param number start
|
||||
* The earliest allowed time.
|
||||
* @param number end
|
||||
* The latest allowed time.
|
||||
* @return boolean
|
||||
* True if the marker fits inside the specified time range.
|
||||
*/
|
||||
function isMarkerInRange(e, start, end) {
|
||||
let m_start = e.start|0;
|
||||
let m_end = e.end|0;
|
||||
|
||||
return (m_start >= start && m_end <= end) || // bounds inside
|
||||
(m_start < start && m_end > end) || // bounds outside
|
||||
(m_start < start && m_end >= start && m_end <= end) || // overlap start
|
||||
(m_end > end && m_start >= start && m_start <= end); // overlap end
|
||||
}
|
||||
|
||||
exports.MarkerView = MarkerView;
|
||||
exports.WATERFALL_MARKER_SIDEBAR_WIDTH = WATERFALL_MARKER_SIDEBAR_WIDTH;
|
||||
@@ -19,6 +19,8 @@ loader.lazyRequireGetter(this, "getColor",
|
||||
"devtools/shared/theme", true);
|
||||
loader.lazyRequireGetter(this, "L10N",
|
||||
"devtools/performance/global", true);
|
||||
loader.lazyRequireGetter(this, "TickUtils",
|
||||
"devtools/performance/waterfall-ticks", true);
|
||||
|
||||
const OVERVIEW_HEADER_HEIGHT = 14; // px
|
||||
const OVERVIEW_ROW_HEIGHT = 11; // px
|
||||
@@ -75,7 +77,7 @@ MarkersOverview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
|
||||
for (let type in blueprint) {
|
||||
this._paintBatches.set(type, { style: blueprint[type], batch: [] });
|
||||
this._lastGroup = Math.max(this._lastGroup, blueprint[type].group);
|
||||
this._lastGroup = Math.max(this._lastGroup, blueprint[type].group || 0);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -143,7 +145,12 @@ MarkersOverview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
let fontFamily = OVERVIEW_HEADER_TEXT_FONT_FAMILY;
|
||||
let textPaddingLeft = OVERVIEW_HEADER_TEXT_PADDING_LEFT * this._pixelRatio;
|
||||
let textPaddingTop = OVERVIEW_HEADER_TEXT_PADDING_TOP * this._pixelRatio;
|
||||
let tickInterval = this._findOptimalTickInterval(dataScale);
|
||||
|
||||
let tickInterval = TickUtils.findOptimalTickInterval({
|
||||
ticksMultiple: OVERVIEW_HEADER_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: OVERVIEW_HEADER_TICKS_SPACING_MIN,
|
||||
dataScale: dataScale
|
||||
});
|
||||
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.font = fontSize + "px " + fontFamily;
|
||||
@@ -190,32 +197,6 @@ MarkersOverview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
return canvas;
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds the optimal tick interval between time markers in this overview.
|
||||
*/
|
||||
_findOptimalTickInterval: function(dataScale) {
|
||||
let timingStep = OVERVIEW_HEADER_TICKS_MULTIPLE;
|
||||
let spacingMin = OVERVIEW_HEADER_TICKS_SPACING_MIN * this._pixelRatio;
|
||||
let maxIters = FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS;
|
||||
let numIters = 0;
|
||||
|
||||
if (dataScale > spacingMin) {
|
||||
return dataScale;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
let scaledStep = dataScale * timingStep;
|
||||
if (++numIters > maxIters) {
|
||||
return scaledStep;
|
||||
}
|
||||
if (scaledStep < spacingMin) {
|
||||
timingStep <<= 1;
|
||||
continue;
|
||||
}
|
||||
return scaledStep;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the theme via `theme` to either "light" or "dark",
|
||||
* and updates the internal styling to match. Requires a redraw
|
||||
|
||||
@@ -56,11 +56,11 @@ const sum = vals => vals.reduce((a, b) => a + b, 0);
|
||||
* parent node is used for all rows.
|
||||
*
|
||||
* @param CallView caller
|
||||
* The CallView considered the "caller" frame. This instance will be
|
||||
* represent the "callee". Should be null for root nodes.
|
||||
* The CallView considered the "caller" frame. This newly created
|
||||
* instance will be represent the "callee". Should be null for root nodes.
|
||||
* @param ThreadNode | FrameNode frame
|
||||
* Details about this function, like { samples, duration, calls } etc.
|
||||
* @param number level
|
||||
* @param number level [optional]
|
||||
* The indentation level in the call tree. The root node is at level 0.
|
||||
* @param boolean hidden [optional]
|
||||
* Whether this node should be hidden and not contribute to depth/level
|
||||
@@ -121,34 +121,32 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
_displaySelf: function(document, arrowNode) {
|
||||
this.document = document;
|
||||
|
||||
let displayedData = this.getDisplayedData();
|
||||
let frameInfo = this.frame.getInfo();
|
||||
|
||||
if (this.visibleCells.duration) {
|
||||
var durationCell = this._createTimeCell(displayedData.totalDuration);
|
||||
var durationCell = this._createTimeCell(document, displayedData.totalDuration);
|
||||
}
|
||||
if (this.visibleCells.selfDuration) {
|
||||
var selfDurationCell = this._createTimeCell(displayedData.selfDuration, true);
|
||||
var selfDurationCell = this._createTimeCell(document, displayedData.selfDuration, true);
|
||||
}
|
||||
if (this.visibleCells.percentage) {
|
||||
var percentageCell = this._createExecutionCell(displayedData.totalPercentage);
|
||||
var percentageCell = this._createExecutionCell(document, displayedData.totalPercentage);
|
||||
}
|
||||
if (this.visibleCells.selfPercentage) {
|
||||
var selfPercentageCell = this._createExecutionCell(displayedData.selfPercentage, true);
|
||||
var selfPercentageCell = this._createExecutionCell(document, displayedData.selfPercentage, true);
|
||||
}
|
||||
if (this.visibleCells.allocations) {
|
||||
var allocationsCell = this._createAllocationsCell(displayedData.totalAllocations);
|
||||
var allocationsCell = this._createAllocationsCell(document, displayedData.totalAllocations);
|
||||
}
|
||||
if (this.visibleCells.selfAllocations) {
|
||||
var selfAllocationsCell = this._createAllocationsCell(displayedData.selfAllocations, true);
|
||||
var selfAllocationsCell = this._createAllocationsCell(document, displayedData.selfAllocations, true);
|
||||
}
|
||||
if (this.visibleCells.samples) {
|
||||
var samplesCell = this._createSamplesCell(displayedData.samples);
|
||||
var samplesCell = this._createSamplesCell(document, displayedData.samples);
|
||||
}
|
||||
if (this.visibleCells.function) {
|
||||
var functionCell = this._createFunctionCell(arrowNode, displayedData.name, frameInfo, this.level);
|
||||
var functionCell = this._createFunctionCell(document, arrowNode, displayedData.name, frameInfo, this.level);
|
||||
}
|
||||
|
||||
let targetNode = document.createElement("hbox");
|
||||
@@ -214,55 +212,59 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
* Functions creating each cell in this call view.
|
||||
* Invoked by `_displaySelf`.
|
||||
*/
|
||||
_createTimeCell: function(duration, isSelf = false) {
|
||||
let cell = this.document.createElement("label");
|
||||
_createTimeCell: function(doc, duration, isSelf = false) {
|
||||
let cell = doc.createElement("description");
|
||||
cell.className = "plain call-tree-cell";
|
||||
cell.setAttribute("type", isSelf ? "self-duration" : "duration");
|
||||
cell.setAttribute("crop", "end");
|
||||
cell.setAttribute("value", L10N.numberWithDecimals(duration, 2) + " " + MILLISECOND_UNITS);
|
||||
return cell;
|
||||
},
|
||||
_createExecutionCell: function(percentage, isSelf = false) {
|
||||
let cell = this.document.createElement("label");
|
||||
_createExecutionCell: function(doc, percentage, isSelf = false) {
|
||||
let cell = doc.createElement("description");
|
||||
cell.className = "plain call-tree-cell";
|
||||
cell.setAttribute("type", isSelf ? "self-percentage" : "percentage");
|
||||
cell.setAttribute("crop", "end");
|
||||
cell.setAttribute("value", L10N.numberWithDecimals(percentage, 2) + PERCENTAGE_UNITS);
|
||||
return cell;
|
||||
},
|
||||
_createAllocationsCell: function(count, isSelf = false) {
|
||||
let cell = this.document.createElement("label");
|
||||
_createAllocationsCell: function(doc, count, isSelf = false) {
|
||||
let cell = doc.createElement("description");
|
||||
cell.className = "plain call-tree-cell";
|
||||
cell.setAttribute("type", isSelf ? "self-allocations" : "allocations");
|
||||
cell.setAttribute("crop", "end");
|
||||
cell.setAttribute("value", count || 0);
|
||||
return cell;
|
||||
},
|
||||
_createSamplesCell: function(count) {
|
||||
let cell = this.document.createElement("label");
|
||||
_createSamplesCell: function(doc, count) {
|
||||
let cell = doc.createElement("description");
|
||||
cell.className = "plain call-tree-cell";
|
||||
cell.setAttribute("type", "samples");
|
||||
cell.setAttribute("crop", "end");
|
||||
cell.setAttribute("value", count || "");
|
||||
return cell;
|
||||
},
|
||||
_createFunctionCell: function(arrowNode, frameName, frameInfo, frameLevel) {
|
||||
let cell = this.document.createElement("hbox");
|
||||
_createFunctionCell: function(doc, arrowNode, frameName, frameInfo, frameLevel) {
|
||||
let cell = doc.createElement("hbox");
|
||||
cell.className = "call-tree-cell";
|
||||
cell.style.MozMarginStart = (frameLevel * CALL_TREE_INDENTATION) + "px";
|
||||
cell.setAttribute("type", "function");
|
||||
cell.appendChild(arrowNode);
|
||||
|
||||
let nameNode = this.document.createElement("label");
|
||||
nameNode.className = "plain call-tree-name";
|
||||
nameNode.setAttribute("flex", "1");
|
||||
nameNode.setAttribute("crop", "end");
|
||||
nameNode.setAttribute("value", frameName);
|
||||
cell.appendChild(nameNode);
|
||||
// Don't render a name label node if there's no function name. A different
|
||||
// location label node will be rendered instead.
|
||||
if (frameName) {
|
||||
let nameNode = doc.createElement("description");
|
||||
nameNode.className = "plain call-tree-name";
|
||||
nameNode.setAttribute("flex", "1");
|
||||
nameNode.setAttribute("crop", "end");
|
||||
nameNode.setAttribute("value", frameName);
|
||||
cell.appendChild(nameNode);
|
||||
}
|
||||
|
||||
// Don't render detailed labels for meta category frames
|
||||
if (!frameInfo.isMetaCategory) {
|
||||
this._appendFunctionDetailsCells(cell, frameInfo);
|
||||
this._appendFunctionDetailsCells(doc, cell, frameInfo);
|
||||
}
|
||||
|
||||
// Don't render an expando-arrow for leaf nodes.
|
||||
@@ -273,40 +275,50 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
|
||||
return cell;
|
||||
},
|
||||
_appendFunctionDetailsCells: function(cell, frameInfo) {
|
||||
let urlNode = this.document.createElement("label");
|
||||
urlNode.className = "plain call-tree-url";
|
||||
urlNode.setAttribute("flex", "1");
|
||||
urlNode.setAttribute("crop", "end");
|
||||
urlNode.setAttribute("value", frameInfo.fileName || "");
|
||||
urlNode.setAttribute("tooltiptext", URL_LABEL_TOOLTIP + " → " + frameInfo.url);
|
||||
urlNode.addEventListener("mousedown", this._onUrlClick);
|
||||
cell.appendChild(urlNode);
|
||||
_appendFunctionDetailsCells: function(doc, cell, frameInfo) {
|
||||
if (frameInfo.fileName) {
|
||||
let urlNode = doc.createElement("description");
|
||||
urlNode.className = "plain call-tree-url";
|
||||
urlNode.setAttribute("flex", "1");
|
||||
urlNode.setAttribute("crop", "end");
|
||||
urlNode.setAttribute("value", frameInfo.fileName);
|
||||
urlNode.setAttribute("tooltiptext", URL_LABEL_TOOLTIP + " → " + frameInfo.url);
|
||||
urlNode.addEventListener("mousedown", this._onUrlClick);
|
||||
cell.appendChild(urlNode);
|
||||
}
|
||||
|
||||
let lineNode = this.document.createElement("label");
|
||||
lineNode.className = "plain call-tree-line";
|
||||
lineNode.setAttribute("value", frameInfo.line ? ":" + frameInfo.line : "");
|
||||
cell.appendChild(lineNode);
|
||||
if (frameInfo.line) {
|
||||
let lineNode = doc.createElement("description");
|
||||
lineNode.className = "plain call-tree-line";
|
||||
lineNode.setAttribute("value", ":" + frameInfo.line);
|
||||
cell.appendChild(lineNode);
|
||||
}
|
||||
|
||||
let columnNode = this.document.createElement("label");
|
||||
columnNode.className = "plain call-tree-column";
|
||||
columnNode.setAttribute("value", frameInfo.column ? ":" + frameInfo.column : "");
|
||||
cell.appendChild(columnNode);
|
||||
if (frameInfo.column) {
|
||||
let columnNode = doc.createElement("description");
|
||||
columnNode.className = "plain call-tree-column";
|
||||
columnNode.setAttribute("value", ":" + frameInfo.column);
|
||||
cell.appendChild(columnNode);
|
||||
}
|
||||
|
||||
let hostNode = this.document.createElement("label");
|
||||
hostNode.className = "plain call-tree-host";
|
||||
hostNode.setAttribute("value", frameInfo.host || "");
|
||||
cell.appendChild(hostNode);
|
||||
if (frameInfo.host) {
|
||||
let hostNode = doc.createElement("description");
|
||||
hostNode.className = "plain call-tree-host";
|
||||
hostNode.setAttribute("value", frameInfo.host);
|
||||
cell.appendChild(hostNode);
|
||||
}
|
||||
|
||||
let spacerNode = this.document.createElement("spacer");
|
||||
let spacerNode = doc.createElement("spacer");
|
||||
spacerNode.setAttribute("flex", "10000");
|
||||
cell.appendChild(spacerNode);
|
||||
|
||||
let categoryNode = this.document.createElement("label");
|
||||
categoryNode.className = "plain call-tree-category";
|
||||
categoryNode.style.color = frameInfo.categoryData.color;
|
||||
categoryNode.setAttribute("value", frameInfo.categoryData.label || "");
|
||||
cell.appendChild(categoryNode);
|
||||
if (frameInfo.categoryData.label) {
|
||||
let categoryNode = doc.createElement("description");
|
||||
categoryNode.className = "plain call-tree-category";
|
||||
categoryNode.style.color = frameInfo.categoryData.color;
|
||||
categoryNode.setAttribute("value", frameInfo.categoryData.label);
|
||||
cell.appendChild(categoryNode);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file contains the "waterfall ticks" view, a header for the
|
||||
* markers displayed in the waterfall.
|
||||
*/
|
||||
|
||||
loader.lazyRequireGetter(this, "L10N",
|
||||
"devtools/performance/global", true);
|
||||
loader.lazyRequireGetter(this, "WATERFALL_MARKER_SIDEBAR_WIDTH",
|
||||
"devtools/performance/marker-view", true);
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS = 100;
|
||||
|
||||
const WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
|
||||
const WATERFALL_HEADER_TICKS_SPACING_MIN = 50; // px
|
||||
const WATERFALL_HEADER_TEXT_PADDING = 3; // px
|
||||
|
||||
const WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
|
||||
const WATERFALL_BACKGROUND_TICKS_SCALES = 3;
|
||||
const WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
|
||||
const WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
|
||||
const WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32; // byte
|
||||
const WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32; // byte
|
||||
|
||||
/**
|
||||
* A header for a markers waterfall.
|
||||
*
|
||||
* @param MarkerView root
|
||||
* The root item of the waterfall tree.
|
||||
*/
|
||||
function WaterfallHeader(root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
WaterfallHeader.prototype = {
|
||||
/**
|
||||
* Creates and appends this header as the first element of the specified
|
||||
* parent element.
|
||||
*
|
||||
* @param nsIDOMNode parentNode
|
||||
* The parent element for this header.
|
||||
*/
|
||||
attachTo: function(parentNode) {
|
||||
let document = parentNode.ownerDocument;
|
||||
let startTime = this.root.interval.startTime;
|
||||
let dataScale = this.root.getDataScale();
|
||||
let waterfallWidth = this.root.getWaterfallWidth();
|
||||
|
||||
let header = this._buildNode(document, startTime, dataScale, waterfallWidth);
|
||||
parentNode.insertBefore(header, parentNode.firstChild);
|
||||
|
||||
this._drawWaterfallBackground(document, dataScale, waterfallWidth);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the node displaying this view.
|
||||
*/
|
||||
_buildNode: function(doc, startTime, dataScale, waterfallWidth) {
|
||||
let container = doc.createElement("hbox");
|
||||
container.className = "waterfall-header-container";
|
||||
container.setAttribute("flex", "1");
|
||||
|
||||
let sidebar = doc.createElement("hbox");
|
||||
sidebar.className = "waterfall-sidebar theme-sidebar";
|
||||
sidebar.setAttribute("width", WATERFALL_MARKER_SIDEBAR_WIDTH);
|
||||
sidebar.setAttribute("align", "center");
|
||||
container.appendChild(sidebar);
|
||||
|
||||
let name = doc.createElement("description");
|
||||
name.className = "plain waterfall-header-name";
|
||||
name.setAttribute("value", L10N.getStr("timeline.records"));
|
||||
sidebar.appendChild(name);
|
||||
|
||||
let ticks = doc.createElement("hbox");
|
||||
ticks.className = "waterfall-header-ticks waterfall-background-ticks";
|
||||
ticks.setAttribute("align", "center");
|
||||
ticks.setAttribute("flex", "1");
|
||||
container.appendChild(ticks);
|
||||
|
||||
let tickInterval = findOptimalTickInterval({
|
||||
ticksMultiple: WATERFALL_HEADER_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: WATERFALL_HEADER_TICKS_SPACING_MIN,
|
||||
dataScale: dataScale
|
||||
});
|
||||
|
||||
for (let x = 0; x < waterfallWidth; x += tickInterval) {
|
||||
let left = x + WATERFALL_HEADER_TEXT_PADDING;
|
||||
let time = Math.round(x / dataScale + startTime);
|
||||
let label = L10N.getFormatStr("timeline.tick", time);
|
||||
|
||||
let node = doc.createElement("description");
|
||||
node.className = "plain waterfall-header-tick";
|
||||
node.style.transform = "translateX(" + left + "px)";
|
||||
node.setAttribute("value", label);
|
||||
ticks.appendChild(node);
|
||||
}
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the background displayed on the marker's waterfall.
|
||||
*/
|
||||
_drawWaterfallBackground: function(doc, dataScale, waterfallWidth) {
|
||||
if (!this._canvas || !this._ctx) {
|
||||
this._canvas = doc.createElementNS(HTML_NS, "canvas");
|
||||
this._ctx = this._canvas.getContext("2d");
|
||||
}
|
||||
let canvas = this._canvas;
|
||||
let ctx = this._ctx;
|
||||
|
||||
// Nuke the context.
|
||||
let canvasWidth = canvas.width = waterfallWidth;
|
||||
let canvasHeight = canvas.height = 1; // Awww yeah, 1px, repeats on Y axis.
|
||||
|
||||
// Start over.
|
||||
let imageData = ctx.createImageData(canvasWidth, canvasHeight);
|
||||
let pixelArray = imageData.data;
|
||||
|
||||
let buf = new ArrayBuffer(pixelArray.length);
|
||||
let view8bit = new Uint8ClampedArray(buf);
|
||||
let view32bit = new Uint32Array(buf);
|
||||
|
||||
// Build new millisecond tick lines...
|
||||
let [r, g, b] = WATERFALL_BACKGROUND_TICKS_COLOR_RGB;
|
||||
let alphaComponent = WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
|
||||
let tickInterval = findOptimalTickInterval({
|
||||
ticksMultiple: WATERFALL_BACKGROUND_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: WATERFALL_BACKGROUND_TICKS_SPACING_MIN,
|
||||
dataScale: dataScale
|
||||
});
|
||||
|
||||
// Insert one pixel for each division on each scale.
|
||||
for (let i = 1; i <= WATERFALL_BACKGROUND_TICKS_SCALES; i++) {
|
||||
let increment = tickInterval * Math.pow(2, i);
|
||||
for (let x = 0; x < canvasWidth; x += increment) {
|
||||
let position = x | 0;
|
||||
view32bit[position] = (alphaComponent << 24) | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
alphaComponent += WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
|
||||
}
|
||||
|
||||
// Flush the image data and cache the waterfall background.
|
||||
pixelArray.set(view8bit);
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
doc.mozSetImageElement("waterfall-background", canvas);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the optimal tick interval between time markers in this timeline.
|
||||
*
|
||||
* @param number ticksMultiple
|
||||
* @param number ticksSpacingMin
|
||||
* @param number dataScale
|
||||
* @return number
|
||||
*/
|
||||
function findOptimalTickInterval({ ticksMultiple, ticksSpacingMin, dataScale }) {
|
||||
let timingStep = ticksMultiple;
|
||||
let maxIters = FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS;
|
||||
let numIters = 0;
|
||||
|
||||
if (dataScale > ticksSpacingMin) {
|
||||
return dataScale;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
let scaledStep = dataScale * timingStep;
|
||||
if (++numIters > maxIters) {
|
||||
return scaledStep;
|
||||
}
|
||||
if (scaledStep < ticksSpacingMin) {
|
||||
timingStep <<= 1;
|
||||
continue;
|
||||
}
|
||||
return scaledStep;
|
||||
}
|
||||
}
|
||||
|
||||
exports.WaterfallHeader = WaterfallHeader;
|
||||
exports.TickUtils = { findOptimalTickInterval };
|
||||
@@ -1,620 +0,0 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file contains the "waterfall" view, essentially a detailed list
|
||||
* of all the markers in the timeline data.
|
||||
*/
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
|
||||
loader.lazyRequireGetter(this, "L10N",
|
||||
"devtools/performance/global", true);
|
||||
loader.lazyRequireGetter(this, "MarkerUtils",
|
||||
"devtools/performance/marker-utils");
|
||||
|
||||
loader.lazyImporter(this, "setNamedTimeout",
|
||||
"resource:///modules/devtools/ViewHelpers.jsm");
|
||||
loader.lazyImporter(this, "clearNamedTimeout",
|
||||
"resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const WATERFALL_SIDEBAR_WIDTH = 200; // px
|
||||
|
||||
const WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT = 30;
|
||||
const WATERFALL_FLUSH_OUTSTANDING_MARKERS_DELAY = 75; // ms
|
||||
|
||||
const FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS = 100;
|
||||
const WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
|
||||
const WATERFALL_HEADER_TICKS_SPACING_MIN = 50; // px
|
||||
const WATERFALL_HEADER_TEXT_PADDING = 3; // px
|
||||
|
||||
const WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
|
||||
const WATERFALL_BACKGROUND_TICKS_SCALES = 3;
|
||||
const WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
|
||||
const WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
|
||||
const WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32; // byte
|
||||
const WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32; // byte
|
||||
const WATERFALL_MARKER_BAR_WIDTH_MIN = 5; // px
|
||||
|
||||
const WATERFALL_ROWCOUNT_ONPAGEUPDOWN = 10;
|
||||
|
||||
/**
|
||||
* A detailed waterfall view for the timeline data.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the waterfall.
|
||||
* @param nsIDOMNode container
|
||||
* The container node that key events should be bound to.
|
||||
* @param Object blueprint
|
||||
* List of names and colors defining markers.
|
||||
*/
|
||||
function Waterfall(parent, container, blueprint) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this._parent = parent;
|
||||
this._document = parent.ownerDocument;
|
||||
this._container = container;
|
||||
this._fragment = this._document.createDocumentFragment();
|
||||
this._outstandingMarkers = [];
|
||||
|
||||
this._headerContents = this._document.createElement("hbox");
|
||||
this._headerContents.className = "waterfall-header-contents";
|
||||
this._parent.appendChild(this._headerContents);
|
||||
|
||||
this._listContents = this._document.createElement("vbox");
|
||||
this._listContents.className = "waterfall-list-contents";
|
||||
this._listContents.setAttribute("flex", "1");
|
||||
this._parent.appendChild(this._listContents);
|
||||
|
||||
this.setupKeys();
|
||||
|
||||
this._isRTL = this._getRTL();
|
||||
|
||||
// Lazy require is a bit slow, and these are hot objects.
|
||||
this._l10n = L10N;
|
||||
this._blueprint = blueprint;
|
||||
this._setNamedTimeout = setNamedTimeout;
|
||||
this._clearNamedTimeout = clearNamedTimeout;
|
||||
|
||||
// Selected row index. By default, we want the first
|
||||
// row to be selected.
|
||||
this._selectedRowIdx = 0;
|
||||
|
||||
// Default rowCount
|
||||
this.rowCount = WATERFALL_ROWCOUNT_ONPAGEUPDOWN;
|
||||
}
|
||||
|
||||
Waterfall.prototype = {
|
||||
/**
|
||||
* Removes any node references from this view.
|
||||
*/
|
||||
destroy: function() {
|
||||
this._parent = this._document = this._container = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates this view with the provided data source.
|
||||
*
|
||||
* @param object data
|
||||
* An object containing the following properties:
|
||||
* - markers: a list of markers received from the controller
|
||||
* - interval: the { startTime, endTime }, in milliseconds
|
||||
*/
|
||||
setData: function({ markers, interval }) {
|
||||
this.clearView();
|
||||
this._markers = markers;
|
||||
this._interval = interval;
|
||||
|
||||
let { startTime, endTime } = interval;
|
||||
let dataScale = this._waterfallWidth / (endTime - startTime);
|
||||
this._drawWaterfallBackground(dataScale);
|
||||
|
||||
this._buildHeader(this._headerContents, startTime, dataScale);
|
||||
this._buildMarkers(this._listContents, markers, startTime, endTime, dataScale);
|
||||
this.selectRow(this._selectedRowIdx);
|
||||
},
|
||||
|
||||
/**
|
||||
* List of names and colors used to paint markers.
|
||||
* @see TIMELINE_BLUEPRINT in timeline/widgets/global.js
|
||||
*/
|
||||
setBlueprint: function(blueprint) {
|
||||
this._blueprint = blueprint;
|
||||
},
|
||||
|
||||
/**
|
||||
* Keybindings.
|
||||
*/
|
||||
setupKeys: function() {
|
||||
let pane = this._container;
|
||||
pane.addEventListener("keydown", e => {
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_UP) {
|
||||
e.preventDefault();
|
||||
this.selectNearestRow(this._selectedRowIdx - 1);
|
||||
}
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DOWN) {
|
||||
e.preventDefault();
|
||||
this.selectNearestRow(this._selectedRowIdx + 1);
|
||||
}
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_HOME) {
|
||||
e.preventDefault();
|
||||
this.selectNearestRow(0);
|
||||
}
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_END) {
|
||||
e.preventDefault();
|
||||
this.selectNearestRow(this._listContents.children.length);
|
||||
}
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP) {
|
||||
e.preventDefault();
|
||||
this.selectNearestRow(this._selectedRowIdx - this.rowCount);
|
||||
}
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
|
||||
e.preventDefault();
|
||||
this.selectNearestRow(this._selectedRowIdx + this.rowCount);
|
||||
}
|
||||
}, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Depopulates this view.
|
||||
*/
|
||||
clearView: function() {
|
||||
while (this._headerContents.hasChildNodes()) {
|
||||
this._headerContents.firstChild.remove();
|
||||
}
|
||||
while (this._listContents.hasChildNodes()) {
|
||||
this._listContents.firstChild.remove();
|
||||
}
|
||||
this._listContents.scrollTop = 0;
|
||||
this._outstandingMarkers.length = 0;
|
||||
this._clearNamedTimeout("flush-outstanding-markers");
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculates and stores the available width for the waterfall.
|
||||
* This should be invoked every time the container window is resized.
|
||||
*/
|
||||
recalculateBounds: function() {
|
||||
let bounds = this._parent.getBoundingClientRect();
|
||||
this._waterfallWidth = bounds.width - WATERFALL_SIDEBAR_WIDTH;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the header part of this view.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the header.
|
||||
* @param number startTime
|
||||
* @see Waterfall.prototype.setData
|
||||
* @param number dataScale
|
||||
* The time scale of the data source.
|
||||
*/
|
||||
_buildHeader: function(parent, startTime, dataScale) {
|
||||
let container = this._document.createElement("hbox");
|
||||
container.className = "waterfall-header-container";
|
||||
container.setAttribute("flex", "1");
|
||||
|
||||
let sidebar = this._document.createElement("hbox");
|
||||
sidebar.className = "waterfall-sidebar theme-sidebar";
|
||||
sidebar.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
|
||||
sidebar.setAttribute("align", "center");
|
||||
container.appendChild(sidebar);
|
||||
|
||||
let name = this._document.createElement("label");
|
||||
name.className = "plain waterfall-header-name";
|
||||
name.setAttribute("value", this._l10n.getStr("timeline.records"));
|
||||
sidebar.appendChild(name);
|
||||
|
||||
let ticks = this._document.createElement("hbox");
|
||||
ticks.className = "waterfall-header-ticks waterfall-background-ticks";
|
||||
ticks.setAttribute("align", "center");
|
||||
ticks.setAttribute("flex", "1");
|
||||
container.appendChild(ticks);
|
||||
|
||||
let offset = this._isRTL ? this._waterfallWidth : 0;
|
||||
let direction = this._isRTL ? -1 : 1;
|
||||
let tickInterval = this._findOptimalTickInterval({
|
||||
ticksMultiple: WATERFALL_HEADER_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: WATERFALL_HEADER_TICKS_SPACING_MIN,
|
||||
dataScale: dataScale
|
||||
});
|
||||
|
||||
for (let x = 0; x < this._waterfallWidth; x += tickInterval) {
|
||||
let left = x + direction * WATERFALL_HEADER_TEXT_PADDING;
|
||||
let time = Math.round(x / dataScale + startTime);
|
||||
let label = this._l10n.getFormatStr("timeline.tick", time);
|
||||
|
||||
let node = this._document.createElement("label");
|
||||
node.className = "plain waterfall-header-tick";
|
||||
node.style.transform = "translateX(" + (left - offset) + "px)";
|
||||
node.setAttribute("value", label);
|
||||
ticks.appendChild(node);
|
||||
}
|
||||
|
||||
parent.appendChild(container);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the markers part of this view.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the markers.
|
||||
* @param number startTime
|
||||
* @see Waterfall.prototype.setData
|
||||
* @param number dataScale
|
||||
* The time scale of the data source.
|
||||
*/
|
||||
_buildMarkers: function(parent, markers, startTime, endTime, dataScale) {
|
||||
let rowsCount = 0;
|
||||
let markerIdx = -1;
|
||||
|
||||
for (let marker of markers) {
|
||||
markerIdx++;
|
||||
|
||||
if (!isMarkerInRange(marker, startTime, endTime)) {
|
||||
continue;
|
||||
}
|
||||
if (!(marker.name in this._blueprint)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only build and display a finite number of markers initially, to
|
||||
// preserve a snappy UI. After a certain delay, continue building the
|
||||
// outstanding markers while there's (hopefully) no user interaction.
|
||||
let arguments_ = [this._fragment, marker, startTime, dataScale, markerIdx, rowsCount];
|
||||
if (rowsCount++ < WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT) {
|
||||
this._buildMarker.apply(this, arguments_);
|
||||
} else {
|
||||
this._outstandingMarkers.push(arguments_);
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no outstanding markers, add a dummy "spacer" at the end
|
||||
// to fill up any remaining available space in the UI.
|
||||
if (!this._outstandingMarkers.length) {
|
||||
this._buildMarker(this._fragment, null);
|
||||
}
|
||||
// Otherwise prepare flushing the outstanding markers after a small delay.
|
||||
else {
|
||||
let delay = WATERFALL_FLUSH_OUTSTANDING_MARKERS_DELAY;
|
||||
let func = () => this._buildOutstandingMarkers(parent);
|
||||
this._setNamedTimeout("flush-outstanding-markers", delay, func);
|
||||
}
|
||||
|
||||
parent.appendChild(this._fragment);
|
||||
},
|
||||
|
||||
/**
|
||||
* Finishes building the outstanding markers in this view.
|
||||
* @see Waterfall.prototype._buildMarkers
|
||||
*/
|
||||
_buildOutstandingMarkers: function(parent) {
|
||||
if (!this._outstandingMarkers.length) {
|
||||
return;
|
||||
}
|
||||
for (let args of this._outstandingMarkers) {
|
||||
this._buildMarker.apply(this, args);
|
||||
}
|
||||
this._outstandingMarkers.length = 0;
|
||||
parent.appendChild(this._fragment);
|
||||
this.selectRow(this._selectedRowIdx);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a single marker in this view.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the marker.
|
||||
* @param object marker
|
||||
* The { name, start, end } marker in the data source.
|
||||
* @param startTime
|
||||
* @see Waterfall.prototype.setData
|
||||
* @param number dataScale
|
||||
* @see Waterfall.prototype._buildMarkers
|
||||
* @param number markerIdx
|
||||
* Index of the marker in this._markers
|
||||
* @param number rowIdx
|
||||
* Index of current row
|
||||
*/
|
||||
_buildMarker: function(parent, marker, startTime, dataScale, markerIdx, rowIdx) {
|
||||
let container = this._document.createElement("hbox");
|
||||
container.setAttribute("markerIdx", markerIdx);
|
||||
container.className = "waterfall-marker-container";
|
||||
|
||||
if (marker) {
|
||||
this._buildMarkerSidebar(container, marker);
|
||||
this._buildMarkerWaterfall(container, marker, startTime, dataScale, markerIdx);
|
||||
container.onclick = () => this.selectRow(rowIdx);
|
||||
} else {
|
||||
this._buildMarkerSpacer(container);
|
||||
container.setAttribute("flex", "1");
|
||||
container.setAttribute("is-spacer", "");
|
||||
}
|
||||
|
||||
parent.appendChild(container);
|
||||
},
|
||||
|
||||
/**
|
||||
* Select first row.
|
||||
*/
|
||||
resetSelection: function() {
|
||||
this.selectRow(0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Select a marker in the waterfall.
|
||||
*
|
||||
* @param number idx
|
||||
* Index of the row to select. -1 clears the selection.
|
||||
*/
|
||||
selectRow: function(idx) {
|
||||
let prev = this._listContents.children[this._selectedRowIdx];
|
||||
if (prev) {
|
||||
prev.classList.remove("selected");
|
||||
}
|
||||
|
||||
this._selectedRowIdx = idx;
|
||||
|
||||
let row = this._listContents.children[idx];
|
||||
if (row && !row.hasAttribute("is-spacer")) {
|
||||
row.focus();
|
||||
row.classList.add("selected");
|
||||
|
||||
let markerIdx = row.getAttribute("markerIdx");
|
||||
this.emit("selected", this._markers[markerIdx]);
|
||||
this.ensureRowIsVisible(row);
|
||||
} else {
|
||||
this.emit("unselected");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Find a valid row to select.
|
||||
*
|
||||
* @param number idx
|
||||
* Index of the row to select.
|
||||
*/
|
||||
selectNearestRow: function(idx) {
|
||||
if (this._listContents.children.length == 0) {
|
||||
return;
|
||||
}
|
||||
idx = Math.max(idx, 0);
|
||||
idx = Math.min(idx, this._listContents.children.length - 1);
|
||||
let row = this._listContents.children[idx];
|
||||
if (row && row.hasAttribute("is-spacer")) {
|
||||
if (idx > 0) {
|
||||
return this.selectNearestRow(idx - 1);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.selectRow(idx);
|
||||
},
|
||||
|
||||
/**
|
||||
* Scroll waterfall to ensure row is in the viewport.
|
||||
*
|
||||
* @param number idx
|
||||
* Index of the row to select.
|
||||
*/
|
||||
ensureRowIsVisible: function(row) {
|
||||
let parent = row.parentNode;
|
||||
let parentRect = parent.getBoundingClientRect();
|
||||
let rowRect = row.getBoundingClientRect();
|
||||
let yDelta = rowRect.top - parentRect.top;
|
||||
if (yDelta < 0) {
|
||||
parent.scrollTop += yDelta;
|
||||
}
|
||||
yDelta = parentRect.bottom - rowRect.bottom;
|
||||
if (yDelta < 0) {
|
||||
parent.scrollTop -= yDelta;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the sidebar part of a marker in this view.
|
||||
*
|
||||
* @param nsIDOMNode container
|
||||
* The container node representing the marker in this view.
|
||||
* @param object marker
|
||||
* @see Waterfall.prototype._buildMarker
|
||||
*/
|
||||
_buildMarkerSidebar: function(container, marker) {
|
||||
let blueprint = this._blueprint[marker.name];
|
||||
|
||||
let sidebar = this._document.createElement("hbox");
|
||||
sidebar.className = "waterfall-sidebar theme-sidebar";
|
||||
sidebar.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
|
||||
sidebar.setAttribute("align", "center");
|
||||
|
||||
let bullet = this._document.createElement("hbox");
|
||||
bullet.className = `waterfall-marker-bullet marker-color-${blueprint.colorName}`;
|
||||
bullet.setAttribute("type", marker.name);
|
||||
sidebar.appendChild(bullet);
|
||||
|
||||
let name = this._document.createElement("label");
|
||||
name.setAttribute("crop", "end");
|
||||
name.setAttribute("flex", "1");
|
||||
name.className = "plain waterfall-marker-name";
|
||||
|
||||
let label = MarkerUtils.getMarkerLabel(marker);
|
||||
name.setAttribute("value", label);
|
||||
name.setAttribute("tooltiptext", label);
|
||||
sidebar.appendChild(name);
|
||||
|
||||
container.appendChild(sidebar);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the waterfall part of a marker in this view.
|
||||
*
|
||||
* @param nsIDOMNode container
|
||||
* The container node representing the marker.
|
||||
* @param object marker
|
||||
* @see Waterfall.prototype._buildMarker
|
||||
* @param startTime
|
||||
* @see Waterfall.prototype.setData
|
||||
* @param number dataScale
|
||||
* @see Waterfall.prototype._buildMarkers
|
||||
*/
|
||||
_buildMarkerWaterfall: function(container, marker, startTime, dataScale) {
|
||||
let blueprint = this._blueprint[marker.name];
|
||||
|
||||
let waterfall = this._document.createElement("hbox");
|
||||
waterfall.className = "waterfall-marker-item waterfall-background-ticks";
|
||||
waterfall.setAttribute("align", "center");
|
||||
waterfall.setAttribute("flex", "1");
|
||||
|
||||
let start = (marker.start - startTime) * dataScale;
|
||||
let width = (marker.end - marker.start) * dataScale;
|
||||
let offset = this._isRTL ? this._waterfallWidth : 0;
|
||||
|
||||
let bar = this._document.createElement("hbox");
|
||||
bar.className = `waterfall-marker-bar marker-color-${blueprint.colorName}`;
|
||||
bar.style.transform = "translateX(" + (start - offset) + "px)";
|
||||
bar.setAttribute("type", marker.name);
|
||||
bar.setAttribute("width", Math.max(width, WATERFALL_MARKER_BAR_WIDTH_MIN));
|
||||
waterfall.appendChild(bar);
|
||||
|
||||
container.appendChild(waterfall);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a dummy spacer as an empty marker.
|
||||
*
|
||||
* @param nsIDOMNode container
|
||||
* The container node representing the marker.
|
||||
*/
|
||||
_buildMarkerSpacer: function(container) {
|
||||
let sidebarSpacer = this._document.createElement("spacer");
|
||||
sidebarSpacer.className = "waterfall-sidebar theme-sidebar";
|
||||
sidebarSpacer.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
|
||||
|
||||
let waterfallSpacer = this._document.createElement("spacer");
|
||||
waterfallSpacer.className = "waterfall-marker-item waterfall-background-ticks";
|
||||
waterfallSpacer.setAttribute("flex", "1");
|
||||
|
||||
container.appendChild(sidebarSpacer);
|
||||
container.appendChild(waterfallSpacer);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the background displayed on the marker's waterfall.
|
||||
*
|
||||
* @param number dataScale
|
||||
* @see Waterfall.prototype._buildMarkers
|
||||
*/
|
||||
_drawWaterfallBackground: function(dataScale) {
|
||||
if (!this._canvas || !this._ctx) {
|
||||
this._canvas = this._document.createElementNS(HTML_NS, "canvas");
|
||||
this._ctx = this._canvas.getContext("2d");
|
||||
}
|
||||
let canvas = this._canvas;
|
||||
let ctx = this._ctx;
|
||||
|
||||
// Nuke the context.
|
||||
let canvasWidth = canvas.width = this._waterfallWidth;
|
||||
let canvasHeight = canvas.height = 1; // Awww yeah, 1px, repeats on Y axis.
|
||||
|
||||
// Start over.
|
||||
let imageData = ctx.createImageData(canvasWidth, canvasHeight);
|
||||
let pixelArray = imageData.data;
|
||||
|
||||
let buf = new ArrayBuffer(pixelArray.length);
|
||||
let view8bit = new Uint8ClampedArray(buf);
|
||||
let view32bit = new Uint32Array(buf);
|
||||
|
||||
// Build new millisecond tick lines...
|
||||
let [r, g, b] = WATERFALL_BACKGROUND_TICKS_COLOR_RGB;
|
||||
let alphaComponent = WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
|
||||
let tickInterval = this._findOptimalTickInterval({
|
||||
ticksMultiple: WATERFALL_BACKGROUND_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: WATERFALL_BACKGROUND_TICKS_SPACING_MIN,
|
||||
dataScale: dataScale
|
||||
});
|
||||
|
||||
// Insert one pixel for each division on each scale.
|
||||
for (let i = 1; i <= WATERFALL_BACKGROUND_TICKS_SCALES; i++) {
|
||||
let increment = tickInterval * Math.pow(2, i);
|
||||
for (let x = 0; x < canvasWidth; x += increment) {
|
||||
let position = x | 0;
|
||||
view32bit[position] = (alphaComponent << 24) | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
alphaComponent += WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
|
||||
}
|
||||
|
||||
// Flush the image data and cache the waterfall background.
|
||||
pixelArray.set(view8bit);
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
this._document.mozSetImageElement("waterfall-background", canvas);
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds the optimal tick interval between time markers in this timeline.
|
||||
*
|
||||
* @param number ticksMultiple
|
||||
* @param number ticksSpacingMin
|
||||
* @param number dataScale
|
||||
* @return number
|
||||
*/
|
||||
_findOptimalTickInterval: function({ ticksMultiple, ticksSpacingMin, dataScale }) {
|
||||
let timingStep = ticksMultiple;
|
||||
let maxIters = FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS;
|
||||
let numIters = 0;
|
||||
|
||||
if (dataScale > ticksSpacingMin) {
|
||||
return dataScale;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
let scaledStep = dataScale * timingStep;
|
||||
if (++numIters > maxIters) {
|
||||
return scaledStep;
|
||||
}
|
||||
if (scaledStep < ticksSpacingMin) {
|
||||
timingStep <<= 1;
|
||||
continue;
|
||||
}
|
||||
return scaledStep;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if this is document is in RTL mode.
|
||||
* @return boolean
|
||||
*/
|
||||
_getRTL: function() {
|
||||
let win = this._document.defaultView;
|
||||
let doc = this._document.documentElement;
|
||||
return win.getComputedStyle(doc, null).direction == "rtl";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a given marker is in the specified time range.
|
||||
*
|
||||
* @param object e
|
||||
* The marker containing the { start, end } timestamps.
|
||||
* @param number start
|
||||
* The earliest allowed time.
|
||||
* @param number end
|
||||
* The latest allowed time.
|
||||
* @return boolean
|
||||
* True if the marker fits inside the specified time range.
|
||||
*/
|
||||
function isMarkerInRange(e, start, end) {
|
||||
return (e.start >= start && e.end <= end) || // bounds inside
|
||||
(e.start < start && e.end > end) || // bounds outside
|
||||
(e.start < start && e.end >= start && e.end <= end) || // overlap start
|
||||
(e.end > end && e.start >= start && e.start <= end); // overlap end
|
||||
}
|
||||
|
||||
exports.Waterfall = Waterfall;
|
||||
@@ -15,11 +15,13 @@ EXTRA_JS_MODULES.devtools.performance += [
|
||||
'modules/logic/recording-model.js',
|
||||
'modules/logic/recording-utils.js',
|
||||
'modules/logic/tree-model.js',
|
||||
'modules/logic/waterfall-utils.js',
|
||||
'modules/widgets/graphs.js',
|
||||
'modules/widgets/marker-details.js',
|
||||
'modules/widgets/marker-view.js',
|
||||
'modules/widgets/markers-overview.js',
|
||||
'modules/widgets/tree-view.js',
|
||||
'modules/widgets/waterfall.js',
|
||||
'modules/widgets/waterfall-ticks.js',
|
||||
'panel.js'
|
||||
]
|
||||
|
||||
|
||||
@@ -25,12 +25,16 @@ loader.lazyRequireGetter(this, "RecordingModel",
|
||||
"devtools/performance/recording-model", true);
|
||||
loader.lazyRequireGetter(this, "GraphsController",
|
||||
"devtools/performance/graphs", true);
|
||||
loader.lazyRequireGetter(this, "Waterfall",
|
||||
"devtools/performance/waterfall", true);
|
||||
loader.lazyRequireGetter(this, "WaterfallHeader",
|
||||
"devtools/performance/waterfall-ticks", true);
|
||||
loader.lazyRequireGetter(this, "MarkerView",
|
||||
"devtools/performance/marker-view", true);
|
||||
loader.lazyRequireGetter(this, "MarkerDetails",
|
||||
"devtools/performance/marker-details", true);
|
||||
loader.lazyRequireGetter(this, "MarkerUtils",
|
||||
"devtools/performance/marker-utils");
|
||||
loader.lazyRequireGetter(this, "WaterfallUtils",
|
||||
"devtools/performance/waterfall-utils");
|
||||
loader.lazyRequireGetter(this, "CallView",
|
||||
"devtools/performance/tree-view", true);
|
||||
loader.lazyRequireGetter(this, "ThreadNode",
|
||||
|
||||
@@ -73,6 +73,8 @@
|
||||
</popupset>
|
||||
|
||||
<hbox class="theme-body" flex="1">
|
||||
|
||||
<!-- Sidebar: controls and recording list -->
|
||||
<vbox id="recordings-pane">
|
||||
<toolbar id="recordings-toolbar"
|
||||
class="devtools-toolbar">
|
||||
@@ -92,43 +94,50 @@
|
||||
<vbox id="recordings-list" flex="1"/>
|
||||
</vbox>
|
||||
|
||||
<!-- Main panel content -->
|
||||
<vbox id="performance-pane" flex="1">
|
||||
<toolbar id="performance-toolbar" class="devtools-toolbar">
|
||||
<hbox id="performance-toolbar-control-other" class="devtools-toolbarbutton-group">
|
||||
|
||||
<!-- Top toolbar controls -->
|
||||
<toolbar id="performance-toolbar"
|
||||
class="devtools-toolbar">
|
||||
<hbox id="performance-toolbar-controls-other"
|
||||
class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="filter-button"
|
||||
class="devtools-toolbarbutton"
|
||||
popup="performance-filter-menupopup"
|
||||
tooltiptext="&profilerUI.options.filter.tooltiptext;"/>
|
||||
</hbox>
|
||||
<hbox id="performance-toolbar-controls-detail-views" class="devtools-toolbarbutton-group">
|
||||
<hbox id="performance-toolbar-controls-detail-views"
|
||||
class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="select-waterfall-view"
|
||||
class="devtools-toolbarbutton devtools-button"
|
||||
label="&profilerUI.toolbar.waterfall;"
|
||||
label="Waterfall"
|
||||
hidden="true"
|
||||
data-view="waterfall" />
|
||||
<toolbarbutton id="select-js-calltree-view"
|
||||
class="devtools-toolbarbutton devtools-button"
|
||||
label="&profilerUI.toolbar.js-calltree;"
|
||||
label="Call Tree"
|
||||
hidden="true"
|
||||
data-view="js-calltree" />
|
||||
<toolbarbutton id="select-js-flamegraph-view"
|
||||
class="devtools-toolbarbutton devtools-button"
|
||||
label="&profilerUI.toolbar.js-flamegraph;"
|
||||
label="Flame Chart"
|
||||
hidden="true"
|
||||
data-view="js-flamegraph" />
|
||||
<toolbarbutton id="select-memory-calltree-view"
|
||||
class="devtools-toolbarbutton devtools-button"
|
||||
label="&profilerUI.toolbar.memory-calltree1;"
|
||||
label="Allocations Tree"
|
||||
hidden="true"
|
||||
data-view="memory-calltree" />
|
||||
<toolbarbutton id="select-memory-flamegraph-view"
|
||||
class="devtools-toolbarbutton devtools-button"
|
||||
label="&profilerUI.toolbar.memory-flamegraph1;"
|
||||
label="Allocations Chart"
|
||||
hidden="true"
|
||||
data-view="memory-flamegraph" />
|
||||
</hbox>
|
||||
<spacer flex="1"></spacer>
|
||||
<hbox id="performance-toolbar-control-options" class="devtools-toolbarbutton-group">
|
||||
<hbox id="performance-toolbar-controls-options"
|
||||
class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="performance-options-button"
|
||||
class="devtools-toolbarbutton devtools-option-toolbarbutton"
|
||||
popup="performance-options-menupopup"
|
||||
@@ -136,7 +145,10 @@
|
||||
</hbox>
|
||||
</toolbar>
|
||||
|
||||
<!-- Recording contents and general notice messages -->
|
||||
<deck id="performance-view" flex="1">
|
||||
|
||||
<!-- "Empty" notice, shown when there's no recordings available -->
|
||||
<hbox id="empty-notice"
|
||||
class="notice-container"
|
||||
align="center"
|
||||
@@ -145,16 +157,25 @@
|
||||
<hbox class="devtools-toolbarbutton-group"
|
||||
pack="center">
|
||||
<toolbarbutton class="devtools-toolbarbutton record-button"
|
||||
label="&profilerUI.startRecording;" />
|
||||
label="&profilerUI.startRecording;"
|
||||
standalone="true"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
|
||||
<!-- Recording contents -->
|
||||
<vbox id="performance-view-content" flex="1">
|
||||
|
||||
<!-- Overview graphs -->
|
||||
<vbox id="overview-pane">
|
||||
<hbox id="markers-overview"/>
|
||||
<hbox id="memory-overview"/>
|
||||
<hbox id="time-framerate"/>
|
||||
</vbox>
|
||||
|
||||
<!-- Detail views and specific notice messages -->
|
||||
<deck id="details-pane-container" flex="1">
|
||||
|
||||
<!-- "Loading" notice, shown when a recording is being loaded -->
|
||||
<hbox id="loading-notice"
|
||||
class="notice-container devtools-throbber"
|
||||
align="center"
|
||||
@@ -162,94 +183,113 @@
|
||||
flex="1">
|
||||
<label value="&profilerUI.loadingNotice;"/>
|
||||
</hbox>
|
||||
<hbox id="recording-notice"
|
||||
|
||||
<!-- "Recording" notice, shown when a recording is in progress -->
|
||||
<vbox id="recording-notice"
|
||||
class="notice-container"
|
||||
align="center"
|
||||
pack="center"
|
||||
flex="1">
|
||||
<vbox>
|
||||
<hbox class="devtools-toolbarbutton-group"
|
||||
pack="center">
|
||||
<toolbarbutton class="devtools-toolbarbutton record-button"
|
||||
label="&profilerUI.stopRecording;" />
|
||||
</hbox>
|
||||
<label class="realtime-disabled-message"
|
||||
value="Realtime recording data disabled on non-multiprocess Firefox."/>
|
||||
<label class="realtime-disabled-on-e10s-message"
|
||||
value="Enable multiprocess Firefox in preferences for rendering recording data in realtime."/>
|
||||
<label class="buffer-status-message"
|
||||
tooltiptext="&profilerUI.bufferStatusTooltip;"/>
|
||||
<label class="buffer-status-message-full"
|
||||
value="&profilerUI.bufferStatusFull;"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<hbox id="console-recording-notice"
|
||||
<hbox class="devtools-toolbarbutton-group"
|
||||
pack="center">
|
||||
<toolbarbutton class="devtools-toolbarbutton record-button"
|
||||
label="&profilerUI.stopRecording;"
|
||||
standalone="true"/>
|
||||
</hbox>
|
||||
<label class="realtime-disabled-message">
|
||||
Realtime recording data disabled on non-multiprocess Firefox.
|
||||
</label>
|
||||
<label class="realtime-disabled-on-e10s-message">
|
||||
Enable multiprocess Firefox in preferences for rendering recording data in realtime.
|
||||
</label>
|
||||
<label class="buffer-status-message"
|
||||
tooltiptext="&profilerUI.bufferStatusTooltip;"/>
|
||||
<label class="buffer-status-message-full"
|
||||
value="&profilerUI.bufferStatusFull;"/>
|
||||
</vbox>
|
||||
|
||||
<!-- "Console" notice, shown when a console recording is in progress -->
|
||||
<vbox id="console-recording-notice"
|
||||
class="notice-container"
|
||||
align="center"
|
||||
pack="center"
|
||||
flex="1">
|
||||
<vbox flex="1" align="center">
|
||||
<hbox class="console-profile-recording-notice">
|
||||
<label value="&profilerUI.console.recordingNoticeStart;" />
|
||||
<label class="console-profile-command" />
|
||||
<label value="&profilerUI.console.recordingNoticeEnd;" />
|
||||
</hbox>
|
||||
<hbox class="console-profile-stop-notice">
|
||||
<label value="&profilerUI.console.stopCommandStart;" />
|
||||
<label class="console-profile-command" />
|
||||
<label value="&profilerUI.console.stopCommandEnd;" />
|
||||
</hbox>
|
||||
<label class="realtime-disabled-message"
|
||||
value="Realtime recording data disabled on non-multiprocess Firefox."/>
|
||||
<label class="realtime-disabled-on-e10s-message"
|
||||
value="Enable multiprocess Firefox in preferences for rendering recording data in realtime."/>
|
||||
<label class="buffer-status-message"
|
||||
tooltiptext="&profilerUI.bufferStatusTooltip;"/>
|
||||
<label class="buffer-status-message-full"
|
||||
value="&profilerUI.bufferStatusFull;"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<hbox class="console-profile-recording-notice">
|
||||
<label value="&profilerUI.console.recordingNoticeStart;" />
|
||||
<label class="console-profile-command" />
|
||||
<label value="&profilerUI.console.recordingNoticeEnd;" />
|
||||
</hbox>
|
||||
<hbox class="console-profile-stop-notice">
|
||||
<label value="&profilerUI.console.stopCommandStart;" />
|
||||
<label class="console-profile-command" />
|
||||
<label value="&profilerUI.console.stopCommandEnd;" />
|
||||
</hbox>
|
||||
<label class="realtime-disabled-message">
|
||||
Realtime recording data disabled on non-multiprocess Firefox.
|
||||
</label>
|
||||
<label class="realtime-disabled-on-e10s-message">
|
||||
Enable multiprocess Firefox in preferences for rendering recording data in realtime.
|
||||
</label>
|
||||
<label class="buffer-status-message"
|
||||
tooltiptext="&profilerUI.bufferStatusTooltip;"/>
|
||||
<label class="buffer-status-message-full"
|
||||
value="&profilerUI.bufferStatusFull;"/>
|
||||
</vbox>
|
||||
|
||||
<!-- Detail views -->
|
||||
<deck id="details-pane" flex="1">
|
||||
|
||||
<!-- Waterfall -->
|
||||
<hbox id="waterfall-view" flex="1">
|
||||
<vbox id="waterfall-breakdown" flex="1" />
|
||||
<vbox flex="1">
|
||||
<hbox id="waterfall-header" />
|
||||
<vbox id="waterfall-breakdown" flex="1" />
|
||||
</vbox>
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
<vbox id="waterfall-details"
|
||||
class="theme-sidebar"
|
||||
width="150"
|
||||
height="150"/>
|
||||
class="theme-sidebar"/>
|
||||
</hbox>
|
||||
|
||||
<!-- JS Tree and JIT view -->
|
||||
<hbox id="js-profile-view" flex="1">
|
||||
<vbox id="js-calltree-view" flex="1">
|
||||
<hbox class="call-tree-headers-container">
|
||||
<label class="plain call-tree-header"
|
||||
type="duration"
|
||||
crop="end"
|
||||
value="&profilerUI.table.totalDuration2;"/>
|
||||
value="&profilerUI.table.totalDuration2;"
|
||||
tooltiptext="&profilerUI.table.totalDuration.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="percentage"
|
||||
crop="end"
|
||||
value="&profilerUI.table.totalPercentage;"/>
|
||||
value="&profilerUI.table.totalPercentage;"
|
||||
tooltiptext="&profilerUI.table.totalPercentage.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-duration"
|
||||
crop="end"
|
||||
value="&profilerUI.table.selfDuration2;"/>
|
||||
value="&profilerUI.table.selfDuration2;"
|
||||
tooltiptext="&profilerUI.table.selfDuration.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-percentage"
|
||||
crop="end"
|
||||
value="&profilerUI.table.selfPercentage;"/>
|
||||
value="&profilerUI.table.selfPercentage;"
|
||||
tooltiptext="&profilerUI.table.selfPercentage.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="samples"
|
||||
crop="end"
|
||||
value="&profilerUI.table.samples;"/>
|
||||
value="&profilerUI.table.samples;"
|
||||
tooltiptext="&profilerUI.table.samples.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="function"
|
||||
crop="end"
|
||||
value="&profilerUI.table.function;"/>
|
||||
value="&profilerUI.table.function;"
|
||||
tooltiptext="&profilerUI.table.function.tooltip;"/>
|
||||
</hbox>
|
||||
<vbox class="call-tree-cells-container" flex="1"/>
|
||||
</vbox>
|
||||
|
||||
<splitter id="js-call-tree-splitter" class="devtools-side-splitter"/>
|
||||
|
||||
<vbox id="jit-optimizations-view" hidden="true">
|
||||
<toolbar id="jit-optimizations-toolbar" class="devtools-toolbar">
|
||||
<hbox id="jit-optimizations-header">
|
||||
@@ -263,19 +303,23 @@
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
||||
<!-- JS FlameChart -->
|
||||
<hbox id="js-flamegraph-view" flex="1">
|
||||
</hbox>
|
||||
|
||||
<!-- Memory Tree -->
|
||||
<vbox id="memory-calltree-view" flex="1">
|
||||
<hbox class="call-tree-headers-container">
|
||||
<label class="plain call-tree-header"
|
||||
type="allocations"
|
||||
crop="end"
|
||||
value="&profilerUI.table.totalAlloc;"/>
|
||||
value="&profilerUI.table.totalAlloc1;"
|
||||
tooltiptext="&profilerUI.table.totalAlloc.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-allocations"
|
||||
crop="end"
|
||||
value="&profilerUI.table.selfAlloc;"/>
|
||||
value="&profilerUI.table.selfAlloc1;"
|
||||
tooltiptext="&profilerUI.table.selfAlloc.tooltip;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="function"
|
||||
crop="end"
|
||||
@@ -284,8 +328,10 @@
|
||||
<vbox class="call-tree-cells-container" flex="1"/>
|
||||
</vbox>
|
||||
|
||||
<!-- Memory FlameChart -->
|
||||
<hbox id="memory-flamegraph-view" flex="1">
|
||||
</hbox>
|
||||
|
||||
</deck>
|
||||
</deck>
|
||||
</vbox>
|
||||
|
||||
@@ -21,7 +21,9 @@ function test() {
|
||||
treeRoot.autoExpandDepth = 0;
|
||||
treeRoot.attachTo(container);
|
||||
|
||||
let $$fun = node => container.querySelectorAll(".call-tree-cell[type=function] > " + node);
|
||||
let $$ = node => container.querySelectorAll(node);
|
||||
let $fun = (node, ancestor) => (ancestor || container).querySelector(".call-tree-cell[type=function] > " + node);
|
||||
let $$fun = (node, ancestor) => (ancestor || container).querySelectorAll(".call-tree-cell[type=function] > " + node);
|
||||
let $$dur = i => container.querySelectorAll(".call-tree-cell[type=duration]")[i];
|
||||
let $$perc = i => container.querySelectorAll(".call-tree-cell[type=percentage]")[i];
|
||||
let $$sampl = i => container.querySelectorAll(".call-tree-cell[type=samples]")[i];
|
||||
@@ -39,13 +41,13 @@ function test() {
|
||||
"The root's samples cell displays the correct value.");
|
||||
is($$fun(".call-tree-name")[0].getAttribute("value"), "(root)",
|
||||
"The root's function cell displays the correct name.");
|
||||
is($$fun(".call-tree-url")[0].getAttribute("value"), "",
|
||||
"The root's function cell displays the correct url.");
|
||||
is($$fun(".call-tree-line")[0].getAttribute("value"), "",
|
||||
"The root's function cell displays the correct line.");
|
||||
is($$fun(".call-tree-host")[0].getAttribute("value"), "",
|
||||
is($$fun(".call-tree-url")[0], null,
|
||||
"The root's function cell displays no url.");
|
||||
is($$fun(".call-tree-line")[0], null,
|
||||
"The root's function cell displays no line.");
|
||||
is($$fun(".call-tree-host")[0], null,
|
||||
"The root's function cell displays the correct host.");
|
||||
is($$fun(".call-tree-category")[0].getAttribute("value"), "",
|
||||
is($$fun(".call-tree-category")[0], null,
|
||||
"The root's function cell displays the correct category.");
|
||||
|
||||
treeRoot.expand();
|
||||
@@ -63,17 +65,17 @@ function test() {
|
||||
"The .A node's percentage cell displays the correct value.");
|
||||
is($$sampl(1).getAttribute("value"), "4",
|
||||
"The .A node's samples cell displays the correct value.");
|
||||
is($$fun(".call-tree-name")[1].getAttribute("value"), "A",
|
||||
is($fun(".call-tree-name", $$(".call-tree-item")[1]).getAttribute("value"), "A",
|
||||
"The .A node's function cell displays the correct name.");
|
||||
is($$fun(".call-tree-url")[1].getAttribute("value"), "baz",
|
||||
is($fun(".call-tree-url", $$(".call-tree-item")[1]).getAttribute("value"), "baz",
|
||||
"The .A node's function cell displays the correct url.");
|
||||
ok($$fun(".call-tree-url")[1].getAttribute("tooltiptext").contains("http://foo/bar/baz"),
|
||||
ok($fun(".call-tree-url", $$(".call-tree-item")[1]).getAttribute("tooltiptext").includes("http://foo/bar/baz"),
|
||||
"The .A node's function cell displays the correct url tooltiptext.");
|
||||
is($$fun(".call-tree-line")[1].getAttribute("value"), ":12",
|
||||
is($fun(".call-tree-line", $$(".call-tree-item")[1]).getAttribute("value"), ":12",
|
||||
"The .A node's function cell displays the correct line.");
|
||||
is($$fun(".call-tree-host")[1].getAttribute("value"), "foo",
|
||||
is($fun(".call-tree-host", $$(".call-tree-item")[1]).getAttribute("value"), "foo",
|
||||
"The .A node's function cell displays the correct host.");
|
||||
is($$fun(".call-tree-category")[1].getAttribute("value"), "Gecko",
|
||||
is($fun(".call-tree-category", $$(".call-tree-item")[1]).getAttribute("value"), "Gecko",
|
||||
"The .A node's function cell displays the correct category.");
|
||||
|
||||
let A = treeRoot.getChild();
|
||||
@@ -92,17 +94,17 @@ function test() {
|
||||
"The .A.B node's percentage cell displays the correct value.");
|
||||
is($$sampl(2).getAttribute("value"), "3",
|
||||
"The .A.B node's samples cell displays the correct value.");
|
||||
is($$fun(".call-tree-name")[2].getAttribute("value"), "B",
|
||||
is($fun(".call-tree-name", $$(".call-tree-item")[2]).getAttribute("value"), "B",
|
||||
"The .A.B node's function cell displays the correct name.");
|
||||
is($$fun(".call-tree-url")[2].getAttribute("value"), "baz",
|
||||
is($fun(".call-tree-url", $$(".call-tree-item")[2]).getAttribute("value"), "baz",
|
||||
"The .A.B node's function cell displays the correct url.");
|
||||
ok($$fun(".call-tree-url")[2].getAttribute("tooltiptext").contains("http://foo/bar/baz"),
|
||||
ok($fun(".call-tree-url", $$(".call-tree-item")[2]).getAttribute("tooltiptext").includes("http://foo/bar/baz"),
|
||||
"The .A.B node's function cell displays the correct url tooltiptext.");
|
||||
is($$fun(".call-tree-line")[2].getAttribute("value"), ":34",
|
||||
is($fun(".call-tree-line", $$(".call-tree-item")[2]).getAttribute("value"), ":34",
|
||||
"The .A.B node's function cell displays the correct line.");
|
||||
is($$fun(".call-tree-host")[2].getAttribute("value"), "foo",
|
||||
is($fun(".call-tree-host", $$(".call-tree-item")[2]).getAttribute("value"), "foo",
|
||||
"The .A.B node's function cell displays the correct host.");
|
||||
is($$fun(".call-tree-category")[2].getAttribute("value"), "Styles",
|
||||
is($fun(".call-tree-category", $$(".call-tree-item")[2]).getAttribute("value"), "Styles",
|
||||
"The .A.B node's function cell displays the correct category.");
|
||||
|
||||
is($$dur(3).getAttribute("value"), "7 ms",
|
||||
@@ -111,17 +113,17 @@ function test() {
|
||||
"The .A.E node's percentage cell displays the correct value.");
|
||||
is($$sampl(3).getAttribute("value"), "1",
|
||||
"The .A.E node's samples cell displays the correct value.");
|
||||
is($$fun(".call-tree-name")[3].getAttribute("value"), "E",
|
||||
is($fun(".call-tree-name", $$(".call-tree-item")[3]).getAttribute("value"), "E",
|
||||
"The .A.E node's function cell displays the correct name.");
|
||||
is($$fun(".call-tree-url")[3].getAttribute("value"), "baz",
|
||||
is($fun(".call-tree-url", $$(".call-tree-item")[3]).getAttribute("value"), "baz",
|
||||
"The .A.E node's function cell displays the correct url.");
|
||||
ok($$fun(".call-tree-url")[3].getAttribute("tooltiptext").contains("http://foo/bar/baz"),
|
||||
ok($fun(".call-tree-url", $$(".call-tree-item")[3]).getAttribute("tooltiptext").includes("http://foo/bar/baz"),
|
||||
"The .A.E node's function cell displays the correct url tooltiptext.");
|
||||
is($$fun(".call-tree-line")[3].getAttribute("value"), ":90",
|
||||
is($fun(".call-tree-line", $$(".call-tree-item")[3]).getAttribute("value"), ":90",
|
||||
"The .A.E node's function cell displays the correct line.");
|
||||
is($$fun(".call-tree-host")[3].getAttribute("value"), "foo",
|
||||
is($fun(".call-tree-host", $$(".call-tree-item")[3]).getAttribute("value"), "foo",
|
||||
"The .A.E node's function cell displays the correct host.");
|
||||
is($$fun(".call-tree-category")[3].getAttribute("value"), "GC",
|
||||
is($fun(".call-tree-category", $$(".call-tree-item")[3]).getAttribute("value"), "GC",
|
||||
"The .A.E node's function cell displays the correct category.");
|
||||
|
||||
finish();
|
||||
|
||||
@@ -26,6 +26,8 @@ function test() {
|
||||
"The root node's 'category' attribute is correct.");
|
||||
is(treeRoot.target.getAttribute("tooltiptext"), "",
|
||||
"The root node's 'tooltiptext' attribute is correct.");
|
||||
is(treeRoot.target.querySelector(".call-tree-category"), null,
|
||||
"The root node's category label cell should be hidden.");
|
||||
|
||||
let A = treeRoot.getChild();
|
||||
let B = A.getChild();
|
||||
@@ -35,7 +37,7 @@ function test() {
|
||||
"The .A.B.D node's 'origin' attribute is correct.");
|
||||
is(D.target.getAttribute("category"), "gc",
|
||||
"The .A.B.D node's 'category' attribute is correct.");
|
||||
is(D.target.getAttribute("tooltiptext"), "D (http://foo/bar/baz:78)",
|
||||
is(D.target.getAttribute("tooltiptext"), "D (http://foo/bar/baz:78:1337)",
|
||||
"The .A.B.D node's 'tooltiptext' attribute is correct.");
|
||||
ok(!A.target.querySelector(".call-tree-category").hidden,
|
||||
"The .A.B.D node's category label cell should not be hidden.");
|
||||
@@ -70,7 +72,7 @@ function test() {
|
||||
is(functionCell.childNodes[4].className, "plain call-tree-column",
|
||||
"The fifth node displayed for function cells is correct.");
|
||||
is(functionCell.childNodes[5].className, "plain call-tree-host",
|
||||
"The fifth node displayed for function cells is correct.");
|
||||
"The sixth node displayed for function cells is correct.");
|
||||
is(functionCell.childNodes[6].tagName, "spacer",
|
||||
"The seventh node displayed for function cells is correct.");
|
||||
is(functionCell.childNodes[7].className, "plain call-tree-category",
|
||||
@@ -93,7 +95,7 @@ let gThread = synthesizeProfileForTest([{
|
||||
{ category: CATEGORY_MASK('other'), location: "(root)" },
|
||||
{ category: CATEGORY_MASK('other'), location: "A (http://foo/bar/baz:12)" },
|
||||
{ category: CATEGORY_MASK('css'), location: "B (http://foo/bar/baz:34)" },
|
||||
{ category: CATEGORY_MASK('gc', 1), location: "D (http://foo/bar/baz:78)" }
|
||||
{ category: CATEGORY_MASK('gc', 1), location: "D (http://foo/bar/baz:78:1337)" }
|
||||
]
|
||||
}, {
|
||||
time: 5 + 1 + 2,
|
||||
@@ -101,7 +103,7 @@ let gThread = synthesizeProfileForTest([{
|
||||
{ category: CATEGORY_MASK('other'), location: "(root)" },
|
||||
{ category: CATEGORY_MASK('other'), location: "A (http://foo/bar/baz:12)" },
|
||||
{ category: CATEGORY_MASK('css'), location: "B (http://foo/bar/baz:34)" },
|
||||
{ category: CATEGORY_MASK('gc', 1), location: "D (http://foo/bar/baz:78)" }
|
||||
{ category: CATEGORY_MASK('gc', 1), location: "D (http://foo/bar/baz:78:1337)" }
|
||||
]
|
||||
}, {
|
||||
time: 5 + 1 + 2 + 7,
|
||||
|
||||
@@ -19,13 +19,13 @@ function test() {
|
||||
treeRoot.attachTo(container);
|
||||
|
||||
let categories = container.querySelectorAll(".call-tree-category");
|
||||
is(categories.length, 7,
|
||||
is(categories.length, 5,
|
||||
"The call tree displays a correct number of categories.");
|
||||
ok(!container.hasAttribute("categories-hidden"),
|
||||
"All categories should be visible in the tree.");
|
||||
|
||||
treeRoot.toggleCategories(false);
|
||||
is(categories.length, 7,
|
||||
is(categories.length, 5,
|
||||
"The call tree displays the same number of categories.");
|
||||
ok(container.hasAttribute("categories-hidden"),
|
||||
"All categories should now be hidden in the tree.");
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const MARKER_DETAILS_WIDTH = 300;
|
||||
const MARKER_DETAILS_WIDTH = 200;
|
||||
|
||||
/**
|
||||
* Waterfall view containing the timeline markers, controlled by DetailsView.
|
||||
@@ -26,24 +26,21 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
initialize: function () {
|
||||
DetailsSubview.initialize.call(this);
|
||||
|
||||
// TODO bug 1167093 save the previously set width, and ensure minimum width
|
||||
$("#waterfall-details").setAttribute("width", MARKER_DETAILS_WIDTH);
|
||||
|
||||
this.waterfall = new Waterfall($("#waterfall-breakdown"), $("#waterfall-view"));
|
||||
this.details = new MarkerDetails($("#waterfall-details"), $("#waterfall-view > splitter"));
|
||||
|
||||
this._onMarkerSelected = this._onMarkerSelected.bind(this);
|
||||
this._onResize = this._onResize.bind(this);
|
||||
this._onViewSource = this._onViewSource.bind(this);
|
||||
|
||||
this.waterfall.on("selected", this._onMarkerSelected);
|
||||
this.waterfall.on("unselected", this._onMarkerSelected);
|
||||
this.headerContainer = $("#waterfall-header");
|
||||
this.breakdownContainer = $("#waterfall-breakdown");
|
||||
this.detailsContainer = $("#waterfall-details");
|
||||
this.detailsSplitter = $("#waterfall-view > splitter");
|
||||
|
||||
this.details = new MarkerDetails($("#waterfall-details"), $("#waterfall-view > splitter"));
|
||||
this.details.on("resize", this._onResize);
|
||||
this.details.on("view-source", this._onViewSource);
|
||||
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
this.waterfall.setBlueprint(blueprint);
|
||||
this.waterfall.recalculateBounds();
|
||||
// TODO bug 1167093 save the previously set width, and ensure minimum width
|
||||
this.details.width = MARKER_DETAILS_WIDTH;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -52,8 +49,6 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
destroy: function () {
|
||||
DetailsSubview.destroy.call(this);
|
||||
|
||||
this.waterfall.off("selected", this._onMarkerSelected);
|
||||
this.waterfall.off("unselected", this._onMarkerSelected);
|
||||
this.details.off("resize", this._onResize);
|
||||
this.details.off("view-source", this._onViewSource);
|
||||
},
|
||||
@@ -69,7 +64,9 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
let startTime = interval.startTime || 0;
|
||||
let endTime = interval.endTime || recording.getDuration();
|
||||
let markers = recording.getMarkers();
|
||||
this.waterfall.setData({ markers, interval: { startTime, endTime } });
|
||||
let rootMarkerNode = this._prepareWaterfallTree(markers);
|
||||
|
||||
this._populateWaterfallTree(rootMarkerNode, { startTime, endTime });
|
||||
this.emit(EVENTS.WATERFALL_RENDERED);
|
||||
},
|
||||
|
||||
@@ -79,11 +76,6 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
*/
|
||||
_onMarkerSelected: function (event, marker) {
|
||||
let recording = PerformanceController.getCurrentRecording();
|
||||
// Race condition in tests due to lazy rendering of markers in the
|
||||
// waterfall? intermittent bug 1157523
|
||||
if (!recording) {
|
||||
return;
|
||||
}
|
||||
let frames = recording.getFrames();
|
||||
|
||||
if (event === "selected") {
|
||||
@@ -98,7 +90,7 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
* Called when the marker details view is resized.
|
||||
*/
|
||||
_onResize: function () {
|
||||
this.waterfall.recalculateBounds();
|
||||
this._markersRoot.recalculateBounds();
|
||||
this.render();
|
||||
},
|
||||
|
||||
@@ -107,7 +99,7 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
*/
|
||||
_onObservedPrefChange: function(_, prefName) {
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
this.waterfall.setBlueprint(blueprint);
|
||||
this._markersRoot.blueprint = blueprint;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -117,5 +109,50 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
gToolbox.viewSourceInDebugger(file, line);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the recording is stopped and prepares data to
|
||||
* populate the waterfall tree.
|
||||
*/
|
||||
_prepareWaterfallTree: function(markers) {
|
||||
let rootMarkerNode = WaterfallUtils.makeEmptyMarkerNode("(root)");
|
||||
|
||||
WaterfallUtils.collapseMarkersIntoNode({
|
||||
markerNode: rootMarkerNode,
|
||||
markersList: markers
|
||||
});
|
||||
|
||||
return rootMarkerNode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the waterfall tree.
|
||||
*/
|
||||
_populateWaterfallTree: function(rootMarkerNode, interval) {
|
||||
let root = new MarkerView({
|
||||
marker: rootMarkerNode,
|
||||
// The root node is irrelevant in a waterfall tree.
|
||||
hidden: true,
|
||||
// The waterfall tree should not expand by default.
|
||||
autoExpandDepth: 0
|
||||
});
|
||||
|
||||
let header = new WaterfallHeader(root);
|
||||
|
||||
this._markersRoot = root;
|
||||
this._waterfallHeader = header;
|
||||
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
root.blueprint = blueprint;
|
||||
root.interval = interval;
|
||||
root.on("selected", this._onMarkerSelected);
|
||||
root.on("unselected", this._onMarkerSelected);
|
||||
|
||||
this.breakdownContainer.innerHTML = "";
|
||||
root.attachTo(this.breakdownContainer);
|
||||
|
||||
this.headerContainer.innerHTML = "";
|
||||
header.attachTo(this.headerContainer);
|
||||
},
|
||||
|
||||
toString: () => "[object WaterfallView]"
|
||||
});
|
||||
|
||||
@@ -33,8 +33,8 @@ XPCOMUtils.defineLazyGetter(this, "events", () => {
|
||||
});
|
||||
|
||||
for (let name of ["WebConsoleUtils", "ConsoleServiceListener",
|
||||
"ConsoleAPIListener", "JSTermHelpers", "JSPropertyProvider",
|
||||
"ConsoleReflowListener"]) {
|
||||
"ConsoleAPIListener", "addWebConsoleCommands", "JSPropertyProvider",
|
||||
"ConsoleReflowListener", "CONSOLE_WORKER_IDS"]) {
|
||||
Object.defineProperty(this, name, {
|
||||
get: function(prop) {
|
||||
if (prop == "WebConsoleUtils") {
|
||||
@@ -297,11 +297,11 @@ WebConsoleActor.prototype =
|
||||
consoleReflowListener: null,
|
||||
|
||||
/**
|
||||
* The JSTerm Helpers names cache.
|
||||
* The Web Console Commands names cache.
|
||||
* @private
|
||||
* @type array
|
||||
*/
|
||||
_jstermHelpersCache: null,
|
||||
_webConsoleCommandsCache: null,
|
||||
|
||||
actorPrefix: "console",
|
||||
|
||||
@@ -362,7 +362,7 @@ WebConsoleActor.prototype =
|
||||
}
|
||||
this._actorPool = null;
|
||||
|
||||
this._jstermHelpersCache = null;
|
||||
this._webConsoleCommandsCache = null;
|
||||
this._lastConsoleInputEvaluation = null;
|
||||
this._evalWindow = null;
|
||||
this._netEvents.clear();
|
||||
@@ -750,8 +750,6 @@ WebConsoleActor.prototype =
|
||||
}
|
||||
}
|
||||
|
||||
messages.sort(function(a, b) { return a.timeStamp - b.timeStamp; });
|
||||
|
||||
return {
|
||||
from: this.actorID,
|
||||
messages: messages,
|
||||
@@ -897,14 +895,16 @@ WebConsoleActor.prototype =
|
||||
// helper functions.
|
||||
let lastNonAlphaIsDot = /[.][a-zA-Z0-9$]*$/.test(reqText);
|
||||
if (!lastNonAlphaIsDot) {
|
||||
if (!this._jstermHelpersCache) {
|
||||
if (!this._webConsoleCommandsCache) {
|
||||
let helpers = {
|
||||
sandbox: Object.create(null)
|
||||
};
|
||||
JSTermHelpers(helpers);
|
||||
this._jstermHelpersCache = Object.getOwnPropertyNames(helpers.sandbox);
|
||||
addWebConsoleCommands(helpers);
|
||||
this._webConsoleCommandsCache =
|
||||
Object.getOwnPropertyNames(helpers.sandbox);
|
||||
}
|
||||
matches = matches.concat(this._jstermHelpersCache.filter(n => n.startsWith(result.matchProp)));
|
||||
matches = matches.concat(this._webConsoleCommandsCache
|
||||
.filter(n => n.startsWith(result.matchProp)));
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -926,6 +926,10 @@ WebConsoleActor.prototype =
|
||||
.getService(Ci.nsIConsoleAPIStorage);
|
||||
ConsoleAPIStorage.clearEvents(windowId);
|
||||
|
||||
CONSOLE_WORKER_IDS.forEach((aId) => {
|
||||
ConsoleAPIStorage.clearEvents(aId);
|
||||
});
|
||||
|
||||
if (this.parentActor.isRootActor) {
|
||||
Services.console.logStringMessage(null); // for the Error Console
|
||||
Services.console.reset();
|
||||
@@ -981,13 +985,13 @@ WebConsoleActor.prototype =
|
||||
* @private
|
||||
* @param object aDebuggerGlobal
|
||||
* A Debugger.Object that wraps a content global. This is used for the
|
||||
* JSTerm helpers.
|
||||
* Web Console Commands.
|
||||
* @return object
|
||||
* The same object as |this|, but with an added |sandbox| property.
|
||||
* The sandbox holds methods and properties that can be used as
|
||||
* bindings during JS evaluation.
|
||||
*/
|
||||
_getJSTermHelpers: function WCA__getJSTermHelpers(aDebuggerGlobal)
|
||||
_getWebConsoleCommands: function(aDebuggerGlobal)
|
||||
{
|
||||
let helpers = {
|
||||
window: this.evalWindow,
|
||||
@@ -998,7 +1002,7 @@ WebConsoleActor.prototype =
|
||||
helperResult: null,
|
||||
consoleActor: this,
|
||||
};
|
||||
JSTermHelpers(helpers);
|
||||
addWebConsoleCommands(helpers);
|
||||
|
||||
let evalWindow = this.evalWindow;
|
||||
function maybeExport(obj, name) {
|
||||
@@ -1052,9 +1056,9 @@ WebConsoleActor.prototype =
|
||||
*
|
||||
* The Debugger.Frame comes from the jsdebugger's Debugger instance, which
|
||||
* is different from the Web Console's Debugger instance. This means that
|
||||
* for evaluation to work, we need to create a new instance for the jsterm
|
||||
* helpers - they need to be Debugger.Objects coming from the jsdebugger's
|
||||
* Debugger instance.
|
||||
* for evaluation to work, we need to create a new instance for the Web
|
||||
* Console Commands helpers - they need to be Debugger.Objects coming from the
|
||||
* jsdebugger's Debugger instance.
|
||||
*
|
||||
* When |bindObjectActor| is used objects can come from different iframes,
|
||||
* from different domains. To avoid permission-related errors when objects
|
||||
@@ -1084,7 +1088,8 @@ WebConsoleActor.prototype =
|
||||
* - window: the Debugger.Object for the global where the string was
|
||||
* evaluated.
|
||||
* - result: the result of the evaluation.
|
||||
* - helperResult: any result coming from a JSTerm helper function.
|
||||
* - helperResult: any result coming from a Web Console commands
|
||||
* function.
|
||||
* - url: the url to evaluate the script as. Defaults to
|
||||
* "debugger eval code".
|
||||
*/
|
||||
@@ -1140,8 +1145,8 @@ WebConsoleActor.prototype =
|
||||
}
|
||||
}
|
||||
|
||||
// Get the JSTerm helpers for the given debugger window.
|
||||
let helpers = this._getJSTermHelpers(dbgWindow);
|
||||
// Get the Web Console commands for the given debugger window.
|
||||
let helpers = this._getWebConsoleCommands(dbgWindow);
|
||||
let bindings = helpers.sandbox;
|
||||
if (bindSelf) {
|
||||
bindings._self = bindSelf;
|
||||
@@ -1155,7 +1160,8 @@ WebConsoleActor.prototype =
|
||||
}
|
||||
|
||||
// Check if the Debugger.Frame or Debugger.Object for the global include
|
||||
// $ or $$. We will not overwrite these functions with the jsterm helpers.
|
||||
// $ or $$. We will not overwrite these functions with the Web Console
|
||||
// commands.
|
||||
let found$ = false, found$$ = false;
|
||||
if (frame) {
|
||||
let env = frame.environment;
|
||||
@@ -1443,6 +1449,10 @@ WebConsoleActor.prototype =
|
||||
function WCA_prepareConsoleMessageForRemote(aMessage)
|
||||
{
|
||||
let result = WebConsoleUtils.cloneObject(aMessage);
|
||||
|
||||
result.workerType = CONSOLE_WORKER_IDS.indexOf(result.innerID) == -1
|
||||
? 'none' : result.innerID;
|
||||
|
||||
delete result.wrappedJSObject;
|
||||
delete result.ID;
|
||||
delete result.innerID;
|
||||
@@ -1609,6 +1619,7 @@ NetworkEventActor.prototype =
|
||||
return {
|
||||
actor: this.actorID,
|
||||
startedDateTime: this._startedDateTime,
|
||||
timeStamp: Date.parse(this._startedDateTime),
|
||||
url: this._request.url,
|
||||
method: this._request.method,
|
||||
isXHR: this._isXHR,
|
||||
|
||||
@@ -5,12 +5,16 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/event-emitter.js");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/devtools/Console.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
|
||||
"resource://gre/modules/devtools/event-emitter.js");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["AbstractTreeItem"];
|
||||
|
||||
@@ -117,13 +121,12 @@ function AbstractTreeItem({ parent, level }) {
|
||||
this._parentItem = parent;
|
||||
this._level = level || 0;
|
||||
this._childTreeItems = [];
|
||||
this._onArrowClick = this._onArrowClick.bind(this);
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onDoubleClick = this._onDoubleClick.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
this._onFocus = this._onFocus.bind(this);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
// Events are always propagated through the root item. Decorating every
|
||||
// tree item as an event emitter is a very costly operation.
|
||||
if (this == this._rootItem) {
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
}
|
||||
|
||||
AbstractTreeItem.prototype = {
|
||||
@@ -150,7 +153,8 @@ AbstractTreeItem.prototype = {
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
_displaySelf: function(document, arrowNode) {
|
||||
throw "This method needs to be implemented by inheriting classes.";
|
||||
throw new Error(
|
||||
"The `_displaySelf` method needs to be implemented by inheriting classes.");
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -162,7 +166,16 @@ AbstractTreeItem.prototype = {
|
||||
* @param array:AbstractTreeItem children
|
||||
*/
|
||||
_populateSelf: function(children) {
|
||||
throw "This method needs to be implemented by inheriting classes.";
|
||||
throw new Error(
|
||||
"The `_populateSelf` method needs to be implemented by inheriting classes.");
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the this tree's owner document.
|
||||
* @return Document
|
||||
*/
|
||||
get document() {
|
||||
return this._containerNode.ownerDocument;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -221,18 +234,36 @@ AbstractTreeItem.prototype = {
|
||||
return this._expanded;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the bounds for this tree's container without flushing.
|
||||
* @return object
|
||||
*/
|
||||
get bounds() {
|
||||
let win = this.document.defaultView;
|
||||
let utils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
return utils.getBoundsWithoutFlushing(this._containerNode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates and appends this tree item to the specified parent element.
|
||||
*
|
||||
* @param nsIDOMNode containerNode
|
||||
* The parent element for this tree item (and every other tree item).
|
||||
* @param nsIDOMNode beforeNode
|
||||
* The child element which should succeed this tree item.
|
||||
* @param nsIDOMNode fragmentNode [optional]
|
||||
* An optional document fragment temporarily holding this tree item in
|
||||
* the current batch. Defaults to the `containerNode`.
|
||||
* @param nsIDOMNode beforeNode [optional]
|
||||
* An optional child element which should succeed this tree item.
|
||||
*/
|
||||
attachTo: function(containerNode, beforeNode = null) {
|
||||
attachTo: function(containerNode, fragmentNode = containerNode, beforeNode = null) {
|
||||
this._containerNode = containerNode;
|
||||
this._constructTargetNode();
|
||||
containerNode.insertBefore(this._targetNode, beforeNode);
|
||||
|
||||
if (beforeNode) {
|
||||
fragmentNode.insertBefore(this._targetNode, beforeNode);
|
||||
} else {
|
||||
fragmentNode.appendChild(this._targetNode);
|
||||
}
|
||||
|
||||
if (this._level < this.autoExpandDepth) {
|
||||
this.expand();
|
||||
@@ -265,6 +296,7 @@ AbstractTreeItem.prototype = {
|
||||
}
|
||||
this._expanded = true;
|
||||
this._arrowNode.setAttribute("open", "");
|
||||
this._targetNode.setAttribute("expanded", "");
|
||||
this._toggleChildren(true);
|
||||
this._rootItem.emit("expand", this);
|
||||
},
|
||||
@@ -278,6 +310,7 @@ AbstractTreeItem.prototype = {
|
||||
}
|
||||
this._expanded = false;
|
||||
this._arrowNode.removeAttribute("open");
|
||||
this._targetNode.removeAttribute("expanded", "");
|
||||
this._toggleChildren(false);
|
||||
this._rootItem.emit("collapse", this);
|
||||
},
|
||||
@@ -315,17 +348,16 @@ AbstractTreeItem.prototype = {
|
||||
* Shows all children of this item in the tree.
|
||||
*/
|
||||
_showChildren: function() {
|
||||
let childTreeItems = this._childTreeItems;
|
||||
let expandedChildTreeItems = childTreeItems.filter(e => e._expanded);
|
||||
let nextNode = this._getSiblingAtDelta(1);
|
||||
|
||||
// First append the child items, and afterwards append any descendants.
|
||||
// Otherwise, the tree will become garbled and nodes will intertwine.
|
||||
for (let item of childTreeItems) {
|
||||
item.attachTo(this._containerNode, nextNode);
|
||||
// If this is the root item and we're not expanding any child nodes,
|
||||
// it is safe to append everything at once.
|
||||
if (this == this._rootItem && this.autoExpandDepth == 0) {
|
||||
this._appendChildrenBatch();
|
||||
}
|
||||
for (let item of expandedChildTreeItems) {
|
||||
item._showChildren();
|
||||
// Otherwise, append the child items and their descendants successively;
|
||||
// if not, the tree will become garbled and nodes will intertwine,
|
||||
// since all the tree items are sharing a single container node.
|
||||
else {
|
||||
this._appendChildrenSuccessive();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -339,6 +371,40 @@ AbstractTreeItem.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Appends all children in a single batch.
|
||||
* This only works properly for root nodes when no child nodes will expand.
|
||||
*/
|
||||
_appendChildrenBatch: function() {
|
||||
if (this._fragment === undefined) {
|
||||
this._fragment = this.document.createDocumentFragment();
|
||||
}
|
||||
|
||||
let childTreeItems = this._childTreeItems;
|
||||
|
||||
for (let i = 0, len = childTreeItems.length; i < len; i++) {
|
||||
childTreeItems[i].attachTo(this._containerNode, this._fragment);
|
||||
}
|
||||
|
||||
this._containerNode.appendChild(this._fragment);
|
||||
},
|
||||
|
||||
/**
|
||||
* Appends all children successively.
|
||||
*/
|
||||
_appendChildrenSuccessive: function() {
|
||||
let childTreeItems = this._childTreeItems;
|
||||
let expandedChildTreeItems = childTreeItems.filter(e => e._expanded);
|
||||
let nextNode = this._getSiblingAtDelta(1);
|
||||
|
||||
for (let i = 0, len = childTreeItems.length; i < len; i++) {
|
||||
childTreeItems[i].attachTo(this._containerNode, undefined, nextNode);
|
||||
}
|
||||
for (let i = 0, len = expandedChildTreeItems.length; i < len; i++) {
|
||||
expandedChildTreeItems[i]._showChildren();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Constructs and stores the target node displaying this tree item.
|
||||
*/
|
||||
@@ -346,7 +412,14 @@ AbstractTreeItem.prototype = {
|
||||
if (this._constructed) {
|
||||
return;
|
||||
}
|
||||
let document = this._containerNode.ownerDocument;
|
||||
this._onArrowClick = this._onArrowClick.bind(this);
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onDoubleClick = this._onDoubleClick.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
this._onFocus = this._onFocus.bind(this);
|
||||
this._onBlur = this._onBlur.bind(this);
|
||||
|
||||
let document = this.document;
|
||||
|
||||
let arrowNode = this._arrowNode = document.createElement("hbox");
|
||||
arrowNode.className = "arrow theme-twisty";
|
||||
@@ -359,6 +432,7 @@ AbstractTreeItem.prototype = {
|
||||
targetNode.addEventListener("dblclick", this._onDoubleClick);
|
||||
targetNode.addEventListener("keypress", this._onKeyPress);
|
||||
targetNode.addEventListener("focus", this._onFocus);
|
||||
targetNode.addEventListener("blur", this._onBlur);
|
||||
|
||||
this._constructed = true;
|
||||
},
|
||||
@@ -434,7 +508,6 @@ AbstractTreeItem.prototype = {
|
||||
if (!e.target.classList.contains("arrow")) {
|
||||
this._onArrowClick(e);
|
||||
}
|
||||
|
||||
this.focus();
|
||||
},
|
||||
|
||||
@@ -477,5 +550,12 @@ AbstractTreeItem.prototype = {
|
||||
*/
|
||||
_onFocus: function(e) {
|
||||
this._rootItem.emit("focus", this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "blur" event on the element displaying this tree item.
|
||||
*/
|
||||
_onBlur: function(e) {
|
||||
this._rootItem.emit("blur", this);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1991,9 +1991,14 @@ AbstractCanvasGraph.createIframe = function(url, parent, callback) {
|
||||
callback(iframe);
|
||||
});
|
||||
|
||||
// Setting 100% width on the frame and flex on the parent allows the graph
|
||||
// to properly shrink when the window is resized to be smaller.
|
||||
iframe.setAttribute("frameborder", "0");
|
||||
iframe.style.width = "100%";
|
||||
iframe.style.minWidth = "50px";
|
||||
iframe.src = url;
|
||||
|
||||
parent.style.display = "flex";
|
||||
parent.appendChild(iframe);
|
||||
};
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
background-position: -48px center;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#eyedropper-button {
|
||||
background-image: url("chrome://global/skin/devtools/command-eyedropper@2x.png");
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.breakpoint {
|
||||
background-image: url("chrome://global/skin/devtools/editor-breakpoint@2x.png");
|
||||
}
|
||||
@@ -56,7 +56,7 @@
|
||||
background-image: url("chrome://global/skin/devtools/editor-debug-location.png");
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.debugLocation {
|
||||
background-image: url("chrome://global/skin/devtools/editor-debug-location@2x.png");
|
||||
}
|
||||
@@ -68,7 +68,7 @@
|
||||
url("chrome://global/skin/devtools/editor-breakpoint.png");
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.breakpoint.debugLocation {
|
||||
background-image:
|
||||
url("chrome://global/skin/devtools/editor-debug-location@2x.png"),
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
const {Cc, Ci, Cu} = require("chrome");
|
||||
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
|
||||
loader.lazyImporter(this, "LongStringClient", "resource://gre/modules/devtools/dbg-client.jsm");
|
||||
|
||||
@@ -28,11 +29,17 @@ function WebConsoleClient(aDebuggerClient, aResponse)
|
||||
this._longStrings = {};
|
||||
this.traits = aResponse.traits || {};
|
||||
this.events = [];
|
||||
this._networkRequests = new Map();
|
||||
|
||||
this.pendingEvaluationResults = new Map();
|
||||
this.onEvaluationResult = this.onEvaluationResult.bind(this);
|
||||
this.onNetworkEvent = this._onNetworkEvent.bind(this);
|
||||
this.onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
|
||||
|
||||
this._client.addListener("evaluationResult", this.onEvaluationResult);
|
||||
this._client.addListener("networkEvent", this.onNetworkEvent);
|
||||
this._client.addListener("networkEventUpdate", this.onNetworkEventUpdate);
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
exports.WebConsoleClient = WebConsoleClient;
|
||||
@@ -41,24 +48,145 @@ WebConsoleClient.prototype = {
|
||||
_longStrings: null,
|
||||
traits: null,
|
||||
|
||||
/**
|
||||
* Holds the network requests currently displayed by the Web Console. Each key
|
||||
* represents the connection ID and the value is network request information.
|
||||
* @private
|
||||
* @type object
|
||||
*/
|
||||
_networkRequests: null,
|
||||
|
||||
getNetworkRequest(actorId) {
|
||||
return this._networkRequests.get(actorId);
|
||||
},
|
||||
|
||||
hasNetworkRequest(actorId) {
|
||||
return this._networkRequests.has(actorId);
|
||||
},
|
||||
|
||||
removeNetworkRequest(actorId) {
|
||||
this._networkRequests.delete(actorId);
|
||||
},
|
||||
|
||||
getNetworkEvents() {
|
||||
return this._networkRequests.values();
|
||||
},
|
||||
|
||||
get actor() { return this._actor; },
|
||||
|
||||
/**
|
||||
* The "networkEvent" message type handler. We redirect any message to
|
||||
* the UI for displaying.
|
||||
*
|
||||
* @private
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object packet
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onNetworkEvent: function (type, packet)
|
||||
{
|
||||
if (packet.from == this._actor) {
|
||||
let actor = packet.eventActor;
|
||||
let networkInfo = {
|
||||
_type: "NetworkEvent",
|
||||
timeStamp: actor.timeStamp,
|
||||
node: null,
|
||||
actor: actor.actor,
|
||||
discardRequestBody: true,
|
||||
discardResponseBody: true,
|
||||
startedDateTime: actor.startedDateTime,
|
||||
request: {
|
||||
url: actor.url,
|
||||
method: actor.method,
|
||||
},
|
||||
isXHR: actor.isXHR,
|
||||
response: {},
|
||||
timings: {},
|
||||
updates: [], // track the list of network event updates
|
||||
private: actor.private,
|
||||
fromCache: actor.fromCache
|
||||
};
|
||||
this._networkRequests.set(actor.actor, networkInfo);
|
||||
|
||||
this.emit("networkEvent", networkInfo);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The "networkEventUpdate" message type handler. We redirect any message to
|
||||
* the UI for displaying.
|
||||
*
|
||||
* @private
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object packet
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onNetworkEventUpdate: function (type, packet)
|
||||
{
|
||||
let networkInfo = this.getNetworkRequest(packet.from);
|
||||
if (!networkInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
networkInfo.updates.push(packet.updateType);
|
||||
|
||||
switch (packet.updateType) {
|
||||
case "requestHeaders":
|
||||
networkInfo.request.headersSize = packet.headersSize;
|
||||
break;
|
||||
case "requestPostData":
|
||||
networkInfo.discardRequestBody = packet.discardRequestBody;
|
||||
networkInfo.request.bodySize = packet.dataSize;
|
||||
break;
|
||||
case "responseStart":
|
||||
networkInfo.response.httpVersion = packet.response.httpVersion;
|
||||
networkInfo.response.status = packet.response.status;
|
||||
networkInfo.response.statusText = packet.response.statusText;
|
||||
networkInfo.response.headersSize = packet.response.headersSize;
|
||||
networkInfo.response.remoteAddress = packet.response.remoteAddress;
|
||||
networkInfo.response.remotePort = packet.response.remotePort;
|
||||
networkInfo.discardResponseBody = packet.response.discardResponseBody;
|
||||
break;
|
||||
case "responseContent":
|
||||
networkInfo.response.content = {
|
||||
mimeType: packet.mimeType,
|
||||
};
|
||||
networkInfo.response.bodySize = packet.contentSize;
|
||||
networkInfo.response.transferredSize = packet.transferredSize;
|
||||
networkInfo.discardResponseBody = packet.discardResponseBody;
|
||||
break;
|
||||
case "eventTimings":
|
||||
networkInfo.totalTime = packet.totalTime;
|
||||
break;
|
||||
case "securityInfo":
|
||||
networkInfo.securityInfo = packet.state;
|
||||
break;
|
||||
}
|
||||
|
||||
this.emit("networkEventUpdate", {
|
||||
packet: packet,
|
||||
networkInfo
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the cached messages from the server.
|
||||
*
|
||||
* @see this.CACHED_MESSAGES
|
||||
* @param array aTypes
|
||||
* @param array types
|
||||
* The array of message types you want from the server. See
|
||||
* this.CACHED_MESSAGES for known types.
|
||||
* @param function aOnResponse
|
||||
* The function invoked when the response is received.
|
||||
*/
|
||||
getCachedMessages: function WCC_getCachedMessages(aTypes, aOnResponse)
|
||||
getCachedMessages: function WCC_getCachedMessages(types, aOnResponse)
|
||||
{
|
||||
let packet = {
|
||||
to: this._actor,
|
||||
type: "getCachedMessages",
|
||||
messageTypes: aTypes,
|
||||
messageTypes: types,
|
||||
};
|
||||
this._client.request(packet, aOnResponse);
|
||||
},
|
||||
@@ -154,7 +282,11 @@ WebConsoleClient.prototype = {
|
||||
};
|
||||
|
||||
this._client.request(packet, response => {
|
||||
this.pendingEvaluationResults.set(response.resultID, aOnResponse);
|
||||
// Null check this in case the client has been detached while waiting
|
||||
// for a response.
|
||||
if (this.pendingEvaluationResults) {
|
||||
this.pendingEvaluationResults.set(response.resultID, aOnResponse);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -469,10 +601,18 @@ WebConsoleClient.prototype = {
|
||||
detach: function WCC_detach(aOnResponse)
|
||||
{
|
||||
this._client.removeListener("evaluationResult", this.onEvaluationResult);
|
||||
this._client.removeListener("networkEvent", this.onNetworkEvent);
|
||||
this._client.removeListener("networkEventUpdate", this.onNetworkEventUpdate);
|
||||
this.stopListeners(null, aOnResponse);
|
||||
this._longStrings = null;
|
||||
this._client = null;
|
||||
this.pendingEvaluationResults.clear();
|
||||
this.pendingEvaluationResults = null;
|
||||
this.clearNetworkRequests();
|
||||
this._networkRequests = null;
|
||||
},
|
||||
|
||||
clearNetworkRequests: function () {
|
||||
this._networkRequests.clear();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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/. */
|
||||
|
||||
// Test that console command input is persisted across toolbox loads.
|
||||
// See Bug 943306.
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,Web Console test for persisting history - bug 943306";
|
||||
const INPUT_HISTORY_COUNT = 10;
|
||||
|
||||
let test = asyncTest(function* () {
|
||||
info ("Setting custom input history pref to " + INPUT_HISTORY_COUNT);
|
||||
Services.prefs.setIntPref("devtools.webconsole.inputHistoryCount", INPUT_HISTORY_COUNT);
|
||||
|
||||
// First tab: run a bunch of commands and then make sure that you can
|
||||
// navigate through their history.
|
||||
yield loadTab(TEST_URI);
|
||||
let hud1 = yield openConsole();
|
||||
is (JSON.stringify(hud1.jsterm.history), "[]", "No history on first tab initially");
|
||||
yield populateInputHistory(hud1);
|
||||
is (JSON.stringify(hud1.jsterm.history), '["0","1","2","3","4","5","6","7","8","9"]',
|
||||
"First tab has populated history");
|
||||
|
||||
// Second tab: Just make sure that you can navigate through the history
|
||||
// generated by the first tab.
|
||||
yield loadTab(TEST_URI);
|
||||
let hud2 = yield openConsole();
|
||||
is (JSON.stringify(hud2.jsterm.history), '["0","1","2","3","4","5","6","7","8","9"]',
|
||||
"Second tab has populated history");
|
||||
yield testNaviatingHistoryInUI(hud2);
|
||||
is (JSON.stringify(hud2.jsterm.history), '["0","1","2","3","4","5","6","7","8","9",""]',
|
||||
"An empty entry has been added in the second tab due to history perusal");
|
||||
|
||||
// Third tab: Should have the same history as first tab, but if we run a
|
||||
// command, then the history of the first and second shouldn't be affected
|
||||
yield loadTab(TEST_URI);
|
||||
let hud3 = yield openConsole();
|
||||
is (JSON.stringify(hud3.jsterm.history), '["0","1","2","3","4","5","6","7","8","9"]',
|
||||
"Third tab has populated history");
|
||||
|
||||
// Set input value separately from execute so UP arrow accurately navigates history.
|
||||
hud3.jsterm.setInputValue('"hello from third tab"');
|
||||
hud3.jsterm.execute();
|
||||
|
||||
is (JSON.stringify(hud1.jsterm.history), '["0","1","2","3","4","5","6","7","8","9"]',
|
||||
"First tab history hasn't changed due to command in third tab");
|
||||
is (JSON.stringify(hud2.jsterm.history), '["0","1","2","3","4","5","6","7","8","9",""]',
|
||||
"Second tab history hasn't changed due to command in third tab");
|
||||
is (JSON.stringify(hud3.jsterm.history), '["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
|
||||
"Third tab has updated history (and purged the first result) after running a command");
|
||||
|
||||
// Fourth tab: Should have the latest command from the third tab, followed
|
||||
// by the rest of the history from the first tab.
|
||||
yield loadTab(TEST_URI);
|
||||
let hud4 = yield openConsole();
|
||||
is (JSON.stringify(hud4.jsterm.history), '["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
|
||||
"Fourth tab has most recent history");
|
||||
|
||||
yield hud4.jsterm.clearHistory();
|
||||
is (JSON.stringify(hud4.jsterm.history), '[]',
|
||||
"Clearing history for a tab works");
|
||||
|
||||
yield loadTab(TEST_URI);
|
||||
let hud5 = yield openConsole();
|
||||
is (JSON.stringify(hud5.jsterm.history), '[]',
|
||||
"Clearing history carries over to a new tab");
|
||||
|
||||
info ("Clearing custom input history pref");
|
||||
Services.prefs.clearUserPref("devtools.webconsole.inputHistoryCount");
|
||||
});
|
||||
|
||||
/**
|
||||
* Populate the history by running the following commands:
|
||||
* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
*/
|
||||
function* populateInputHistory(hud) {
|
||||
let jsterm = hud.jsterm;
|
||||
let {inputNode} = jsterm;
|
||||
|
||||
for (let i = 0; i < INPUT_HISTORY_COUNT; i++) {
|
||||
// Set input value separately from execute so UP arrow accurately navigates history.
|
||||
jsterm.setInputValue(i);
|
||||
jsterm.execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check pressing up results in history traversal like:
|
||||
* [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
|
||||
*/
|
||||
function* testNaviatingHistoryInUI(hud) {
|
||||
let jsterm = hud.jsterm;
|
||||
let {inputNode} = jsterm;
|
||||
inputNode.focus();
|
||||
|
||||
// Count backwards from original input and make sure that pressing up
|
||||
// restores this.
|
||||
for (let i = INPUT_HISTORY_COUNT - 1; i >= 0; i--) {
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(inputNode.value, i, "Pressing up restores last input");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf8,Test that the netmonitor " +
|
||||
"displays requests that have been recorded in the " +
|
||||
"web console, even if the netmonitor hadn't opened yet.";
|
||||
|
||||
const TEST_FILE = "test-network-request.html";
|
||||
const TEST_PATH = "http://example.com/browser/toolkit/devtools/webconsole/test/" +
|
||||
TEST_FILE;
|
||||
|
||||
const NET_PREF = "devtools.webconsole.filter.networkinfo";
|
||||
Services.prefs.setBoolPref(NET_PREF, true);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(NET_PREF);
|
||||
});
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, browser } = yield loadTab(TEST_URI);
|
||||
|
||||
// Test that the request appears in the console.
|
||||
let hud = yield openConsole();
|
||||
info("Web console is open");
|
||||
|
||||
yield loadDocument(browser);
|
||||
info("Document loaded.");
|
||||
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
name: "network message",
|
||||
text: TEST_FILE,
|
||||
category: CATEGORY_NETWORK,
|
||||
severity: SEVERITY_LOG
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Test that the request appears in the network panel.
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
|
||||
info("Network panel is open.");
|
||||
|
||||
testNetmonitor(toolbox);
|
||||
});
|
||||
|
||||
|
||||
function loadDocument(browser) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
deferred.resolve();
|
||||
}, true);
|
||||
content.location = TEST_PATH;
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testNetmonitor(toolbox) {
|
||||
let monitor = toolbox.getCurrentPanel();
|
||||
let { RequestsMenu } = monitor.panelWin.NetMonitorView;
|
||||
is(RequestsMenu.itemCount, 1, "Network request appears in the network panel");
|
||||
|
||||
let item = RequestsMenu.getItemAtIndex(0);
|
||||
is(item.attachment.method, "GET", "The attached method is correct.");
|
||||
is(item.attachment.url, TEST_PATH, "The attached url is correct.");
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const prefs = {
|
||||
"net": [
|
||||
"network",
|
||||
"netwarn",
|
||||
"netxhr",
|
||||
"networkinfo"
|
||||
],
|
||||
"css": [
|
||||
"csserror",
|
||||
"cssparser",
|
||||
"csslog"
|
||||
],
|
||||
"js": [
|
||||
"exception",
|
||||
"jswarn",
|
||||
"jslog",
|
||||
],
|
||||
"logging": [
|
||||
"error",
|
||||
"warn",
|
||||
"info",
|
||||
"log",
|
||||
"serviceworkers",
|
||||
"sharedworkers",
|
||||
"windowlessworkers"
|
||||
]
|
||||
};
|
||||
|
||||
let test = asyncTest(function* () {
|
||||
// Set all prefs to true
|
||||
for (let category in prefs) {
|
||||
prefs[category].forEach(function(pref) {
|
||||
Services.prefs.setBoolPref("devtools.webconsole.filter." + pref, true);
|
||||
});
|
||||
}
|
||||
|
||||
yield loadTab("about:blank");
|
||||
|
||||
let hud = yield openConsole();
|
||||
|
||||
let hud2 = yield onConsoleOpen(hud);
|
||||
let hud3 = yield onConsoleReopen1(hud2);
|
||||
yield onConsoleReopen2(hud3);
|
||||
|
||||
// Clear prefs
|
||||
for (let category in prefs) {
|
||||
prefs[category].forEach(function(pref) {
|
||||
Services.prefs.clearUserPref("devtools.webconsole.filter." + pref);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function onConsoleOpen(hud) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let hudBox = hud.ui.rootElement;
|
||||
|
||||
// Check if the filters menuitems exists and are checked
|
||||
for (let category in prefs) {
|
||||
let button = hudBox.querySelector(".webconsole-filter-button[category=\""
|
||||
+ category + "\"]");
|
||||
ok(isChecked(button), "main button for " + category + " category is checked");
|
||||
|
||||
prefs[category].forEach(function(pref) {
|
||||
let menuitem = hudBox.querySelector("menuitem[prefKey=" + pref + "]");
|
||||
ok(isChecked(menuitem), "menuitem for " + pref + " is checked");
|
||||
});
|
||||
}
|
||||
|
||||
// Set all prefs to false
|
||||
for (let category in prefs) {
|
||||
prefs[category].forEach(function(pref) {
|
||||
hud.setFilterState(pref, false);
|
||||
});
|
||||
}
|
||||
|
||||
//Re-init the console
|
||||
closeConsole().then(() => {
|
||||
openConsole().then(deferred.resolve);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function onConsoleReopen1(hud) {
|
||||
info("testing after reopening once");
|
||||
let deferred = promise.defer();
|
||||
|
||||
let hudBox = hud.ui.rootElement;
|
||||
|
||||
// Check if the filter button and menuitems are unchecked
|
||||
for (let category in prefs) {
|
||||
let button = hudBox.querySelector(".webconsole-filter-button[category=\""
|
||||
+ category + "\"]");
|
||||
ok(isUnchecked(button), "main button for " + category + " category is not checked");
|
||||
|
||||
prefs[category].forEach(function(pref) {
|
||||
let menuitem = hudBox.querySelector("menuitem[prefKey=" + pref + "]");
|
||||
ok(isUnchecked(menuitem), "menuitem for " + pref + " is not checked");
|
||||
});
|
||||
}
|
||||
|
||||
// Set first pref in each category to true
|
||||
for (let category in prefs) {
|
||||
hud.setFilterState(prefs[category][0], true);
|
||||
}
|
||||
|
||||
// Re-init the console
|
||||
closeConsole().then(() => {
|
||||
openConsole().then(deferred.resolve);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function onConsoleReopen2(hud) {
|
||||
info("testing after reopening again");
|
||||
|
||||
let hudBox = hud.ui.rootElement;
|
||||
|
||||
// Check the main category button is checked and first menuitem is checked
|
||||
for (let category in prefs) {
|
||||
let button = hudBox.querySelector(".webconsole-filter-button[category=\""
|
||||
+ category + "\"]");
|
||||
ok(isChecked(button), category + " button is checked when first pref is true");
|
||||
|
||||
let pref = prefs[category][0];
|
||||
let menuitem = hudBox.querySelector("menuitem[prefKey=" + pref + "]");
|
||||
ok(isChecked(menuitem), "first " + category + " menuitem is checked");
|
||||
}
|
||||
}
|
||||
|
||||
function isChecked(aNode) {
|
||||
return aNode.getAttribute("checked") === "true";
|
||||
}
|
||||
|
||||
function isUnchecked(aNode) {
|
||||
return aNode.getAttribute("checked") === "false";
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that the basic console.log()-style APIs and filtering work for sharedWorkers
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = "http://example.com/browser/toolkit/devtools/webconsole/test/test-console-workers.html";
|
||||
|
||||
function pushPrefEnv()
|
||||
{
|
||||
let deferred = promise.defer();
|
||||
let options = {'set': [["dom.workers.sharedWorkers.enabled", true]]};
|
||||
SpecialPowers.pushPrefEnv(options, deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
yield pushPrefEnv();
|
||||
yield loadTab(TEST_URI);
|
||||
|
||||
let hud = yield openConsole();
|
||||
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
text: "foo-bar-shared-worker"
|
||||
}],
|
||||
});
|
||||
|
||||
hud.setFilterState('sharedworkers', false);
|
||||
|
||||
is(hud.outputNode.querySelectorAll(".filtered-by-type").length, 1,
|
||||
"1 message hidden for sharedworkers (logging turned off)")
|
||||
|
||||
hud.setFilterState('sharedworkers', true);
|
||||
|
||||
is(hud.outputNode.querySelectorAll(".filtered-by-type").length, 0,
|
||||
"1 message shown for sharedworkers (logging turned on)")
|
||||
|
||||
hud.setFilterState('sharedworkers', false);
|
||||
|
||||
hud.jsterm.clearOutput(true);
|
||||
});
|
||||
@@ -0,0 +1,214 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*
|
||||
* Contributor(s):
|
||||
* Julian Viereck <jviereck@mozilla.com>
|
||||
* Patrick Walton <pcwalton@mozilla.com>
|
||||
* Mihai Șucan <mihai.sucan@gmail.com>
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
// Tests that network log messages bring up the network panel.
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,Web Console network logging tests";
|
||||
|
||||
const TEST_NETWORK_REQUEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-network-request.html";
|
||||
|
||||
const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test/test-image.png";
|
||||
|
||||
const TEST_DATA_JSON_CONTENT =
|
||||
'{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ] }';
|
||||
|
||||
let lastRequest = null;
|
||||
let requestCallback = null;
|
||||
let browser, hud;
|
||||
|
||||
function test()
|
||||
{
|
||||
loadTab(TEST_URI).then((tab) => {
|
||||
browser = tab.browser;
|
||||
|
||||
openConsole().then((aHud) => {
|
||||
hud = aHud;
|
||||
|
||||
HUDService.lastFinishedRequest.callback = requestCallbackWrapper;
|
||||
|
||||
executeSoon(testPageLoad);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function requestCallbackWrapper(aRequest)
|
||||
{
|
||||
lastRequest = aRequest;
|
||||
|
||||
hud.ui.webConsoleClient.getResponseContent(lastRequest.actor,
|
||||
function(aResponse) {
|
||||
lastRequest.response.content = aResponse.content;
|
||||
lastRequest.discardResponseBody = aResponse.contentDiscarded;
|
||||
|
||||
hud.ui.webConsoleClient.getRequestPostData(lastRequest.actor,
|
||||
function(aResponse) {
|
||||
lastRequest.request.postData = aResponse.postData;
|
||||
lastRequest.discardRequestBody = aResponse.postDataDiscarded;
|
||||
|
||||
if (requestCallback) {
|
||||
requestCallback();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testPageLoad()
|
||||
{
|
||||
requestCallback = function() {
|
||||
// Check if page load was logged correctly.
|
||||
ok(lastRequest, "Page load was logged");
|
||||
|
||||
is(lastRequest.request.url, TEST_NETWORK_REQUEST_URI,
|
||||
"Logged network entry is page load");
|
||||
is(lastRequest.request.method, "GET", "Method is correct");
|
||||
ok(!lastRequest.request.postData.text, "No request body was stored");
|
||||
ok(lastRequest.discardRequestBody, "Request body was discarded");
|
||||
ok(!lastRequest.response.content.text, "No response body was stored");
|
||||
ok(lastRequest.discardResponseBody || lastRequest.fromCache,
|
||||
"Response body was discarded or response came from the cache");
|
||||
|
||||
lastRequest = null;
|
||||
requestCallback = null;
|
||||
executeSoon(testPageLoadBody);
|
||||
};
|
||||
|
||||
content.location = TEST_NETWORK_REQUEST_URI;
|
||||
}
|
||||
|
||||
function testPageLoadBody()
|
||||
{
|
||||
// Turn on logging of request bodies and check again.
|
||||
hud.ui.setSaveRequestAndResponseBodies(true).then(() => {
|
||||
ok(hud.ui._saveRequestAndResponseBodies,
|
||||
"The saveRequestAndResponseBodies property was successfully set.");
|
||||
|
||||
testPageLoadBodyAfterSettingUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
function testPageLoadBodyAfterSettingUpdate()
|
||||
{
|
||||
let loaded = false;
|
||||
let requestCallbackInvoked = false;
|
||||
|
||||
requestCallback = function() {
|
||||
ok(lastRequest, "Page load was logged again");
|
||||
ok(!lastRequest.discardResponseBody, "Response body was not discarded");
|
||||
is(lastRequest.response.content.text.indexOf("<!DOCTYPE HTML>"), 0,
|
||||
"Response body's beginning is okay");
|
||||
|
||||
lastRequest = null;
|
||||
requestCallback = null;
|
||||
requestCallbackInvoked = true;
|
||||
|
||||
if (loaded) {
|
||||
executeSoon(testXhrGet);
|
||||
}
|
||||
};
|
||||
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
loaded = true;
|
||||
|
||||
if (requestCallbackInvoked) {
|
||||
executeSoon(testXhrGet);
|
||||
}
|
||||
}, true);
|
||||
|
||||
content.location.reload();
|
||||
}
|
||||
|
||||
function testXhrGet()
|
||||
{
|
||||
requestCallback = function() {
|
||||
ok(lastRequest, "testXhrGet() was logged");
|
||||
is(lastRequest.request.method, "GET", "Method is correct");
|
||||
ok(!lastRequest.request.postData.text, "No request body was sent");
|
||||
ok(!lastRequest.discardRequestBody, "Request body was not discarded");
|
||||
is(lastRequest.response.content.text, TEST_DATA_JSON_CONTENT,
|
||||
"Response is correct");
|
||||
|
||||
lastRequest = null;
|
||||
requestCallback = null;
|
||||
executeSoon(testXhrPost);
|
||||
};
|
||||
|
||||
// Start the XMLHttpRequest() GET test.
|
||||
content.wrappedJSObject.testXhrGet();
|
||||
}
|
||||
|
||||
function testXhrPost()
|
||||
{
|
||||
requestCallback = function() {
|
||||
ok(lastRequest, "testXhrPost() was logged");
|
||||
is(lastRequest.request.method, "POST", "Method is correct");
|
||||
is(lastRequest.request.postData.text, "Hello world!",
|
||||
"Request body was logged");
|
||||
is(lastRequest.response.content.text, TEST_DATA_JSON_CONTENT,
|
||||
"Response is correct");
|
||||
|
||||
lastRequest = null;
|
||||
requestCallback = null;
|
||||
executeSoon(testFormSubmission);
|
||||
};
|
||||
|
||||
// Start the XMLHttpRequest() POST test.
|
||||
content.wrappedJSObject.testXhrPost();
|
||||
}
|
||||
|
||||
function testFormSubmission()
|
||||
{
|
||||
// Start the form submission test. As the form is submitted, the page is
|
||||
// loaded again. Bind to the load event to catch when this is done.
|
||||
requestCallback = function() {
|
||||
ok(lastRequest, "testFormSubmission() was logged");
|
||||
is(lastRequest.request.method, "POST", "Method is correct");
|
||||
isnot(lastRequest.request.postData.text.
|
||||
indexOf("Content-Type: application/x-www-form-urlencoded"), -1,
|
||||
"Content-Type is correct");
|
||||
isnot(lastRequest.request.postData.text.
|
||||
indexOf("Content-Length: 20"), -1, "Content-length is correct");
|
||||
isnot(lastRequest.request.postData.text.
|
||||
indexOf("name=foo+bar&age=144"), -1, "Form data is correct");
|
||||
is(lastRequest.response.content.text.indexOf("<!DOCTYPE HTML>"), 0,
|
||||
"Response body's beginning is okay");
|
||||
|
||||
executeSoon(testNetworkPanel);
|
||||
};
|
||||
|
||||
let form = content.document.querySelector("form");
|
||||
ok(form, "we have the HTML form");
|
||||
form.submit();
|
||||
}
|
||||
|
||||
function testNetworkPanel()
|
||||
{
|
||||
// Open the NetworkPanel. The functionality of the NetworkPanel is tested
|
||||
// within separate test files.
|
||||
let networkPanel = hud.ui.openNetworkPanel(hud.ui.filterBox, lastRequest);
|
||||
|
||||
networkPanel.panel.addEventListener("popupshown", function onPopupShown() {
|
||||
networkPanel.panel.removeEventListener("popupshown", onPopupShown, true);
|
||||
|
||||
is(hud.ui.filterBox._netPanel, networkPanel,
|
||||
"Network panel stored on anchor node");
|
||||
ok(true, "NetworkPanel was opened");
|
||||
|
||||
// All tests are done. Shutdown.
|
||||
networkPanel.panel.hidePopup();
|
||||
lastRequest = null;
|
||||
HUDService.lastFinishedRequest.callback = null;
|
||||
browser = requestCallback = hud = null;
|
||||
executeSoon(finishTest);
|
||||
}, true);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf8,Test that the web console " +
|
||||
"displays requests that have been recorded in the " +
|
||||
"netmonitor, even if the console hadn't opened yet.";
|
||||
|
||||
const TEST_FILE = "test-network-request.html";
|
||||
const TEST_PATH = "http://example.com/browser/toolkit/devtools/webconsole/test/" +
|
||||
TEST_FILE;
|
||||
|
||||
const NET_PREF = "devtools.webconsole.filter.networkinfo";
|
||||
Services.prefs.setBoolPref(NET_PREF, true);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(NET_PREF);
|
||||
});
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, browser } = yield loadTab(TEST_URI);
|
||||
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
|
||||
info("Network panel is open.");
|
||||
|
||||
yield loadDocument(browser);
|
||||
info("Document loaded.");
|
||||
|
||||
// Test that the request appears in the network panel.
|
||||
testNetmonitor(toolbox);
|
||||
|
||||
// Test that the request appears in the console.
|
||||
let hud = yield openConsole();
|
||||
info("Web console is open");
|
||||
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
name: "network message",
|
||||
text: TEST_FILE,
|
||||
category: CATEGORY_NETWORK,
|
||||
severity: SEVERITY_LOG
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function loadDocument(browser) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
deferred.resolve();
|
||||
}, true);
|
||||
content.location = TEST_PATH;
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testNetmonitor(toolbox) {
|
||||
let monitor = toolbox.getCurrentPanel();
|
||||
let { RequestsMenu } = monitor.panelWin.NetMonitorView;
|
||||
is(RequestsMenu.itemCount, 1, "Network request appears in the network panel");
|
||||
|
||||
let item = RequestsMenu.getItemAtIndex(0);
|
||||
is(item.attachment.method, "GET", "The attached method is correct.");
|
||||
is(item.attachment.url, TEST_PATH, "The attached url is correct.");
|
||||
}
|
||||
@@ -10,6 +10,7 @@ support-files =
|
||||
[test_basics.html]
|
||||
[test_bug819670_getter_throws.html]
|
||||
[test_cached_messages.html]
|
||||
[test_commands_registration.html]
|
||||
[test_consoleapi.html]
|
||||
[test_consoleapi_innerID.html]
|
||||
[test_file_uri.html]
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!DOCTYPE HTML>
|
||||
<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
|
||||
<meta charset="utf-8">
|
||||
<title>Console test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
var sw = new SharedWorker('data:application/javascript,console.log("foo-bar-shared-worker");');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,192 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>Test for Web Console commands registration.</title>
|
||||
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript;version=1.8" src="common.js"></script>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
</head>
|
||||
<body>
|
||||
<p>Test for Web Console commands registration.</p>
|
||||
<p id="quack"></p>
|
||||
|
||||
<script class="testbody" type="text/javascript;version=1.8">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
let gState;
|
||||
let tests;
|
||||
|
||||
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let {WebConsoleCommands} = devtools.require("devtools/toolkit/webconsole/utils");
|
||||
|
||||
function evaluateJS(input) {
|
||||
return new Promise((resolve) => gState.client.evaluateJS(input, resolve));
|
||||
}
|
||||
|
||||
function* evaluateJSAndCheckResult(input, result) {
|
||||
let response = yield evaluateJS(input);
|
||||
checkObject(response, {result});
|
||||
}
|
||||
|
||||
function startTest()
|
||||
{
|
||||
removeEventListener("load", startTest);
|
||||
|
||||
attachConsole(["PageError"], onAttach, true);
|
||||
}
|
||||
|
||||
function onAttach(aState, aResponse)
|
||||
{
|
||||
gState = aState;
|
||||
|
||||
runTests(tests, testEnd);
|
||||
}
|
||||
|
||||
tests = [
|
||||
Task.async(function* registerNewCommand() {
|
||||
let win;
|
||||
WebConsoleCommands.register("setFoo", (owner, value) => {
|
||||
owner.window.foo = value;
|
||||
return "ok";
|
||||
});
|
||||
|
||||
ok(WebConsoleCommands.hasCommand("setFoo"),
|
||||
"The command should be registered");
|
||||
|
||||
let command = "setFoo('bar')";
|
||||
let response = yield evaluateJS(command);
|
||||
|
||||
checkObject(response, {
|
||||
from: gState.actor,
|
||||
input: command,
|
||||
result: "ok"
|
||||
});
|
||||
is(top.foo, "bar", "top.foo should equal to 'bar'");
|
||||
nextTest();
|
||||
}),
|
||||
|
||||
Task.async(function* wrapCommand() {
|
||||
let origKeys = WebConsoleCommands.getCommand("keys");
|
||||
|
||||
let newKeys = (...args) => {
|
||||
let [owner, arg0] = args;
|
||||
if (arg0 === ">o_/") {
|
||||
return "bang!";
|
||||
}
|
||||
else {
|
||||
return origKeys(...args);
|
||||
}
|
||||
};
|
||||
|
||||
WebConsoleCommands.register("keys", newKeys);
|
||||
is(WebConsoleCommands.getCommand("keys"), newKeys,
|
||||
"the keys() command should have been replaced");
|
||||
|
||||
let response = yield evaluateJS("keys('>o_/')");
|
||||
checkObject(response, {
|
||||
from: gState.actor,
|
||||
result: "bang!"
|
||||
});
|
||||
|
||||
response = yield evaluateJS("keys({foo: 'bar'})");
|
||||
checkObject(response, {
|
||||
from: gState.actor,
|
||||
result: {
|
||||
class: "Array",
|
||||
preview: {
|
||||
items: ["foo"]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
WebConsoleCommands.register("keys", origKeys);
|
||||
is(WebConsoleCommands.getCommand("keys"), origKeys,
|
||||
"the keys() command should be restored");
|
||||
nextTest();
|
||||
}),
|
||||
|
||||
Task.async(function* unregisterCommand() {
|
||||
WebConsoleCommands.unregister("setFoo");
|
||||
|
||||
let response = yield evaluateJS("setFoo");
|
||||
|
||||
checkObject(response, {
|
||||
from: gState.actor,
|
||||
input: "setFoo",
|
||||
result: {
|
||||
type: "undefined"
|
||||
},
|
||||
exceptionMessage: /setFoo is not defined/
|
||||
});
|
||||
nextTest();
|
||||
}),
|
||||
|
||||
Task.async(function* registerAccessor() {
|
||||
WebConsoleCommands.register("$foo", {
|
||||
get(owner) {
|
||||
let foo = owner.window.frames[0].window.document.getElementById("quack");
|
||||
return owner.makeDebuggeeValue(foo);
|
||||
}
|
||||
});
|
||||
let command = "$foo.textContent = '>o_/'";
|
||||
let response = yield evaluateJS(command);
|
||||
|
||||
checkObject(response, {
|
||||
from: gState.actor,
|
||||
input: command,
|
||||
result: ">o_/"
|
||||
});
|
||||
is(document.getElementById("quack").textContent, ">o_/",
|
||||
"#foo textContent should equal to \">o_/\"");
|
||||
WebConsoleCommands.unregister("$foo");
|
||||
ok(!WebConsoleCommands.hasCommand("$foo"), "$foo should be unregistered");
|
||||
nextTest();
|
||||
}),
|
||||
|
||||
Task.async(function* unregisterAfterOverridingTwice() {
|
||||
WebConsoleCommands.register("keys", (owner, obj) => "command 1");
|
||||
info("checking the value of the first override");
|
||||
yield evaluateJSAndCheckResult("keys('foo');", "command 1");
|
||||
|
||||
let orig = WebConsoleCommands.getCommand("keys");
|
||||
WebConsoleCommands.register("keys", (owner, obj) => {
|
||||
if (obj === "quack")
|
||||
return "bang!";
|
||||
return orig(owner, obj);
|
||||
});
|
||||
|
||||
info("checking the values after the second override");
|
||||
yield evaluateJSAndCheckResult("keys({});", "command 1");
|
||||
yield evaluateJSAndCheckResult("keys('quack');", "bang!");
|
||||
|
||||
WebConsoleCommands.unregister("keys");
|
||||
|
||||
info("checking the value after unregistration (should restore " +
|
||||
"the original command)");
|
||||
yield evaluateJSAndCheckResult("keys({});", {
|
||||
class: "Array",
|
||||
preview: {items: []}
|
||||
});
|
||||
nextTest();
|
||||
|
||||
})
|
||||
];
|
||||
|
||||
function testEnd()
|
||||
{
|
||||
// If this is the first run, reload the page and do it again.
|
||||
// Otherwise, end the test.
|
||||
delete top.foo;
|
||||
closeDebugger(gState, function() {
|
||||
gState = null;
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
|
||||
addEventListener("load", startTest);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -14,10 +14,10 @@ loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
|
||||
loader.lazyImporter(this, "LayoutHelpers", "resource://gre/modules/devtools/LayoutHelpers.jsm");
|
||||
|
||||
// TODO: Bug 842672 - toolkit/ imports modules from browser/.
|
||||
// Note that these are only used in JSTermHelpers, see $0 and pprint().
|
||||
loader.lazyImporter(this, "gDevTools", "resource://gre/modules/devtools/gDevTools.jsm");
|
||||
// Note that these are only used in WebConsoleCommands, see $0 and pprint().
|
||||
loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
|
||||
loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
|
||||
loader.lazyImporter(this, "VariablesView", "resource://gre/modules/devtools/VariablesView.jsm");
|
||||
loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
|
||||
loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm");
|
||||
|
||||
// Match the function name from the result of toString() or toSource().
|
||||
@@ -32,13 +32,15 @@ const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
|
||||
const REGEX_MATCH_FUNCTION_ARGS = /^\(?function\s*[^\s(]*\s*\((.+?)\)/;
|
||||
|
||||
// Number of terminal entries for the self-xss prevention to go away
|
||||
const CONSOLE_ENTRY_THRESHOLD = 5
|
||||
const CONSOLE_ENTRY_THRESHOLD = 5;
|
||||
|
||||
// Provide an easy way to bail out of even attempting an autocompletion
|
||||
// if an object has way too many properties. Protects against large objects
|
||||
// with numeric values that wouldn't be tallied towards MAX_AUTOCOMPLETIONS.
|
||||
const MAX_AUTOCOMPLETE_ATTEMPTS = exports.MAX_AUTOCOMPLETE_ATTEMPTS = 100000;
|
||||
|
||||
const CONSOLE_WORKER_IDS = exports.CONSOLE_WORKER_IDS = [ 'SharedWorker', 'ServiceWorker', 'Worker' ];
|
||||
|
||||
// Prevent iterating over too many properties during autocomplete suggestions.
|
||||
const MAX_AUTOCOMPLETIONS = exports.MAX_AUTOCOMPLETIONS = 1500;
|
||||
|
||||
@@ -555,7 +557,7 @@ let WebConsoleUtils = {
|
||||
_usageCount: 0,
|
||||
get usageCount() {
|
||||
if (WebConsoleUtils._usageCount < CONSOLE_ENTRY_THRESHOLD) {
|
||||
WebConsoleUtils._usageCount = Services.prefs.getIntPref("devtools.selfxss.count")
|
||||
WebConsoleUtils._usageCount = Services.prefs.getIntPref("devtools.selfxss.count");
|
||||
if (Services.prefs.getBoolPref("devtools.chrome.enabled")) {
|
||||
WebConsoleUtils.usageCount = CONSOLE_ENTRY_THRESHOLD;
|
||||
}
|
||||
@@ -917,7 +919,7 @@ function JSPropertyProvider(aDbgObject, anEnvironment, aInputValue, aCursor)
|
||||
return null;
|
||||
}
|
||||
|
||||
if (/\[\d+\]$/.test(prop)) {
|
||||
if (/\[\d+\]$/.test(prop)) {
|
||||
// The property to autocomplete is a member of array. For example
|
||||
// list[i][j]..[n]. Traverse the array to get the actual element.
|
||||
obj = getArrayMemberProperty(obj, prop);
|
||||
@@ -1449,7 +1451,7 @@ ConsoleAPIListener.prototype =
|
||||
}
|
||||
|
||||
let apiMessage = aMessage.wrappedJSObject;
|
||||
if (this.window) {
|
||||
if (this.window && CONSOLE_WORKER_IDS.indexOf(apiMessage.innerID) == -1) {
|
||||
let msgWindow = Services.wm.getCurrentInnerWindowWithId(apiMessage.innerID);
|
||||
if (!msgWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(msgWindow)) {
|
||||
// Not the same window!
|
||||
@@ -1489,6 +1491,10 @@ ConsoleAPIListener.prototype =
|
||||
});
|
||||
}
|
||||
|
||||
CONSOLE_WORKER_IDS.forEach((id) => {
|
||||
messages = messages.concat(ConsoleAPIStorage.getEvents(id));
|
||||
});
|
||||
|
||||
if (this.consoleID) {
|
||||
messages = messages.filter((m) => m.consoleID == this.consoleID);
|
||||
}
|
||||
@@ -1510,297 +1516,425 @@ ConsoleAPIListener.prototype =
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* WebConsole commands manager.
|
||||
*
|
||||
* Defines a set of functions /variables ("commands") that are available from
|
||||
* the Web Console but not from the web page.
|
||||
*
|
||||
*/
|
||||
let WebConsoleCommands = {
|
||||
_registeredCommands: new Map(),
|
||||
_originalCommands: new Map(),
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Reserved for built-in commands. To register a command from the code of an
|
||||
* add-on, see WebConsoleCommands.register instead.
|
||||
*
|
||||
* @see WebConsoleCommands.register
|
||||
*/
|
||||
_registerOriginal: function (name, command) {
|
||||
this.register(name, command);
|
||||
this._originalCommands.set(name, this.getCommand(name));
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a new command.
|
||||
* @param {string} name The command name (exemple: "$")
|
||||
* @param {(function|object)} command The command to register.
|
||||
* It can be a function so the command is a function (like "$()"),
|
||||
* or it can also be a property descriptor to describe a getter / value (like
|
||||
* "$0").
|
||||
*
|
||||
* The command function or the command getter are passed a owner object as
|
||||
* their first parameter (see the example below).
|
||||
*
|
||||
* Note that setters don't work currently and "enumerable" and "configurable"
|
||||
* are forced to true.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* WebConsoleCommands.register("$", function JSTH_$(aOwner, aSelector)
|
||||
* {
|
||||
* return aOwner.window.document.querySelector(aSelector);
|
||||
* });
|
||||
*
|
||||
* WebConsoleCommands.register("$0", {
|
||||
* get: function(aOwner) {
|
||||
* return aOwner.makeDebuggeeValue(aOwner.selectedNode);
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
register: function(name, command) {
|
||||
this._registeredCommands.set(name, command);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregister a command.
|
||||
*
|
||||
* If the command being unregister overrode a built-in command,
|
||||
* the latter is restored.
|
||||
*
|
||||
* @param {string} name The name of the command
|
||||
*/
|
||||
unregister: function(name) {
|
||||
this._registeredCommands.delete(name);
|
||||
if (this._originalCommands.has(name)) {
|
||||
this.register(name, this._originalCommands.get(name));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a command by its name.
|
||||
*
|
||||
* @param {string} name The name of the command.
|
||||
*
|
||||
* @return {(function|object)} The command.
|
||||
*/
|
||||
getCommand: function(name) {
|
||||
return this._registeredCommands.get(name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if a command is registered with the given name.
|
||||
*
|
||||
* @param {string} name The name of the command.
|
||||
*
|
||||
* @return {boolean} True if the command is registered.
|
||||
*/
|
||||
hasCommand: function(name) {
|
||||
return this._registeredCommands.has(name);
|
||||
},
|
||||
};
|
||||
|
||||
exports.WebConsoleCommands = WebConsoleCommands;
|
||||
|
||||
|
||||
/*
|
||||
* Built-in commands.
|
||||
*
|
||||
* A list of helper functions used by Firebug can be found here:
|
||||
* http://getfirebug.com/wiki/index.php/Command_Line_API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Find a node by ID.
|
||||
*
|
||||
* @param string aId
|
||||
* The ID of the element you want.
|
||||
* @return nsIDOMNode or null
|
||||
* The result of calling document.querySelector(aSelector).
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("$", function JSTH_$(aOwner, aSelector)
|
||||
{
|
||||
return aOwner.window.document.querySelector(aSelector);
|
||||
});
|
||||
|
||||
/**
|
||||
* Find the nodes matching a CSS selector.
|
||||
*
|
||||
* @param string aSelector
|
||||
* A string that is passed to window.document.querySelectorAll.
|
||||
* @return nsIDOMNodeList
|
||||
* Returns the result of document.querySelectorAll(aSelector).
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("$$", function JSTH_$$(aOwner, aSelector)
|
||||
{
|
||||
return aOwner.window.document.querySelectorAll(aSelector);
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the result of the last console input evaluation
|
||||
*
|
||||
* @return object|undefined
|
||||
* Returns last console evaluation or undefined
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("$_", {
|
||||
get: function(aOwner) {
|
||||
return aOwner.consoleActor.getLastConsoleInputEvaluation();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* JSTerm helper functions.
|
||||
* Runs an xPath query and returns all matched nodes.
|
||||
*
|
||||
* Defines a set of functions ("helper functions") that are available from the
|
||||
* Web Console but not from the web page.
|
||||
*
|
||||
* A list of helper functions used by Firebug can be found here:
|
||||
* http://getfirebug.com/wiki/index.php/Command_Line_API
|
||||
*
|
||||
* @param object aOwner
|
||||
* The owning object.
|
||||
* @param string aXPath
|
||||
* xPath search query to execute.
|
||||
* @param [optional] nsIDOMNode aContext
|
||||
* Context to run the xPath query on. Uses window.document if not set.
|
||||
* @return array of nsIDOMNode
|
||||
*/
|
||||
function JSTermHelpers(aOwner)
|
||||
WebConsoleCommands._registerOriginal("$x", function JSTH_$x(aOwner, aXPath, aContext)
|
||||
{
|
||||
/**
|
||||
* Find a node by ID.
|
||||
*
|
||||
* @param string aId
|
||||
* The ID of the element you want.
|
||||
* @return nsIDOMNode or null
|
||||
* The result of calling document.querySelector(aSelector).
|
||||
*/
|
||||
aOwner.sandbox.$ = function JSTH_$(aSelector)
|
||||
{
|
||||
return aOwner.window.document.querySelector(aSelector);
|
||||
let nodes = new aOwner.window.wrappedJSObject.Array();
|
||||
let doc = aOwner.window.document;
|
||||
aContext = aContext || doc;
|
||||
|
||||
let results = doc.evaluate(aXPath, aContext, null,
|
||||
Ci.nsIDOMXPathResult.ANY_TYPE, null);
|
||||
let node;
|
||||
while ((node = results.iterateNext())) {
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
return nodes;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the currently selected object in the highlighter.
|
||||
*
|
||||
* @return Object representing the current selection in the
|
||||
* Inspector, or null if no selection exists.
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("$0", {
|
||||
get: function(aOwner) {
|
||||
return aOwner.makeDebuggeeValue(aOwner.selectedNode);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Clears the output of the WebConsole.
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("clear", function JSTH_clear(aOwner)
|
||||
{
|
||||
aOwner.helperResult = {
|
||||
type: "clearOutput",
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Find the nodes matching a CSS selector.
|
||||
*
|
||||
* @param string aSelector
|
||||
* A string that is passed to window.document.querySelectorAll.
|
||||
* @return nsIDOMNodeList
|
||||
* Returns the result of document.querySelectorAll(aSelector).
|
||||
*/
|
||||
aOwner.sandbox.$$ = function JSTH_$$(aSelector)
|
||||
{
|
||||
return aOwner.window.document.querySelectorAll(aSelector);
|
||||
/**
|
||||
* Clears the input history of the WebConsole.
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("clearHistory", function JSTH_clearHistory(aOwner)
|
||||
{
|
||||
aOwner.helperResult = {
|
||||
type: "clearHistory",
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the result of the last console input evaluation
|
||||
*
|
||||
* @return object|undefined
|
||||
* Returns last console evaluation or undefined
|
||||
*/
|
||||
Object.defineProperty(aOwner.sandbox, "$_", {
|
||||
get: function() {
|
||||
return aOwner.consoleActor.getLastConsoleInputEvaluation();
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* Returns the result of Object.keys(aObject).
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to return the property names from.
|
||||
* @return array of strings
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("keys", function JSTH_keys(aOwner, aObject)
|
||||
{
|
||||
return aOwner.window.wrappedJSObject.Object.keys(WebConsoleUtils.unwrap(aObject));
|
||||
});
|
||||
|
||||
/**
|
||||
* Runs an xPath query and returns all matched nodes.
|
||||
*
|
||||
* @param string aXPath
|
||||
* xPath search query to execute.
|
||||
* @param [optional] nsIDOMNode aContext
|
||||
* Context to run the xPath query on. Uses window.document if not set.
|
||||
* @return array of nsIDOMNode
|
||||
*/
|
||||
aOwner.sandbox.$x = function JSTH_$x(aXPath, aContext)
|
||||
{
|
||||
let nodes = new aOwner.window.wrappedJSObject.Array();
|
||||
let doc = aOwner.window.document;
|
||||
aContext = aContext || doc;
|
||||
/**
|
||||
* Returns the values of all properties on aObject.
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to display the values from.
|
||||
* @return array of string
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("values", function JSTH_values(aOwner, aObject)
|
||||
{
|
||||
let arrValues = new aOwner.window.wrappedJSObject.Array();
|
||||
let obj = WebConsoleUtils.unwrap(aObject);
|
||||
|
||||
let results = doc.evaluate(aXPath, aContext, null,
|
||||
Ci.nsIDOMXPathResult.ANY_TYPE, null);
|
||||
let node;
|
||||
while ((node = results.iterateNext())) {
|
||||
nodes.push(node);
|
||||
}
|
||||
for (let prop in obj) {
|
||||
arrValues.push(obj[prop]);
|
||||
}
|
||||
|
||||
return nodes;
|
||||
};
|
||||
return arrValues;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the currently selected object in the highlighter.
|
||||
*
|
||||
* @return Object representing the current selection in the
|
||||
* Inspector, or null if no selection exists.
|
||||
*/
|
||||
Object.defineProperty(aOwner.sandbox, "$0", {
|
||||
get: function() {
|
||||
return aOwner.makeDebuggeeValue(aOwner.selectedNode)
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
/**
|
||||
* Opens a help window in MDN.
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("help", function JSTH_help(aOwner)
|
||||
{
|
||||
aOwner.helperResult = { type: "help" };
|
||||
});
|
||||
|
||||
/**
|
||||
* Clears the output of the JSTerm.
|
||||
*/
|
||||
aOwner.sandbox.clear = function JSTH_clear()
|
||||
{
|
||||
aOwner.helperResult = {
|
||||
type: "clearOutput",
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the result of Object.keys(aObject).
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to return the property names from.
|
||||
* @return array of strings
|
||||
*/
|
||||
aOwner.sandbox.keys = function JSTH_keys(aObject)
|
||||
{
|
||||
return aOwner.window.wrappedJSObject.Object.keys(WebConsoleUtils.unwrap(aObject));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the values of all properties on aObject.
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to display the values from.
|
||||
* @return array of string
|
||||
*/
|
||||
aOwner.sandbox.values = function JSTH_values(aObject)
|
||||
{
|
||||
let arrValues = new aOwner.window.wrappedJSObject.Array();
|
||||
let obj = WebConsoleUtils.unwrap(aObject);
|
||||
|
||||
for (let prop in obj) {
|
||||
arrValues.push(obj[prop]);
|
||||
}
|
||||
|
||||
return arrValues;
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a help window in MDN.
|
||||
*/
|
||||
aOwner.sandbox.help = function JSTH_help()
|
||||
{
|
||||
aOwner.helperResult = { type: "help" };
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the JS evaluation scope.
|
||||
*
|
||||
* @param DOMElement|string|window aWindow
|
||||
* The window object to use for eval scope. This can be a string that
|
||||
* is used to perform document.querySelector(), to find the iframe that
|
||||
* you want to cd() to. A DOMElement can be given as well, the
|
||||
* .contentWindow property is used. Lastly, you can directly pass
|
||||
* a window object. If you call cd() with no arguments, the current
|
||||
* eval scope is cleared back to its default (the top window).
|
||||
*/
|
||||
aOwner.sandbox.cd = function JSTH_cd(aWindow)
|
||||
{
|
||||
if (!aWindow) {
|
||||
aOwner.consoleActor.evalWindow = null;
|
||||
aOwner.helperResult = { type: "cd" };
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof aWindow == "string") {
|
||||
aWindow = aOwner.window.document.querySelector(aWindow);
|
||||
}
|
||||
if (aWindow instanceof Ci.nsIDOMElement && aWindow.contentWindow) {
|
||||
aWindow = aWindow.contentWindow;
|
||||
}
|
||||
if (!(aWindow instanceof Ci.nsIDOMWindow)) {
|
||||
aOwner.helperResult = { type: "error", message: "cdFunctionInvalidArgument" };
|
||||
return;
|
||||
}
|
||||
|
||||
aOwner.consoleActor.evalWindow = aWindow;
|
||||
/**
|
||||
* Change the JS evaluation scope.
|
||||
*
|
||||
* @param DOMElement|string|window aWindow
|
||||
* The window object to use for eval scope. This can be a string that
|
||||
* is used to perform document.querySelector(), to find the iframe that
|
||||
* you want to cd() to. A DOMElement can be given as well, the
|
||||
* .contentWindow property is used. Lastly, you can directly pass
|
||||
* a window object. If you call cd() with no arguments, the current
|
||||
* eval scope is cleared back to its default (the top window).
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("cd", function JSTH_cd(aOwner, aWindow)
|
||||
{
|
||||
if (!aWindow) {
|
||||
aOwner.consoleActor.evalWindow = null;
|
||||
aOwner.helperResult = { type: "cd" };
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the passed aObject. This is done by opening the PropertyPanel.
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to inspect.
|
||||
*/
|
||||
aOwner.sandbox.inspect = function JSTH_inspect(aObject)
|
||||
{
|
||||
let dbgObj = aOwner.makeDebuggeeValue(aObject);
|
||||
let grip = aOwner.createValueGrip(dbgObj);
|
||||
if (typeof aWindow == "string") {
|
||||
aWindow = aOwner.window.document.querySelector(aWindow);
|
||||
}
|
||||
if (aWindow instanceof Ci.nsIDOMElement && aWindow.contentWindow) {
|
||||
aWindow = aWindow.contentWindow;
|
||||
}
|
||||
if (!(aWindow instanceof Ci.nsIDOMWindow)) {
|
||||
aOwner.helperResult = { type: "error", message: "cdFunctionInvalidArgument" };
|
||||
return;
|
||||
}
|
||||
|
||||
aOwner.consoleActor.evalWindow = aWindow;
|
||||
aOwner.helperResult = { type: "cd" };
|
||||
});
|
||||
|
||||
/**
|
||||
* Inspects the passed aObject. This is done by opening the PropertyPanel.
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to inspect.
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("inspect", function JSTH_inspect(aOwner, aObject)
|
||||
{
|
||||
let dbgObj = aOwner.makeDebuggeeValue(aObject);
|
||||
let grip = aOwner.createValueGrip(dbgObj);
|
||||
aOwner.helperResult = {
|
||||
type: "inspectObject",
|
||||
input: aOwner.evalInput,
|
||||
object: grip,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Prints aObject to the output.
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to print to the output.
|
||||
* @return string
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("pprint", function JSTH_pprint(aOwner, aObject)
|
||||
{
|
||||
if (aObject === null || aObject === undefined || aObject === true ||
|
||||
aObject === false) {
|
||||
aOwner.helperResult = {
|
||||
type: "inspectObject",
|
||||
input: aOwner.evalInput,
|
||||
object: grip,
|
||||
type: "error",
|
||||
message: "helperFuncUnsupportedTypeError",
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
aOwner.helperResult = { rawOutput: true };
|
||||
|
||||
if (typeof aObject == "function") {
|
||||
return aObject + "\n";
|
||||
}
|
||||
|
||||
let output = [];
|
||||
|
||||
let obj = WebConsoleUtils.unwrap(aObject);
|
||||
for (let name in obj) {
|
||||
let desc = WebConsoleUtils.getPropertyDescriptor(obj, name) || {};
|
||||
if (desc.get || desc.set) {
|
||||
// TODO: Bug 842672 - toolkit/ imports modules from browser/.
|
||||
let getGrip = VariablesView.getGrip(desc.get);
|
||||
let setGrip = VariablesView.getGrip(desc.set);
|
||||
let getString = VariablesView.getString(getGrip);
|
||||
let setString = VariablesView.getString(setGrip);
|
||||
output.push(name + ":", " get: " + getString, " set: " + setString);
|
||||
}
|
||||
else {
|
||||
let valueGrip = VariablesView.getGrip(obj[name]);
|
||||
let valueString = VariablesView.getString(valueGrip);
|
||||
output.push(name + ": " + valueString);
|
||||
}
|
||||
}
|
||||
|
||||
return " " + output.join("\n ");
|
||||
});
|
||||
|
||||
/**
|
||||
* Print the String representation of a value to the output, as-is.
|
||||
*
|
||||
* @param any aValue
|
||||
* A value you want to output as a string.
|
||||
* @return void
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("print", function JSTH_print(aOwner, aValue)
|
||||
{
|
||||
aOwner.helperResult = { rawOutput: true };
|
||||
if (typeof aValue === "symbol") {
|
||||
return Symbol.prototype.toString.call(aValue);
|
||||
}
|
||||
// Waiving Xrays here allows us to see a closer representation of the
|
||||
// underlying object. This may execute arbitrary content code, but that
|
||||
// code will run with content privileges, and the result will be rendered
|
||||
// inert by coercing it to a String.
|
||||
return String(Cu.waiveXrays(aValue));
|
||||
});
|
||||
|
||||
/**
|
||||
* Copy the String representation of a value to the clipboard.
|
||||
*
|
||||
* @param any aValue
|
||||
* A value you want to copy as a string.
|
||||
* @return void
|
||||
*/
|
||||
WebConsoleCommands._registerOriginal("copy", function JSTH_copy(aOwner, aValue)
|
||||
{
|
||||
let payload;
|
||||
try {
|
||||
if (aValue instanceof Ci.nsIDOMElement) {
|
||||
payload = aValue.outerHTML;
|
||||
} else if (typeof aValue == "string") {
|
||||
payload = aValue;
|
||||
} else {
|
||||
payload = JSON.stringify(aValue, null, " ");
|
||||
}
|
||||
} catch (ex) {
|
||||
payload = "/* " + ex + " */";
|
||||
}
|
||||
aOwner.helperResult = {
|
||||
type: "copyValueToClipboard",
|
||||
value: payload,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Prints aObject to the output.
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to print to the output.
|
||||
* @return string
|
||||
*/
|
||||
aOwner.sandbox.pprint = function JSTH_pprint(aObject)
|
||||
{
|
||||
if (aObject === null || aObject === undefined || aObject === true ||
|
||||
aObject === false) {
|
||||
aOwner.helperResult = {
|
||||
type: "error",
|
||||
message: "helperFuncUnsupportedTypeError",
|
||||
};
|
||||
return null;
|
||||
|
||||
/**
|
||||
* (Internal only) Add the bindings to |owner.sandbox|.
|
||||
* This is intended to be used by the WebConsole actor only.
|
||||
*
|
||||
* @param object aOwner
|
||||
* The owning object.
|
||||
*/
|
||||
function addWebConsoleCommands(owner) {
|
||||
if (!owner) {
|
||||
throw new Error("The owner is required");
|
||||
}
|
||||
for (let [name, command] of WebConsoleCommands._registeredCommands) {
|
||||
if (typeof command === "function") {
|
||||
owner.sandbox[name] = command.bind(undefined, owner);
|
||||
}
|
||||
else if (typeof command === "object") {
|
||||
let clone = Object.assign({}, command, {
|
||||
// We force the enumerability and the configurability (so the
|
||||
// WebConsoleActor can reconfigure the property).
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
aOwner.helperResult = { rawOutput: true };
|
||||
|
||||
if (typeof aObject == "function") {
|
||||
return aObject + "\n";
|
||||
}
|
||||
|
||||
let output = [];
|
||||
|
||||
let obj = WebConsoleUtils.unwrap(aObject);
|
||||
for (let name in obj) {
|
||||
let desc = WebConsoleUtils.getPropertyDescriptor(obj, name) || {};
|
||||
if (desc.get || desc.set) {
|
||||
// TODO: Bug 842672 - toolkit/ imports modules from browser/.
|
||||
let getGrip = VariablesView.getGrip(desc.get);
|
||||
let setGrip = VariablesView.getGrip(desc.set);
|
||||
let getString = VariablesView.getString(getGrip);
|
||||
let setString = VariablesView.getString(setGrip);
|
||||
output.push(name + ":", " get: " + getString, " set: " + setString);
|
||||
if (typeof command.get === "function") {
|
||||
clone.get = command.get.bind(undefined, owner);
|
||||
}
|
||||
else {
|
||||
let valueGrip = VariablesView.getGrip(obj[name]);
|
||||
let valueString = VariablesView.getString(valueGrip);
|
||||
output.push(name + ": " + valueString);
|
||||
if (typeof command.set === "function") {
|
||||
clone.set = command.set.bind(undefined, owner);
|
||||
}
|
||||
}
|
||||
|
||||
return " " + output.join("\n ");
|
||||
};
|
||||
|
||||
/**
|
||||
* Print the String representation of a value to the output, as-is.
|
||||
*
|
||||
* @param any aValue
|
||||
* A value you want to output as a string.
|
||||
* @return void
|
||||
*/
|
||||
aOwner.sandbox.print = function JSTH_print(aValue)
|
||||
{
|
||||
aOwner.helperResult = { rawOutput: true };
|
||||
if (typeof aValue === "symbol") {
|
||||
return Symbol.prototype.toString.call(aValue);
|
||||
Object.defineProperty(owner.sandbox, name, clone);
|
||||
}
|
||||
// Waiving Xrays here allows us to see a closer representation of the
|
||||
// underlying object. This may execute arbitrary content code, but that
|
||||
// code will run with content privileges, and the result will be rendered
|
||||
// inert by coercing it to a String.
|
||||
return String(Cu.waiveXrays(aValue));
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy the String representation of a value to the clipboard.
|
||||
*
|
||||
* @param any aValue
|
||||
* A value you want to copy as a string.
|
||||
* @return void
|
||||
*/
|
||||
aOwner.sandbox.copy = function JSTH_copy(aValue)
|
||||
{
|
||||
let payload;
|
||||
try {
|
||||
if (aValue instanceof Ci.nsIDOMElement) {
|
||||
payload = aValue.outerHTML;
|
||||
} else if (typeof aValue == "string") {
|
||||
payload = aValue;
|
||||
} else {
|
||||
payload = JSON.stringify(aValue, null, " ");
|
||||
}
|
||||
} catch (ex) {
|
||||
payload = "/* " + ex + " */";
|
||||
}
|
||||
aOwner.helperResult = {
|
||||
type: "copyValueToClipboard",
|
||||
value: payload,
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.JSTermHelpers = JSTermHelpers;
|
||||
|
||||
exports.addWebConsoleCommands = addWebConsoleCommands;
|
||||
|
||||
/**
|
||||
* A ReflowObserver that listens for reflow events from the page.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
const {Cc, Ci, Cu} = require("chrome");
|
||||
|
||||
let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
|
||||
const {Utils: WebConsoleUtils, CONSOLE_WORKER_IDS} = require("devtools/toolkit/webconsole/utils");
|
||||
|
||||
loader.lazyServiceGetter(this, "clipboardHelper",
|
||||
"@mozilla.org/widget/clipboardhelper;1",
|
||||
@@ -26,6 +26,8 @@ loader.lazyGetter(this, "ConsoleOutput",
|
||||
() => require("devtools/webconsole/console-output").ConsoleOutput);
|
||||
loader.lazyGetter(this, "Messages",
|
||||
() => require("devtools/webconsole/console-output").Messages);
|
||||
loader.lazyGetter(this, "asyncStorage",
|
||||
() => require("devtools/toolkit/shared/async-storage"));
|
||||
loader.lazyImporter(this, "EnvironmentClient", "resource://gre/modules/devtools/dbg-client.jsm");
|
||||
loader.lazyImporter(this, "ObjectClient", "resource://gre/modules/devtools/dbg-client.jsm");
|
||||
loader.lazyImporter(this, "VariablesView", "resource://gre/modules/devtools/VariablesView.jsm");
|
||||
@@ -136,6 +138,10 @@ const LEVELS = {
|
||||
count: SEVERITY_LOG
|
||||
};
|
||||
|
||||
// This array contains the prefKey for the workers and it must keep them in the
|
||||
// same order as CONSOLE_WORKER_IDS
|
||||
const WORKERTYPES_PREFKEYS = [ 'sharedworkers', 'serviceworkers', 'windowlessworkers' ];
|
||||
|
||||
// The lowest HTTP response code (inclusive) that is considered an error.
|
||||
const MIN_HTTP_ERROR_CODE = 400;
|
||||
// The highest HTTP response code (inclusive) that is considered an error.
|
||||
@@ -176,6 +182,7 @@ const MIN_FONT_SIZE = 10;
|
||||
const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout";
|
||||
const PREF_PERSISTLOG = "devtools.webconsole.persistlog";
|
||||
const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";
|
||||
const PREF_INPUT_HISTORY_COUNT = "devtools.webconsole.inputHistoryCount";
|
||||
|
||||
/**
|
||||
* A WebConsoleFrame instance is an interactive console initialized *per target*
|
||||
@@ -199,7 +206,6 @@ function WebConsoleFrame(aWebConsoleOwner)
|
||||
this._outputQueue = [];
|
||||
this._itemDestroyQueue = [];
|
||||
this._pruneCategoriesQueue = {};
|
||||
this._networkRequests = {};
|
||||
this.filterPrefs = {};
|
||||
|
||||
this.output = new ConsoleOutput(this);
|
||||
@@ -247,14 +253,6 @@ WebConsoleFrame.prototype = {
|
||||
*/
|
||||
_initDefer: null,
|
||||
|
||||
/**
|
||||
* Holds the network requests currently displayed by the Web Console. Each key
|
||||
* represents the connection ID and the value is network request information.
|
||||
* @private
|
||||
* @type object
|
||||
*/
|
||||
_networkRequests: null,
|
||||
|
||||
/**
|
||||
* Last time when we displayed any message in the output.
|
||||
*
|
||||
@@ -447,12 +445,29 @@ WebConsoleFrame.prototype = {
|
||||
/**
|
||||
* Initialize the WebConsoleFrame instance.
|
||||
* @return object
|
||||
* A promise object for the initialization.
|
||||
* A promise object that resolves once the frame is ready to use.
|
||||
*/
|
||||
init: function WCF_init()
|
||||
init: function()
|
||||
{
|
||||
this._initUI();
|
||||
return this._initConnection();
|
||||
let connectionInited = this._initConnection();
|
||||
|
||||
// Don't reject if the history fails to load for some reason.
|
||||
// This would be fine, the panel will just start with empty history.
|
||||
let allReady = this.jsterm.historyLoaded.catch(() => {}).then(() => {
|
||||
return connectionInited;
|
||||
});
|
||||
|
||||
// This notification is only used in tests. Don't chain it onto
|
||||
// the returned promise because the console panel needs to be attached
|
||||
// to the toolbox before the web-console-created event is receieved.
|
||||
let notifyObservers = () => {
|
||||
let id = WebConsoleUtils.supportsString(this.hudId);
|
||||
Services.obs.notifyObservers(id, "web-console-created", null);
|
||||
};
|
||||
allReady.then(notifyObservers, notifyObservers);
|
||||
|
||||
return allReady;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -479,9 +494,6 @@ WebConsoleFrame.prototype = {
|
||||
aReason.error + ": " + aReason.message);
|
||||
this.outputMessage(CATEGORY_JS, node, [aReason]);
|
||||
this._initDefer.reject(aReason);
|
||||
}).then(() => {
|
||||
let id = WebConsoleUtils.supportsString(this.hudId);
|
||||
Services.obs.notifyObservers(id, "web-console-created", null);
|
||||
});
|
||||
|
||||
return this._initDefer.promise;
|
||||
@@ -632,7 +644,8 @@ WebConsoleFrame.prototype = {
|
||||
{
|
||||
let prefs = ["network", "networkinfo", "csserror", "cssparser", "csslog",
|
||||
"exception", "jswarn", "jslog", "error", "info", "warn", "log",
|
||||
"secerror", "secwarn", "netwarn", "netxhr"];
|
||||
"secerror", "secwarn", "netwarn", "netxhr", "sharedworkers",
|
||||
"serviceworkers", "windowlessworkers"];
|
||||
for (let pref of prefs) {
|
||||
this.filterPrefs[pref] = Services.prefs
|
||||
.getBoolPref(this._filterPrefsPrefix + pref);
|
||||
@@ -1009,8 +1022,11 @@ WebConsoleFrame.prototype = {
|
||||
// (filter="error", filter="cssparser", etc.) and add or remove the
|
||||
// "filtered-by-type" class, which turns on or off the display.
|
||||
|
||||
let attribute = WORKERTYPES_PREFKEYS.indexOf(aPrefKey) == -1
|
||||
? 'filter' : 'workerType';
|
||||
|
||||
let xpath = ".//*[contains(@class, 'message') and " +
|
||||
"@filter='" + aPrefKey + "']";
|
||||
"@" + attribute + "='" + aPrefKey + "']";
|
||||
let result = doc.evaluate(xpath, outputNode, null,
|
||||
Ci.nsIDOMXPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
|
||||
for (let i = 0; i < result.snapshotLength; i++) {
|
||||
@@ -1071,6 +1087,12 @@ WebConsoleFrame.prototype = {
|
||||
isFiltered = true;
|
||||
}
|
||||
|
||||
// Filter by worker type
|
||||
if ("workerType" in aNode && !this.getFilterState(aNode.workerType)) {
|
||||
aNode.classList.add("filtered-by-type");
|
||||
isFiltered = true;
|
||||
}
|
||||
|
||||
// Filter on the search string.
|
||||
let search = this.filterBox.value;
|
||||
let text = aNode.clipboardText;
|
||||
@@ -1192,6 +1214,9 @@ WebConsoleFrame.prototype = {
|
||||
this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage,
|
||||
[aMessage]);
|
||||
break;
|
||||
case "NetworkEvent":
|
||||
this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [aMessage]);
|
||||
break;
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
@@ -1348,6 +1373,12 @@ WebConsoleFrame.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
let workerTypeID = CONSOLE_WORKER_IDS.indexOf(aMessage.workerType);
|
||||
if (workerTypeID != -1) {
|
||||
node.workerType = WORKERTYPES_PREFKEYS[workerTypeID];
|
||||
node.setAttribute('workerType', WORKERTYPES_PREFKEYS[workerTypeID]);
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
|
||||
@@ -1486,19 +1517,14 @@ WebConsoleFrame.prototype = {
|
||||
/**
|
||||
* Log network event.
|
||||
*
|
||||
* @param object aActor
|
||||
* The network event actor to log.
|
||||
* @param object networkInfo
|
||||
* The network request information to log.
|
||||
* @return nsIDOMElement|null
|
||||
* The message element to display in the Web Console output.
|
||||
*/
|
||||
logNetEvent: function WCF_logNetEvent(aActor)
|
||||
logNetEvent: function(networkInfo)
|
||||
{
|
||||
let actorId = aActor.actor;
|
||||
let networkInfo = this._networkRequests[actorId];
|
||||
if (!networkInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let actorId = networkInfo.actor;
|
||||
let request = networkInfo.request;
|
||||
let clipboardText = request.method + " " + request.url;
|
||||
let severity = SEVERITY_LOG;
|
||||
@@ -1518,7 +1544,8 @@ WebConsoleFrame.prototype = {
|
||||
|
||||
let messageNode = this.createMessageNode(CATEGORY_NETWORK, severity,
|
||||
methodNode, null, null,
|
||||
clipboardText);
|
||||
clipboardText, null,
|
||||
networkInfo.timeStamp);
|
||||
if (networkInfo.private) {
|
||||
messageNode.setAttribute("private", true);
|
||||
}
|
||||
@@ -1756,84 +1783,29 @@ WebConsoleFrame.prototype = {
|
||||
/**
|
||||
* Handle the network events coming from the remote Web Console.
|
||||
*
|
||||
* @param object aActor
|
||||
* The NetworkEventActor grip.
|
||||
* @param object networkInfo
|
||||
* The network request information.
|
||||
*/
|
||||
handleNetworkEvent: function WCF_handleNetworkEvent(aActor)
|
||||
handleNetworkEvent: function(networkInfo)
|
||||
{
|
||||
let networkInfo = {
|
||||
node: null,
|
||||
actor: aActor.actor,
|
||||
discardRequestBody: true,
|
||||
discardResponseBody: true,
|
||||
startedDateTime: aActor.startedDateTime,
|
||||
request: {
|
||||
url: aActor.url,
|
||||
method: aActor.method,
|
||||
},
|
||||
isXHR: aActor.isXHR,
|
||||
response: {},
|
||||
timings: {},
|
||||
updates: [], // track the list of network event updates
|
||||
private: aActor.private,
|
||||
};
|
||||
|
||||
this._networkRequests[aActor.actor] = networkInfo;
|
||||
this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [aActor]);
|
||||
this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [networkInfo]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle network event updates coming from the server.
|
||||
*
|
||||
* @param string aActorId
|
||||
* The network event actor ID.
|
||||
* @param string aType
|
||||
* Update type.
|
||||
* @param object aPacket
|
||||
* @param object networkInfo
|
||||
* The network request information.
|
||||
* @param object packet
|
||||
* Update details.
|
||||
*/
|
||||
handleNetworkEventUpdate:
|
||||
function WCF_handleNetworkEventUpdate(aActorId, aType, aPacket)
|
||||
handleNetworkEventUpdate: function(networkInfo, packet)
|
||||
{
|
||||
let networkInfo = this._networkRequests[aActorId];
|
||||
if (!networkInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
networkInfo.updates.push(aType);
|
||||
|
||||
switch (aType) {
|
||||
case "requestHeaders":
|
||||
networkInfo.request.headersSize = aPacket.headersSize;
|
||||
break;
|
||||
case "requestPostData":
|
||||
networkInfo.discardRequestBody = aPacket.discardRequestBody;
|
||||
networkInfo.request.bodySize = aPacket.dataSize;
|
||||
break;
|
||||
case "responseStart":
|
||||
networkInfo.response.httpVersion = aPacket.response.httpVersion;
|
||||
networkInfo.response.status = aPacket.response.status;
|
||||
networkInfo.response.statusText = aPacket.response.statusText;
|
||||
networkInfo.response.headersSize = aPacket.response.headersSize;
|
||||
networkInfo.discardResponseBody = aPacket.response.discardResponseBody;
|
||||
break;
|
||||
case "responseContent":
|
||||
networkInfo.response.content = {
|
||||
mimeType: aPacket.mimeType,
|
||||
};
|
||||
networkInfo.response.bodySize = aPacket.contentSize;
|
||||
networkInfo.discardResponseBody = aPacket.discardResponseBody;
|
||||
break;
|
||||
case "eventTimings":
|
||||
networkInfo.totalTime = aPacket.totalTime;
|
||||
break;
|
||||
}
|
||||
|
||||
if (networkInfo.node && this._updateNetMessage(aActorId)) {
|
||||
if (networkInfo.node && this._updateNetMessage(packet.from)) {
|
||||
this.emit("new-messages", new Set([{
|
||||
update: true,
|
||||
node: networkInfo.node,
|
||||
response: aPacket,
|
||||
response: packet,
|
||||
}]));
|
||||
}
|
||||
|
||||
@@ -1858,7 +1830,7 @@ WebConsoleFrame.prototype = {
|
||||
*/
|
||||
_updateNetMessage: function WCF__updateNetMessage(aActorId)
|
||||
{
|
||||
let networkInfo = this._networkRequests[aActorId];
|
||||
let networkInfo = this.webConsoleClient.getNetworkRequest(aActorId);
|
||||
if (!networkInfo || !networkInfo.node) {
|
||||
return;
|
||||
}
|
||||
@@ -2404,8 +2376,8 @@ WebConsoleFrame.prototype = {
|
||||
else if (typeof methodOrNode != "function") {
|
||||
connectionId = methodOrNode._connectionId;
|
||||
}
|
||||
if (connectionId && connectionId in this._networkRequests) {
|
||||
delete this._networkRequests[connectionId];
|
||||
if (connectionId && this.webConsoleClient.hasNetworkRequest(connectionId)) {
|
||||
this.webConsoleClient.removeNetworkRequest(connectionId);
|
||||
this._releaseObject(connectionId);
|
||||
}
|
||||
}
|
||||
@@ -2482,7 +2454,7 @@ WebConsoleFrame.prototype = {
|
||||
}
|
||||
else if (aNode._connectionId &&
|
||||
aNode.category == CATEGORY_NETWORK) {
|
||||
delete this._networkRequests[aNode._connectionId];
|
||||
this.webConsoleClient.removeNetworkRequest(aNode._connectionId);
|
||||
this._releaseObject(aNode._connectionId);
|
||||
}
|
||||
else if (aNode.classList.contains("inlined-variables-view")) {
|
||||
@@ -2979,7 +2951,7 @@ WebConsoleFrame.prototype = {
|
||||
this._itemDestroyQueue.forEach(this._destroyItem, this);
|
||||
this._itemDestroyQueue = [];
|
||||
this._pruneCategoriesQueue = {};
|
||||
this._networkRequests = {};
|
||||
this.webConsoleClient.clearNetworkRequests();
|
||||
|
||||
if (this._outputTimerInitialized) {
|
||||
this._outputTimerInitialized = false;
|
||||
@@ -3058,17 +3030,11 @@ function JSTerm(aWebConsoleFrame)
|
||||
{
|
||||
this.hud = aWebConsoleFrame;
|
||||
this.hudId = this.hud.hudId;
|
||||
this.inputHistoryCount = Services.prefs.getIntPref(PREF_INPUT_HISTORY_COUNT);
|
||||
|
||||
this.lastCompletion = { value: null };
|
||||
this.history = [];
|
||||
this._loadHistory();
|
||||
|
||||
// Holds the number of entries in history. This value is incremented in
|
||||
// this.execute().
|
||||
this.historyIndex = 0; // incremented on this.execute()
|
||||
|
||||
// Holds the index of the history entry that the user is currently viewing.
|
||||
// This is reset to this.history.length when this.execute() is invoked.
|
||||
this.historyPlaceHolder = 0;
|
||||
this._objectActorsInVariablesViews = new Map();
|
||||
|
||||
this._keyPress = this._keyPress.bind(this);
|
||||
@@ -3083,6 +3049,53 @@ function JSTerm(aWebConsoleFrame)
|
||||
JSTerm.prototype = {
|
||||
SELECTED_FRAME: -1,
|
||||
|
||||
/**
|
||||
* Load the console history from previous sessions.
|
||||
* @private
|
||||
*/
|
||||
_loadHistory: function() {
|
||||
this.history = [];
|
||||
this.historyIndex = this.historyPlaceHolder = 0;
|
||||
|
||||
this.historyLoaded = asyncStorage.getItem("webConsoleHistory").then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
// Since it was gotten asynchronously, there could be items already in
|
||||
// the history. It's not likely but stick them onto the end anyway.
|
||||
this.history = value.concat(this.history);
|
||||
|
||||
// Holds the number of entries in history. This value is incremented in
|
||||
// this.execute().
|
||||
this.historyIndex = this.history.length;
|
||||
|
||||
// Holds the index of the history entry that the user is currently viewing.
|
||||
// This is reset to this.history.length when this.execute() is invoked.
|
||||
this.historyPlaceHolder = this.history.length;
|
||||
}
|
||||
}, console.error);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the console history altogether. Note that this will not affect
|
||||
* other consoles that are already opened (since they have their own copy),
|
||||
* but it will reset the array for all newly-opened consoles.
|
||||
* @returns Promise
|
||||
* Resolves once the changes have been persisted.
|
||||
*/
|
||||
clearHistory: function() {
|
||||
this.history = [];
|
||||
this.historyIndex = this.historyPlaceHolder = 0;
|
||||
return this.storeHistory();
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores the console history for future console instances.
|
||||
* @returns Promise
|
||||
* Resolves once the changes have been persisted.
|
||||
*/
|
||||
storeHistory: function() {
|
||||
return asyncStorage.setItem("webConsoleHistory", this.history);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores the data for the last completion.
|
||||
* @type object
|
||||
@@ -3273,6 +3286,9 @@ JSTerm.prototype = {
|
||||
case "clearOutput":
|
||||
this.clearOutput();
|
||||
break;
|
||||
case "clearHistory":
|
||||
this.clearHistory();
|
||||
break;
|
||||
case "inspectObject":
|
||||
if (aAfterMessage) {
|
||||
if (!aAfterMessage._objectActors) {
|
||||
@@ -3392,6 +3408,12 @@ JSTerm.prototype = {
|
||||
// value that was not evaluated yet.
|
||||
this.history[this.historyIndex++] = aExecuteString;
|
||||
this.historyPlaceHolder = this.history.length;
|
||||
|
||||
if (this.history.length > this.inputHistoryCount) {
|
||||
this.history.splice(0, this.history.length - this.inputHistoryCount);
|
||||
this.historyIndex = this.historyPlaceHolder = this.history.length;
|
||||
}
|
||||
this.storeHistory();
|
||||
WebConsoleUtils.usageCount++;
|
||||
this.setInputValue("");
|
||||
this.clearCompletion();
|
||||
@@ -3883,7 +3905,7 @@ JSTerm.prototype = {
|
||||
hud.groupDepth = 0;
|
||||
hud._outputQueue.forEach(hud._destroyItem, hud);
|
||||
hud._outputQueue = [];
|
||||
hud._networkRequests = {};
|
||||
this.webConsoleClient.clearNetworkRequests();
|
||||
hud._repeatNodes = {};
|
||||
|
||||
if (aClearStorage) {
|
||||
@@ -5023,8 +5045,6 @@ WebConsoleConnectionProxy.prototype = {
|
||||
client.addListener("logMessage", this._onLogMessage);
|
||||
client.addListener("pageError", this._onPageError);
|
||||
client.addListener("consoleAPICall", this._onConsoleAPICall);
|
||||
client.addListener("networkEvent", this._onNetworkEvent);
|
||||
client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
client.addListener("fileActivity", this._onFileActivity);
|
||||
client.addListener("reflowActivity", this._onReflowActivity);
|
||||
client.addListener("lastPrivateContextExited", this._onLastPrivateContextExited);
|
||||
@@ -5089,6 +5109,8 @@ WebConsoleConnectionProxy.prototype = {
|
||||
this.webConsoleClient = aWebConsoleClient;
|
||||
|
||||
this._hasNativeConsoleAPI = aResponse.nativeConsoleAPI;
|
||||
this.webConsoleClient.on("networkEvent", this._onNetworkEvent);
|
||||
this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
|
||||
let msgs = ["PageError", "ConsoleAPI"];
|
||||
this.webConsoleClient.getCachedMessages(msgs, this._onCachedMessages);
|
||||
@@ -5118,7 +5140,10 @@ WebConsoleConnectionProxy.prototype = {
|
||||
Cu.reportError("Web Console getCachedMessages error: invalid state.");
|
||||
}
|
||||
|
||||
this.owner.displayCachedMessages(aResponse.messages);
|
||||
let messages = aResponse.messages.concat(...this.webConsoleClient.getNetworkEvents());
|
||||
messages.sort((a, b) => a.timeStamp - b.timeStamp);
|
||||
|
||||
this.owner.displayCachedMessages(messages);
|
||||
|
||||
if (!this._hasNativeConsoleAPI) {
|
||||
this.owner.logWarningAboutReplacedAPI();
|
||||
@@ -5184,15 +5209,15 @@ WebConsoleConnectionProxy.prototype = {
|
||||
* the UI for displaying.
|
||||
*
|
||||
* @private
|
||||
* @param string aType
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object aPacket
|
||||
* The message received from the server.
|
||||
* @param object networkInfo
|
||||
* The network request information.
|
||||
*/
|
||||
_onNetworkEvent: function WCCP__onNetworkEvent(aType, aPacket)
|
||||
_onNetworkEvent: function(type, networkInfo)
|
||||
{
|
||||
if (this.owner && aPacket.from == this._consoleActor) {
|
||||
this.owner.handleNetworkEvent(aPacket.eventActor);
|
||||
if (this.owner) {
|
||||
this.owner.handleNetworkEvent(networkInfo);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5201,16 +5226,17 @@ WebConsoleConnectionProxy.prototype = {
|
||||
* the UI for displaying.
|
||||
*
|
||||
* @private
|
||||
* @param string aType
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object aPacket
|
||||
* @param object packet
|
||||
* The message received from the server.
|
||||
* @param object networkInfo
|
||||
* The network request information.
|
||||
*/
|
||||
_onNetworkEventUpdate: function WCCP__onNetworkEvenUpdatet(aType, aPacket)
|
||||
_onNetworkEventUpdate: function(type, { packet, networkInfo })
|
||||
{
|
||||
if (this.owner) {
|
||||
this.owner.handleNetworkEventUpdate(aPacket.from, aPacket.updateType,
|
||||
aPacket);
|
||||
this.owner.handleNetworkEventUpdate(networkInfo, packet);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5310,11 +5336,11 @@ WebConsoleConnectionProxy.prototype = {
|
||||
this.client.removeListener("logMessage", this._onLogMessage);
|
||||
this.client.removeListener("pageError", this._onPageError);
|
||||
this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
|
||||
this.client.removeListener("networkEvent", this._onNetworkEvent);
|
||||
this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
this.client.removeListener("fileActivity", this._onFileActivity);
|
||||
this.client.removeListener("reflowActivity", this._onReflowActivity);
|
||||
this.client.removeListener("lastPrivateContextExited", this._onLastPrivateContextExited);
|
||||
this.webConsoleClient.off("networkEvent", this._onNetworkEvent);
|
||||
this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
this.target.off("will-navigate", this._onTabNavigated);
|
||||
this.target.off("navigate", this._onTabNavigated);
|
||||
|
||||
|
||||
@@ -163,6 +163,13 @@ function goUpdateConsoleCommands() {
|
||||
prefKey="info"/>
|
||||
<menuitem label="&btnConsoleLog;" type="checkbox" autocheck="false"
|
||||
prefKey="log"/>
|
||||
<menuseparator />
|
||||
<menuitem label="&btnConsoleSharedWorkers;" type="checkbox"
|
||||
autocheck="false" prefKey="sharedworkers"/>
|
||||
<menuitem label="&btnConsoleServiceWorkers;" type="checkbox"
|
||||
autocheck="false" prefKey="serviceworkers"/>
|
||||
<menuitem label="&btnConsoleWindowlessWorkers;" type="checkbox"
|
||||
autocheck="false" prefKey="windowlessworkers"/>
|
||||
</menupopup>
|
||||
</toolbarbutton>
|
||||
</hbox>
|
||||
|
||||
@@ -51,14 +51,22 @@
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerUI.table.*): These strings are displayed
|
||||
- in the call tree headers for a recording. -->
|
||||
<!ENTITY profilerUI.table.totalDuration2 "Total Time">
|
||||
<!ENTITY profilerUI.table.selfDuration2 "Self Time">
|
||||
<!ENTITY profilerUI.table.totalPercentage "Total Cost">
|
||||
<!ENTITY profilerUI.table.selfPercentage "Self Cost">
|
||||
<!ENTITY profilerUI.table.samples "Samples">
|
||||
<!ENTITY profilerUI.table.totalAlloc "Total Allocations">
|
||||
<!ENTITY profilerUI.table.selfAlloc "Self Allocations">
|
||||
<!ENTITY profilerUI.table.function "Function">
|
||||
<!ENTITY profilerUI.table.totalDuration2 "Total Time">
|
||||
<!ENTITY profilerUI.table.totalDuration.tooltip "The amount of time spent in this function and functions it calls.">
|
||||
<!ENTITY profilerUI.table.selfDuration2 "Self Time">
|
||||
<!ENTITY profilerUI.table.selfDuration.tooltip "The amount of time spent only within this function.">
|
||||
<!ENTITY profilerUI.table.totalPercentage "Total Cost">
|
||||
<!ENTITY profilerUI.table.totalPercentage.tooltip "The percentage of time spent in this function and functions it calls.">
|
||||
<!ENTITY profilerUI.table.selfPercentage "Self Cost">
|
||||
<!ENTITY profilerUI.table.selfPercentage.tooltip "The percentage of time spent only within this function.">
|
||||
<!ENTITY profilerUI.table.samples "Samples">
|
||||
<!ENTITY profilerUI.table.samples.tooltip "The number of times this function was on the stack when the profiler took a sample.">
|
||||
<!ENTITY profilerUI.table.function "Function">
|
||||
<!ENTITY profilerUI.table.function.tooltip "The name and source location of the sampled function.">
|
||||
<!ENTITY profilerUI.table.totalAlloc1 "Total Sampled Allocations">
|
||||
<!ENTITY profilerUI.table.totalAlloc.tooltip "The total number of Object allocations sampled at this location and in callees.">
|
||||
<!ENTITY profilerUI.table.selfAlloc1 "Self Sampled Allocations">
|
||||
<!ENTITY profilerUI.table.selfAlloc.tooltip "The number of Object allocations sampled at this location.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerUI.newtab.tooltiptext): The tooltiptext shown
|
||||
- on the "+" (new tab) button for a profile when a selection is available. -->
|
||||
|
||||
@@ -76,6 +76,16 @@
|
||||
<!ENTITY btnConsoleXhr "XHR">
|
||||
<!ENTITY btnConsoleReflows "Reflows">
|
||||
|
||||
<!-- LOCALIZATION NODE (btnConsoleSharedWorkers) the term "Shared Workers"
|
||||
- should not be translated. -->
|
||||
<!ENTITY btnConsoleSharedWorkers "Shared Workers">
|
||||
<!-- LOCALIZATION NODE (btnConsoleServiceWorkers) the term "Service Workers"
|
||||
- should not be translated. -->
|
||||
<!ENTITY btnConsoleServiceWorkers "Service Workers">
|
||||
<!-- LOCALIZATION NODE (btnConsoleWindowlessWorkers) the term "Workers"
|
||||
- should not be translated. -->
|
||||
<!ENTITY btnConsoleWindowlessWorkers "Add-on or Chrome Workers">
|
||||
|
||||
<!ENTITY filterOutput.placeholder "Filter output">
|
||||
<!ENTITY btnClear.label "Clear">
|
||||
<!ENTITY btnClear.tooltip "Clear the Web Console output">
|
||||
|
||||
@@ -99,7 +99,7 @@ body {
|
||||
background-image: url("debugger-play.png");
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#element-picker::before {
|
||||
background-image: url("chrome://global/skin/devtools/command-pick@2x.png");
|
||||
background-size: 64px;
|
||||
@@ -351,7 +351,7 @@ body {
|
||||
background-image: url(rewind.png);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.timeline .toggle::before {
|
||||
background-image: url(debugger-pause@2x.png);
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
list-style-image: url(debugger-step-out.png);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#resume {
|
||||
list-style-image: url(debugger-play@2x.png);
|
||||
-moz-image-region: rect(0px,64px,32px,32px);
|
||||
@@ -250,7 +250,7 @@
|
||||
background-size: 12px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.selected .call-item-gutter {
|
||||
background-image: url("editor-debug-location@2x.png");
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
-moz-image-region: rect(0px, 64px, 16px, 48px);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#developer-toolbar-toolbox-button {
|
||||
list-style-image: url("chrome://global/skin/devtools/toggle-tools@2x.png");
|
||||
-moz-image-region: rect(0px, 32px, 32px, 0px);
|
||||
@@ -111,7 +111,7 @@
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#developer-toolbar-closebutton {
|
||||
list-style-image: url("chrome://global/skin/devtools/close@2x.png");
|
||||
}
|
||||
@@ -199,7 +199,7 @@ html|*#gcli-output-frame {
|
||||
background-position: -16px center;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.gclitoolbar-input-node::before {
|
||||
background-image: url("chrome://global/skin/devtools/commandline-icon@2x.png");
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ body {
|
||||
background-size: 5px 8px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.property-value, .other-property-value {
|
||||
background-image: url(arrow-e@2x.png);
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ div.CodeMirror span.eval-text {
|
||||
background-position: -42px 0;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.theme-twisty, .theme-checkbox {
|
||||
background-image: url("chrome://global/skin/devtools/controls@2x.png");
|
||||
}
|
||||
@@ -372,7 +372,7 @@ div.CodeMirror span.eval-text {
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.theme-tooltip-panel .panel-arrow[side="top"],
|
||||
.theme-tooltip-panel .panel-arrow[side="bottom"] {
|
||||
list-style-image: url("chrome://global/skin/devtools/tooltip/arrow-vertical-dark@2x.png");
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
list-style-image: url(debugger-blackbox.png);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#black-box {
|
||||
list-style-image: url(debugger-blackbox@2x.png);
|
||||
}
|
||||
@@ -76,7 +76,7 @@
|
||||
list-style-image: url(debugger-prettyprint.png);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#pretty-print {
|
||||
list-style-image: url(debugger-prettyprint@2x.png);
|
||||
}
|
||||
@@ -86,7 +86,7 @@
|
||||
list-style-image: url(debugger-toggleBreakpoints.png);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#toggle-breakpoints {
|
||||
list-style-image: url(debugger-toggleBreakpoints@2x.png);
|
||||
}
|
||||
@@ -100,7 +100,7 @@
|
||||
-moz-image-region: rect(0px,32px,16px,16px);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#sources-toolbar .devtools-toolbarbutton:not([label]) {
|
||||
-moz-image-region: rect(0px,32px,32px,0px);
|
||||
}
|
||||
@@ -134,7 +134,7 @@
|
||||
-moz-margin-end: 5px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#black-boxed-message-button > .button-box > .button-icon {
|
||||
background-image: url(debugger-blackbox@2x.png);
|
||||
}
|
||||
@@ -222,7 +222,7 @@
|
||||
-moz-image-region: rect(0px,32px,16px,16px);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#trace {
|
||||
list-style-image: url(tracer-icon@2x.png);
|
||||
-moz-image-region: rect(0px,32px,32px,0px);
|
||||
@@ -324,7 +324,7 @@
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.dbg-expression-arrow {
|
||||
background-image: url(commandline-icon@2x.png);
|
||||
}
|
||||
@@ -560,7 +560,7 @@
|
||||
list-style-image: url(debugger-play.png);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#resume {
|
||||
list-style-image: url(debugger-pause@2x.png);
|
||||
-moz-image-region: rect(0px,32px,32px,0px);
|
||||
@@ -592,7 +592,7 @@
|
||||
list-style-image: url(debugger-step-out.png);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#step-over {
|
||||
list-style-image: url(debugger-step-over@2x.png);
|
||||
}
|
||||
@@ -622,7 +622,7 @@
|
||||
-moz-image-region: rect(0px,32px,16px,16px);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#instruments-pane-toggle {
|
||||
list-style-image: url(debugger-collapse@2x.png);
|
||||
-moz-image-region: rect(0px,32px,32px,0px);
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
-moz-image-region: rect(0px,32px,16px,16px);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#inspector-pane-toggle {
|
||||
list-style-image: url(debugger-collapse@2x.png);
|
||||
-moz-image-region: rect(0px,32px,32px,0px);
|
||||
|
||||
@@ -343,7 +343,7 @@ div.CodeMirror span.eval-text {
|
||||
background-position: -14px 0;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.theme-twisty, .theme-checkbox {
|
||||
background-image: url("chrome://global/skin/devtools/controls@2x.png");
|
||||
}
|
||||
@@ -381,7 +381,7 @@ div.CodeMirror span.eval-text {
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.theme-tooltip-panel .panel-arrow[side="top"],
|
||||
.theme-tooltip-panel .panel-arrow[side="bottom"] {
|
||||
list-style-image: url("chrome://global/skin/devtools/tooltip/arrow-vertical-light@2x.png");
|
||||
|
||||
@@ -477,7 +477,7 @@ label.requests-menu-status-code {
|
||||
-moz-image-region: rect(0px,32px,16px,16px);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#details-pane-toggle {
|
||||
list-style-image: url("chrome://global/skin/devtools/debugger-collapse@2x.png");
|
||||
-moz-image-region: rect(0px,32px,32px,0px);
|
||||
@@ -606,7 +606,7 @@ label.requests-menu-status-code {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.security-warning-icon {
|
||||
background-image: url(alerticon-warning@2x.png);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
/* CSS Variables specific to this panel that aren't defined by the themes */
|
||||
.theme-dark {
|
||||
--cell-border-color: rgba(255,255,255,0.15);
|
||||
--cell-border-color-light: rgba(255,255,255,0.1);
|
||||
--focus-cell-border-color: rgba(255,255,255,0.5);
|
||||
--row-alt-background-color: rgba(29,79,115,0.15);
|
||||
--row-hover-background-color: rgba(29,79,115,0.25);
|
||||
@@ -13,6 +14,7 @@
|
||||
|
||||
.theme-light {
|
||||
--cell-border-color: rgba(0,0,0,0.15);
|
||||
--cell-border-color-light: rgba(0,0,0,0.1);
|
||||
--focus-cell-border-color: rgba(0,0,0,0.3);
|
||||
--row-alt-background-color: rgba(76,158,217,0.1);
|
||||
--row-hover-background-color: rgba(76,158,217,0.2);
|
||||
@@ -20,19 +22,10 @@
|
||||
|
||||
/* Toolbar */
|
||||
|
||||
#performance-toolbar > tabs,
|
||||
#performance-toolbar {
|
||||
-moz-border-end-color: var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
#performance-toolbar-control-other {
|
||||
-moz-padding-end: 5px;
|
||||
}
|
||||
|
||||
#performance-toolbar-controls-detail-views > toolbarbutton {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#performance-toolbar-controls-detail-views .toolbarbutton-text {
|
||||
-moz-padding-start: 4px;
|
||||
-moz-padding-end: 8px;
|
||||
@@ -60,117 +53,7 @@
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
/* Recording Notice */
|
||||
|
||||
#performance-view .notice-container {
|
||||
font-size: 120%;
|
||||
background-color: var(--theme-toolbar-background);
|
||||
color: var(--theme-body-color);
|
||||
padding-bottom: 20vh;
|
||||
}
|
||||
|
||||
#performance-view .notice-container button {
|
||||
min-width: 30px;
|
||||
min-height: 28px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#performance-view .notice-container vbox {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.console-profile-command {
|
||||
font-family: monospace;
|
||||
margin: 3px 2px;
|
||||
}
|
||||
|
||||
.theme-dark #performance-view toolbarbutton.record-button {
|
||||
background-color: var(--theme-tab-toolbar-background);
|
||||
border: 1px solid var(--theme-sidebar-background);
|
||||
}
|
||||
|
||||
.theme-light #performance-view toolbarbutton.record-button {
|
||||
background-color: rgba(221, 221, 221, 1);
|
||||
border: 1px solid rgba(204, 204, 204, 1);
|
||||
}
|
||||
|
||||
#performance-view toolbarbutton.record-button {
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
#performance-view .realtime-message {
|
||||
opacity: 0.5;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#performance-view toolbarbutton.record-button[checked],
|
||||
#performance-view toolbarbutton.record-button[checked] {
|
||||
color: var(--theme-selection-color);
|
||||
background: var(--theme-selection-background);
|
||||
}
|
||||
|
||||
#performance-view .realtime-disabled-message,
|
||||
#performance-view .realtime-disabled-on-e10s-message {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#performance-view[e10s="disabled"] .realtime-disabled-on-e10s-message {
|
||||
display: block;
|
||||
opacity: 0.5;
|
||||
|
||||
}
|
||||
#performance-view[e10s="unsupported"] .realtime-disabled-message {
|
||||
display: block;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#details-pane-container .buffer-status-message,
|
||||
#details-pane-container .buffer-status-message-full {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#details-pane-container[buffer-status="in-progress"] .buffer-status-message {
|
||||
display: block;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#details-pane-container[buffer-status="full"] .buffer-status-message {
|
||||
display: block;
|
||||
color: var(--theme-highlight-red);
|
||||
font-weight: bold;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#details-pane-container[buffer-status="full"] .buffer-status-message-full {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Overview Panel */
|
||||
|
||||
#main-record-button {
|
||||
list-style-image: url(profiler-stopwatch.svg);
|
||||
}
|
||||
|
||||
#main-record-button[checked] {
|
||||
list-style-image: url(profiler-stopwatch-checked.svg);
|
||||
}
|
||||
|
||||
#main-record-button[locked] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#main-record-button .button-icon {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#main-record-button .button-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Details Panel */
|
||||
/* Details panel buttons */
|
||||
|
||||
#select-waterfall-view {
|
||||
list-style-image: url(performance-icons.svg#details-waterfall);
|
||||
@@ -186,11 +69,126 @@
|
||||
list-style-image: url(performance-icons.svg#details-flamegraph);
|
||||
}
|
||||
|
||||
/* Recording buttons */
|
||||
|
||||
#main-record-button {
|
||||
list-style-image: url(profiler-stopwatch.svg);
|
||||
}
|
||||
|
||||
#main-record-button[checked] {
|
||||
list-style-image: url(profiler-stopwatch-checked.svg);
|
||||
}
|
||||
|
||||
#main-record-button .button-icon {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#main-record-button .button-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.notice-container .record-button {
|
||||
padding: 5px !important;
|
||||
}
|
||||
|
||||
.notice-container .record-button[checked],
|
||||
.notice-container .record-button[checked] {
|
||||
color: var(--theme-selection-color) !important;
|
||||
background: var(--theme-selection-background) !important;
|
||||
}
|
||||
|
||||
.record-button[locked] {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Sidebar & recording items */
|
||||
|
||||
.recording-item {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.recording-item-title {
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
.recording-item-footer {
|
||||
padding-top: 4px;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.recording-item-save {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.recording-item-duration,
|
||||
.recording-item-save {
|
||||
color: var(--theme-body-color-alt);
|
||||
}
|
||||
|
||||
#recordings-list .selected label {
|
||||
/* Text inside a selected item should not be custom colored. */
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
/* Recording notices */
|
||||
|
||||
.notice-container {
|
||||
font-size: 120%;
|
||||
background-color: var(--theme-toolbar-background);
|
||||
color: var(--theme-body-color);
|
||||
padding-bottom: 20vh;
|
||||
}
|
||||
|
||||
.console-profile-command {
|
||||
font-family: monospace;
|
||||
margin: 3px 2px;
|
||||
}
|
||||
|
||||
.realtime-disabled-message,
|
||||
.realtime-disabled-on-e10s-message {
|
||||
display: none;
|
||||
/* This label does not want to wrap naturally (based on some combination of
|
||||
it's parents and flex). Quick and dirty way to force it to wrap -> don't
|
||||
let it get bigger than half the screen size */
|
||||
max-width: 60vw;
|
||||
}
|
||||
|
||||
#performance-view[e10s="disabled"] .realtime-disabled-on-e10s-message {
|
||||
display: auto;
|
||||
opacity: 0.5;
|
||||
|
||||
}
|
||||
#performance-view[e10s="unsupported"] .realtime-disabled-message {
|
||||
display: auto;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.buffer-status-message,
|
||||
.buffer-status-message-full {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#details-pane-container[buffer-status="in-progress"] .buffer-status-message {
|
||||
display: auto;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#details-pane-container[buffer-status="full"] .buffer-status-message {
|
||||
display: auto;
|
||||
color: var(--theme-highlight-red);
|
||||
font-weight: bold;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#details-pane-container[buffer-status="full"] .buffer-status-message-full {
|
||||
display: auto;
|
||||
}
|
||||
|
||||
/* Profile call tree */
|
||||
|
||||
.call-tree-cells-container {
|
||||
/* Hack: force hardware acceleration */
|
||||
transform: translateZ(1px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@@ -227,7 +225,7 @@
|
||||
.call-tree-cell[type="allocations"],
|
||||
.call-tree-header[type="self-allocations"],
|
||||
.call-tree-cell[type="self-allocations"] {
|
||||
width: 7vw;
|
||||
width: 9vw;
|
||||
}
|
||||
|
||||
.call-tree-header[type="function"],
|
||||
@@ -262,7 +260,7 @@
|
||||
background-color: var(--theme-tab-toolbar-background);
|
||||
}
|
||||
|
||||
.call-tree-item:last-child:not(:focus) {
|
||||
.call-tree-item:last-child {
|
||||
border-bottom: 1px solid var(--cell-border-color);
|
||||
}
|
||||
|
||||
@@ -278,7 +276,7 @@
|
||||
background-color: var(--theme-selection-background);
|
||||
}
|
||||
|
||||
.call-tree-item:focus label {
|
||||
.call-tree-item:focus description {
|
||||
color: var(--theme-selection-color) !important;
|
||||
}
|
||||
|
||||
@@ -294,8 +292,11 @@
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.call-tree-name {
|
||||
-moz-margin-end: 4px !important;
|
||||
}
|
||||
|
||||
.call-tree-url {
|
||||
-moz-margin-start: 4px !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -322,113 +323,19 @@
|
||||
color: var(--theme-content-color2);
|
||||
}
|
||||
|
||||
.call-tree-name[value=""],
|
||||
.call-tree-url[value=""],
|
||||
.call-tree-line[value=""],
|
||||
.call-tree-column[value=""],
|
||||
.call-tree-host[value=""] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.call-tree-zoom {
|
||||
-moz-appearance: none;
|
||||
background-color: transparent;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 11px;
|
||||
min-width: 11px;
|
||||
-moz-margin-start: 8px !important;
|
||||
cursor: zoom-in;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.theme-dark .call-tree-zoom {
|
||||
background-image: url(magnifying-glass.png);
|
||||
}
|
||||
|
||||
.theme-light .call-tree-zoom {
|
||||
background-image: url(magnifying-glass-light.png);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
.theme-dark .call-tree-zoom {
|
||||
background-image: url(magnifying-glass@2x.png);
|
||||
}
|
||||
|
||||
.theme-light .call-tree-zoom {
|
||||
background-image: url(magnifying-glass-light@2x.png);
|
||||
}
|
||||
}
|
||||
|
||||
.call-tree-item:hover .call-tree-zoom {
|
||||
transition: opacity 0.3s ease-in;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.call-tree-item:hover .call-tree-zoom:hover {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.call-tree-category {
|
||||
transform: scale(0.75);
|
||||
transform-origin: center right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Details Waterfall Styles
|
||||
* Waterfall ticks header
|
||||
*/
|
||||
|
||||
.waterfall-list-contents {
|
||||
/* Hack: force hardware acceleration */
|
||||
transform: translateZ(1px);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.waterfall-header-contents {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.waterfall-background-ticks {
|
||||
/* Background created on a <canvas> in js. */
|
||||
/* @see browser/devtools/timeline/widgets/waterfall.js */
|
||||
background-image: -moz-element(#waterfall-background);
|
||||
background-repeat: repeat-y;
|
||||
background-position: -1px center;
|
||||
}
|
||||
|
||||
.waterfall-marker-container[is-spacer] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.theme-dark .waterfall-marker-container:not([is-spacer]):nth-child(2n) {
|
||||
background-color: rgba(255,255,255,0.03);
|
||||
}
|
||||
|
||||
.theme-light .waterfall-marker-container:not([is-spacer]):nth-child(2n) {
|
||||
background-color: rgba(128,128,128,0.03);
|
||||
}
|
||||
|
||||
.theme-dark .waterfall-marker-container:hover {
|
||||
background-color: rgba(255,255,255,0.1) !important;
|
||||
}
|
||||
|
||||
.theme-light .waterfall-marker-container:hover {
|
||||
background-color: rgba(128,128,128,0.1) !important;
|
||||
}
|
||||
|
||||
.waterfall-marker-item {
|
||||
.waterfall-header-ticks {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.waterfall-sidebar {
|
||||
-moz-border-end: 1px solid var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
.waterfall-marker-container:hover > .waterfall-sidebar {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.waterfall-header-name {
|
||||
padding: 2px 4px;
|
||||
font-size: 90%;
|
||||
@@ -445,6 +352,104 @@
|
||||
-moz-margin-start: -100px !important; /* Don't affect layout. */
|
||||
}
|
||||
|
||||
.waterfall-background-ticks {
|
||||
/* Background created on a <canvas> in js. */
|
||||
/* @see browser/devtools/timeline/widgets/waterfall.js */
|
||||
background-image: -moz-element(#waterfall-background);
|
||||
background-repeat: repeat-y;
|
||||
background-position: -1px center;
|
||||
}
|
||||
|
||||
/**
|
||||
* Markers waterfall breakdown
|
||||
*/
|
||||
|
||||
#waterfall-breakdown {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.theme-light .waterfall-tree-item:not([level="0"]) {
|
||||
background-image: repeating-linear-gradient(
|
||||
-45deg,
|
||||
transparent 0px,
|
||||
transparent 2px,
|
||||
rgba(0,0,0,0.025) 2px,
|
||||
rgba(0,0,0,0.025) 4px
|
||||
);
|
||||
}
|
||||
|
||||
.theme-dark .waterfall-tree-item:not([level="0"]) {
|
||||
background-image: repeating-linear-gradient(
|
||||
-45deg,
|
||||
transparent 0px,
|
||||
transparent 2px,
|
||||
rgba(255,255,255,0.05) 2px,
|
||||
rgba(255,255,255,0.05) 4px
|
||||
);
|
||||
}
|
||||
|
||||
.theme-light .waterfall-tree-item[expandable] .waterfall-marker-bullet,
|
||||
.theme-light .waterfall-tree-item[expandable] .waterfall-marker-bar {
|
||||
background-image: repeating-linear-gradient(
|
||||
-45deg,
|
||||
transparent 0px,
|
||||
transparent 5px,
|
||||
rgba(255,255,255,0.35) 5px,
|
||||
rgba(255,255,255,0.35) 10px
|
||||
);
|
||||
}
|
||||
|
||||
.theme-dark .waterfall-tree-item[expandable] .waterfall-marker-bullet,
|
||||
.theme-dark .waterfall-tree-item[expandable] .waterfall-marker-bar {
|
||||
background-image: repeating-linear-gradient(
|
||||
-45deg,
|
||||
transparent 0px,
|
||||
transparent 5px,
|
||||
rgba(0,0,0,0.35) 5px,
|
||||
rgba(0,0,0,0.35) 10px
|
||||
);
|
||||
}
|
||||
|
||||
.waterfall-tree-item[expanded],
|
||||
.waterfall-tree-item:not([level="0"]) + .waterfall-tree-item[level="0"] {
|
||||
box-shadow: 0 -1px var(--cell-border-color-light);
|
||||
}
|
||||
|
||||
.waterfall-tree-item:nth-child(2n) > .waterfall-marker {
|
||||
background-color: var(--row-alt-background-color);
|
||||
}
|
||||
|
||||
.waterfall-tree-item:hover {
|
||||
background-color: var(--row-hover-background-color);
|
||||
}
|
||||
|
||||
.waterfall-tree-item:last-child {
|
||||
border-bottom: 1px solid var(--cell-border-color);
|
||||
}
|
||||
|
||||
.waterfall-tree-item:focus {
|
||||
background-color: var(--theme-selection-background);
|
||||
}
|
||||
|
||||
.waterfall-tree-item:focus description {
|
||||
color: var(--theme-selection-color) !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker left sidebar
|
||||
*/
|
||||
|
||||
.waterfall-sidebar {
|
||||
-moz-border-end: 1px solid var(--cell-border-color);
|
||||
}
|
||||
|
||||
.waterfall-tree-item > .waterfall-sidebar:hover,
|
||||
.waterfall-tree-item:hover > .waterfall-sidebar,
|
||||
.waterfall-tree-item:focus > .waterfall-sidebar {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.waterfall-marker-bullet {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
@@ -458,23 +463,40 @@
|
||||
padding-bottom: 1px !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker timebar
|
||||
*/
|
||||
|
||||
.waterfall-marker {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.waterfall-marker-bar {
|
||||
height: 9px;
|
||||
transform-origin: left center;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.waterfall-marker-container.selected > .waterfall-sidebar,
|
||||
.waterfall-marker-container.selected > .waterfall-marker-item {
|
||||
background-color: var(--theme-selection-background);
|
||||
color: var(--theme-selection-color);
|
||||
.waterfall-marker > .theme-twisty {
|
||||
/* Don't affect layout. */
|
||||
width: 14px;
|
||||
-moz-margin-end: -14px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker details view
|
||||
*/
|
||||
|
||||
#waterfall-details {
|
||||
-moz-padding-start: 8px;
|
||||
-moz-padding-end: 8px;
|
||||
padding-top: 2vh;
|
||||
overflow: auto;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
#waterfall-details > * {
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.marker-details-bullet {
|
||||
@@ -483,6 +505,23 @@
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.marker-details-labelname {
|
||||
-moz-padding-end: 4px;
|
||||
}
|
||||
|
||||
.marker-details-type {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.marker-details-duration {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker colors
|
||||
*/
|
||||
|
||||
menuitem.marker-color-graphs-purple:before,
|
||||
.marker-color-graphs-purple {
|
||||
background-color: var(--theme-graphs-purple);
|
||||
@@ -508,53 +547,6 @@ menuitem.marker-color-graphs-blue:before,
|
||||
background-color: var(--theme-graphs-blue);
|
||||
}
|
||||
|
||||
#waterfall-details > * {
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.marker-details-labelname {
|
||||
-moz-padding-end: 4px;
|
||||
}
|
||||
|
||||
.marker-details-type {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.marker-details-duration {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Recording items */
|
||||
|
||||
.recording-item {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.recording-item-title {
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
.recording-item-footer {
|
||||
padding-top: 4px;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.recording-item-save {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.recording-item-duration,
|
||||
.recording-item-save {
|
||||
color: var(--theme-body-color-alt);
|
||||
}
|
||||
|
||||
#recordings-list .selected label {
|
||||
/* Text inside a selected item should not be custom colored. */
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* JIT View
|
||||
*/
|
||||
@@ -665,7 +657,7 @@ menuitem.marker-color-graphs-blue:before,
|
||||
background-position: -16px -16px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#jit-optimizations-view .opt-icon::before {
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole@2x.png);
|
||||
}
|
||||
@@ -674,8 +666,8 @@ menuitem.marker-color-graphs-blue:before,
|
||||
/**
|
||||
* Configurable Options
|
||||
*
|
||||
* Elements can be tagged with a class and visibility is controlled via a preference being
|
||||
* applied or removed.
|
||||
* Elements can be tagged with a class and visibility is controlled via a
|
||||
* preference being applied or removed.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -190,7 +190,7 @@
|
||||
background-image: url(newtab.png);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.theme-dark #profile-newtab-button {
|
||||
background-image: url(newtab-inverted@2x.png);
|
||||
}
|
||||
@@ -367,7 +367,7 @@
|
||||
background-image: url(magnifying-glass-light.png);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.theme-dark .call-tree-zoom {
|
||||
background-image: url(magnifying-glass@2x.png);
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
list-style-image: url("chrome://global/skin/devtools/responsiveui-rotate.png");
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.devtools-responsiveui-close {
|
||||
list-style-image: url("chrome://global/skin/devtools/close@2x.png");
|
||||
}
|
||||
@@ -174,7 +174,7 @@
|
||||
-moz-image-region: rect(0px,32px,16px,16px);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.devtools-responsiveui-touch {
|
||||
list-style-image: url("chrome://global/skin/devtools/responsiveui-touch@2x.png");
|
||||
-moz-image-region: rect(0px,32px,32px,0px);
|
||||
@@ -189,7 +189,7 @@
|
||||
list-style-image: url("chrome://global/skin/devtools/responsiveui-screenshot.png");
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.devtools-responsiveui-screenshot {
|
||||
list-style-image: url("chrome://global/skin/devtools/responsiveui-screenshot@2x.png");
|
||||
}
|
||||
@@ -321,7 +321,7 @@
|
||||
border-bottom-left-radius: 12px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.devtools-responsiveui-resizebarV {
|
||||
background-image: url("chrome://global/skin/devtools/responsive-vertical-resizer@2x.png");
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
}
|
||||
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.ruleview-warning {
|
||||
background-image: url(alerticon-warning@2x.png);
|
||||
}
|
||||
@@ -194,7 +194,7 @@
|
||||
background-size: 1em;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.ruleview-bezierswatch {
|
||||
background: url("chrome://global/skin/devtools/cubic-bezier-swatch@2x.png");
|
||||
background-size: 1em;
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.side-menu-widget-item-checkbox .checkbox-check {
|
||||
background-image: url(itemToggle@2x.png);
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.stylesheet-enabled {
|
||||
background-image: url(itemToggle@2x.png);
|
||||
}
|
||||
|
||||
@@ -317,7 +317,7 @@
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.devtools-button::before {
|
||||
background-size: 32px;
|
||||
}
|
||||
@@ -438,7 +438,7 @@
|
||||
-moz-image-region: rect(0, 32px, 16px, 16px);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.theme-dark .devtools-searchinput {
|
||||
background-image: url(magnifying-glass@2x.png);
|
||||
}
|
||||
@@ -763,10 +763,10 @@
|
||||
}
|
||||
|
||||
#command-button-rulers > image {
|
||||
background-image: url("chrome://browser/skin/devtools/command-rulers.png");
|
||||
background-image: url("chrome://global/skin/devtools/command-rulers.png");
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#command-button-paintflashing > image {
|
||||
background-image: url("chrome://global/skin/devtools/command-paintflashing@2x.png");
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ text {
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#inspector-pane-toggle {
|
||||
list-style-image: url(debugger-collapse@2x.png);
|
||||
-moz-image-region: rect(0px,32px,32px,0px);
|
||||
|
||||
@@ -52,7 +52,7 @@ a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.message > .icon::before {
|
||||
background-image: url(chrome://global/skin/devtools/webconsole@2x.png);
|
||||
}
|
||||
@@ -361,7 +361,7 @@ a {
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.jsterm-input-node {
|
||||
background-image: -moz-image-rect(url('chrome://global/skin/devtools/commandline-icon@2x.png'), 0, 64, 32, 32);
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.scrollbutton-up > .toolbarbutton-icon,
|
||||
.scrollbutton-down > .toolbarbutton-icon {
|
||||
background-image: url("breadcrumbs-scrollbutton@2x.png");
|
||||
@@ -641,7 +641,7 @@
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.variable-or-property-non-writable-icon {
|
||||
background-image: url("chrome://global/skin/devtools/vview-lock@2x.png");
|
||||
}
|
||||
@@ -741,7 +741,7 @@
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.variables-view-delete {
|
||||
background-image: url("chrome://global/skin/devtools/vview-delete@2x.png");
|
||||
}
|
||||
@@ -767,7 +767,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.variables-view-edit {
|
||||
background-image: url("chrome://global/skin/devtools/vview-edit@2x.png");
|
||||
}
|
||||
@@ -793,7 +793,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.variables-view-open-inspector {
|
||||
background-image: url("chrome://global/skin/devtools/vview-open-inspector@2x.png");
|
||||
}
|
||||
@@ -1386,7 +1386,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.tree-widget-item:before {
|
||||
background-image: url("chrome://global/skin/devtools/controls@2x.png");
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
#include "nsIDOMDataChannel.h"
|
||||
#endif
|
||||
#include "nsIDOMDataTransfer.h"
|
||||
#include "nsIDOMDeviceStorage.h"
|
||||
#include "nsIDOMDOMCursor.h"
|
||||
#include "nsIDOMDOMException.h"
|
||||
#include "nsIDOMDOMRequest.h"
|
||||
@@ -353,7 +352,6 @@ const ComponentsInterfaceShimEntry kComponentsInterfaceShimMap[] =
|
||||
#endif
|
||||
DEFINE_SHIM(DataContainerEvent),
|
||||
DEFINE_SHIM(DataTransfer),
|
||||
DEFINE_SHIM(DeviceStorage),
|
||||
DEFINE_SHIM(DOMCursor),
|
||||
DEFINE_SHIM(DOMException),
|
||||
DEFINE_SHIM(DOMRequest),
|
||||
|
||||
Reference in New Issue
Block a user